Having problem with treeview and Multilist Hierarchical data - c#

I have the following 2 classes:
public class DeviceGroup
{
public String Name { get; set; }
public ObservableCollection<DeviceGroup> DeviceGroups { get; set; }
public ObservableCollection<Device> Devices { get; set; }
public DeviceGroup()
{
Name = String.Empty;
DeviceGroups = new ObservableCollection<DeviceGroup>();
Devices = new ObservableCollection<Device>();
}
}
public class Device
{
public String Name { get; set; }
}
My main class has an ObservableCollection.
In my Xaml - I can create a treeview easily if I just specify DeviceGroup within my HierachicalDataTemplate, as follows:
<Window.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type local:Device}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:DeviceGroup}" ItemsSource="{Binding DeviceGroups}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding DeviceGroups}"/>
</Grid>
The question is: How can I select the Devices collection as well as the DeviceGroup? I'd like the Devices to appear something like Windows Explorer (Directories and Files). Is there a Xaml solution to this problem? Or will I have to create the TreeViewItems in the codebehind.
Thanks.

The only solution I've found so far is within the code behind:
private void LoadTree()
{
foreach (DeviceGroup dg in ttvm.DeviceGroups)
{
TreeViewItem tvi = new TreeViewItem();
tvi.Header = dg;
treeView1.Items.Add(tvi);
AddTreeItems(tvi, dg);
}
}
private void AddTreeItems(TreeViewItem node, DeviceGroup deviceGroup)
{
foreach (DeviceGroup dg in deviceGroup.DeviceGroups)
{
TreeViewItem groupTVI = new TreeViewItem();
groupTVI.Header = dg;
node.Items.Add(groupTVI);
AddTreeItems(groupTVI, dg);
}
foreach (Device device in deviceGroup.Devices)
{
TreeViewItem deviceTVI = new TreeViewItem();
deviceTVI.Header = device;
node.Items.Add(deviceTVI);
}
}
LoadTree() is called after InitilizeComponent. The Xaml changed to:
window resources:
<HierarchicalDataTemplate DataType="{x:Type local:DeviceGroup}" ItemsSource="{Binding DeviceGroups}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
with just a plain treeview in a grid.

Related

XAML - How to write nested CollectionViewSource for hierarchical data

I have a treeview that has a hierarchical data. I'd like to add a filter to it so, I could search any element up to n-level. To do that, I find the concept of nested collectionviewsource interesting. But I'm not sure how it will programmatically work.
Suppose we have a Collection of groups and each group has a collection of elements. So, how do I write a nested collectionviewsource so, I could filter the search result of all the elements in all groups?
Here's a sample code that I thought it should work but it only filters the data on the first level and does not goes down to search 'Elements'. So, I wonder how do I write a right nested collectionviewsource to filter the hierarchical data in a treeview.
<UserControl.Resources>
<CollectionViewSource x:Key="CollectionViewSourceGroups" x:Name="_CollectionViewSourceGroups" Source="{Binding Groups}" Filter="_filterGroups"/>
<CollectionViewSource x:Key="CollectionViewSourceElements" x:Name="_CollectionViewSourceElements" Source="{Binding Elements, Source={StaticResource CollectionViewSourceGroups}}" Filter="_filterElements"/>
</UserControl.Resources>
Here's the code that would trigger the filter command:
var cvs = TryFindResource("CollectionViewSourceGroups") as CollectionViewSource;
if (cvs != null)
{
if (cvs.View != null)
cvs.View.Refresh();
}
cvs = TryFindResource("CollectionViewSourceElements") as CollectionViewSource;
if (cvs != null)
{
if (cvs.View != null)
cvs.View.Refresh();
}
Edit:
Here's the sample MVVM and XAML code:
/// <summary>
/// Group of Views
/// </summary>
public class ViewGroup : INotifyPropertyChanged
{
public string Name { get; set; }
public List<ViewGroup> Groups { get; set; }
private List<ProjectObject> _elements;
public List<ProjectObject> Elements
{
get { return _elements; }
set
{
_elements = value;
OnPropertyChanged();
}
}
public ViewGroup()
{
Groups = new List<ViewGroup>();
Elements = new List<ProjectObject>();
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class SheetManagerViewModel
{
public static SheetManagerViewModel Instance;
public ObservableCollection<ViewGroup> Groups { get; set; }
public SheetManagerViewModel()
{
Instance = this;
Groups = new ObservableCollection<ViewGroup>();
}
}
<Grid Grid.Row="1">
<Grid>
<TreeView x:Name="ProjectBrowserTreeView" ItemsSource="{Binding Groups}" MouseMove="ProjectBrowserTreeView_OnMouseMove" DragOver="ProjectBrowserTreeView_OnDragOver" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Groups}" DataType="{x:Type local:ViewGroup}">
<Label Content="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Elements}" DataType="{x:Type local:ViewGroup}">
<Label Content="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type local:ProjectObject}">
<Label Content="{Binding Name}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Grid>

Unable to Display Nested treeView Items

In My View model I have following structure:
class MainWindowViewModel:BaseEntity
{
#region Output Proprties
public ObservableCollection<Customer> Customers { get; private set; }
public ObservableCollection<TreeViewItems> RoorTreeViewItem { get; set; }
public ObservableCollection<Level2Child> l2Childs { get; set; }
public ObservableCollection<Level1Child> l1Childs { get; set; }
public Level1Child l1Child { get; set; }
#endregion
public MainWindowViewModel()
{
ClickCommand = new RelayCommand(paremer =>
{
var customeList = SampleMVVM.Service.Service.GetAllCustomers();
Customers = new ObservableCollection<Customer>(customeList);
ObservableCollection<TreeViewItems> tViewIte = new ObservableCollection<TreeViewItems>();
l1Childs = new ObservableCollection<Level1Child>();
l2Childs = new ObservableCollection<Level2Child>();
Level2Child l2Child1 = new Level2Child { Name = "Zems001", Description = "Zemms as ZemsBond" };
Level2Child l2Child2 = new Level2Child { Name = "Zems002", Description = "Zemms as ZemsBond" };
Level2Child l2Child3 = new Level2Child { Name = "Zems003", Description = "Zemms as ZemsBond" };
Level2Child l2Child4 = new Level2Child { Name = "Zems004", Description = "Zemms as ZemsBond" };
Level2Child l2Child5 = new Level2Child { Name = "Zems005", Description = "Zemms as ZemsBond" };
l2Childs.Add(l2Child1);
l2Childs.Add(l2Child2);
l2Childs.Add(l2Child3);
l2Childs.Add(l2Child4);
l2Childs.Add(l2Child5);
Level1Child l1Child = new Level1Child { Name = "Bond", Description = "Gems Bond", Level2Child = l2Childs };
l1Childs.Add(l1Child);
TreeViewItems rootItem = new TreeViewItems {Name= "Shon Conery",Description= "Octopussy", Level1Child = l1Childs };
tViewIte.Add(rootItem);
RoorTreeViewItem = new ObservableCollection<TreeViewItems>(tViewIte);
NotifyPropertyChanged("Customers");
NotifyPropertyChanged("RoorTreeViewItem");
});
}
#region InputCommands
public ICommand ClickCommand { get; private set; }
#endregion
}
I am trying to show the tree view using XAML:
<Window x:Class="SampleMVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
xmlns:vm="clr-namespace:SampleMVVM.ViewModel"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<!--<HierarchicalDataTemplate DataType = "{x:Type vm:Level1Child}" ItemsSource = "{Binding Path=l1Childs}">
<TextBlock Text="{Binding Name}"></TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType = "{x:Type vm:Level2Child}" ItemsSource = "{Binding Path=l2Childs}">
<TextBlock Text="{Binding Name}"></TextBlock>
</HierarchicalDataTemplate>-->
<HierarchicalDataTemplate
DataType="{x:Type vm:Level1Child}"
ItemsSource="{Binding Path=Level2Child, diag:PresentationTraceSources.TraceLevel=High}">
<TextBlock Text="{Binding Name}"></TextBlock>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView Name="viewsTreeView" ItemsSource="{Binding RoortTreeViewItem}">
</TreeView>
<Button Height="50" Width="100" Content="Get Records" Command="{Binding ClickCommand}" Margin="132,260,285,10"/>
</Grid>
Ideally I should see the tree structure similar to following image, but just getting namespace value as root, nothing else.
Edit
Made following changes, but still getting Top Level elements only:
<Window.Resources>
<HierarchicalDataTemplate
DataType="{x:Type vm:TreeViewItems}"
ItemsSource="{Binding Path=l2Childs, diag:PresentationTraceSources.TraceLevel=High}">
<TextBlock Text="{Binding Name}"></TextBlock>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView Name="viewsTreeView" ItemsSource="{Binding RootTreeViewItem}"/>
It would be very helpful if you would include all of your code. I'm left guessing at a lot of stuff that's not shown. There may be further problems in the code that you didn't include.
But to start with, it appears that you are binding the wrong thing to ItemsSource in your templates. You left out your class definitions, but it looks like Level1Child keeps its children in a property called Level2Child. In the template for Level1Child, the DataContext is an instance of Level1Child, not an instance of your viewmodel. So bind to the property on Level1Child:
<HierarchicalDataTemplate
DataType="{x:Type vm:Level1Child}"
ItemsSource="{Binding Path=Level2Child}"
>
<TextBlock Text="{Binding Name}"></TextBlock>
</HierarchicalDataTemplate>
In addition, you have no template at all for your root item type, TreeViewItems. You need a template for that type as well, and as with the others, you need to bind ItemsSource to the correct name of the actual child-collection property on the TreeViewItems class.
You can diagnose these binding issues more easily by adding a trace to a binding that's not doing what you expect. Add the System.Diagnostics namespace to the outermost tag in your XAML file:
<Window
...
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
...
>
And add this attached property to the Binding that's not working as you expect:
diag:PresentationTraceSources.TraceLevel=High
...like so:
<HierarchicalDataTemplate
DataType="{x:Type vm:Level1Child}"
ItemsSource="{Binding Path=Level2Child, diag:PresentationTraceSources.TraceLevel=High}"
>
<TextBlock Text="{Binding Name}"></TextBlock>
</HierarchicalDataTemplate>
In your "Output" pane in Visual Studio at runtime, when it tries to resolve that binding, you'll get a lot of information about what the DataContext actually is, and what the binding is trying to do to resolve itself.
The trace output slows things down, so don't leave it in there permanently. Take it out when you've resolved the problem.

Databinding a nested List property in WPF

I am using the following XAML code to display a list of checked list boxes.
<ListBox x:Name="lbxProjects" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<ListBox x:Name="lbxUnits" ItemsSource="{Binding Units}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox Content="{Binding unit.Name}" IsChecked="{Binding isSelected}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The data model is as follows
public class ProjectsListBox
{
public Project project { get; set; }
public List<UnitsCheckBox> Units = new List<UnitsCheckBox>();
public ProjectsListBox(Project project)
{
this.project = project;
foreach(var d in project.Documents)
{
Units.Add(new UnitsCheckBox(d));
}
}
}
public class UnitsCheckBox : INotifyPropertyChanged
{
public Document unit { get; set; }
private bool isselected = true;
public bool isSelected
{
get { return isselected; }
set
{
isselected = value;
NotifyPropertyChanged("isSelected");
}
}
public UnitsCheckBox(Document d)
{
unit = d;
}
}
I am assigning the data source for the parent listbox like
lbxProjects.DataContext = projectsList;
The code creates the child list boxes but not the checkboxes inside the child list boxes. What am i missing?
How should WPF resolve unit.Name?
If the type UnitsCheckBox contains a Name property, then the CheckBox's Content should be bound to Name:
Content="{Binding Name}"
You should always specify the type of your DataTemplate:
<DataTemplate DataType="{x:Type local:UnitsCheckBox}" ...>
Those are the probable problems but I can't be sure unless you give us the UnitsCheckBox code.

WPF TreeView-How to refresh tree after adding/removing node?

I refer to this article:
WPF TreeView HierarchicalDataTemplate - binding to object with multiple child collections
and modify the tree structure like:
Root
|__Group
|_Entry
|_Source
In Entry.cs:
public class Entry
{
public int Key { get; set; }
public string Name { get; set; }
public ObservableCollection<Source> Sources { get; set; }
public Entry()
{
Sources = new ObservableCollection<Source>();
}
public ObservableCollection<object> Items
{
get
{
ObservableCollection<object> childNodes = new ObservableCollection<object>();
foreach (var source in this.Sources)
childNodes.Add(source);
return childNodes;
}
}
}
In Source.cs:
public class Source
{
public int Key { get; set; }
public string Name { get; set; }
}
In XAML file:
<UserControl.CommandBindings>
<CommandBinding Command="New" Executed="Add" />
</UserControl.CommandBindings>
<TreeView x:Name="TreeView">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="TreeViewItem.IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Root}" ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Path=Name}" IsEnabled="True">
</TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Group}" ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Path=Name}" IsEnabled="True">
</TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Entry}" ItemsSource="{Binding Items}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" IsEnabled="True">
<TextBlock.ContextMenu>
<ContextMenu >
<MenuItem Header="Add" Command="New">
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Source}" >
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
In UserControl.cs:
public ObservableCollection<Root> Roots = new ObservableCollection<Root>();
public UserControl6()
{
InitializeComponent();
//...Add new node manually
TreeView.ItemsSource = Roots;
}
private void Add(object sender, ExecutedRoutedEventArgs e)
{
Entry ee = (Entry)TreeView.SelectedItem;
Source s3 = new Source() { Key = 3, Name = "New Source" };
ee.Sources.Add(s3);
}
When I click right button on specific node "Entry" to add a new node "Source" under Entry
(call "Add" method), I add a new "Source" object under Entry successfully, but I can't see this new node on treeview. How to refresh treeview when adding/deleting node?
Use ObservableCollection instead of IList if you want to notify the user interface that something in the collection has changed
As far as I'm concerned, changing of type for Items to ObservableCollection<T> will not resolve the problem. You need to implement INotifyPropertyChanged.
I tested both solutions for my tree view, because I faced the same problem.
In my case changing of type from IList to ObservableCollection didn't refreshed GUI. However when I changed my auto property:
public List<SourceControlItemViewBaseModel> Items { get; set; }
to
private IEnumerable<SourceControlItemViewBaseModel> _items;
public IEnumerable<SourceControlItemViewBaseModel> Items
{
get { return _items; }
set
{
_items = value;
OnPropertyChanged();
}
}
Namely, I've implemented INotifyPropertyChanged and that changed the situation. The method that builds the tree structure defines the actual type of Items as new List<T>(), but it works and refreshes the GUI .
Nevertheless my tree was built in pure MVVM pattern without usage code-behind.
I use
<TreeView ItemsSource="{Binding SourceControlStructureItems}" />
and in the view model I use:
currentVm.Items= await SourceControlRepository.Instance.BuildSourceControlStructureAsync(currentVm.ServerPath);
That means I didn't added/removed items, but I rebuilt Node's sub collection.
Use this class and any changes in Sources collection will update/refresh tree in UI.
public class Entry
{
public int Key { get; set; }
public string Name { get; set; }
public ObservableCollection<Source> Sources { get; set; }
public Entry()
{
Sources = new ObservableCollection<Source>();
}
public CompositeCollection Items
{
get
{
return new CompositeCollection()
{
new CollectionContainer() { Collection = Sources },
// Add other type of collection in composite collection
// new CollectionContainer() { Collection = OtherTypeSources }
};
}
}
}

Custom WPF TreeView

I have a custom class that I would like to bind a WPF TreeView that has three tiers. Each tier needs to be bound like this:
Monitor
--> LCD
--> Samsung 1445 LCD
--> CRT
--> Sony 125 CRT
Here is the example code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
SystemInventory sysInventory = new SystemInventory();
//Possibly do something like this.
_myTreeView.DataContext = sysInventory.DeviceGroupInstances;
}
public class SystemInventory
{
public ObservableCollection<DeviceGroup> DeviceGroupInstances { get; set; }
public SystemInventory()
{
DeviceGroupInstances = new ObservableCollection<DeviceGroup>();
DeviceGroupInstances.Add(new DeviceGroup("Monitor"));
}
}
public class DeviceGroup
{
public string DeviceGroupName { get; set; }
public ObservableCollection<DeviceType> DeviceTypeInstances { get; set; }
public DeviceGroup(string deviceGroupName)
{
DeviceTypeInstances = new ObservableCollection<DeviceType>();
DeviceGroupName = deviceGroupName;
if (deviceGroupName == "Monitor")
{
DeviceTypeInstances.Add(new DeviceType("LCD"));
DeviceTypeInstances.Add(new DeviceType("CRT"));
}
}
}
public class DeviceType
{
public string DeviceTypeName { get; set; }
public ObservableCollection<DeviceInstance> DeviceInstances { get; set; }
public DeviceType(string deviceGroupName)
{
DeviceInstances = new ObservableCollection<DeviceInstance>();
DeviceTypeName = deviceGroupName;
if (deviceGroupName == "Monitor")
{
DeviceInstances.Add(new DeviceInstance("Samsung 1445 LCD"));
}
else
{
DeviceInstances.Add(new DeviceInstance("Sony 125 CRT"));
}
}
}
public class DeviceInstance
{
public string DeviceInstanceName { get; set; }
public DeviceInstance(string instanceName)
{
DeviceInstanceName = instanceName;
}
}
}
First FIX
Change CTOR:
public MainWindow()
{
InitializeComponent();
SystemInventory sysInventory = new SystemInventory();
//Possibly do something like this.
this.Content = sysInventory;
}
Second FIX
Use XAML below (add to MainWindow), where {x:Type pc:MainWindow+SystemInventory} used for nested classes
<Window.Resources>
<DataTemplate DataType="{x:Type pc:MainWindow+SystemInventory}">
<TreeView ItemsSource="{Binding DeviceGroupInstances}"/>
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type pc:MainWindow+DeviceGroup}" ItemsSource="{Binding DeviceTypeInstances}">
<Label Content="{Binding DeviceGroupName}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type pc:MainWindow+DeviceType}" ItemsSource="{Binding DeviceInstances}">
<Label Content="{Binding DeviceTypeName}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type pc:MainWindow+DeviceInstance}">
<Label Content="{Binding DeviceInstanceName}"/>
</DataTemplate>
</Window.Resources>
About namespaces
Namespace is what you need to know to use classes. If your classes have namespace:
namespace MyCompany.MyProject.MyComponent
{
public class SystemInventory
{
....
}
}
This namespace should be added to XAML with alias to use:
<Window x:Class="MyCompany.MyProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myAlias="clr-namespace:MyCompany.MyProject.MyComponent"
Title="Window1" Height="300" Width="350">
<Window.Resources>
...
Now you can use this classes in XAML like:
<DataTemplate DataType="{x:Type myAlias:DeviceInstance}">
<Label Content="{Binding DeviceInstanceName}"/>
</DataTemplate>
Just translate the property names in the answer Charlie already gave you.
<TreeView ItemsSource="{Binding DeviceGroups}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding DeviceTypeInstances}">
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding DeviceInstances}">
<TextBlock Text="{Binding DeviceInstanceName}"/>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<TextBlock Text="{Binding DeviceTypeName}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
You need to use a HierarchicalDataTemplate. There are many examples of this on Stack Overflow (and elsewhere) and if you search around you'll quickly find one. I would create a mock-up to illustrate it, but based on your very low accept rate, I'm not sure it would be worth it. You should look over your past questions and accept answers for all of them.

Categories