Dynamically grouping/nesting flat data in a TreeView - c#

I would like to take a flat list of objects and present them in a TreeView using custom groups.
public enum DocumentType { Current, Inactive, Transition, Checkpack, TechLog, Delivery }
public enum Status { Approved, Rejected, Pending }
public class Document
{
public string Name { get; set; }
public DateTime Created { get; set; }
public string CreatedBy { get; set; }
public DateTime Modified { get; set; }
public string ModifiedBy { get; set; }
public DocumentType Type { get; set; }
public Status Status { get; set; }
}
For example... The user might want to see this list, with the top level group being "Status" and the second level being "Name". This all needs to be configurable from the UI, and I'm struggling to find the best way to achieve it.
I've had a brief look at the CollectionViewSource object, but couldn't find a good way to get it to dynamically build a TreeView.
My gut feeling is that i'll need to do some clever templating in XAML - this is as far as i've got...
<Window.Resources>
<DataTemplate x:Key="DocumentTemplate">
<DockPanel>
<TextBlock Text="{Binding Name}" />
</DockPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="GroupTemplate"
ItemsSource="{Binding Path=Items}"
ItemTemplate="{StaticResource DocumentTemplate}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding Documents.View.Groups}"
ItemTemplate="{StaticResource GroupTemplate}"/>
</Grid>
public CollectionViewSource Documents
{
get
{
var docs = new CollectionViewSource();
docs.Source = DocumentFactory.Documents;
docs.GroupDescriptions.Add(new PropertyGroupDescription("CreatedBy"));
return docs;
}
}
Of course this only displays the Top-level group ("CreatedBy").
After reading a question below, I managed to come up with a better question...
My question: Is it possible to have a generic HierarchicalDataTemplate for a TreeView that displays custom groups applied to a CollectionViewSource.

Honestly this should be marked as a bug in WPF. I too tried and found that Documents.View.Groups throws binding error on View property being null.
Also
<TextBlock Text="{Binding Path=Name}" />
is correct in the GroupTemplate but not in the DocumentTemplate. Note that Groups are of special type GroupItem where Name is one such property that holds the value on which grouping has taken place.
On the other hand in DocumentTemplate, we should refer the property that we need to display on the leaf nodes items e.g. in my example I used Employee.FirstName (I grouped on Gender).
<DataTemplate x:Key="DocumentTemplate">
<DockPanel>
<TextBlock Text="{Binding FirstName}" />
</DockPanel>
</DataTemplate>
Now for binding to take effect I had to introduce a converter which simply returns Groups.
public class GroupsConverter : IValueConverter
{
public object Convert(object value, ...)
{
return ((CollectionViewSource)value).View.Groups;
}
....
}
And tree view binding was changed this way...
<TreeView x:Name="treeView"
ItemsSource="{Binding Path=Documents,
Converter={StaticResource GroupsConverter}}"
ItemTemplate="{StaticResource GroupTemplate}" />
Then this worked for me.
Does this help you?

Related

WPF databinding to a TreeView from Code behind

so I have a treeView which works fine. But my problem is that I want to display different trees, of different Types without having to create one for every scenario in XAML. I know how to set the content of a listView from code behind, is the same possible for a treeView? My treeView right now looks like the following but obviously only works for Items of the Type CAN-Message:
<TreeView TreeViewItem.Selected="OnItemSelected" MouseDoubleClick="Tree_MouseDoubleClick" Name="tree">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type srcM:CANMessage}" ItemsSource="{Binding Signals}">
<StackPanel Orientation="Horizontal" >
<TextBlock Text="{Binding Name}"/>
<TextBlock Text=" (0x"/>
<TextBlock Text="{Binding CANid}"/>
<TextBlock Text=")"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
The thing is I have different scenarios. One for example where I only want to display the Signals List of the class ADTF (shown below) which is of the Type string.
And in another case I want to display the CANMessages list of the CAN Class and for each Message Element I want to set the signal list it contains as subelements (Which is implemented in the XAML example). I have a short version of said classes following:
public class ADTF : ISignalSource
{
public string Id { get; set; }
public List<string> Signals { get; set; }
}
and
public class CAN: ISignalSource
{
public string Id { get; set; }
public List<CANMessage> Signals { get; set; }
}
public class CANMessage
{
public string Id { get; set; }
public List<string> Signals { get; set; }
}
So what I think I need to do is create a HierarchicalDataTemplate for every scenario. But I want to do it in code behind because it seems to take less code than implementing a HierarchicalDataTemplate in XAML for every scenario. Is there a way to o this?
If i understood correctly, You can use Interface to get this working.
Similar question: wpf Treeview with three levels in

How do you bind to a complex object in usercontrol.resources for Windows Phone 8.1 (C#)

I have searched the web for the last few days but can't seem to find something that I would have thought was quite a simple task. I would like to add a resource in my XAML page of my windows phone application which will reference a complex object but I can't find the correct method. Is this possible? Object is made up something similar to:
Public class ComplexClass
{
Public string name { get; set; }
Public int ID { get; set; }
Public observablecollection<SimpleClass> simpleObjects { get; set; }
Public addSimpleObject(SimpleClass newSimpleObject)
{
if (simpleObjects == null)
simpleObjects = new ObservableCollection<SimpleClass>();
simpleObjects.Add(newSimpleObject);
}
}
Public Class SimpleClass
{
Public String Name { get; set; }
Public String Disc { get; set; }
}
You could use MVVM do achieve this. There are already heaps of tutorials available that you can access to show you how to follow this design pattern, so I won't go into that.
Instead I'll just show you a simple way of getting the data to your view.
In the constructor of your UserControl (or Page or whatever), set up the DataContext to an instance of your ComplexClass:
ComplexClass complexClass;
public MyUserControl1()
{
complexClass = new ComplexClass();
complexClass.AddSimpleObject(new SimpleClass { Name = "Bob" });
this.DataContext = complexClass;
this.InitializeComponent();
}
Then in your XAML you can bind to it like this:
<StackPanel>
<!-- Binding to properties on ComplexClass -->
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding ID}" />
<ListView ItemsSource="{Binding SimpleObjects}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<!-- Binding to properties on SimpleClass -->
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Disc}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
Without knowing specifics of your code, it's hard for me to suggest a method that is most suitable for you. I'd read up on MVVM and view models.

How bind an object to treeview from Xaml

I am trying to bind an object to the treeviewcontrol WPF by XAML, I am getting the treview as empty. When i am doing that by treeview.items.add(GetNode()) then its working.
I am using MVVM Framework(caliburn.Micro) I wanted to do it in Xaml, how do I assign Item source property in xaml? I tried with creating a property of Node class and calling the Method GetNode() with in the property, and assigned that property as itemssource of the treeview and changed the List to Observable collection. Still issue is same.
Working Xaml when doing treeview.items.Add(GetNode()) which returns a Node and and i as assigning Nodes collection to Hireachial Template.
<TreeView Name="treeview2"
Grid.RowSpan="2"
Grid.ColumnSpan="2"
ItemContainerStyle="{StaticResource StretchTreeViewItemStyle}" Width="300">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<DockPanel LastChildFill="True">
<TextBlock Padding="15,0,30,0" Text="{Binding Path=numitems}" TextAlignment="Right"
DockPanel.Dock="Right"/>
<TextBlock Text="{Binding Path=Text}" DockPanel.Dock="Left" TextAlignment="Left" />
</DockPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Server Side Code:
this.treeview2.Items.Add(GetNode());
GetNode recursively build a list of type Node.
public class Node
{
public string Text { get; set; }
public List<Node> Nodes { get; set; }
public ObservableCollection<Node> Nodes{get;set;} // with list and observable collection same results
public int numitems { get; set; }
}
In addition to the HierarchicalDataTemplate, which seems just fine, add a binding to the ItemsSource property of your TreeView:
public class ViewModel
{
private List<Node> _rootNodes;
public List<Node> RootNodes
{
get
{
return _rootNodes;
}
set
{
_rootNodes = value;
NotifyPropertyChange(() => RootNodes);
}
}
public ViewModel()
{
RootNodes = new List<Node>{new Node(){Text = "This is a Root Node}",
new Node(){Text = "This is the Second Root Node"}};
}
And in XAML:
<TreeView ItemsSource="{Binding RootNodes}"
.... />
Edit: Remove the call that does this.Treeview.... you don't need that. Try to keep to the minimum the amount of code that references UI Elements. You can do everything with bindings and have no need to manipulate UI Elements in code.

Binding list of objects to ItemsControl with custom ItemTemplate in MVVM

Current Setup
I have a custom class representing an installer file and some properties about that file, conforming to the following interface
public interface IInstallerObject
{
string FileName { get; set; }
string FileExtension { get; set; }
string Path { get; set; }
int Build { get; set; }
ProductType ProductType { get; set; }
Architecture ArchType { get; set; }
bool Configurable { get; set; }
int AverageInstallTime { get; set; }
bool IsSelected { get; set; }
}
My ViewModel has a ReadOnlyObservableCollection<IInstallerObject> property named AvailableInstallerObjects.
My View has a GroupBox containing the ItemsControl which binds to the aforementioned property.
<GroupBox Header="Products">
<ItemsControl ItemsSource="{Binding Path=AvailableInstallerObjects}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=FileName}" Margin="5" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
The binding works correctly, except it's not user friendly. 100+ items are shown.
Need Help Here
I'd like to be able to use my collection of IInstallerObjects but have the View present them with the following ItemTemplate structure.
<GroupBox Header="Products">
<ItemsControl ItemsSource="{Binding Path=AvailableInstallerObjects}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=ProductType}" Margin="5" />
<ComboBox ItemsSource="{Binding Path=Build}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
Basically I want to be able to group by the ProductType property, showing a list of the available products, with the ComboBox representing the available Build property values for IInstallerObjects of the ProductType.
I can use LINQ in the ViewModel to extract the groupings, but I have no idea how I'd bind to what I've extracted.
My research also turned up the possibility of using a CollectionViewSource but I'm not certain on how I can apply that to my current setup.
I appreciate your help in advance. I'm willing to learn so if I've overlooked something obvious please direct me to the information and I'll gladly educate myself.
If Build should be a collection type.
so your class should be structured like this as an example.
Public Class Customer
Public Property FirstName as string
Public Property LastName as string
Public Property CustomerOrders as observableCollection(OF Orders)
End Class
This should give you the expected results. Each item in the main items presenter will show first name last name and combobox bound to that customers orders.
I know it's simple but this should do.
All you have to do is declare a CollectionViewSource in your view and bind it to the ObservableCollection. Within this object you declare one or more GroupDescriptions which will split up the source into several groups.
Bind this source to the listbox, create a Template for the group description and you are done.
An example can be found here: WPF Sample Series – ListBox Grouping, Sorting, Subtotals and Collapsible Regions. More about CollectionViewSource can be found here: WPF’s CollectionViewSource
The description of your problem lead me to believe you are looking for some kind of colapsing / expanding / grouped / tree-view sort of thing.
XAML for the tree-view
<Window x:Class="WPFLab12.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:WPFLab12"
Title="MainWindow" Height="350" Width="525">
<Grid>
<GroupBox Header="Products">
<TreeView ItemsSource="{Binding Path=ProductTypes}">
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type loc:ProductType}"
ItemsSource="{Binding AvailableInstallerObjects}">
<TextBlock Text="{Binding Description}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:InstallerObject}">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=FileName}" Margin="5" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</GroupBox>
</Grid>
</Window>
What does that do? Well, it establishes a hierarchy of controls in the tree based on the type of data found. The first HierarchicalDataTemplate handles how to display the data for each class, and how they are related in the hierarchy. The second HierarchicalDataTemplate handles how to display each InstallerObject.
Code behind for the Main Window:
public partial class MainWindow : Window
{
public ReadOnlyObservableCollection<ProductType> ProductTypes
{
get { return (ReadOnlyObservableCollection<ProductType>)GetValue(ProductTypesProperty); }
set { SetValue(ProductTypesProperty, value); }
}
// Using a DependencyProperty as the backing store for ProductTypes. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ProductTypesProperty =
DependencyProperty.Register("ProductTypes", typeof(ReadOnlyObservableCollection<ProductType>), typeof(MainWindow), new UIPropertyMetadata(null));
public MainWindow()
{
this.InitializeComponent();
this.ProductTypes = new ReadOnlyObservableCollection<ProductType>(
new ObservableCollection<ProductType>()
{
new ProductType()
{
Description = "Type A",
AvailableInstallerObjects = new ReadOnlyObservableCollection<InstallerObject>(
new ObservableCollection<InstallerObject>()
{
new InstallerObject() { FileName = "A" },
new InstallerObject() { FileName = "B" },
new InstallerObject() { FileName = "C" },
})
},
new ProductType()
{
Description = "Type B",
AvailableInstallerObjects = new ReadOnlyObservableCollection<InstallerObject>(
new ObservableCollection<InstallerObject>()
{
new InstallerObject() { FileName = "A" },
new InstallerObject() { FileName = "D" },
})
}
});
this.DataContext = this;
}
}
This is totally cheating, though - normally the MainWindow.cs would not serve as the DataContext and have all this stuff. But for this example I just had it make a list of ProductTypes and populate each ProductType class with the InstallerObject instances.
Classes I used, note I made some assumptions and modified your class to suit this View Model better:
public class InstallerObject
{
public string FileName { get; set; }
public string FileExtension { get; set; }
public string Path { get; set; }
public int Build { get; set; }
public bool Configurable { get; set; }
public int AverageInstallTime { get; set; }
public bool IsSelected { get; set; }
}
public class ProductType
{
public string Description { get; set; }
public ReadOnlyObservableCollection<InstallerObject> AvailableInstallerObjects
{
get;
set;
}
public override string ToString()
{
return this.Description;
}
}
So, in MVVM, it seems to me that your current InstallerObject class is more of a Model layer sort of thing. You might consider transforming it in your ViewModel to a set of collection classes that are easier to manage in your View. The idea in the ViewModel is to model things similarly to how they are going to be viewed and interracted with. Transform your flat list of InstallerObjects to a new collection of hierarchical data for easier binding to the View.
More info on various ways to use and customize your TreeView: http://www.codeproject.com/Articles/124644/Basic-Understanding-of-Tree-View-in-WPF

WPF TreeView does not apply DataTemplate accordingly

I have a business object project, which contains composite structure:
public class Tree
{ public IProductComponent TreeRoot { get; set; } }
public interface ITreeComponent
{ public string Name { get; set; } }
public class ContainerComponent : ITreeComponent
{ public BindingList<ITreeComponent> Children { get; set; } }
public class LeafComponent : ITreeComponent
{ }
I need to bind this structure to a TreeView in my WPF project. The tree view first:
<TreeView x:Name="treeView" Grid.ColumnSpan="2">
<TreeView.Resources>
<HierarchicalDataTemplate
ItemsSource="{Binding Children}"
DataType="{x:Type businessObjects:ContainerComponent}">
<Label Content="{Binding Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type businessObjects:LeafComponent}">
<Label Content="{Binding Name}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
And the code for binding:
bTreeView = new Binding();
bTreeView.Source = MyTree;
bTreeView.Path = new PropertyPath("TreeRoot.Children");
treeView.SetBinding(TreeView.ItemsSourceProperty, bTreeView);
The problem is that the TReeView does not actually use those templates (it displays only the top level of hierarchy and calls .ToString() to display those items. Please tell me where have I gone wrong. Otherwise, if I set the it is working, but I cannot define two templates there.
Thanks.
Well I notice you are putting the template in resources, not under TreeVeiw.ItemTemplate.
TreeView should have an ItemTemplate (the Hierarchical) and the ItemsSource set. Shouldn't need anything more than that.
Would help with example data for us to test though.
My bad - the Main assembly was loading the dll with entities two times instead of one. That caused it to go crazy - as soon as I fixed it and the assembly loaded once the problems went away.

Categories

Resources