WPF Window lag when moving mouse - c#

Can anyone shed some light on why a WPF window lags when you move your mouse quickly over it? Is there any way around this? A change to the window render settings perhaps?
The following code runs a smooth animation of the rectangle, until you start moving your mouse over the window where the framerate will drop drastically (confirmed using the Application Profiler in VS). Also happens in a Release version with no debugger.
I've read Why does the Dispatcher Timer in WPF lag when i hover over my Application Window? which suggests using a different timer to update the underlying data. I've tried System.Threading.Timer, System.Timers.Timer and System.Windows.Threading.DispatcherTimer, along with creating a new thread to update the value in a loop with a Thread.Sleep. All of these provide the same result, so I don't think it actually has anything to do with the timers per se.
CodeBehind:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private int _value;
public int Value { get => _value; set { _value = value; RaisePropertyChangedEvent(); } }
public event PropertyChangedEventHandler PropertyChanged;
public Timer Timer { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
Timer = new Timer((o) =>
{
Value = Value > 100 ? -100 : Value + 1;
}, null, 0, 10);
}
protected void RaisePropertyChangedEvent([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
<Window x:Class="MouseLagTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MouseLagTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Rectangle Width="50" Height="50" Fill="Red">
<Rectangle.RenderTransform>
<TranslateTransform X="{Binding Value}"/>
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
EDIT
This issue seems to occur when built against .Net Framework 4.X and .Net Core. If built using Framework 3.X it runs super smooth irrespective of mouse movements. I'd prefer a solution as using 3.X isn't an option.

So it seems that when you call PropertyChanged from a background thread, the WPF binding automatically marshals the update onto the GUI thread for the update.
WPF Databinding thread safety?
For some reason, I think this is where the lag occurs when quickly moving the mouse, probably due to event priority in the Dispatcher queue.
If you manually marshall the update onto the GUI thread it fixes the issue.
Ie for a specific property update:
Timer = new Timer((o) =>
{
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
Value = Increment(Value);
}));
}, null, 0, 10);
Or in general:
protected void RaisePropertyChangedEvent(String propertyName = "")
{
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}));
}
Performing either of the above will give smooth updates irrespective of mouse movements. If using a timer, you could also just use a DispatcherTimer so the call is performed at the top of the Dispatcher loop on the main thread.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatchertimer?view=windowsdesktop-5.0
Happy to hear some advice if there is a better way to achieve this, or any caveats involved.

Related

WPF using Helix Tool, PropertyChangedEventHandler is always null while trying to update ModelVisual3D view

I am new to WPF and I am having issues trying to update the main window UI after a change.
I have the following MainWindow.xaml (I will show a part of it because it is large)
<Window
x:Class="SimpleDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:HelixToolkit="clr-namespace:HelixToolkit.Wpf;assembly=HelixToolkit.Wpf"
xmlns:local="clr-namespace:SimpleDemo"
Title="Plot 3D data"
Width="680"
Height="680" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
Next important part is the 3D plot section of the MainWindow, where I Bind the Content of the plot with my Model3D object named as Model.
<!-- Remember to add light to the scene -->
<HelixToolkit:SunLight />
<!-- The content of this visual is defined in MainViewModel.cs -->
<ModelVisual3D Content="{Binding Path = Model, Mode=OneWayToSource,NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}" />
<!-- You can also add elements here in the xaml -->
<HelixToolkit:GridLinesVisual3D
Width="800"
Length="800"
MajorDistance="15"
MinorDistance="15"
Thickness="0.05" />
</HelixToolkit:HelixViewport3D>
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal">
<Button Margin="5" Click="Backward_Click">Backward</Button>
<Button Margin="5" Click="Forward_Click">Forward</Button>
</StackPanel>
The idea of the program is to read the first 500 data points (x,y,z) from a txt file and then creating a AddRectangularMesh() from those using Helix Toolkit. I do this for multiple set of points and I finally have a mesh with multiple AddRectangularMesh() objects. After that, I display the final result to the HelixViewport3D.
In the UI I have two buttons Forward and Backward. The forward button is trying to draw the next 500 points from the txt file.
In my MainWindow.xaml.cs file I call the Event handlers of the buttons which calls the Functions in MainViewModel.cs file where all the functions for that data points are calculated and the final result will be saved on Model3D Model object, and then must be drawn on the UI.
Button calls the Forward_click event and Increases the counter to read the next data points
<Button Margin="5" Click="Forward_Click">Forward</Button>
After I press the button the Event Handlers is called but it is null, so the UI is never update and I can't show the next data points.
In my code I have implemented the Event handler, to listen on the changes of the Model, so after the execution of the functions the model object will be updated and then call the handler to update the UI.
The MainViewModel Class is defined as:
public class MainViewModel : Window, INotifyPropertyChanged
and the event handlers as below:
private Model3D model;
public Model3D Model
{
get { return model; }
set
{
model = value;
OnPropertyChanged(nameof(Model));
}
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, e);
}
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
I put breakpoints on the code, and when I press the Button, the handler is called but is null and then nothing is updated or done, the function calculates the new Model3D object with new points, but does not draw the new result.
If I run the same function on public MainViewModel() section the first 500 points are shown, but it due to the first run of the window.
This is how the Model object is updated after each button press.
public void CoreCalculations()
{
for (int i = 0 ; i < 500; i++)
{
//read File Line
// --- Do something with the points ---
// create a mesh using the points and AddRectangularMesh
}
MeshGeometry3D mesh = meshBuilder.ToMesh(true);
Material blueMaterial = MaterialHelper.CreateMaterial(Colors.Blue);
modelGroup.Children.Add(new GeometryModel3D { Geometry = mesh, Transform = new TranslateTransform3D(2, 0, 0), Material = blueMaterial, BackMaterial = blueMaterial });
Model = modelGroup;
}
Try changing OneWayToSource to OneWay. From the documentation
Updates the source property when the target property changes.
And you probably want to do it the other way around. Update the target whenever the model changes.
It is a bit odd that MainViewModel inherits from Window. I would suggest taking a look at the helix3D "SimpleDemo". Just to confirm that changing the model works, Make the mainViewModel inherit INotifyPropertyChanged, and add the following code:
private async Task RemoveModel()
{
await Task.Delay(5000);
Model = null;
OnPropertyChanged(nameof(Model));
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
And call RemoveModel in the constructor. That should make the model be removed after 5s. Note that the example code is suitable for debugging, and nothing else.
You should also be able to use the live visual tree explorer to help debug the UI.

Drawing image into a Window and update it

I have an unsafe class that generate a Bitmap which is converted to ToImageSource in order to draw into a Window. The Bitmap itself contains a sinusoidal text which is frequently updated and I want to it "move" from the left to the right (marquee style?). Anyway it works just fine in a WinForm but I'm stuck with the WPF Window.
Here are some code samples:
public AboutWindow()
{
InheritanceBehavior = InheritanceBehavior.SkipAllNow;
InitializeComponent();
Initialize();
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
drawingContext.DrawImage(bitmapRender.WindowBitmap, drawingArea);
if (!worker.IsBusy)
worker.RunWorkerAsync(); // BackgroundWorker in charge of updating the bitmap
}
void DispatcherTimerRender_Tick(object sender, EventArgs e) => InvalidateVisual();
My issues are: there is nothing displayed on the Window and the DispatchedTimer that calls InvalidateVisual() leads to this exception:
System.InvalidOperationException: 'Cannot use a DependencyObject that belongs to a different thread than its parent Freezable.'
I have looked at other threads and I'm aware that WPF is a retained drawing system but I would love to achieve it anyway.
Any suggestion about the "best" way to achieve this?
Any useful explanation/link would be very much appreciated.
[Edit]
<Window x:Class="CustomerManagement.View.AboutWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" Height="450" WindowStartupLocation="CenterScreen" Width="800" ResizeMode="NoResize" AllowsTransparency="True" WindowStyle="None">
<Grid KeyDown="Grid_KeyDown">
<Image Width="800" Height="450" Source="{Binding 'Image'}" />
</Grid>
</Window>
You should use an Image element that has its Source property bound to an ImageSource property in a view model. This is the "standard" way, based on the MVVM architectural pattern, and therefore the "best" way - in my opinion.
<Image Source="{Binding Image}"/>
The view model could look like this:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ImageSource image;
public ImageSource Image
{
get { return image; }
set
{
image = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Image)));
}
}
}
and an instance of it would be assigned to the DataContext of the window:
public AboutWindow()
{
InitializeComponent();
var vm = new ViewModel();
DataContext = vm;
}
For testing it, the code below performs a slide show of image files in a directory. You may as well assign any other ImageSource - e.g. a DrawingImage - to the Image property.
var imageFiles = Directory.GetFiles(..., "*.jpg");
var index = -1;
var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
timer.Tick += (s, e) =>
{
if (++index < imageFiles.Length)
{
vm.Image = new BitmapImage(new Uri(imageFiles[index]));
}
else
{
timer.Stop();
}
};
timer.Start();

Problems with binding values

I'm having a problem with binding values to controls. I checked in other windows I have and I think I'm doing it the same way.
I want to show something like loading window from App before main window will open. The problem is nothing is changing (text/content/value of controls).
InitizalizationWindow:
public InitializationWindow()
{
...
InitializationWindowClass.Progress = new InitializationWindowClass();
this.mainSP.DataContext = InitializationWindowClass.Progress;
}
and part of xaml:
<StackPanel Name="mainSP" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="0, 0, 0, 10">
<TextBlock x:Name="tblProgress" FontSize="14" Text="{Binding ProgressText}" TextAlignment="Center" TextWrapping="Wrap" />
<Grid>
<telerik:RadProgressBar x:Name="progress" Value="{Binding ProgressValue}" telerik:StyleManager.Theme="Summer" Height="25" IsIndeterminate="False" />
<Label x:Name="lblPercent" FontWeight="Bold" Content="{Binding ProgressValueString}" HorizontalAlignment="Center" VerticalContentAlignment="Center" />
</Grid>
</StackPanel>
InitializationWindowClass:
public class InitializationWindowClass : INotifyPropertyChanged
{
public static InitializationWindowClass Progress { get; set; }
private string progressText = String.Empty, progressValueString = String.Empty;
private int progressValue = 0;
public event PropertyChangedEventHandler PropertyChanged;
public string ProgressText
{
get
{
return progressText;
}
set
{
progressText = value;
NotifyPropertyChanged("ProgressText");
}
}
public string ProgressValueString
{
get
{
return progressValueString;
}
set
{
progressValueString = value;
NotifyPropertyChanged("ProgressValueString");
}
}
public int ProgressValue
{
get
{
return progressValue;
}
set
{
progressValue = value;
ProgressValueString = String.Format("{0}%", progressValue);
NotifyPropertyChanged("ProgressValue");
NotifyPropertyChanged("ProgressValueString");
}
}
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
and part of App.xaml.cs:
protected override void OnStartup(StartupEventArgs e)
{
InitializationWindow iw = new InitializationWindow();
iw.Show();
InitializationWindowClass.Progress.ProgressValue = Convert.ToInt32(((decimal)count / (decimal)sum) * 100);
InitializationWindowClass.Progress.ProgressText = "Some text";
...
...
InitializationWindowClass.Progress.ProgressValue = Convert.ToInt32(((decimal)count / (decimal)sum) * 100);
InitializationWindowClass.Progress.ProgressText = "New text";
...
...
}
I checked and when I'm changing i.e. ProgressValue from App.xaml.cs, the value is changing - going to
get
{
return progressValue;
}
So the question is - what I'm doing wrong?
This is a threading issue. Binding changes are posted to the Dispatcher, and then the UI thread pulls those events out of the Dispatcher queue and processes them.
Here you are making changes to the bindings in the OnStartup event, which itself is being handled by the UI thread. This means that those binding changes won't be handled until the OnStartup event is exited. What you probably end-up seeing in your user interface is just the values of those progress properties when they were last changed.
What you need to do is move the progress reporting to another thread. In other words, shift the work currently done in the OnStartup event to a different thread, using async/await or some other threading mechanism.
Original, incorrect answer follows:
ProgressText, ProgressValueString and ProgressValue are all properties on InitializationWindowClass, not InitializationWindow. It looks like you need to either set the DataContext on the StackPanel to point at the Progress property of the InitializationWindow, or include the Progress property as part of the binding path for the individual elements.
That is, either this:
<StackPanel ... DataContext="{Binding Progress}">
... rest the same ...
</StackPanel>
or this:
<StackPanel ...>
<TextBlock ... Text="{Binding Progress.ProgressText}" ... />
<Grid>
<telerik:RadProgressBar ... Value="{Binding Progress.ProgressValue}" ... />
<Label ... Content="{Binding Progress.ProgressValueString}" ... />
</Grid>
</StackPanel>
What you are doing won't work. You are displaying your splash screen and performing load operations on the same thread. This way, while your loading operations run, the main UI thread is busy and is not updating, even if the data in the background are changing properly. That's why you are probably seing "wait" cursor, your splash window is unresponsive, and your main window may not even appear at all.
SplashScreen should be displayied on different thread
You have to have a way of communicating with that thread, because:
you can't directly update UI from one thread using another thread
Here you have a nice example of working SplashScreen in wpf

using the dispatcher to load usercontrols in WPF

I have a user control that has a grid with like 2900 items in it, there is nothing I can do about this cause that is the way the business want's it... Obviously this is slow to load/render so I created this trick using the dispatcher, in the view model that handles the event (prism event... not a standard windows event).
public void ShowPopUp(Type viewType)
{
var waitScreen = new Controls.Views.SampleView();
var popUp = new ShellBlank();
popUp.Content = waitScreen;
popUp.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;
popUp.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background, new Action(delegate() {
popUp.Content = container.Resolve(viewType);})
);
popUp.ShowDialog();
}
It works just fine however on my SampleView (as it is called at the moment) there is an in-determinant progress bar however it never updates, like you know - the green bara going back and fourth... Here is the XAML for it.
<Border>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Margin="12" FontSize="16" Foreground="WhiteSmoke" Content="Loading... Please wait"/>
<ProgressBar Grid.Row="1" IsIndeterminate="True" Width="280" Height="24"/>
</Grid>
</Border>
Is it something to do with the dispatcher not letting it update?
Anyone ever done something like this? got any suggestions?
Thanks!
My guess is that the Dispatcher thread is busy trying to render your control and hasn't been able to update the ProgressBar.
Is the popup window responsive? Try moving the window or adding a button and seeing if you can click on the button. This might help identify whether its a simple problem with the progress bar or the Dispatcher being too busy.
Actually I ended up doing was calling container.resolve on the view, I called called container.resolve on the view model, this could be done with a standard background worker - then in the RunWorkerCompleted with happens bank on the main thread I create the view passing in the view model that we happily waited for in the background and switch out the wait screen which of course wasn't impacted by our long running process. Here is the code.
AddVendorModalityViewModel viewModel;
var waitScreen = new Controls.Views.SampleView();
var popUp = new ShellBlank();
popUp.Content = waitScreen;
popUp.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;
var bw = new BackgroundWorker() { WorkerReportsProgress = true, WorkerSupportsCancellation = true };
bw.DoWork += (s, e) =>
{
viewModel = container.Resolve<AddVendorModalityViewModel>();
e.Result = viewModel;
};
bw.RunWorkerCompleted += (s, e) =>
{
viewModel = (AddVendorModalityViewModel)e.Result;
AddVendorModalityView view = new AddVendorModalityView(viewModel);
popUp.Content = view;
};
bw.RunWorkerAsync();
popUp.ShowDialog();
Hope this is useful for someone... The general idea is create the ViewModel on a different thread cause it is the thing that will take forever to load etc...

Interacting with two UI threads using different windows

My application is using an image processing library to handle a long running task. The main UI with settings and controls is implemented in WPF. The image processing needs to be displayed and the main UI needs to remain responsive. Upon clicking the 'process' button in the main UI a new thread is spawned which creates a new WinForm window to display the processed images in.
Before it was multithreaded the UI would hang while processing and the progress would be visible in the WinForm for displaying the images. Then when the processing would complete the WinForm would remain with the image in it. Events are added to the new WinForm that allow panning and zooming. The panning and zooming functionality worked correctly.
It became obvious due to the requirements of the project that it would need to be multithreaded to function properly.
Now with the new thread the WinForm window is created as before and the image is processed and displayed. The problem is that when this method is completed the thread exits. Having the thread exit means that if the allocated image buffers are not freed then the application throws an exception. To fix this there is a method called to free all allocations before the thread exits. This fixes the exception and makes the entire thread execute successfully but it means that the image display buffer and form to display it in are freed/disposed of and so there is not time available for the zooming and panning events.
The best solution to make the Thread not exit was to make an AutoResetEvent and have something like this at the end of the image processing thread.
while (!resetEvent.WaitOne(0, false)) { }
threadKill(); // frees all allocations
The AutoResetEvent is fired by the by a button on the main UI that kills the thread. This works to have the image display as long as needed and killed explicitly by the user, however it fails to allow the firing of Click and Drag events needed to make the image pan and zoom. Is there a way to make the thread not exit without having a spinning while loop which prevents the events from being fired? The desired functionality is to have the thread remain alive so that the allocations do not have to be freed and the panning and zooming can be implemented.
Even though the solution may be obvious to someone with more experience threading, any help would be appreciated as I am new to multithreaded applications.
Thanks
EDIT: It should be known that the end goal is to display a constant stream of frames which are processed in this way taken from a frame grabber. So I don't think that it will work to process them separately in the background and then display them in the main UI, because there is going to need to be a constant stream of displays and this would lock up the main UI.
EDIT: The real intent of the question is not to find a better way to do something similar. Instead I am asking if the new thread can be stopped from exiting so that the click events can fire. If this behavior cannot be achieved with System.Threading.Thread then saying it cannot be achieved would also be an accepted answer.
If you can use the new parallel classes and collections in C# 4.0 this is a pretty easy task. Using a BlockingCollection<T> you can add images from any thread to the collection and have a background consumer taking images off this collection and process them. This background processing can be easily created and managed (or canceled) using a Task from the TaskFactory. Check out this simple WPF application for loading images and converting them to black and white as long as there are images to process without blocking the UI. It doesn't use two windows but I think it demonstrates the concepts:
using System;
using System.Collections.Concurrent;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Microsoft.Win32;
namespace BackgroundProcessing
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private readonly BlockingCollection<BitmapImage> _blockingCollection = new BlockingCollection<BitmapImage>();
private readonly CancellationTokenSource _tokenSource = new CancellationTokenSource();
private ImageSource _processedImage;
public MainWindow()
{
InitializeComponent();
CancellationToken cancelToken = _tokenSource.Token;
Task.Factory.StartNew(() => ProcessBitmaps(cancelToken), cancelToken);
PendingImages = new ObservableCollection<BitmapImage>();
DataContext = this;
}
public ObservableCollection<BitmapImage> PendingImages { get; private set; }
public ImageSource ProcessedImage
{
get { return _processedImage; }
set
{
_processedImage = value;
InvokePropertyChanged(new PropertyChangedEventArgs("ProcessedImage"));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private void ProcessBitmaps(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
BitmapImage image;
try
{
image = _blockingCollection.Take(token);
}
catch (OperationCanceledException)
{
return;
}
FormatConvertedBitmap grayBitmapSource = ConvertToGrayscale(image);
Dispatcher.BeginInvoke((Action) (() =>
{
ProcessedImage = grayBitmapSource;
PendingImages.Remove(image);
}));
Thread.Sleep(1000);
}
}
private static FormatConvertedBitmap ConvertToGrayscale(BitmapImage image)
{
var grayBitmapSource = new FormatConvertedBitmap();
grayBitmapSource.BeginInit();
grayBitmapSource.Source = image;
grayBitmapSource.DestinationFormat = PixelFormats.Gray32Float;
grayBitmapSource.EndInit();
grayBitmapSource.Freeze();
return grayBitmapSource;
}
protected override void OnClosed(EventArgs e)
{
_tokenSource.Cancel();
base.OnClosed(e);
}
private void BrowseForFile(object sender, RoutedEventArgs e)
{
var dialog = new OpenFileDialog
{
InitialDirectory = "c:\\",
Filter = "Image Files(*.jpg; *.jpeg; *.gif; *.bmp)|*.jpg; *.jpeg; *.gif; *.bmp",
Multiselect = true
};
if (!dialog.ShowDialog().GetValueOrDefault(false)) return;
foreach (string name in dialog.FileNames)
{
CreateBitmapAndAddToProcessingCollection(name);
}
}
private void CreateBitmapAndAddToProcessingCollection(string name)
{
Dispatcher.BeginInvoke((Action)(() =>
{
var uri = new Uri(name);
var image = new BitmapImage(uri);
image.Freeze();
PendingImages.Add(image);
_blockingCollection.Add(image);
}), DispatcherPriority.Background);
}
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
}
}
This would be the XAML:
<Window x:Class="BackgroundProcessing.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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<Border Grid.Row="0" Grid.ColumnSpan="3" Background="#333">
<Button Content="Add Images" Width="100" Margin="5" HorizontalAlignment="Left" Click="BrowseForFile"/>
</Border>
<ScrollViewer VerticalScrollBarVisibility="Visible" Grid.Column="0" Grid.Row="1">
<ItemsControl ItemsSource="{Binding PendingImages}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<Border Grid.Column="1" Grid.Row="1" Background="#DDD">
<Image Source="{Binding ProcessedImage}"/>
</Border>
</Grid>
Use the background worker to process the image for pan and zooming, pass the data to the backgroundworker.RunCompleted Event. You can then display the new image in the main UI thread with no slow down or locking.

Categories

Resources