Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BindingList not updating bound ListBox

I have a ListBox that is bound to a BindingList. The BindingList is built up when a third party application raises an event. I can see the BindingList being bound correctly... but nothing enters the ListBox. I have used the exact same logic with some of my own custom types and it usually works very well.

Form class

private Facade.ControlFacade _controlFacade;        
public UavControlForm()
{
    InitializeComponent();  
    _controlFacade = new UavController.Facade.ControlFacade();      
    UpdateEntityListBox();
}
private void UpdateEntityListBox()
{
    lsbEntities.DataSource = _controlFacade.GetEntityTally();
    lsbEntities.DisplayMember = "InstanceName";
}

Facade class

private Scenario _scenario;
public ControlFacade()
{
    _scenario = new Scenario();
}
public BindingList<AgStkObject> GetEntityTally()
{
    BindingList<AgStkObject> entityTally = _scenario.EntityTally;
    return entityTally;
}

Scenario class

private static BindingList<IAgStkObject> _entityTally = new BindingList<AgStkObject>();
public Scenario()
{
    if (UtilStk.CheckThatStkIsAvailable())
    {
        UtilStk.StkRoot.OnStkObjectAdded += new IAgStkObjectRootEvents_OnStkObjectAddedEventHandler(TallyScenarioObjects);
        UtilStk.StkRoot.OnStkObjectDeleted += new IAgStkObjectRootEvents_OnStkObjectDeletedEventHandler(TallyScenarioObjects);
    }          
}
private void TallyScenarioObjects(object sender)
{
    List<AgStkObject> tallyOfStkObjects = UtilStk.GetRunningTallyOfAllStkObjects();
    List<string> stkObjectNames = UtilStk.GetInstanceNamesOfStkObjects(tallyOfStkObjects);

    foreach (string stkObjectName in stkObjectNames)
    {
        if (!SearchFlightUavTallyByName(stkObjectName))
        {
            if (!SearchLoiterUavTallyByName(stkObjectName))
            {
                if (!SearchEntityTallyByName(stkObjectName))
                {
                    int i = stkObjectNames.IndexOf(stkObjectName);
                    _entityTally.Add(tallyOfStkObjects[i]);
                }
            }
        }
    }
}

I can see the event fire from the third-party application - this adds an entity to _entityList as desired, but noothing is added to lsbEntities - why?

like image 601
wulfgarpro Avatar asked Jan 28 '11 00:01

wulfgarpro


1 Answers

(jump right to the last example if you want to see it fixed etc)

Threads and "observer" patterns (such as the data-binding on winforms) are rarely good friends. You could try replacing your BindingList<T> usage with the ThreadedBindingList<T> code I used on a previous answer - but this combination of threads and UI is not an intentional use-case of winforms data-binding.

The listbox itself should support binding via list notification events (IBindingList / IBindingListView), as long as they arrive form the right thread. ThreadedBindingList<T> attempts to fix this by thread-switching on your behalf. Note that for this to work you must create the ThreadedBindingList<T> from the UI thread, after it has a sync-context, i.e. after it has started displaying forms.


To illustrate the point that listbox does respect list-change notifications (when dealing with a single thread):

using System;
using System.ComponentModel;
using System.Windows.Forms;
class Foo
{
    public int Value { get; set; }
    public Foo(int value) { Value = value; }
    public override string ToString() { return Value.ToString(); }
}
static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        using(var form = new Form())
        using (var lst = new ListBox())
        using (var timer = new Timer())
        {
            var data = new BindingList<Foo>();
            form.Controls.Add(lst);
            lst.DataSource = data;
            timer.Interval = 1000;
            int i = 0;
            timer.Tick += delegate
            {
                data.Add(new Foo(i++));
            };
            lst.Dock = DockStyle.Fill;
            form.Shown += delegate
            {
                timer.Start();
            };
            Application.Run(form);
        }
    }
}

and now with added threading / ThreadedBindingList<T> (it doesn't work with the regular BindingList<T>):

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
class Foo
{
    public int Value { get; set; }
    public Foo(int value) { Value = value; }
    public override string ToString() { return Value.ToString(); }
}
static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        using(var form = new Form())
        using (var lst = new ListBox())
        {
            form.Controls.Add(lst);            
            lst.Dock = DockStyle.Fill;
            form.Shown += delegate
            {
                BindingList<Foo> data = new ThreadedBindingList<Foo>();
                lst.DataSource = data;
                ThreadPool.QueueUserWorkItem(delegate
                {
                    int i = 0;
                    while (true)
                    {
                        data.Add(new Foo(i++));
                        Thread.Sleep(1000);
                    }
                });
            };
            Application.Run(form);
        }
    }
}
public class ThreadedBindingList<T> : BindingList<T>
{
    private readonly SynchronizationContext ctx;
    public ThreadedBindingList()
    {
        ctx = SynchronizationContext.Current;
    }
    protected override void OnAddingNew(AddingNewEventArgs e)
    {
        SynchronizationContext ctx = SynchronizationContext.Current;
        if (ctx == null)
        {
            BaseAddingNew(e);
        }
        else
        {
            ctx.Send(delegate
            {
                BaseAddingNew(e);
            }, null);
        }
    }
    void BaseAddingNew(AddingNewEventArgs e)
    {
        base.OnAddingNew(e);
    }
    protected override void OnListChanged(ListChangedEventArgs e)
    {
        if (ctx == null)
        {
            BaseListChanged(e);
        }
        else
        {
            ctx.Send(delegate
            {
                BaseListChanged(e);
            }, null);
        }
    }
    void BaseListChanged(ListChangedEventArgs e)
    {
        base.OnListChanged(e);
    }
}
like image 136
Marc Gravell Avatar answered Nov 11 '22 20:11

Marc Gravell