0

I am encountering a problem with an image in a WPF application not updating. It has been suggested that I, "put the method in the window's dispatcher dispatcher. Dispatch to the closest element as possible. And asynchronously..." But I cannot find any examples of how to do this.

How would I call a method, 'from the Window's dispatcher asynchronously'?

This is the code that is in a while loop and it is not updating until the while loop is finished:

writeableBitmap = new WriteableBitmap(CleanVegMap);
image.Source = writeableBitmap;
DrawDinos2d();

This is the XAML of the MainWindow:

<Window x:Class="DinosaurIsland.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Dinosaur Island" Height="600" Width="600" WindowState="Normal"  Icon="/DinosaurIsland;component/Icon1.ico" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" WindowStartupLocation="CenterOwner">
<Window.Resources>
    <ResourceDictionary>
        <DataTemplate DataType="{x:Type BitmapImage}">
            <Image Source="{Binding}" />
        </DataTemplate>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Resources.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Window.Resources>

<DockPanel>
    <Menu x:Name="MainMenu" DockPanel.Dock="Top">
        <MenuItem Header="_File">
            <MenuItem Header="_Open Dinosaur Island 'snapshot' file..." x:Name="OpenSnapshotFile" Click="OpenSnapshotFile_click" />
            <MenuItem Header="_Save"/>
            <MenuItem Header="_Exit" x:Name="ExitApp" Click="ExitAppClick" />
        </MenuItem>
        <MenuItem Header="_Height Map">
            <MenuItem Header="Load Height Map..." Name="LoadHeightMap" Click="LoadHeightMapClick" />
            <Separator />
            <MenuItem Header="Display Height Map" x:Name="DisplayHeightMap" Click="DisplayHeightMapClick" />
        </MenuItem>
        <MenuItem Header="Terrain">
            <MenuItem Header="Load Terrain Map..." x:Name="LoadTerrainMap" Click="LoadTerrainMap_Click" />
            <MenuItem Header="Draw Terrain..." x:Name="DrawTerrain" Click="DisplayTerrainPaintBoxClick" />
            <MenuItem Header="Save Terrain Map..." x:Name="SaveTerrainMap" Click="SaveTerrainMap_Click"/>
            <MenuItem Header="Get Terrain Data From BMP..." x:Name="TerrainFromBMP" Click="TerrainFromBMP_Click" />
            <Separator />
            <MenuItem Header="Adjust Terrain Transparency..." x:Name="AdjustTerrainTransparency"  Click="AdjustTerrainTransparency_Click"/>
            <MenuItem Header="Display Terrain Map" x:Name="DisplayTerrainMap"  Click="DisplayTerrainMap_Click"/>
        </MenuItem>

        <MenuItem Header="_Vegetation">
            <MenuItem Header="Plant Vegetation..." x:Name="PlantVegetation" Click="PlantVegetation_Click" />
            <Separator />
            <MenuItem Header="Load Vegetation Map..." x:Name="LoadVegetation" Click="LoadVegetation_Click" />
            <MenuItem Header="Save Vegetation Map..." x:Name="SaveVegetation" Click="SaveVegetation_Click" />
            <Separator />
            <MenuItem Header="Display Vegetation"  Click="DisplayVegetation_Click" />

        </MenuItem>

        <MenuItem Header="Dinosaurs">
            <MenuItem Header="Edit / Place Dinosaurs..." x:Name="EditDinosaurs" Click="EditDinosaurs_Click" />
            <Separator />
            <MenuItem Header="Load Dinosaur Map" Name="LoadDinosaurnMap" Click="LoadDinosaurs_Click"/>
            <MenuItem Header="Save Dinosaur Map" Name="SaveDinosaurMap" Click="SaveDinosaurs_Click"/>
        </MenuItem>

        <MenuItem Header="Time">
            <MenuItem Header="Start..." x:Name="AdvanceTime" Click="StartTime_Click" />
            <MenuItem Header="Stop..." x:Name="StopTime" Click="StopTime_Click" />
            <Separator />
            <MenuItem Header="Adjust Time Step..." x:Name="AdjustTimeStep"  Click="AdjustTimeStep_Click"/>
        </MenuItem>

        <MenuItem Header="Help">
            <MenuItem Header="About Dinosaur Island" Name="AboutDinosaurIsland" Click="AboutDinoIslandClick" />
        </MenuItem>
    </Menu>

    <StatusBar DockPanel.Dock="Bottom">
        <TextBlock Name="StatusBarField1">Location = X,Y</TextBlock>
        <Separator/>
        <TextBlock Name="StatusBarField2">Elevation = X</TextBlock>
        <Separator/>
        <TextBlock Name="StatusBarField3">Terrain = None</TextBlock>
        <Separator/>
        <TextBlock Name="StatusBarField4">Plants = None</TextBlock>
        <Separator/>
        <TextBlock Name="StatusBarField5">Dinosaurs = None</TextBlock>
        <Separator/>
        <TextBlock Name="StatusBarField6">Zoom</TextBlock>
        <Separator/>
        <TextBlock Name="StatusBarField7">Time 0:00</TextBlock>
    </StatusBar>

    <Label DockPanel.Dock="Bottom" Content="Scale = 2000 meters" Height="23" HorizontalAlignment="Center"  Name="HorizScaleDisplayText"  Width="127" />
    <Label DockPanel.Dock="Bottom" Content="└───────────────────────────────┴───────────────────────────────────┘" Height="20" HorizontalAlignment="Center"  Name="HorizScaleDisplayLine"  Width="423" />
    <Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

        <Slider Grid.Column="0" Orientation="Vertical" HorizontalAlignment="Left" Minimum="1" x:Name="slider"/>
    <ScrollViewer Name="scrollViewer" Grid.Column="1" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible" Margin="0,0,0,6">

        <Grid Name="grid" Width="400" Height="400" RenderTransformOrigin="0.5,0.5">
                <Grid.RowDefinitions>
                    <RowDefinition Height="37*" />
                    <RowDefinition Height="363*" />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="297*" />
                    <ColumnDefinition Width="103*" />
                </Grid.ColumnDefinitions>
                <Grid.LayoutTransform>
                <TransformGroup>
                    <ScaleTransform x:Name="scaleTransform"/>
                </TransformGroup>
            </Grid.LayoutTransform>
            <Viewbox x:Name="viewBox" Margin="-35,-12,-22,22" Grid.ColumnSpan="2" Grid.RowSpan="2">
                    <ContentPresenter x:Name="contentPresenter" Width="350" Height="350" >
                        <ContentPresenter.Content>
                            <Image x:Name="image" Width="350" Height="350">
                                <Image.Source >
                                    <BitmapImage x:Name="HeightMapImage" UriSource="DinoIslandLogo.bmp" />
                                </Image.Source>
                            </Image>
                        </ContentPresenter.Content>
                    </ContentPresenter>
                </Viewbox>
            </Grid>
        </ScrollViewer>
    </Grid>
</DockPanel>

For more details see WPF window image updating from menuitem but not when in while loop

Attempting to modify code to follow suggestion below I have:

  public MainWindow()
    {
        InitializeComponent();
        this.UpdateImage();
        .....



        private void UpdateImage()
    {
        writeableBitmap = new WriteableBitmap(CleanVegMap);
        image.Source = writeableBitmap;
        DrawDinos2d();
    }
Community
  • 1
  • 1
zetar
  • 1,225
  • 2
  • 20
  • 45

1 Answers1

2

This hacked together sample will explain the basics of responsive UI voodoo:

I guess your MenuItem does what is done in manualLockClick and your loop does uiLockClick. You need to be doing dispatchedClick.

Code behind:

//some usings...

namespace DispatcherSample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private int _counter = 0;

        public MainWindow()
        {
            InitializeComponent();
            this.UpdateBox();
        }

        private void manualLockClick(object sender, RoutedEventArgs e)
        {
            _counter++;
            this.UpdateBox();
        }

        // runs on the UI thread will lock all updates until done
        private void uiLockClick(object sender, RoutedEventArgs e)
        {
            for (int i = 0; i < 5; i++)
            {
                _counter++;
                this.UpdateBox();
                Thread.Sleep(1000);
            }
        }

        //runs on a background thread, dispatches to UI thread for updates of controls only
        private void dispatchedClick(object sender, RoutedEventArgs e)
        {
            Task.Factory.StartNew(() =>
                {
                    for (int i = 0; i < 5; i++)
                    {
                        _counter++;
                        this.Dispatcher.Invoke(new Action(() => this.UpdateBox()));
                        Thread.Sleep(1000);
                    }
                });
        }

        private void UpdateBox()
        {
            textBox.Text = _counter.ToString();
        }
    }
}

XAML:

<Window x:Class="DispatcherSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        Height="350"
        Width="525">
    <StackPanel Orientation="Vertical">
        <Button Content="Manual locking Increment"
                Click="manualLockClick" />
        <Button Content="UI locking countdown"
                Click="uiLockClick" />
        <Button Content="Dispatched background countdown"
                Click="dispatchedClick" />
        <TextBlock x:Name="textBox" />
    </StackPanel>
</Window>

Once you grasp this, read up on async/await when you can use C#4.5+ as this can make responsive UIs loads of fun to make.

And to do this proper you should lock some input (or better yet your logic behind it as well) while doing background work or the things will happen that you can see when you spam dispatchedClick.

UI work is single threaded by nature, you will want to be doing no work besides UI updates/input handling on the thread that comes with your MainWindow.

MrDosu
  • 3,427
  • 15
  • 18
  • The while loop is coming off Click="StartTime_Click" – zetar Jul 18 '13 at 22:32
  • So, in your example, my graphic calls would go inside UpdateBox(), right? (Obviously, different name, but that's what's getting called) – zetar Jul 18 '13 at 22:35
  • This line: this.UpdateImage(); (your this.UpdateBox) is throwing a XAMLParseException was undhandled error. "'The invocation of the constructor on type 'DinosaurIsland.MainWindow' that matches the specified binding constraints threw an exception." – zetar Jul 18 '13 at 23:05
  • Set a breakpoint at the start of MainWindows constructor, when the constructor fails it will give you a XamlParseException, that is not much use to the cause of the issue. – MrDosu Jul 18 '13 at 23:26
  • I'm getting a compile-time error: Error 1 'System.Windows.Threading.Dispatcher' does not contain a definition for 'InvokeAsync' and no extension method 'InvokeAsync' accepting a first argument of type 'System.Windows.Threading.Dispatcher'. I've added a reference to System.ServiceModel. What am I missing/ – zetar Jul 19 '13 at 10:41
  • I JUST READ (here: http://stackoverflow.com/questions/12390175/targeting-net-framework-4-5-via-visual-studio-2010) "You must use Visual Studio 2012 in order to utilize .NET 4.5." I am running VS 2010. – zetar Jul 19 '13 at 10:50
  • Sorry, the InvokeAsync was a suggestion from the other thread. – zetar Jul 19 '13 at 12:00
  • I'm still getting an XamlParseException on this line: this.UpdateBox(); and it's suggesting using a new, but I don't see how that's possible. – zetar Jul 19 '13 at 12:03
  • Specifically, the error is "'The invocation of the constructor on type 'DinosaurIsland.MainWindow' that matches the specified binding constraints threw an exception.' – zetar Jul 19 '13 at 12:04
  • Are you sure that the x:Name of the TextBox is textBox? Debug the constructor and see what exactly fails within UpdateBox – MrDosu Jul 19 '13 at 12:06
  • I'm not using TextBox. I need to update an image, not a text box so I'm modifying your code. Instead of UpdateBox, I have (see above). – zetar Jul 19 '13 at 12:27
  • I would suggest to play with/debug the code given as is until you understand what is actually happening. Shooting a shotgun filled with snippets of knowledge at your main solution will only lead to pain. – MrDosu Jul 19 '13 at 12:29
  • I appreciate the help, but the problem is that I'm getting a runtime error on the constructor for this.UpdateImage(); I can't slip into a debugger any lower than this. This is the 2nd line, right after InitalizeComponents(). – zetar Jul 19 '13 at 13:18
  • I know your pain, but this is something you need to just buckle up and power through. Every other help would just be someone taking your solution and doing it for you. – MrDosu Jul 19 '13 at 13:21
  • I can't put ANY constructor where you did without it throwing an error. Are you suggesting adding a class with a constructor? – zetar Jul 19 '13 at 13:36