Click here to Skip to main content
15,891,253 members
Articles / Desktop Programming / WPF

WPF Control Composition (Part 2 of 2)

Rate me:
Please Sign up or sign in to vote.
4.86/5 (12 votes)
21 Mar 2012CPOL6 min read 44.8K   1.6K   36  
Theming an existing user control adds flexibilty at the application side without changing the original implementation. This article gives an example by theming a user control that was previously not fully themeable.

<ResourceDictionary
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  
  mc:Ignorable="d"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
>

  <!-- Add resources for FolderTreeView control -->
  <ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="Shared.xaml" />
  </ResourceDictionary.MergedDictionaries>

  <ControlTemplate x:Key="ComboBoxToggleButton" TargetType="{x:Type ToggleButton}">
    <ControlTemplate.Resources>
      <Storyboard x:Key="HoverOn">
        <DoubleAnimation Duration="00:00:00.1000000" Storyboard.TargetName="BtnOver" Storyboard.TargetProperty="Opacity" To="0.8"/>
        <DoubleAnimation Duration="00:00:00.1000000" Storyboard.TargetName="Background_over" Storyboard.TargetProperty="Opacity" To="1"/>
      </Storyboard>
      <Storyboard x:Key="HoverOff">
        <DoubleAnimation Duration="00:00:00.4000000" Storyboard.TargetName="BtnOver" Storyboard.TargetProperty="Opacity" To="0"/>
        <DoubleAnimation Duration="00:00:00.4000000" Storyboard.TargetName="Background_over" Storyboard.TargetProperty="Opacity" To="0"/>
      </Storyboard>
      <Storyboard x:Key="PressedOn">
        <DoubleAnimation Duration="00:00:00.1000000" Storyboard.TargetName="BtnPress" Storyboard.TargetProperty="Opacity" To="0.8"/>
        <DoubleAnimation Duration="00:00:00.1000000" Storyboard.TargetName="Background_press" Storyboard.TargetProperty="Opacity" To="1"/>
        <DoubleAnimation Duration="00:00:00.1000000" Storyboard.TargetName="BtnPress_highlight" Storyboard.TargetProperty="Opacity" To="1"/>
      </Storyboard>
      <Storyboard x:Key="PressedOff">
        <DoubleAnimation Duration="00:00:00.4000000" Storyboard.TargetName="BtnPress" Storyboard.TargetProperty="Opacity" To="0"/>
        <DoubleAnimation Duration="00:00:00.4000000" Storyboard.TargetName="Background_press" Storyboard.TargetProperty="Opacity" To="0"/>
        <DoubleAnimation Duration="00:00:00.4000000" Storyboard.TargetName="BtnPress_highlight" Storyboard.TargetProperty="Opacity" To="0"/>
      </Storyboard>
    </ControlTemplate.Resources>
    <Grid>
      <Rectangle x:Name="Background" Fill="#FFFFFFFF" Stroke="#FFABAEB3" RadiusX="3" RadiusY="3" IsHitTestVisible="false"/>
      <Rectangle x:Name="Background_over" Stroke="{StaticResource TextBoxOverBrush}" RadiusX="3" RadiusY="3" Opacity="0" IsHitTestVisible="false"/>
      <Rectangle x:Name="Background_press" Stroke="{StaticResource TextBoxPressBrush}" RadiusX="3" RadiusY="3" Opacity="0" IsHitTestVisible="false"/>
      <Border x:Name="BtnOver" Width="20" Background="{StaticResource BtnOverFill}" BorderBrush="{StaticResource TextBoxPressBrush}" BorderThickness="1" CornerRadius="0,3,3,0" Opacity="0" HorizontalAlignment="Right"/>
      <Border x:Name="BtnPress" Width="20" Background="{StaticResource BtnPressFill}" BorderBrush="{StaticResource btnPressStroke1}" BorderThickness="1" CornerRadius="0,3,3,0" Opacity="0" HorizontalAlignment="Right"/>
      <Border x:Name="BtnPress_highlight" Background="{StaticResource ComboTogglePressHighlight}" Margin="1" Width="18" CornerRadius="0,2,2,0" Opacity="0" HorizontalAlignment="Right"/>
      <Border x:Name="BtnOverlay" Margin="1" Width="18" CornerRadius="0,2,2,0" HorizontalAlignment="Right">
        <Border.Background>
          <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
            <GradientStop Color="#00FFFFFF"/>
            <GradientStop Color="#72FFFFFF" Offset="0.189"/>
            <GradientStop Color="#72FFFFFF" Offset="0.5"/>
            <GradientStop Color="#00FFFFFF" Offset="0.51"/>
          </LinearGradientBrush>
        </Border.Background>
      </Border>
      <Path x:Name="BtnArrow" Margin="0,0,7,0" Width="6" Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z " HorizontalAlignment="Right" Stretch="Uniform">
        <Path.Fill>
          <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
            <GradientStop Color="{StaticResource Arrow}" Offset="0"/>
            <GradientStop Color="#FF000000" Offset="1"/>
          </LinearGradientBrush>
        </Path.Fill>
      </Path>
      <Rectangle x:Name="DisabledVisualElement" Margin="1" Fill="#00FFFFFF" RadiusX="3" RadiusY="3" IsHitTestVisible="false" Visibility="Collapsed"/>
    </Grid>
    <ControlTemplate.Triggers>
      <Trigger Property="IsMouseOver" Value="true">
        <Trigger.ExitActions>
          <BeginStoryboard Storyboard="{StaticResource HoverOff}" x:Name="HoverOff_BeginStoryboard"/>
        </Trigger.ExitActions>
        <Trigger.EnterActions>
          <BeginStoryboard Storyboard="{StaticResource HoverOn}"/>
        </Trigger.EnterActions>

      </Trigger>
      <Trigger Property="IsChecked" Value="true"/>
      <Trigger Property="IsPressed" Value="True">
        <Trigger.ExitActions>
          <BeginStoryboard Storyboard="{StaticResource PressedOff}" x:Name="PressedOff_BeginStoryboard"/>
        </Trigger.ExitActions>
        <Trigger.EnterActions>
          <BeginStoryboard Storyboard="{StaticResource PressedOn}" x:Name="PressedOn_BeginStoryboard"/>
        </Trigger.EnterActions>
      </Trigger>
      <Trigger Property="IsEnabled" Value="False">
        <Setter Property="Foreground" Value="{DynamicResource DisabledForegroundBrush}"/>
        <Setter Property="Visibility" TargetName="DisabledVisualElement" Value="Visible"/>
      </Trigger>
    </ControlTemplate.Triggers>
  </ControlTemplate>


  <ControlTemplate x:Key="ComboBoxTextBox" TargetType="{x:Type TextBox}">
    <!-- http://stackoverflow.com/questions/8266137/editable-combo-box-selection-text-overflows -->
    <ScrollViewer x:Name="PART_ContentHost"
                  Focusable="False"
                  Background="{TemplateBinding Background}" />
  </ControlTemplate>


  <Style TargetType="{x:Type ComboBox}">
    <Setter Property="SnapsToDevicePixels" Value="true"/>
    <Setter Property="Foreground" Value="{StaticResource OutsideFontColor}"/>
    <Setter Property="Template" Value="{DynamicResource ComboBoxTemplate}" />
  </Style>

  <ControlTemplate x:Key="ComboBoxTemplate" TargetType="{x:Type ComboBox}">
    <ControlTemplate.Resources>
      <Storyboard x:Key="FocusedOn">
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="FocusVisualElement" Storyboard.TargetProperty="(UIElement.Opacity)">
          <SplineDoubleKeyFrame KeyTime="00:00:00.1000000" Value="1"/>
        </DoubleAnimationUsingKeyFrames>
      </Storyboard>
      <Storyboard x:Key="FocusedOff">
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="FocusVisualElement" Storyboard.TargetProperty="(UIElement.Opacity)">
          <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0"/>
        </DoubleAnimationUsingKeyFrames>
      </Storyboard>
    </ControlTemplate.Resources>
    <Grid>
      <ToggleButton Grid.Column="2" Template="{DynamicResource ComboBoxToggleButton}" x:Name="ToggleButton" Focusable="false" IsChecked="{Binding Path=IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" ClickMode="Press"/>
      <ContentPresenter HorizontalAlignment="Left" Margin="3,3,23,3" x:Name="ContentSite" VerticalAlignment="Center" Content="{TemplateBinding SelectionBoxItem}" ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" IsHitTestVisible="False"/>

      <TextBox Visibility="Hidden" Template="{DynamicResource ComboBoxTextBox}" HorizontalAlignment="Left" Margin="3,3,23,3" x:Name="PART_EditableTextBox" Style="{x:Null}" VerticalAlignment="Center" Focusable="True" Background="Transparent" IsReadOnly="{TemplateBinding IsReadOnly}"/>
      <Rectangle x:Name="DisabledVisualElement" Fill="#A5FFFFFF" RadiusX="4" RadiusY="4" IsHitTestVisible="false" Visibility="Collapsed" />
      <Rectangle x:Name="FocusVisualElement" Margin="-1" Stroke="{StaticResource selectedStroke}" StrokeThickness="1" RadiusX="4" RadiusY="4" IsHitTestVisible="false" Opacity="0"/>
      <Popup IsOpen="{TemplateBinding IsDropDownOpen}" Placement="Bottom" x:Name="Popup" Focusable="False" AllowsTransparency="True" PopupAnimation="Slide">
        <Grid MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{TemplateBinding ActualWidth}" x:Name="DropDown" SnapsToDevicePixels="True">
          <Border x:Name="DropDownBorder" Background="#FFFFFFFF" BorderBrush="{StaticResource TextBoxNorm}" BorderThickness="1" CornerRadius="1,1,3,3">
            <ScrollViewer Margin="4,6,4,6"   SnapsToDevicePixels="True" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" CanContentScroll="True">

              <StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained"/>

            </ScrollViewer>
          </Border>
        </Grid>
      </Popup>
    </Grid>
    <ControlTemplate.Triggers>
      <Trigger Property="IsFocused" Value="True">
        <Trigger.ExitActions>
          <BeginStoryboard Storyboard="{StaticResource FocusedOff}" x:Name="FocusedOff_BeginStoryboard"/>
        </Trigger.ExitActions>
        <Trigger.EnterActions>
          <BeginStoryboard Storyboard="{StaticResource FocusedOn}"/>
        </Trigger.EnterActions>
      </Trigger>
      <Trigger Property="HasItems" Value="false">
        <Setter Property="MinHeight" Value="95" TargetName="DropDownBorder"/>
      </Trigger>
      <Trigger Property="IsEnabled" Value="false">
        <Setter Property="Foreground" Value="{DynamicResource DisabledForegroundBrush}"/>
        <Setter Property="Visibility" TargetName="DisabledVisualElement" Value="Visible"/>
      </Trigger>
      <Trigger Property="IsGrouping" Value="true">
        <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
      </Trigger>
      <Trigger Property="AllowsTransparency" SourceName="Popup" Value="true">
        <Setter Property="CornerRadius" Value="4" TargetName="DropDownBorder"/>
        <Setter Property="Margin" Value="0,2,0,0" TargetName="DropDownBorder"/>
      </Trigger>
      <Trigger Property="IsEditable" Value="true">
        <Setter Property="IsTabStop" Value="false"/>
        <Setter Property="Visibility" Value="Visible" TargetName="PART_EditableTextBox"/>
        <Setter Property="Visibility" Value="Hidden" TargetName="ContentSite"/>
      </Trigger>
    </ControlTemplate.Triggers>
  </ControlTemplate>

  <Style d:IsControlPart="True" TargetType="{x:Type ComboBoxItem}">
    <Setter Property="SnapsToDevicePixels" Value="true"/>
    <Setter Property="HorizontalContentAlignment" Value="Stretch" />
    <Setter Property="VerticalContentAlignment" Value="Center" />
    <Setter Property="Padding" Value="3" />
    <Setter Property="Foreground" Value="{StaticResource OutsideFontColor}"/>
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="{x:Type ComboBoxItem}">
          <ControlTemplate.Resources>
            <Storyboard x:Key="HoverOn">
              <DoubleAnimation Duration="00:00:00.1000000" Storyboard.TargetName="BackgroundGradientOver" Storyboard.TargetProperty="Opacity" To="0.73"/>
            </Storyboard>
            <Storyboard x:Key="HoverOff">
              <DoubleAnimation Duration="00:00:00.4000000" Storyboard.TargetName="BackgroundGradientOver" Storyboard.TargetProperty="Opacity" To="0"/>
            </Storyboard>
            <Storyboard x:Key="SelectedOn">
              <DoubleAnimation Duration="00:00:00.1000000" Storyboard.TargetName="BackgroundGradientSelected" Storyboard.TargetProperty="Opacity" To="0.84"/>
            </Storyboard>
            <Storyboard x:Key="SelectedOff">
              <DoubleAnimation Duration="00:00:00.4000000" Storyboard.TargetName="BackgroundGradientSelected" Storyboard.TargetProperty="Opacity" To="0"/>
            </Storyboard>
          </ControlTemplate.Resources>
          <Grid SnapsToDevicePixels="true">
            <Rectangle x:Name="BackgroundGradientOver" Fill="{StaticResource hoverGradient}" Stroke="{StaticResource hoverStroke}" RadiusX="2" RadiusY="2"  Opacity="0"/>
            <Rectangle x:Name="BackgroundGradientSelected" Fill="{StaticResource BtnOverFill}" Stroke="{StaticResource selectedStroke}" RadiusX="2" RadiusY="2" Opacity="0"/>
            <Rectangle x:Name="BackgroundHighlight" Margin="1" Stroke="#A0FFFFFF" RadiusX="1" RadiusY="1"/>
            <!-- ContentPresenter
                            x:Name="contentPresenter"
                            Content="{TemplateBinding Content}"
                            ContentTemplate="{TemplateBinding ContentTemplate}"
                            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                            Margin="{TemplateBinding Padding}"/ -->
         
            <!-- Fixes problem in Drop-Down when using DisplayMemberPath in binding: http://wpf.codeplex.com/workitem/10129 -->
            <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
            VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
            Margin="3,3,23,3"
            x:Name="ContentSite" />
                    </Grid>
          <ControlTemplate.Triggers>

            <Trigger Property="IsMouseOver" Value="True">
              <Trigger.ExitActions>
                <BeginStoryboard Storyboard="{StaticResource HoverOff}" x:Name="HoverOff_BeginStoryboard"/>
              </Trigger.ExitActions>
              <Trigger.EnterActions>
                <BeginStoryboard Storyboard="{StaticResource HoverOn}" x:Name="HoverOn_BeginStoryboard"/>
              </Trigger.EnterActions>
            </Trigger>

            <Trigger Property="IsHighlighted" Value="true"/>
            <Trigger Property="Selector.IsSelected" Value="True">
              <Trigger.ExitActions>
                <BeginStoryboard Storyboard="{StaticResource SelectedOff}" x:Name="SelectedOff_BeginStoryboard1"/>
              </Trigger.ExitActions>
              <Trigger.EnterActions>
                <BeginStoryboard Storyboard="{StaticResource SelectedOn}" x:Name="SelectedOn_BeginStoryboard1"/>
              </Trigger.EnterActions>
            </Trigger>

            <Trigger Property="IsEnabled" Value="false">
              <Setter Property="Foreground" Value="{DynamicResource DisabledForegroundBrush}"/>
            </Trigger>
          </ControlTemplate.Triggers>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>

</ResourceDictionary>

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Germany Germany
The Windows Presentation Foundation (WPF) and C# are among my favorites and so I developed Edi

and a few other projects on GitHub. I am normally an algorithms and structure type but WPF has such interesting UI sides that I cannot help myself but get into it.

https://de.linkedin.com/in/dirkbahle

Comments and Discussions