C# WPF Binding to indexed property - what am I doing wrong?

4.3k views Asked by At

I have recently discovered indexed properties. This looks like the perfect solution to the scenario in which the data I am working with would best be expressed in a collection, yet still needs to be implemented as a property that can be used in XAML databinding. I started with just a test of creating indexed properties, and I had no problems there, but I just don't seem to be able to get the binding to work.

Can anyone point out where I'm going wrong?

Here is the test class with a nested class to create the indexed property:

public class TestListProperty 
{

    public readonly ListProperty ListData;

    //---------------------------
    public class ListProperty : INotifyPropertyChanged 
    {
        private List<string> m_data;

        internal ListProperty()
        {
            m_data = new List<string>();
        }

        public string this[int index]
        {
            get
            {
                if ( m_data.Count > index )
                {
                    return m_data[index]; 
                }
                else
                {
                    return "Element not set for " + index.ToString();
                }
            }
            set
            {
                if ( m_data.Count > index )
                {
                    m_data[index] = value;
                }
                else
                {
                    m_data.Insert( index, value );
                }
                OnPropertyChanged( "Item[]" );
                Console.WriteLine( value );
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

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

    }
    //---------------------------
    public TestListProperty()
    {
        ListData = new ListProperty();
    }

}

Here is the XAML:

<Window x:Class="TestIndexedProperties.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>

        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <TextBox Width="200" Grid.Row="0" Text="{Binding Path=ListData[0], Mode=TwoWay}" />
        <TextBox Width="200" Grid.Row="1" Text="{Binding Path=ListData[1], Mode=TwoWay}" />
        <TextBox Width="200" Grid.Row="2" Text="{Binding Path=ListData[2], Mode=TwoWay}" />


    </Grid>
</Window>

And here is the Window code:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        TestListProperty test = new TestListProperty();

        this.DataContext = test;

        test.ListData[0] = "ABC";
        test.ListData[1] = "Pleeez 2 wurk?";
        test.ListData[2] = "Oh well";

    }
}

Thanks for any and all help!

1

There are 1 answers

3
Mark Feldman On BEST ANSWER

There's no mechanism in your code to indicate to the front-end when the list has changed, i.e. ListProperty is implementing INotifyPropertyChanged instead of INotifyCollectionChanged. The easiest fix would be to change m_data to type ObservableCollection and bind your XAML controls to that instead, although that probably defeats the purpose of what you're trying to do in the first place. The "proper" way is to subscribe to the CollectionChanged event and propagate the messages through:

public class TestListProperty
{
    public ListProperty ListData { get; private set; }

    //---------------------------
    public class ListProperty : INotifyCollectionChanged
    {
        private ObservableCollection<string> m_data;

        internal ListProperty()
        {
            m_data = new ObservableCollection<string>();
            m_data.CollectionChanged += (s, e) =>
            {
                if (CollectionChanged != null)
                    CollectionChanged(s, e);
            };
        }

        public string this[int index]
        {
            get
            {
                if (m_data.Count > index)
                {
                    return m_data[index];
                }
                else
                {
                    return "Element not set for " + index.ToString();
                }
            }
            set
            {
                if (m_data.Count > index)
                {
                    m_data[index] = value;
                }
                else
                {
                    m_data.Insert(index, value);
                }
                Console.WriteLine(value);
            }
        }

        public event NotifyCollectionChangedEventHandler CollectionChanged;

    }
    //---------------------------
    public TestListProperty()
    {
        ListData = new ListProperty();
    }
}