Displaying mixed types using HierarchicalDataTemplate - wpf

I am trying to display a tree view with mixed types using HierarchicalDataTemplate but so far all my attempts failed..
I have a tree-like class that can have two different types of children. (Location and Device). To make things understandable here is the illustration of what I am trying to display:
->Location 1
|
|--->Device 1.1
|--->Device 1.2
|--->Location 1.2
|
|---->Device 1.2.1
|---->Location 1.2.1
.....etc.....
I have tried a lot of solutions I found but none worked. In the most cases I just get the class name in the tree view.
Is what I am trying to do even possible using HierarchicalDataTemplate? If it is please let me know how.

After several hours of trial and error I went back to the beginning and found the solution. I can't believe it is actually so simple.
The template looks like this:
<HierarchicalDataTemplate x:Key="NavigatorDataTemplate" DataType="{x:Type local:Location}" ItemsSource="{Binding Locations}">
<StackPanel>
<TextBlock Text="{Binding Description}"/>
<ListBox ItemsSource="{Binding Devices}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</HierarchicalDataTemplate>
The TreeView is just this:
<TreeView Name="treeNav" ItemTemplate="{StaticResource NavigatorDataTemplate}"/>
And since the collection that needs to be displayed is in the code already i just set it to the ItemsSource:
treeNav.ItemsSource = locations;

The HierarchicalDataTemplate should help to solve your problem. Take a look on latest sample on MSDN Data Templating Overview

For this hierarchy to work Location object should have composite collection of Locations and Devices and you simply need to set that collection as ItemsSource of your HierarchicalDataTemplate. This sample should work then -
<HierarchicalDataTemplate DataType="{x:Type local:Location}"
ItemsSource="{Binding CompositeCollection}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Device}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
I am assuming you have Name property in each object Location and Device which you need to show on UI. Add these data templates to resources of your ItemsControl which may be ListBox, TreeView or any other ItemsControl.

Being new to WPF, I recently solved this same problem. The approach I used was to create a new class that would be used to display the data in the tree.
TreeDisplayItem Class has contractor per class type that will be displayed.
In each constructor, the _display property will be set for the class passed.
itemDef = Category Name
ItemPrice = Price + Quantity + Store location
public class TreeDisplayItem
{
readonly string _display;
readonly string _id;
readonly List<TreeDisplayItem> _items;
public LegoPriceDisplay(ItemDef itemDef, List<TreeDisplayItem> items)
{
...//Root node class type ItemDef
}
public LegoPriceDisplay(ItemPrice price)
{
....//child node type = ItemPrice
}
public List<TreeDisplayItem> Items{ get; private set; }
public string DisplayId
{
get { return _id; }
}
public string Display
{
get { return _display; }
}
}
In the WPF Page XAML, define a treeview with nested HierarchicalDataTemplates.
Of course this works for only use cases where you have fixed / Known tree item depth.
In my case, treeview will drill down to two layers (counting the root)
<TreeView Margin="0" Name="TvStorePrices" ItemsSource="{Binding TreeDisplayItemList}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="local:TreeDisplayItem" ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Display}" />
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate DataType="local:TreeDisplayItem" ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Display}" />
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>

Related

WPF databinding from DataTemplate - many-to-many relationship

I have a WPF control for laying out items and set ItemsSource for it as
ItemsSource="{Binding item_portfolio}" DataContext="{Binding SelectedItem}"
In the layout control's Resources I set the template for its items:
<Style>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical" Margin="5">
<TextBlock Text="{Binding portfolio.PortfolioName}" />
<ListView ItemsSource="{Binding ?}">
</ListView>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
The data for the bindings is a many-to-many relationship (one item can have many portfolios and one portfolio can have many items) and specified in 3 separate tables in the database (I use Entity Framework to access it). Schema and example data below:
item item_portfolio portfolio
ID (PK) Name itemID (FK) portfolioID (FK) ID PortfolioName
1 Item 1 1 1 1 Portfolio 1
2 Item 2 1 2 2 Portfolio 2
1 3 3 Portfolio 3
2 2
2 3
TextBlock binding under DataTemplate works correctly.
I don't however know how to bind the ListView ItemsSource so that it would show all the item objects belonging to that portfolio.
Edit:
I want to list portfolios in the layout control. Then under portfolio, I want to show what items it contains. The below image shows the UI when the SelectedItem is Item 1.
(First I show what portfolios this item has. This gives 3 portfolios. This works ok. Then on UI I click the portfolio 3, and it should show all items (Item 1 and Item 2) belonging to that portfolio. That doesn't work yet.)
We don't model our data models in code in the same way as they are modelled in the database. We don't make 'joining classes' to model 'joining tables'. Instead, we create hierarchical data models. In your case, you should have an Item class that has a Portfolio property of type Portfolio. There should be no ItemPortfolio class, as it makes no sense. Then, your Binding should be this`:
<ItemsControl ItemsSource="{Binding Items}" ... />
Then your DataTemplate (which will automatically have its DataContext set to an item from the Item class collection) should look more like this:
<DataTemplate>
<StackPanel Orientation="Vertical" Margin="5">
<TextBlock Text="{Binding PortfolioName}" />
<ListView ItemsSource="{Binding Portfolio}">
</ListView>
</StackPanel>
</DataTemplate>
Clearly, to make this work, you'd need to have those properties defined in your Item class.
UPDATE >>>
In that case, make a Portfolio class with an Items property of type ObservableCollection<Item> and set the ItemsSource like this:
<ItemsControl ItemsSource="{Binding Item.Portfolios}" ... />
To clarify, your Item class should also have a collection property of type ObservableCollection<Portfolio>... this will lead to some data object duplication, but in WPF it is more important to provide your views with the data that they require.
I used Expander control here as you want PortfolioName as header and on click of
PortfolioName you want to display corresponding PortfolioItemList.
xaml code
<ItemsControl MaxHeight="300" ItemsSource="{Binding PortfolioInfo}" ScrollViewer.VerticalScrollBarVisibility="Auto" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander>
<Expander.Header>
<TextBlock Background="YellowGreen" Text="{Binding name}"></TextBlock>
</Expander.Header>
<Expander.Content>
<ListBox ItemsSource="{Binding ItemList}"></ListBox>
</Expander.Content>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
C# code
public partial class MainWindow : Window
{
public ObservableCollection<Portfolio> PortfolioInfo { get; set; }
public MainWindow()
{
InitializeComponent();
PortfolioInfo = new ObservableCollection<Portfolio>()
{
new Portfolio(){name="Portfolio1", ItemList= new List<string>(){"Item1","Item2","Item3"}},
new Portfolio(){name="Portfolio2", ItemList= new List<string>(){"Item1","Item2","Item3"}},
new Portfolio(){name="Portfolio3", ItemList= new List<string>(){"Item1","Item2","Item3"}}
};
this.DataContext = this;
}
}
public class Portfolio
{
public string name { get; set; }
public List<string> ItemList { get; set; }
}
Note : Edit Expander style/template to achieve desired UI
Rseult

WPF XCEED DataGrid multiple property in one column

I have a class which has two level of collections. The software is about salaries payment. The idea is a payment action consists of multiple paid employees. And each employees could have multiple salaries cuts. The Object would look like:
Payment -->Payment Object
Date
ID
Employees: -->ObservableCollection of EmpPayment Objects
Emp A -->And EmpPayment Object
Name
TotalCut --> An Integer Readonly Property that sums the Cuts.
Cuts --> Collection of Cut object
Cut1 --> Cut object 1
Cut2 --> Cut object 2
Emp B
Name
TotalCuts
Cuts
Cut1
Cut2
I am using a exceed DataGrid. So far so good except I want to display two values in one column using CellContentTemplate, that is the Cuts and the TotalCut. So the datagrid will look like:
Employee | Cuts
Emp A | [The Total Cut]
Cut 1
Cut 2
Emp B | [The Total Cut]
Cut 1
Cut 2
At the cuts column, i want to use a Xceed DropDownButton to show the total, and user can edit the cuts by editting the dropdowncontent. So far the XAML i made for the Employees ObservableCollection:
<xcdg:DataGridControl x:Name="GridEmployees" ItemsSource="{Binding Employees}" AutoCreateColumns="False">
<xcdg:DataGridControl.Columns>
<xcdg:Column FieldName="Name" Title="Employee"/>
<xcdg:Column FieldName="Cuts" Title="Cuts">
<xcdg:Column.CellContentTemplate>
<DataTemplate>
<xctk:DropDownButton Content="{Binding Path=TOTALCUT}">
<xctk:DropDownButton.DropDownContent>
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding CutDescription}"/>
<TextBox Text="{Binding Amount}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</xctk:DropDownButton.DropDownContent>
</xctk:DropDownButton>
</DataTemplate>
</xcdg:Column.CellContentTemplate>
</xcdg:Column>
</xcdg:DataGridControl.Columns>
</xcdg:DataGridControl>
Binding to the Cuts ObservableCollection works well, but not the TotalCut. How do I bind the TotalCut [Intentionally written in all caps above] to that same column?
Using two columns is possible, but will not be pretty.
Forget the XCeed DataGridControl and welcome to standard DataGrid. Just use the DataGridTemplateColumn there.
The other alternative is just to use two columns in XCeed DataGridControl which is not preety.
All you have to do - provide your own cell editor and CellTemplate, which will bind to corresponding properties. Here's my working solution:
<xcdg:DataGridControl AutoCreateColumns="False" ItemsSource="{Binding Items}">
<xcdg:DataGridControl.View>
<xcdg:TableflowView ScrollingAnimationDuration="0" ContainerHeight="44"/>
</xcdg:DataGridControl.View>
<xcdg:DataGridControl.Resources>
<DataTemplate x:Key="EditDataTemplate" DataType="wpfApplication1:State">
<StackPanel>
<TextBox Text="{Binding Path=Name}"/>
<TextBox Text="{Binding Path=Post}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ReadDataTemplate" DataType="wpfApplication1:State">
<StackPanel>
<TextBlock HorizontalAlignment="Right" Text="{Binding Path=Name}"/>
<TextBlock HorizontalAlignment="Right" Text="{Binding Path=Post}"/>
</StackPanel>
</DataTemplate>
</xcdg:DataGridControl.Resources>
<xcdg:DataGridControl.Columns>
<xcdg:Column FieldName="Zip" Title="Zip"/>
<xcdg:Column FieldName="Stat" Title="Zip2"
CellContentTemplate="{StaticResource ReadDataTemplate}">
<xcdg:Column.CellEditor>
<xcdg:CellEditor EditTemplate="{StaticResource EditDataTemplate}"/>
</xcdg:Column.CellEditor>
</xcdg:Column>
</xcdg:DataGridControl.Columns>
</xcdg:DataGridControl>
and codebehind of "data object":
public class User
{
private string _zip;
public string Zip
{
get { return _zip; }
set
{
_zip = value;
Stat = new State();
Stat.Name = "stat: " + value;
}
}
public State Stat { get; set; }
}
public class State
{
public string Name { get; set; }
public string Post { get; set; }
}
Entire solution code

How to Dynamically Populate a treeView in WPF getting value from database?

I want to populate a tree getting parent and nodes from database in WPF. My database structure is as;
Here deficiencyID is the id of the node and parentID is the id of the parent.
You can model your db table with an entity like this:
public class Deficiency
{
public int DeficiencyID { get; set; }
public int ParentID { get; set; }
//... OTHER PROPERTIES ...//
}
then take a look at this answer.
EDIT: Your XAML with the treeview can be like this:
<!--Bind to Groups generated from codebehind
Every group have property Name and Items -->
<TreeView Name="treeview1" ItemsSource="{Binding Groups}" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Items}">
<!-- Here Bind to the name of the group => this is the Parent ID -->
<TextBlock Text="{Binding Path=Name}" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<StackPanel>
<!-- Here the data context is your class Deficiency,
so bind to its properties-->
<TextBlock Text="{Binding Path=DeficiencyID}"/>
<!-- ... -->
<TextBlock Text="{Binding Path=OtherProperties}"/>
<!-- ... -->
</StackPanel>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
and your codebehind should be something like this:
List<Deficiency> myList = new List<Deficiency>();
// here load from DB //
ICollectionView view = CollectionViewSource.GetDefaultView(myList);
view.GroupDescriptions.Add(new PropertyGroupDescription("ParentID"));
treeview1.DataContext = view;
HTH

Display Dynamic Number in WPF TabItem Header

I have a TabControl where every item contains a User Control called Timeline. This "Timeline" has a property called "Number" which changes during runtime.
I want to make the property "Number" to be displayed in the TabItem header. And i have really no idea how to do that to be honest.
My first thought is that i have to create a Custom Control that derives from the original TabItem Control and create a DependencyProperty or something with a custom ControlTemplate.
I feel that i'm pretty bad on explaining this...
An example: I Want to do something like the third image in the post on following url, but instead of the close-button, i want to display the property "Number" that dynamically changes during runtime!
http://geekswithblogs.net/kobush/archive/2007/04/08/closeabletabitem.aspx
If we have this class:
public class MyItem : INotifyPropertyChanged
{
public string Title {get; set;}
private int number;
public int Number
{
get { return number; }
set
{
number= value;
OnPropertyChanged("Number");
}
}
}
We can bind the collection of items to TabControl:
<TabControl ItemsSource="{Binding MyItems}">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}"/>
<TextBlock Text="{Binding Number}"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<my:TimeLine Number="{Binding Number, Mode=TwoWay}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>

WPF ListView SelectedValue not being set

I've looked at a few solutions but nothing has worked yet for me.
I'm using MVVM for this project and have a ListView that I can't set the SelectedItem property.
If this is my (simplified) XAML.
<ListView Name="uxPackageGroups" ItemsSource="{Binding Path=PackageGroups, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" BorderThickness="0"
BorderBrush="#FF0000E8" ScrollViewer.CanContentScroll="True"
SelectedItem="{Binding Path=PackageGroupSelectedItem, Mode=TwoWay}" >
<ListView.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Name}" Height="20" Margin="0" Padding="0"/>
</DataTemplate>
</ListView.ItemTemplate>
And I bind it to a PackageGroups in my ViewModel
public PackageGroup PackageGroupSelectedItem {get; set; }
public ObservableCollection<PackageGroup> PackageGroups {get; set; }
private void LoadUI()
{
PackageGroups = Factory.LoadAllPackageGroups())
// if I try to hard-code a pre-selected item here it doesn't work.
// 34 is a valid ID and I see a valid object when stepping through the code
PackageGroupSelectedItem = PackageGroup.LoadByID(db, 34);
}
Anything glaring in my code?
Thanks.
One possible problem is that you're not implementing INotifyPropertyChanged in the PackageGroupSelectedItem property.
I've just came into the same situation and it turned out that my collection item had incorrectly implemented "Equals" method. It doesn't need to implement INotifyPropertyChanged on collection item, but Equals should be implemented correctly...

Resources