Force444
Force444

Reputation: 3381

WPF TreeView CheckBox Binding - How to populate ViewModel with checked boxes

I'm slightly confused about how to set up a CheckBox with a binding that ensures that my ViewModel is populated with all the checked fields. I have provided some of the code and a description at the bottom.

My Xaml file let's call it TreeView.xaml:

<TreeView x:Name="availableColumnsTreeView"          
          ItemsSource="{Binding Path=TreeFieldData, Mode=OneWay, Converter={StaticResource SortingConverter}, ConverterParameter='DisplayName.Text'}">

    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate x:Uid="HierarchicalDataTemplate_1" ItemsSource="{Binding Path=Children, Mode=OneWay, Converter={StaticResource SortingConverter}, ConverterParameter='DisplayName.Text'}">
            <CheckBox VerticalAlignment="Center" IsChecked="{Binding IsSelected, Mode=TwoWay}">
                <TextBlock x:Uid="TextBlock_1" Text="{Binding DisplayName.Text, Mode=OneWay}" />
            </CheckBox>              
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

The "code behind" TreeView.xaml.cs

public partial class MultipleColumnsSelectorView : UserControl
{
    public MultipleColumnsSelectorView()
    {
        InitializeComponent();
    }

    private MultipleColumnsSelectorVM Model
    {
        get { return DataContext as MultipleColumnsSelectorVM; }
    }
}

The ViewModel (tried to include only the relevant stuff) MultipleColumnsSelectorVM:

public partial class MultipleColumnsSelectorVM : ViewModel, IMultipleColumnsSelectorVM
{
    public ReadOnlyCollection<TreeFieldData> TreeFieldData
    {
        get { return GetValue(Properties.TreeFieldData); }
        set { SetValue(Properties.TreeFieldData, value); }
    }

    public List<TreeFieldData> SelectedFields
    {
        get { return GetValue(Properties.SelectedFields); }
        set { SetValue(Properties.SelectedFields, value); }
    }

    private void AddFields()
    {
       //Logic which loops over SelectedFields and when done calls a delegate which passes 
      //the result to another class. This works, implementation hidden
    }

The model TreeFieldData:

public class TreeFieldData : INotifyPropertyChanged
{    
    public event PropertyChangedEventHandler PropertyChanged;
    public IEnumerable<TreeFieldData> Children { get; private set; }
    private bool _isSelected;
    public bool IsSelected
    {
        get { return _isSelected; }
        set
        {
            _isSelected = value;
            if (PropertyChanged != null)
                PropertyChanged.Invoke(this, new PropertyChangedEventArgs("IsSelected"));
        }
    }
}

The Problem:

The behaviour that I want is when the user checks a checkbox, it should set the IsSelected property of TreeField (it does that right now) but then I want to go back to the ViewModel and make sure that this specific TreeField is added to SelectedFields. I don't really understand what the PropertyChangedEvent.Invoke does and who will receive that event? How can I make sure that SelectedFields gets populated so when AddFields() is invoked it has all the TreeField data instances which were checked?

Upvotes: 1

Views: 1380

Answers (2)

ckuri
ckuri

Reputation: 4535

The subscriber of the PropertyChanged event is the view, so that if you change IsSelected programmatically the view knows it needs to update.

To insert the selected TreeField into your list you would add this code to your setter.

Also, you could define the following function which makes the notification much easier if you have many properties:

private void NotifyPropertyChange([CallerMemberName] string propertyName = null)
{
  PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

The CallerMemberName attribute instructs the compiler to automatically insert the name of the property calling the method. The ? after PropertyChanged is a shorthand to your comparison to not null.

The setter of IsSelected can then be changed to

set
{
  _isSelected = value;
  if (value) { viewModel.SelectedFields.Add(this); }
  else { viewModel.SelectedFields.Remove(this); }
  NotifyPropertyChange();
}

Of course you would need to provide the TreeFieldData with the ViewModel instance, e.g. in the constructor.

I don't know if SelectedFields is bounded/shown in your view. If yes and you want the changes made to the list to be shown, you should change List to ObservableCollection.

Upvotes: 0

mm8
mm8

Reputation: 169160

You could iterate through the TreeFieldData objects in the TreeFieldData collection and hook up an event handler to their PropertyChanged event and then add/remove the selected/unselected items from the SelectedFields collection, e.g.:

public MultipleColumnsSelectorVM()
{
    Initialize();

    //do this after you have populated the TreeFieldData collection
    foreach (TreeFieldData data in TreeFieldData)
    {
        data.PropertyChanged += OnPropertyChanged;
    }
}

private void OnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    if (e.PropertyName == "IsSelected")
    {
        TreeFieldData data = sender as TreeFieldData;
        if (data.IsSelected && !SelectedFields.Contains(data))
            SelectedFields.Add(data);
        else if (!data.IsSelected && SelectedFields.Contains(data))
            SelectedFields.Remove(data);
    }
}

Upvotes: 2

Related Questions