Click here to Skip to main content
6,294,871 members and growing! (16,613 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » Windows Presentation Foundation » Controls     Intermediate License: The Code Project Open License (CPOL)

WPF Custom Controls - Without The Pain

By AndyL2

Creating a maintainable, extensible WPF custom control library is a lot easier if you know a few tricks.
C# 2.0, C# 3.0.NET 3.0, .NET 3.5, XAML, WPF, Dev
Version:3 (See All)
Posted:13 Aug 2008
Views:20,883
Bookmarked:83 times
Announcements
Loading...
 
Search    
Advanced Search
printPrint   Broken Article?Report       add Share
  Discuss Discuss   Recommend Article Email
23 votes for this article.
Popularity: 5.85 Rating: 4.30 out of 5
1 vote, 4.3%
1

2
2 votes, 8.7%
3
9 votes, 39.1%
4
11 votes, 47.8%
5

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)

About the Author

AndyL2


Member
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").
Location: United States United States

Other popular Windows Presentation Foundation articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 10 of 10 (Total in Forum: 10) (Refresh)FirstPrevNext
GeneralWork request PinmemberRockywood4:47 10 Feb '09  
GeneralThanks PinmemberJosh Andy4:19 7 Feb '09  
GeneralNice! Pinmembermtonsager2:41 27 Oct '08  
GeneralVery nice, one question... Pinmembergourmelin22:35 22 Aug '08  
GeneralRe: Very nice, one question... PinmemberAndyL221:25 24 Aug '08  
GeneralYes, Xaml is just the best thing if've seen in quite a while. PinmemberLeslie Godwin19:09 18 Aug '08  
GeneralRe: Yes, Xaml is just the best thing if've seen in quite a while. PinmemberAndyL222:01 24 Aug '08  
GeneralGood one ... PinmemberNorbert Freitag2:25 15 Aug '08  
GeneralGreat article PinmemberJean-Paul Mikkers22:14 13 Aug '08  
GeneralNice job! PinmemberGukar15:53 13 Aug '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 13 Aug 2008
Editor: Deeksha Shenoy
Copyright 2008 by AndyL2
Everything else Copyright © CodeProject, 1999-2009
Web20 | Advertise on the Code Project