Question on Hierarchial data template in WPF Tree - wpf

I have a WPF tree which needs to show some nodes. Lets say I have 2 types of entities, EntityA and EntityB. Both these entities implement a common interface IEntity. Now, EntityA will have a collection of EntityB elements as well as EntityA elements. How can I show this via a HierarchicalDataTemplate ?
I am exposing a ObservableCollection() called "DisplayItems" in my VM that will contain elements of EntityA type.
Both EnittyA and EntityB will have another ObservableCollection called "ItemCollection". For EntityA, the ItemCollection list should ideally contain entities of EntityA and EntityB types.
The current HierarchicalDataTemplate and the XAML that I am using is as follows:
<HierarchicalDataTemplate ItemsSource="{Binding Path=ItemCollection}" DataType="{x:Type Entities:EntityB}">
<Grid>
<StackPanel Orientation="Horizontal" x:Name="compositeCT">
<Image Source="/Images/EntityB.png" Width="15" Height="15"/>
<Label Foreground="Blue" Content="{Binding Path=Name}"/>
<Label Foreground="Black" Content=" = "/>
<Label Foreground="Blue" Content="{Binding Path=CompositeLabel}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" x:Name="nCompositeCT">
<Image Source="/Images/EntityB.png" Width="15" Height="15"/>
<TextBlock Foreground="Blue" Text="{Binding Path=Name}"/>
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Foreground="Green" Text="{Binding}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=ItemCollection}" DataType="{x:Type Entities:EntityA}">
<StackPanel Orientation="Horizontal" >
<Image Source="/Images/ElementA.png" Margin="3" Width="15" Height="15" Focusable="False"/>
<TextBlock Foreground="Red" Text="{Binding Path = Name}" Focusable="False"/>
</StackPanel>
</HierarchicalDataTemplate>
<TreeView x:Name="tvMyTree"
ItemsSource="{Binding DisplayItems}"
AllowDrop="True"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
ScrollViewer.IsDeferredScrollingEnabled="True"
Margin="5"
TreeViewItem.Expanded="OnTreeViewItemExpanded"
TreeViewItem.Selected="OnTreeViewItemSelected"
/>

You can define two HierarchicalDataTemplates you are fine. And in the place of TextBlock you can put whatever complex visualization you need depending on the other Properties of your EntityA and EntityB
<HierarchicalDataTemplate DataType="{x:Type local:EnittyA}" ItemsSource="{Binding ItemCollection}" >
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:EnittyB}" ItemsSource="{Binding ItemCollection}" >
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>

I suppose ItemTemplateSelector fits perfect to your requirements. ItemTemplateSelector is inherited, so you should not care about endpoint receiver of template. Receiver (item container) just addresses to selector and the last one returns proper template according to DataType:
public class LayoutItemTemplateSelectorItem
{
public Type TargetType
{
get;
set;
}
public DataTemplate Template
{
get;
set;
}
}
[ContentProperty("Items")]
public class LayoutItemTemplateSelector : DataTemplateSelector
{
public LayoutItemTemplateSelector()
{
this.Items = new Collection<LayoutItemTemplateSelectorItem>();
}
public Collection<LayoutItemTemplateSelectorItem> Items
{
get;
private set;
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var component = (LayoutItem)item;
var typeToSearch = component.GetType();
var foundItem = this.Items
.Where(i => i.TargetType == typeToSearch)
.FirstOrDefault();
if (foundItem != null)
{
return foundItem.Template;
}
throw new Exception(string.Format(Properties.Resources.AppropriateTemplateNotFound, typeToSearch.FullName));
}
}
Usage in XAML:
<UserControl ...>
<UserControl.Resources>
<ResourceDictionary>
<HierarchicalDataTemplate x:Key="EntityBTemplate"
ItemsSource="{Binding Path=ItemCollection}"
DataType="{x:Type Entities:EntityB}">
...
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="EntityATemplate"
ItemsSource="{Binding Path=ItemCollection}"
DataType="{x:Type Entities:EntityA}">
...
</HierarchicalDataTemplate>
<LayoutItemTemplateSelector x:Key="TemplateSelector">
<LayoutItemTemplateSelectorItem TargetType="{x:Type EntityA}"
Template="{StaticResource EntityATemplate}"/>
<LayoutItemTemplateSelectorItem TargetType="{x:Type EntityB}"
Template="{StaticResource EntityBTemplate}"/>
</LayoutItemTemplateSelector>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<TreeView ItemsSource="{Binding DisplayItems}"
ItemTemplateSelector="{StaticResource TemplateSelector}"/>
</Grid>
</UserControl>

Related

Multiple levels of binding in WPF ItemsControl

How can i bind multiple levels of data in same time like List of chapters and under each chapter list of pages.
The class structure and xaml i used is shown here
public class ContentsPage
{
public string cname{ get; set; }
public string label { get; set; }
}
public class Chapter
{
public string name { get; set; }
public string id { get; set; }
public List<ContentsPage> pages { get; set; }
}
public class Model
{
public List<Chapter> chapters { get; set; }
}
<ItemsControl x:Name="TopLevelListBox" ItemsSource="{Binding}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander ExpandDirection="Down" Width="175">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=name}" Margin="0,0,5,0"/>
</StackPanel>
</Expander.Header>
<ListBox x:Name="SubListBox" ItemsSource="{Binding Path=enrichments}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=cname}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
For binding i used the code
Model data = new Model(); //load data
TopLevelListBox.DataContext = data.chapters;
Only my expander headers are filled with result. What i need to do fill the pages inside the expander ? Any ideas or samples link for doing the same
Your ItemsControl Item template should change a bit. Instead of setting the ListBox as Content of Expander, set it as Content Template.
<Expander ExpandDirection="Down"
Width="175" Content="{Binding}">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=name}"
Margin="0,0,5,0" />
</StackPanel>
</Expander.Header>
<Expander.ContentTemplate>
<DataTemplate>
<ListBox x:Name="SubListBox"
ItemsSource="{Binding Path=enrichments}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=cname}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</Expander.ContentTemplate>
</Expander>
And make sure you have sub items to show in ListBox.

accordion contenttemplate communication with viewmodel

In a silverlight 5 mvvm project I have the following code:
View:
<navigation:Page x:Class="LobDemo.View.MainView"
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:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"
DataContext="{Binding Source={StaticResource Locator}, Path=Main}" >
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<toolkit:DockPanel Grid.Column="0">
<toolkit:Accordion Name="accordion1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding Path=MenuItems}"
Margin="5,5,5,5">
<toolkit:Accordion.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</toolkit:Accordion.ItemTemplate>
<toolkit:Accordion.ContentTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Path=SubMenuItems}"
Margin="2 2 0 0"
BorderThickness="0"
SelectedItem="{Binding Path=SelectedMenuItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</toolkit:Accordion.ContentTemplate>
</toolkit:Accordion>
</toolkit:DockPanel>
</Grid>
ViewModel: (I'm only showing the required properties)
public ObservableCollection<MenuItem> MenuItems
{
get { return _menuItems; }
set
{
_menuItems = value;
RaisePropertyChanged("MenuItems");
}
}
public object SelectedMenuItem
{
get { return _selectedMenuItem; }
set
{
_selectedMenuItem = value;
RaisePropertyChanged("SelectedMenuItem");
}
}
MenuItem:
public string Name { get; set; }
public ObservableCollection<SubMenuItem> SubMenuItems { get; set; }
SubMenuItem:
public string Name { get; set; }
The code is working fine, my MenuItems are visible in the accordion control, the SubMenuItems are also loaded in the listbox. The problem comes when I select one of the items in the listbox, I want the selected item reported back to my ViewModel as SelectedMenuItem. But the property SelectedMenuItem is never filled, so I'm guessing the code cannot resolve the location of the property.
Can somebody point out what I'm doing wrong?
I found the solution for my problem, I've updated the Accordion.ContentTemplate code in the view, the code now looks like this:
<toolkit:Accordion.ContentTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Path=SubMenuItems}"
Margin="2 2 0 0"
BorderThickness="0"
SelectedItem="{Binding RelativeSource={RelativeSource AncestorType=navigation:Page}, Path=DataContext.SelectedMenuItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
With this code, the view now finds the SelectedMenuItem property in the ViewModel

WPF Relative Binding using hierarchical collection structure

I have an ObservableCollection containing an ObservableCollection:
public class BooksDetailModel
{
public BookModel Book{ get; set; }
public ObservableCollection<AuthorModel> Authors { get; set; }
}
Property in ViewModel:
public ObservableCollection<BooksDetailModel> Books { get; set; }
I would like to render this in a ListBox like this:
Book1
Author1
Author2
Book2
Author1
etc.
The top level bind is easy but I am having trouble with the inner child collection.
XAML so far:
<ListBox ItemsSource="{Binding Books}" BorderBrush="{x:Null}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Book.Name}" FontSize="12" FontWeight="Bold" />
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ???, Path=Author.Name}" FontSize="10" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Any suggestions for ??? - Relative binding the inner listbox itemsource to the parent itemsource's Authors collection.
You should bind the ItemsSource of the inner ListBox to the Authors property. And the binding in the DataTemplate will be simply binding to the Name property of the author:
<ListBox ItemsSource="{Binding Books}" BorderBrush="{x:Null}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Book.Name}" FontSize="12" FontWeight="Bold" />
<ListBox ItemsSource="{Binding Authors}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" FontSize="10" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

WPF/XAML TreeView doesn't Hightlight Nodes after Binding

So I am having an issue with the TreeView. If I build a tree view statically, every node in the tree is selectable in that when I click on it, it highlights itself blue, indicating that node is selected.
<TreeView
Grid.Column="0"
Grid.Row="2"
MinHeight="100"
MinWidth="100"
BorderBrush="White"
DataContext="{Binding Projects, Source={x:Static SizingApp:Manager.Instance}}">
<TreeViewItem Header="Project 1" IsExpanded="True">
<TreeViewItem Header="Step 1" IsExpanded="True">
<TreeViewItem Header="Load 1" IsExpanded="True"></TreeViewItem>
<TreeViewItem Header="Load 2" IsExpanded="True"></TreeViewItem>
</TreeViewItem>
<TreeViewItem Header="Step 2" IsExpanded="True">
<TreeViewItem Header="Load 1" IsExpanded="True"></TreeViewItem>
<TreeViewItem Header="Load 2" IsExpanded="True"></TreeViewItem>
</TreeViewItem>
</TreeViewItem>
However, I am Binding to the TreeView to populate it. Furthermore, I am binding to objects that implement Emiel Jongerius's BindableObjectBase3 class. This is a wonderful base class implementation that allows my objects to be Bindable and implement the INotifyPropertyChanged interface with the concern for manual DependencyProperty management.
So this is the basic class structure (simplified from my actual objects) that I am trying to implement in a TreeView.
public abstract class MyCustomClass : BindableObjectBase3
{
private string m_strName;
public virtual string Name
{
get
{
using (this.GetPropertyTracker(() => this.Name))
{
return m_strName;
}
}
set
{
this.SetValue(ref this.m_strName, value, () => this.Name);
}
}
}
public class Project : MyCustomClass
{
private List<Step> m_steps;
public List<Step> Steps
{
get
{
using (this.GetPropertyTracker(() => this.Steps))
{
return m_steps;
}
}
set
{
this.SetValue(ref this.m_steps, value, () => this.Steps);
}
}
}
public class Step : MyCustomClass
{
private List<Load> m_loads;
public List<Load> Loads
{
get
{
using (this.GetPropertyTracker(() => this.Loads))
{
return m_loads;
}
}
set
{
this.SetValue(ref this.m_loads, value, () => this.Steps);
}
}
}
public class Load : MyCustomClass
{
}
And this is the basic XAML I use to implement the TreeView:
<TreeView
Grid.Column="0"
Grid.Row="2"
MinHeight="100"
MinWidth="100"
BorderBrush="White"
DataContext="{Binding Projects, Source={x:Static SizingApp:Manager.Instance}}">
<TreeView.Resources>
<HierarchicalDataTemplate x:Key="LoadTemplate">
<TreeViewItem Header="{Binding Path=Name}">
</TreeViewItem>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="StepTemplate">
<TreeViewItem Header="{Binding Path=Name}" IsExpanded="True"
ItemsSource="{Binding Path=Loads}"
ItemTemplate="{StaticResource LoadTemplate}">
</TreeViewItem>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="ProjectTemplate">
<TreeViewItem Header="{Binding Path=Name}" IsExpanded="True"
ItemsSource="{Binding Path=Steps}"
ItemTemplate="{StaticResource StepTemplate}">
</TreeViewItem>
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeViewItem
Header="{Resx ResxName=PSSPECApplication.Controls.ProjectControlResources, Key=projectTree_Header}"
ItemsSource="{Binding}"
IsExpanded="True"
Focusable="True"
ItemTemplate="{StaticResource ProjectTemplate}">
</TreeViewItem>
</TreeView>
Now all of this works fine as far as binding goes. I can bind to an ObservableCollection<Project> and as I add/remove/manipulate projects, the TreeView updates accordingly.
However, the only node in the TreeView that seems selectable is the first node (the one that is static). All of the other nodes create through dynamic Binding do not indicate that they are selected on the GUI. I would expect that they would also highlight blue when clicked on. But instead they do nothing. Does anyone have an idea of why?
You shouldn't be defining TreeViewItems explicitly in your ItemTemplates. The reason you can't select any of the items is that they have no parent TreeView to control selection behavior. You need to let the TreeView generate the TreeViewItem controls for you and only use the item templates to define the UI for the Headers and the bindings for their items. Use something like this instead:
<Window.Resources>
<HierarchicalDataTemplate x:Key="LoadTemplate">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="StepTemplate" ItemsSource="{Binding Loads}" ItemTemplate="{StaticResource LoadTemplate}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="ProjectTemplate" ItemsSource="{Binding Steps}" ItemTemplate="{StaticResource StepTemplate}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
</Window.Resources>
<TreeView MinHeight="100" MinWidth="100" BorderBrush="White"
ItemsSource="{Binding Path=Projects}"
ItemTemplate="{StaticResource ProjectTemplate}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
My TreeView is acting just as the original poster has described. I'm able to select everything in my TreeView Control but If I select a childnode the Parent Node is what Highlights even though It binds and clicks the child. I tried structuring my code to resemble John Bowen's suggestion and still get the same results. Here is my XAML Code:
<Fluent:RibbonWindow.Resources>
<HierarchicalDataTemplate x:Key="MyTreeViewStyle" ItemsSource="{Binding Contacts}">
<!-- Display the Index by showing it's Index string -->
<StackPanel Orientation="Horizontal">
<Image x:Name="img" Width="16" Height="16" Stretch="Fill" Source="Images\closedfolder16.png" />
<TextBlock Text="{Binding Index}" Margin="5,0" ToolTip="{Binding Index}"/>
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image x:Name="img2" Width="16" Height="16" Stretch="Fill" Source="Images\closedfolder16.png" />
<TextBlock Text="{Binding Name}" Margin="5,0" ToolTip="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</Fluent:RibbonWindow.Resources>
Then I use it here in my TreeView Control:
<Grid>
<TreeView Margin="2,2,2,2" x:Name="MyTreeView" ItemTemplate="{StaticResource MyTreeViewStyle}" ItemContainerStyle="{StaticResource AlwaysExpand}" ScrollViewer.CanContentScroll="True" BorderThickness="0" TreeViewItem.Selected="btnDisplayContact_Click" />
</Grid>
The result is this screenshot, notice how the parent node D is highlighted after clicking the child node, instead of the child node being highlighted:
http://home.swbell.net/davis32/Snapshot.png
Can't post images so just cut and paste the URL link.

Silverlight Data Binding for Collection in Stack Panel

I'm new to Silverlight, so I don't have a complete grasp of all the controls at my disposal. What I would like to do is use databinding and a view model to maintain a collection of items. Here is some mock code for what I'd like to do:
Model
public class MyItem
{
public string DisplayText { get; set; }
public bool Enabled { get; set; }
}
ViewModel
public class MyViewModel : INotifyPropertyChanged
{
private ObservableCollection<MyItem> _myItems = new ObservableCollection<MyItem>();
public ObservableCollection<MyItem> MyItems
{
get { return _myItems; }
set
{
_myItems = value
NotifyPropertyChanged(this, "MyItems");
}
}
}
View
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel ItemsSource="{Binding MyItems}">
<StackPanel Orientation="Horizontal">
<CheckBox "{Binding Enabled, Mode=TwoWay}"></CheckBox>
<TextBlock Text="{Binding DisplayText, Mode=TwoWay}" />
</StackPanel>
</StackPanel>
</Grid>
So my end goal would be that every time I add another MyItem to the MyItems collection it would create a new StackPanel with checkbox and textblock. I don't have to use a stack panel but just thought I'd use that for this sample.
Looks like you want a <ListBox>, then set the <ListBox.ItemTemplate> to your <StackPanel> something like this.....
<ListBox ItemsSource=”{Binding Classes, Source={StaticResource model}}”>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox "{Binding Enabled, Mode=TwoWay}"/>
<TextBlock Text="{Binding DisplayText, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
here is a great example (it's WPF, but should only be minor changes for silverlight)
yes, looks like you want a <ListBox>
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SilverlightApplication4.MainPage"
Width="640" Height="480">
<UserControl.Resources>
<DataTemplate x:Key="ItemTemplate">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Enabled, Mode=TwoWay}"/>
<TextBlock Text="{Binding DisplayText}"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" DataContext="{Binding Source={StaticResource SampleDataSource}}">
<ListBox Margin="0,0,8,0" ItemTemplate="{StaticResource ItemTemplate}" ItemsSource="{Binding Collection}"/>
</Grid>
This code will give you a ListBox with all your Data bound to a Checkbox and TextBlock with the Checkbox first and TextBox next to it.

Resources