Here is what I am trying to do. I have 2 Data Templates defined which both refer to a different user control.
<UserControl.Resources>
<DataTemplate x:Key="myDataTemplate1">
<Border BorderBrush="Black" BorderThickness="1">
<myUserControl1 />
</Border>
</DataTemplate>
<DataTemplate x:Key="myDataTemplate2">
<Border BorderBrush="Black" BorderThickness="1">
<myUserControl2/>
</Border>
</DataTemplate>
</UserControl.Resources>
I am using these Data Templates to display a list of items using ItemsControl like this:
<ItemsControl x:Name="myItemList" ItemTemplate="{StaticResource myDataTemplate1}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate />
</ItemsControl.ItemsPanel>
</ItemsControl>
I would like the ItemTemplate to conditionally be either myDataTemplate1 or myDataTemplate1 depending on the value of an integer variable being 1 or 2 respectively.
Should I use DataTriggers for this or is there another way to do this? Appreciate the help.
Don't set the ItemTemplate but use an ItemTemplateSelector.
DataTriggers would be fine too of course, spares you the extra class for the selector. e.g.
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ThatProperty}" Value="1">
<Setter Property="ContentTemplate"
Value="{StaticResource myDataTemplate1}" />
</DataTrigger>
<DataTrigger Binding="{Binding ThatProperty}" Value="2">
<Setter Property="ContentTemplate"
Value="{StaticResource myDataTemplate2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
Related
Why does one label update but not the other? Both bound to the same property. I assume there's a problem with the binding being in a dataTemplate? Using Resharper I'm told that my lblOverallInt cannot resolve the symbol. How could I fix this?
<Label Name="lbl1" Content="{Binding Path=lblOverallInt, UpdateSourceTrigger=PropertyChanged}"/>
<Expander>
<Expander.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Name="lbl2" Content="{Binding Path=lblOverallInt, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</DataTemplate>
</Expander.HeaderTemplate>
</Expander>
UPDATE
Related follow on question; I have the following style binding also which works when applied to the label but not to the Expander. Is there a similar process for wiring this up as mm8 solution to the top part of this question?
Added separate solution for this part
<Expander.Style>
<Style TargetType="{x:Type Expander}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=lblOverallInt}" Value="0">
<Setter Property="Foreground" Value="Yellow"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=lblOverallInt, Converter={StaticResource isZeroConverter}}" Value="False">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Expander.Style>
The DataContext of the HeaderTemplate is the header itself. Try this:
<Expander>
<Expander.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Name="lbl2" Content="{Binding Path=DataContext.lblOverallInt, RelativeSource={RelativeSource AncestorType=Expander}}"/>
</StackPanel>
</DataTemplate>
</Expander.HeaderTemplate>
</Expander>
Or this:
<Expander Header="{Binding Path=lblOverallInt}">
<Expander.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Name="lbl2" Content="{Binding}"/>
</StackPanel>
</DataTemplate>
</Expander.HeaderTemplate>
</Expander>
For part 2 of my question I had the style binding part still attached to the Expander when it actually needed to be included in the DataTemplate;
<Expander.HeaderTemplate>
<DataTemplate>
<Border Height="24">
<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Resources>
<Style TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="0">
<Setter Property="Foreground" Value="Yellow"/>
</DataTrigger>
<DataTrigger Binding="{Binding Converter={StaticResource isZeroConverter}}" Value="False">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Style.Resources>
</Style>
</StackPanel.Style>
<Label>Errors/Warnings:</Label>
<Label Name="lbl2" Content="{Binding}"/>
</StackPanel>
</Border>
</DataTemplate>
</Expander.HeaderTemplate>
I have a ListView which I want to present different kinds of user controls, depending on which view model is set for the list view item.
In xaml:
<ListView ItemsSource="{Binding Items}">
<ListView.Resources>
<DataTemplate DataType="{x:Type viewModels:LabelledTextViewModel}">
<controls:LabelledTextBox/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:FolderChooserViewModel}">
<standardControls:FolderChooser/>
</DataTemplate>
</ListView.Resources>
</ListView>
Now this works fine, but my LabelledTextViewModel can be editable or not editable. How do I say in XAML to check the property "IsEditable" on my viewmodel, and depending on its value show LabelledTextBlockControl or LabelledTextBoxControl?
You can use a DataTrigger in your DataTemplate:
<ListView ItemsSource="{Binding Items}">
<ListView.Resources>
<DataTemplate DataType="{x:Type viewModels:LabelledTextViewModel}">
<Grid>
<controls:LabelledTextBlockControl x:Name="textBlock"/>
<controls:LabelledTextBoxControl x:Name="textBox" Visibility="Collapsed"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsEditable}" Value="True">
<Setter TargetName="textBlock" Property="Visibility" Value="Collapsed"/>
<Setter TargetName="textBox" Property="Visibility" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:FolderChooserViewModel}">
<standardControls:FolderChooser/>
</DataTemplate>
</ListView.Resources>
</ListView>
For your requirements, you either need to use the DataTemplateSelector Class to make that selection for you, or you could try to name your DataTemplates and set them using a DataTrigger:
<ListView>
<ListView.Resources>
<DataTemplate x:Key="DefaultDataTemplate" DataType="{x:Type viewModels:LabelledTextViewModel}">
<controls:LabelledTextBox/>
</DataTemplate>
<DataTemplate x:Key="AnotherDataTemplate" DataType="{x:Type viewModels:FolderChooserViewModel}">
<standardControls:FolderChooser/>
</DataTemplate>
</ListView.Resources>
<ListView.Style>
<Style TargetType="{x:Type ListView}">
<Setter Property="ItemTemplate" Value="{StaticResource DefaultDataTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsEditable}" Value="True">
<Setter Property="ItemTemplate" Value="{StaticResource AnotherDataTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>
</ListView>
This example assumes that you are setting the DataTemplates to the ItemTemplate property... if not, you'll need to change that property to the relevant one.
I have a user control with a items control and I would like it to have 2 completely different item template styles depending on the alternation index. I've seen lots of tutorials on how to change the background colour based on the index but not changing the style on each index. Here's what I have so far.
Defined templates:
<UserControl.Resources>
<DataTemplate x:Key="ItemLeft" >
<Border Background="Blue" Height="10">
<!-- Define Left Style -->
</Border>
</DataTemplate>
<DataTemplate x:Key="ItemRight">
<Border Background="Red" Height="10">
<!-- Define Right Style -->
</Border>
</DataTemplate>
</UserControl.Resources>
I've removed the data template code to make it easier to read. It's a lot more than border colours.
Items Control:
<ItemsControl Name="ItemControl" AlternationCount="2">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Style>
<Style>
<Style.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="ItemsControl.ItemTemplate" Value="{StaticResource ItemRight}"/>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="ItemsControl.ItemTemplate" Value="{StaticResource ItemLeft}"/>
</Trigger>
</Style.Triggers>
</Style>
</ItemsControl.Style>
</ItemsControl>
I'm pretty sure I shouldn't be doing this type of trigger in the style but I'm not sure how else to do it. I'm new to using WPF, I've found most of it pretty intuitive but I'm lost here. I would like to try and contain this to just XAML code.
Thanks
ItemTemplate applies to all items. What you can do is use ContentControl as ItemTemplate with custom style that chooses ContentTemplate based on ItemsControl.AlternationIndex
<ItemsControl Name="ItemControl" AlternationCount="2">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource ItemLeft}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ContentPresenter}}, Path=(ItemsControl.AlternationIndex)}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource ItemRight}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
A simpler solution would be to set the ItemContainerStyle, and use a Trigger instead of a DataTrigger for the AlternationIndex:
<ItemsControl ... AlternationCount="2">
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="ContentTemplate" Value="{StaticResource ItemLeft}"/>
<Style.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="ContentTemplate"
Value="{StaticResource ItemRight}"/>
</Trigger>
</Style.Triggers>
</Style>
</ItemsControl.ItemContainerStyle>
...
</ItemsControl>
i hope you can help me. I got following Code in the Resources:
<UserControl.Resources>
<BitmapImage x:Key="img_src_lock" UriSource="/EEBase;component/Images/Lock_24x32.png" />
<BitmapImage x:Key="img_src_unlock" UriSource="/EEBase;component/Images/Unlock_24x32.png" />
<Style TargetType="{x:Type ToggleButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsChecked}" Value="True">
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource img_src_lock}" />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsChecked}" Value="False">
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource img_src_unlock}" />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<!-- TypeComboTemplateCollapsed -->
<DataTemplate x:Key="TypeComboTemplateCollapsed">
<TextBlock
Text="{Binding Path=Text, Mode=OneWay}"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="5,0,0,5"
/>
</DataTemplate>
<!-- TypeComboTemplateExpanded -->
<DataTemplate x:Key="TypeComboTemplateExpanded">
<TextBlock
Text="{Binding Path=Text, Mode=OneWay}"
VerticalAlignment="Center"
Margin="5,0,0,5"
/>
</DataTemplate>
<!-- EditCircleTemplate -->
<DataTemplate x:Key="EditCircleTemplate">
<!-- some content here, no ToggleButton -->
</DataTemplate>
<!-- EditRectangleTemplate -->
<DataTemplate x:Key="EditRectangleTemplate">
<!-- some other content here, including the ToggleButtons -->
<ToggleButton
IsChecked="{Binding Path=BaseLocked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
Margin="5"
/>
<ToggleButton
IsChecked="{Binding Path=HeightLocked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
Margin="5"
/>
</DataTemplate>
</UserControl.Resources>
To me that looks all correct.
Now, the problem is:
When i do the following, an exceptions occurs:
Specified element is already the logical child of another element. Disconnect it first.
1. load the control, selected type is CIRC
2. change the dropdown to select RECT (template triggers and togglebuttons are shown correctly)
3. change the dropdown back to CIRC
--> now the Exception occurs.
4. if i ignore the exception, the template "EditCircleTemplate" does not get loaded, and the normal ToString of the model object gets displayed.
Additional info:
originally there are 4 different types in the WPF, two of them with ToggleButtons (that's why i use templates). I cut them out, they dont differ really. But what i found out using all 4 templates is that the error does not occur when switching to a new Template, but when unloading a template with the toggle buttons. Which is kinda strange.
Also if i remove the DataTriggers for the ToggleButtons everything works like a charm.
The Exception comes from the XAML-Interpreter, so the Stacktrace is not useful at all.
Can anyone give me a hint what i am doing wrong?
Edit:
oops, i guess i forgot the content code:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<ComboBox
Margin="5"
Grid.Row="0"
Grid.Column="0"
ItemsSource="{Binding Path=PossibleTypes, Mode=OneTime}"
SelectedItem="{Binding Path=SelectedType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<ContentControl x:Name="content" Content="{Binding}" ContentTemplate="{StaticResource TypeComboTemplateExpanded}"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ComboBoxItem}}" Value="{x:Null}">
<Setter TargetName="content" Property="ContentTemplate" Value="{StaticResource TypeComboTemplateCollapsed}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ContentControl
Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="2"
Content="{Binding Mode=OneWay}">
<ContentControl.ContentTemplate>
<DataTemplate >
<ContentControl
Name="inputContent"
Content="{Binding Mode=OneWay}"
ContentTemplate="{x:Null}"
/>
<DataTemplate.Triggers>
<DataTrigger
Binding="{Binding Path=SelectedType.Type, Mode=OneWay}"
Value="CIRC">
<Setter
TargetName="inputContent"
Property="ContentTemplate"
Value="{StaticResource EditCircleTemplate}"
/>
</DataTrigger>
<DataTrigger
Binding="{Binding Path=SelectedType.Type, Mode=OneWay}"
Value="RECT">
<Setter
TargetName="inputContent"
Property="ContentTemplate"
Value="{StaticResource EditRectangleTemplate}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
Edit2/Solution:
I just found a workaround - it just does not satisfy me:
Instead of putting the style in the UserControl.Resources, which for me would be the more clean and intuitive solution, i have to set the style with the triggers on each ToggleButton separately.
So removing the and adding following code to each ToggleButton did the trick:
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsChecked}" Value="True" >
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource img_src_lock}" />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsChecked}" Value="False">
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource img_src_unlock}" />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
The big question still persists: WHY?
The problem is that if you create an Image in a style there is only one instance, so as soon as multiple controls use the style there is going to be a fight over this one instance which can only be owned by one control.
The easiest solution to this is placing the Style in a resource and setting x:Shared to false, that way a copy of the style is used whereever referenced.
Why do you need to create BitmapImages and set them as Source to your Content Images in Triggers? Why arent you using the URI source to Images directly?
<Style TargetType="{x:Type ToggleButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsChecked}" Value="True">
<Setter Property="Content">
<Setter.Value>
<Image Source="/EEBase;component/Images/Lock_24x32.png" />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsChecked}" Value="False">
<Setter Property="Content">
<Setter.Value>
<Image Source="/EEBase;component/Images/Unlock_24x32.png" />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
Let me know if this helps.
I would think this is possible, but the obvious way isn't working.
Currently, I'm doing this:
<ContentControl
Content="{Binding HurfView.EditedPart}">
<ContentControl.Resources>
<Style
TargetType="ContentControl"
x:Key="emptytemplate">
<Style.Triggers>
<DataTrigger
Binding="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Content}"
Value="{x:Null}">
<Setter
Property="ContentControl.Template">
<Setter.Value>
<ControlTemplate>
<Grid
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock>EMPTY!</TextBlock>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Resources>
</ContentControl>
I'm not getting any binding errors and this compiles. However, it doesn't produce the expected result. I've also tried the obvious:
<DataTemplate DataType="{x:Null}"><TextBlock>Hurf</TextBlock></DataTemplate>
This won't compile. And attempting to set the content twice fails as well:
<ContentControl
Content="{Binding HurfView.EditedPart}">
<TextBlock>DEFAULT DISPLAY</TextBlock>
</ContentControl>
Can I do this without writing a custom template selector?
Simple, you have to bind the content property in the style. Styles won't overwrite a value on a control if there's a binding present, even if the value evaluates to Null. Try this.
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content" Value="{Binding HurfView.EditedPart}" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Content}" Value="{x:Null}">
<Setter Property="ContentControl.Template">
<Setter.Value>
<ControlTemplate>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock>EMPTY!</TextBlock>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Since I stumbled upon this question and had the same problem today, I wanted to contribute another way how I solved the problem. Since I did not like to add another style trigger I used the property TargetNullValue which seems to be a bit more readable than the accepted solution (which works nevertheless):
<ContentControl>
<ContentControl.Content>
<Binding Path="ContentViewModel">
<Binding.TargetNullValue>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock>EMPTY!</TextBlock>
</Grid>
</Binding.TargetNullValue>
</Binding>
</ContentControl.Content>
</ContentControl>
You could return DBNull.Value as the FallbackValue of the Binding for the Content of the ContentControl, and create a DataTemplate for DBNull :
<DataTemplate DataType="{x:Type system:DBNull}">
<!-- The default template -->
</DataTemplate>
...
<ContentControl Content="{Binding HurfView.EditedPart, FallbackValue={x:Static system:DBNull.Value}}" />