0
votes

I've got ComboBox and ListBox of CheckBoxes. Depending on SelectedItem of ComboBox ItemSource of ListBox must change. I made a sample to make thing easier. Here is the code:

ViewModel

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace Test
{
    class Data
    {
        public long Id;
        public object Value;

        public override string ToString()
        {
            return Value.ToString();
        }
    }

    class CheckedData: INotifyPropertyChanged
    {
        private Data myData;

        public Data MyData
        {
            get { return myData; }
            set
            {
                if (myData == value)
                    return;
                myData = value;
                RaisePropertyChanged(nameof(MyData));
            }
        }
        private bool isChecked;

        public bool IsChecked
        {
            get { return isChecked; }
            set
            {
                isChecked = value;
                RaisePropertyChanged(nameof(IsChecked));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    class BindingObject: INotifyPropertyChanged
    {
        private ObservableCollection<Data> dataList = new ObservableCollection<Data>();

        public ObservableCollection<Data> DataList
        {
            get { return dataList; }
            set
            {
                dataList = value;
                RaisePropertyChanged(nameof(DataList));
            }
        }

        private Data selectedItem;

        public Data SelectedItem
        {
            get { return selectedItem; }
            set
            {
                if (value == selectedItem)
                    return;
                selectedItem = value;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

    }

    class ViewModel: INotifyPropertyChanged
    {
        public ViewModel()
        {
            var tmp = new Data() {Id = 1, Value = "Cat"};
            Obj.DataList.Add(tmp);
            Obj.SelectedItem = tmp;
            Obj.DataList.Add(new Data() {Id = 2, Value = "Dog"});
            Mapping[1] = new ObservableCollection<CheckedData>()
            {
                new CheckedData() {IsChecked = true, MyData = new Data() {Id = 1, Value = "Maine coon"}},
                new CheckedData() {IsChecked = true, MyData = new Data() {Id = 2, Value = "Siberian"}}
            };

        }

        private BindingObject obj = new BindingObject();

        public BindingObject Obj
        {
            get { return obj; }
            set
            {
                if (obj == value)
                    return;
                obj = value;
                RaisePropertyChanged(nameof(Obj));
            }
        }

        private Dictionary<long, ObservableCollection<CheckedData>> mapping = new Dictionary<long, ObservableCollection<CheckedData>>();

        public Dictionary<long, ObservableCollection<CheckedData>> Mapping
        {
            get { return mapping; }
            set
            {
                if (mapping == value)
                    return;
                mapping = value;
                RaisePropertyChanged(nameof(Mapping));
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

View

<ComboBox x:Name="comboBox" ItemsSource="{Binding Path=Obj.DataList}" SelectedItem="{Binding Path=Obj.SelectedItem, Mode=TwoWay}"/>
        <ListBox x:Name="listBox" Height="100" ItemsSource="{Binding Path=Mapping[Obj.SelectedItem.Id]}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}" Content="{Binding Path=MyData.Value}" Margin="0,5,5,0"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

That is what I thought should work. ComboBox is okay, but ListBox ItemSource binding doesn't work. Only if I bind directly to list like this:

ViewModel

private ObservableCollection<CheckedData> test = new ObservableCollection<CheckedData>()
{
    new CheckedData() {IsChecked = true, MyData = new Data() {Id = 1, Value = "Maine coon"}},
    new CheckedData() {IsChecked = false, MyData = new Data() {Id = 2, Value = "Siberian"}}
};

public ObservableCollection<CheckedData> Test
{
    get { return test; }
    set
    {
        test = value;
        RaisePropertyChanged(nameof(Test));
    }
}

View

<ListBox x:Name="listBox" Height="100" ItemsSource="{Binding Path=Test}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}" Content="{Binding Path=MyData.Value}" Margin="0,5,5,0"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Everything starts working.. Except Content binding, because I can't go deeper than 1 level of property path. So I have to override ToString() method in Data.

What should I fix to make everything work? Am I able to bind ItemSource like this? Why can't I go deeper than 1 lvl property binding in CheckBox?

2
This kind of bindings are not supported in XAML: Binding Path=Mapping[Obj.SelectedItem.Id]. You must replace Obj.SelectedItem.Id with a constant value like "1".mm8
And what does "deeper than 1 level" mean? What property do you want to bind the Checkbox to?mm8
@mm8, MyData.ValueKeltar Helviett
You can only bind to properties so Value must be a property. But you must provide a valid binding for the ItemsSource for this to work. See my answer.mm8

2 Answers

0
votes

Am I able to bind ItemSource like this?

No, this kind of bindings are not supported in XAML:

Binding Path=Mapping[Obj.SelectedItem.Id]. 

You must replace Obj.SelectedItem.Id with a constant key value like 1 or bind to some other property that returns the collection of items.

Everything starts working.. Except Content binding

You can only bind to public properties so Value must be a property and not a field:

class Data
{
    public long Id { get; set; }
    public object Value { get; set; }
}
0
votes

You can achive this easy with:

public ObservableCollection<CheckedData> SelectedData 
{ 
   get 
   { 
        return Mapping[Obj.SelectedItem.Id];
   } 
} 

And into

public Data SelectedItem
{
    get { return selectedItem; }
    set
    {
        if (value == selectedItem)
            return;
        selectedItem = value;
        RaisePropertyChanged(nameof(SelectedData)); // add this. 
    }
}

Now, in XAML, you can easy:

<ListBox x:Name="listBox" Height="100" ItemsSource="{Binding Path=Obj.SelectedData}">