In my application I update a treeview in a backgrounworker thread. While updating the treeview, the combobox values are not visible. When the treeview is updated, the values appear.
Here's my backgroundworker code:
void _bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
tvCategories.Invoke((MethodInvoker)delegate()
{
FillCategoryTreeView(); // Fills the treeview
}
);
}
The code that fills my combobox:
private void FillCategoryCombo()
{
Category categorie = new Category();
List<Category> categories = categorie.GetQuestionCategories();
cmbCategories.DataSource = categories;
cmbCategories.DisplayMember = "Description";
cmbCategories.ValueMember = "Id";
}
The combobox is filled in the constructor of the form.
The reason that I've put the treeview in a seperate thread is because the treeview must be updated. In the meantime I want to continue using the application. Therefore I need access to a combobox. But the values of the combobox are invisible while the treeview is being updated.
What to do to solve this?
I'm not quite sure there is enough information in your post to fully answer the question... but assuming that you create the Background worker thread in the Constructor prior to calling the FillCategoryCombo() method... this makes sense.
In your background worker method, you immediately call Invoke which switches control right back to the UI thread, which will then be doing the work of FillCategoryTreeView() before FillCategoryCombo() has a chance to run.
If you want to asynchronously fill your treeview (assuming it comes from a long running database call), then what you need to do is actually have separate Invoke calls in FillCategoryTreeView when you specifically need to add a tree view item. That way as each database call (or whatever takes a long time) finishes, it only does an operation on the UI thread when it needs to add a physical tree node.
Related
I have a problem with populating my comboBoxes in a WinForm application that I'm writing. The data that I use to populate these comboBoxes is pulled from database. The problem is that there is a lot of data that needs to be binded to the comboBoxes, so this process takes a very long time during which the entire application is locked (the entire process of binding data takes over 9 seconds, while the process of pulling the data from the database takes only 400 milliseconds). I'm trying to speed things up by splitting the processes of creating the controls (main thread) and populating the comboBoxes (background worker), but naturally I get the cross thread error.
This is the part of code that I use:
private void Populate()
{
comboBox1.BindingContext = new System.Windows.Forms.BindingContext();
comboBox1.DataSource = MyClass.dtMyDataTable;
comboBox1.DisplayMember = "TitleColumn";
.//I repeat the same code for each comboBox
.//I use the BiningContext because some of the comboBoxes have to display the
.//same data.
}
I created a class that contains all DataTables that I need in this form - there are multiple forms that use the same data from the database, so I created a class and created an object that fills all of these DataTables on the parent Form.Load(), and then I pass them to the children forms when I create them. This way I load the data when the application loads (it doesen't even take that long), so it should be ready to use when I call it from child forms. I've tried to call the Populate() method from backgroundWorker.DoWork() method, and there I get the cross thread error.
My question is - is there a way to make this work, and if not, what could I use as alternative solution.
Thank you
I'm not a full bottle on invoking but try this:
PopulateData()
{
if (combobox1.InvokeRequired)
{
combobox1.Invoke(new EventHandler(delegate(object o, EventArgs a)
{
PopulateData();
}
));
}
else
{
// Do your updates here...
}
}
I believe this will find the thread responsible for combobox1, which is going to be the same thread for the other combo's, and then run.
I'm sure someone else will chime in with a better way to invoke perhaps at the form level ?
I've found a good alternative, and it sped things up from 9 seconds to 1.5 seconds. The solution is to place the comboBox.DisplayMember BEFORE the 'comboBox.DataSource' line because when you change the DisplayMember (or ValueMember) the data source repopulates itself. So if the comboBox.DisplayMember is after the 'comboBox.DataSource' line, the data source populates itself 2 times (I think that ClearBeforeFill is enabled by default when binding data sources, that is why there are no duplicates in the binded data).
Thanks anyway.
I have a windows form 'MyForm' with a text box that is bound to a property in another class 'MyData'. The Data source update mode is set to "On Property Change"
I used the VisualStudio IDE. It created the following code for the binding
this.txtYield.DataBindings.Add(new Binding("Text", this.BindingSourceMyDataClass, "PropertyInMyDataClass", true, DataSourceUpdateMode.OnPropertyChanged));
In the form constructor, after initialize values code was added to bind the MyData Class to the form
myDataClassInstantiated = new MyDataClass();
BindingSourceMyDataClass.DataSource = myDataClassInstantiated;
The INotifyProperty Interface has been implemented:
public double PropertyInMyDataClass
{
get { return _PropertyInMyDataClass; }
set
{
if (!Equals(_PropertyInMyDataClass, value))
{
_PropertyInMyDataClass = value;
FirePropertyChanged("PropertyInMyDataClass");
}
}
}
A background worker is used to run the calculations and update the property 'PropertyInMyDataClass'
I expected that the text box on the form would update automatically when the background worker completed. That didn't happen
If I manually copy assign the value from the property to the form text box, the value is displayed properly
this.txtYield.Text = String.Format("{0:F0}", myDataClassInstantiated.PropertyInMyDataClass);
I tried to add Refresh() and Update() to the MyForm.MyBackgroundWorker_RunWorkerCompleted method, but the data still is not refreshed.
If I later run a different background worker that updates different text boxes on the same form, the text box bound to PropertyInMyDataClass gets updated
I would appreciate suggestions that will help me to understand and resolve this databinding problem
The problem comes from a couple of angles. If you are running the process on a background thread, the background thread cannot directly access a control on your form (which lives in a different thread) directly, otherwise you will get an exception. You also cannot expect the UI thread to update based on states in the background thread, unless you wire it up to do so. In order to do this, you need to invoke a delegate on the main UI thread..
Place this code (modify it to update whatever control you want with whatever value type you want) on the UI form.
public void UpdateOutput(string text)
{
this.Invoke((MethodInvoker) delegate {
lstOutput.Items.Add(text);
});
}
Then you can call this function in your background worker thread. (assuming your background process function lives in the same form, you can call it directly), if not then you will need a reference to the UI form in the class that the background process runs in.
I have written a class that fills a treeview for me. IN my project I need this treeview several times and I don;t want to copy paste my code, so I decided to create a class that fills the treeview for me.
On some forms I want to use a thread to fill the treeview. This is because sometimes it can take some time to load the data and fill the treeview.
In my treeview-class I pass the treeview in the constructor. At the moment I want to fill the treeview, I call the LoadTreeview() method.
I'd like to call the LoadTreeview method on a thread, but when I do this I get the exception that the treeview is created on another thread. Which is logic off course. But I was wondering, what is the best way to create a custom class that works with controls and you want to use this class in a thread?
Do I need to write this code on every 'GUI-action'?
treeview.Invoke((MethodInvoker)delegate
{
treeview.Nodes.Add(MyNode);
})
Or are there other (smarter) ways?
Both your and Levisaxos' solutions will prevent the crash but you should really benchmark the runtime performance of this. The problem is that if you insert lots of nodes to the treeview and each node is inserted through Control.Invoke your code will not be doing much but synchronizing to the UI thread. If this is the case you should consider to separate loading the data that is needed to create the nodes for the treeview from the actual insertion of the nodes. Instead load the data asynchronously and then synchronously insert all nodes at once.
public delegate TreeView updateLabelDelegate(TreeView view);
private TreeView InvokeTreeView(TreeView view)
{
if (view.InvokeRequired)
{
view.Invoke(new updateLabelDelegate(InvokeTreeView), new object[] { view });
return null;
}
else
{
return view;
}
}
I hope this helps you. This is how I am handeling Async operations.
[edit]
Depending on how you want to use it.
In a thread:
public TreeView thistreeviewsucks;
void SomeThread()
{
TreeView tv = new TreeView();
tv.Items.Add("something");
//upon completion
this.thistreeviewsucks = InvokeTreeView(tv);
}
As far as I know this will work.
I am creating an array of ListViewItems on a background thread and then i populate my listview with it on the ui thread. The problem is that if the array is too big the ui blocks while the listview is updated.
Is there a way to populate the listview with a small impact on the ui?
If you have a lot of data going into it you might want to use it in virtual mode, by setting the VirtualMode property of the ListView control to true. That means that the ListView will not be populated in the traditional sense, but you will hook up event handlers where you deliver the information to the list view in small chunks as the items are being displayed.
Very simple example:
private List<string> _listViewData = new List<string>();
private void toolStripButton1_Click(object sender, EventArgs e)
{
_listViewData = GetData(); // fetch the data that will show in the list view
listView1.VirtualListSize = _listViewData.Count; // set the list size
}
// event handler for the RetrieveVirtualItem event
private void listView_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
if (e.ItemIndex >= 0 && e.ItemIndex < _listViewData.Count)
{
e.Item = new ListViewItem(_listViewData[e.ItemIndex]);
}
}
You should also look into using the CacheVirtualItems event.
You can also use BeginUpdate and EndUPdate methods
listView1.BeginUpdate();
//Add Values
listView1.EndUpdate();
My comments got quite long, so I decided to make this a separate post.
First, +1 for Fredrik Mörk, using VirtualMode is the way to go. However, you do lose some functionality, e.g. column autosize, and sorting is easier handled on your own.
If that is a problem, populating from a worker thread may sound sounds tempting. However, the population will still take place in the thread that owns the list control (i.e. virtually always the main thread) - .NET makes that visible by enforcing you to use (Begin)Invoke. Additionally, the context switches will notably increase the total time you need to fill all items if you fill one by one, so you want to fill in chunks (say 50 items at a time, or even better fill as many as you can in 20 milliseconds). Add to that the additional synchronization required when contents changes, you have a quite complex solution for a not-so-stellar result.
The image below shows how my code works. When I press button2 the listbox is updated, but not when I press button1. Why?
Is the problem threading related? If it is, where should I add the call to (Begin)Invoke?
One interesting thing to note is that if I first press button1 and then button2 the data generated by the button1 click is shown when I click button2. So it seems like the data generated by doFoo is buffered somewhere, and then pushed to the listbox once I press button2.
EDIT:
I tried adding AddNumber to the form code, and added a call to Invoke when listBox1.InvokeRequired returns true. This solves the problem, but isn't the nicest of designs. I don't want the GUI to have to "worry" about how to add items to a list that's part of the model.
How can I keep the logic behind adding to the list internal to the list class, while still updating the gui when the list changes?
EDIT 2:
Now that we have confirmed that this is a threading issue I've updated the image to more closely reflect the design of the actual code I'm working on.
While Lucero's suggestion still solves the problem, I was hoping for something that doesn't require the form to know anything about the dll or CDllWrapper.
The model (ListBoxDataBindingSource etc) should know nothing at all about the view (listboxes, buttons, labels etc)
My guess is that this is due to the update message being handled on the wrong thread. Background: each thread has its own message queue. Messages posted into the message queue will land in the same thread as the caller by default. Therefore, the callback will maybe post a message on the wrong thread.
Try this: move the AddNumber() method to the form and use Invoke() (inherited by Control) to add the item in the correct thread. This may get rid of the issue.
Edit to reflect your followup:
The UI doesn't have to know about your component. What you need is just a proper synchronization between adding the item to your list and the UI, since UI updates will oly work if the thread matches. Therefore, you might want to supply the Control to your class which wraps the BindingList, and then do the Invoke on the list itself. This makes the list worry about triggering the upate on the UI thread and does take the worry from both the UI and the external component of invoking the handler on the correct thread.
Like this:
internal class ListBoxDataBindingSource {
private readonly Control uiInvokeControl;
private readonly BindingList<Item> list = new BindingList<Item>();
public ListBoxDataBindingSource(Control uiInvokeControl) {
if (uiInvokeControl == null) {
throw new ArgumentNullException("uiInvokeControl");
}
this.uiInvokeControl = uiInvokeControl;
CDIIWrapper.setFP(AddNumber);
}
public void AddNumber(int num) {
Item item = new Item(num.ToString());
if (uiInvokeControl.InvokeRequired) {
uiInvokeControl.Invoke(list.Add, item);
} else {
list.Add(item);
}
}
private BindingList<Item> List {
get {
return list;
}
}
}
I know this is old, although I had a very similar problem.
Here was the solution: BindingList not updating bound ListBox.
Instead of having the setFP set the callback to lbDataBindingSource.AddNumber, create a private method in your code to handle the callback and then call lbDataBindingSource.AddNumber from that callback.
void MyForm_Load(object sender, EventArgs e)
{
//...
cdll.setFP(FPCallback);
}
private void FPCallback(int num)
{
lbDataBindingSoruce.AddNumber(num);
}
I need to call my view model to add things to the bindinglist, so I need to write an anonymous function
Reference to Lucero's Answer and following post:
Anonymous method in Invoke call
My Code:
listBox.Invoke((Action)delegate
{
MyViewModel.AddItem(param1, param2);
});