5

The image below shows how my code works. When I press button2 the listbox is updated, but not when I press button1. Why?

pseudo code

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)

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Tobbe
  • 3,282
  • 6
  • 41
  • 53

4 Answers4

2

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;
        }
    }
}
Lucero
  • 59,176
  • 9
  • 122
  • 152
  • I understand what the problem is, now we just have to come up with a good solution :) I'm trying to design this according to the MVC pattern, so letting ListBoxDataBindingSource (part of the Model) know about a control (part of the View) is generally seen as bad practice. – Tobbe Apr 20 '09 at 15:51
  • Well, it's not really knowing about the control, but only needs to know hot to marshal the add call to the correct thread. You could make your own marshaling class which knows about the control and is passed to the list, so that this aspect is properly hidden. – Lucero Apr 20 '09 at 16:38
  • (*@Tobbe:* Maybe [this answer](http://stackoverflow.com/questions/3381536/winforms-data-binding-to-business-objects-in-a-multi-threaded-scenario-without-in/3381685#3381685) to one of my previous questions -- [Winforms data-binding to business objects in a multi-threaded scenario without InvokeRequired?](http://stackoverflow.com/q/3381536/240733) -- will apply here.) – stakx - no longer contributing Jan 30 '11 at 09:38
1

I know this is old, although I had a very similar problem.

Here was the solution: BindingList not updating bound ListBox.

Community
  • 1
  • 1
wulfgarpro
  • 6,666
  • 12
  • 69
  • 110
  • Thank you. I put this project on ice a long time ago, but when I pick it up again I'll see if that helps :) – Tobbe Feb 10 '11 at 07:37
0

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);
});
Community
  • 1
  • 1
0

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);
}
Justin Niessner
  • 242,243
  • 40
  • 408
  • 536