Data-based template selection in WPF - wpf

I have this simple XAML example:
<Window x:Class="DynTemplateTest.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">
<Window.Resources>
<DataTemplate x:Key="ItemTemplate">
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Width="30" Height="30" Fill="Red"></Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Position}"></Setter>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</DataTemplate>
</Window.Resources>
<DockPanel LastChildFill="True">
<ContentPresenter
Content="{Binding Path=Items}"
ContentTemplate="{StaticResource ItemTemplate}"
>
</ContentPresenter>
</DockPanel>
</Window>
It is rendering my items in the observable collection in MVVM style. Each item is having its horizontal position in a property. Each item also has a property IsSpecial which tells if it wants to be rendered in some special way. I want ordinary items (IsSpecial=false) render as red squares (already in the code) and special items as blue circles with "special" text inside.
What I do not know is how to adjust the XAML code to do the template selection for the items. Is there a way to do that without coding my own ItemTemplateSelector? Will it still work with the canvas positioning based on binding. I think that the solution is to extract the item template to a separate template, create one more template for special items and then somehow play with triggers ... but it is not very easy for me as I am a WPF beginner at the moment.
The other thing is that I quite dislike the way how the Position is passed to the items. Is there some other way?
Are there any other recommendations how to improve the code?

I solved it myself :D
<Window x:Class="DynTemplateTest.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">
<Window.Resources>
<DataTemplate x:Key="NormalItem">
<Rectangle Width="30" Height="30" Fill="Red"></Rectangle>
</DataTemplate>
<DataTemplate x:Key="SpecialItem">
<Rectangle Width="30" Height="30" Fill="Red"></Rectangle>
</DataTemplate>
<DataTemplate x:Key="ItemTemplate">
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource NormalItem}" x:Name="ItemsContentControl" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsSpecial}" Value="true">
<Setter TargetName="ItemsContentControl" Property="ContentTemplate" Value="{StaticResource SpecialItem}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Position}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</DataTemplate>
</Window.Resources>
<DockPanel LastChildFill="True">
<ContentPresenter
Content="{Binding Path=Items}"
ContentTemplate="{StaticResource ItemTemplate}"
>
</ContentPresenter>
</DockPanel>
</Window>
But still, any thoughts on alternatives or improvements?

Related

Changing mouseover effect on Mahapps Tile

So recently I've started using MahApps.Metro for an application.
It's been going great, but one problem I cannot solve is MouseOver effect on a Tile.
I have a Grid, in which there's an Expander that hosts all the Tiles each of which represent a connection to the specific database. They are bound to an ObservableCollection which I populate from another database.
<Grid>
<Expander Margin="5" Header="Server Connections">
<ListBox ItemsSource="{Binding OmsConnections}" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<controls:Tile
Title="{Binding Name}"
controls:ControlsHelper.MouseOverBorderBrush="{DynamicResource BlackBrush}"
Background="{DynamicResource GrayBrush2}"
Command="{Binding DataContext.TileClickCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}"
HorizontalTitleAlignment="Left"
Style="{StaticResource LargeTileStyle}"
TiltFactor="2">
<Image
Width="60"
Height="60"
Source="{Binding OmsConnectionTypeId, Converter={StaticResource ConnectionTypeToIconConverter}}" />
</controls:Tile>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Expander>
</Grid>
This is the style applied via Style
<Style x:Key="LargeTileStyle" TargetType="controls:Tile">
<Setter Property="Height" Value="125" />
<Setter Property="TitleFontSize" Value="14" />
<Setter Property="TextOptions.TextFormattingMode" Value="Display" />
<Setter Property="TextOptions.TextRenderingMode" Value="ClearType" />
<Setter Property="Width" Value="210" />
</Style>
So Whenever I mouseover an item I get the black border as specified, and this Orange Background Color (Which, if I'm not mistaken, is AccentColorBrush3) and I have no idea how to change it.
Here's the Image, since my rep is low and i cannot embed it.
Also, I'm really, really bad with Templates and Styles, so this is pretty much what i scrapped from the internet. ANY Feedback would be much appreciated for both the way I Bound to a collection and how to change the MouseOver color.
You could "override" the AccentColorBrush3 resource by adding a SolidColorBrush resource to the ListBox:
<ListBox ItemsSource="{Binding OmsConnections}" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<!-- Specify the highlight brush here: -->
<ListBox.Resources>
<SolidColorBrush x:Key="AccentColorBrush3" Color="Yellow" />
</ListBox.Resources>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<controls:Tile
Title="{Binding Name}"
controls:ControlsHelper.MouseOverBorderBrush="{DynamicResource BlackBrush}"
Background="{DynamicResource GrayBrush2}"
Command="{Binding DataContext.TileClickCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}"
HorizontalTitleAlignment="Left"
Style="{StaticResource LargeTileStyle}"
TiltFactor="2">
<Image Width="60"
Height="60"
Source="{Binding OmsConnectionTypeId, Converter={StaticResource ConnectionTypeToIconConverter}}" />
</controls:Tile>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

How can i set different names to DataTemplate elements?

i'm making something like TicTacToeGame, trying to use MVVM and at this point i faced a problem. I can't understand how can i (if it's even possible), set different names to different DataTempalte elements("Buttons" to be exact).
<Window x:Class="TicTacToeCommand.MainWindow"
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:local="clr-namespace:TicTacToeCommand"
mc:Ignorable="d"
Title="MainWindow" Height="500" Width="400"
Background="White"
Name="mainW">
<Window.Resources>
<Style x:Key="ButtonsStyle" TargetType="Button">
<Setter Property="Command" Value="{Binding ElementName=mainW, Path=DataContext.GetButtonPressCommand}"></Setter>
<Setter Property="Margin" Value="5"></Setter>
<Setter Property="Background" Value="Black"></Setter>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="47*"/>
<RowDefinition Height="20*"/>
</Grid.RowDefinitions>
<ItemsControl Grid.Row="0" ItemsSource="{Binding ButtonsList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Content}"
Name="b1"
Style="{StaticResource ButtonsStyle}"
CommandParameter="{Binding ElementName=b1, Path=Name}">
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="3" Columns="3" Name="uniformGrid1">
</UniformGrid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
That's my XAML code.
And also, i'm trying to send the name as a Command Parameter in a Command, but im allways getting "b1" name, because they all getting it and i need them all to have different name...
If it is possible how after this I send these names to command?
I will be very grateful for the help, and please excuse me in advance for possible mistakes.
element name, especially in template, doesn't help a lot
<Button Content="{Binding Content}"
Name="b1"
Style="{StaticResource ButtonsStyle}"
CommandParameter="{Binding ElementName=b1, Path=Name}">
button is bound to some item here. why not send that item as CommandParameter?
<Button Content="{Binding Content}"
Style="{StaticResource ButtonsStyle}"
CommandParameter="{Binding Path=.}">
then in view model you can cast to item type and get acces to all its properties (Content, etc)

Need multiple styles dependent on Listboxitem

i have a Listbox, which stores two different object types, based on the same baseclass. (e.g. BaseObject = baseclass and the children of it: CustomPath and CustomImage)
The Datasource:
ObservableCollection<BattlegroundBaseObject> _baseObjectCollection;
public ObservableCollection<BattlegroundBaseObject> BaseObjectCollection
{
get { return _baseObjectCollection?? (_baseObjectCollection= new ObservableCollection<BaseObject>()); }
}
The Listbox databinding: <ListBox ItemsSource="{Binding BaseObjectCollection}"
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" x:Name="ListBoxPathLineStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem" x:Name="BattlegroundObjectControlTemplate">
<Path Stroke="{Binding ObjectColor}" StrokeThickness="{Binding StrokeThickness}" Data="{Binding PathGeometryData}" x:Name="PathLine" Opacity="{Binding Opacity}">
</Path>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Effect" TargetName="PathLine">
<Setter.Value>
<DropShadowEffect Color="CornflowerBlue" ShadowDepth="3" BlurRadius="10" />
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
I want to add to the ControlTemplate where the Path is, also a Image and to differ it by type or a property. doesnt matter.
anyone any ideas?
You can add to ListBox resources DataTemplate for each type.
In my example classes Car and Motorbike derived from Vehicle class.
<ListBox x:Name="listBox">
<ListBox.Resources>
<DataTemplate DataType="{x:Type local:Car}">
<StackPanel Background="Red">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Motorbike}">
<StackPanel Background="Orange">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.Resources>
</ListBox>
EDIT:
You can add style for ListBoxItem to resources:
<ListBox x:Name="listBox">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect Color="CornflowerBlue" ShadowDepth="3" BlurRadius="10" />
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
<DataTemplate DataType="{x:Type local:Car}">
<StackPanel Background="Red">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Motorbike}">
<StackPanel Background="Orange">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.Resources>
</ListBox>
You could define some DataTemplates for your different classes. They determine how the classes are displayed. I've been using them to display derived classes differently when working with a collection of the base class.
<DataTemplate DataType="{x:Type CustomPath}">
<TextBlock Text="This is a CustomPath"/>
</DataTemplate>
<DataTemplate DataType="{x:Type CustomImage}">
<TextBlock Text="This is a CustomImage"/>
</DataTemplate>
At the moment you are changing the style of the controls that WPF is using to render your bound data. A better way to do this is to provide WPF with a way of generating the correct controls. Ignore the ListBoxItem and use DataTemplates for your actual objects.
First you need to tell the Window or control how to find your types.
<Window or UserControl
...
xmlns:model="clr-namespace:yourNamespace"
>
Then you can provide WPF with a way to show your objects e.g.
<DataTemplate TargetType="{x:Type model:CustomPath}">
<Path Stroke="{Binding ObjectColor}" StrokeThickness="{Binding StrokeThickness}"
Data="{Binding PathGeometryData}" x:Name="PathLine" Opacity="{Binding Opacity}"/>
<!-- maybe use a binding from the Path.Effect back to the IsSelected and ValueConverters
to re-apply the selection effect-->
</DataTemplate>
<DataTemplate TargetType="{x:Type model:CustomImage}">
<Image Src="{Binding SomeProperty}" />
</DataTemplate>
Now all you need to do is to make these available to the ListBox in some way. Almost every element in WPF can have .Resources added to it, so you could choose to do these across the entire window
<Window ...>
<Window.Resources>
<DataTemplate .../>
<DataTemplate .../>
</Window.Resources>
...
<ListBox .../>
</Window>
or you can apply it more locally
<Window ...>
...
<ListBox>
<ListBox.Resources>
<DataTemplate .../>
<DataTemplate .../>
</ListBox.Resources>
</ListBox>
</Window>
And this way your listbox definition can become much neater too, e.g. if you are using Window.Resources
<ListBox ItemsSource="{Binding BaseObjectCollection}"/>

Using the Treeview HierarchicalDataTemplate.Triggers to change folder icon

I'm pretty fresh to WPF and this is the closest I have come to achieving what I set out to do after reviewing many of the previously asked questions posted here.
The XAML code:
<TreeView x:Name="folderView" Grid.Column="0" Grid.Row="1" BorderThickness="0">
<TreeViewItem Header="Folders" ItemsSource="{Binding SubFolders, Source={StaticResource RootFolderDataProvider}}" Margin="5"/>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type my:FolderView}" ItemsSource="{Binding SubFolders}">
<StackPanel Orientation="Horizontal" Name="myPanel">
<Image x:Name="img" Width="16" Height="16" Source="Images/FolderClosed.png" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding IsExpanded}" Value="True">
<Setter TargetName="img" Property="Source" Value="Images/FolderOpen.png"/>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
This displays the FolderClosed image on all my subfolders except the very top root folder.
The HierachicalDataTemplate trigger also fails to fire when expanded.
Any help would be appreciated.
If you are binding to the TreeViewItem IsExpanded property, then you will have to update your binding like:
<DataTrigger Binding="{Binding IsExpanded, RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}}" Value="True">
<Setter TargetName="img" Property="Source" Value="Images/FolderOpen.png"/>
</DataTrigger>
I cannot pin point the issues. But as a first step you should check if the binding is working or not.
you can add debugging for binding as mentioned in here
eg :
<Window
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
xmlns:local="clr-namespace:DebugDataBindings"
x:Class="DebugDataBindings.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="layoutRoot">
<Grid.Resources>
<local:DebugConverter x:Key="debugConverter" />
</Grid.Resources>
<TextBox
Text="{Binding Path=Customer.FirstName, diag:PresentationTraceSources.TraceLevel=High}"
Height="23" HorizontalAlignment="Left" Margin="90,18,0,0" VerticalAlignment="Top" Width="120"/>
</Grid>
</Window>

How to make itemtemplate aware of its containing template?

I want this Ellipse to get its coordinates from its corresponding BallViewModel, and to use them to determine its location inside a canvas.
The list of balls is bound to List<BallVM> in the mainviewmodel and thus I chose an itemsControl which has a canvas panel.
Is this approach correct?
If I try to bind to X and Y inside an itemcontainerstyle, then it's not specific to a certain ball.
No matter what I set in the Canvas.bottom or canvas.left properties the ellipse is always at the top left.
<Grid>
<ItemsControl ItemsSource="{Binding Balls}" Background="red">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas MouseMove="Canvas_MouseMove" Background="Blue"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type VM:BallVM}">
<Ellipse Canvas.Bottom="{Binding Y}" Canvas.Left="{Binding X}" Width="100" Height="100" Fill="Red"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
When you use an ItemTemplate with an ItemControls it does not directly put your Elippses on the Canvas but wraps them into an ContentPresenter. So you have to apply your canvas.Bottom/Left properties on the ItemsPresenter. You can can do this with the ItemContainerStyle:
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Bottom" Value="{Binding Y}" />
<Setter Property="Canvas.Left" Value="{Binding X}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type VM:BallVM}">
<Ellipse Width="100" Height="100" Fill="Red"/>
</DataTemplate>
</ItemsControl.ItemTemplate>

Resources