I have an ObservableCollection that gets data from a foreach. The UI updates great when changes are made to the collection, but there is a lag as the collection size starts to increase in size. I have about 500+ items in my collection that get deleted, reordered and added. But I've noticed that there is a definite lag between when the changes are made and when they are reflected in the UI. The example below is just a simple one. Obviously, the progress bar won't be on for long in this case (fraction of a second), but the point I'm making is that the UI won't reflect the changes I've made for many, many noticeable seconds after the progress bar is stopped below. What event can I capture or implement in my object that will allow me to add code that updates the progress bar while the ObservableCollection is being pushed to the UI? Or does someone want to send me a "DealWithIt" dog picture :)
a_progressbar.IsIndeterminate = true;
foreach (Group<SomeItem> sortedItem in sortedItems)
{
OList.Add(sortedItem);
}
a_progressbar.IsIndeterminate = false;
Update Here is the code I used to make it work.
_dataBinder = new BackgroundWorker();
_dataBinder.DoWork += new DoWorkEventHandler(DataBinderWork);
_dataBinder.RunWorkerCompleted += new RunWorkerCompletedEventHandler(DataBinderComplete);
...
private void DataBinderComplete(object sender, RunWorkerCompletedEventArgs e)
{
Dispatcher.BeginInvoke(() =>
{
a_progressbar.IsIndeterminate = false;
});
}
private void DataBinderWork(object sender, DoWorkEventArgs e)
{
Dispatcher.BeginInvoke(() => LotsOfWork());
}
This sounds like a good job for the BackgroundWorker component. The component will allow you to perform expensive operations in a background thread so your UI won't hang; meanwhile, the UI thread can be notified via events so it knows when the work has completed.
Update:
I must have misunderstood the question. If your question is about a list in your UI that is bound to the ObservableCollection being slow to update when adding a bunch of items, one thing you could attempt to do is to build a new ObservableCollection, filling it with the data you need, then assigning the new collection to the UI.
I've had problems with ObservableCollection in the past because it raises an event every time an item is added, which makes the UI responsiveness poor. My workaround was to create a sub-class of the collection where you could do an AddRange which only raised one event, but simply building a new collection that isn't bound to the UI then binding it after it's been populated is another option.
A big warning against the use of ProgressBar.IsIndeterminate - it takes more than 60% of the CPU resources and can have disastrous impact in some cases. See this article for more info.
Related
I'm using a DataGridViewCheckBoxColumn inside a DataGridView in a WinForm panel.
When a checkbox is clicked, I need to compute things that might change a Control state outside the DataGridView.
To do so, I have to handle the CellContentClick event because I need to compute only when a checkbox value is actually changed.
Grid.CellContentClick += Grid_CellContentClick
private void Grid_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
DataGridView dgv = (DataGridView)sender;
dgv.EndEdit();
// Compute stuff
}
However CellContentClick doesn't always fire, while the internal event that does change the DataGridViewCheckboxCell checked state is.
Most importantly, fast successive clicks on a checkbox do not fire CellContentClick, only the first is catched until the user stops clicking.
As a result I end up in an invalid state where the control outside the DataGridView doesn't display as intended because the computation doesn't use the checkboxes final values.
I've tried to debounce the event and creating a pseudo-lock using MouseDown and the grid ReadOnly property, with no success.
Is there a way to catch only the last event of a series of clicks? Is there a better way to do this?
Thank you #Jimi and #JohnG for your insights, it helped me solving this issue.
I could not make it work using CellValueChanged and CellContentClick, even with async and await Task.Delay(...) as it did not fire correctly and triggered inter-thread exceptions in my computation afterwards.
It might just have been me though, but I wasn't very fond of using Threading in this context anyway.
I hadn't considered using CellValueChanged and noticed that it wouldn't trigger for a DataGridViewCheckBoxCell when clicked, so I ended up reading this thread and the solution is actually quite simple.
Grid.CurrentCellDirtyStateChanged += Grid_CurrentCellDirtyStateChanged;
Grid.CellValueChanged += Grid_CellValueChanged;
private void Grid_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (Grid.IsCurrentCellDirty)
{
Grid.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
private void Grid_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
DataGridViewCheckBoxCell cell = (DataGridViewCheckBoxCell)Grid[e.ColumnIndex, e.RowIndex];
// Compute stuff
}
This code is executed each time a grid checkbox is clicked on, and has a far better logic since it relies on a direct value change.
However it means the computation takes place each time the value is changed.
I believe one could debounce the computation in order to improve this solution, fortunately mine isn't too resource expensive so it runs smoothly and I don't need to take it that far.
I am having a bit of a problem with my UI hanging even though I am using a Dispatcher, and before I get any further I'm wondering if it's the way I'm handling the data retrieval.
Right now I have my main window creating a View and ViewModel. Then, inside of a new Thread (using a Dispatcher) it sets the View.DataContext = ViewModel. A very large ObservableCollection is lazily created when the binding kicks in which is causing the slowdown. However, it seems that some of the other UI items that should be showing up before that slowdow don't actually show up.
private void ButtonClick(Object sender, RoutedEventArgs e)
{
MyView view = new MyView();
MyViewModel vm = new MyViewModel();
TabItem tabItem = new TabItem();
tabItem.Header = "MyView";
tabItem.Content = view;
MyTabCollection.Items.Add(tabItem);
Window working = new Working();
working.Show();
ThreadStart thread = delegate()
{
DispatcherOperation operation = Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
new Action(delegate()
{
view.DataContext = vm;
((FrameworkElement)view.Parent).Focus();
working.Close();
}
)
);
};
Thread theThread = new Thread(thread);
theThread.Start();
}
This basically says it's supposed to create a view and a viewmodel, then add the view to the tab collection I have (which means it should show the new tab at the least). And, it should also show a "Working..." window. After that, a separate thread is supposed to link the ViewModel to the view, focus on that tab and close the working window. The problem is that the first portion doesn't show until everything is done; The tab is not displayed and the working window is not shown until after the new Thread actually finishes (which causes the Working window to show/close right away). I'm guessing it might have to do with the way I retrieve the data, but I'm not sure. Here is the way it does it:
Create View
Create ViewModel
Create TabItem with Content set to the View and add the TabItem to the TabCollection.
Create/Show the "Working..." window
Dispatcher: Set the View.DataContext = ViewModel. This event sets off the DataBindings, which in turn grab the ObservableCollection. Since the OC is created Lazily it is now being created (this is the bottleneck). <-- Is this messing up my separate thread/dispatcher?
Dispatcher: Set Focus to the tab
Close the "Working..." window
All your extra thread is doing is marshalling another call back to the dispatcher thread. Presumably you actually want to do work on the extra thread, or there's no point in creating it.
Ideally your extra thread should be fetching all the data appropriately, leaving you only to actually connect it all up in the dispatcher thread. The important thing is to decide which work you need to do on the UI thread and which work you need to do on the background thread.
Obviously your analysis of the problem is correct. Your view model is lazily loading data when it is needed, and this is not happening until the Dispatcher callback, at which point you are back on the UI thread again and everything is locked up.
In my opinion, the solution is to do the threading in the data access layer:
For collections: You can define special collections that return only items that have already been loaded from the upstream data source, then trigger loading of additional items on a separate thread when someone subscribes to INotifyCollectionChanged. When the additional items arreive, fire INotifyCollectionChanged events. When INotifyCollectionChanged is unsubscribed, cancel any pending load.
For totals and the like: Same idea. As data comes in the total increases and events occur (automatically for DependencyProperty or using INotifyPropertyChanged).
In addition, the data layer should have a parallel property to each collection, sum, or other delay-loaded value indicating whether it is fully loaded or not, allowing the UI to gray out sections that aren't fully loaded. It is also convenient to have an overall "loading" flag somewhere that can be used to gray out UI sections when anything at all is loading (easier to write the UI this way).
Note that sometimes an operation must block until the actual data has been retrieved. I think the easiest thing in this case is to provide methods in the data layer to force data to be loaded synchronously.
Your DispatcherPriority is set to Normal - try setting it to Background as this may improve the rendering
Just wondered what that best approach is for this scenario - trying to databind to a collection which is being populated in another background thread.
My background thread is adding items to the collection in a processing loop which may run for several minutes. Every now and then it raises an event to the UI and passes a reference to the data from the collection, for the UI to visualize, (so the user can start to interact with what they have) and carries on processing.
Trouble is the UI starts to render the visualisation (which is quite complex itself), which involves a foreach() loop over the collection of data, and understandably that loop craps itself if my background thread changes the data in the collection during the enumeration.
So my brainstorming has gone like this:
pause the background thread; but I really don’t want to pause
take a duplicate snapshot copy of all, or some the data in each event, and databind to the snapshot. Doubles my memory usage but would probably work
implement some kind of lock{} on the syncroot or whatever of the collection so while the UI is updating the background process has to wait. Not confident about that working anyway
fire the event all the time and just pass one bit of data at a time, which has the same result as #2 but with more overhead..
cheers
ewart.
The easiest way to fix your problem is by changing the foreach loop to a for loop that loops from 0 to the number of items in your collection before you start to loop.
instead of:
var coll = new List<string>();
foreach (string item in coll)
{
//do your stuff
}
use:
var coll = new List<string>();
int length = coll.Count - 1;
for (int i = 0; i < length; i++)
{
}
OR
I don't know how complex your UI is but with WPF you can almost always change the code to advanced databinding bound to an ObservableCollection. The Observable collection triggers an update of the binding when an item is added or removed.
But to provide a solution for that requires a lot of information on how your UI is build.
I am designing the architecture for a module where a search takes place.
the search takes some time, and I would like to have the UI to be responsive, so I get to delegate the retrieval of the data to a class that will do the search on a separate thread.
Then, I can think of two options :
either
1° : the search method returns a viewmodel with empty data as a return value (place holder), but once the search process is over, the viewmodel's members will be updated and the result displayed on screen thanks to data binding,
2° : the search method doesn't have any return type, but once the search process is over, an event is raised and the viewmodel with its final values is passed in the event args so it can be used by the calling code. (and eventually be consumed by a view)
Any thoughts on this one ?
EDIT: Of course, with solution 1° I'm refering to WPF databinding on the objects returned by the search-results "place-holder" objects
If you use a BackgroundWorker, the design pattern is done for you. Call your search method in the DoWork event handler, and put the results in the Results property of the DoWorkEventArgs that's passed in.
Update the UI with the results in the RunWorkerCompleted event handler (they'll be in the RunWorkerCompletedEventArgs object's Results property).
If you want to update the UI while the search is in progress, have your search method call ReportProgress and update the UI in the ProgressChanged event handler. You can put anything you like into the UserState property of the ProgressChangedEventArgs, including intermediate search results, though you do have to be careful not to pass any objects that the background thread is going to touch as it continues executing.
I'd take advantage of data binding and bind your search result presentation control to a observable collection of search results and update that collection in your ViewModel from a background worker thread. Then it doesn't required any callbacks or update messages in you code, it just works as you'd expect taking advantage of the plumbing in place in WPF.
You could also leverage Priority Binding (http://msdn.microsoft.com/en-us/library/system.windows.data.prioritybinding.aspx). It provides the fast / slow options and you don't have to worry about update threads. Unless of course you are looking to add some visuals to let the user know something is happening.
Here is what I did on a recent project.
The IsBusy property is something I have in the base class for all my viewmodels. It is a boolean that a view can bind to if it wants to display some kind of waiting control like a spinner or whatever.
The _retrieveData field is just an Action that I set up during the construction of the viewmodel. I do this because the viewmodel may get its list of Cars in several different ways - so _retrieveData may have different code in it based on the constructor that was used. After _retrieveData gets the data, it will set the private backer, _cars with the data. So after _retrieveData is done, setting the public Cars to the value with the new data in _cars causes the PropertyChangedEvent which lets the view know to update itself.
So the effect is that when the view goes to get the data for the first time, it returns immediately but gets null. Then a few seconds later, it gets the actual data. During that time, the UI is responsive. And also, the IsBusy is true if the UI wants to let the user know that it is working in the background.
Not sure if this is a good way to handle it, but it is working for me so far.
public List<Car> Cars
{
get
{
if (this._cars == null)
{
base.IsBusy = true;
// Start a background thread to get the data...
ThreadPool.QueueUserWorkItem(new WaitCallback(delegate(object nullObject)
{
this._retrieveData.Invoke();
this.Cars = this._cars;
base.IsBusy = false;
}));
// While waiting on the background thread, return null for now. When the background thread
// completes, the setter will raise OnPropertyChanged and the view will know its time to bind...
return this._cars;
}
return this._cars;
}
set
{
this._cars = value;
base.OnPropertyChanged("Cars");
}
}
I have set up a dialog with several tabs. One of these contains twenty combo boxes, each with over 100 items, added like this :
foreach (var x in collection)
{
string text = FormatItem (x);
combo.Items.Add (text);
}
so there is nothing fancy at all with the items. They are plain strings and the combo boxes get filled when the dialog is created. This happens almost instantenously.
However, when the user clicks on the tab containing all these combo boxes for the very first time, the GUI freezes for several seconds (and I am running on a really beefy machine).
I loaded the symbols for System.Windows.Forms and tried to break into the debugger while the program is stuck. What I have discovered is a stack trace with the following calls:
System.Windows.Forms.Control.CreateHandle()
System.Windows.Forms.ComboBox.CreateHandle()
System.Windows.Forms.Control.CreateControl(...) x 3
System.Windows.Forms.Control.SetVisibleCore(true)
System.Windows.Forms.TabPage.Visible.set(true)
which results in plenty of native transitions, WndProc calls, etc. I suppose this happens for every single item in every combo box. Phew.
Obviously, I cannot optimize WinForms. But maybe I can take some actions in order to avoid all this hell getting lose on my poor GUI? Any ideas?
Nota bene:
I've no event handlers attached on the combo boxes which could be called when the controls get created for real.
If I try to access the Handle property of the combo boxes just after having created and populated the form, I pay the penalty at that moment, rather than when the tab becomes visible for the first time. But having to wait several seconds when creating the form is not acceptable either. I really want to get rid of the long set up time.
The idea of applying BeginUpdate and EndUpdate does not apply here: these should be used to prevent the control from repainting when its items list gets filled. But in my case, the problem happens well after the control has been set up.
What you're saying is not consistent with anything I ever observed... :s
But have you tried using .BeginUpdate / .EndUpdate ?
Another thing you coud try is not populate the boxes until needed. Delay it until the box gets focus for example... (If you trap the dropdown event some user might be annoyed that the up/down arrow keys won't work.)
Everything I tried failed up to now to speed up the first display of the tab containing all the combo boxes. Data binding didn't help either.
I finally decided to fix the issue by doing a trick, similar to what danbystrom proposed, i.e. only populate the Items collection when the focus first arrives on a combo. This still produces a noticeable delay, the time all items get created (within a BeginUpdate and EndUpdate pair of method calls), but it is tolerable (approx. 200ms versus several seconds in my original scenario).
Instead of iteration your collections, would't setting the ComboBox.DataSource be a viable, and much faster alternative?
comboBox1.DataSource = myCollection1;
comboBox2.DataSource = myCollection2;
comboBox3.DataSource = myCollection3;
// and so on...
Here is a more complete example:
public class Entity
{
public string Title { get; set; }
public override string ToString()
{
return Title;
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
List<Entity> list = new List<Entity>
{
new Entity {Title = "Item1"},
new Entity {Title = "Item2"},
new Entity {Title = "Item3"}
};
comboBox1.DataSource = list;
}
I've just been coming up against this same problem, where populating a combobox with around 4000k items was unacceptably slow.
I was filling the combo in the OnLoad event handler of a form, however, when I shifted this code to constructor, after InitializeComponent(), there was no delay at all.
I guess doing this operation in the OnLoad was causing a redraw of the combo to fire, hence the delay? Anyway, just thought I'd add this in case it's of use to anyone else in this situation.
Lots of controls on a form can be a problem. I once had a form that dynamically created between 50-100 textbox controls. It was slow to load.
We solved that problem by using a datagrid instead. It's a control that is optimized for lots of data. I don't know what your exact requirements are, but it might work.