views:

315

answers:

1

I have a treeview which shows a hierarchy of items where each item has a checkbox. I would like to show below the treeview a listbox with all the checked items. How to achieve such functionality using MVVM pattern?

Thanks in advance Lukasz Glaz

A: 

Here's an example:

ViewModel

public class TreeNodeViewModel : ViewModelBase
{
    #region Constructors

    public TreeNodeViewModel(string text, params TreeNodeViewModel[] nodes)
        : this(text, new ObservableCollection<TreeNodeViewModel>(nodes))
    {
    }

    public TreeNodeViewModel(string text, ObservableCollection<TreeNodeViewModel> nodes)
    {
        Text = text;
        Nodes = nodes;
        foreach (var node in Nodes)
        {
            node.Parent = this;
        }
        Nodes.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Nodes_CollectionChanged);
    }

    #endregion

    #region Private methods

    private void Nodes_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        foreach (var node in e.OldItems.Cast<TreeNodeViewModel>())
        {
            node.Parent = null;
        }
        foreach (var node in e.NewItems.Cast<TreeNodeViewModel>())
        {
            node.Parent = this;
        }
        OnPropertyChanged("CheckedNodes");
    }

    private void NotifyParent()
    {
        if (Parent != null)
        {
            Parent.OnPropertyChanged("CheckedNodes");
            Parent.NotifyParent();
        }
    }

    #endregion

    #region Private data

    private string _text;
    private bool _isChecked;
    private TreeNodeViewModel _parent;

    #endregion

    #region Public properties

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

    public bool IsChecked
    {
        get { return _isChecked; }
        set
        {
            if (value != _isChecked)
            {
                _isChecked = value;
                NotifyParent();
                OnPropertyChanged("IsChecked");
            }
        }
    }

    public ObservableCollection<TreeNodeViewModel> Nodes { get; private set; }

    public IEnumerable<TreeNodeViewModel> CheckedNodes
    {
        get
        {
            foreach (var node in Nodes)
            {
                if (node.IsChecked)
                    yield return node;
                foreach (var child in node.CheckedNodes)
                {
                    yield return child;
                }
            }
        }
    }

    public TreeNodeViewModel Parent
    {
        get { return _parent; }
        private set
        {
            if (value != _parent)
            {
                _parent = value;
                OnPropertyChanged("Parent");
            }
        }
    }

    #endregion
}

XAML

    <TreeView Grid.Row="0" ItemsSource="{Binding Nodes}">
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
                <StackPanel Orientation="Horizontal">
                    <CheckBox IsChecked="{Binding IsChecked}" />
                    <TextBlock Text="{Binding Text}" />
                </StackPanel>
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
    <ListBox Grid.Row="1" ItemsSource="{Binding CheckedNodes}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Text}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

Code-behind

        this.DataContext = new TreeNodeViewModel(
            null,
            new TreeNodeViewModel(
                "1",
                new TreeNodeViewModel(
                    "1.1",
                    new TreeNodeViewModel("1.1.1"),
                    new TreeNodeViewModel("1.1.2")),
                new TreeNodeViewModel("1.2")),
            new TreeNodeViewModel(
                "2",
                new TreeNodeViewModel("2.1"),
                new TreeNodeViewModel(
                    "2.2",
                    new TreeNodeViewModel("2.2.1"))));

Note that it is a rather naive implementation, it could easily be improved... For instance, the whole list of checked nodes is reevaluated every time a node is checked/unchecked, which could lead to bad performance for a big TreeView

Thomas Levesque
Thank you for your very detailed and easy to understand example :)
GUZ