ItemsControl with two DataTemplates using AlternationIndex trigger, XAML - wpf

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>

Related

XAML custom text in TextBlock

Good afternoon,
I´m binding a list of doubles (Latlng) to an ItemsControl and in a TextBlock I want to make a custom text when the list binded has a count of 0. With the code I have the TextBlock is empty when the ItemsSource of the ItemsControl is that list.
What am I doing wrong?
Btw the list Latlng is a property of a class.
<ItemsControl Name="icLatLng" ItemsSource="{Binding Latlng}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock FontFamily="Arial" FontSize="14">
<TextBlock.Style>
<Style>
<Setter Property="TextBlock.Text" Value="{Binding}"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ItemsControl}, Path=Items.Count}" Value="0">
<Setter Property="TextBlock.Text" Value="—"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Thanks in advance.
Welcome to SO.
ItemsControl.ItemTemplate is the template that gets applied to each element in the Items list the control is bound to. If there are no items to begin with, then it won't be created, so anything you do in it won't be seen.
I suspect what you're really trying to do is replace the look of the entire control when the list is empty. If so, you can do that by applying a new template to the ItemsControl itself using a DataTrigger on the HasItems property:
<ItemsControl Name="icLatLng" ItemsSource="{Binding Latlng}">
<ItemsControl.Style>
<Style TargetType="{x:Type ItemsControl}" BasedOn="{StaticResource {x:Type ItemsControl}}">
<Style.Triggers>
<DataTrigger Binding="{Binding HasItems, RelativeSource={RelativeSource Self}}" Value="False">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ItemsControl}">
<TextBlock Text="-" />
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.Style>
</ItemsControl>

ListBox items separator when more than one item

I have a ListBox and I'm trying to insert a separator between items but I want the separator to show when there is more than one item.
<ListBox Grid.Row="1" x:Name="WarningListBox" Visibility="Visible"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
ItemsSource="{Binding Path=Warnings}">
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Setter Property="Height" Value="0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding CanShowAll}" Value="True">
<Setter Property="Height" Value="Auto"/>
<Setter Property="MaxHeight" Value="120"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource NewDesignListItemBoxStyle}">
<Setter Property="Margin" Value="0"/>
<Setter Property="Padding" Value="0"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.Resources>
<DataTemplate x:Key="TypeOneViewModel" DataType="{x:Type vm:TypeOneViewModel}">
<notifications:TypeOneNotification Focusable="False" MinHeight="30" Background="Beige"/>
</DataTemplate>
<DataTemplate x:Key="TypeTwoViewModel" DataType="{x:Type vm:TypeTwoViewModel}">
<notifications:TypeTwoNotification Focusable="False" MinHeight="30" Background="Beige"/>
</DataTemplate>
</ListBox.Resources>
<ListBox.ItemTemplateSelector>
<templateSelectors:WarningTemplateSelector
TypeOneDataTemplate="{StaticResource TypeOneViewModel}"
TypeTwoDataTemplate="{StaticResource TypeTwoViewModel}"/>
</ListBox.ItemTemplateSelector>
</ListBox>
I've tried adding the Separator right before the closing tag
<Separator Margin="30 0 30 0" BorderBrush="#CCCCCC" BorderThickness="2"/>
but it crashes the application. Should I somehow put it in a DataTemplate? I think it will then show under each item (even when there's only one item).
I think, you want to achieve something like this:
<DataTemplate x:Key="TypeOneViewModel" DataType="{x:Type vm:TypeOneViewModel}">
<DockPanel LastChildFill="True">
<Separator x:Name="separator" HorizontalAlignment="Stretch" VerticalAlignment="Top" DockPanel.Dock="Top">
<Separator.Style>
<Style TargetType="Separator">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource PreviousData}, FallbackValue={x:Null}}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Separator.Style>
</Separator>
<!--Rest of your DataTemplate-->
<notifications:TypeOneNotification Focusable="False" MinHeight="30" Background="Beige"/>
</DockPanel>
</DataTemplate>
This will hide the separator, if item have no PreviousData, so for the 1st item.
You can easy take Separator style outside if used more then once, and just call it as StaticResource.

How to use different ItemsControl.ItemContainerStyle for different ItemsPanelTemplate

I have an ItemsControl which uses different ItemsPanelTemplate based on certain condition. I want to have different ItemContainerStyle for each ItemsPanelTemplate (in fact, I want ItemsContainerStyle for only one of the templates). How can I achieve that? Here is the code I am using:
<UserControl.Resources>
<ItemsPanelTemplate x:Key="UGridItemsPanelTemplate">
<UniformGrid Name="MyUGrid" Columns="{Binding Columns}" Rows="{Binding Rows}"/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="GridItemsPanelTemplate">
<Grid Name="MyGrid" Loaded="MyGrid_Loaded"/>
</ItemsPanelTemplate>
</UserControl.Resources>
<Grid>
<!--ItemList has 1000+ items if IsMap is FALSE; using ItemsConatinerStyle in this case slows the UI down-->
<ItemsControl Name="MyPresenter" ItemsSource="{Binding ItemList}" Tag="{Binding IsMap}">
<ItemsControl.Style>
<Style TargetType="{x:Type ItemsControl}">
<Setter Property="ItemsPanel" Value="{StaticResource UGridItemsPanelTemplate}"/>
<Style.Triggers>
<Trigger Property="Tag" Value="TRUE">
<!--I want to use ItemContainerStyle only for this template-->
<Setter Property="ItemsPanel" Value="{StaticResource GridItemsPanelTemplate}"/>
</Trigger>
</Style.Triggers>
</Style>
</ItemsControl.Style>
<!--Use this style only if IsMap is TRUE-->
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Grid.Row" Value="{Binding GridRow}"/>
<Setter Property="Grid.Column" Value="{Binding GridColumn}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border x:Name="Border1" Background="{Binding BorderVisible}"
BorderThickness="1" Padding="{Binding PaddingVal}">
<Button Name="ItemButton" Content="{Binding Label}" IsEnabled="{Binding IsButtonEnabled}" CommandParameter="{Binding}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
Thanks,
RDV
I found a way, When IsMap is TRUE, set ItemsContainerStyle along with ItemsPanel. Updated code is posted below:
<UserControl.Resources>
<ItemsPanelTemplate x:Key="UGridItemsPanelTemplate">
<UniformGrid Name="MyUGrid" Columns="{Binding Columns}" Rows="{Binding Rows}"/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="GridItemsPanelTemplate">
<Grid Name="MyGrid" Loaded="MyGrid_Loaded"/>
</ItemsPanelTemplate>
<Style x:Key="ClusterGridContainerStyle">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Grid.Row" Value="{Binding UnitGridRow}"/>
<Setter Property="Grid.Column" Value="{Binding UnitGridColumn}"/>
</Style>
</UserControl.Resources>
<Grid>
<!--ItemList has 1000+ items if IsMap is FALSE-->
<ItemsControl Name="MyPresenter" ItemsSource="{Binding ItemList}" Tag="{Binding IsMap}">
<ItemsControl.Style>
<Style TargetType="{x:Type ItemsControl}">
<Setter Property="ItemsPanel" Value="{StaticResource UGridItemsPanelTemplate}"/>
<Style.Triggers>
<Trigger Property="Tag" Value="TRUE">
<!--I want to use ItemContainerStyle only for this template-->
<Setter Property="ItemsPanel" Value="{StaticResource GridItemsPanelTemplate}"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource ClusterGridContainerStyle}"/>
</Trigger>
</Style.Triggers>
</Style>
</ItemsControl.Style>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border x:Name="Border1" Background="{Binding BorderVisible}"
BorderThickness="1" Padding="{Binding PaddingVal}">
<Button Name="ItemButton" Content="{Binding Label}" IsEnabled="{Binding IsButtonEnabled}" CommandParameter="{Binding}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>

Setting ItemsPanel in Trigger fails to respond to change in theme for WPF ListBox

The visible items in a listbox do not see changes when the theme changes when I set the ItemsPanel via a trigger. If I scroll the listbox down a few pages and then scroll back up the initial visible items do get updated.
If I set the ItemsPanel directly (see commented out xaml below, and remove the style) the visible items in the listbox do reflect the change in theme
immediately.
Can some one please suggest how to change out an ItemsPanelTemplate and get it to respond to a change in theme?
<ListBox
SelectionMode="Extended"
VirtualizingStackPanel.IsVirtualizing="True"
ItemTemplateSelector="{StaticResource LayerItemTemplateSelector}"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding MyItemsView}"
SelectedItem="{Binding Path=SelectedMyItem, Mode=TwoWay}">
<ListBox.Resources>
<ItemsPanelTemplate x:Key="StackItemsPanelTemplate">
<VirtualizingStackPanel
Orientation="Vertical"
VirtualizationMode="Recycling"
IsContainerVirtualizable="True"
IsVirtualizing="True"
/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="WrapItemsPanelTemplate">
<telerik:VirtualizingWrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.Resources>
<!-- THIS WORKS
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel
Orientation="Vertical"
VirtualizationMode="Recycling"
IsContainerVirtualizable="True"
IsVirtualizing="True"
/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>-->
<ListBox.Style>
<Style TargetType="ListBox" BasedOn="{StaticResource ListBoxStyle}">
<Setter Property="ItemsPanel" Value="{DynamicResource StackItemsPanelTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsLoadingUnitShowLayerActive, Mode=OneWay}" Value="True">
<Setter Property="ItemsPanel" Value="{DynamicResource StackItemsPanelTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsLoadingUnitHideLayerActive, Mode=OneWay}" Value="True">
<Setter Property="ItemsPanel" Value="{DynamicResource WrapItemsPanelTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource ListBoxItemStyle}">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>

Conditional DataTemplate

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>

Resources