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);
});
Related
My form looks something like a three-pane email client. Left side is a grid with a list of people. Top right is the current person's detail record. Bottom right is a custom control with many checkboxes displaying the current person's areas of expertise:
[x] cooking [x] window cleaning [x] brain surgery
[x] massage-therapy [x] singing [ ] random acts of vandalism
When the form is opened, the focus goes to the first person listed in the grid on the left-side of the form,, and the grid's focused_row_changed event fires. In the handler for this event I get the current person's id, then fetch detail data for that person from the database and populate the detail record, and also fetch the person's areas-of-expertise rows and set the checkboxes. All of this is working fine except when the form is first opened, because then the custom control with its many checkboxes is not yet initialized. At that point MyCustomControl is null.
if (null != MyCustomControl)
{
MyCustomControl.SetCheckedValues( datasource);
}
What is the best practice design-pattern for handling this situation? What do I do here when my control isn't fully initialized yet?
if (null != MyCustomControl)
{
MyCustomControl.SetCheckedValues( datasource);
}
else
{
// ?? Wait around for a bit and keep trying every 100ms?
}
The way I have solved this in my controls when they have had this problem is to implement ISupportInitialize.
In your control, you would put something like:
public class MyCustomControl: ISupportInitialize
{
private bool _initializing = false;
private void BeginInit()
{
_initializing = true;
}
private void EndInit()
{
_initializing = false;
}
private void SomeMethodThatWouldRaiseAnEventDuringInit()
{
if (_initializing) return;
//...
}
}
The windows forms designer checks for your control implementing the interface, and produces this code in the .Designer.cs file:
((System.ComponentModel.ISupportInitialize)(this.customControl1)).BeginInit();
///
/// customControl1
///
this.customControl1.SelectedIndex = 0; //this would normally raise the event
((System.ComponentModel.ISupportInitialize)(this.customControl1)).EndInit();
From what I understand, you are setting MyCustomControl.SetCheckedValues( datasource); when focused_row_changed event fires.
This also tends to happen when the form is just loading, which is generally not desired, because you end up with events telling things to load when, for example, a selected index is still -1.
The way I have been working around this is I have a global boolean in the form called doneLoading. It starts off false and becomes true when the Form_Shown() event gets called.
From there I just put an if(doneLoading) around any piece of code that needs to wait until the form is actually done loading before it is allowed to execute. In your case, I would do:
if(doneLoading)
{
MyCustomControl.SetCheckedValues( datasource);
}
Do your UI initialization functions in a subroutine that isn't called until after all the other UI elements are initialized, or base your calculations on the back-end values instead of the UI.
in response to comments and other posts, if you can't get anything else to work, you can add a 'refresh' button to the UI
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 have a window that contains a label (player1). I also have a class that gathers data asynchronously in the background inside a thread. When that data has been gathered, I want to changed the content of my label. Since the label was created by the UI and I'm trying to edit it from another thread, I tried using Dispatcher. However, after hours of trying and different examples, I can't get it to work. In it's most simple form below, the method dispatchP1 changes the value of player1 when called from my main window. However, it doesn't work when called from my class. Also, I don't receive an error or anything.
public delegate void MyDelegate();
public void dispatchP1()
{
player1.Dispatcher.BeginInvoke(new MyDelegate(p1SetContent));
}
public void p1SetContent()
{
player1.Content = "text";
}
Any help would be appreciated.
That code doesn't seem particularly problematic - but WPF has a habit of swallowing exceptions. In your App.xaml, you can handle the event DispatcherUnhandledException and put a breakpoint in there to determine if it is really throwing an exception or not.
You know you can use anonymous delegates?
player1.Dispatcher.BeginInvoke( () =>
{
player1.Content = "text";
});
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.
So I've got some serious problems with removing a Control from a Form of my application. It's kinda messed up but I can't change anything. I have a form and I have a separated user Control. The control opens an exe file and shows a progress bar while loading it's bytes. And here comes the problem. I do all of it with a BackgroundWorker and when the worker_DoWorkerCompleted method is called the original form should show a MessageBox and remove the Control.
BackGround_Loader bgLoad = new BackGround_Loader();
bgLoad.Location = new Point(this.Width/2 - bgLoad.Width/2, this.Height/2 - bgLoad.Height/2);
this.Controls.Add(bgLoad);
bgLoad.BringToFront();
bgLoad.AddReferences(this.executableFile, this.SourceReader);
bgLoad.occuredEvent();
At first I set the control's location to be in the middle of the Form itself. Then I add the control to the form, and bring it to the front. After these I send the path of the executable and a RichTextBox's reference to this. With the occuredEvent I start the BackgroundWorker itself. And here comes my problem. I should show a MessageBox in the Form when the in the bgLoad the backgroundworker gets to the DoWorkerCompleted status. Kindly I have no idea how to do it. It works just perfect however the control stays in the middle of the form.
UI actions must be performed on the main UI thread. The events that get raised from the background worker thread are (obviously) in a different thread.
You need something like the following code:
private void backgroundWorker_DoWork(object sender, AlbumInfoEventArgs e)
{
// Check with an element on the form whether this is a cross thread call
if (dataGridView.InvokeRequired)
{
dataGridView.Invoke((MethodInvoker)delegate { AddToGrid(e.AlbumInfo); });
}
else
{
AddToGrid(e.AlbumInfo);
}
}
In this case AddToGrid is my method for adding a row to a DataGridView, but in your case it will be a method that does what you need to do.
Similarly for the backgroundWorker_RunWorkerCompleted method
See this MSDN example
I could find a way to solve the problem but I don't really like it. In the addReferences method I pass the Form itself and an object of the bgLoad class. Then in the RunWorkerCompleted I check if the control is on the form and if it is then I remove it.
bgLoad.AddReferences(this, bgLoad, this.executableFile, this.SourceReader);
...
private void worker_DoWorkerCompleted(object sender, DoWorkerEventArgs e) {
if(this.MainForm.Controls.Contains(this.Control) {
this.MainForm.Controls.Remove(this.Control);
}
}
Like this it works but it's awful for me.