Is it possible to have a DataTemplate around another DataTemplate? - wpf

Hello everyone and welcome to yet another nested DataTemplate question!
In this one, I'd like to have a DataTemplate like this, written on a ResourceDictionary:
<DataTemplate x:Key="Vector3Template">
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Horizontal">
<xctk:DoubleUpDown Tag="X" Style="{StaticResource DoubleUpDownStyle}" Value="{Binding X}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<xctk:DoubleUpDown Tag="Y" Style="{StaticResource DoubleUpDownStyle}" Value="{Binding Y}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<xctk:DoubleUpDown Tag="Z" Style="{StaticResource DoubleUpDownStyle}" Value="{Binding Z}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
Being surrounded by a DataTemplate with a border, like the following, also written on a ResourceDictionary (in the future it's gonna have a couple more elements to it):
<DataTemplate x:Key="ComponentTemplate">
<Border Margin="5" BorderThickness="2" BorderBrush="Gray"/>
</DataTemplate>
Why would I want this, you ask? Well, I'm trying to display an ObservableCollection of IComponent named _components and I want all instances to share the same Borders, but with its core being specific to every class type that inherits from IComponent.
In order to display the list with its differents type, I'm using the following code on a UserControl:
<Grid x:Name="LayoutRoot" Background="White">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<StackPanel>
<ListView x:Name="_componentsList"
ItemsSource="{Binding Components}"
HorizontalContentAlignment="Stretch">
<ListView.Resources>
<DataTemplate DataType="{x:Type models:Transform}">
<ContentControl Content="{StaticResource ComponentTemplate}" ContentTemplate="{StaticResource TransformTemplate}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type models:Vector3}">
<ContentPresenter ContentTemplate="{StaticResource Vector3Template}"/>
</DataTemplate>
</ListView.Resources>
</ListView>
</StackPanel>
</ScrollViewer>
Trying to build this system with Prism 6.3 and with almost no code-behind, every c# code that I have is just for models, so no real logic here so far.
Is this possible? How so? I've started playing with WPF a few days ago and still have a lot to learn.

I believe what you're looking for is to simply use a DataTemplateSelector in which the DataTemplate used is determined by the data. You can find a full tutorial here. Once you've setup your DataTemplateSelector you simply would pass it in as the DataTemplate to your control.
public class PropertyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DefaultnDataTemplate { get; set; }
public DataTemplate BooleanDataTemplate { get; set; }
public DataTemplate EnumDataTemplate { get; set; }
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
DependencyPropertyInfo dpi = item as DependencyPropertyInfo;
if (dpi.PropertyType == typeof(bool))
{
return BooleanDataTemplate;
}
if (dpi.PropertyType.IsEnum)
{
return EnumDataTemplate;
}
return DefaultnDataTemplate;
}
}

Related

Custom Control for a ComboBox with Default Element

What I want:
I'm trying to create a ComboBox which has a 'Default' element seperated from the rest of the items.
What I have:
So by following the countless threads about grouping the items and overwritting the ItemTemplate I came to this result:
.CS
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ObservableCollection<GroupItem>()
{
new GroupItem() { Name = "Default Item", GroupName = "Default"},
new GroupItem() { Name = "Item 1", GroupName = "Item"},
new GroupItem() { Name = "Item 2", GroupName = "Item"},
new GroupItem() { Name = "Item 3", GroupName = "Item"}
};
}
public class GroupItem
{
public string Name { get; set; }
public string GroupName { get; set; }
}
}
}
XAML
<Window.Resources>
<CollectionViewSource Source="{Binding}" x:Key="GroupedDataCollView">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="GroupName" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<DataTemplate x:Key="GroupHeaderTemplate">
<TextBlock Text="{Binding GroupName}" Margin="10,0,0,0" Foreground="#989791" />
</DataTemplate>
<!--Here, we tell that the URL's description should be displayed as item text.-->
<DataTemplate x:Key="NameTemplate">
<TextBlock Text="{Binding Name}" Margin="10,0,0,0"/>
</DataTemplate>
<Style x:Key="GroupStyleContainerStyle" TargetType="{x:Type GroupItem}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel>
<Separator />
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Window>
<!-- ... -->
<ComboBox Grid.Row="1" Grid.Column="1"
ItemsSource="{Binding Source={StaticResource GroupedDataCollView}}"
Width="200" Margin="10">
<ComboBox.GroupStyle>
<GroupStyle
ContainerStyle="{StaticResource GroupStyleContainerStyle}"
HeaderTemplate="{StaticResource GroupHeaderTemplate}" />
</ComboBox.GroupStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!-- ... -->
</Window>
The Problem: Since we use Dictionaries for styling and templates which lie in another project, I can't use this 'solution' because this would require the style to know what kind of items he has to expect (Destroys the isolation of the custom styles and templates). Furthermore it's not really a group that I want to create rather a 'special' item, which is seperated by the others visually.
So I tried to create a custom UserControl to solve the problem so I could use it like this:
<ComboBox ItemsSource="{Binding Items}" DefaultItem="{Binding DefaultItem}" SelectedItem="{Binding SelectedItem}" />
In this CustomControl I would create the necessary grouping and handling hidden from the user. But how can I do this? And how can I overwrite the whole selection behaviour since I don't want to return a Key-Value-Pair (GroupItem) instead of the item the user expects.
So in short: How can I create a CustomControl which provides the necessary functionality shown at the start combined with the simplicity shown above?

How to put an Expander inside Datatemplate?

Strange one.
I have a contentcontrol on a WPF form, this loads a datatemplate within it.
This shows up fine (handwritten summary code so ignore errors/lack of attributes):
<DataTemplate>
<Label Content="Found datatemplate" />
</DataTemplate>
This however renders blank
<DataTemplate>
<Expander Header="Why dont I show">
<Label Content="Found datatemplate" />
</Expander>
</DataTemplate>
I have set the expander to visibile, isexpanded to true etc and no matter what it doesn't render at all.
Confused- is this just not possible?
I've recently done something similar to what you're describing and it worked for me. I have an ItemsControl that binds to a collection of view models, each of which contains a UserControl representing custom content. I implemented the ItemsControl.ItemTemplate to display the custom control inside an Expander like this:
<ItemsControl Margin="0,20,0,0" ItemsSource="{Binding ControlItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="0,0,0,0"
BorderBrush="#E7E7E7"
BorderThickness="0,1,0,0"
Padding="20,0">
<Expander Foreground="#E7E7E7"
IsExpanded="{Binding Path=IsExpanded,
Mode=TwoWay}">
<Expander.Header>
<Grid>
<TextBlock HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="24"
Text="{Binding Title}" />
</Grid>
</Expander.Header>
<DockPanel>
<ScrollViewer MinHeight="250">
<ContentControl Content="{Binding Control}" />
</ScrollViewer>
</DockPanel>
</Expander>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This is what my view model looks like:
public class SidePanelControlItem : ModelBase
{
private bool _isExpanded;
public SidePanelControlItem(UserControl control)
{
if (control == null) { throw new ArgumentNullException("control");}
Control = control;
}
public string Title { get; set; }
public UserControl Control { get; private set; }
public bool IsExpanded
{
get { return _isExpanded; }
set
{
_isExpanded = value;
OnPropertyChanged("IsExpanded");
}
}
}

How to make custom control with ListBox?

I want to make horizontal ListBox with customized item template, so I make a basic template of it.
However, I couldn't find an example of binding 'things' to that WPF XAML, especially with ListBox filled with customized items.
I simply want to dynamically add/remove items in the ListBox with Image, Label, ComboBox with previously filled with number 1 to 10.
the ADD/REMOVE button will be placed outside WPF control, it means that the buttons will be on the Main Window Form.
Also, there are TextBox and picture selector in the Main Window Form so that I can change the text and image.
Below is code behind XAML :
Public Class listSequence
Public Sub New()
InitializeComponent()
listbox.Items.Add("hi")
listbox.Items.Add("there")
End Sub
End Class
Below is XAML :
<ListBox Name="listbox" VerticalContentAlignment="Stretch" ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Stretch" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border Padding="10" Margin="5" BorderThickness="1" BorderBrush="Aqua" CornerRadius="0" Width="120" VerticalAlignment="Stretch">
<StackPanel>
<Image />
<Label Content="{Binding}" />
<TextBlock Text="hi" />
<ComboBox x:Name="cboRepeat" ItemsSource="{Binding}" DisplayMemberPath="RepeatCounter" IsSynchronizedWithCurrentItem="True"/>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Let's say we have a basic class named Item:
public class Item : INotifyPropertyChanged
{
public string Text { get; set; } // Implement INotifyPropertyChanged
public string ImagePath { get; set; } // properly on these properties
}
And a collection of these in a view model:
public ObservableCollection<Item> Items { get; set; }
Now to display these items in the UI, we use a ListBox and set the ItemsSource property:
<ListBox ItemsSource="{Binding Items}">
</ListBox>
When it comes to defining the ListBox.ItemTemplate, you need to understand that this DataTemplate will be applied to each item and that it has access to all of the properties defined in the Item class:
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image ImageSource="{Binding ImagePath}" />
<TextBlock Text="{Binding Text}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Therefore, you can access the properties in the collection class as shown above. You can find out the full story by looking at the ItemsControl.ItemTemplate Property page on MSDN.

How can I add additional item templates to an extended WPF treeview

I'm trying to set up a Treeview descendent class that can be used as a common template for
all Treeview instances in my application, but with additional formatting and templates for each instance.
For the base, I have a UserControl that descends from Treeview, with the common styles and a single standard data template
<TreeView x:Class="BaseTreeView" ... >
<TreeView.ItemContainerStyle> ... </TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" DataType="{x:Type local:BaseTreeViewItem}">
<TextBlock Text="{Binding Caption}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Then in each window, I use this extended Treeview and add additional data templates for the specific TreeviewItems I'm displaying.
e.g.
<Window x:Class="Window1" ... >
...
<BaseTreeView ItemsSource="{Binding RootTreeItems}" >
<MyTreeView.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" DataType="{x:Type ExtendedTreeViewItem1}">
<StackPanel Orientation="Horizontal">
<Image Source="Images/Image1.png" />
<TextBlock Text="{Binding Caption}" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type ExtendedTreeViewItem2}">
<StackPanel Orientation="Horizontal">
<Image Source="Images/Image2.png" />
<TextBlock Text="{Binding Caption}" />
</StackPanel>
</DataTemplate>
</MyTreeView.Resources>
</BaseTreeView>
...
</Window>
This compiles fine, but at runtime I get an error
"'Set property 'System.Windows.ResourceDictionary.DeferrableContent' threw an exception.' Line number '27' and line position '59'."
"Cannot re-initialize ResourceDictionary instance."
Is there any way around this, or can someone suggest a better way to set up a base treeview template and multiple descedent versions.
You could try moving your templates to the <Window.Resources> instead of <MyTreeView.Resources>
If it doesn't work, maybe using a DataTemplateSelector suits your case best. You can create a DataTemplateSelector class like this:
public class ExtendedTreeViewTemplateSelector : DataTemplateSelector
{
public DataTemplate ExtendedTreeViewItem1Template { get; set; }
public DataTemplate ExtendedTreeViewItem2Template { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is ExtendedTreeViewItem1)
return ExtendedTreeViewItem1Template;
if (item is ExtendedTreeViewItem2)
return ExtendedTreeViewItem2Template;
}
}
And then use it in your XAML like this:
<Window x:Class="Window1" ... >
<Window.Resources>
<HierarchicalDataTemplate x:Key="extendedTreeViewItem1Template" ItemsSource="{Binding Children}" DataType="{x:Type ExtendedTreeViewItem1}">
<StackPanel Orientation="Horizontal">
<Image Source="Images/Image1.png" />
<TextBlock Text="{Binding Caption}" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate x:Key="extendedTreeViewItem2Template" DataType="{x:Type ExtendedTreeViewItem2}">
<StackPanel Orientation="Horizontal">
<Image Source="Images/Image2.png" />
<TextBlock Text="{Binding Caption}" />
</StackPanel>
</DataTemplate>
<selector:ExtendedTreeViewTemplateSelector x:Key="treeViewTemplateSelector"
ExtendedTreeViewItem1Template="{StaticResource extendedTreeViewItem1Template}"
ExtendedTreeViewItem2Template="{StaticResource extendedTreeViewItem2Template}" />
</Window.Resources>
...
<BaseTreeView ItemsSource="{Binding RootTreeItems}"
ItemTemplateSelector={StaticResource treeViewTemplateSelector}" />
...
</Window>

WPF Treeview - Binding to a collection with different depths, and styling differently

Apologies for the long post -- Read a few threads about this, but still find it hard to implement. Basically, I have a collection of objects, defined as:
public class LibData
{
public string Name { get; set; }
ObservableCollection<LibObject> _list;
public ObservableCollection<LibObject> List { get { return _list; } }
public LibData(string name, LibDataType type)
{
this.Name = name;
_list = new ObservableCollection<LibObject>();
}
}
and the object:
public class LibObject
{
public string Name { get; set; }
public LibObject(string name)
{
this.Name = name;
}
}
My main problem is in the XAML, and styling this TreeView. I need to have a specific style for a "root" item, and a specific style for a "leaf". Thing is, that one item in the bound list is "Root->Leaf", and another is "Root->Child->Leaf".
I tried this:
<TreeView x:Name="myTree" ItemsSource="{x:Static local:myDataList}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=List}" >
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<CheckBox IsChecked="True" Content="HeaderCheckbox"/>
</StackPanel>
</Grid>
<HierarchicalDataTemplate.ItemTemplate >
<DataTemplate>
<Grid>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="True" Content="LeafCheckbox" />
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</Grid>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
This obviously works fine for the "Root->Leaf" item, but not for the "Root-Child-Leaf".
The XAML implementation seems to be more of a "hard coded" solution, where I know that the item layout is always "Root->Leaf" - how do I make this dynamic?
Again, I have seen solutions to different levels (including using converters), but the problem i'm having is that I need specific styles to root and leaf and nothing for levels in between.
I'm wondering if I'm looking at this completely wrong ...
Easy way to do this. Pull the DataTemplates out of the TreeView and put them in the resources section. Specify the DataType property for each DataTemplate, but do not include a key. The ItemTemplateSelector (a property of type DataTemplateSelector) on the TreeView will do the magic of using whichever DataTemplate suits the correct item type.
Create a HierarchicalDataTemplate for types Root and Child, and a DataTemplate for type Leaf.
Something like this:
<Window.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type local:Leaf}">
<Grid>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="True" Content="LeafCheckbox" />
<TextBlock Text="{Binding Path=SomeValue}"/>
</StackPanel>
</Grid>
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Child}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Child " />
<TextBlock Text="{Binding Path=SomeValue}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Root}"
ItemsSource="{Binding Children}">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Root " />
<TextBlock Text="{Binding Path=SomeValue}" />
</StackPanel>
</Grid>
</HierarchicalDataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<TreeView x:Name="myTree" ItemsSource="{Binding}" />
</Grid>

Resources