Are recursive DataTemplates possible? - wpf

I have a class something like:
public class Section
{
private IEnumerable<Section> sections;
private IEnumerable<KeyValuePair<string, string>> attributes
public string Name {get;set;}
public IEnumerable<Section> Sections
{
get {return sections;}
}
public IEnumerable<KeyValuePair<string, string>> Attributes
{
get {return attributes;}
}
public Section(...)
{
//constructor logic
}
}
Which is contained in another class, lets call it OtherClass for sake of argument, that wraps around it and is used in WPF in an ObjectDataProvider.
As you can see, an instance of Section can contain many other instances of Section.
Is there a way in XAML to create a template that deals with this recursion?
This is what I've got so far:
<UserControl x:Class="OtherClassEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:OtherClassNS="clr-namespace:NameSpace"
Height="300" Width="300">
<UserControl.Resources>
<ObjectDataProvider ObjectType="{x:Type OtherClassNS:OtherClass}" x:Key="ViewModel" />
<DataTemplate x:Key="listViewAttr">
<WrapPanel>
<TextBlock Text="{Binding Path=Key}"></TextBlock><TextBlock Margin="0,0,4,0">: </TextBlock>
<TextBlock Text="{Binding Path=Value}"></TextBlock>
</WrapPanel>
</DataTemplate>
<DataTemplate x:Key="listViewSection">
<StackPanel>
<WrapPanel Margin="0,0,0,8">
<TextBlock Margin="0,0,4,0">Section:</TextBlock>
<TextBlock Text="{Binding Path=Name}"/>
</WrapPanel>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="listViewData">
<StackPanel>
<WrapPanel Margin="0,0,0,8">
<TextBlock Margin="0,0,4,0">Section: </TextBlock>
<TextBlock Text="{Binding Path=Name}"/>
<ListView DataContext="{Binding Path=Sections}" ItemTemplate="{StaticResource listViewSection}" ItemsSource="{Binding Sections}">
</ListView>
<ListView DataContext="{Binding Path=Attributes}" ItemsSource="{Binding Attributes}" ItemTemplate="{StaticResource listViewAttr}">
</ListView>
</WrapPanel>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListView DataContext="{StaticResource ViewModel}" ItemTemplate="{StaticResource listViewData}" ItemsSource="{Binding Sections}">
</ListView>
</Grid>
</UserControl>
But I can't get recursion, as a DataTemplate can only reference one that is declared before it.
Can this be done in XAML? If so, how? Is this something that I'll have to do in code behind?
Are DataTemplates even the way to go? Is there a better way to display and edit this data?

In case anyone needs to see how to do this without using an HierarchicalDataTemplate:
<UserControl x:Class="OtherClassEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:OtherClassNS="clr-namespace:NameSpace"
Height="300" Width="300">
<UserControl.Resources>
<ObjectDataProvider ObjectType="{x:Type OtherClassNS:OtherClass}" x:Key="ViewModel" />
<DataTemplate x:Key="listViewAttr">
<WrapPanel>
<TextBlock Text="{Binding Path=Key}"></TextBlock>
<TextBlock Margin="0,0,4,0">: </TextBlock>
<TextBlock Text="{Binding Path=Value}"></TextBlock>
</WrapPanel>
</DataTemplate>
<DataTemplate x:Key="listViewData">
<StackPanel>
<WrapPanel Margin="0,0,0,8">
<TextBlock Margin="0,0,4,0">Section: </TextBlock>
<TextBlock Text="{Binding Path=Name}"/>
</WrapPanel>
<ListView ItemTemplate="{DynamicResource listViewData}" ItemsSource="{Binding Sections}" />
<ListView ItemsSource="{Binding Attributes}" ItemTemplate="{StaticResource listViewAttr}" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListView DataContext="{StaticResource ViewModel}" ItemTemplate="{DynamicResource listViewData}" ItemsSource="{Binding Sections}">
</ListView>
</Grid>
</UserControl>
This gets me basically what I want.
The main change is as Dan Bryant suggested: changing the ItemTemplate to use a Dynamic rather than static resource for the recursive object (Section).

Perhaps I'm misunderstanding your scenario, but is HierarchicalDataTemplate what you're looking for?

Related

Reference converter in another xaml

I got a datatemplate in Resource.xaml:
<DataTemplate x:Key="SplitViewMenuItemWithCount">
<RelativePanel>
<RelativePanel RelativePanel.AlignVerticalCenterWithPanel="True" Margin="0,10,0,10">
<SymbolIcon Foreground="White" Width="25" RelativePanel.AlignVerticalCenterWithPanel="True" Symbol="{Binding Symbol}" x:Name="SymbolIcon" Margin="10,0,0,0"/>
<StackPanel Visibility="{Binding Count, Converter={StaticResource BooleanToVisibilityConverter}, TargetNullValue=Collapsed, FallbackValue=Collapsed}" x:Name="StackPanelCount" RelativePanel.RightOf="SymbolIcon" Margin="0,20,0,0" CornerRadius="9" Background="White" Width="15" Height="15">
<TextBlock Text="{Binding Count, FallbackValue='0'}" TextAlignment="Center" FontSize="10" Foreground="{StaticResource AppDarkBlueColor}"/>
</StackPanel>
<TextBlock Margin="10,0,0,0" Foreground="White" x:Name="TextBlockMenuItemText" RelativePanel.RightOf="StackPanelCount" Text="{Binding Text}" FontSize="16" Padding="5,3,0,5"/>
</RelativePanel>
</RelativePanel>
</DataTemplate>
I'm using the datatemplate in my MainPage.xaml as a datatemplate for a ListView, but it crashes because my BooleanToVisibilityConverter. It can't find the converter in Resource.xaml.
However if I put the datatemplate in my MainPage.Resources it finds the converter (because it's defined there).
Is there any way to keep the datatemplate in Resource.xaml? I rather have it there than in MainPage.Resources.
You have to add a reference from your mainPage.xaml to your resources.xaml by using ResourceDictionnary tag as following:
<Window x:Class="MainPage" ... ... >
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/YourProjectDLL;component/Resource.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
... Your window body
</Grid>
</Window>

DataType on TreeView

I'm trying to make some data binding on a TreeView in a WPF solution. However, I can't seem to figure it out.
my XAML is as follows:
<Window x:Class="NoteNest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Note Nest" Height="512" Width="756">
<DockPanel LastChildFill="True">
<StackPanel Width="190">
<TreeView HorizontalAlignment="Left" VerticalAlignment="Top" BorderThickness="0" SelectedItemChanged="NoteSelect" DockPanel.Dock="Left" Name="Nest">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type Note}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</StackPanel>
</DockPanel>
</Window>
I keep getting the error:
Note is not supported in a Windows Presentation Foundation (WPF) project.
For the record. I have a public class named Note in the same namespace, so ether I have completely misunderstood how DataType works, or I'm just missing something.
What do I do?

ListView with ScrollViewer but no MouseWheelEvents

I have the following List:
These are ListView with Expanders as ListViewItems. The ListView itself is in a ScrollViewer to make the right scrolling behaviour.
My Problem is that the MouseWheel is not working on this List.
Here is the XAML:
<Window x:Class="ResourceListExpanderStyle.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converter="clr-namespace:ResourceListExpanderStyle.Converter"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<converter:DeviceTypeGroupToDeviceListConverter x:Key="DeviceTypeGroupToDeviceListConverter" />
<converter:IntegerToBrushConverter x:Key="IntegerToBrushConverter" />
</Window.Resources>
<DockPanel>
<ScrollViewer x:Name="ScrollViewer">
<ListView x:Name="OuterListView" ItemsSource="{Binding DeviceTypeGroupListByStation}" HorizontalContentAlignment="Stretch">
<ListView.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Path=NAME}" Background="{Binding Path=BACKCOLOR, Converter={StaticResource IntegerToBrushConverter}}">
<ListView x:Name="InnerListView" ItemsSource="{Binding Converter={StaticResource DeviceTypeGroupToDeviceListConverter}}" HorizontalContentAlignment="Stretch">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=NAME}" Background="{Binding Path=BACKCOLOR, Converter={StaticResource IntegerToBrushConverter}}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Expander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
</DockPanel>
Any Idea?

WPF: Databinding and Combo Boxes

I have two classes
Company
CompanyKey
CompanyName
Person
FirstName
LastName
CompanyKey
The list items on the combo box is bound to a collection of CompanyObjects.
How to I databind the selected item property of the Combobox to the Person.CompanyKey property?
If I've understood your question correctly, here is a demo app that explains databinding of combo box: Demo App
Hope this helps.
Regards,
Mihir Gokani
EDIT: Fragment from code sample
<Window
x:Class="WpfApplication.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1"
Height="300"
Width="300">
<StackPanel>
<TextBlock
Margin="10">Persons</TextBlock>
<ComboBox
x:Name="comboPersons"
Height="25"
Margin="10"
ItemsSource="{Binding Persons}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel
Orientation="Horizontal">
<TextBlock
Text="{Binding FirstName}"
Margin="0,0,5,0" />
<TextBlock
Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock
Margin="10">Companies</TextBlock>
<ComboBox
x:Name="comboCompanies"
Height="25"
Margin="10"
ItemsSource="{Binding Companies}"
DisplayMemberPath="CompanyName"
SelectedValuePath="CompanyKey"
SelectedValue="{Binding SelectedItem.CompanyKey, ElementName=comboPersons}" />
</StackPanel>
</Window>
The solution is simple, you need to use an IValueConverter to convert to Company to the Person object.
For more information on IValueConverter, please see:
http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter.aspx
You can then bind it in your xaml with something like:
{Binding Path=combox.SelectedItem, Converter={StaticResource CompanyToPersonConvertor}}

Does any one know of an exhaustive collection of WPF databinding examples?

It seems that every time I read an article on "how to do WPF data binding", it is done with some different variation, sometimes with DataContext, sometimes without, sometimes with Itemssource or both Itemssource and DataContext, there's also ObjectDataProvider, and you can have any of these in XAML or codebehind, or no codebehind and bind directly from XAML to your ViewModels.
It seems like there are dozens of different syntaxes to use within the XAML itself, e.g.:
<ListBox ItemsSource="{Binding Source={StaticResource Customers}}">
and
<ListBox DataContext="{StaticResource Customers}" ItemsSource="{Binding}">
These two code samples, for example, do the same thing:
1. Using ObjectDataProvider with no code-behind:
<Window x:Class="TestDataTemplate124.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestDataTemplate124"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<ObjectDataProvider x:Key="Customers"
ObjectType="{x:Type local:Customer}"
MethodName="GetAllCustomers"/>
</Window.Resources>
<StackPanel>
<ListBox DataContext="{StaticResource Customers}" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text=" "/>
<TextBlock Text="{Binding LastName}"/>
<TextBlock Text=" ("/>
<TextBlock Text="{Binding Age}"/>
<TextBlock Text=")"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
2. Example with no DataContext:
<Window x:Class="TestDataTemplate123.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestDataTemplate123"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ListBox x:Name="ListBox1">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text=" "/>
<TextBlock Text="{Binding LastName}"/>
<TextBlock Text=" ("/>
<TextBlock Text="{Binding Age}"/>
<TextBlock Text=")"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
using System.Collections.ObjectModel;
using System.Windows;
namespace TestDataTemplate123
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
ListBox1.ItemsSource = Customer.GetAllCustomers();
}
}
}
Does anyone know of a source that explains WPF Databinding by instead of just saying "here's how you do databinding" and then explain one particular way, but instead attempt to explain the various ways to go about databinding and show perhaps what the advantages and disadvantages of e.g. having DataContext or not, binding in XAML or code-behind, etc.?
Check out this cheatsheet
I can recommend you the blog from Bea Stollnitz. If I'm not mistaken, she works at Microsoft and is involved in the development on WPF, particularly in databinding. She really has some great WPF tutorials, many on databinding. You should find some really good info here.

Resources