using the dispatcher to load usercontrols in WPF - c#

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...

Related

WPF Window lag when moving mouse

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.

What are the implications of creating WPF controls on background thread?

So, lets say I have STA thread running on background and I create a user control there.
How functional it is going to be? What are the limitations?
_workingThread = new Thread(() =>
{
//so far so good
var myControl = new MyCustomControl();
//what happens if i set DataContext? Will databinding work?
//It looks like it does, but I am not entirely sure.
myControl.DataContext = new MyViewModel();
//if databinding works, can I assume that at this point
//myControl's properties are already updated?
//what happens exactly if I invoke a delgate using Dispatcher property?
myControl.Dispatcher.Invoke(SomeMethod);
//or current dispatcher?
Dispatcher.CurrentDispatcher.BeginInvoke(SomeOtherMethod);
});
_workingThread.SetApartmentState(ApartmentState.STA);
_workingThread.Start();
To answer the question why: there is a component in .Net called XpsDocument which allows you to write visuals into xps file. I don't see a reason, why I should do it on UI thread.
Here is example of WPF app, which creates Window in new STA Thread. I don't see any problem with it. I printed out some things: Thread name, ThreadId and Counter (changes via INotifyPropertyChanged). Also I change stackPanelCounter's background from timer Dispatcher.BeginInvoke.
XAML:
<Window x:Class="WpfWindowInAnotherThread.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"
mc:Ignorable="d"
SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen" Title="WPF: Windows and Threads">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Vertical">
<TextBlock Text="{Binding ThreadId, StringFormat='ThreadId: {0}'}" />
<TextBlock Text="{Binding ThreadName, StringFormat='ThreadName: {0}'}" />
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal" Name="stackPanelCounter">
<TextBlock Text="Counter: " />
<TextBlock Text="{Binding Counter}" />
</StackPanel>
<StackPanel Grid.Row="2">
<Button Name="btnStartInNewThread" Content="Start window in new Thread"
Click="btnStartInNewThread_Click"/>
<Button Name="btnStartTheSameThread"
Content="Start window in the same Thread"
Click="btnStartTheSameThread_Click" />
</StackPanel>
</Grid>
</Window>
Code:
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
namespace WpfWindowInAnotherThread
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
static int _threadNumber = 0;
readonly Timer _timer;
int _Counter;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public int ThreadId
{
get { return Thread.CurrentThread.ManagedThreadId; }
}
public string ThreadName
{
get { return Thread.CurrentThread.Name; }
}
public int Counter
{
get { return _Counter; }
set { _Counter = value; PropertyChanged(this, new PropertyChangedEventArgs("Counter")); }
}
public MainWindow()
{
DataContext = this;
_timer = new Timer((o) => {
Counter++;
MainWindow wnd = o as MainWindow;
wnd.Dispatcher.BeginInvoke(new Action<MainWindow>(ChangeStackPanelBackground), wnd);
}, this, 0, 200);
InitializeComponent();
}
private void btnStartTheSameThread_Click(object sender, RoutedEventArgs e)
{
MainWindow mainWnd = new MainWindow();
mainWnd.Show();
}
private void btnStartInNewThread_Click(object sender, RoutedEventArgs e)
{
Thread thread = new Thread(new ThreadStart(ThreadMethod));
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
}
private static void ThreadMethod()
{
Thread.CurrentThread.Name = "MainWindowThread# " + _threadNumber.ToString();
Interlocked.Increment(ref _threadNumber);
MainWindow mainWnd = new MainWindow();
mainWnd.Show();
Dispatcher.Run();
}
private static void ChangeStackPanelBackground(MainWindow wnd)
{
Random rnd = new Random(Environment.TickCount);
byte[] rgb = new byte[3];
rnd.NextBytes(rgb);
wnd.stackPanelCounter.Background = new SolidColorBrush(Color.FromArgb(0xFF, rgb[0], rgb[1], rgb[2]));
}
}
}
I spent some time testing things out, and I think Clemens's comment was accurate. Key points are:
myControl.Dispatcher and Dispatcher.CurrentDispatcher are one and the same, both hold a reference to dispatcher of background thread. No surprises here.
In general controls will not behave correctly without dispatcher running, because Dispatcher.BeginInvoke calls will not be processed. You have two options. Either call Dispatcher.Run() on background thread and create your controls using invokes:
_backgroundDispatcher.BeginInvoke(new Action(() =>
{
var myControl = new MyCustomControl();
//do stuff
}));
or manually push dispatcher frame every time you want to process dispatcher queue and "refresh" your control. When it comes to building XPS pages, both approaches are viable.
Data bindings do work, even when control is created on background thread. However in some cases they are not applied instantly and you might have to wait for dispatcher to process it's queue.

How to show cursor immediately on app startup?

My app takes a bit to start because the UI is pretty heavy. I want to show Cursors.AppWaiting cursor the moment the user double-clicks on the shortcut. So I pop Cursor.Current = Cursors.AppStarting; into the constructor of the main form. However, when I start the app, the cursor does not change until after the form is loaded.
Is there a way to change the cursor immediately after the user double-clicks the shortcut?
Well, you can do the work using a Background Worker and UseWaitCursor property to change cursor.
var bw = new BackgroundWorker();
UseWaitCursor = true;
bw.DoWork += (s, e) =>
{
//do work..
};
bw.RunWorkerCompleted += (s, e) =>
{
Invoke((Action)(() => UseWaitCursor = false));
};
bw.RunWorkerAsync();
Try adding
Application.DoEvents();
After changing the cursor.
Just collapse your MainGrid until the app is loaded
Converter Reference
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
XAML ElementReference
Visibility="{Binding Path=IsLoaded, Converter={StaticResource BooleanToVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"

Adding images to StackPanel in WPF

I have a WPF-application that is looking for new images in a database and if something comes up, it adds the image into a list. When that event is raised I want it to add the image into a StackPanel.
First I tried just to insert the image, but got an InvalidOperationException saying "The calling thread must be STA, because many UI components require this." and came up with:
public void Instance_GraphicChanged(object sender, PropertyChangedEventArgs e)
{
foreach (Model.Graphic item in Model.IncomingCall.Instance.Graphics)
{
if(!_strings.Contains(item.ImageId.ToString()))
{
Thread thread = new Thread( new ThreadStart(
delegate()
{
//sp_images StackPanel for Images
sp_images.Dispatcher.Invoke(
DispatcherPriority.Normal, new Action(
delegate()
{
Image img = new Image();
img.Source = item.ImageObj; //ImageObj returns a BitmapImage
sp_images.Children.Add(img);
}
));
}
));
_strings.Add(item.ImageId.ToString());
}
}
}
This does not throw any kind of exception, but actually nothing happens...
In reference to my comment, you could try something like this:
XAML
<!-- ... Other XAML Code ... -->
<ItemsControl x:Name="sp_images">
<ItemsControl.ItemsPanel>
<StackPanel Orientation="Horizontal" />
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Code Behind
private readonly HashSet<string> mImageIds = new HashSet<string>();
private readonly ObservableCollection<BitmapImage> mImages = new ObservableCollection<BitmapImage>();
// ... Inside the constructor
{
InitializeComponent();
sp_images.ItemsSource = mImages;
}
public void Instance_GraphicChanged(object sender, PropertyChangedEventArgs e)
{
foreach (Model.Graphic item in Model.IncomingCall.Instance.Graphics)
{
// Have we already seen the image
if (mImageIds.Add(item.ImageId.ToString()))
{
// We've not seen the image yet, so add it to the collection
// Note: We must invoke this on the Dispatcher thread.
this.Dispatcher.BeginInvoke((Action)delegate()
{
mImages.Add(item.ImageObj);
});
}
}
}
This should bypass any cross-thread exceptions you might have had before. It should also allow you to easily add new images to the ObservableCollection, which will automatically update the UI with images. Also, the use of the ItemTemplate means you don't have to actually build the UI every time yourself; WPF will handle this for you.
See here for more information on using the ObservableCollection. Also, refer to this StackOverflow question for an explanation on the container templating.

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