Search code examples
c#wpfc#-4.0wpf-controlswpf-4.0

How do you change TemplateBindings to Bindings in a button's control template, without repeating the whole control template


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.


Solution

  • 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).