I am writing a custom control in WPF. The control have several properties that cause update of the control's logical tree. There are several methods of this form:
private static void OnXXXPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((MyControl)obj).RebuildTree();
}
Suppose the RebuildTree() method is very complex and lenghty and if users changes several properties, this method is called several times causing application slowdown and hanging.
I would like to introduce something like BeginUpdate() and EndUpdate() methods in a Windows Forms fashion (to ensure the update is called just once), but this practice is widely disouraged in WPF.
I know the renderer have lower priority and flicker may not appear, but still why to spoil precious running time by calling the same update method multiple times?
Is there any official best practice on how to make efficient update of multiple dependency properties (without updating the entire control after setting each one)?
Just set a flag when any of these properties change, and have the refresh method queued to the Dispatcher only once.
private static void OnXXXPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((MyControl)obj).NeedsRefresh = true;
((MyControl)obj).OnNeedsRefresh();
}
void OnNeedsRefresh()
{
Dispatcher.BeginInvoke((Action)(() =>
{
if (NeedsRefresh)
{
NeedsRefresh = false;
RebuildTree();
}
}),DispatcherPriority.ContextIdle);
}
This way, all your properties will be updated and THEN the Dispatcher will call your BeginInvoke, set the flag to false and refresh only once.
Related
I use a browse for files dialog to allow a user to select multiple images. If a lot of images are selected, as expected it takes a bit. Below is an example of what I do with the selected images. I loop through the filepaths to images and create an instance of a user control, the user control has an Image control and a few other controls. I create the instance of this control then add it to a existing stackPanel created in the associating window xaml file. The example just below works fine, but I'm trying to understand BackGroundWorker better, I get the basics of how to set it up, with it's events, and pass back a value that could update a progress bar, but because my loop that takes up time below adds the usercontrol instance to an existing stackPanel, It won't work, being in a different thread. Is BackGroundWorker something that would work for an example like this? If so, what's the best way to update the ui (my stackpanel) that is outside the thread. I'm fairly new to wpf and have never used the BackGroundWorker besides testing having it just update progress with a int value, so I hope this question makes sense, if I'm way off target just let me know. Thanks for any thoughts.
Example of how I'm doing it now, which does work fine.
protected void myMethod(string[] fileNames) {
MyUserControl uc;
foreach (String imagePath in fileNames) {
uc = new MyUserControl();
uc.setImage(imagePath);
stackPanel.Children.Add(uc);
progressBar.Value = ++counter;
progressBar.Refresh();
}
}
below this class i have this so I can have the progressBar refresh:
public static class extensionRefresh {
private static Action EmptyDelegate = delegate() { };
public static void Refresh(this UIElement uiElement) {
uiElement.Dispatcher.Invoke(DispatcherPriority.Background, EmptyDelegate);
}
}
Check out this article on
Building more responsive apps with the Dispatcher
Now that you have a sense of how the Dispatcher works, you might be surprised to know that you will not find use for it in most cases. In Windows Forms 2.0, Microsoft introduced a class for non-UI thread handling to simplify the development model for user interface developers. This class is called the BackgroundWorker
In WPF, this model is extended with a DispatcherSynchronizationContext class. By using BackgroundWorker, the Dispatcher is being employed automatically to invoke cross-thread method calls. The good news is that since you are probably already familiar with this common pattern, you can continue using BackgroundWorker in your new WPF projects
Basically the approach is
BackgroundWorker _backgroundWorker = new BackgroundWorker();
// Set up the Background Worker Events
_backgroundWorker.DoWork += _backgroundWorker_DoWork;
_backgroundWorker.RunWorkerCompleted += _backgroundWorker_RunWorkerCompleted;
// Run the Background Worker
_backgroundWorker.RunWorkerAsync(5000);
// Worker Method
void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Do something
}
// Completed Method
void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Doing UI stuff
if (e.Cancelled)
{
statusText.Text = "Cancelled";
}
else if (e.Error != null)
{
statusText.Text = "Exception Thrown";
}
else
{
statusText.Text = "Completed";
}
}
Using a BackgroundWorker alone won't solve your issue since elements created during the DoWork portion will still have originated from a non-UI thread. You must call Freeze on any objects you intend to use on another thread. However only certain UI objects will be freezable. You may have to load in the images as BitmapImages on the background thread, then create the rest of your user control on the UI thread. This may still accomplish your goals, since loading in the image is probably the most heavyweight operation.
Just remember to set BitmapImage.CacheOption to OnLoad, so it actually loads up the image when you create the object rather than waiting until it needs to be displayed.
my issue is the following:
I have a windows form in which I've placed a LayoutPanel, when the forms Loads, multiple controls like: textboxes and labels are being added to the LayoutPanel.
Then on a button click, I need to process the data entered by the user on those dynamically created controls. For that purpouse I use a Backgroundworker which is supposed to take those controls and read their data.
My issue es that the Backgroundworker doesn't allows me to access the control from the DoWork Method, but I need to do it that way because I'll be reporting the progress of the operations.
Here are portions of my code to clarify the concept:
private void frmMyForm_Load(object sender, EventArgs e)
{
//I add multiple controls, this one is just for example
LayoutPanel1.add(TextBox1);
....
}
private void bgwBackground_DoWork(object sender, DoWorkEventArgs e)
{
foreach (Control controlOut in LayoutPanel1.Controls)
{
//do some stuff, this one is just for example
string myString = controlOut.Name; //-> Here is the error, cant access controls from different Thread.
}
}
Setting text is simple just using a delegate, but how about getting the entire parent control to manipulate the child controls (just for getting info, I don't want to set any data, just need to Get Name, Text, stuff like that).
Hope I made myself clear, thank you all.
You can only access Windows Forms controls from the GUI thread. If you want to manipulate them from another thread, you will need to use the Control.Invoke method to pass in a delegate to execute on the GUI thread. In your situation, you should be able to do this:
private void bgwBackground_DoWork(object sender, DoWorkEventArgs e)
{
foreach (Control controlOut in LayoutPanel1.Controls)
{
this.Invoke(new MethodInvoker(delegate {
// Execute the following code on the GUI thread.
string myString = controlOut.Name;
}));
}
}
I like to define an extension method that allows me to use the cleaner lambda syntax:
// Extension method.
internal static void Invoke(this Control control, Action action) {
control.Invoke(action);
}
// Code elsewhere.
this.Invoke(() => {
string myString = controlOut.Name;
});
As you are already aware, accessing control values from any thread other than the UI thread is a big no-no. I'd say one reasonable implementation is to use a .NET synchronization mechanism, such as a WaitHandle, to suspend your background thread while the UI thread updates a thread-safe data structure of your choice.
The idea is that your background thread notifies the UI thread (via the delegate mechanism you are already familiar with) that it needs information, then waits. When the UI is finished populating the shared variable with information, it resets the WaitHandle, and the background worker resumes.
Without writing out and testing all the code, let me give you a few resources:
WaitHandle.WaitOne documentation with example usage: http://msdn.microsoft.com/en-us/library/kzy257t0.aspx
My own favorite method of invoking an event on the UI thread: http://www.notesoncode.com/articles/2009/01/24/PowerfulExtensionMethodsPart1.aspx
is there a way I can get a Load event for System.Windows.Forms.Control just like System.Windows.Forms.Form.Load?
I want to run some initialize code before the control first shown.
Also, it would be nice to be able to do the same for System.Windows.Forms.ToolStripStatusLabel which is not actually a Control, but works like one.
Ideally, I can do this:
control.OnLoad(() => { dosomething here; });
in which OnLoad is a extension method that would run the argument Action when the "control" "Loads".
Thanks!
Form.Load event is called by the OnLoad method which is called from the OnCreateControl method which belongs to the Control class. So for the form the calling sequence would be following:
OnCreateControl start
OnLoad start
Form Load event call
OnLoad finish
OnCreateControl finish
I guess you can override OnCreateControl for your component and add your optimization code there.
Hope this helps, Regards.
For a control you can override either OnControlCreated or OnHandleCreated. The latter one can fire multiple times if it is necessary to recreate the control window. Be sure to use it if your code affects the window itself. In other words, if you do anything that requires the Handle property.
Few suitable choices for a ToolStripItem derived control. I'd recommend overriding SetVisibleCore() or OnAvailableChanged() or the AvailableChanged event. They run when the Visible property of the ToolStripItem changes. Beware that it may fire multiple times, keep a bool field that tracks that your initialization code has already run.
Last but not least, be sure to only do any of this if your code actually requires the control to be created. The vast majority of init code can go in the constructor. You only need a Load event if your code depends on the actual Location and Size of the control. Which might be different from the designer value if the form rescales itself due to a different system font or video DPI setting on the target machine.
I needed a solution like this for a TabPage within a TabControl.The only thing I came up with was using the paint event handler. I added the event handler for Paint and in the very first line I remove the event handler and then do more initialization code. This only works if you do nothave any custom painting. Alternatively, if you do need to do custom painting you could add a flag to check for each time Paint Executes.
//Paint only runs once
private void tabPage1_Paint(object sender, PaintEventArgs e)
{
tabPage1.Paint -= tabPage1_Paint;
//Do initialization here
}
/////////////////////////////////////////////////////////////////////////////////
//Paint always runs
private bool IsFirstPaint = true;
private void tabPage1_Paint(object sender, PaintEventArgs e)
{
if(IsFirstPaint)
{
IsFirstPaint = false;
//Do initialization here
}
//Do custom painting here
}
At present I have two WPF listboxes imitating the following functionality
(source: psu.edu)
I am using 2 ObservableCollections to allow users to select whatever items they require (flexibility is the key here). The main issue is that I have thousands of items that are grouped in both listboxes. All in all the design works really well (with a few dozen items), but my stumbling block is when a user copies all the available items from the left to the right as the screen freezes (time to run on a different thread?).
Looking at ObservableCollection it lacks an AddRange method and there are various implementations available on the internet. I also know the CollectionChanged event is needlessly being fired as each item is copied over draining performance horribly.
It may well be that I have to allow users to choose from groups of over 10 000 items in the future, which sounds like a bad idea, but is non-negotiable as the grouping on the listbox (CollectionViewSource) works really well, but has the side effect of switching off the Virtualising of both the listboxes
What can I do to improve the performance when loading a listbox with thousands of items when databound to an ObservableCollection? Are there any AddRange type implementations that you would recommend? Is the only choice I have here to run this on a background thread which seems expensive because I am not loading data from a database?
I have removed the CollectionViewSource and the grouping and the items are copied over in 1/2 a second, but with the grouping on it can take up to a minute because virtualisation does not work with the grouping.
I will need to decide whether to use the CollectionViewSource
I couldn't resist answering this. I don't think you won't need this answer anymore, but maybe somebody else can use it.
Don't think too hard (do not approach this multithreaded (this will make things error-prone and unnecessary complicated. Only use threading for hard calculations/IO), all those different actiontypes will make it very difficult to buffer. The most annoying part is, that if you remove or add 10000 items your application (listboxes) will be very busy with handling the events raised by the ObservableCollection. The event already supports multiple items. So.....
You could buffer the items until it changes the action. So Add actions will be buffered and wil be raised as batch if the 'user' changes action or flushes it.
Haven't test it, but you could do something like this:
// Written by JvanLangen
public class BufferedObservableCollection<T> : ObservableCollection<T>
{
// the last action used
public NotifyCollectionChangedAction? _lastAction = null;
// the items to be buffered
public List<T> _itemBuffer = new List<T>();
// constructor registeres on the CollectionChanged
public BufferedObservableCollection()
{
base.CollectionChanged += new NotifyCollectionChangedEventHandler(ObservableCollectionUpdate_CollectionChanged);
}
// When the collection changes, buffer the actions until the 'user' changes action or flushes it.
// This will batch add and remove actions.
private void ObservableCollectionUpdate_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// if we have a lastaction, check if it is changed and should be flush else only change the lastaction
if (_lastAction.HasValue)
{
if (_lastAction != e.Action)
{
Flush();
_lastAction = e.Action;
}
}
else
_lastAction = e.Action;
_itemBuffer.AddRange(e.NewItems.Cast<T>());
}
// Raise the new event.
protected void RaiseCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (this.CollectionChanged != null)
CollectionChanged(sender, e);
}
// Don't forget to flush the list when your ready with your action or else the last actions will not be 'raised'
public void Flush()
{
if (_lastAction.HasValue && (_itemBuffer.Count > 0))
{
RaiseCollectionChanged(this, new NotifyCollectionChangedEventArgs(_lastAction.Value, _itemBuffer));
_itemBuffer.Clear();
_lastAction = null;
}
}
// new event
public override event NotifyCollectionChangedEventHandler CollectionChanged;
}
Have fun!, J3R03N
You could probably inherit from ObservableCollection<T> (or directly implement INotifyCollectionChanged) to add BeginUpdate and EndUpdate methods. Changes made between calls to BeginUpdate and EndUpdate would be queued, then combined into one (or several if there are separate ranges) NotifyCollectionChangedEventArgs object that would be passed to the handlers of the CollectionChanged event when EndUpdate is called.
You can find a Thread safe observable collection here. Make your Observable collection thread safe and bind it to listbox.
I was wondering if there's a way to watch all RoutedEvents that are raised in a WPF application. A way to write some info about the events fired to the console would be prefect to see what's going on.
I've found another way:
I've added this to the loaded handler of my UserControl.
var events = EventManager.GetRoutedEvents();
foreach (var routedEvent in events)
{
EventManager.RegisterClassHandler(typeof(myUserControl),
routedEvent,
new RoutedEventHandler(handler));
}
and this is the handler method:
internal static void handler(object sender, RoutedEventArgs e)
{
if (e.RoutedEvent.ToString() != "CommandManager.PreviewCanExecute" &&
e.RoutedEvent.ToString() != "CommandManager.CanExecute")
Console.WriteLine(e.OriginalSource+"=>"+e.RoutedEvent);
}
The CanExecute events are a bit too much in my case. If you would like to see these too, just remove the if statement.
Yes, but it requires some reflection. You're better off using a tool like Snoop that already does the hard lifting for you.
In the tab Events you can see list of events, and the element that handled it.