I am trying to have a circular overlay come to the top when a certain view model enters an offline state. So it becomes partially transparent and on top of other elements in the Grid.
DataTriggers in the style have worked for everything so far, but I cannot set Panel.ZIndex. There is no error in build or run, but the property is not set (I assume because it's an attached property?)
<Ellipse Fill="DarkGray" Panel.ZIndex="-10" Width="50" Height="50">
<Ellipse.Style TargetType="Ellipse">
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="Offline">
<Setter Property="Opacity" Value=".6" />
<Setter Property="Panel.ZIndex" Value="10" />
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
You have the syntax correct, however the problem is that you are defining Panel.ZIndex in the <Ellipse> tag, and properties set in the tag itself will take precedence over any triggered values.
To fix it, simply set Panel.ZIndex in your style instead of the Ellipse tag
<Ellipse Fill="DarkGray" Width="50" Height="50">
<Ellipse.Style TargetType="Ellipse">
<Style>
<Setter Property="Panel.ZIndex" Value="-10" />
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="Offline">
<Setter Property="Opacity" Value=".6" />
<Setter Property="Panel.ZIndex" Value="10" />
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
See MSDN's article on Dependency Property Precedence for more info
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}" />
this is my latest try to make the canvas Invisible whenever the label.Content is an empty String. Any help/advice appreciated, thanks.
<Canvas Visibility="Visible">
<Label Content="" Name="holamouse" />
<Canvas.Resources>
<Style TargetType="{x:Type Canvas}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Content, ElementName=holamouse, UpdateSourceTrigger=PropertyChanged}" Value="{x:Static sys:String.Empty}">
<Setter Property="Canvas.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Canvas.Resources>
</Canvas>
The problem here is that a local property value always has higher precedence than a value set by a Style Setter. See Dependency Property Value Precedence.
When you set Visibility="Visible" on the Canvas, any Style Setter for that property is silently ignored. You could move the property assignment to the Style, although Visible is the default value anyway:
<Canvas>
<Label Content="" Name="holamouse" />
<Canvas.Resources>
<Style TargetType="{x:Type Canvas}">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Content, ElementName=holamouse}"
Value="{x:Static sys:String.Empty}">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
<DataTrigger Binding="{Binding Content, ElementName=holamouse}"
Value="{x:Null}">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Canvas.Resources>
</Canvas>
Please note also that there is a second trigger for Value="{x:Null}" now.
You need to move the default Visibility property out of the <Canvas> tag and into the <Style>
This is because properties defined in the <Tag> take precedence over any property setters, including triggered property setters. See MSDN's Dependency Property Precedence List if you want more details.
<Canvas>
<Label Content="" Name="holamouse" />
<Canvas.Resources>
<Style TargetType="{x:Type Canvas}">
<Setter Property="Canvas.Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Content, ElementName=holamouse, UpdateSourceTrigger=PropertyChanged}" Value="{x:Static sys:String.Empty}">
<Setter Property="Canvas.Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Canvas.Resources>
</Canvas>
The following dims the image when I disable the button and show clearly when enabled:
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type UIElement}, AncestorLevel=1}, Path=IsEnabled}" Value="False">
<Setter Property="Opacity" Value="0.25"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
...
<Button Name="btnPageFirst" IsEnabled="False">
<Image Source="..\Resources\imgMoveFirst.png" />
</Button>
I want to do a similar effect but with Path. I want to gray the image. But it does not gray and there is no error.
<Style TargetType="{x:Type Path}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type UIElement}, AncestorLevel=1}, Path=IsEnabled}" Value="False">
<Setter Property="Fill" Value="Gray"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
...
<Button Name="btnPageFirst" IsEnabled="False">
<Path Data="F1M820.557,535.025L838.189,535.024 817.857,555.36 857.82,555.36 857.82,568.301 817.998,568.301 838.226,588.531 820.557,588.499 793.82,561.765 820.557,535.025z" Stretch="Uniform" Fill="DodgerBlue" Width="16" Height="16" Margin="0,0,0,0" />
</Button>
There is a precedence order which is used to calculate the values of the dependency properties during runtime.
The oversimplified precedence list:
Local value (what you set on the control)
Triggers
Style setters
Your problem is that you set Fill="DodgerBlue" on your path and because it has higher precedence then the Trigger that is way you don't see the fill change. Also that is why it works for your Image because there you don't set the Opacity directly.
To make it work:
Remove the Fill="DodgerBlue" from your path
Set it in your style:
<Style TargetType="{x:Type Path}">
<Style.Setters>
<Setter Property="Fill" Value="DodgerBlue"/>
</Style.Setters>
<Style.Triggers>
<!-- ... -->
</Style.Triggers>
</Style>
As a side note: if you always "inside" in a button you can rewrite the RelativeSource to:
RelativeSource={RelativeSource AncestorType={x:Type Button}}
I'm looking for a simplest way to remove duplication in my WPF code.
Code below is a simple traffic light with 3 lights - Red, Amber, Green. It is bound to a ViewModel that has one enum property State taking one of those 3 values.
Code declaring 3 ellipses is very duplicative. Now I want to add animation so that each light fades in and out - styles will become even bigger and duplication will worsen.
Is it possible to parametrize style with State and Color arguments so that I can have a single style in resources describing behavior of a light and then use it 3 times - for 'Red', 'Amber' and 'Green' lights?
<UserControl.Resources>
<l:TrafficLightViewModel x:Key="ViewModel" />
</UserControl.Resources>
<StackPanel Orientation="Vertical" DataContext="{StaticResource ViewModel}">
<StackPanel.Resources>
<Style x:Key="singleLightStyle" TargetType="{x:Type Ellipse}">
<Setter Property="StrokeThickness" Value="2" />
<Setter Property="Stroke" Value="Black" />
<Setter Property="Height" Value="{Binding Width, RelativeSource={RelativeSource Self}}" />
<Setter Property="Width" Value="60" />
<Setter Property="Fill" Value="LightGray" />
</Style>
</StackPanel.Resources>
<Ellipse>
<Ellipse.Style>
<Style TargetType="{x:Type Ellipse}" BasedOn="{StaticResource singleLightStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding State}" Value="Red">
<Setter Property="Fill" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<Ellipse>
<Ellipse.Style>
<Style TargetType="{x:Type Ellipse}" BasedOn="{StaticResource singleLightStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding State}" Value="Amber">
<Setter Property="Fill" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<Ellipse>
<Ellipse.Style>
<Style TargetType="{x:Type Ellipse}" BasedOn="{StaticResource singleLightStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding State}" Value="Green">
<Setter Property="Fill" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
</StackPanel>
As long as your "Traffic Light" is wrapped up inside a control, which it appears it is, I don't think this is horrible. Each ellipse is well defined and has different triggers, each indicating its own state. You've already factored the common parts out into the base style, which is good.
You could wrap the individual ellipses inside another user control (which wouldn't need a backing ViewModel) that had an ActiveState property and an ActiveFill property. Then your TrafficLight looks something like:
<StackPanel Orientation="Vertical" DataContext="{StaticResource ViewModel}">
<my:Indicator State="{Binding State}" ActiveState="Red" ActiveFill="Red" />
<my:Indicator State="{Binding State}" ActiveState="Amber" ActiveFill="Red" />
<my:Indicator State="{Binding State}" ActiveState="Green" ActiveFill="Green" />
</StackPanel>
This lets you wrap up all your Ellipse styling inside your Indicator control and the only thing that control needs to worry about is comparing the State to the ActiveState to determine if it should fill itself with the ActiveFill brush.
As to if this is worth the effort or not, that depends on how many of these you have floating around and if you use them outside of your Traffic Light user control. Remember: You Ain't Gonna Need It.
I have a custom window which have two depencency properties: Boolean? ValidationStatus, and string ValidationMessage. Binding these properties works fine but trigger doesn't seem to be triggered when these values change. What am I doing wrong?
<TextBlock x:Name="validationTextBox"
Grid.Row="1"
Grid.ColumnSpan="2"
Text="{Binding ElementName=_this, Path=ValidationMessage}"
TextAlignment="Center"
Background="Green">
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Value="False" Binding="{Binding ElementName=_this, Path=ValidationStatus}">
<Setter Property="Panel.Background" Value="Red"/>
<Setter Property="TextBox.Text" Value="Outer checkbox is not checked"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Style Setters do not override local attribute settings. Therefore the data trigger's values are being ignored because you have specified the Text and Background properties on the TextBlock. To fix the problem set the default values of these properties in the style as shown in the following code:
<TextBlock x:Name="validationTextBox"
Grid.Row="1"
Grid.ColumnSpan="2"
TextAlignment="Center">
<TextBlock.Style>
<Style>
<Setter Property="TextBox.Text" Value="{Binding ElementName=_this, Path=ValidationMessage}"/>
<Setter Property="TextBox.Background" Value="Green"/>
<Style.Triggers>
<DataTrigger Value="False" Binding="{Binding ElementName=_this, Path=ValidationStatus}">
<Setter Property="TextBox.Background" Value="Red"/>
<Setter Property="TextBox.Text" Value="Outer checkbox is not checked"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>