0

Using the MVVM pattern, I am trying to build a TreeView in XAML that looks like this (Note that bracketed items are actually representative of buttons):

- [Edit] [Delete] Location 1
    - [Edit] [Delete] Location 1, Project 1
    - [Edit] [Delete] Location 1, Project 2
    - [Edit] [Delete] Location 1, Project 3
    - [New Project]
- [Edit] [Delete] Location 2
    - [Edit] [Delete] Location 2, Project 1
    - [New Project]
- [Edit] [Delete] Location 3
    - [Edit] [Delete] Location 3, Project 1
    - [Edit] [Delete] Location 3, Project 2
    - [New Project]
- [New Location]

The idea is that each node in the TreeView has two buttons next to the header text that a user can click to delete/edit the node. Then, at the bottom of each "group" of projects, there is a [New Project] button that can be clicked to add a new project to that "group". Finally, there is a [New Location] button at the bottom of the entire TreeView so that a new Location node can be added.

I have the TreeView and the [New Location] button laid out correctly, but can't figure out how to add the [New Project] button to the bottom of the group of projects for each Location. I've tried using multiple nested TreeView controls, but then the grouping and scrolling don't seem to function as expected.

Here is my MainPageView code:

<UserControl xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"  x:Class="SilverlightApplication2.MainPageView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:silverlightApplication2="clr-namespace:SilverlightApplication2"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <UserControl.DataContext>
        <silverlightApplication2:MainPageViewModel/>
    </UserControl.DataContext>

    <StackPanel Orientation="Vertical">
        <sdk:TreeView ItemsSource="{Binding Locations}" BorderBrush="Transparent">
            <sdk:TreeView.ItemTemplate>
                <sdk:HierarchicalDataTemplate ItemsSource="{Binding Projects}">
                    <StackPanel Orientation="Horizontal">
                        <Button Margin="2">
                            <Image Source="edit.png"/>
                            <ToolTipService.ToolTip>
                                <ToolTip Content="Edit item"/>
                            </ToolTipService.ToolTip>
                        </Button>
                        <Button Margin="2">
                            <Image Source="delete.png"/>
                            <ToolTipService.ToolTip>
                                <ToolTip Content="Delete item"/>
                            </ToolTipService.ToolTip>
                        </Button>
                        <sdk:TreeViewItem Header="{Binding Name}"/>
                    </StackPanel>
                </sdk:HierarchicalDataTemplate>
            </sdk:TreeView.ItemTemplate>
        </sdk:TreeView>
        <Button Margin="30,0,0,0" Height="23" Width="23" HorizontalAlignment="Left">
            <Image Source="green-plus-sign-md.png"></Image>
            <ToolTipService.ToolTip>
                <ToolTip Content="Add new location"/>
            </ToolTipService.ToolTip>
        </Button>
    </StackPanel>
</UserControl>

Here is my MainPageViewModel code:

using System.Collections.ObjectModel;

namespace SilverlightApplication2
{
    public class MainPageViewModel
    {
        #region Constructors

        public MainPageViewModel()
        {
            this.Locations = new ObservableCollection<LocationViewModel>
                                 {
                                     new LocationViewModel
                                         {
                                             Name = "Location 1",
                                             Projects = new ObservableCollection<ProjectViewModel>
                                                            {
                                                                new ProjectViewModel { Name = "Location 1 - Project 1"},
                                                                new ProjectViewModel { Name = "Location 1 - Project 2"},
                                                                new ProjectViewModel { Name = "Location 1 - Project 3"},
                                                                new ProjectViewModel { Name = "Location 1 - Project 4"},
                                                            }
                                         },
                                     new LocationViewModel
                                         {
                                             Name = "Location 2",
                                             Projects = new ObservableCollection<ProjectViewModel>
                                                            {
                                                                new ProjectViewModel { Name = "Location 2 - Project 1"},
                                                                new ProjectViewModel { Name = "Location 2 - Project 2"}
                                                            }
                                         },
                                     new LocationViewModel
                                         {
                                             Name = "Location 3",
                                             Projects = new ObservableCollection<ProjectViewModel>
                                                            {
                                                                new ProjectViewModel { Name = "Location 3 - Project 1"},
                                                                new ProjectViewModel { Name = "Location 3 - Project 2"},
                                                                new ProjectViewModel { Name = "Location 3 - Project 3"},
                                                                new ProjectViewModel { Name = "Location 3 - Project 4"},
                                                                new ProjectViewModel { Name = "Location 3 - Project 5"},
                                                                new ProjectViewModel { Name = "Location 3 - Project 6"},
                                                                new ProjectViewModel { Name = "Location 3 - Project 7"}
                                                            }
                                         }
                                 };
        }

        #endregion

        #region Properties

        public ObservableCollection<LocationViewModel> Locations { get; set; } 

        #endregion
    }
}

Here is the LocationViewModelCode:

using System.Collections.ObjectModel;
using Microsoft.Practices.Prism.ViewModel;


namespace SilverlightApplication2
{
    public class LocationViewModel : NotificationObject
    {
        #region Properties

        public string Name { get; set; }

        public ObservableCollection<ProjectViewModel> Projects { get; set; }

        #endregion
    }
}

And finally, the ProjectViewModel code:

namespace SilverlightApplication2
{
    public class ProjectViewModel
    {
        #region Properties

        public string Name { get; set; }

        #endregion
    }
}

As you can see, I am doing this in Silverlight, but a WPF solution would probably be just as good if somebody knows how to do that.

I also realize that this can probably be done pretty easily with code-behind, but I am trying to stay true to the MVVM pattern and find a way to do this purely in XAML.

Thanks for any help.

meyousikmann
  • 163
  • 4
  • 14
  • http://stackoverflow.com/questions/24569156/dispay-treeviewitem-as-grid-rows-in-wpf/24571956#24571956 and added extra stackpanel http://prntscr.com/42wuhe – Heena Jul 15 '14 at 14:30
  • The usage of code-behind is neither against the MVVM pattern nor the opposite. – Martin Jul 15 '14 at 14:47
  • @HeenaPatil - I think your solution would work fine for a single level TreeView. Mine has multiple levels so I am using a HierarchicalDataTemplate to describe how to display the multiple levels. I may be wrong, but simply adding a StackPanel to the hierarchical list will produce an "Add new.." button at each node rather than at the bottom of the group of nodes. – meyousikmann Jul 16 '14 at 13:42
  • @Martin - Point taken; however, I strongly believe that the best way to follow the MVVM pattern is to do it in the XAML if at all possible and only in the code-behind if there is no other way. I fully appreciate that this is a philosophical debate that would carry on forever so I will just leave it at that. – meyousikmann Jul 16 '14 at 13:44

2 Answers2

0

I'd generalize the view model for the tree node.

Perhaps having IsLocation, IsProject and HasContent properties.

The HasContent property can control whether the node shows the 'New...' link. Then you can check what type it is in the command for that link.

If you don't want to have IsLocation and IsProject, you could use an abstract base class for a tree node and have two types inherit from it. Both having similar logic for HasContent and then having different commands for the 'New' logic.

Marlon
  • 2,129
  • 3
  • 21
  • 40
  • I like your ideas. I was hoping for a quick way to do this just in XAML code, but perhaps a bit of redesign of my view models is in order. I will give your ideas some thought and see what I can come up with. – meyousikmann Jul 16 '14 at 13:48
  • If you go down the route of having an abstract base class, I believe WPF supports have templates for a given Type. So you could have templates defined for a Project node and a Location node. – Marlon Jul 16 '14 at 14:55
0

Maybe a rethink of the architecture will help?

Personally I'd consider the 'new project' operation to be an action on a Location object, not something that sits within the Projects collection. That being the case, your 'new project' buttons should probably sit with the edit and delete buttons on the root of Location.

Not a direct answer to your question maybe, but a side-ways approach to it.

Mashton
  • 6,037
  • 2
  • 25
  • 35
  • I totally agree; however, I have a UI spec that I can't change and that spec puts the New Project button at the bottom of the list of projects for each location. – meyousikmann Jul 16 '14 at 13:45