0
votes

I am having an issue binding a listbox to an ObservableCollection several classes deep, basically like:

Device.State.Menu.Items

As a test if I create a standalone Items collection on my form and bind to it then it does work.

If I use my full object and try to bind to the path above it doesn't work. I see it try to raise the events, but the PropertyChangedEventHandler is null like it isn't not bound so the list doesn't refresh. I've tried different ways of handling the Items class, with observablecollection/inotify. Here is a stripped down version:

    public class NetworkMenu
    {
        public string Title = "";
        private ObservableCollection<NetworkMenuItem> _items = new ObservableCollection<NetworkMenuItem>();
        public ObservableCollection<NetworkMenuItem> Items
        {
            get { return _items; }
        }
    }

    public class NetworkMenuItem : INotifyPropertyChanged
    {
        private int _index = 0;
        private string _title = "";
        private string _code = "";

        public event PropertyChangedEventHandler PropertyChanged;

        public NetworkMenuItem(int index, string title, string code = "")
        {
            _index = index;
            _title = title;
            _code = code;
        }

        public int Index
        {
            get { return _index; }
            set
            {
                if (_index != value)
                {
                    _index = value;
                    OnPropertyChanged("Index");
                }
            }
        }

        public string Title
        {
            get { return _title; }
            set
            {
                if (_title != value)
                {
                    _title = value;
                    OnPropertyChanged("Title");
                }
            }
        }

        public string Code { get; set; }

        protected virtual void OnPropertyChanged(string property)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(property));
        }
    }

This is how I'm binding it:

        this.Browser.ItemsSource = device.State.Menu.Items;

PropertyChangedEventHandler is null, like it's not bound.

However in my test if I bypass the device class and just create an Items collection on my main form, manually adding menu items by pressing a button:

    ObservableCollection<Device.NetworkMenuItem> items = new ObservableCollection<Device.NetworkMenuItem>();

The binding works

I'm still having issues - I changed the data context to the device

Changed the XAML:

    <ListBox HorizontalAlignment="Left" Height="655" Margin="224,57,0,0" VerticalAlignment="Top" Width="370" x:Name="Browser" ItemsSource="{Binding State.Menu.Items}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Path=Title}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

Changed state under device:

    private DeviceState _state = new DeviceState();
    public DeviceState State
    {
        get { return _state; }
        set { _state = value; }
    }

Changed menu under state:

        private NetworkMenu _menu = new NetworkMenu();
        public NetworkMenu Menu
        {
            get { return _menu; }
            set { _menu = value; }
        }

Another update, further testing shows binding does work until I create a connection, so I believe it's an issue with tasks/threading, which I haven't ever dealt with coming from mainly .net 2.0, and I'm re-writing an older silverlight WP7 app.

As soon as I start my loop to monitor the sockets binding stops working:

            await Task.Factory.StartNew(WaitForMessage);

Everything else works as expected

    public async void Connect(string p_hostName, string p_port)
    {
        hostName = p_hostName;
        port = p_port;

        socket = new StreamSocket();
        try
        {                
            await socket.ConnectAsync(new HostName(hostName), port);
            OnConnect(new ConnectArgs(true));
            await Task.Factory.StartNew(WaitForMessage);
        }
        catch (Exception ex)
        {
            Debug.WriteLine("Connect Error: " + ex.Message);
            OnConnect(new ConnectArgs(false));
        }
    }

    private async void WaitForMessage()
    {
        if (socket == null)
            return;

        string trailingMessage = null;

        DataReader reader = new DataReader(socket.InputStream);
        reader.InputStreamOptions = InputStreamOptions.Partial;
        //try
        {
            while (true)
            {
                await reader.LoadAsync(BufferSize);

                byte[] bData = new byte[reader.UnconsumedBufferLength];
                reader.ReadBytes(bData);

                string data = Encoding.UTF8.GetString(bData, 0, bData.Length);

                bool bufferWasPreviouslyFull = !string.IsNullOrEmpty(trailingMessage);
                if (bufferWasPreviouslyFull)
                {
                    trailingMessage = null;
                }

                if (string.IsNullOrWhiteSpace(data))
                {
                    OnDisconnect(new EventArgs());
                    break;
                }

                var messages  = new List<string>(data.Split("\n\r".ToCharArray(), StringSplitOptions.None));

                var lastMessage = messages.LastOrDefault();
                bool isBufferFull = !string.IsNullOrWhiteSpace(lastMessage);
                if (isBufferFull)
                {
                    trailingMessage = lastMessage;
                    messages.Remove(lastMessage);
                }

                foreach (var message in messages)
                {
                    if (string.IsNullOrWhiteSpace(message))
                        continue;

                    ProcessMessage(message);
                }

            }
        }
        //catch (Exception ex)
        //{
        //    Debug.WriteLine("WaitForMessage Error: " + ex.Message);
        //    OnDisconnect(new EventArgs());
        //}
    }

Final update ... working now. There was an additional issue with how the menu was being reset, so using properties as noted and using itemssource = device.State.Menu.Items, no datacontext, it's now working correctly

1
this.Browser.ItemsSource = device.State.Menu.Items; --> this is not BINDING you simply SET the itemssource. - blindmeis
having issues does not explain much, if you datacontext is set to a device instance you should see binding errors, in the debug output window - Xi Sigma
By that I mean it's doing the same thing as before. Still nothing in the debug output window, changing the level from warning to all - Jason

1 Answers

2
votes

Your Items ObservableCollection is a field, make it a Property, you can't bind to fields only to Properties.

as per your last update you are not really binding anything, make it this.DataContext = device;

in your XAML bind the items source as

<ListBox ItemsSource="{Binding State.Menu.Items}"></ListBox>

and make sure that State Menu and Items are public properties.