Creating a WPF ValueConverter for a Brush - wpf

On the Nerd Plus Art blog today, there was a post about creating WPF Resources for arrows, which the author uses frequently. I have a side project that has Back and Forward buttons, so I thought that the Left and Right arrows would work great on those buttons.
I added the LeftArrow and RightArrow Geometries to my application's resources, and then used them as the content of the buttons:
<Application x:Class="Notes.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
<Geometry x:Key="RightArrow">M0,0 L1,0.5 0,1Z</Geometry>
<Geometry x:Key="LeftArrow">M0,0.5 L1,1 1,0Z</Geometry>
</Application.Resources>
</Application>
<Button x:Name="BackButton"
Padding="5,5,5,5"
Command="{x:Static n:Commands.GoBackCommand}">
<Path Data="{StaticResource LeftArrow}" Width="10" Height="8"
Stretch="Fill" Fill="Black"/>
</Button>
<Button x:Name="ForwardButton"
Padding="5,5,5,5"
Command="{x:Static n:Commands.GoForwardCommand}">
<Path Data="{StaticResource RightArrow}" Width="10" Height="8"
Stretch="Fill" Fill="Red" />
</Button>
That worked, except that the arrows were drawn in black regardless of whether the button was enabled or not. So, I created a ValueConverter to go from a bool to a Brush:
class EnabledColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
bool b = (bool)value;
return b ? Brushes.Black : Brushes.Gray;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
}
(I realize that I should probably use system colors instead of hard coded black and gray, but I just wanted to get this working, first.)
I modified the Fill property of the Path to use my converter (which I created within the application's resources):
<Path Data="{StaticResource LeftArrow}" Width="10" Height="8"
Stretch="Fill"
Fill="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Button}, Path=IsEnabled, Converter={StaticResource EnabledColorConverter}}"/>
Unfortunately, this doesn't work, and I'm not sure why. When I run it, the arrow isn't drawn at all. I checked the Output window in Visual Studio, and no binding errors were displayed. I also verified that the bool is the right value in the converter, based on the whether the button should be enabled or not.
If I change the Path back to a TextBlock (and bind its Foreground property in the same manner as Path.Fill), the text is always drawn in black.
Am I doing something wrong? Why is the Brush returned by my converter not used to render the Path in the button?

For this kind of UI state updates, try using triggers instead; it'll save you from writing a value converter, and it's a lot shorter to write.
Try this:
<Application x:Class="Notes.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
<Geometry x:Key="RightArrow">M0,0 L1,0.5 0,1Z</Geometry>
<Geometry x:Key="LeftArrow">M0,0.5 L1,1 1,0Z</Geometry>
<!-- Base Arrow Style, with a trigger to toggle it Gray when its parent button is disabled -->
<Style x:Key="ArrowStyle" TargetType="Path">
<Setter Property="Width" Value="10"/>
<Setter Property="Height" Value="8"/>
<Setter Property="Stretch" Value="Fill"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Button}, Path=IsEnabled}" Value="False">
<Setter Property="Fill" Value="Gray"/>
</DataTrigger>
</Style.Triggers>
</Style>
<!-- Left Arrow Style, with the Left Arrow fill and data -->
<Style x:Key="LeftArrowStyle" BasedOn="{StaticResource ArrowStyle}" TargetType="Path">
<Setter Property="Fill" Value="Black"/>
<Setter Property="Data" Value="{StaticResource LeftArrow}"/>
</Style>
<!-- Right Arrow Style, with the Right Arrow fill and data -->
<Style x:Key="RightArrowStyle" BasedOn="{StaticResource ArrowStyle}" TargetType="Path">
<Setter Property="Fill" Value="Red"/>
<Setter Property="Data" Value="{StaticResource RightArrow}"/>
</Style>
</Application.Resources>
<Button x:Name="BackButton" Padding="5,5,5,5" IsEnabled="False">
<Path Style="{StaticResource LeftArrowStyle}"/>
</Button>
<Button x:Name="ForwardButton" Padding="5,5,5,5">
<Path Style="{StaticResource RightArrowStyle}"/>
</Button>
</Application>
Then, you set your default fill in LeftArrowStyle and RightArrowStyle, for the left and right arrows, respectively. If you set it on the Path itself, then that value would take precedence and override anything a style or its trigger may do. The base style, ArrowStyle, contains a DataTrigger bound to the parent button - it fires whenever IsEnabled is false, and changes the Path's Fill to Gray.

Why no just bind the Fill of your Path to the Foreground of the Button?
<Button x:Name="BackButton"
Padding="5,5,5,5"
Command="{x:Static n:Commands.GoBackCommand}">
<Path Data="{StaticResource LeftArrow}" Width="10" Height="8"
Stretch="Fill" Fill="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Button}, Path=Foreground"/>
</Button>

Your code essentially works. I think your static resource may be wrong as your not specifying where this is getting the converter from.
You probably need
<Window.Resources>
<conv:EnabledColorConverter x:Key="brushConv" />
</Window.Resources>
and then specify your binding as:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Button}, Path=IsEnabled, Converter={StaticResource brushConv}}

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" />

How can I parameterise a WPF button's Content's child element attribute?

My application (using MahApps.Metro) has a lot of <Button> elements with Content that all have identical markup except for varying attributes on the <Button> itself, as well as a single Button.Content child element attribute that I want to parameterize through a new attribute (an Attached Property?) on the <Button>.
I have about 20 instances of this XAML for a button:
<Button
Style="{StaticResource MetroCircleButtonStyle}"
Margin="0,-4,-4,2"
DockPanel.Dock="Right"
Width="32"
Height="32"
Name="targetSystemAutoconfigureButton"
ToolTip="{x:Static me:Resources.Settings_AutoconfigureTargetSystem}"
Command="{Binding AutoconfigureTargetSystemCommand}"
>
<Button.Content>
<Rectangle Fill="{Binding Foreground, ElementName=targetSystemAutoconfigureButton}" Width="12" Height="12">
<Rectangle.OpacityMask>
<DrawingBrush Drawing="{StaticResource appbar_magnify}" Stretch="Uniform" />
</Rectangle.OpacityMask>
</Rectangle>
</Button.Content>
</Button>
I've already shrunk it down by moving the constant properties to a derived style:
<!-- In a <ResourceDictionary> located elsewhere -->
<Style x:Key="inputSplitIconButton" TargetType="Button" BasedOn="{StaticResource MetroCircleButtonStyle}">
<Setter Property="Margin" Value="0,-4,-4,2" />
<Setter Property="DockPanel.Dock" Value="Right" />
<Setter Property="Width" Value="32" />
<Setter Property="Height" Value="32" />
</Style>
<!-- Button XAML is now: -->
<Button
Style="{StaticResource inputSplitIconButton}"
Name="targetSystemAutoconfigureButton"
ToolTip="{x:Static me:Resources.Settings_AutoconfigureTargetSystem}"
Command="{Binding AutoconfigureTargetSystemCommand}"
>
<Button.Content>
<Rectangle Fill="{Binding Foreground, ElementName=targetSystemAutoconfigureButton}" Width="12" Height="12">
<Rectangle.OpacityMask>
<DrawingBrush Drawing="{StaticResource appbar_magnify}" Stretch="Uniform" />
</Rectangle.OpacityMask>
</Rectangle>
</Button.Content>
</Button>
But I want to have just this:
<Button
Style="{StaticResource inputSplitIconButton}"
Name="targetSystemAutoconfigureButton"
ToolTip="{x:Static me:Resources.Settings_AutoconfigureTargetSystem}"
Command="{Binding AutoconfigureTargetSystemCommand}"
ImageMask="{StaticResource appbar_magnify}" <--- this property
/>
A problem with WPF is that there's at least three different ways to accomplish the same end result, however I don't know WPF well enough to choose the best at this point. I know my options are:
Set the Content property in the <Style x:Key="inputSplitIconButton">.
But how do I parameterize the Drawing="" attribute?
Set the DataTemplate property in the <Style x:Key="inputSplitIconButton"> and use a DataContext binding for the Drawing="" attribute, and pass that in as the new DataContext for that button instance
But this means I can't use my existing Bindings.
Along the lines of adding a DataTemplate, there are variations-on-a-theme:
Use an Attached Property to set the attribute in the DataTemplate
Abuse the Tag property to store the StaticResource name.
Subclass Button and add my own properties there and create the content structure in code.
This will be very painful and is very non-idiomatic WPF.
Define an Attached Property ("me:Drawing=""" for example), that when set on an element automatically adds the <Rectangle>... etc child content.
Is this "correct" and idiomatic WPF? How does an Attached Property get to manipulate the markup of its applied element?
I'd create a UserControl with your button.
All of your button xaml moves into the UserControl.
Any parameters that need to be set dynamically become dependency properties on the UserControl.
I'd add example code to this answer, but I'm not sure it would benefit you at all. Hopefully right now you're having a lightbulb moment, and see how this works out. I've employed this pattern multiple times to solve exactly the problem you're describing, and it fits perfectly.
You could even go so far as to drop in a ContentPresenter control inside of the content of the button, and expose that control's Content property as another dependency property for your binding pleasure. Bind whatever you like to it: more text, a rectangle, etc... or nothing at all.
Feel free to comment and I'm happy to flesh this out more...
I came to a solution by using a DataTemplate, with an Attached Property to set the image name as a string, with IValueConverter to map from string names to Drawing object resources for the binding to the OpacityMask.
With thanks to these resources:
WPF Attached Property Data Binding
WPF use binding to assign static resource
Resources.xaml:
<!-- Note the ordering of elements is important -->
<me:StringToStaticResourceConverter x:Key="ssr" />
<DataTemplate x:Key="inputSplitIconButtonContentTemplate">
<Rectangle Fill="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Button}}" Width="12" Height="12">
<Rectangle.OpacityMask>
<DrawingBrush Drawing="{Binding Path=(me:Buttons.Image), Mode=OneWay, Converter={StaticResource ssr}, RelativeSource={RelativeSource AncestorType=Button}}" Stretch="Uniform" />
</Rectangle.OpacityMask>
</Rectangle>
</DataTemplate>
<Style x:Key="inputSplitIconButton" TargetType="Button" BasedOn="{StaticResource MetroCircleButtonStyle}">
<Setter Property="Margin" Value="0,-4,-4,2" />
<Setter Property="DockPanel.Dock" Value="Right" />
<Setter Property="Width" Value="32" />
<Setter Property="Height" Value="32" />
<Setter Property="TabIndex" Value="10" />
<Setter Property="ContentTemplate" Value="{StaticResource inputSplitIconButtonContentTemplate}" />
</Style>
AttachedProperties.cs:
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace MyProject
{
public static class Buttons
{
public static readonly DependencyProperty ImageProperty = DependencyProperty.RegisterAttached
(
name : "Image",
propertyType : typeof(String),
ownerType : typeof(Buttons),
defaultMetadata: new FrameworkPropertyMetadata( defaultValue: null, flags: FrameworkPropertyMetadataOptions.AffectsRender )
);
public static void SetImage(UIElement element, String value)
{
element.SetValue( ImageProperty, value );
}
public static String GetImage(UIElement element)
{
return (String)element.GetValue( ImageProperty );
}
}
public class StringToStaticResourceConverter : IValueConverter
{
public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture)
{
return Application.Current.FindResource( value );
}
public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture)
{
return null;
}
}
}
Window.xaml:
<Button
Style="{StaticResource inputSplitIconButton}"
ToolTip="{x:Static me:Resources.Settings_BrowseFile}"
me:Buttons.Image="appbar_folder_ellipsis_drawing"
/>
It even works in the WPF XAML designer too!
I think this could be simplified by using {StaticResource appbar_folder_ellipsis_drawing} in Window.xaml and just passing that through... time to experiment!
Update: Simplified:
I was able to simplify my solution further and eliminate the StringToStaticResourceConverter when I realised that the {StaticResource key} works fine with Attached Properties too.
In Resources.xaml:
Remove this line: <me:StringToStaticResourceConverter x:Key="ssr" />
The <DrawingBrush line becomes this: <DrawingBrush Drawing="{Binding Path=(me:Buttons.Image), Mode=OneWay, RelativeSource={RelativeSource AncestorType=Button}}" Stretch="Uniform" />
In Window.xaml:
Change the attached property attribute value to the {StaticResource} like so: me:Buttons.Image="{StaticResource appbar_folder_ellipsis_drawing}".

Change Background color to a Visual pattern based

I have a list of elements (simple buttons with plain textblock) which are color coded based on the list item content. User can update the Listitem and thus listitem color should change. For certain listitem background colors like "Red", I want to add a pattern as well.
I have added the following VisualPatterns in XAML:
<Window.Resources>
<VisualBrush x:Key="FwdPattern" TileMode="Tile" Viewport="0,0,15,15" ViewportUnits="Absolute" Viewbox="0,0,15,15" ViewboxUnits="Absolute">
<VisualBrush.Visual>
<Grid>
<Path Data="M 0 15 L 15 0" Stroke="Gray" />
</Grid>
</VisualBrush.Visual>
</VisualBrush>
<VisualBrush x:Key="BckPattern" TileMode="Tile" Viewport="0,0,15,15" ViewportUnits="Absolute" Viewbox="0,0,15,15" ViewboxUnits="Absolute">
<VisualBrush.Visual>
<Grid>
<Path Data="M 0 0 L 15 15" Stroke="Gray" />
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Window.Resources>
Button template used in ListItem is:
<Border Background="{Binding BackgroundClr}">
<Button Name="MyButton" Content="Testing">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="{Binding BackgroundClr}"/>
<Style.Triggers>
<!-- This does not work, see [http://stackoverflow.com/questions/39583263/brush-mvvm-binding-does-not-give-named-color/39583422#39583422][1] -->
<DataTrigger Binding="{Binding BackgroundClr}" Value="Red">
<Setter Property="Background" Value="{StaticResource BckPattern}"/>
</DataTrigger>
<!-- This does not work either, it goes in infinite loop
and StackOverflow exception is thrown-
probably because I am reading the background color in
the datatrigger and again updating it- but i dont know-->
<DataTrigger Binding="{Binding Background.Color, RelativeSource={RelativeSource Self}}" Value="Red">
<Setter Property="Background" Value="{StaticResource BckPattern}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Border>
Currently I have no other knowledge except the Button BackgroundClr from VM to determine if I need to provide a pattern or not.
Tried Solutions
One solution is to have a bound property- PatternName and based on it, determine which pattern to apply:
The above code works, but I have to have an additional property in VM
The other solution is to access VisualBrush in VM and directly apply the pattern in BackgroundClr - i have not figured out how to do this yet.
Which is a better solution or there any other way to achieve the same?
Thanks,
RDV
Change {Binding BackgroundClr} to {Binding BackgroundClr.Color}.

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>

Setter in property trigger fails if target property already has an explicit value

I'm currently trying to create a ControlTemplate for the Button class in WPF, replacing the usual visual tree with something that makes the button look similar to the little (X) close icon on Google Chrome's tabs. I decided to use a Path object in XAML to achieve the effect. Using a property trigger, the control responds to a change in the IsMouseOver property by setting the icon's red background.
Here's the XAML from a test app:
<Window x:Class="Widgets.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style x:Key="borderStyle" TargetType="Border">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="#CC0000"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
<ControlTemplate x:Key="closeButtonTemplate" TargetType="Button">
<Border Width="12" Height="12" CornerRadius="6"
BorderBrush="#AAAAAA" Background="Transparent"
Style="{StaticResource borderStyle}"
ToolTip="Close">
<Viewbox Margin="2.75">
<Path Data="M 0,0 L 10,10 M 0,10 L 10,0" Stroke="{Binding BorderBrush, RelativeSource={RelativeSource FindAncestor, AncestorType=Border, AncestorLevel=1}}" StrokeThickness="1.8"/>
</Viewbox>
</Border>
</ControlTemplate>
</Window.Resources>
<Grid Background="White">
<Button Template="{StaticResource closeButtonTemplate}"/>
</Grid>
</Window>
Note that the circular background is always there - it's just transparent when the mouse isn't over it.
The problem with this is that the trigger just isn't working. Nothing changes in the button's appearance. However, if I remove the Background="Transparent" value from the Border object in the ControlTemplate, the trigger does work (albeit only when over the 'X').
I really can't explain this. Setters for any other properties placed in the borderStyle resource work fine, but the Background setter fails as soon as the default background is specified in the ControlTemplate.
Any ideas why it's happening and how I can fix it? I know I could easily replace this code with, for example, a .PNG-based image, but I want to understand why the current implementation isn't working.
Thanks! :)
Try moving the explict "Background" assignment from inside the Border declaration to the Style itself:
<Style x:Key="borderStyle" TargetType="Border">
<Setter Property="Background" Value="Transparent" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="#CC0000"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
...
<Border Width="12" Height="12" CornerRadius="6"
BorderBrush="#AAAAAA"
Style="{StaticResource borderStyle}"
ToolTip="Close">
Styles can't override a property that has been explicitly set. You need to set the value in the style.
I think your problem is that the Border does not 'catch' the mouse events when it is transparant.
To verify - try changing the background to #01FFFFFF instead of Transparent.

Resources