I want to change the background color of a button in its normal, moused-over and pressed states to various shades of green. I am forced to add the following grotesquely verbose Button.Template
to my button so that I can modify the RenderMouseOver
and RenderPressed
attributes to use Binding
instead of TemplateBinding
, so that my triggers (in the Button.Style
) actually take effect instead of being ignored due to the compile time nature of TemplateBinding
. Is there anyway to override these two attributes more cleanly for an Aero theme than to repeat the entire template binding in its entirety? XAML follows:
<Window x:Class="ButtonMouseOver.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button>
Hello
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Microsoft_Windows_Themes:ButtonChrome SnapsToDevicePixels="true"
x:Name="Chrome" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" RenderDefaulted="{TemplateBinding IsDefaulted}"
RenderMouseOver="{Binding IsMouseOver}" RenderPressed="{Binding IsPressed}"
>
<ContentPresenter Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Microsoft_Windows_Themes:ButtonChrome>
</ControlTemplate>
</Button.Template>
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="LightGreen"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property = "Background" Value="Green"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property = "Foreground" Value="DarkGreen"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</Window>
Important note: The control template is necessary only for the Aero theme. For other themes, just having the Button.Style alone will do the job.
The best I can think of is to add an attached behavior which disables this with reflection once the Button has Loaded. I don't know if you like this better than to re-template it, but you won't have to include PresentationFramework.Aero and its re-usable
<Button behaviors:DisableButtonChromeBehavior.DisableButtonChrome="True"
...>
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="LightGreen"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="Green"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Foreground" Value="DarkGreen"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
The DisableButtonChromeBehavior
public static class DisableButtonChromeBehavior
{
public static readonly DependencyProperty DisableButtonChromeProperty =
DependencyProperty.RegisterAttached
(
"DisableButtonChrome",
typeof(bool),
typeof(DisableButtonChromeBehavior),
new UIPropertyMetadata(false, OnDisableButtonChromePropertyChanged)
);
public static bool GetDisableButtonChrome(DependencyObject obj)
{
return (bool)obj.GetValue(DisableButtonChromeProperty);
}
public static void SetDisableButtonChrome(DependencyObject obj, bool value)
{
obj.SetValue(DisableButtonChromeProperty, value);
}
private static void OnDisableButtonChromePropertyChanged(DependencyObject dpo,
DependencyPropertyChangedEventArgs e)
{
Button button = dpo as Button;
if (button != null)
{
if ((bool)e.NewValue == true)
{
button.Loaded += OnButtonLoaded;
}
else
{
button.Loaded -= OnButtonLoaded;
}
}
}
private static void OnButtonLoaded(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
Action action = () =>
{
object buttonChrome = VisualTreeHelper.GetChild(button, 0);
Type type = buttonChrome.GetType();
PropertyInfo propertyInfo = type.GetProperty("RenderMouseOver");
if (propertyInfo != null)
{
propertyInfo.SetValue(buttonChrome, false, null);
propertyInfo = type.GetProperty("RenderPressed");
propertyInfo.SetValue(buttonChrome, false, null);
propertyInfo = type.GetProperty("RenderDefaulted");
propertyInfo.SetValue(buttonChrome, false, null);
}
};
button.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
}
}
Update
There's also the shorter non re-usable way, just doing this in code behind.
private void Button_Loaded(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
object buttonChrome = VisualTreeHelper.GetChild(button, 0);
PropertyInfo renderMouseOverInfo = buttonChrome.GetType().GetProperty("RenderMouseOver");
if (renderMouseOverInfo != null)
{
renderMouseOverInfo.SetValue(buttonChrome, false, null);
}
}
Or (if you don't mind including Aero)
<Button ...>
<Button.Resources>
<Style TargetType="Microsoft_Windows_Themes:ButtonChrome">
<EventSetter Event="Loaded" Handler="ButtonChrome_Loaded"/>
</Style>
</Button.Resources>
void ButtonChrome_Loaded(object sender, RoutedEventArgs e)
{
ButtonChrome buttonChrome = sender as ButtonChrome;
if (buttonChrome != null)
{
buttonChrome.RenderMouseOver = false;
buttonChrome.RenderPressed = false;
}
}
Other than that, I think you don't have any other outs than the solution you pointed yourself (re-templating).