3
votes

I'm trying to restyle a few ToggleButtons. Apparently I cannot simply set the background to a new color, because there is a "Control Template" that provides the ToggleButton's visual behavior. So what I need to do is specify in XAML a replacement "ControlTemplate" for the ToggleButton that provides different visual behavior, beyond the simple background color.
Q1. Is this correct?


I figured to start with the "default" controltemplate for the ToggleButton, which I grabbed from here, and then modify it. Actually that is the default ControlTemplate for Silverlight, I guess, and I am not using Silverlight, I'm using WPF. But... The corresponding doc page for WPF does not include a specification of the default controltemplate. It provides "a" ControlTemplate, which is not what I want.
Q2. I'm not sure if it matters that I am using the thing from Silverlight. Does it?


In the Silverlight example, there is an XML namespace prefix of of vsm applied to the VisualStateManager. Apparently the xml namespace is

  xmlns:vsm = "clr-namespace:System.Windows;assembly=System.Windows"  

... but somewhere else I read that this XML namespace "is no longer necessary."

This is all very very confusing.

In the Googlespace, there are references to something called "The WPF toolkit" which I have had prior exposure to - I used it for an autocomplete textbox prior to the release of WPF V4. I am guessing that some of the WPF Toolkit stuff was rolled into WPF for .NET v4.0, and that is why I no longer have to specify the WPF toolkit.
Q3. If someone could confirm that understanding I'd appreciate it.


Ok, now starting with the "default" ControlTemplate for ToggleButton, my first step was to compile it, before making any changes. It does not compile, failing with

c:\dev...\ToggleButtonStyle1.xaml(23,14): error MC3074: The tag 'VisualStateManager.VisualStateGroups' does not exist in XML namespace 'http://schemas.microsoft.com/winfx/2006/xaml/presentation'. Line 23 Position 14.

Clear enough. I then looked at the documentation for specifying VisualStateManager in XAML. It, confusingly enough, specifies two xml namespaces, one of them is the one I actually used.

enter image description here

Q4 Um, which of these am I supposed to use? One of them, I DID use, and it didn't work. The documentation is completely unclear on what it means to specify TWO XML namespaces. (off with their heads!)

I have a reference to PresentationFramework.dll in the project file:

  <ItemGroup>
     ....
    <Reference Include="PresentationFramework" />
  </ItemGroup>

I am not using Visual Studio here; I'm using a text editor. I want to understand how it works, not what buttons to push.

Thanks for any help y'all can provide.


Just a side comment - this all seems very very complicated. All I want to do is change the color of a ToggleButton when it is ON. It really shouldn't be this complicated.

2

2 Answers

5
votes

You don't need to specify a namespace for the VSM (the http://schemas.microsoft.com/winfx/2006/xaml/presentation namespace is the default WPF namespace, declared as xmlns="..." in most standard .xaml's) -- you can however only use it at certain parts of your visual hierarchy.

For example, when I use the VSM in a standard UserControl, it looks something like so:

<UserControl x:Class="Whatever"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid x:Name="LayoutRoot">
    <VisualStateManager.VisualStateGroups>
      <VisualStateGroup x:Name="CommonStates">
        <VisualState x:Name="Disabled">
          <!-- Storyboards go here -->
        </VisualState>
      </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
  </Grid>
</UserControl>

Placing the VSM xaml at this level will enable your storyboards to refer to any elements contained within the Grid. This works the same in a ControlTemplate like you're working with. One thing to note though, is that while in your own UserControls you can name the visual states whatever you like (because you'll ultimately be making calls to switch to that visual state in code), with the built-in controls, your visual states have to be named exactly what the control is expecting.

0
votes

Answering my own question....
Q1 YES
Q2 No - it doesn't matter. The templates are "about" the same.
Q3 Not sure
Q4 I don't know.

The key here was that I needed to specify TargetFramework = 4.0. I had been compiling against v3.5, and the VSM I guess was first available in v4.0, so that was the reason for the "not found in namespace xxxxx" error.

I did not need to specify an XMLNS in the XAML file.

Stepping back - the answer to the larger question - how to get a ToggleButton that changes color when depressed... I tried fiddling with the builtin ControlTemplate, but it was too complicated for me to understand. The animations that occurred with the Press and Checked and so on - I could never figure out how to get it to just do what I wanted.

I stripped down the template into something much more spares, without all the gradients and animations, then I added back in a few animations and triggers to make it do about what I wanted it to do.
The visual effect is like this. ON:

enter image description here

OFF:

enter image description here


The XAML I used:

<ResourceDictionary xmlns     = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x   = "http://schemas.microsoft.com/winfx/2006/xaml">
  <Style x:Key      = "ToggleButtonStyle3"
         TargetType = "ToggleButton">

<!-- This is a style (template?) for a toggle button. I wanted it to
     change to a contrasty color when depressed. This took me a
     loooooong time and much trial and error to figure out. The visual
     effect is somewhat like the buttons in the compile output log in
     Visual Studio, in which you can toggle the display of errors and
     warnings.
-->
    <Setter Property="Background" Value="#FFF7F0D2"/>
    <Setter Property="Foreground" Value="#FF000000"/>
    <Setter Property="Padding" Value="3"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="BorderBrush">
      <Setter.Value>
        <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
          <GradientStop Color="#FFC4BC64" Offset="0"/>
          <GradientStop Color="#FFADA658" Offset="0.375"/>
          <GradientStop Color="#FFA19A52" Offset="0.375"/>
          <GradientStop Color="#FF847E43" Offset="1"/>
        </LinearGradientBrush>
      </Setter.Value>
    </Setter>
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="ToggleButton">
          <Grid x:Name="ButtonGrid">
            <VisualStateManager.VisualStateGroups>
              <VisualStateGroup x:Name="CommonStates">
                <VisualState x:Name="Normal"/>
                <VisualState x:Name="MouseOver">
                  <Storyboard>
                    <DoubleAnimation Duration="0" Storyboard.TargetName="InnerRectangle" Storyboard.TargetProperty="Opacity" To="0.3"/>
                  </Storyboard>
                </VisualState>
                <VisualState x:Name="Pressed">
                  <Storyboard>
                    <ColorAnimation Duration="00:00:00"
                                    Storyboard.TargetName="InnerRectangle"
                                    Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)"
                                    To="#FFF5BF0F"/>
                  </Storyboard>
                </VisualState>
                <VisualState x:Name="Disabled">
                  <Storyboard>
                    <DoubleAnimation Duration="0" Storyboard.TargetName="DisabledVisualElement" Storyboard.TargetProperty="Opacity" To=".55"/>
                  </Storyboard>
                </VisualState>
              </VisualStateGroup>
              <VisualStateGroup x:Name="CheckStates">
                <VisualState x:Name="Checked">
                  <Storyboard>
                    <ColorAnimation Duration="00:00:00"
                                    Storyboard.TargetName="InnerRectangle"
                                    Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)"
                                    To="#FFF5D018"/>
                  </Storyboard>
                </VisualState>
                <VisualState x:Name="Unchecked"/>
              </VisualStateGroup>
              <VisualStateGroup x:Name="FocusStates">
                <VisualState x:Name="Focused">
                  <Storyboard>
                    <DoubleAnimation Duration="0" Storyboard.TargetName="FocusVisualElement" Storyboard.TargetProperty="Opacity" To="1"/>
                  </Storyboard>
                </VisualState>
                <VisualState x:Name="Unfocused" />
              </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>

            <Border x:Name          ="ButtonBorder"
                    CornerRadius    ="1"
                    Background      ="{TemplateBinding Background}"
                    BorderThickness ="{TemplateBinding BorderThickness}"
                    BorderBrush     ="{TemplateBinding BorderBrush}">
              <Border x:Name="InnerButtonBorder"
                      CornerRadius="1"
                      BorderThickness="2"
                      Background="#FFFAEB16">
                <Rectangle x:Name="InnerRectangle" Opacity="1" Fill="#F7F0D2" />
              </Border>
            </Border>

            <ContentPresenter
                x:Name="contentPresenter"
                Content="{TemplateBinding Content}"
                ContentTemplate="{TemplateBinding ContentTemplate}"
                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                TextBlock.Foreground="{TemplateBinding Foreground}"
                Margin="{TemplateBinding Padding}"/>
            <Rectangle x:Name="DisabledVisualElement" RadiusX="3" RadiusY="3" Fill="#FFFFFFFF" Opacity="0" IsHitTestVisible="false" />
            <Rectangle x:Name="FocusVisualElement" RadiusX="2" RadiusY="2" Margin="1" Stroke="#FFD1C44D" StrokeThickness="1" Opacity="0" IsHitTestVisible="false" />
          </Grid>
          <ControlTemplate.Triggers>

            <Trigger Property="ToggleButton.IsChecked" Value="True">
              <!-- This setter hides the desired element when the ToggleButton's initial state is checked -->
              <Setter TargetName="ButtonBorder" Property="Background" Value="#FFF5D018"/>
              <Setter TargetName="contentPresenter" Property="TextBlock.Foreground" Value="#FF000000"/>
            </Trigger>
            <Trigger Property="ToggleButton.IsChecked" Value="False">
              <Setter TargetName="contentPresenter" Property="TextBlock.Foreground" Value="#78999999"/>
            </Trigger>
          </ControlTemplate.Triggers>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary>

I put that into a separate file, call it ToggleButtonStyle3.xaml. Then I use it like this:

<Window.Resources>
  <ResourceDictionary>
    <Style ....
    </Style>
    <ResourceDictionary.MergedDictionaries>
      <ResourceDictionary Source="Resources/ToggleButtonStyle3.xaml" />
    </ResourceDictionary.MergedDictionaries>
  </ResourceDictionary>
</Window.Resources>
....

         <ToggleButton Name       ="btnShowAlerts"
                      IsChecked  ="{Binding Path=ShowAlerts, Mode=TwoWay}"
                      Style      ="{StaticResource ToggleButtonStyle3}"
                      Content    ="alerts"
                      FontSize   ="9"
                      Padding    ="8,2"
                      Margin     ="0"
                      ClickMode  ="Press"
                      />

I don't know if that's the best way to do things. I know that it doesn't keep with the "theme" of the desktop. I know it's probably pretty basic. I just know that it took me a loooong time to figure out how to get a ToggleButton that changed color.