Click here to Skip to main content
Click here to Skip to main content

WPF Custom Controls - Without The Pain

, 13 Aug 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
Creating a maintainable, extensible WPF custom control library is a lot easier if you know a few tricks.

Introduction

WPF enables developers with even the most limited design skills to create highly-polished user interfaces, but the process may involve a long series of frustrating trial-and-error experiments using unfamiliar design tools, borrowing secret sauce from (usually randomly selected) online demos, and the generation of large volumes of XAML markup filled with indecipherable references like “Data=’M81.719619,26.720311 C41.467481,9.9004151 42.081815,10.203673 1.3438841,26.022972 8.820712,-17.102152 81.334525,-9.4161124 81.719619,26.720311 z’”, with results that sometimes get distorted when sizes or color schemes change from what was specified in the original demo.

The attached project lets you quickly try out a wide range of production quality variations on common UI effects (rollover “inner glow”, button affordance, “glass” highlights…), which technically COULD be applied directly to a shipping product just by referencing the custom control DLL and setting button properties, but is actually intended to demonstrate some interesting WPF Custom Control implementation issues.

ButtonDesigner_Default2.png

ButtonDesigner_CompareSmall.png

What's Covered

  • The ‘XSButton’ custom control is implemented as a true WPF Custom Control Library (not the hijacked UserControl often used in online examples), and this has some unexpected requirements and constraints, such as the need to use a Themes\generic.xaml file and ComponentResourceKey references. The control code makes extensive use of Dependency Properties, dynamically switches Styles and Templates, and illustrates some WPF techniques which may be useful in a variety of other situations.

  • Many developers continue to write code for items that could be handled more easily with a few lines of XAML, and many WPF demos blindly incorporate large amounts of raw tool-generated XAML, which a little refactoring could reduce to a smaller, simpler body of mark up, that a developer could actually read. The attached project (client and custom control) is implemented almost completely using hand-coded XAML and property definitions. Markup for the custom control is encapsulated in separate ResourceDictionaries rather than dumped directly into generic.xaml, to support the real-world expectation that our library will contain definitions for multiple controls. Similarly, color definitions are kept separate from the markup that references them, for maintenance purposes.

  • The "Button Design Workbench” client project uses simple reflection to cache and restore property values...

    internal void CopyValues( FrameworkElement element )
    {
       foreach( PropertyInfo prop in element.GetType().GetProperties() )
       {
          _originalValues.Add( prop.Name, prop.GetValue( element, null ) );
       }
    }

    ... and leverages FlowDirection.RightToLeft to generate a fully-interactive “secondary” view.

    <local:viewButtonEditor Grid.Column="1" Margin="0,12,12,12" x:Name="panelRight"
                            FlowDirection="RightToLeft" Visibility="Collapsed" />

    Even trivial features, such as the use of double borders to generate an attractive “inset” panel effect, and the syntax for binding ComboBox options to enum values, may not be obvious, but are often very useful.

    <!-- ////////// Button Style Enum Values ////////// -->
    
    <ObjectDataProvider MethodName="GetValues" 
    	ObjectType="{x:Type sys:Enum}" x:Key="StyleOptions">
       <ObjectDataProvider.MethodParameters>
          <x:Type TypeName="custom:Highlight" />
       </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
    
    <ComboBox Grid.Row="6" Height="23"
              ItemsSource="{Binding Source={StaticResource StyleOptions}}"
              SelectedItem="{Binding HighlightStyle, ElementName=btnTarget}" />

Button Construction

The default ControlTemplate for ‘XSButton’ defines “Outer” and “Inner” borders, to support common visual effects related to “seating” the control on its panel host. The core of the template consists of whatever content might have been specified by the client, sandwiched between an underlying RadialGradient “Glow”, and overlaid with a semi-transparent glass “Highlight”, with rollover and click animations.

<ControlTemplate TargetType="{x:Type local:XSButton}">

   <Border Name="OuterBorder" CornerRadius="{Binding CornerRadius, 
	ElementName=InnerBorder}"
         BorderBrush="{TemplateBinding OuterBorderBrush}" 
	BorderThickness="{TemplateBinding OuterBorderThickness}" >

      <Border Name="InnerBorder" Background="{TemplateBinding Background}" 
	CornerRadius="{TemplateBinding CornerRadius}"
        	BorderBrush="{TemplateBinding InnerBorderBrush}" 
	BorderThickness="{TemplateBinding InnerBorderThickness}" >

         <Grid >

            <Border Name="Glow" Opacity="0" CornerRadius="{Binding CornerRadius, 
		ElementName=InnerBorder}">
               <Border.Background>
                  <RadialGradientBrush>
                     <GradientStop Offset="0" Color="{Binding GlowColor, 
			RelativeSource={RelativeSource TemplatedParent},
                        	Converter={StaticResource ColorToAlphaColorConverter}, 
			ConverterParameter=176}" />
                     <GradientStop Offset="1" Color="{Binding GlowColor, 
			RelativeSource={RelativeSource TemplatedParent},
                           	Converter={StaticResource ColorToAlphaColorConverter}, 
			ConverterParameter=0}" />
                  </RadialGradientBrush>
               </Border.Background>
            </Border>

            <Border Name="padding" Margin="{TemplateBinding Padding}"
                    HorizontalAlignment="Center" VerticalAlignment="Center">
               <ContentPresenter Name="content" />
            </Border>

            <Control Name="Highlight" 
		Template="{TemplateBinding HighlightAppearance}" />

         </Grid>
      </Border>
   </Border>

   <ControlTemplate.Triggers>

      <Trigger Property="IsPressed" Value="True">
         <Setter TargetName="OuterBorder" Property="Opacity" Value="0.9" />
         <Setter TargetName="InnerBorder" Property="Opacity" Value="0.9" />
         <Setter TargetName="content" Property="Margin" Value="2,2,0,0" />
         <Setter TargetName="Glow" Property="Opacity" Value="0.5" />
         <Setter TargetName="Highlight" Property="Opacity" Value="0.5" />
      </Trigger>

      <Trigger Property="IsMouseOver" Value="True" >
         <Trigger.EnterActions>
            <BeginStoryboard Storyboard="{StaticResource GlowOn}" />
         </Trigger.EnterActions>
         <Trigger.ExitActions>
            <BeginStoryboard Storyboard="{StaticResource GlowOff}" />
         </Trigger.ExitActions>
      </Trigger>

   </ControlTemplate.Triggers>

</ControlTemplate>

DependencyProperties expose custom features to clients, and provide support for internal binding. The code hides awkward ComponentResourceKey references from the client, translating internally between convenient enum, and actual ComponentResourceKey values.

private static void OnHighlightStyleChanged
	( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
   XSButton btn = (XSButton)d;

   Highlight highlight = (Highlight)e.NewValue;

   // Assign style associated with user-selected enum value
   btn.Style = (Style)btn.TryFindResource
	( new ComponentResourceKey( btn.GetType(), highlight.ToString() ) );
}

Drawbacks and Limitations

There is much more that we could add to our custom button. For example, a custom ContentPresenter could dynamically insert our “glow” element BETWEEN a button image and button text, and we could add a whole series of highlight styles to simulate crystal sphere, plastic, ceramic, and other effects… but, along with the advantages of being able to ensure a more consistent appearance across projects, and reducing the amount of time developers must spend pretending to be designers, you sacrifice a significant amount of flexibility in trying to define one end-all-be-all custom control, rather than encouraging individuals to composite their own buttons from a collection of styles. Development environments featuring strong engineering and design skills will probably reserve the use of custom controls for situations involving significant code-behind, such as in my other posted article, The WPF AlarmBar.

Another option would be to encapsulate the code in a UserControl, or to generate a UserControl and replace all actual “UserControl” references in the *.xaml and *.cs files with your preferred base class. This may or may not work, depending on the base class you choose, and what you need for it to do. For example, you canNOT derive directly from the Window class, but you CAN derive from a Custom Control which derives from the Window class, with an associated template defined in generic.xaml, in order to customize the non-client area to create a unique Window frame style for your product. Since generic.xaml will work in ALL situations, and is the technique used internally at Microsoft, it may be better to stick with that approach.

Other Projects by Andy L.

License

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

Share

About the Author

AndyL2

United States United States
I started out writing applications in C for a software development shop in Japan, did alot of work in C++/MFC, and some DirectX, at two Silicon Valley startups, and have been working with C# and Windows Forms ever since the release of .Net 1.0. Although I took a couple intro. to CS courses at CAL (Berkeley), my degree was actually in Asian Studies, and I learned to program "in the trenches". I was also the .Net evangelist at my most recent company, authoring internal white papers on .Net, sending out a weekly ".Net FYI" e-mail, conducting .Net training, and sweating the truth out of job candidates who claimed to have .Net experience (You'd be amazed at the number of Silicon Valley engineers who list "three years of C#" on their resumes, who are unable to explain how to hook up a simple event handler, or identify basic terms like "reflection", "attributes" -- or "Richter" and "Löwy").

Comments and Discussions

 
QuestionHow to reach Static Resouces of style in CC ? Pinmemberdanies82-Aug-11 22:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.141022.2 | Last Updated 13 Aug 2008
Article Copyright 2008 by AndyL2
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid