How can I set the trigger to be depend on a children's property?
Like I want to change the header of my Expander depending whether the Expander's ListView
does have children or not.
But I always get an Comilor error, that HasItems can not be resolved...
<Expander Header="Expand to add new ports">
<Expander.Resources>
<Style TargetType="{x:Type Expander}">
<Style.Triggers>
<Trigger Property="Content.HasItems" Value="False">
<Setter Property="Header" Value="No children" />
</Trigger>
</Style.Triggers>
</Style>
</Expander.Resources>
<ListView ItemsSource="{Binding Path=SomeItems}">
</ListView>
You can use a DataTrigger bound to the ListView using ElementName:
<Expander>
<Expander.Resources>
<Style TargetType="{x:Type Expander}">
<Setter Property="Header" Value="Expand to add new ports" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ListView, Path=HasItems}" Value="False">
<Setter Property="Header" Value="No children" />
</DataTrigger>
</Style.Triggers>
</Style>
</Expander.Resources>
<ListView x:Name="ListView" ItemsSource="{Binding Path=SomeItems}">
</ListView>
</Expander>
Also note that if you set a property along with the declaration of the control the Setter in the trigger won't have any effect.
Use this:
<Setter Property="Header" Value="Expand to add new ports" />
Instead of this:
<Expander Header="Expand to add new ports">
There are multiple issues in your code -
First, you should set the property in style setter instead of declaring it locally.
Second, use DataTrigger in place of Trigger.
<Expander>
<Expander.Style>
<Style TargetType="{x:Type Expander}">
<Setter Property="Header" Value="Expand to add new ports"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SomeItems.Count}" Value="0">
<Setter Property="Header" Value="No children" />
</DataTrigger>
</Style.Triggers>
</Style>
</Expander.Style>
<ListView ItemsSource="{Binding Path=SomeItems}">
</ListView>
</Expander>
Related
I would like to change the image of an WPF Button on ListView empty, but I don't now which is the property I must set from the trigger.
<Style x:Key="DisableOnEmptyLvStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=myListView, Path=Items.Count}" Value="0">
<Setter Property="IsEnabled" Value="False"/>
<!-- which button property I must set here? -->
</DataTrigger>
</Style.Triggers>
</Style>
<Button Style="{StaticResource DisableOnEmptyLvStyle}">
<Image Source="{StaticResource myImage}"/>
</Button>
Once ListView has items, then the image must change to the normal one.
Any ideas how to do this?
You should never set the Content directly using a Style, simply because the Style is instantiated once and potentially reused across multiple controls. The issue is that the value that you assign in the Setter for Content is also instantiated once and shared, but a control can only have a single parent in WPF.
What can go wrong? If you set or change the Content in a style and reference it for e.g. multiple Buttons you will see that only the last button shows the image. The first button gets its image set as Content, then the next button sets the image as Content and effectively removes the content of the first button.
There are two options that you can consider.
Create the images as resources and force each reference in XAML to get a new instance of the image by setting the x:Shared attribute to False.
When set to false, modifies WPF resource-retrieval behavior so that requests for the attributed resource create a new instance for each request instead of sharing the same instance for all requests.
<Window.Resources>
<!-- ...your image sources for "myImage" and "myImage". -->
<Image x:Key="myImageConrol" x:Shared="False" Source="{StaticResource myImage}"/>
<Image x:Key="myOtherImageConrol" x:Shared="False" Source="{StaticResource myImage}"/>
<Style x:Key="DisableOnEmptyLvStyle" TargetType="{x:Type Button}">
<Setter Property="Content" Value="{StaticResource myImageConrol}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=myListView, Path=Items.Count}" Value="0">
<Setter Property="IsEnabled" Value="False" />
<Setter Property="Content" Value="{StaticResource myOtherImageConrol}">
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Button Style="{StaticResource DisableOnEmptyLvStyle}"/>
Create data templates and swap them out. This works because - as the name says - they are templates and its controls will be instantiated for each control they are applied to.
<Style x:Key="DisableOnEmptyLvStyle" TargetType="{x:Type Button}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image Source="{StaticResource myImage}"/>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=myListView, Path=Items.Count}" Value="0">
<Setter Property="IsEnabled" Value="False" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image Source="{StaticResource myOtherImage}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<Button Style="{StaticResource DisableOnEmptyLvStyle}"/>
Please try the following:
<Style x:Key="DisableOnEmptyLvStyle" TargetType="{x:Type Button}">
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource myImageAtLeastOne}"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=myListView, Path=Items.Count}" Value="0">
<Setter Property="IsEnabled" Value="False" />
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource myImageEmpty}"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<Button Style="{StaticResource DisableOnEmptyLvStyle}" />
How can I set different style for first item in ListView? In my case, I want to change first item border, to get GUI like this:
My current code (no top border):
<ListView
ItemsSource="{Binding MyData}">
<ListView.ItemContainerStyle>
<Setter Property="BorderThickness" Value="0,0,0,1" />
</ListView.ItemContainerStyle>
</ListView>
There is a very simple solution. You don't have to write custom converters etc. Use PreviousData in RelativeSource
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="BorderThickness" Value="0,0,0,1" />
<Style.Triggers>
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource PreviousData}}" Value="{x:Null}">
<Setter Property="BorderThickness" Value="0,1,0,1"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
I am using DataTrigger to deselect ComboBox with CheckBox (Unchecked == deselected).
What i Need is:
when i select some item from ComboBox then CheckBox need to become
Checked.
When i uncheck CheckBox then ComboBox need to deselect.
CheckBox cannot be checked if ComboBox is deselected (or if ComboBox is deselected after CheckBox is Checked ComboBox should be selected with first item)
Here is XAML (this is working version and need to be changed). Solution must be pure XAML (no C# code)!
<CheckBox HorizontalContentAlignment="Stretch" VerticalContentAlignment="Center" Margin="90,519,13,0" VerticalAlignment="Top" Height="21" Width="Auto">
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}" BasedOn="{StaticResource {x:Type CheckBox}}">
<Style.Resources>
<Style TargetType="Path">
<Setter Property="FlowDirection" Value="LeftToRight" />
</Style>
<Style TargetType="TextBlock">
<Setter Property="FlowDirection" Value="LeftToRight" />
</Style>
</Style.Resources>
<Setter Property="FlowDirection" Value="RightToLeft" />
<!--<Setter Property="IsChecked" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem, ElementName=comboBoxEventDevice, UpdateSourceTrigger=PropertyChanged}" Value="{x:Null}">
<Setter Property="IsChecked" Value="False" />
</DataTrigger>
</Style.Triggers>-->
</Style>
</CheckBox.Style>
<ComboBox x:Name="comboBoxEventDevice" FlowDirection="LeftToRight" SelectionChanged="comboBoxEventDevice_SelectionChanged" Width="Auto">
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
<Setter Property="SelectedIndex" Value="1" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChecked, RelativeSource={RelativeSource AncestorType={x:Type CheckBox}}}" Value="True">
<Setter Property="SelectedIndex" Value="0" />
</DataTrigger>
<!--<DataTrigger Binding="{Binding Path=IsChecked, RelativeSource={RelativeSource AncestorType={x:Type CheckBox}}}" Value="False">
<Setter Property="SelectedIndex" Value="1" />
</DataTrigger>-->
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
</CheckBox>
UPDATE:
I try to not use nested controls (combobox inside checkbox) so i change XAML bu everything is same as before:
<CheckBox x:Name="checkBoxEventDevice" Margin="70,600,13,0" VerticalAlignment="Top" Height="21"/>
<ComboBox x:Name="comboBoxEventDevice" Margin="90,519,10,0" VerticalAlignment="Top" SelectionChanged="comboBoxEventDevice_SelectionChanged">
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
<Setter Property="SelectedIndex" Value="1" />
<Style.Triggers>
<!--<DataTrigger Binding="{Binding Path=IsChecked, ElementName=checkBoxEventDevice}" Value="True">
<Setter Property="SelectedIndex" Value="0" />
</DataTrigger>-->
<DataTrigger Binding="{Binding Path=IsChecked, ElementName=checkBoxEventDevice}" Value="false">
<Setter Property="SelectedIndex" Value="-1" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
Problem is in <Setter Property="SelectedIndex" Value="1" /> line. If i remove it then ComboBox is deselected when datatrigger is off but if i keep it then when datatrigger is off combobox is set to second item. So i need some line where i can tell: do nothing, do not deselect combobox, keep current state.
I found solution and these features are satisfied :
When i select some item from ComboBox then CheckBox need to become Checked.
When i uncheck CheckBox then ComboBox need to deselect.
If ComboBox is in deselected state, and when CheckBox is Checked ComboBox should be selected with first item (if items exist elsewhere CheckBox stays checked and ComboBox deselected).
Here is XAML:
<CheckBox HorizontalContentAlignment="Stretch" VerticalContentAlignment="Center" Margin="90,519,13,0" VerticalAlignment="Top" Height="21">
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}" BasedOn="{StaticResource {x:Type CheckBox}}">
<Style.Resources>
<Style TargetType="Path">
<Setter Property="FlowDirection" Value="LeftToRight" />
</Style>
<Style TargetType="TextBlock">
<Setter Property="FlowDirection" Value="LeftToRight" />
</Style>
</Style.Resources>
<Setter Property="FlowDirection" Value="RightToLeft" />
<Setter Property="IsChecked" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedIndex, ElementName=comboBoxEventDevice}" Value="-1">
<Setter Property="IsChecked" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
<ComboBox x:Name="comboBoxEventDevice" FlowDirection="LeftToRight" SelectionChanged="comboBoxEventDevice_SelectionChanged">
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
<Setter Property="SelectedIndex" Value="0" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChecked, RelativeSource={RelativeSource AncestorType={x:Type CheckBox}}}" Value="False">
<Setter Property="SelectedIndex" Value="-1" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
</CheckBox>
Now if someone need WPF nullable ComboBox all what need to do is to simple replace existing Combobox tag in its XAML code with XAML above and change x:Name and ElementName of ComboBox to original ComboBox in these lines:
<DataTrigger Binding="{Binding Path=SelectedIndex, ElementName=comboBoxEventDevice}" Value="-1">
<ComboBox x:Name="comboBoxEventDevice" FlowDirection="LeftToRight" SelectionChanged="comboBoxEventDevice_SelectionChanged">
Also callback for SelectionChanged="comboBoxEventDevice_SelectionChanged" should be changed or removed depending do you need event handling or not.
Margins of CheckBox need to be changed to your margins (and maybe you will need to add Width property but that depends on your project) in line:
<CheckBox HorizontalContentAlignment="Stretch" VerticalContentAlignment="Center" Margin="90,519,13,0" VerticalAlignment="Top" Height="21">
This is a question following my previous problem, you can find it right there
So. Now I defined a DataGrid with a specific ElementStyle for each column (which just defines the TextBlocks inside in bold & white -- will come over this problem later)
So now I have two questions
First question (solved)
When I happen to set a background to my cell, it overrides the default style, and the background stays the same when the cell is highlighted.
One example of a style:
<!-- Green template for market-related -->
<ControlTemplate x:Key="Green" TargetType="{x:Type tk:DataGridCell}">
<Grid Background="Green">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
I'd naturally say that this is "normal" because I set the Grid's background to Green. I therefore tried it this way:
<!-- Light green template for sophis-related -->
<ControlTemplate x:Key="LightGreen" TargetType="{x:Type tk:DataGridCell}">
<Grid Background="LightGreen">
<Grid.Resources>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}},
Converter={StaticResource DebugConverter}}" Value="True">
<Setter Property="Grid.Background" Value="#FF3774FF" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
This won't work either. As you can see I put a DebugConverter so I can check that the trigger is actually called, which is the case, but... Background does not change (and Snoop confirms this...)
Third try:
<!-- Light green template for sophis-related -->
<ControlTemplate x:Key="LightGreen" TargetType="{x:Type tk:DataGridCell}">
<ControlTemplate.Resources>
<Style TargetType="{x:Type tk:DataGridCell}">
<Setter Property="Background" Value="LightGreen" />
</Style>
</ControlTemplate.Resources>
<Grid>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
And... No background will be displayed (stays transparent)
So I think I am working in the wrong way here and I was wondering what should I do to JUST define the "not selected" template.
I would say that I may need to define a style BasedOn the "classic" style but, how would I do that? I tried to add TemplateBindings with no success
** EDIT: Solution**
As H B suggested in his answer, problem was coming from DependencyProperty Precedence, here's the solution:
<!-- Light green template for sophis-related -->
<ControlTemplate x:Key="LightGreen" TargetType="{x:Type tk:DataGridCell}">
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}},
Converter={StaticResource DebugConverter}}" Value="True">
<Setter Property="Grid.Background" Value="#FF316AC5" />
</DataTrigger>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}},
Converter={StaticResource DebugConverter}}" Value="False">
<Setter Property="Grid.Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
Second question
Now, let's speak Triggers.
Basically, what I want to do is to define specific Triggers to my ElementStyle so the font color is white if the cell's background is Red or Green (the only aim of this is to have a better readability as Red and Green are kinda dark, black font on dark background results in a nice fail :p )
Edit Seems like I'm not clear enough: the following style is the style applied to each item of the datagrid, through the property DataGridTextColumn.ElementStyle. Here is the code handling that:
void VolatilityDataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
DataGridTextColumn column = e.Column as DataGridTextColumn;
column.ElementStyle = s_boldCellStyle;
// Other stuff here...
}
Here is what I do:
<!-- Cell style for colored matrix-->
<Style x:Key="BoldCellStyle" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Background, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}}}"
Value="Red">
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding Background, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}},
Converter={StaticResource DebugConverter}}"
Value="Green">
<Setter Property="Foreground" Value="White" />
</DataTrigger>
</Style.Triggers>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
And... It doesn't work. Strangely, what goes through converter is ONLY transparent background colors. I am definitely missing something here!
BTW, I also tried with classic triggers, no success either, I use DataTriggers here so I can debug the binding values!
Now I've been stuck for more than three days on this and I'm starting to freak out... Hopefully the Stackoverflow community will save me :)
Thanks!
Edit
Okay, update.
I understood why my Trigger does not work. The Background actually set is on the Grid and NOT on the DataGridCell. It is therefore normal that I don't get any color set there.
However, I ran some tests and found out that when the binding is set, the TextBlock does not have any parent yet (Parent = null). Binding to a RelativeSource of type Grid will bind me to... The whole DataGrid items presenter.
I'm not sure what to do now, since it seems like that from the actual TextBlock style I can't reach the parent Grid and therefore cannot resolve what color should I display according to the background.
Also, I can't change the Font color in my ControlTemplate because the DataGrid wants a Style for each column, which overrides the template's style by default (see my previous question and its answer)
So... Stuck again I am!
Dependency Property Value Precedence
This:
<Grid Background="LightGreen">
<Grid.Resources>
<Style TargetType="{x:Type Grid}">
<!-- Trigger Stuff -->
</Style>
</Grid.Resources>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
Needs to be:
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Grid}">
<Setter Property="Background" Value="LightGreen"/>
<!-- Trigger Stuff -->
</Style>
</Grid.Resources>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
Not sure about your second question as of now, possibly a related problem, i would suggest setting TextElement.Foreground instead of Foreground for starters. Getting Transparent as value is not very helpful, what control template do you use for the DataGridCell? If it is custom, is the Background hooked up properly via a TemplateBinding?
This works as long as the Background property is used, so if you have a ControlTemplate which sets things internally you need to externalize that. A normal DataGrid example:
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Background" Value="LightGreen"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Content}" Value="Apple">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Content}" Value="Tomato">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Background, RelativeSource={RelativeSource AncestorType={x:Type DataGridCell}}}" Value="Red">
<Setter Property="Foreground" Value="White"/>
</DataTrigger>
<DataTrigger Binding="{Binding Background, RelativeSource={RelativeSource AncestorType={x:Type DataGridCell}}}" Value="Green">
<Setter Property="Foreground" Value="White"/>
</DataTrigger>
</Style.Triggers>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
So if the CellStyle sets the ControlTemplate the properties need to be hooked up via TemplateBinding. e.g.
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Background" Value="LightGreen"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Content}" Value="Apple">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Content}" Value="Tomato">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
Do not do the triggering inside the template or it will get messy.
I want to change the CheckBox which is inside of a DataGridColumn into an image when it is checked and another when it is unchecked, How can i do ?
Ps: My DataGridCheckBoxColumn is defined like this:
<DataGridCheckBoxColumn Header="Priority" Binding="{Binding PRIORITY, Converter={StaticResource converter}}"/>
Converter is converting bytes to boolean.
Use the ElementStyle and EditingElementStyle properties to create and set a different Template for the CheckBox which fits that.
e.g.
<DataGridCheckBoxColumn Binding="{Binding IsActive}">
<DataGridCheckBoxColumn.ElementStyle>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="IsEnabled" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Image MaxWidth="32" MaxHeight="32">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="Images/Error.ico" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, RelativeSource={RelativeSource AncestorType=CheckBox}}" Value="True">
<Setter Property="Source" Value="Images/Default.ico" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridCheckBoxColumn.ElementStyle>
</DataGridCheckBoxColumn>
This makes the column display an image based on IsChecked, the URIs are just hardcoded and the CheckBox is disabled because editing in the ElementStyle does not change any properties on the bound object. Its only purpose is to display the approriate image.
(The EditingElementStyle is not set here so if the user clicks the cell again to edit it a normal CheckBox appears which can be checked or unchecked.)