Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WPF

WPF Custom Controls - Without The Pain

Rate me:
Please Sign up or sign in to vote.
4.54/5 (36 votes)
13 Aug 2008CPOL4 min read 197.3K   7.5K   157   28
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...

    C#
    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.

    XML
    <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.

    XML
    <!-- ////////// 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.

XML
<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.

C#
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)


Written By
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

 
GeneralMy vote of 5 Pin
gg000831-May-16 5:28
gg000831-May-16 5:28 
QuestionSmall Demo Pin
Rocky B. Patel26-Dec-13 1:21
Rocky B. Patel26-Dec-13 1:21 
GeneralMy vote of 5 Pin
Hassan Shafipour4-Dec-12 9:20
Hassan Shafipour4-Dec-12 9:20 
QuestionTHIS ARTICLE IS WORTHLESS AND FULL OF BUGS Pin
ngggrfuck28-Nov-12 14:10
ngggrfuck28-Nov-12 14:10 
QuestionHand Written XAML??? Pin
PythonesqueSpam13-Oct-12 5:19
PythonesqueSpam13-Oct-12 5:19 
QuestionHow about an Icon/Image? Pin
Johnny J.27-Aug-12 4:38
professionalJohnny J.27-Aug-12 4:38 
QuestionWow ! Pin
C George Williams3-May-12 18:12
C George Williams3-May-12 18:12 
"and sweating the truth out of job candidates who claimed to have .Net experience "

Really? Why not take the effort to nurture people who have taken an interest in something that you also find cool? Do you need to cause them pain in an interview - an already painful experience in and of itself for many people?

Do you know how to find abstract qualities like "promise" and "problem-solving."

Tech companies need more people with passion, and less people who know enough just to write abstruse technical wikis and books.

Anyway, its seems a wiser and more positive use of your wisdom and experience - construction not destruction.

-2 cents-
AnswerRe: Wow ! Pin
Brett Goodman23-Feb-14 18:10
Brett Goodman23-Feb-14 18:10 
QuestionNice job Pin
AeroAMA18-Oct-11 22:47
AeroAMA18-Oct-11 22:47 
QuestionHow to reach Static Resouces of style in CC ? Pin
danies82-Aug-11 22:47
danies82-Aug-11 22:47 
GeneralNice Bench Pin
Xmen Real 7-Jul-10 18:02
professional Xmen Real 7-Jul-10 18:02 
GeneralNice Pin
John Underhill15-Feb-10 10:09
John Underhill15-Feb-10 10:09 
QuestionWhy Usercontrol = hijack ? Pin
Serge Wautier11-Dec-09 1:48
Serge Wautier11-Dec-09 1:48 
AnswerRe: Why Usercontrol = hijack ? Pin
AndyL211-Dec-09 17:09
AndyL211-Dec-09 17:09 
GeneralNice one Pin
Member 45654332-Sep-09 22:59
Member 45654332-Sep-09 22:59 
GeneralWork request Pin
Rockywood10-Feb-09 3:47
Rockywood10-Feb-09 3:47 
GeneralThanks Pin
Rami Shareef7-Feb-09 3:19
Rami Shareef7-Feb-09 3:19 
GeneralNice! Pin
mtonsager27-Oct-08 1:41
mtonsager27-Oct-08 1:41 
GeneralRe: Nice! Pin
mongeon525-Aug-09 9:29
mongeon525-Aug-09 9:29 
GeneralVery nice, one question... Pin
christian wirth22-Aug-08 21:35
christian wirth22-Aug-08 21:35 
GeneralRe: Very nice, one question... Pin
AndyL224-Aug-08 20:25
AndyL224-Aug-08 20:25 
GeneralRe: Very nice, one question... Pin
Kevin Marois17-Apr-12 10:43
professionalKevin Marois17-Apr-12 10:43 
GeneralRe: Very nice, one question... Pin
Johnny J.27-Aug-12 2:35
professionalJohnny J.27-Aug-12 2:35 
GeneralYes, Xaml is just the best thing if've seen in quite a while. Pin
Leslie Godwin18-Aug-08 18:09
Leslie Godwin18-Aug-08 18:09 
GeneralRe: Yes, Xaml is just the best thing if've seen in quite a while. Pin
AndyL224-Aug-08 21:01
AndyL224-Aug-08 21:01 

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

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