Infosys Microsoft Alliance and Solutions blog

« Business Insights Webcast: SharePoint 2010 Adoption Made Quicker and Easier with Infosys Solutions (Level 100) | Main | The need for a business applications framework »

WPF - Disabled look for Button

If you check online forums, one of the issues that has caused possibly most grievances is the styling of the WPF button control. You set some property values for things like foreground color and background color and when you run the application things don't always work out. Either the button will show some default animation when having focus or will not use the specified background when is in disabled state.

Recently a colleague was styling a Button control and had a need for custom disabled look and we landed in the same problem. We had set a background color, but when disabled the button will default to the light gray color. Trying various options didn't work out. Interestingly the properties like Foreground or Opacity worked fine. The designer in us said that we can always create a custom style, but the developer in us wanted to know why this doesn't work.

The basic style of create a button template will be to drop a button on the designer surface (in Blend), then right click on it and select "Edit Template / Edit a copy...". This copies the current style along with control template as part of the window's resources. The following is what we get.

<Style x:Key="ButtonFocusVisual">
    <Setter Property="Control.Template">
        <Setter.Value>
            <ControlTemplate>
                <Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="Black" 
                    StrokeThickness="1" StrokeDashArray="1 2"/>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<LinearGradientBrush x:Key="ButtonNormalBackground" EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#F3F3F3" Offset="0"/>
    <GradientStop Color="#EBEBEB" Offset="0.5"/>
    <GradientStop Color="#DDDDDD" Offset="0.5"/>
    <GradientStop Color="#CDCDCD" Offset="1"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ButtonNormalBorder" Color="#FF707070"/>
<Style x:Key="ButtonStyle1" TargetType="{x:Type Button}">
    <Setter Property="FocusVisualStyle" Value="{StaticResource ButtonFocusVisual}"/>
    <Setter Property="Background" Value="{StaticResource ButtonNormalBackground}"/>
    <Setter Property="BorderBrush" Value="{StaticResource ButtonNormalBorder}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    <Setter Property="HorizontalContentAlignment" Value="Center"/>
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="Padding" Value="1"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <Microsoft_Windows_Themes:ButtonChrome x:Name="Chrome" 
                    BorderBrush="{TemplateBinding BorderBrush}" 
                    Background="{TemplateBinding Background}" 
                    RenderMouseOver="{TemplateBinding IsMouseOver}" 
                    RenderPressed="{TemplateBinding IsPressed}" 
                    RenderDefaulted="{TemplateBinding IsDefaulted}" 
                    SnapsToDevicePixels="true">
                    <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                Margin="{TemplateBinding Padding}" 
                                RecognizesAccessKey="True" 
                                SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
                                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                </Microsoft_Windows_Themes:ButtonChrome>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsKeyboardFocused" Value="true">
                        <Setter Property="RenderDefaulted" TargetName="Chrome" Value="true"/>
                    </Trigger>
                    <Trigger Property="ToggleButton.IsChecked" Value="true">
                        <Setter Property="RenderPressed" TargetName="Chrome" Value="true"/>
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Foreground" Value="#ADADAD"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
 

Where comes Microsoft_Windows_Themes from

xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"

.The point of interest to us was the Trigger IsEnabled. We tried two options based on different forum suggestions.

<Trigger Property="IsEnabled" Value="false">
    <Setter Property="Foreground" Value="#ADADAD"/>
    <Setter Property="Background" Value="Green" />
</Trigger>
 

and

<Trigger Property="IsEnabled" Value="false">
    <Setter Property="Foreground" Value="#ADADAD"/>
    <Setter TargetName="Chrome" Property="Background" Value="Green" />
</Trigger>
 

but both didn't work. We then decided to look at the ButtonChrome in PresentationFramework.Aero.dll. The default path for .net 3.0 based assembly is C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0. Loading this in Reflector, we found the following.

ButtonChromeProps.jpg

We can see that the ButtonChrome exposes Background and BorderBrush properties that are template bound in the control template that we created earlier. However there is this other property BackgroundOverlay, which is private and this is where lies the issue. Digging further into this property, we saw that if the control is not enabled, it returns a SolidColorBrush from another private property called CommonDisabledBackgroundOverlay and if digging further into its code, we saw the following

ButtonChromeMethod.jpg

The SolidColorBrush is hard coded and returns RGB of 244,244,244,which is a shade of gray and that's what we see in the button's background when disabled. Since these various properties are private, there is no way we can change it and thus can't override the default behavior. Additionally ButtonChrome itself is sealed class and hence cannot be inherited from, to write a custom class. Hope Microsoft will fix this someday and make BackgroundOverlay as also a public property.

However fortunately all is not lost and there is an easy way to get around this. In Blend, you can use the simple styles for WPF controls controls that are available in Assets > Styles > Simple Styles and you can pick up the SimpleButton from here. Following the same steps as mentioned earlier to create the template, we get the following

<Style x:Key="ButtonStyle2" TargetType="{x:Type Button}" BasedOn="{x:Null}">

    <Setter Property="FocusVisualStyle" Value="{DynamicResource SimpleButtonFocusVisual}"/>

    <Setter Property="Background" Value="{DynamicResource NormalBrush}"/>

    <Setter Property="BorderBrush" Value="{DynamicResource NormalBorderBrush}"/>

    <Setter Property="Template">

        <Setter.Value>

            <ControlTemplate TargetType="{x:Type Button}">

 

                <!-- We use Grid as a root because it is easy to add more elements to customize the button -->

                <Grid x:Name="Grid">

                    <Border x:Name="Border" Background="{TemplateBinding Background}"

                           BorderBrush="{TemplateBinding BorderBrush}"

                           BorderThickness="{TemplateBinding BorderThickness}"

                           Padding="{TemplateBinding Padding}"/>

 

                    <!-- Content Presenter is where the text content etc is placed by the control -->

                    <!-- The bindings are useful so that the control can be parameterized without editing the template -->

                    <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"

                                    Margin="{TemplateBinding Padding}"

                                    VerticalAlignment="{TemplateBinding VerticalContentAlignment}"

                                    RecognizesAccessKey="True"/>

                </Grid>

 

                <!--Each state sets a brush on the Border in the template -->

                <ControlTemplate.Triggers>

                    <Trigger Property="IsKeyboardFocused" Value="true">

                        <Setter Property="BorderBrush" Value="{DynamicResource DefaultedBorderBrush}" TargetName="Border"/>

                    </Trigger>

                    <Trigger Property="IsMouseOver" Value="true">

                        <Setter Property="Background" Value="{DynamicResource MouseOverBrush}" TargetName="Border"/>

                    </Trigger>

                    <Trigger Property="IsPressed" Value="true">

                        <Setter Property="Background" Value="{DynamicResource PressedBrush}" TargetName="Border"/>

                        <Setter Property="BorderBrush" Value="{DynamicResource PressedBorderBrush}" TargetName="Border"/>

                    </Trigger>

                    <Trigger Property="IsEnabled" Value="true"/>

                    <Trigger Property="IsEnabled" Value="false">

                        <Setter Property="Background" Value="{DynamicResource DisabledBackgroundBrush}" TargetName="Border"/>

                        <Setter Property="BorderBrush" Value="{DynamicResource DisabledBorderBrush}" TargetName="Border"/>

                        <Setter Property="Foreground" Value="{DynamicResource DisabledForegroundBrush}"/>

                    </Trigger>

                </ControlTemplate.Triggers>

            </ControlTemplate>

        </Setter.Value>

    </Setter>

</Style>

 

Notice that the Background is set to a DisabledBackgroundBrush that is actually defined in SimpleStyles.xaml. This file gets added to the project when a control from this Simple Styles assets is added to the designer. We can now change the value in SimpleStyles.xaml and get the necessary behavior. Like for example, we can set this as

<SolidColorBrush x:Key="DisabledBackgroundBrush" Color="Green"/>

 

and the button will look green when disabled. Hope this helps.

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Please key in the two words you see in the box to validate your identity as an authentic user and reduce spam.

Subscribe to this blog's feed

Follow us on

Blogger Profiles

Infosys on Twitter