Style trigger based on parent style property - wpf

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.

Related

How to change the foreground color of all command buttons in MahApps

Is there a way to change buttons foreground in MetroWindow?
I have even tried to override the IronicallyNamedChromelessButtonStyle but the foreground color was still the same.
Edit:
The buttons are in the Window Bar (e.g Close, Minimize, Maximize).
After a deep dive into the MahApps codе... Here is the source which is responsible for the buttons:
https://github.com/MahApps/MahApps.Metro/blob/master/MahApps.Metro/Themes/MetroWindow.xaml
If you look carefully, you will notice that every style has triggers that override the style foreground with hard-coded "White":
<Style.Triggers>
<DataTrigger Binding="{Binding ShowTitleBar, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Controls:MetroWindow}}}"
Value="True">
<Setter Property="Foreground"
Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding ShowTitleBar, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Controls:MetroWindow}}}"
Value="False">
<Setter Property="Background"
Value="Transparent" />
</DataTrigger>
</Style.Triggers>
My solution was to override all necessary style triggers:
<Style TargetType="{x:Type MahControls:WindowButtonCommands}">
<Style.Triggers>
<DataTrigger Binding="{Binding ShowTitleBar, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MahControls:MetroWindow}}}"
Value="True">
<Setter Property="Foreground"
Value="{StaticResource IdealForegroundColorBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
Hope this will help anyone in my case.
Special thanks to #Rui and #Sankarann for the ideas and help.
If anyone have a better solution, please share it.
You can use an implicit style. An implicit style is a style that does not have a "key", for example, if you add this style in Application.Resources it will affect all buttons in your application:
<Application.Resources>
<Style TargetType="Button">
<Setter Property="Foreground" Value="Blue"/>
</Style>
</Application.Resources>
You can set it in, for example, a Grid's Resources and it will affect only the Buttons inside that Grid.
Yes your right the colours are applied in appbar_ canvas via
Fill="{DynamicResource BlackBrush}"
So as your not in control of BlackBrush you cant really apply a SolidColorBrush to it as some other control in the MahApps Library will overwite your setting.
You need to point NuGet to the Loose resources file (so you get an Icons.xaml file to play with locally)
Copy the appbar icons you want to change color (maybe create a resource dictionary called MyIcons.xaml and save them in there, adding MyIcons.xaml to your App.xaml MergedDictionaries)
Then in MyIcons.xaml define your icon (with its new colour too) :
<SolidColorBrush x:Key="IconBrushOrange" Color="Orange" />
<Canvas x:Key="my_connecting" All other fields...>
<Path Stretch="Fill" Fill="{StaticResource IconBrushOrange}" All other fields.... />
</Canvas>
Then in your UI :
<Rectangle Width="20" Height="20">
<Rectangle.Fill>
<VisualBrush Stretch="Fill" Visual="{StaticResource my_connecting}" />
</Rectangle.Fill>
</Rectangle>
Just redefine the Brush in window resources:
<SolidColorBrush x:Key="MahApps.Brushes.IdealForeground" Color="WhateverColor" />

Binding Path Fill to Button Foreground in ContentPresenter

I have a Button Style with a Template containing a ContentPresenter, in which I am attempting to bind the Fill of a Path to the Foreground of a button:
<!-- This is inside the template of a button style -->
<ContentPresenter>
<ContentPresenter.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="Fill" Value="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
I also have a Path with no Fill set, that I can reference in the button as the content, like so:
<Button Style="{DynamicResource MyButtonStyle}" Content="{DynamicResource PathIcon}" Foreground="Blue"/>
I would expect the Path inside the button to be blue, but it isn't... it doesn't grab the foreground from the button.
How can I get the Path to bind to the color of the button?
Thank you!
P.S.:
If I put a hardcoded color in the Value (i.e. Value="Red"), the Path inside the button is red... so I know that works...
<ContentPresenter>
<ContentPresenter.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="Fill" Value="Red"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
Edit:
Here is the complete Style and ControlTemplate:
<Style x:Key="Button_Style" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="{StaticResource White_Brush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid x:Name="grid" Background="Transparent">
<ContentPresenter>
<ContentPresenter.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="Fill" Value="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<!-- Should affect Text as well as Paths in the Content property of the button! -->
<Setter Property="Foreground" Value="{StaticResource Black_Brush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Okay, let's order:
it doesn't grab the foreground from the button.
In styles this construction:
RelativeSource={RelativeSource AncestorType=Button}
will not work, because the Style is just the collection of setters, he does not know about control, are there, specifically about the content of the visual tree. Because RelativeSource should refer to the items above in the visual tree. For this purpose, usually using DataTemplate or ControlTemplate.
If I put a hardcoded color in the Value (i.e. Value="Red")
Yes, in this case, will be working, and always better to create the design of the form:
<SolidColorBrush x:Key="MyButtonColor" Color="Blue" />
And use it for control, like Button:
<Button Background="{StaticResource MyButtonColor}" ... />
and in Style or elsewhere:
<Setter Property="Fill" Value="{StaticResource MyButtonColor}" />
That is, it is better not to depend on the element parameters (background color, etc.) located in a visual tree, because it can:
May move to another panel (Grid, StackPanel) or UserControl
May leave from the project
And brushes in the as resources will always be in one place, changing them in this place, all the elements of their pick up. Also colors can be stored in a special data model that does not depend on the specific technical implementations (resources, variables) in which the data can come from an external source, such as the project/config settings.
If possible, it is better to avoid the use of dynamic resources due to unnecessary use of system perfomance (and in some cases memory leaks), in your cases they are not needed.
Dynamic resources are usually explicitly defined for SolidColorBrush and another species brushes, because by default they are frozen, and they not recommended changed because of the above mentioned reasons (memory leaks). More information can be found here:
Freezable Objects Overview on MSDN
Edit
As I understand it, you want to make universal Style for Button to make the contents of Path or Text (in the case of simultaneous use will be easier). As I have already mentioned above, RelativeSource should be around ControlTemplate, therefore, the Path will be in the Grid with the ContentPresenter.
To style knew, which is provided for the text or for the path, to the Tag (optional property) indicates two properties: OnlyText or OnlyPath.
To set the data for the Path, I've created a attached dependency property, and prescribed it in the ControlTemplate.
Below is a complete example:
XAML
<Window x:Class="ButtonPathHelp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ButtonPathHelp"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<SolidColorBrush x:Key="Green_Brush" Color="Green" />
<SolidColorBrush x:Key="Black_Brush" Color="Black" />
<Style x:Key="Button_Style" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="{StaticResource Green_Brush}" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid x:Name="grid">
<ContentPresenter x:Name="MyContent"
Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}" />
<Path x:Name="MyPath"
SnapsToDevicePixels="True"
Width="20"
Height="18"
Stretch="Fill"
Fill="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
Data="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(local:MyDependencyClass.DataForPath)}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="{StaticResource Black_Brush}"/>
</Trigger>
<Trigger Property="Tag" Value="OnlyText">
<Setter TargetName="MyPath" Property="Visibility" Value="Collapsed" />
<Setter TargetName="MyContent" Property="Visibility" Value="Visible" />
</Trigger>
<Trigger Property="Tag" Value="OnlyPath">
<Setter TargetName="MyPath" Property="Visibility" Value="Visible" />
<Setter TargetName="MyContent" Property="Visibility" Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<WrapPanel>
<WrapPanel.Resources>
<sys:String x:Key="Save">
F1 M 20.5833,20.5833L 55.4167,20.5833L 55.4167,55.4167L 45.9167,55.4167L 45.9167,44.3333L 30.0833,44.3333L 30.0833,
55.4167L 20.5833,55.4167L 20.5833,20.5833 Z M 33.25,55.4167L 33.25,50.6667L 39.5833,50.6667L 39.5833,55.4167L 33.25,
55.4167 Z M 26.9167,23.75L 26.9167,33.25L 49.0833,33.25L 49.0833,23.75L 26.9167,23.75 Z
</sys:String>
<sys:String x:Key="Search">
F1 M 23.4454,49.2637L 31.7739,41.1598C 30.6986,39.2983 30.4792,37.1377 30.4792,34.8333C 30.4792,27.8377 35.7544,
22.1667 42.75,22.1667C 49.7456,22.1667 55.4167,27.8377 55.4167,34.8333C 55.4167,41.8289 49.7456,47.1042 42.75,
47.1042C 40.5639,47.1042 38.5072,46.9462 36.7125,45.9713L 28.3196,54.1379C 27.0829,55.3746 24.6821,55.3746 23.4454,
54.1379C 22.2088,52.9013 22.2088,50.5004 23.4454,49.2637 Z M 42.75,26.9167C 38.3777,26.9167 34.8333,30.4611 34.8333,
34.8333C 34.8333,39.2056 38.3777,42.75 42.75,42.75C 47.1222,42.75 50.6667,39.2056 50.6667,34.8333C 50.6667,
30.4611 47.1222,26.9167 42.75,26.9167 Z
</sys:String>
</WrapPanel.Resources>
<Button Name="SaveButton"
Style="{StaticResource Button_Style}"
Tag="OnlyPath"
local:MyDependencyClass.DataForPath="{StaticResource Save}"
Margin="10" />
<Button Name="JustText"
Style="{StaticResource Button_Style}"
Tag="OnlyText"
Content="Just Text"
Margin="10" />
<Button Name="SearchButton"
Style="{StaticResource Button_Style}"
Tag="OnlyPath"
local:MyDependencyClass.DataForPath="{StaticResource Search}"
Margin="10" />
</WrapPanel>
</Window>
Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class MyDependencyClass : DependencyObject
{
#region IsCheckedOnDataProperty
public static readonly DependencyProperty DataForPathProperty;
public static void SetDataForPath(DependencyObject DepObject, string value)
{
DepObject.SetValue(DataForPathProperty, value);
}
public static string GetDataForPath(DependencyObject DepObject)
{
return (string)DepObject.GetValue(DataForPathProperty);
}
#endregion
static MyDependencyClass()
{
PropertyMetadata MyPropertyMetadata = new PropertyMetadata(String.Empty);
DataForPathProperty = DependencyProperty.RegisterAttached("DataForPath",
typeof(string),
typeof(MyDependencyClass),
MyPropertyMetadata);
}
}
Note: In the Style I have not used TemplateBinding for attached property, because TemplateBinding doesn’t work outside a template or outside its VisualTree property, so you can’t even use TemplateBinding inside a template’s trigger. Therefore, we must use the construction {RelativeSource TemplatedParent} and a Path equal to the dependency property whose value you want to retrieve.
Output
To download the entire example please follow this link.
I stumbled across simillar problem but was wondering how to get to the 'Foreground Colour' of the Button in its DISABLED state (to have correct colour of my drawing). Here is a finally simple sollution. No templates, No styles, no code, nothing at all. Just the right relative binding :-) :
<StackPanel Orientation="Horizontal">
<Button Height="22" IsEnabled="False">
<Polygon Points="4,0 4,5 5,5 2.5,10 0,5 1,5 1,0 "
Fill="{Binding (TextElement.Foreground), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}}">
<Polygon.LayoutTransform>
<RotateTransform Angle="90"></RotateTransform>
</Polygon.LayoutTransform>
</Polygon>
</Button>
<Button Height="22" IsEnabled="True">
<Polygon Points="4,0 4,5 5,5 2.5,10 0,5 1,5 1,0 "
Fill="{Binding (TextElement.Foreground), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}}">
<Polygon.LayoutTransform>
<RotateTransform Angle="180"></RotateTransform>
</Polygon.LayoutTransform>
</Polygon>
</Button>
</StackPanel>

How to use the Calendar's Display Mode as a DataTrigger's Value

So I need to know how to set up a xmlns to let me use the CalendarMode in a Trigger's value.
I have tried using xmlns:cal="clr-namespace:System.Windows.Controls", xmlns:cal="clr-namespace:System.Windows.Controls.Calendar" and I've built the project each time, but I got error telling me that the CLR namespace is undefined and cannot be found.
Here is where I used it
<DataTrigger Binding="{Binding Source=_Calendar, Path=Calendar.DisplayMode}">
<DataTrigger.Value>
<cal:CalendarMode>Month</cal:CalendarMode>
</DataTrigger.Value>
<Setter Property="Grid.Opacity" Value="1" />
</DataTrigger>
I guess I could just listen to the DisplayModeChanged event on the calendar but since I've been searching online for this solution all day, I'd really like to know how I can approach this problem in this way.
Any input will be highly appreciated. Thanks!
actually I did not understand exactly what you need. But I'll try to help.
the definition we see:
then able to use the xaml we have to do:
xmlns:presentation="clr-namespace:System.Windows.Controls;assembly=PresentationFramework"
Now, if you want something to happen with a dependency property of the own control, you should use Triggers and not DataTriggers
Sample:
<Calendar Height="170" HorizontalAlignment="Left" Margin="83,112,0,0" Name="calendar1" VerticalAlignment="Top" Width="180">
<Calendar.Style>
<Style TargetType="Calendar">
<Setter Property="Opacity" Value="0.4"/>
<Style.Triggers>
<Trigger Property="SelectionMode" Value="{x:Static presentation:CalendarMode.Month}">
<Setter Property="Opacity" Value="1.0"/>
</Trigger>
</Style.Triggers>
</Style>
</Calendar.Style>
</Calendar>
Normally DataTriggers are used for objects created by you, which implementational INotifyPropertyChanged. Do not mess.
Now, if you want to change another control (when CalendarMode changes) you should do:
<Calendar Height="170" HorizontalAlignment="Left" Margin="83,112,0,0"
Name="calendar1" VerticalAlignment="Top" Width="180"/>
<Grid>
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Opacity" Value="0.5"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=calendar1, Path=CalendarMode}">
<DataTrigger.Value>
<presentation:CalendarMode>Month</presentation:CalendarMode>
</DataTrigger.Value>
<Setter Property="Opacity" Value="1.0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
I suggest you read more about triggers, datatriggers and bindings.

Trigger on an Inner/Attached property

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>

Trigger doesnt want to work. However it does work well when in style

<Button Name="btnNewGame" Margin="120,292,450,180" Style="{StaticResource mainLobbyBtnStyle}">
<Grid Height="35" Width="200">
...
<Line Name="lineNewGame" X1="200" X2="200" Y1="0" Y2="35" ... />
</Grid>
<Button.Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Content" Value="qwerty"/>
</Trigger>
</Button.Triggers>
</Button>
I get "'IsMouseOver' member is not valid because it does not have a qualifying type name". Need help in fixing this.
And second question.
Can i change parameters of lineNewGame in my button trigger? How?
#NDQuattro, why would that suck? By adding 2 more lines to your code I got it working for me....
<Button Name="btnNewGame" Margin="120,292,450,180">
<Grid Height="35" Width="200">
<Line Name="lineNewGame" X1="200" X2="200" Y1="0" Y2="35" ... />
</Grid>
<Button.Style>
<Style TargetType="{x:Type Button}"
BasedOn="{StaticResource mainLobbyBtnStyle}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Content" Value="qwerty"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
To answer your first question:
You could use UIElement.IsMouseOver and Button.Content to avoid this error message. But then you would have the next problem: "Triggers collection members must be of type EventTrigger".
In FrameworkElement you can use:
EventTrigger
In Style, ControlTemplate, DataTemplate you can use:
EventTrigger,
Trigger or MultiTrigger,
DataTrigger or MultiDataTrigger
Regular Triggers as well as DataTriggers are meant to be in a Style, period.
The only kind of trigger you can use directly as you're doing in your example, is EventTrigger, you then need to define an animation (in a storyboard) that will run when an event fires.
You can do it straight in though:
<Button>
<Button.Style>
<Style Target="Button">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Content" Value="qwerty"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>

Resources