Displaying hierarchical data associated with checkboxes in Silverlight - c#

Hello and thanks in advance for the assistance,
I have a list of parent objects, in which each parent object has a list of children objects. I would like to display the data in a fashion in which the user may select child objects, press a button and then I will save the selected children objects, along with their parent, likely in xml through serializing the objects with an xml serializer. An idea of what the display should look like is:
<sdk:Label x:Name="ParentLabel" content="ParentNameString" />
<CheckBox x:Name="ChildCheckBox1" Content="ChildNameString" />
<CheckBox x:Name="ChildCheckBox2" Content="ChildNameString" />
<CheckBox x:Name="ChildCheckBox3" Content="ChildNameString" />
<sdk:Label x:Name="ParentLabel2" content="ParentNameString" />
<CheckBox x:Name="ChildCheckBox4" Content="ChildNameString" />
<CheckBox x:Name="ChildCheckBox5" Content="ChildNameString" />
<CheckBox x:Name="ChildCheckBox6" Content="ChildNameString" />
I know that there are options for a checkbox column in the DataGrid control, but would I be able to display the hierarchical relationship there via Header/children? or is there an option for datatemplating in a Listbox control that would allow headings and associated children elements? What would you recommend?
Thanks again for the help.

If you have a single level of Parent and Child you can do something like this.
A simple codebehind with some sample data
namespace SilverlightApplication2
{
public partial class MainPage : UserControl
{
public ObservableCollection<Parent> ParentList { get; set; }
public MainPage()
{
Populate();
InitializeComponent();
}
private void Save_Click(object sender, RoutedEventArgs e)
{
foreach (var child in ParentList
.SelectMany(p => p.Children)
.Where(c => c.IsSelected))
{
//Save the child
Debug.WriteLine(string.Format("Child {0} saved", child.Name));
}
}
private void Populate()
{
ParentList = new ObservableCollection<Parent>();
ParentList.Add(new Parent
{
Name = "John",
Children = new List<Child> { new Child { Name = "Paul" }, new Child { Name = "Pat" } }
});
ParentList.Add(new Parent
{
Name = "Mike",
Children = new List<Child> { new Child { Name = "Bob" }, new Child { Name = "Alice" } }
});
ParentList.Add(new Parent
{
Name = "Smith",
Children = new List<Child> { new Child { Name = "Ryan" }, new Child { Name = "Sue" }, new Child { Name = "Liz" } }
});
}
}
public class Parent
{
public string Name { get; set; }
public List<Child> Children { get; set; }
}
public class Child
{
public string Name { get; set; }
public bool IsSelected { get; set; }
}
}
Your xaml would be something like this.
<UserControl x:Class="SilverlightApplication2.MainPage"
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:my="clr-namespace:SilverlightApplication2"
x:Name="MainUserControl"
Width="400"
Height="300"
mc:Ignorable="d">
<UserControl.Resources>
<DataTemplate x:Key="ChildTemplate">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected}"/>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ParentTemplate">
<StackPanel>
<TextBlock Text="{Binding Name}" />
<ListBox ItemsSource="{Binding Path=Children}" ItemTemplate="{StaticResource ChildTemplate}"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<StackPanel x:Name="LayoutRoot" Background="White">
<ListBox ItemsSource="{Binding ElementName=MainUserControl, Path=ParentList}" ItemTemplate="{StaticResource ParentTemplate}"/>
</StackPanel>
</UserControl>
This results in a view like this. I have omitted all styling for simplicity, I am sure you can make it more sexy ;)
Now you can in the code behind only process Child with IsSelected true
If you have more than one level... ie..Your Children have children you will have to use the HierarchicalDataTemplate

Related

MVVM WPF TreeView in the MainWindow bound to the ObservableCollection doesn't update when the ObservableCollection was modified from another window

The problem might look similar to this
But I don't have an issue with updating the TreeView if the ObservableCollection is modified in the same window (as in the case of the linked post).
I have a TreeView located in my MainWindow. I use a HierarchicalDataTemplate for the TreeView, basically the TreeView is structured into three levels of hierarchy: Group, Report, Node
For each level of the TreeView, I have created simple Model classes, each class will have a Name property and an ObservableCollection of its children class as second property (e.g., Group class will have a Name and a ReportList property, whilst Report class will have a Name and NodeList property)
In the ViewModel of my MainWindow, I have an ObservableCollection of the Group objects (I named it Groups) that is bound to the TreeView. I also have a RelayCommand method that will add a new Group object to the Groups collection (Let's call it the ImportReport Method). If I bind the RelayCommand to the button inside the MainWindow, everything worked fine, the TreeView is updated immediately with new Group Object each time I press the button.
However, this becomes an issue when I have another window, let's call it ImportWindow. In ImpportWindow, I have a button. Probably this is the cause of the issue, but I use the same ViewModel for both the ImportWindow and the MainWindow so that they can share the Groups property that is bound to the TreeView in the MainWindow. My goal is to perform addition to the TreeView from the second window by pressing a button from there. Now, If I move the binding of the ImportReport command to the button in ImportWindow instead, then try to run the code, pressing the button on ImportWindowdoesn't update the TreeView at all. I have debugged the code and the Groups ObservableCollection in the ViewModel indeed recorded a new addition but the TreeView doesn't display the new Group object.
Any explanation to this? Thanks!
MainWindow
<Window x:Class="StressReportWPF.MainWindow"
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:StressReportWPF"
xmlns:ribbon="clr-namespace:System.Windows.Controls.Ribbon;assembly=System.Windows.Controls.Ribbon"
xmlns:viewModel="clr-namespace:StressReportWPF.ViewModel"
xmlns:data="clr-namespace:StressReportWPF.DataElements"
mc:Ignorable="d"
Title="StressReportApp"
Height="750"
Width="1000">
<Window.DataContext>
<viewModel:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type data:DisplayGroup}"
ItemsSource="{Binding ReportList}">
<TextBlock Text="{Binding DisplayGroupName}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type data:DisplayReport}"
ItemsSource="{Binding NodeList}">
<TextBlock Text="{Binding DisplayReportName}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type data:DisplayNode}">
<TextBlock Text="{Binding DisplayNodeName}"/>
</DataTemplate>
</Window.Resources>
<Border Background="#e7f4fe">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="75"/>
<RowDefinition/>
</Grid.RowDefinitions>
...
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0"
Margin="0,20,20,20"
Background="#dde6ed"
BorderBrush="Black"
BorderThickness="1">
<ScrollViewer>
<TreeView Background="Transparent"
ItemsSource="{Binding Groups, UpdateSourceTrigger=PropertyChanged}">
</TreeView>
</ScrollViewer>
</Border>
<Border Grid.Column="1"
Margin="20,20,0,20"
Background="White"
BorderBrush="Black"
BorderThickness="1">
<ContentControl Margin="0"
Content="{Binding CurrentView}">
</ContentControl>
</Border>
</Grid>
</Grid>
</Border>
ImportWindow
<Window x:Class="StressReportWPF.ImportWindow"
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:StressReportWPF"
xmlns:viewModel="clr-namespace:StressReportWPF.ViewModel"
xmlns:data="clr-namespace:StressReportWPF.DataElements"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
mc:Ignorable="d"
Title="ImportWindow" Height="450" Width="800"
Background="#dde6ed">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
<DataTemplate x:Key="GroupComboBoxTemplate">
<TextBlock Text="{Binding DisplayGroupName}"/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<viewModel:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="500"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
...
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="200"/>
<RowDefinition Height="150"/>
<RowDefinition/>
</Grid.RowDefinitions>
...
<Button Grid.Row="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Height="40"
Width="100"
Content="Import"
Style="{StaticResource StandardButtonStyle}"
Command="{Binding ImportCommand}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:CallMethodAction MethodName="Close"
TargetObject="{Binding RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType=Window}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
</Grid>
ViewModel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StressReportWPF.Core;
using StressReportWPF.DataElements;
using System.Collections.ObjectModel;
using System.Windows.Input;
using StressReportWPF.Views;
namespace StressReportWPF.ViewModel
{
public class MainViewModel: ObservableObject
{
#region ViewModel-related Properties
public RelayCommand HomePageViewCommand { get; set; }
public RelayCommand LoadCaseAssignmentViewCommand { get; set; }
public RelayCommand JointNumberAssignmentViewCommand { get; set; }
public HomePageViewModel HomePageVM { get; set; }
public LoadCaseAssignmentViewModel LoadCaseAssignmentVM { get; set; }
public JointNumberAssignmentViewModel JointNumberAssignmentVM { get; set; }
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
OnPropertyChanged();
}
}
#endregion
private ICommand _importCommand;
public ICommand ImportCommand
{
get
{
if (_importCommand == null)
{
_importCommand = new RelayCommand(
param => ImportReport()
);
}
return _importCommand;
}
}
private ICommand _importWindowCommand;
public ICommand ImportWindowCommand
{
get
{
if (_importWindowCommand == null)
{
_importWindowCommand = new RelayCommand(
param => ShowImportWindow()
);
}
return _importWindowCommand;
}
}
#region Data Properties
private readonly ObservableCollection<DisplayGroup> _groups = new ObservableCollection<DisplayGroup>();
public ObservableCollection<DisplayGroup> Groups
{
get { return _groups; }
}
public bool IsNewGroupCreated { get; set; }
public object SelectedGroup { get; set; }
private DisplayGroup _newGroup = new DisplayGroup();
public DisplayGroup NewGroup
{
get { return _newGroup; }
set
{
_newGroup = value;
OnPropertyChanged();
}
}
#endregion
public MainViewModel()
{
HomePageVM = new HomePageViewModel();
LoadCaseAssignmentVM = new LoadCaseAssignmentViewModel();
JointNumberAssignmentVM = new JointNumberAssignmentViewModel();
CurrentView = HomePageVM;
HomePageViewCommand = new RelayCommand(o =>
{
CurrentView = HomePageVM;
});
LoadCaseAssignmentViewCommand = new RelayCommand(o =>
{
CurrentView = LoadCaseAssignmentVM;
});
JointNumberAssignmentViewCommand = new RelayCommand(o =>
{
CurrentView = JointNumberAssignmentVM;
});
// Set a temporary Group list
List<DisplayGroup> testGroupList = new List<DisplayGroup>();
List<DisplayNode> testNodeList = new List<DisplayNode>();
List<DisplayReport> testReportList = new List<DisplayReport>();
DisplayNode nodeA = new DisplayNode();
nodeA.DisplayNodeName = "Node A";
DisplayNode nodeB = new DisplayNode();
nodeB.DisplayNodeName = "Node B";
DisplayNode nodeC = new DisplayNode();
nodeC.DisplayNodeName = "Node C";
testNodeList.AddRange(new List<DisplayNode> { nodeA, nodeB, nodeC }) ;
ObservableCollection<DisplayNode> testNodeObsCol = new ObservableCollection<DisplayNode>(testNodeList);
DisplayReport reportA = new DisplayReport();
reportA.DisplayReportName = "Report A";
reportA.NodeList = testNodeObsCol;
DisplayReport reportB = new DisplayReport();
reportB.DisplayReportName = "Report B";
reportB.NodeList = testNodeObsCol;
DisplayReport reportC = new DisplayReport();
reportC.DisplayReportName = "Report C";
reportC.NodeList = testNodeObsCol;
testReportList.AddRange(new List<DisplayReport> { reportA, reportB, reportC });
ObservableCollection<DisplayReport> testReportObsCol = new ObservableCollection<DisplayReport>(testReportList);
DisplayGroup groupA = new DisplayGroup();
groupA.DisplayGroupName = "Group A";
groupA.ReportList = testReportObsCol;
DisplayGroup groupB = new DisplayGroup();
groupB.DisplayGroupName = "Group B";
groupB.ReportList = testReportObsCol;
DisplayGroup groupC = new DisplayGroup();
groupC.DisplayGroupName = "Group C";
testGroupList.AddRange(new List<DisplayGroup> { groupA, groupB, groupC });
foreach (var item in testGroupList)
{
Groups.Add(item);
}
}
public void ShowImportWindow()
{
//ImportViewModel importVM = new ImportViewModel();
//importVM.Groups = this.Groups;
ImportWindow importWindow = new ImportWindow();
//importWindow.DataContext = importVM;
importWindow.Show();
}
public void ImportReport()
{
if (IsNewGroupCreated)
{
DisplayGroup newGroup = new DisplayGroup();
newGroup.DisplayGroupName = "Group D";
Groups.Add(newGroup);
}
else
{
}
}
}
}

ItemsControl using WrapPanel, not displaying anything

Goal: I am trying to create a wrap panel that has children that are bound to an observable collection.
Current Expected Behavior: I expect to see 3 nested wrap panels that have an ellipse, a text block, a label and a checkbox.
Problem: My wrap panel and contents are not displayed at runtime. (Note: "Test" and "Test 2" Labels outside of the itemscontrol do display as expected.)
I have read this and it doesn't seem to solve my problem.
Code Behind
using MVVM_SandBox.Models;
using MVVM_SandBox.ViewModels;
namespace MVVM_SandBox
{
public partial class MainWindow
{
public MainViewModel VMMain = new MainViewModel();
public MainWindow()
{
VMMain.SomeItemModelBlahs = new System.Collections.ObjectModel.ObservableCollection<ItemModelBlah>() { new ItemModelBlah() { Label = "blah0" }, new ItemModelBlah() { Label = "blah1", CoolStuff = true }, new ItemModelBlah() { Label = "blah2" } };
InitializeComponent();
}
}
}
XAML
<Window x:Name="winMain" x:Class="MVVM_SandBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewmodels="clr-namespace:MVVM_SandBox.ViewModels"
Title="MVVM SandBox" Height="600" Width="800" AllowDrop="True">
<Window.DataContext>
<viewmodels:MainViewModel></viewmodels:MainViewModel>
</Window.DataContext>
<StackPanel>
<Label Width="Auto" Height="Auto">Test</Label>
<ItemsControl ItemsSource="{Binding SomeItemModelBlahs}" Margin="10,10,10,10">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel x:Name="WrapPanelOfModelItems" Margin="10,10,10,10" Width="400" Height="200" IsItemsHost="True" MinWidth="200" MinHeight="200">
</WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Width="100" Height="100" Margin="10">
<Ellipse Width="10" Height="10" Fill="Aqua"></Ellipse>
<TextBlock Margin="10" Text="{Binding Label}"></TextBlock>
<Label>kjasdkjalsdjklsad</Label>
<CheckBox IsChecked="{Binding CoolStuff}">
<Label>Hello</Label>
</CheckBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Label Height="Auto" Width="Auto">Test 2</Label>
</StackPanel>
</Window>
View Model
using MVVM_SandBox.Models;
using System.Collections.ObjectModel;
namespace MVVM_SandBox.ViewModels
{
//[ImplementPropertyChanged]
public class MainViewModel
{
public ObservableCollection<ItemModelBlah> SomeItemModelBlahs { get; set; }
public MainViewModel()
{
}
}
}
Model
namespace MVVM_SandBox.Models
{
public class ItemModelBlah
{
public string Label { get; set; }
public bool CoolStuff { get; set; }
}
}
The code is creating two instances of MainViewModel: once in the code behind, and again in the XAML. The code behind instance has a non-null ObservableCollection, while the instance in the XAML is set as the DataContext.
I would suggest removing the viewmodels:MainViewModel from the XAML, and creating it solely in code:
public partial class MainWindow
{
public MainViewModel VMMain = new MainViewModel();
public MainWindow()
{
DataContext = VWMain;
InitializeComponent();
}
}
Also, it's a better design to set up the ObservableCollection from within the view model itself:
class MainViewModel
{
public ObservableCollection<ItemModelBlah> SomeItemModelBlahs { get; private set; }
public MainViewModel()
{
SomeItemModelBlahs = new ObservableCollection<ItemModelBlah>()
{
new ItemModelBlah() { Label = "blah0" },
new ItemModelBlah() { Label = "blah1", CoolStuff = true },
new ItemModelBlah() { Label = "blah2" }
};
}
}

WPF DataGrid with 2 DataGridComboBoxColumns update second combobox based on selection of first combobox

I have a Datagrid that is bound to a datasoruce. Also there are 2 DataGridComboBoxColumn's that are populated by a static List. The binding of the DataGridComboBoxColumn's are like:
<DataGridComboBoxColumn Header="Category" Width="150" SelectedValuePath="ID" SelectedValueBinding="{Binding Category, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="CategoryName" ItemsSource="{Binding ReturnCategories, Source={StaticResource Typeslist}}"/>
<DataGridComboBoxColumn Header="Sub Category" Width="150" SelectedValuePath="ID" SelectedValueBinding="{Binding SubCategory, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="CategoryName" ItemsSource="{Binding ReturnSubCategories, Source={StaticResource Typeslist}}"/>
When the datagrid is loaded the Main Category combobox is populated with the categories and also selected correctly based on the record set.
The second combobox is populated also. What I want to achieve is whenever I change the main category combobox the sub category combobox needs to load corresponding values based on the selection of main categories.
Currently the static resources of the main and sub categories doesn’t accept any parameters. Is it possible to pass a parameter to the sub category static resource so it could load the corresponding list.
The static resources are populated by the database on call from XAML.
static List<MainCategories> mainCatergoryBuffer = new List<MainCategories>();
static List<SubCategories> subCatergoryBuffer = new List<SubCategories>();
If I should change the subcategory content based on main category selection does this mean that the other rows values of the sub category will effected also?
How can I solve this issue?
Grid example:
EDIT
You won't be easily able to do that, these combo boxes do not inherit the data grid data context.
http://msdn.microsoft.com/query/dev12.query?appId=Dev12IDEF1&l=EN-US&k=k(System.Windows.Controls.DataGridComboBoxColumn);k(VS.XamlEditor);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5)&rd=true
Binding in a WPF data grid text column
https://www.google.fr/search?q=Cannot+find+governing+FrameworkElement+or+FrameworkContentElement+for+target+element.&oq=Cannot+find+governing+FrameworkElement+or+FrameworkContentElement+for+target+element.&aqs=chrome..69i57j0l4.1185j0j7&sourceid=chrome&es_sm=122&ie=UTF-8
WPF Error: Cannot find governing FrameworkElement for target element
Advice : use the free data grid from Xceed, you won't run onto such issues
https://wpftoolkit.codeplex.com/
Here's a really simple example on how to achieve this,
Obivously you'll want to adapt to it to your current classes.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
namespace WpfApplication5
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DataContext = new MyObject();
}
}
internal class MyObject
{
public MyObject()
{
AvailableCategories = new ObservableCollection<Category>(new List<Category>(new[]
{
new Category
{
Name = "category1",
SubCategories = new List<Category>(new[]
{
new Category {Name = "subCategory1a"},
new Category {Name = "subCategory1b"}
})
},
new Category
{
Name = "category2",
SubCategories = new List<Category>(new[]
{
new Category {Name = "subCategory2a"},
new Category {Name = "subCategory2b"}
})
}
}));
}
public ObservableCollection<Category> AvailableCategories { get; private set; }
}
public class Category
{
public string Name { get; set; }
public List<Category> SubCategories { get; set; }
public override string ToString()
{
return String.Format("Name: {0}", Name);
}
}
}
And the XAML :
<Window x:Class="WpfApplication5.MainWindow"
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:wpfApplication5="clr-namespace:WpfApplication5"
Title="MainWindow"
Width="300"
Height="300"
Loaded="Window_Loaded"
mc:Ignorable="d">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="DataTemplateCategory" DataType="wpfApplication5:Category">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</Grid.Resources>
<StackPanel>
<ComboBox x:Name="ComboBox1"
ItemTemplate="{StaticResource DataTemplateCategory}"
ItemsSource="{Binding AvailableCategories}"
d:DataContext="{d:DesignData MyClass}" />
<ComboBox DataContext="{Binding ElementName=ComboBox1,
Path=SelectedItem}"
ItemTemplate="{StaticResource DataTemplateCategory}"
ItemsSource="{Binding Path=SubCategories}"
d:DataContext="{d:DesignData Category}" />
</StackPanel>
</Grid>
</Window>

Adding TreeView in WPF with context menu in subitems

In my WPF application, I want to add a TreeView control. The tree view control needs to be populated with items from database. So I bind the ItemsSource property to string collection.
Every item in the tree control can have from 0 to 32 child items. Again these items need to be binded. Each of these sub items should have a context menu with two options "Rename" and "Delete". How can I do this in WPF?
There are a few ways to do this. Here's one way that applies the context menu using a trigger that is bound to a property IsLeaf on the underlying view model.
MainWindow.xaml:
<Window x:Class="WpfScratch.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.Resources>
<!-- the context menu for all tree view items -->
<ContextMenu x:Key="TreeViewItemContextMenu">
<MenuItem Header="Rename" />
<MenuItem Header="Delete" />
</ContextMenu>
<!-- the data template for all tree view items -->
<HierarchicalDataTemplate x:Key="TreeViewItemTemplate" ItemsSource="{Binding Nodes}">
<TextBlock x:Name="TextBlock" Text="{Binding Text}" />
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding IsLeaf}" Value="True">
<Setter TargetName="TextBlock" Property="ContextMenu" Value="{StaticResource TreeViewItemContextMenu}" />
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</Window.Resources>
<!-- the treeview -->
<TreeView DataContext="{Binding TreeView}"
ItemsSource="{Binding Nodes}"
ItemTemplate="{StaticResource TreeViewItemTemplate}">
</TreeView>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowModel(
new MainWindowTreeViewModel(
new MainWindowTreeViewNodeModel(
"1",
new MainWindowTreeViewNodeModel("A"),
new MainWindowTreeViewNodeModel("B"),
new MainWindowTreeViewNodeModel("C")),
new MainWindowTreeViewNodeModel(
"2",
new MainWindowTreeViewNodeModel("A"),
new MainWindowTreeViewNodeModel("B"),
new MainWindowTreeViewNodeModel("C")),
new MainWindowTreeViewNodeModel(
"3",
new MainWindowTreeViewNodeModel("A"),
new MainWindowTreeViewNodeModel("B"),
new MainWindowTreeViewNodeModel("C"))));
}
}
MainWindowModel.cs:
public class MainWindowModel
{
public MainWindowModel(MainWindowTreeViewModel treeView)
{
TreeView = treeView;
}
public MainWindowTreeViewModel TreeView { get; private set; }
}
public class MainWindowTreeViewModel
{
public MainWindowTreeViewModel(params MainWindowTreeViewNodeModel[] nodes)
{
Nodes = nodes.ToList().AsReadOnly();
}
public ReadOnlyCollection<MainWindowTreeViewNodeModel> Nodes { get; private set; }
}
public class MainWindowTreeViewNodeModel
{
public MainWindowTreeViewNodeModel(string text, params MainWindowTreeViewNodeModel[] nodes)
{
Text = text;
Nodes = nodes.ToList().AsReadOnly();
}
public string Text { get; private set; }
public ReadOnlyCollection<MainWindowTreeViewNodeModel> Nodes { get; private set; }
public bool IsLeaf { get { return Nodes.Count == 0; } }
}

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 }
};
}
}
}

Categories