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
Related
I have an object that follows Composite design pattern. I would like to show this object in a WPF using tree view but I have trouble binding the data correctly. I have two classes: Leaf, simple class that doesnt have any children, and Box, compound class that has children elements which could be both of Leaf class of Box class. I also have a common interface called ITree
Interface
public interface ITree
{
string Name { get; }
string Property1 { get; }
string Property2 { get; }
}
Simple class
public class Leaf : ITree
{
string ITree.Name { get { return _name; } }
string ITree.Property1 { get { return property1; } }
string ITree.Property2 { get { return property2; } }
}
Compound class
public class Box : ITree
{
string ITree.Name { get { return _name; } }
string ITree.Property1 { get { return property1; } }
string ITree.Property2 { get { return property2; } }
List<ITree> Children = new List<ITree>();
}
xaml.cs
List<ITree> ListToBind = new List<ITree>();
ITree finalObject = PopulateCompositeObjeectWithData();
ListToBind.Add(finalObject);
xaml
<TreeView ItemsSource="{Binding ElementName=Window, Path= ListToBind}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The tree view I am trying to achieve:
Box - Name
|-Leaf - Name
|-Leaf - Name
|-Box - Name
| |-Leaf - Name
| |-Leaf - Name
Any suggestion or code samples would be greatly appreciated
Thank you
First of all, Children must be public property for you to be able to bind to it:
public class Box : ITree
{
string ITree.Name { get { return _name; } }
string ITree.Property1 { get { return property1; } }
string ITree.Property2 { get { return property2; } }
public List<ITree> Children { get; } = new List<ITree>();
}
Second, you should bind to explicit implemented interface members using parentheses like this:
<TreeView ItemsSource="{Binding ElementName=Window, Path= ListToBind}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding (local:ITree.Name)}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
I am trying to create a TreeView for my application. This is the first time I am using TreeView with the MVVM structure, by all accounts the binding is working and displaying correctly.
However:
How do I get the selection so I can perform some logic after the user selects something?
I thought that the TextValue property in SubSection class would fire PropertyChanged, but it doesn't, so I am left scratching my head.
This is the most simplified set of code I could make for this question:
Using PropertyChanged setup like this in : ViewModelBase class
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
The VeiwModel:
public class ShiftManagerViewModel : ViewModelBase
{
public ShiftManagerViewModel()
{
Departments = new List<Department>()
{
new Section("Section One"),
new Section("Section Two")
};
}
private List<Section> _sections;
public List<Section> Sections
{
get{return _sections;}
set
{
_sections = value;
NotifyPropertyChanged();
}
}
}
The classes:
public class Section : ViewModelBase
{
public Section(string depname)
{
DepartmentName = depname;
Courses = new List<SubSection>()
{
new SubSection("SubSection One"),
new SubSection("SubSection One")
};
}
private List<SubSection> _courses;
public List<SubSection> Courses
{
get{ return _courses; }
set
{
_courses = value;
NotifyPropertyChanged();
}
}
public string DepartmentName { get; set; }
}
public class SubSection : ViewModelBase
{
public SubSection(string coursename)
{
CourseName = coursename;
}
public string CourseName { get; set; }
private string _vTextValue;
public string TextValue
{
get { return _vTextValue; }
set
{
_vTextValue = value;
NotifyPropertyChanged();
}
}
}
And the XAML:
<Window.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding Courses}" DataType="{x:Type viewModels:Section}">
<Label Content="{Binding DepartmentName}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding TextValue}" DataType="{x:Type viewModels:SubSection}">
<Label Content="{Binding CourseName}" />
</HierarchicalDataTemplate>
</Window.Resources>
Could someone point me in the right direction?
You could cast the SelectedItem property of the TreeView to a Section or a SubSection or whatever the type of the selected item is:
Section section = treeView1.SelectedItem as Section;
if (section != null)
{
//A Section is selected. Access any of its properties here
string name = section.DepartmentName;
}
else
{
SubSection ss = treeView1.SelectedItem as SubSection;
if(ss != null)
{
string ssName = ss.CourseName;
}
}
Or you could add an IsSelected property to the Section and SubSection types and bind the IsSelected property of the TreeView to it:
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
Then you get the selected item by iterating through the ItemsSource of the TreeView and look for the item that has the IsSelected source property set to true.
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
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();
}
}
I'm not sure my Title is right but this is the problem I am facing now.. I have the below XAML code..
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding Path=AvailableFields}"
SelectedItem="{Binding Path=SelectedField}"
></ComboBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
What this basically does is, If my data source contains ten items, this is going to generate 10 row of comboboxes and all comboboxes are bounded to the same itemsource.
Now my requirement is Once an item is selected in the first combo box, that item should not be available in the subsequent combo boxes. How to satisfy this requirement in MVVM and WPF?
This turned out to be harder than I thought when I started coding it. Below sample does what you want. The comboboxes will contain all letters that are still available and not selected in another combobox.
XAML:
<Window x:Class="TestApp.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=SelectedLetters}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Path=AvailableLetters}"
SelectedItem="{Binding Path=Letter}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Window>
Code behind:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
namespace TestApp
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new VM();
}
}
public class VM : INotifyPropertyChanged
{
public VM()
{
SelectedLetters = new List<LetterItem>();
for (int i = 0; i < 10; i++)
{
LetterItem letterItem = new LetterItem();
letterItem.PropertyChanged += OnLetterItemPropertyChanged;
SelectedLetters.Add(letterItem);
}
}
public List<LetterItem> SelectedLetters { get; private set; }
private void OnLetterItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != "Letter")
{
return;
}
foreach (LetterItem letterItem in SelectedLetters)
{
letterItem.RefreshAvailableLetters(SelectedLetters);
}
}
public event PropertyChangedEventHandler PropertyChanged;
public class LetterItem : INotifyPropertyChanged
{
static LetterItem()
{
_allLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".Select(c => c.ToString());
}
public LetterItem()
{
AvailableLetters = _allLetters;
}
public void RefreshAvailableLetters(IEnumerable<LetterItem> letterItems)
{
AvailableLetters = _allLetters.Where(c => !letterItems.Any(li => li.Letter == c) || c == Letter);
}
private IEnumerable<string> _availableLetters;
public IEnumerable<string> AvailableLetters
{
get { return _availableLetters; }
private set
{
_availableLetters = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("AvailableLetters"));
}
}
}
private string _letter;
public string Letter
{
get { return _letter; }
set
{
if (_letter == value)
{
return;
}
_letter = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Letter"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private static readonly IEnumerable<string> _allLetters;
}
}
}
This functionality is not provided by WPF, but it can be implemented using some custom coding.
I've created 3 ViewModel classes:
PreferencesVM - This will be our DataContext. It contains the master list of options which can appear in the ComboBoxes, and also contains a SelectedOptions property, which keeps track of which items are selected in the various ComboBoxes. It also has a Preferences property, which we will bind our ItemsControl.ItemsSource to.
PreferenceVM - This represents one ComboBox. It has a SelectedOption property, which ComboBox.SelectedItem is bound to. It also has a reference to PreferencesVM, and a property named Options (ComboBox.ItemsSource is bound to this), which returns the Options on PreferencesVM via a filter which checks if the item may be displayed in the ComboBox.
OptionVM - Represents a row in the ComboBox.
The following points form the key to the solution:
When PreferenceVM.SelectedOption is set (ie a ComboBoxItem is selected), the item is added to the PreferencesVM.AllOptions collection.
PreferenceVM handles Preferences.SelectedItems.CollectionChanged, and triggers a refresh by raising PropertyChanged for the Options property.
PreferenceVM.Options uses a filter to decide which items to return - which only allows items which are not in PreferencesVM.SelectedOptions, unless they are the SelectedOption.
What I've described above might be enough to get you going, but to save you the headache I'll post my code below.
PreferencesVM.cs:
public class PreferencesVM
{
public PreferencesVM()
{
PreferenceVM pref1 = new PreferenceVM(this);
PreferenceVM pref2 = new PreferenceVM(this);
PreferenceVM pref3 = new PreferenceVM(this);
this._preferences.Add(pref1);
this._preferences.Add(pref2);
this._preferences.Add(pref3);
//Only three ComboBoxes, but you can add more here.
OptionVM optRed = new OptionVM("Red");
OptionVM optGreen = new OptionVM("Green");
OptionVM optBlue = new OptionVM("Blue");
_allOptions.Add(optRed);
_allOptions.Add(optGreen);
_allOptions.Add(optBlue);
}
private ObservableCollection<OptionVM> _selectedOptions =new ObservableCollection<OptionVM>();
public ObservableCollection<OptionVM> SelectedOptions
{
get { return _selectedOptions; }
}
private ObservableCollection<OptionVM> _allOptions = new ObservableCollection<OptionVM>();
public ObservableCollection<OptionVM> AllOptions
{
get { return _allOptions; }
}
private ObservableCollection<PreferenceVM> _preferences = new ObservableCollection<PreferenceVM>();
public ObservableCollection<PreferenceVM> Preferences
{
get { return _preferences; }
}
}
PreferenceVM.cs:
public class PreferenceVM:INotifyPropertyChanged
{
private PreferencesVM _preferencesVM;
public PreferenceVM(PreferencesVM preferencesVM)
{
_preferencesVM = preferencesVM;
_preferencesVM.SelectedOptions.CollectionChanged += new NotifyCollectionChangedEventHandler(SelectedOptions_CollectionChanged);
}
void SelectedOptions_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this,new PropertyChangedEventArgs("Options"));
}
private OptionVM _selectedOption;
public OptionVM SelectedOption
{
get { return _selectedOption; }
set
{
if (value == _selectedOption)
return;
if (_selectedOption != null)
_preferencesVM.SelectedOptions.Remove(_selectedOption);
_selectedOption = value;
if (_selectedOption != null)
_preferencesVM.SelectedOptions.Add(_selectedOption);
}
}
private ObservableCollection<OptionVM> _options = new ObservableCollection<OptionVM>();
public IEnumerable<OptionVM> Options
{
get { return _preferencesVM.AllOptions.Where(x=>Filter(x)); }
}
private bool Filter(OptionVM optVM)
{
if(optVM==_selectedOption)
return true;
if(_preferencesVM.SelectedOptions.Contains(optVM))
return false;
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
}
OptionVM.cs:
public class OptionVM
{
private string _name;
public string Name
{
get { return _name; }
}
public OptionVM(string name)
{
_name = name;
}
}
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new PreferencesVM();
}
}
MainWindow.xaml:
<Window x:Class="WpfApplication64.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">
<Grid>
<ItemsControl ItemsSource="{Binding Path=Preferences}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=Options}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedOption}"></ComboBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
**Note that to reduce lines of code, my provided solution only generates 3 ComboBoxes (not 10).