I want to display a ListBox whose item when selected will display a button which performs specific action on the selected data item. For this, I use two DataTemplates one is NormalTemplate(which has no button) and the other is SelectedTemplate (which has a button whose Tag property is bound to data, which is used in button click event handler). When an item in Listbox is selected I want to assign the SelectedTemplate.
For this I used custom ControlTemplate which has VisualStateManager which selects appropriate Template based on the VisualState (i.e., Selected & Unselected). The problem with this solution is I’ve to create new ControlTemplate every time I need to use different DataTemplates. I’m trying to find a solution where you specify the templates for normal & selected items and use common code for changing the datatemplate based on the visualstate.
Below is my DataTemplate for Unselected & Selected items:
<DataTemplate x:Key="NormalItemTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1" Margin="8,0,0,3" VerticalAlignment="Center">
<TextBlock Text="{Binding Name}" FontWeight="Bold" FontFamily="Arial" FontSize="14"/>
<TextBlock Text="{Binding Description}" />
</StackPanel>
</Grid>
</Border>
</DataTemplate>
<DataTemplate x:Key="SelectedItemTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="65"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1" Margin="8,0,0,3">
<TextBlock Text="{Binding Name}" FontWeight="Bold" FontFamily="Arial" FontSize="14"/>
<TextBlock Text="{Binding Description}" />
</StackPanel>
<Border Grid.Column="2" Grid.RowSpan="2" VerticalAlignment="Stretch">
<Button Content="Process" Tag={Binding} OnClick="Process_Clicked" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</Grid>
</Border>
</DataTemplate>
Below is the style which defines custom ControlTemplate which uses VisualStateManager to display or hide appropriate ContentPresenters whose ContentTemplate is assigned to NormalItemTemplate & SelectedItemTemplate
<Style TargetType="ListBoxItem" x:Key="ActiveGamesItemContainerStyle">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid Background="{TemplateBinding Background}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
</VisualStateGroup>
<VisualStateGroup x:Name="SelectionStates">
<VisualState x:Name="Unselected">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SelectedContentPresenter" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="UnSelectedContentPresenter" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Selected">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SelectedContentPresenter" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="UnSelectedContentPresenter" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter
x:Name="SelectedContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{StaticResource NormalItemTemplate}"
HorizontalAlignment="Stretch"
Margin="{TemplateBinding Padding}"
Visibility="Collapsed"/>
<ContentPresenter
x:Name="UnSelectedContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{StaticResource SelectedItemTemplate}"
HorizontalAlignment="Stretch"
Margin="{TemplateBinding Padding}"
Visibility="Visible"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The problems with this solution are:
-
For every new ListBoxItem which needs to use different DataTemplates I need to create a style with ControlTemplate which is same as the above but just need to change the ContentPresenter’s ContentTemplate. Thereby, lot of duplicate code/XAML.
-
Since, I’m using the button in the SelectedItemTemplate, I need to define the styles in the same UserControl class where the Click eventhandler is defined. If the UserControl uses more than one ListBox then there will be a huge Style definition declared for every ListBoxes.
I tried to solve this problem using attached properties but its not working. As, I’m not able to get hold of Selected ListBoxItem (instead I’m getting the bound data). The idea was to get hold of the ListBoxItem get its ContentPresenter and set it’s ContentTemplate to the SelectedItemTemplate.
Is there any better way to do this?
Thanks & Regards,
Sunil
No need for two separate templates, simply use the one and switch the visibility using a
DataTrigger.Create a view model of your item that has an
IsSelectedproperty. Add NotifyPropertyChanged events.Set the
ItemContainerStyleto bind theListBoxItem.IsSelectedproperty to yourIsSelectedproperty on your viewmodel.Then in your
DataTemplatehave aDataTriggerthat changes theVisibilityof the button whenIsSelectedisTrue.For an example of how do the above, take a look at the answer to this StackOverflow question.