WPF apply triggers to listview items to change background - wpf

I am trying to create a sort of left menu for navigation inside the desktopapplication. My idea is to use Buttons as Listview items which should behave in this way: when i hover with the mouseover them theri background should change color (becomes darkCyan) and when i click one its background color should change persistently (to darkCyan) until i click on another button of the list. The problem is that i am using a the DataTemplate property to specify how the buttons should look like and I am tryin to apply the triggers to change the background color on the ControlTemplate of the ListView. The result is that sometimes the background color changes but the command related to the button is not fired other times the contrary. I think that I am doing the things in the wrong element of the tree view, but I don't have enough knowledge of the tree view so I am not understanding what I am doing wrong. Here is the code of the XAML in which i define the styles for the Buttons and the ListView
<Window.Resources>
<DataTemplate DataType="{x:Type viewModels:TripViewModel}">
<views:TripView />
</DataTemplate>
<Style TargetType="{x:Type Button}">
<Setter Property="Height"
Value="50" />
<Setter Property="Background"
Value="#555D6F" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border>
<Grid Background="Transparent">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border BorderBrush="Transparent"
BorderThickness="0"
Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="Background"
Value="DarkCyan" />
</Trigger>
<Trigger Property="IsSelected"
Value="True">
<Setter Property="Background"
Value="DarkCyan" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
And here is the code in which I create the ListView
<ListView Name="MenuButtons"
ItemsSource="{Binding PageViewModels}"
Background="Transparent"
IsSynchronizedWithCurrentItem="True">
<ListView.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Margin="0" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Anyone can help?
Thanks

I have solved the issue by using a ListBox instead of a ListView and setting the ItemContainer to be a button in the following way
<ListBox.ItemTemplate>
<ItemContainerTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Margin="0" />
</ItemContainerTemplate>
</ListBox.ItemTemplate>

Related

WPF DataGridTemplateColumn background color not changing on datagrid row selected

I have below WPF Toolkit DataGrid. I have created a custom column using DataGridTemplateColumn which contains a toggle button (without text).
When datagrid row is selected, toggle button background color is correctly changed with the same color as row selection but the cell's background is not changing the color, it remains white. Why?
This is the appearance of the DataGridTemplateColumn column before and after row selection.
Before:
After:
Here the code:
<UserControl.Resources>
<ResourceDictionary>
<!-- Body content datagrid cell vertical centering -->
<Style x:Key="Body_Content_DataGrid_Centering" TargetType="{x:Type dg:DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type dg:DataGridCell}">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<!-- Other Stuff -->
<dg:DataGrid x:Name="MyDg"
ItemsSource="{Binding Path=ListOfItems}"
Margin="3 5 5 5"
AutoGenerateColumns="False"
CanUserAddRows="False" CanUserResizeRows="False"
SelectionMode="Single"
SelectedItem="{Binding Path=MySelectedItem}"
ColumnWidth="*"
AlternationCount="2"
Focusable="False" SelectionUnit="FullRow"
CellStyle="{StaticResource Body_Content_DataGrid_Centering}">
<dg:DataGrid.Columns>
<dg:DataGridTemplateColumn Header="Selection" Width="Auto" CanUserResize="False">
<dg:DataGridTemplateColumn.CellStyle>
<Style TargetType="dg:DataGridCell">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</dg:DataGridTemplateColumn.CellStyle>
<dg:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ToggleButton Content="" IsChecked="{Binding Path=IsSelected}">
<ToggleButton.Template>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<StackPanel Orientation="Horizontal">
<Image MaxWidth="32" MaxHeight="32">
<Image.Style>
<Style>
<Setter Property="Image.Source" Value="/My.Graphics;component/PNG/Unchecked.png" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type ToggleButton}}}" Value="True">
<Setter Property="Image.Source" Value="/My.Graphics;component/PNG/Checked.png" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<!-- <ContentPresenter Content="{TemplateBinding Content}" Margin="0,0,0,0" />-->
</StackPanel>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
</DataTemplate>
</dg:DataGridTemplateColumn.CellTemplate>
</dg:DataGridTemplateColumn>
</dg:DataGrid.Columns>
</dg:DataGrid>
Finally, I have solved it, the problem was in the style declaration for the DataGridTemplateColumn.
Despite setting toggle button background to Transparent it was not working as Raviraj suggested.
Finally the solution was to change the piece of code regarding to the DataGridTemplateColumn:
<dg:DataGridTemplateColumn.CellStyle>
<Style TargetType="dg:DataGridCell">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</dg:DataGridTemplateColumn.CellStyle>
By this one:
<dg:DataGridTemplateColumn.CellStyle>
<Style TargetType="dg:DataGridCell">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type dg:DataGridCell}">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</dg:DataGridTemplateColumn.CellStyle>
Doing this is working.
Below the result.
Before row selection:
After row selection:
Set the background color of your the column template to transparent.
Something like this:
<DataTemplate>
<ToggleButton Background="Transparent" Content="" IsChecked="{Binding Path=IsSelected}">

Merge Style.Triggers with property Template in WPF

I want to set a style for my DataGrid, but I do not know where is the problem
the backgroud property does not work with its value in the presence of the Template property.
my code:
<Style x:Key="DataGridStyle1" TargetType="{x:Type DataGrid}">
<Setter Property="CellStyle" Value="{DynamicResource GridStyle1}"/>
</Style>
<Style x:Key="GridStyle1" TargetType="DataGridCell">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True" >
<Setter Property="Background" Value="SeaGreen"/>
</Trigger>
</Style.Triggers>
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Border Name="DataGridCellBorder">
<ContentControl Content="{TemplateBinding Content}">
<ContentControl.ContentTemplate>
<DataTemplate>
<TextBlock Background="Transparent" TextWrapping="WrapWithOverflow" TextTrimming="CharacterEllipsis"
Height="auto" Width="auto" Text="{Binding Text}"/>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
Help me please.
You have explicitly set TextBlock background to Transparent, so it won't pick value from DataGridCell. You should bind with background of DataGridCell using RelativeSource like this:
<TextBlock Background="{Binding Background, RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType=DataGridCell}}"
TextWrapping="WrapWithOverflow" TextTrimming="CharacterEllipsis"
Height="auto" Width="auto" Text="{Binding Text}"/>

ListView Style Trigger affecting one control and not another

I have a ListView control with a list of system messages in it, and a corresponding "action" button that will help my user resolve the message:
If the message is an error message, I want the Foreground of the text and the action button (the [more...] part) to turn red. As you can see, it's not doing that.
I'm using a Style.Trigger bound to the message data's Severity to set the Foreground. This works fine for the TextBlock in the DataTemplate, but fails to affect the Button in that Template. My XAML looks like this:
<ListView x:Name="ImageCenterStatus" Background="{DynamicResource SC.ControlBrush}" Margin="10,22,10,0" FontSize="12" Grid.Column="1" ClipToBounds="True" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock TextWrapping="WrapWithOverflow" cal:Message.Attach="[Event MouseUp] = [Action DoStatusAction($dataContext)]" Text="{Binding Path=DisplayedMessage}"/>
<Button x:Name="ActionButton" Content="{Binding Path=DisplayedActionLabel}" FontSize="12" cal:Message.Attach="DoStatusAction($dataContext)" Style="{DynamicResource SC.ActionButtonHack}"></Button>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Severity}" Value="Warning">
<Setter Property="Foreground" Value="#FFCE7A16" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Severity}" Value="Error">
<Setter Property="Foreground" Value="#FFD13636" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>
The Button has its own Style, SC.ActionButtonHack, but I am very careful not to override the Foreground anywhere that style:
<Style x:Key="SC.ActionButtonHack" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ContentPresenter x:Name="contentPresenter" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Why does the TextBlock in the DataTemplate respond to the Foreground change in the Trigger, but not the Button?
What do I need to do to get the button to respect the Foreground change?
Bind TextElement.Foreground of ContentPresenter to ListViewItem's foreground to get it work:
<Style x:Key="SC.ActionButtonHack" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ContentPresenter x:Name="contentPresenter"
TextElement.Foreground="{Binding Foreground,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=ListViewItem}}"
HorizontalAlignment="Center"
VerticalAlignment="Center" Margin="0"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
OR
Either bind it on Button itself:
<Button x:Name="ActionButton"
Foreground="{Binding Foreground, RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType=ListViewItem}}"
Content="{Binding Path=Name}" FontSize="12"
Style="{DynamicResource SC.ActionButtonHack}"/>

Stretch and Center Alignment?

I am making a kind of navigation bar using a horizontal ListBox (There is probably a better way) reason being I needed to be able to select only 1 thing at a time, and have it stay selected, then be able to deselect it. So to make that work I'm using a ToggleButton.
Problem is, I 'Stretch' the content to fit in the box so that I can click anywhere in the selection area to deselect using the togglebutton.
My problem is that my buttons are now top aligned because of the stretch, is it possible to vertical center them whilst using the stretch? Whenever I try and center them at the same time it puts me back to square one where the button itself gets shrunk to the center again.
<ListBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding PageViewModels}" SelectedItem="{Binding CurrentPageViewModel}" Style="{StaticResource MenuStyle}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Padding" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#FCCC"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#FAAA"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<ToggleButton VerticalAlignment="Stretch"
Content="{Binding Name}"
IsChecked="{Binding IsSelected, Mode=TwoWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"
Style="{StaticResource BlankButtonStyle}"
/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is probably a really stupid question but it's really bugging me and I'm a bit stuck.
You can use VerticalContentAlignment property.
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="VerticalContentAlignment" Value="Center"/>
...
So I was able to solve this by simply adding a TextBlock to the ListItem template.
I can stretch the now blank ToggleButton to the full size of the ListItem and then center/do whatever with the TextBlock.
It's not an ideal implementation but it's the best one I could come up with for now.
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" HorizontalAlignment="Center" />
<ToggleButton VerticalAlignment="Stretch"
Content=""
IsChecked="{Binding IsSelected, Mode=TwoWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"
Style="{StaticResource BlankButtonStyle}"
/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>

WPF Listbox Show Button in ItemTemplate on MouseOver

I have a listbox containing and image and a button. By default the button is hidden. I want to make the button visible whenever I hover over an item in the listbox.
The XAML I am using is below. Thanks
<Window.Resources>
<Style TargetType="{x:Type ListBox}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1" Margin="6">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=FullPath}" Height="150" Width="150"/>
<Button x:Name="sideButton" Width="20" Visibility="Hidden"/>
</StackPanel>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
Ok, try this in your button declaration:
<Button x:Name="sideButton" Width="20">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ListBoxItem}},Path=IsMouseOver}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
So I'm using a style with a trigger to look back up the visual tree until I find a ListBoxItem, and when its IsMouseOver property flips over to True I set the button's visibility to Visible.
See if it's close to what you want.
This Style does what you need. On mouse over, the button becomes only visible when the pointer is over the ListBoxItem. The special trick is to bind to the TemplatedParent for reaching IsMouseOver and use TargetName on the Setter to only affect the Button.
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black"
BorderThickness="1"
Margin="6">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=FullPath}"
Height="150"
Width="150" />
<Button x:Name="sideButton"
Width="20"
Visibility="Hidden" />
</StackPanel>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsMouseOver,RelativeSource={RelativeSource TemplatedParent}}"
Value="True">
<Setter Property="Visibility"
TargetName="sideButton"
Value="Visible" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
#David is showing the right way,
But I have one suggestion to your XAML architecture. If you don't have any DataBinding on the Button it is better to put that in to the ListBoxItem style than the DataTemplate as bellow.
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black"
BorderThickness="1"
Margin="6">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=FullPath}"
Height="150"
Width="150" />
</StackPanel>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Grid Background="Transparent">
<Button x:Name="sideButton" Width="20" HorizontalAlignment="Right" Visibility="Hidden" />
<ContentPresenter/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Visibility"
TargetName="sideButton"
Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
One solution to find what item was clicked is to add the following Event setter
XAML
C#
void ListBoxItem_MouseEnter(object sender, MouseEventArgs e)
{
_memberVar = (sender as ListBoxItem).Content;
}
Just wondering, if we use the technique above, how do we determine what item the button was clicked on?
To answer Brian's question, in the button click handler you can walk up the visual tree to find the item that contains the button:
DependencyObject dep = (DependencyObject)e.OriginalSource;
while ((dep != null) && !(dep is ListBoxItem))
{
dep = VisualTreeHelper.GetParent(dep);
}
if (dep != null)
{
// TODO: do stuff with the item here.
}

Resources