Trigger on an Inner/Attached property - wpf

Trigger on an Inner property
<Button BorderBrush="Black" BorderThickness="2" x:Name="TimeButton" ClickMode="Press" Click="SetTime_Click" Height="26" HorizontalAlignment="Left" Margin="15, 0, 0, 0" Style="{StaticResource ImageButtonStyle}" ToolTip="Set Time" Width="26">
<Button.Background>
<ImageBrush x:Name="TimeImageBrush" ImageSource="/YCS;component/Images/Clock.png" Stretch="Uniform" TileMode="None" />
</Button.Background>
</Button>
I need to make a trigger to set the ImageBrush in the Button.Background property to something different according to a boolean named HasHours which I can bind easily from my itemssource, any one knows how I can achieve this, I could not find any examples linking to this property....
I tried something like this
<Button.Triggers>
<DataTrigger Binding="{Binding HasHours}" Value="false">
<Setter TargetName="TimeImageBrush" Property="ImageSource" Value="/YCS;component/Images/ClockRed.png"/>
</DataTrigger>
</Button.Triggers>
but it gives me this error:
Cannot find the static member 'ImageSourceProperty' on the type 'ContentPresenter'.
Any help is much appreciated

This is perhaps not exactly an answer to your question.
First, i guess you won't be able to add a DataTrigger to the Triggers collection, since that only supports EventTriggers.
But, you could define the DataTrigger in the Button's Style. Here, instead of setting the ImageBrush's ImageSource property, simply set a new ImageBrush as Background.
<Button ...>
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding HasHours}" Value="False">
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="/YCS;component/Images/ClockRed.png"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>

Put the image as Content, not as Background, since you have no content.
Put the DataTrigger in the Triggers of the Image, not of the Button.
You will have to seek for the DataContext of the Trigger :
So something like :
<Button ... >
<Image ... >
<Image.Triggers>
<DataTrigger
Binding="{Binding Path= HasHours, RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Button}}}"
Value="false" >
<Setter Property="ImageSource" Value="/YCS;component/Images/ClockRed.png"/>
</DataTrigger>
</Image.Triggers>
</Image>
</Button>

Related

WPF RelativeSource not updating it's value

I'm trying to implement a button that uses XAML-defined icons to show a different state (like in the second comment here: https://codereview.stackexchange.com/questions/183871/wpf-button-with-xaml-defined-icon-that-changes-with-state). The icons are defined with a "Fill" property that uses a RelativeSource binding, like so:
<Canvas x:Key="icon_stop"
x:Shared="False"
Width="76"
Height="76"
Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
<Rectangle
Width="28.5"
Height="28.5"
Canvas.Left="23.75"
Canvas.Top="23.75"
Stretch="Fill"
Fill="{Binding RelativeSource={
RelativeSource Mode=FindAncestor,
AncestorType=Control
}, Path=Foreground}"
/>
</Canvas>
The button is bound to a property in the window's ViewModel like this:
<Button Name="PlayStopButton"
Padding="20 0 20 0"
Command="{Binding Path=StartStopCommand}">
<ContentControl Style="{StaticResource style_icon}">
<Binding
Path="IsRunning"
UpdateSourceTrigger="PropertyChanged"
FallbackValue="{StaticResource icon_play}">
<Binding.Converter>
<converters:BoolToObjectConverter
TrueObject="{StaticResource icon_stop}"
FalseObject="{StaticResource icon_play}"
NullObject="{StaticResource icon_play}"
/>
</Binding.Converter>
</Binding>
</ContentControl>
</Button>
(I'm omitting the "style" part here and the "BoolToObjectConverter", that should be clear for the purpose)
The problem is that, when I run the program, the button gets correctly updated, but the icon is not visible. This is because the "icon_stop" resource is not updated with the correct "Fill" color. In fact, if I force it (for example to "Black") the icon shows correctly.
It seems to me that the "RelativeSource" bounded property gets not updated.
I think this is related to the fact that the resource is a "StaticResource" and thus gets created only once on the application loading.
I've found that there are also "DynamicResource" references in WPF, but I can't use them here (or at least I can't see how now as them must be bound to a dependency property and I have the value converter here).
How can I solve this?
Your XAML seems overly complicated. You may try something simple like this:
<Style TargetType="Button" x:Key="PlayButtonStyle">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Path Fill="{Binding Foreground,
RelativeSource={RelativeSource AncestorType=Button}}"
Data="{Binding}"/>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Content" Value="M5,0 L25,15 5,30Z"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsRunning}" Value="True">
<Setter Property="Content">
<Setter.Value>
<RectangleGeometry Rect="0,0,30,30"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
Try it like this:
<Button Style="{StaticResource PlayButtonStyle}" Foreground="Red" />

Style trigger based on parent style property

I want to have buttons with little icons as content.
The icons are defined and stored in a ResourceDictionary like this:
<Path x:Key="BackIcon" Data="F1 M 57,42L 57,34L 32.25,34L 42.25,24L 31.75,24L 17... "/>
<Path x:Key="LoadFromFileIcon" Data="F1 M 48,39L 56,39L 56,49L 63.25,49L 52,60.2... "/>
<Path x:Key="SaveToFileIcon" Data="F1 M 48,60L 56,60L 56,50L 63.25,50L 52,38.75L... "/>
Since I also need to provide Path.Fill, Path.Stretch etc. properties, I decided to make my own IconButtonStyle so I won't have to repeat the same attribute in every Path in the icon dictionary and make buttons very easily like this:
<Button Style="{StaticResource IconButtonStyle}"
Content="{StaticResource ResetIcon}"/>
This is what I came up with:
<Style x:Key="IconButtonStyle" TargetType="Button">
<Style.Resources>
<Style TargetType="Path">
<Setter Property="Fill" Value="Black"/> <!-- Default path fill. -->
<Style.Triggers>
<!-- How can I set path fill to "Red" based on the parent Button IsEnabled property? -->
</Style.Triggers>
</Style>
</Style.Resources>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="false">
<!-- ?? -->
</Trigger>
</Style.Triggers>
</Style>
My custom icon buttons have default path style defined via Style.Resources. Setting default path fill is easy but I can't figure out how to set a trigger that'll change path's fill to red when the owner button is disabled.
Is it even possible?
Okay... I guess you need to modify a few things before proceeding.
First in XAML you should use a code like this:
<Button Style="{DynamicResource IconButtonStyle}" IsEnabled="False" Content="{StaticResource _rect}">
</Button>
In this case I used a rectangle which I defined in Resources. this is the code for rectangle:
<Rectangle Width="10" Height="10" Style="{StaticResource ContentButtonPathStyle}" x:Key="_rect"></Rectangle>
you will obviously use your paths instead of rectangles...
You notice that the Style is set to ContentButtonPathStyle .
This is the code for that style:
<Style x:Key="ContentButtonPathStyle" TargetType="{x:Type Rectangle}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource=
{RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}, Path=IsEnabled}" Value="False">
<Setter Property="Fill" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource=
{RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}, Path=IsEnabled}" Value="True">
<Setter Property="Fill" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
Note that you must define ContentButtonPathStyle before your rectangle (paths).
The last thing is that you don't even need to specify a Style for your button. unless you need it for other purposes.

WPF Button icon gets mirrored, why?

This Button looks fine (see screenshot) when defining the image as done below. Note that the shield shaped icon with the letter 'T' is visualized correctly.
<Button Command="w:MainWindow.BrowseTorrentSite">
<StackPanel>
<Image Source="../icons/kickasstorrent.png" />
</StackPanel>
</Button>
When I want to rely on the buttons enabled state, the icon gets mirrored.
<StackPanel
Orientation="Horizontal"
FlowDirection="RightToLeft">
<Button
x:Name="KatButton"
Command="w:MainWindow.BrowseTorrentSite">
<StackPanel>
<Image>
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=IsEnabled, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Button}}"
Value="True">
<Setter Property="Source" Value="../icons/kickasstorrent.png" />
</DataTrigger>
<DataTrigger
Binding="{Binding Path=IsEnabled, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Button}}"
Value="False">
<Setter Property="Source" Value="../icons/kickasstorrent_disabled.png" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</StackPanel>
</Button>
</StackPanel>
Note the shield shaped icon with the letter 'T' is now mirrored.
What is responsible for mirroring the icon?
If somebody has tips to debug this in whatever way possible, feel free to point me in the right direction!
The problem is in the parent StackPanel. If the StackPanel is defining the FlowDirection from right to left, the Image definition inherits this property which results in the flipped icon.
To solve this problem redefine left to right on the image itself.
<StackPanel
Orientation="Horizontal"
FlowDirection="RightToLeft">
<Button>
<Image FlowDirection="LeftToRight">
<Image.Style>
<!-- etc etc -->
</Image.Style>
</Image>
</Button>
</StackPanel>

WPF How to attach Popup to simple UIElement like rectangle

It took me hours to figure out the answer to this question, so I thought I would write an FAQ or answer for what I found. (it is based on the following thread Binding Textbox IsFocused to Popup IsOpen plus additional conditions)
I found lots of examples of binding popups to things like toggle buttons and other things that are based on windows chrome and have built in triggers. But in my application I wanted to bind a popup to a simple rectangle with a custom brush fill. I could not find an example on how to have a popup open ans stay open when a user mouses over the rectangle.
So I am posting this question and I will immediately post the answer I found so that hopefully someone else can benefit from it. I will also mark an answer for anyone who can help me understand if stackoverflow allows posts like this, or a better way I could have gone about it.
EDIT 1)
I can't self answer for 8 hours so here is the working code:
the following is a simple example of how to use the popup on a basic UIElement like a rectangle/ellipse/etc...
<Grid HorizontalAlignment="Stretch" Height="Auto">
<Rectangle x:Name="PopupRec"
Grid.Row="0"
Width="20" Height="20"
HorizontalAlignment="Right"
Fill="Gray" Margin="0,0,0,10" />
<Popup x:Name="SortPopup"
PlacementTarget="{Binding ElementName=PopupRec}"
StaysOpen="False"
PopupAnimation="Slide"
AllowsTransparency="True">
<Border Background="White" Padding="15">
<StackPanel Orientation="Vertical">
<Button Command="{Binding MyCommand}" CommandParameter="5">5</Button>
<Button Command="{Binding MyCommand}" CommandParameter="10">10</Button>
<Button Command="{Binding MyCommand}" CommandParameter="15">15</Button>
<Button Command="{Binding MyCommand}" CommandParameter="20">20</Button>
</StackPanel>
</Border>
<Popup.Style>
<Style TargetType="{x:Type Popup}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=PopupRec, Path=IsMouseOver}" Value="True">
<Setter Property="IsOpen" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=SortPopup, Path=IsMouseOver}" Value="True">
<Setter Property="IsOpen" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</Popup.Style>
</Popup>
</Grid>
Just paste this inside window/usercontrol/etc...
I'd suggest the following improvement. It would make the Popup Style independent of any element names, and would thus enable you to use it as a default Style by putting it into the Window's or UserControl's Resources.
<Style TargetType="{x:Type Popup}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsMouseOver,
RelativeSource={RelativeSource Self}}"
Value="True">
<Setter Property="IsOpen" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=PlacementTarget.IsMouseOver,
RelativeSource={RelativeSource Self}}"
Value="True">
<Setter Property="IsOpen" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
And please note that a Rectangle is not a "basic UIElement". It's a Shape, which itself is a FrameworkElement.

How do I change the image when the button is disabled?

I'm trying to show a different image when the button is disabled; I thought it would be easy with triggers.
However, I have not been able to get the image source to switch to the disabled image when the button is disabled. I've tried setting triggers on both the image and button. What is wrong with what I have below? How can I change the image source when the button is enabled/disabled?
<Button
x:Name="btnName"
Command="{Binding Path=Operation}"
CommandParameter="{x:Static vm:Ops.OpA}">
<Button.Content>
<StackPanel>
<Image
Width="24"
Height="24"
RenderOptions.BitmapScalingMode="NearestNeighbor"
SnapsToDevicePixels="True"
Source="/MyAssembly;component/images/enabled.png">
<Image.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=btnName, Path=Button.IsEnabled}" Value="False">
<Setter Property="Image.Source" Value="/MyAssembly;component/images/disabled.png" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</StackPanel>
</Button.Content>
</Button>
Yeah this one pops up quite a bit.
Any property that's explicitly set in the object's declaration can't be changed in a style. So because you've set the image's Source property in the declaration of the image, the style's Setter won't touch it.
Try this instead:
<Image
Width="24"
Height="24"
RenderOptions.BitmapScalingMode="NearestNeighbor"
SnapsToDevicePixels="True"
>
<Image.Style>
<Style TargetType="Image">
<Setter Property="Source"
Value="/MyAssembly;component/images/enabled.png" />
<Style.Triggers>
... your trigger and setter ...
</Style.Triggers>
</Style>
</Image.Style>
</Image>

Resources