HierarchicalDataTemplate with Treeview in MVVM - wpf

I'm relatively new to MVVM and WPF. I'm attempting to fill a TreeView control with a directory and it's files / subdirectories (in effect the contents of a zip file that I have unpacked)
Following along after this SO question, I have the following class:
namespace IFR_Full.Model
{
public class Item
{
public string Name { get; set; }
public string Path { get; set; }
}
public class FileItem : Item
{
}
public class DirectoryItem : Item
{
public List<Item> Items { get; set; }
public DirectoryItem()
{
Items = new List<Item>();
}
}
public class TVItemProvider
{
public List<Item> GetItems(string path)
{
var items = new List<Item>();
var dirInfo = new DirectoryInfo(path);
foreach (var directory in dirInfo.GetDirectories())
{
var item = new DirectoryItem
{
Name = directory.Name,
Path = directory.FullName,
Items = GetItems(directory.FullName)
};
items.Add(item);
}
foreach (var file in dirInfo.GetFiles())
{
var item = new FileItem
{
Name = file.Name,
Path = file.FullName
};
items.Add(item);
}
return items;
}
}
}
In my ViewModel class I have these properties:
TVItemProvider TVIP = new TVItemProvider();
private List<Item> _tvitems;
public List<Item> TVItems
{
get { return _tvitems; }
}
which is created in this method:
private void LoadIDMLTreeView(string path)
{
_tvitems = TVIP.GetItems(path);
}
I set the header and DataContext of my MainWindow like this:
...
xmlns:ViewModel="clr-namespace:IFR_Full"
xmlns:Model ="clr-namespace:IFR_Full.Model"
...
<Window.DataContext>
<ViewModel:ExcelImportViewModel/>
</Window.DataContext>
and set my treeview xaml code like this:
<TreeView ItemsSource="{Binding}" Name="IDMLView" Margin="10,171.74,10,8" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type Model:DirectoryItem}" ItemsSource="{Binding Path=TVItems}">
<TextBlock Text="{Binding Path=Name}" ToolTip="{Binding Path=Path}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type Model:FileItem}">
<TextBlock Text="{Binding Path=Name}" ToolTip="{Binding Path=Path}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
When I run the program in debug mode I can see that TVItems contains the appropriate items (Directories and files), but my TreeView control is blank.
I imagine that the issue is with the bindings?

Change <TreeView ItemsSource="{Binding}" ... to <TreeView ItemsSource="{Binding TVItems}" ...
Also , Change to <HierarchicalDataTemplate DataType="{x:Type local:DirectoryItem}" ItemsSource="{Binding Items}" >
Your class has to be like this :
public class TVItemProvider
{
List<object> items = new List<object>();
DirectoryInfo dirInfo;
public List<object> GetItems(string path)
{
dirInfo = new DirectoryInfo(path);
foreach (var directory in dirInfo.GetDirectories())
{
var item = new DirectoryItem
{
Name = directory.Name,
Path = directory.FullName,
Items = new TVItemProvider().GetItems(directory.FullName)
};
items.Add(item);
}
foreach (var file in dirInfo.GetFiles())
{
var item = new FileItem
{
Name = file.Name,
Path = file.FullName
};
items.Add(item);
}
return items;
}
}
Finally change the type of your lists to List<object> (all of them)
Hope it would help

Related

Bind WPF TreeView on button click with MVVM

I am building WPF application where I am using MVVM pattern for data binding and command binding.
When I bind the tree view in a constructor(LoadTreeViewViewModel) it works without any issue but when I do the same in Button Click it is not working. I did a little bit of research where I am also binding to listview and binding is working without issue for listview on button click. So the problem is only with TreeView binding
Below is the complete code for the sample application.
XAML
<Window x:Class="WpfApp1.LoadTreeView"
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:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="LoadTreeView" Height="300" Width="300">
<Window.DataContext>
<local:LoadTreeViewViewModel></local:LoadTreeViewViewModel>
</Window.DataContext>
<Window.Resources>
<local:LoadTreeViewViewModel x:Key="viewModel"></local:LoadTreeViewViewModel>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="5*"></RowDefinition>
<RowDefinition Height="5*"></RowDefinition>
<RowDefinition Height="1*"></RowDefinition>
</Grid.RowDefinitions>
<TreeView ItemsSource="{Binding Folders}" Grid.Column="0" Grid.Row="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Folders}" DataType="{x:Type local:IFolder}">
<TreeViewItem Header="{Binding FolderLabel}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<ListView VerticalAlignment="Top" Grid.Column="0" Grid.Row="1"
ItemsSource="{Binding Lists, Source={StaticResource viewModel}}">
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75"
Grid.Column="0" Grid.Row="2"
Command="{Binding LoadSourceCommand, Source={StaticResource viewModel}}"/>
</Grid>
</Window>
LoadTreeView
public partial class LoadTreeView : Window
{
public LoadTreeView()
{
InitializeComponent();
//this.DataContext = new LoadTreeViewViewModel();
}
}
IFolder
public interface IFolder
{
List<IFolder> Folders { get; set; }
string FolderLabel { get; set; }
string FullPath { get; set; }
}
Folder
public class Folder : IFolder
{
public List<IFolder> Folders { get; set; }
public string FolderLabel { get; set; }
public string FullPath { get; set; }
public Folder()
{
Folders = new List<IFolder>();
}
}
LoadTreeViewViewModel
class LoadTreeViewViewModel : INotifyPropertyChanged
{
public LoadTreeViewViewModel()
{
this.LoadSourceCommand = new ViewModel.btnClick(this.LoadData, this.IsValid);
//this.Folders = await LoadDataAsync();
//LoadData();
}
private async void LoadData()
{
this.Folders = await LoadTreeAsync();
this.Lists = await LoadListAsync();
}
private async Task<string[]> LoadListAsync()
{
List<string> temp = new List<string>();
await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
//add Root items
temp.Add("Dummy1");
temp.Add("Dummy2");
temp.Add("Dummy3");
temp.Add("Dummy4");
return temp.ToArray();
}
private async Task<List<IFolder>> LoadTreeAsync()
{
List<IFolder> temp = new List<IFolder>();
await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
//add Root items
temp.Add(new Folder { FolderLabel = "Dummy1", FullPath = #"C:\dummy1" });
temp.Add(new Folder { FolderLabel = "Dummy2", FullPath = #"C:\dummy2" });
temp.Add(new Folder { FolderLabel = "Dummy3", FullPath = #"C:\dummy3" });
temp.Add(new Folder { FolderLabel = "Dummy4", FullPath = #"C:\dummy4" });
//add sub items
temp[0].Folders.Add(new Folder { FolderLabel = "Dummy11", FullPath = #"C:\dummy11" });
temp[0].Folders.Add(new Folder { FolderLabel = "Dummy12", FullPath = #"C:\dummy12" });
temp[0].Folders.Add(new Folder { FolderLabel = "Dummy13", FullPath = #"C:\dummy13" });
temp[0].Folders.Add(new Folder { FolderLabel = "Dummy14", FullPath = #"C:\dummy14" });
return temp;
}
private bool IsValid()
{
return true;
}
#region Members
private ViewModel.btnClick loadSourceCommand;
public btnClick LoadSourceCommand
{
get { return loadSourceCommand; }
set { loadSourceCommand = value; }
}
private List<IFolder> m_folders;
public List<IFolder> Folders
{
get { return m_folders; }
set
{
m_folders = value;
NotifiyPropertyChanged("Folders");
}
}
private string[] lists;
public string[] Lists
{
get { return lists; }
set { lists = value; NotifiyPropertyChanged("Lists"); }
}
#endregion
void NotifiyPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public event PropertyChangedEventHandler PropertyChanged;
}
btnClick
public class btnClick : System.Windows.Input.ICommand
{
private Action WhatToExecute;
private Func<bool> WhenToExecute;
public btnClick(Action what, Func<bool> when)
{
WhatToExecute = what;
WhenToExecute = when;
}
public void Refresh()
{
if (this.CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return WhenToExecute();
}
public void Execute(object parameter)
{
WhatToExecute();
}
}
You likely have a binding error.
Check the output log for and BindingExpression errors.
I suspect you want to use viewModel instance you've defined, rather than the window's datacontext, because they're going to be two different instances.
<TreeView ItemsSource="{Binding Folders, Source={StaticResource viewModel}}"
Grid.Column="0"
Grid.Row="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Folders}"
DataType="{x:Type local:IFolder}">
<TreeViewItem Header="{Binding FolderLabel}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

Unable to cast object of type 'Model.TreeModel' to type 'ViewModels.TreeViewModel'

I am trying to create objects from another object which has a list of objects. But I am getting a casting error on the line where I do the casting. Is there a way to cast object of different collection on to another collection.
Code:
public List<TreeViewModel> getAllTreeNodesFromModel()
{
treeNodeViewModel = treeModel.getTreeNodes().Select(a => new TreeViewModel
{
Children = a.Children.Select(c => new TreeViewModel{Value = c.Value}).ToList(),
Value = a.Value,
}).ToList();
return treeNodeViewModel;
}
// Making list observable
public ObservableCollection<TreeViewModel> TreeView
{
get
{
return treeView;
}
set
{
if (treeView == value) return;
treeView = value;
NotifyPropertyChanged("TreeView");
}
}
XAML:
<TreeView Margin="644,137,6,6" Grid.RowSpan="2" ItemsSource="{Binding TreeView}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MainWindowModel}" ItemsSource="{Binding Children}">
<CheckBox IsChecked="{Binding Value}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Here is my model:
public class TreeModel
{
public string Name { get; set; }
public List<TreeModel> Children { get; set; }
public string Value { get; set; }
public TreeModel()
{
Children = new List<TreeModel>();
}
public TreeModel(string name, List<TreeModel> children)
{
this.Name = name;
this.Children = children;
}
public List<TreeModel> BuildTree(IEnumerable<string> strings)
{
return (
from s in strings
let split = s.Split('.')
group s by s.Split('.')[0] into g // Group by first component (before /)
select new TreeModel
{
Value = g.Key,
Children = BuildTree( // Recursively build children
from s in g
where s.Length > g.Key.Length + 1
select s.Substring(g.Key.Length + 1)) // Select remaining components
}
).ToList();
}
// View Model
public string Value { get; set; }
public List<TreeViewModel> Children { get; set; }
// List VM mapped from Model
public List<TreeViewModel> getAllTreeNodesFromModel()
{
treeNodeViewModel = treeModel.getTreeNodes().Select(a => new TreeViewModel
{
Children = a.Children.Cast<TreeViewModel>().ToList(),
Value = a.Value,
}).ToList();
return treeNodeViewModel;
}
XAML:
<TreeView Margin="644,137,6,6" Grid.RowSpan="2" ItemsSource="{Binding TreeView}" >
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MainWindowModel}" ItemsSource="{Binding Children}">
<CheckBox IsChecked="{Binding Value}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Copying answer from comment:
You should create TreeViewModel for each model's child, not cast:
Children = a.Children.Select(c => new TreeViewModel{Value = c.Value}).ToList()
As Children are recursive it would better to write it that way:
public List<TreeViewModel> getAllTreeNodesFromModel()
{
treeNodeViewModel = treeModel.getTreeNodes().Select(a => getChildTreeNodesFromModel(a)).ToList();
return treeNodeViewModel;
}
public List<TreeViewModel> getChildTreeNodesFromModel(TreeModel a_model)
{
return new TreeViewModel
{
Value = a_model.Value,
Children = a_model.Children.Select(c => getChildTreeNodesFromModel(c)).ToList()
}
}

Silverlight Treeview inline HierarchicalDataTemplate binding issue

I have MyCollection of MyPOCO object (that has two string properties).
When I try to implement a HierarchicalDataTemplate into a treeview the binding is not working, I get the class name.
I know that if i take out the datatemplate from the control everything works fine but i am interested to see why this example is not working.
<Grid x:Name="LayoutRoot" Background="White" DataContext="{Binding Source={StaticResource testVM}}">
<sdk:TreeView Margin="8,8,8,111" ItemsSource="{Binding MyCollection}">
<sdk:HierarchicalDataTemplate ItemsSource="{Binding MyPOCO}">
<sdk:HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Property1}"/>
<TextBlock Text="{Binding Property2}"/>
</StackPanel>
</DataTemplate>
</sdk:HierarchicalDataTemplate.ItemTemplate>
</sdk:HierarchicalDataTemplate>
</sdk:TreeView>
</Grid>
Here is the ViewModel also.
namespace MyPOCProject
{
public class MyPOCO
{
private string property1;
public string Property1
{
get { return property1; }
set { property1 = value; }
}
private string property2;
public string Property2
{
get { return property2; }
set { property2 = value; }
}
public MyPOCO(string p1, string p2)
{
property1 = p1;
property2 = p2;
}
}
public class MyViewModel : INotifyPropertyChanged
{
private ObservableCollection<MyPOCO> myCollection;
public ObservableCollection<MyPOCO> MyCollection
{
get { return myCollection; }
set { myCollection = value; RaisePropertyChanged("MyCollection"); }
}
public MyViewModel()
{
MyPOCO _poco1 = new MyPOCO("aaa1", "bbb1");
MyPOCO _poco2 = new MyPOCO("aaa2", "bbb2");
MyPOCO _poco3 = new MyPOCO("aaa3", "bbb3");
MyCollection = new ObservableCollection<MyPOCO>() { _poco1, _poco2, _poco3 };
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
So what am I doing wrong?
AGAIN ... I am interested in this particular example. I want to know what's wrong with this example and why.
Thanks.
The code you posted is not hierarchical, In other words: The MyPOCO Class is not containing a property MyCollection<MYPOCO> Children.
Here is an example for the HierarchicalDataTemplate
Xaml:
<sdk:TreeView x:Name="MyTreeView"
HorizontalAlignment="Left"
ItemsSource="{Binding MyCollection}"
Width="200" Height="280">
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=Header}"/>
<sdk:HierarchicalDataTemplate.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=Header}"/>
</sdk:HierarchicalDataTemplate>
</sdk:HierarchicalDataTemplate.ItemTemplate>
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
</sdk:TreeView>
Codebehind classes:
public class MyPoco
{
public MyPoco(string header, int sampleChildrenCount)
{
this.Header = header;
this.Children = new ObservableCollection<MyPoco>();
if (sampleChildrenCount > 0)
{
for (int i = 0; i < sampleChildrenCount; i++)
{
string newHeader = String.Format("Test {0}", sampleChildrenCount * i);
var myPoco = new MyPoco(newHeader, sampleChildrenCount - 1)
this.Children.Add(myPoco);
}
}
}
public string Header { get; set; }
public ObservableCollection<MyPoco> Children { get; set; }
}
public class MyViewModel
{
public MyViewModel()
{
MyCollection = new ObservableCollection<MyPoco>();
for (int i = 0; i < 6; i++)
{
this.MyCollection.Add(new MyPoco(String.Format("Test {0}", i), 5));
}
}
public ObservableCollection<MyPoco> MyCollection { get; set; }
}
Codebehind startup:
public MainPage()
{
public MainPage()
{
InitializeComponent();
MyTreeView.DataContext = new MyViewModel();
}
}

Silverlight 4 ListBoxDragDropTarget drop onto ListBoxItem

I have 2 list boxes (ListA and ListB) that display data from different entities (EntityA, EntityB). These entities are related - EntityA has a property of a collection of EntityB. I want to be able to use drag and drop behaviour in order to add items from ListB into the collection of the dropped item in ListA.
To clarify, I don't want to add ListItemB into the collection of the selected ListItemA, I want to add it into the collection of the list item that I drop it onto (the ListItemA that the mouse is over when I release).
Using ListBoxDragDropTarget, is it possible for a ListBoxItem to be the drop target, instead of the listbox itself?
Any suggestions as to a solution for this scenario?
It can be done by creating two ListBoxes, as you described, one bound to an ObservableCollection<EntityA> and one bound to an ObservableCollection<EntityB>. EntityA contains an ObservableCollection<EntityB> as a property. The ListBox items of EntityA are templated to display the child collection of EntityB's as a ListBox. The ListBoxDragDropTarget is specified in this ItemTemplate, rather than the parent. Here is some XAML to demonstrate:
<ListBox Name="listOfEntityA">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding EntityName}" />
<toolKit:ListBoxDragDropTarget AllowDrop="True" AllowedSourceEffects="All">
<ListBox ItemsSource="{Binding ChildEntityBs}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding EntityName}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</toolKit:ListBoxDragDropTarget>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<toolKit:ListBoxDragDropTarget AllowDrop="True" AllowedSourceEffects="All">
<ListBox Name="listOfEntityB" />
</toolKit:ListBoxDragDropTarget>
After a bit of work I think I have it:
<StackPanel Orientation="Horizontal">
<Controls:ListBoxDragDropTarget AllowDrop="True">
<ListBox x:Name="FromBox" Width="200" ItemsSource="{Binding IssueList}" DisplayMemberPath="Name"/>
</Controls:ListBoxDragDropTarget>
<Controls:ListBoxDragDropTarget AllowDrop="True" Drop="ToBoxDragDropTarget_Drop">
<ListBox x:Name="ToBox" Width="150" ItemsSource="{Binding ObjectiveList}" DisplayMemberPath="Name" Margin="80,0,0,0" />
</Controls:ListBoxDragDropTarget>
<TextBlock x:Name="UpdateText"/>
</StackPanel>
and the codebehind (which will now be refactored into my ViewModel):
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
IssueList = new ObservableCollection<Issue>
{
new Issue{ ID = 1, Name="One"},
new Issue{ ID = 2, Name="Two"},
new Issue{ ID = 3, Name="Three"},
new Issue{ ID = 4, Name="Four"},
new Issue{ ID = 5, Name="Five"},
};
ObjectiveList = new ObservableCollection<Objective>
{
new Objective {ID = 10, Name = "Ten"},
new Objective {ID = 11, Name = "Eleven"},
new Objective {ID = 12, Name = "Twelve"},
new Objective {ID = 13, Name = "Thirteen"},
new Objective {ID = 14, Name = "Fourteen"},
};
LayoutRoot.DataContext = this;
}
public ObservableCollection<Issue> IssueList { get; set; }
public ObservableCollection<Objective> ObjectiveList { get; set; }
private void ToBoxDragDropTarget_Drop(object sender, Microsoft.Windows.DragEventArgs e)
{
var droppedOnObjective = ((FrameworkElement)e.OriginalSource).DataContext as Objective;
var args = e.Data.GetData(typeof(ItemDragEventArgs)) as ItemDragEventArgs;
if (args != null)
{
var draggedItems = args.Data as SelectionCollection;
var draggedItem = draggedItems[0];
if (droppedOnObjective != null)
{
var draggedIssue = (Issue)draggedItem.Item;
if (!droppedOnObjective.Issues.Contains(draggedIssue))
{
droppedOnObjective.Issues.Add(draggedIssue);
UpdateText.Text = string.Format("Issue <{0}> added to Objective <{1}>", draggedIssue.Name, droppedOnObjective.Name);
}
else
{
UpdateText.Text = string.Format("Objective <{0}> already contains Issue <{1}>", droppedOnObjective.Name, draggedIssue.Name);
}
}
else
UpdateText.Text = "selections or dropOnObjective is null";
}
else
UpdateText.Text = "args null";
}
}
public class Issue
{
public int ID { get; set; }
public string Name { get; set; }
}
public class Objective
{
public int ID { get; set; }
public string Name { get; set; }
public List<Issue> Issues { get; set; }
public Objective()
{
Issues = new List<Issue>();
}
}

Treeview binding wpf

I want to make a treeview showing the file system.
public class FileSystem
{
public IList< Folder> folders;
public FileSystem()
{
foreach (DriveInfo di in DriveInfo.GetDrives())
{
Folder f = new Folder(di.Name);
f.fillSubFolders();
folders.Add(f);
}
}
}
public class FileItem
{
public string name;
public FileItem(string _name)
{
name = _name;
}
}
public class Folder
{
public string name;
public IList<Folder> subFolders;
public IList<FileItem> items;
public Folder(string _name)
{
name = _name;
subFolders = new List<Folder>();
items = new List<FileItem>();
}
public void fillSubFolders() {
foreach (string fl in Directory.GetFiles(name))
{
FileItem f = new FileItem(fl);
items.Add(f);
}
foreach (string dir in Directory.GetDirectories(name))
{
Folder f = new Folder(dir);
subFolders.Add(f);
f.fillSubFolders();
}
}
}
What should I add in XAML code in order to bind the data?
<TreeView Height="311" HorizontalAlignment="Left" Name="treeView1" VerticalAlignment="Top" Width="199" ItemsSource="{Binding items}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{Binding}">
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
You might want to look at this article (especially the "View implementation" section).
The below link could help
http://www.mattlong.com.au/?p=37

Resources