TreeView display hierarchical data - c#

I have the following two model-classes:
public class FileItem : NotifyBase
{
public FileItem(string fullPath)
{
FullPath = fullPath;
}
public string FullPath
{
get { return Get<string>(); }
set
{
Set(value);
OnPropertyChanged("FileName");
OnPropertyChanged("Directory");
}
}
public string Directory
{
get { return Path.GetDirectoryName(FullPath); }
}
public string FileName
{
get { return Path.GetFileName(FullPath); }
}
}
and
public class Directory : NotifyBase
{
public Directory(string fullPath)
{
FullPath = fullPath;
Files = new ObservableCollection<FileItem>();
Directories = new ObservableCollection<Directory>();
}
public ObservableCollection<FileItem> Files
{
get { return Get<ObservableCollection<FileItem>>(); }
set { Set(value); }
}
public ObservableCollection<Directory> Directories
{
get { return Get<ObservableCollection<Directory>>(); }
set { Set(value); }
}
public string FullPath
{
get { return Get<string>(); }
set
{
Set(value);
OnPropertyChanged("DirectoryName");
}
}
public string DirectoryName
{
get
{
string[] directoryNameSplit = FullPath.Split(new[] { "\\" }, StringSplitOptions.RemoveEmptyEntries);
if (directoryNameSplit.Length > 0)
{
return directoryNameSplit[directoryNameSplit.Length - 1];
}
return "UNKNOWN";
}
}
}
The class NotifyBase is just an implementation of INotifyPropertyChanged.
I load the data from the filesystem and so I have a hierarchy of objects. Now I want to display that hierarchy of Directorys and FileItems in the View as a TreeView.
In the ViewModel I have an ObservableCollection<Directory> called Directories with the root nodes.
My View is:
<TreeView Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Margin="5" x:Name="tv"
ItemsSource="{Binding Directories, UpdateSourceTrigger=PropertyChanged}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type model:Directory}" ItemsSource="{Binding Directories}">
<Label Content="{Binding Path=DirectoryName}" VerticalAlignment="Center"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type model:FileItem}">
<Label Content="{Binding FileName}" VerticalAlignment="Center" Background="Red"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
All folders are displayed perfect, but I don't see any FileItems and I don't know why. What I'm doing wrong here?

Because you have your two collections separate, and you are not binding to Files anywhere.
Instead of two collections, have one collection of two types.

Related

Why TreeView doesn't list my subdirectories? [duplicate]

I am trying to get my head around Heirarchical DataTemplates and TreeViews in WPF and am having some trouble.
I have created an app with only a TreeView on the form as below and defined HierarchicalDataTemplate's for both a Directory object and a File object I then Bind the TreeView to the Directories property (ObservableCollection) of my model.
<Grid>
<Grid.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Directory}" ItemsSource ="{Binding Directories}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:File}" ItemsSource ="{Binding Files}">
<TextBlock Text="{Binding Path=FileName}"/>
</HierarchicalDataTemplate>
</Grid.Resources>
<TreeView Margin="12,12,0,12" Name="treeView1" HorizontalAlignment="Left" Width="204" >
<TreeViewItem ItemsSource="{Binding Directories}" Header="Folder Structure" />
</TreeView>
</Grid>
This works in that in the TreeView I see my directories and it recursively displays all sub directories, but what I want to see is Directories and Files! I've checked the model and it definately has files in some of the sub directories but I can't see them in the tree.
I'm not sure if it is my template that is the problem or my model so I have included them all! :-)
Thanks
OneShot
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private MainWindowViewModel _vm;
public MainWindowViewModel VM
{
set
{
_vm = value;
this.DataContext = _vm;
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var d = new Directory() { Name = "temp" };
recurseDir("c:\\temp", ref d);
VM = new MainWindowViewModel( new List<Directory>() { d } );
}
private void recurseDir(string path, ref Directory dir)
{
var files = System.IO.Directory.GetFiles(path);
var dirs = System.IO.Directory.GetDirectories(path);
dir.Name = path.Substring(path.LastIndexOf("\\")+1);
for (int i = 0; i < files.Length; i++)
{
var fi = new FileInfo(files[i]);
dir.Files.Add(new File() {
FileName = System.IO.Path.GetFileName(files[i]),
DirectoryPath = System.IO.Path.GetDirectoryName(files[i]),
Size = fi.Length,
Extension= System.IO.Path.GetExtension(files[i])
});
}
for (int i = 0; i < dirs.Length; i++)
{
var d = new Directory() { Name = dirs[i].Substring(dirs[i].LastIndexOf("\\")+1) };
recurseDir(dirs[i], ref d);
dir.Directories.Add(d);
}
}
}
-
public class MainWindowViewModel
: DependencyObject
{
public MainWindowViewModel(List<Directory> Dirs)
{
this.Directories = new ObservableCollection<Directory>( Dirs);
}
public ObservableCollection<Directory> Directories
{
get { return (ObservableCollection<Directory>)GetValue(DirectoriesProperty); }
set { SetValue(DirectoriesProperty, value); }
}
public static readonly DependencyProperty DirectoriesProperty =
DependencyProperty.Register("Directories", typeof(ObservableCollection<Directory>), typeof(MainWindowViewModel), new UIPropertyMetadata(null));
public Directory BaseDir
{
get { return (Directory)GetValue(BaseDirProperty); }
set { SetValue(BaseDirProperty, value); }
}
public static readonly DependencyProperty BaseDirProperty =
DependencyProperty.Register("BaseDir", typeof(Directory), typeof(MainWindowViewModel), new UIPropertyMetadata(null));
}
-
public class Directory
{
public Directory()
{
Files = new List<File>();
Directories = new List<Directory>();
}
public List<File> Files { get; private set; }
public List<Directory> Directories { get; private set; }
public string Name { get; set; }
public int FileCount
{
get
{
return Files.Count;
}
}
public int DirectoryCount
{
get
{
return Directories.Count;
}
}
public override string ToString()
{
return Name;
}
}
-
public class File
{
public string DirectoryPath { get; set; }
public string FileName { get; set; }
public string Extension { get; set; }
public double Size { get; set; }
public string FullPath
{
get
{
return System.IO.Path.Combine(DirectoryPath, FileName);
}
}
public override string ToString()
{
return FileName;
}
}
Take a look at this again:
<HierarchicalDataTemplate DataType="{x:Type local:Directory}" ItemsSource ="{Binding Directories}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:File}" ItemsSource ="{Binding Files}">
<TextBlock Text="{Binding Path=FileName}"/>
</HierarchicalDataTemplate>
What you're saying is that if you encounter an object of type File, display it with a text block and get its children from a property Files under the File. What you really want is for the Files to show up under each Directory, so you should create a new property that exposes both Directories and Files:
public class Directory
{
//...
public IEnumerable<Object> Members
{
get
{
foreach (var directory in Directories)
yield return directory;
foreach (var file in Files)
yield return file;
}
}
//...
}
and then your template becomes:
<HierarchicalDataTemplate DataType="{x:Type local:Directory}" ItemsSource ="{Binding Members}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:File}">
<TextBlock Text="{Binding Path=FileName}"/>
</DataTemplate>
UPDATE:
Actually, the above is not sufficient if you want to receive collection changed notifications for the Members. If that's the case, I recommend creating a new ObservableCollection and adding Directory and File entries to it in parallel to adding to the Files and Directories collections.
Alternately, you may wish to reconsider how you store your information and put everything in a single collection. The other lists are then simply filtered views of the main collection.

UWP RadExpanderControl is binding ContentTemplate but not ExpandableContentTemplate

the idea here is that every revision of a file spawns a new file with a name that adds "_v1", "_v2", etc. The back end gets the files from disk and groups them based on that name. So a header might say "example", then when you click on the expander, you see "example", "example_v1", "example_v2", etc.
However, when this grouped data reaches the front-end, only the keys are showing, with an empty list of expanded content. Is my XAML messed up or is it something else?
This is MainPage.xaml:
<my:RadDataBoundListBox x:Name="listBox">
<my:RadDataBoundListBox.ItemTemplate>
<DataTemplate>
<my1:RadExpanderControl
Content="{ Binding }"
ExpandableContent="{ Binding }"
IsExpanded="True">
<my1:RadExpanderControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{ Binding Key }" />
</DataTemplate>
</my1:RadExpanderControl.ContentTemplate>
<my1:RadExpanderControl.ExpandableContentTemplate>
<DataTemplate>
<TextBlock Text="{ Binding Name }" />
</DataTemplate>
</my1:RadExpanderControl.ExpandableContentTemplate>
</my1:RadExpanderControl>
</DataTemplate>
</my:RadDataBoundListBox.ItemTemplate>
</my:RadDataBoundListBox>
This is from MainPage.xaml.cs:
public sealed partial class MainPage : Page
{
CollectionViewSource cvs = null;
public MainPage()
{
InitializeComponent();
cvs = new CollectionViewSource();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
cvs.Source = ViewModel.Files;
cvs.IsSourceGrouped = true;
listBox.ItemsSource = (IOrderedEnumerable<IGrouping<string, Files>>)cvs.Source;
}
}
This is from the ViewModel:
private IOrderedEnumerable<IGrouping<string, Files>> files;
public IOrderedEnumerable<IGrouping<string, Files>> Files
{
get
{
if (files == null)
{
var source = Source;
string[] separaters = { "_", "." };
files = from file in source
group file by file.Name.Split(separaters, StringSplitOptions.RemoveEmptyEntries)[0]
into g
orderby g.Key
select g;
}
return files;
}
}
This is how the list of filenames is pulled from disk:
public ObservableCollection<Files> Source
{
get
{
return GetFilesData(Folder).Result;
}
}
This is the model for the filenames:
public class Files
{
public string Name { get; set; }
}

initialize a WPF with undefined datatype

Here is a class with undefined variable that needs to be passed into the WPF window.
public class SelectedVal<T>
{
public T val {get;set;}
}
Window:
public partial class SOMEDialogue : Window
{
public List<SelectedVal<T>> returnlist { get { return FullList; } }
public List<SelectedVal<T>> FullList = new List<SelectedVal<T>>();
public SOMEDialogue (List<SelectedVal<T>> inputVal)
{
InitializeComponent();
}
}
So here is the question, how can I do this properly to get the T and have a global variable set in my WPF?
Edited (code edited too):
The purpose for the WPF is:
A list of SelectedVal<T> input
Display this input in this WPF
Depend on the T type, user can do something about this input
When finished a return List<SelectedVal<T>> returnlist can be
accessed
This is the basic idea I'm describing. Let me know if you hit any snags. I'm guessing that the search text and the min/max int values are properties of the dialog as a whole. I'm also assuming that there may be a mixture of item types in the collection, which may be an assumption too far. Can you clarify that?
Selected value classes
public interface ISelectedVal
{
Object Val { get; set; }
}
public class SelectedVal<T> : ISelectedVal
{
public T Val { get; set; }
object ISelectedVal.Val
{
get => this.Val;
set => this.Val = (T)value;
}
}
public class StringVal : SelectedVal<String>
{
}
public class IntVal : SelectedVal<int>
{
}
Dialog Viewmodel
public class SomeDialogViewModel : ViewModelBase
{
public SomeDialogViewModel(List<ISelectedVal> values)
{
FullList = values;
}
public List<ISelectedVal> FullList { get; set; }
private String _searchText = default(String);
public String SearchText
{
get { return _searchText; }
set
{
if (value != _searchText)
{
_searchText = value;
OnPropertyChanged();
}
}
}
private int _minInt = default(int);
public int MinInt
{
get { return _minInt; }
set
{
if (value != _minInt)
{
_minInt = value;
OnPropertyChanged();
}
}
}
private int _maxInt = default(int);
public int MaxInt
{
get { return _maxInt; }
set
{
if (value != _maxInt)
{
_maxInt = value;
OnPropertyChanged();
}
}
}
}
.xaml.cs
public SOMEDialogue (List<ISelectedVal> inputValues)
{
InitializeComponent();
DataContext = new SomeDialogViewModel(inputValues);
}
XAML
<Window.Resources>
<DataTemplate DataType="{x:Type local:StringVal}">
<StackPanel>
<Label>Value</Label>
<Label Content="{Binding Val}" />
<Label>Search text:</Label>
<TextBox Text="{Binding DataContext.SearchText, RelativeSource={RelativeSource AncestorType=Window}}" />
<!-- Other stuff -->
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:IntVal}">
<StackPanel>
<Label>Value</Label>
<Label Content="{Binding Val}" />
<Label>Min value:</Label>
<TextBox Text="{Binding DataContext.MinIntVal, RelativeSource={RelativeSource AncestorType=Window}}" />
<Label>Max value:</Label>
<TextBox Text="{Binding DataContext.MaxIntVal, RelativeSource={RelativeSource AncestorType=Window}}" />
<!-- Other stuff -->
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl
ItemsSource="{Binding FullList}"
/>
</Grid>

WPF Treeview Databinding with mixed collections

I am trying to proper bind several collection by using composite collection.
Please point out my mistake
I am expecting something like
Car
Year
2014
2013
Name
Toyota
Euro
Benz
But Instead I am getting
Car
2014
2013
Toyota
Benz
XAML:
<TreeView Margin="8" Grid.Row="2" Grid.RowSpan="2" Name="CarTree" >
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Car}" ItemsSource="{Binding Children}">
<StackPanel>
<TextBlock Text="{Binding Path=CarName}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Year}">
<StackPanel>
<TextBlock Text="{Binding Path=YearName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Year Class:
public class Year:ViewModelBase
{
private string _yearname= String.Empty;
public string YearName
{
get { return _yearname; }
set
{
_yearname= value;
OnPropertyChanged("YearName");
}
}
}
Car Class:
public class Car: ViewModelBase
{
private string _carname= String.Empty;
public string CarName
{
get { return _carname; }
set
{
//ignore if values are equal
if (value == _carname) return;
_carname= value;
OnPropertyChanged("CarName");
}
}
private ObservableCollection<Year> _years=new ObservableCollection<Year>();
public ObservableCollection<Year> Years
{
get { return _years; }
set
{
//ignore if values are equal
if (value == _years) return;
_years= value;
OnPropertyChanged("Years");
}
}
public IList Children
{
get
{
return new CompositeCollection()
{
new CollectionContainer() { Collection = this.Years},
new CollectionContainer() { Collection = ...},
new CollectionContainer() { Collection =... }
};
}
}
}
Maybe you'd better not use hierarchical data templates as your data structure does not seem recursive.
But if you want to keep them you can do something like:
public class YearsCollection : ObservableCollection<Year>
{
}
...
YearsCollection _years = new YearsCollection();
public YearsCollection Years
{
get { return _years; }
set
{
//ignore if values are equal
if (value == _years) return;
_years = value;
OnPropertyChanged("Years");
}
}
public IList Children
{
get
{
return new[]
{
this.Years
...
};
}
}
And add a template for each intermediate collection:
<HierarchicalDataTemplate DataType="{x:Type local:YearsCollection}" ItemsSource="{Binding}">
<StackPanel>
<TextBlock Text="Years" />
</StackPanel>
</HierarchicalDataTemplate>

How to make binding to WPF TreeView?

I have a TreeView with Binding, but in the TreeView only 1st level items are shown. I need a treeview =) I broke my head what is wrong.
Here is my code:
MainWindow.xaml
<TreeView Margin="2.996,10,214,10" ItemsSource="{Binding Path=Urls}" Grid.Column="1">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<Grid>
<Rectangle Fill="{Binding Path=Color}" HorizontalAlignment="Left" Stroke="Black" VerticalAlignment="Top" Height="20" Width="20"/>
<TextBlock Text="{Binding Path=AbsoluteUrl}" Margin="25,0,0,0" />
</Grid>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=AbsoluteUrl}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
MainViewModel.cs (part)
public ObservableCollection<Url> Urls { get; set; }
public MainWindowViewModel()
{
Urls = new ObservableCollection<Url>();
}
Url.cs
class Url : INotifyPropertyChanged
{
public Url() { }
public Url(string absoluteUrl, bool isBroken, string color)
{
AbsoluteUrl = absoluteUrl;
IsBroken = isBroken;
Color = color;
}
enum Status { Working, Broken };
private ObservableCollection<Url> childUrlsValue = new ObservableCollection<Url>();
public ObservableCollection<Url> ChildUrls
{
get
{
return childUrlsValue;
}
set
{
childUrlsValue = value;
}
}
private string _absoluteUrl;
public string AbsoluteUrl
{
get { return _absoluteUrl; }
set
{
if (_absoluteUrl != value)
{
_absoluteUrl = value;
OnPropertyChanged("AbsoluteUrl");
}
}
}
private bool _isBroken;
public bool IsBroken
{
get { return _isBroken; }
set
{
if (_isBroken != value)
{
_isBroken = value;
OnPropertyChanged("IsBroken");
}
}
}
private string _color;
public string Color
{
get { return _color; }
set
{
if (_color != value)
{
_color = value;
OnPropertyChanged("Color");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And just about this i'm adding items to Urls:
Url DataGridTopic = new Url(startUrl.ToString(), true, "red");
DataGridTopic.ChildUrls.Add(
new Url(startUrl.ToString(), true, "red"));
DataGridTopic.ChildUrls.Add(
new Url(startUrl.ToString(), true, "red"));
DataGridTopic.ChildUrls.Add(
new Url(startUrl.ToString(), true, "red"));
Urls.Add(DataGridTopic);
You will have to tell the HierarchicalDataTemplate where to get the child items of a node from by using its ItemsSource property.
In your case:
<HierarchicalDataTemplate
DataType="{x:Type my:Url}"
ItemsSource="{Binding Path=ChildUrls}"
>
...
</HierarchicalDataTemplate>
Note also the usage od the DataType attribute, which often will become a necessity if the levels of the tree are made of different object types (a tree of directories and files would be such an example). However, i am not sure whether this would apply to your scenario or not.

Categories