1

This question is a follow up to this question. I've taken some advice from this site and decided to start learning MVVM implementation for my work with TreeViews. With that being said I am VERY new to MVVM and I'm still getting familiar with the syntax and implementation.

I have a TreeView that displays integer-type data but I would like it to work with strings instead. The tree also allows the user to add to any level by selecting the TreeViewItem and then typing in the new integer header into the textBox, and then clicking a button.

Example Image

I'd like for a parent, child, and grandchild to be available at start-up with pre-defined names. Another thing to note is that that user will only be able to add to the TreeView at the level of the child (so they'll only be able to add grandchildren).

Model

public class TreeViewModel : PropertyChangedBase
{
    public string Value { get; set; }

    public ObservableCollection<TreeViewModel> Items { get; set; }

    public CollectionView ItemsView { get; set; }

    public TreeViewModel(string value)
    {
        Items = new ObservableCollection<TreeViewModel>();
        ItemsView = new ListCollectionView(Items)
        {
            SortDescriptions =
            {
                new SortDescription("Value",ListSortDirection.Ascending)
            }
        };
        Value = value;
    }
}

ViewModel

public class SortedTreeViewWindowViewModel : PropertyChangedBase
{
    private string _newValueString;

    public string NewValueString
    {
        get { return _newValueString; }
        set
        {
            _newValueString = value;

            OnPropertyChanged("NewValueString");
        }
    }

    public TreeViewModel SelectedItem { get; set; }

    public ObservableCollection<TreeViewModel> Items { get; set; }

    public ICollectionView ItemsView { get; set; }

    public SortedTreeViewWindowViewModel()
    {
        Items = new ObservableCollection<TreeViewModel>();
        ItemsView = new ListCollectionView(Items) { SortDescriptions = { new SortDescription("Value", ListSortDirection.Ascending) } };
    }

    public void AddNewItem()
    {
        ObservableCollection<TreeViewModel> targetcollection;

        //Insert the New Node as a Root node if nothing is selected.
        targetcollection = SelectedItem == null ? Items : SelectedItem.Items;

        if (_newValueString != null)
        {
            targetcollection.Add(new TreeViewModel(_newValueString));
            NewValueString = string.Empty;
        }
    }
}

View Code-Behind

public partial class Window1 : Window
{
    public SortedTreeViewWindowViewModel ViewModel { get { return DataContext as SortedTreeViewWindowViewModel; } set { DataContext = value; } }

    public Window1()
    {
        InitializeComponent();
        ViewModel = new SortedTreeViewWindowViewModel()
            {
                Items = {new TreeViewModel("Test")}
            };
    }

    private void AddNewItem(object sender, RoutedEventArgs e)
    {
        ViewModel.AddNewItem();
    }

    private void OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        ViewModel.SelectedItem = e.NewValue as TreeViewModel;
    }
}

Thanks so much for the help. I'm hoping that going through this will help me understand how to edit and build ViewModels so that I can learn to improvise a little more in the future.

UPDATE

The TreeView is now composed of strings, so that part is solved. I still need help with the default nodes though. I have updated my code to reflect this change.

Community
  • 1
  • 1
Eric after dark
  • 1,768
  • 4
  • 31
  • 79
  • Can you not just change all of the type declarations from int to string? – Tyler Morrow Jul 31 '13 at 14:42
  • Sure, but I still don't know how to deal with the default `TreeViewItems` at start up. – Eric after dark Jul 31 '13 at 14:44
  • Another comment: I see that you are new to MVVM, but be aware that when implementing MVVM, you should rarely need any code-behind other than setting the DataContext to a ViewModel. – Tyler Morrow Jul 31 '13 at 15:13
  • I went through an example like that, but for some reason the answer that I got this code from put that stuff in the code-behind. So I really don't know where else to put it. – Eric after dark Jul 31 '13 at 15:18
  • Do you want the default nodes gone on startup? – Tyler Morrow Jul 31 '13 at 15:19
  • No, I would like my `TreeView` to have a child and grandchild by default at startup. – Eric after dark Jul 31 '13 at 15:22
  • Whenever you create those default nodes at startup, instead of setting them to contain integer data, set them to contain string data. – Tyler Morrow Jul 31 '13 at 15:25
  • I believe that I've completed that step. Please check my updated code. – Eric after dark Jul 31 '13 at 15:32
  • So what problem remains? – Tyler Morrow Jul 31 '13 at 15:33
  • @TheRedLou sorry, you're wrong. Code behind is perfectly valid in MVVM, the same way Bindings are valid in MVVM. Code behind is valid when you deal with UI Element's limitations (such as `TreeView`, which does not have a Bindable `SelectedItem` property). Code behind however must NOT contain business logic / data manipulation. It must delegate that to the ViewModel. – Federico Berasategui Jul 31 '13 at 15:36
  • @HighCore I never said it was invalid and in THIS case he could make use of Commands for what he's doing in the code-behind which is typically preferred. – Tyler Morrow Jul 31 '13 at 15:40
  • Currently the program starts with one lone parent `TreeViewItem`, I want it to start with a parent -> child -> grandchild from startup. – Eric after dark Jul 31 '13 at 15:42

1 Answers1

3

Here are my suggestions

  • Change the places where you have int to string. The TreeView should handle that change.
  • In the constructor of your ViewModel, manually insert your default nodes. Make sure you understand how to work with a TreeView as that will affect the design of your Model and ViewModel and naturally improve the implementation.

Here is a very simple example how to fill a tree in the ViewModel which is bound to a TreeView in the View:

View

<Window x:Class="TreeViewExample.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">
    <StackPanel>
        <StackPanel>
            <TreeView ItemsSource="{Binding Tree}"/>
        </StackPanel>
    </StackPanel>
</Window>

View Code-Behind

namespace TreeViewExample
{
    using System.Windows;

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            DataContext = new MainWindowViewModel();
            InitializeComponent();
        }
    }
}

ViewModel

namespace TreeViewExample
{
    using System.Collections.ObjectModel;
    using System.Windows.Controls;

    class MainWindowViewModel
    {
        public ObservableCollection<TreeViewItem> Tree { get; set; }

        public MainWindowViewModel()
        {
            Tree = new ObservableCollection<TreeViewItem>();
            Tree.Add(GetLoadedTreeRoot());
        }

        private TreeViewItem GetLoadedTreeRoot()
        {
            TreeViewItem parent = new TreeViewItem() { Header = "Parent" };
            TreeViewItem child1 = new TreeViewItem() { Header = "Child 1" };
            TreeViewItem child2 = new TreeViewItem() { Header = "Child 2" };
            TreeViewItem grandchild1 = new TreeViewItem() { Header = "Grandchild 1" };
            TreeViewItem grandchild2 = new TreeViewItem() { Header = "Grandchild 2" };

            child1.Items.Add(grandchild1);
            child2.Items.Add(grandchild2);
            parent.Items.Add(child1);
            parent.Items.Add(child2);
            return parent;
        }
    }
}

Produces:

  • Parent
    • Child 1
      • Grandchild 1
    • Child 2
      • Grandchild 2

Additional thoughts:

  • To clean up your code-behind, you might look up a Command implementation, of which there are many. Although you sometimes need it, avoid code in the code-behind when possible. I really like this example because it shows you a general MVVM implementation without getting into advanced Command-related topics (ItemTemplates, Interactivity namespace, etc.).
Tyler Morrow
  • 949
  • 8
  • 31
  • Thanks a lot. For `AddNewItem` do I need to do something like, `Items.AddNewItem(new TreeViewItem("New Node"));` Or am I way off? – Eric after dark Jul 31 '13 at 15:56
  • @TheRedLou I know about commands, but I don't suggest that to beginners because it's way too much info (databinding, ItemTemplates, etc). And having to add a reference to System.Windows.Interactivity for the `InvokeCommandAction` in the case of events complicates it all too much. – Federico Berasategui Jul 31 '13 at 16:00
  • I REALLY get what you're saying, and I appreciate the links, but nonetheless I'm still fumbling around trying to add the root, child, and grandchild nodes. If you could just include some code in your answer of where and how to implement this I might have an idea. EDIT: All I have in my `ViewModel` constructor is a `TreeViewModel`, not really a `TreeViewItem` to add to. – Eric after dark Jul 31 '13 at 18:04
  • Provided a code example and rewrote answer to be more direct. – Tyler Morrow Jul 31 '13 at 18:31
  • I called to `LoadTreeExample()` right after my `Items = new ObservableCollection();` and `ItemsView = new ListCollectionView(Items);` and nothing showed up. Are those two lines of code interfering with the `TreeView` that you are adding? – Eric after dark Jul 31 '13 at 18:50
  • Yet another example posted in my answer to help. Take my examples and apply them to your situation. I'm trying to show how to use the tools so YOU can apply them to your situation. – Tyler Morrow Jul 31 '13 at 19:23
  • Thank you, thank you, thank you! The only thing I had to work with was the the `ViewModel` from the previous answer, which had some other stuff going on that confused me. – Eric after dark Jul 31 '13 at 19:37