Click here to Skip to main content
15,860,861 members
Articles / Desktop Programming / WPF
Article

Creating and consuming a custom WPF control

Rate me:
Please Sign up or sign in to vote.
4.71/5 (44 votes)
1 Mar 20078 min read 237.9K   3.8K   111   25
Creating and consuming a custom WPF control

Introduction

Whenever a new technology comes along, I personally find that the best way to get to grips with its functionality is to try and create something you have done in another language. To this end, this article will describe how to create a custom control in WPF which will raise custom events. The custom control will then be placed with a standard XAML window and the custom control's events shall be subscribed to. That's it in a nutshell. But along the way there are several things that I would like to point out to you.

The proposed structure will be as follows:

  • A word on XAML / WPF
  • The custom Control Itself
  • A word on events in .NET 3.0
  • Referencing an external custom control in an XAML window
  • Where is the InitializeComponent() method anyway
  • A screen shot of the demo app

A Word On XAML / WPF

WPF applications are quite similar is one sense to ASP.NET applications; there may (or may not) be an XAML file, and also a code behind file where the XAML file contains the windows / control rendering, and the code behind does all the procedural code. This is one development model. But there is another way. Anything that can be done in XAML can also be done entirely in code behind (C#/ VB). To this end the custom control that I've created is entirely code created. As for this example it just seemed to make more sense.

The Custom Control Itself

As color pickers seem to be almost universally popular at codeproject, I thought let's do one of them. This is a single control that has been created in a separate Visual Studio 2005 project, and is part of the whole solution. I have done this as it is the most common way that we all use third party controls. We get a DLL and make a reference to it. In fact I have chosen this path, as the XAML directives to reference a control do vary slightly depending on whether it is an internal class, or an external DLL. Most commonly I thought it would be a third party external DLL that was being referenced. If you don't get this, don't worry. There will be more on it later.

So without further ado, let's look at the code:

C#
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;

namespace ColorPicker
{
    #region ColorPickerControl CLASS
    /// <summary>
    /// A simple color picker control, with a custom event that uses
    /// the standard RoutedEventArgs.
    /// <br/>
    /// NOTE: I also tried to create a custom event with custom inherited
    /// RoutedEventArgs, but this didn't seem to work, 
    /// so this event is commented out. But if anyone knows how to do this 
    /// please let me know, as far as I know
    /// I am doing everything correctly
    /// </summary>
    public class ColorPickerControl : ListBox
    {
        #region InstanceFields
        //A RoutedEvent using standard RoutedEventArgs, event declaration
        //The actual event routing
        public static readonly RoutedEvent NewColorEvent =
            EventManager.RegisterRoutedEvent
        ("NewColor", RoutingStrategy.Bubble,
                   typeof(RoutedEventHandler), typeof(ColorPickerControl));

        //A RoutedEvent using standard custom ColorRoutedEventArgs, 
        //event declaration

        ////the event handler delegate
        public delegate void NewColorCustomEventHandler
            (object sender, ColorRoutedEventArgs e);

        ////The actual event routing
        public static readonly RoutedEvent NewColorCustomEvent =
             EventManager.RegisterRoutedEvent
        ("NewColorCustom", RoutingStrategy.Bubble,
                   typeof(NewColorCustomEventHandler), 
        typeof(ColorPickerControl));
        //******************************************************************
        //string array or colors
        private string[] _sColors =
        {
            "Black", "Brown", "DarkGreen", "MidnightBlue",
                "Navy", "DarkBlue", "Indigo", "DimGray",
            "DarkRed", "OrangeRed", "Olive", "Green",
                "Teal", "Blue", "SlateGray", "Gray",
            "Red", "Orange", "YellowGreen", "SeaGreen",
                "Aqua", "LightBlue", "Violet", "DarkGray",
            "Pink", "Gold", "Yellow", "Lime",
                "Turquoise", "SkyBlue", "Plum", "LightGray",
            "LightPink", "Tan", "LightYellow", "LightGreen",
                "LightCyan", "LightSkyBlue", "Lavender", "White"
        };
        #endregion
        #region Constructor
        /// <summary>
        /// Constructor for ColorPickerControl, which is a ListBox subclass
        /// </summary>
        public ColorPickerControl()
        {
            // Define a template for the Items, 
            // used the lazy FrameworkElementFactory method
            FrameworkElementFactory fGrid = new
                FrameworkElementFactory
        (typeof(System.Windows.Controls.Primitives.UniformGrid));
            fGrid.SetValue
       (System.Windows.Controls.Primitives.UniformGrid.ColumnsProperty,10);
            // update the ListBox ItemsPanel with the new 
            // ItemsPanelTemplate just created
            ItemsPanel = new ItemsPanelTemplate(fGrid);

            // Create individual items
            foreach (string clr in _sColors)
            {
                // Creat bounding rectangle for items data
                Rectangle rItem = new Rectangle();
                rItem.Width = 10;
                rItem.Height = 10;
                rItem.Margin = new Thickness(1);
                rItem.Fill = 
            (Brush)typeof(Brushes).GetProperty(clr).GetValue(null, null);
                //add rectangle to ListBox Items
                Items.Add(rItem);

                //add a tooltip
                ToolTip t = new ToolTip();
                t.Content = clr;
                rItem.ToolTip = t;
            }
            //Indicate that SelectedValue is Fill property of Rectangle item.
            //Kind of like an XPath query, 
            //this is the string name of the property
            //to use as the selected item value from the actual item data. 
            //The item data being a Rectangle in this case
            SelectedValuePath = "Fill";
        }
        #endregion
        #region Events
        // Provide CLR accessors for the event
        public event RoutedEventHandler NewColor
        {
            add { AddHandler(NewColorEvent, value); }
            remove { RemoveHandler(NewColorEvent, value); }
        }

        // This method raises the NewColor event
        private void RaiseNewColorEvent()
        {
            RoutedEventArgs newEventArgs = new RoutedEventArgs(NewColorEvent);
            RaiseEvent(newEventArgs);
        }

        // Provide CLR accessors for the event
        public event NewColorCustomEventHandler NewColorCustom
        {
            add { AddHandler(NewColorCustomEvent, value); }
            remove { RemoveHandler(NewColorCustomEvent, value); }
        }

        // This method raises the NewColorCustom event
        private void RaiseNewColorCustomEvent()
        {
            ToolTip t = (ToolTip)(SelectedItem as Rectangle).ToolTip;
            ColorRoutedEventArgs newEventArgs = 
        new ColorRoutedEventArgs(t.Content.ToString());
            newEventArgs.RoutedEvent = ColorPickerControl.NewColorCustomEvent;
            RaiseEvent(newEventArgs);
        }
        //*******************************************************************
        #endregion
        #region Overrides
        /// <summary>
        /// Overrides the OnSelectionChanged ListBox inherited method, and
        /// raises the NewColorEvent
        /// </summary>
        /// <param name="e">the event args</param>
        protected override void OnSelectionChanged(SelectionChangedEventArgs e)
        {
            base.OnSelectionChanged(e);
            //raise the event with standard RoutedEventArgs event args
            RaiseNewColorEvent();
            //raise the event with the custom ColorRoutedEventArgs event args
            RaiseNewColorCustomEvent();
            //****************************************************************
        }
        #endregion
    }
    #endregion
    #region ColorRoutedEventArgs CLASS
    /// <summary>
    /// ColorRoutedEventArgs : a custom event argument class
    /// </summary>
    public class ColorRoutedEventArgs : RoutedEventArgs
    {
        #region Instance fields
        private string _ColorName = "";
        #endregion
        #region Constructor
        /// <summary>
        /// Constructs a new ColorRoutedEventArgs object
        /// using the parameters provided
        /// </summary>
        /// <param name="clrName">the color name string</param>
        public ColorRoutedEventArgs(string clrName)
        {
            this._ColorName = clrName;
        }
        #endregion
        #region Public properties
        /// <summary>
        /// Gets the stored color name
        /// </summary>
        public string ColorName
        {
            get { return _ColorName; }
        }
        #endregion
    }
    #endregion
}

It can be seen that this is all fairly normal C# .NET 3.0 code (that is if you are OK with .NET 3.0 stuff, I am just learning). I want to pay some special attention to the constructor. Let's have a look at that part by part.

C#
// Define a template for the Items, use the lazy FrameworkElementFactory
// method
FrameworkElementFactory fGrid = new FrameworkElementFactory
    (typeof(System.Windows.Controls.Primitives.UniformGrid));
    fGrid.SetValue
    (System.Windows.Controls.Primitives.UniformGrid.ColumnsProperty, 10);
//update the ListBox ItemsPanel with the new ItemsPanelTemplate just created
ItemsPanel = new ItemsPanelTemplate(fGrid);

The FrameworkElementFactory class is a way to programmatically create templates, which are subclasses of FrameworkTemplate such as ControlTemplate or DataTemplate. This is equivalent to creating a <ControlTemplate> tag in XAML markup. So what we are really doing here is saying that the internal inherited Listbox.ItemsPanel will have a template applied to it that will be a uniform grid layout with 10 columns.

C#
// Create individual items
foreach (string clr in _sColors)
{
    // Create bounding rectangle for items data
    Rectangle rItem = new Rectangle();
    rItem.Width = 10;
    rItem.Height = 10;
    rItem.Margin = new Thickness(1);
    rItem.Fill = 
    (Brush)typeof(Brushes).GetProperty(clr).GetValue(null, null);
    //add rectangle to ListBox Items
    Items.Add(rItem);
    //add a tooltip
    ToolTip t = new ToolTip();
    t.Content = clr;
    rItem.ToolTip = t;
}

This section of the code is responsible for creating the individual ListItem contents. So what is going on? Well, the items are being created as Rectangle objects (yep that's right, Rectangles). Then the Rectangles are being filled with a Brush color, and then the Rectangle has a ToolTip applied.

C#
//Indicate that SelectedValue is Fill property of Rectangle item.
//Kind of like an XPath query, this is the string name of the property
//to use as the selected item value from the actual item data. The item
//data being a Rectangle in this case
SelectedValuePath = "Fill";

Finally the SelectedValuePath is told that the property that should be mapped to the SelectedValue is "Fill". So this means that whenever we get the SelectedValue the object it will be is a <codd>Brush as default, as "Fill" is a Brush Type, unless it is cast to another object Type. Isn't that mental. WPF is mind blowing, it really is.

The more eagle eyed amongst you will notice that the code for the control contains 2 events one of which is commented out. More on this later.

A word on events in .NET 3.0

Microsoft being Microsoft didn't want us to get too comfortable with things, so they have overhauled everything it would appear. Even something as small as events, is no longer the same as it was in .NET 2.0.

The code snippets below represent the new .NET 3.0 way of creating events.

I have created a custom event called NewColorEvent so let's have a look at how to define an event:

C#
//A RoutedEvent using standard RoutedEventArgs, event declaration
//The actual event routing
public static readonly RoutedEvent NewColorEvent =
          EventManager.RegisterRoutedEvent("NewColor", RoutingStrategy.Bubble,
            typeof(RoutedEventHandler), typeof(ColorPickerControl));

What else do we need to do, well we need to create the accessors for subscribing / unsubscribing to the event.

C#
// Provide CLR accessors for the event
public event RoutedEventHandler NewColor
{
    add { AddHandler(NewColorEvent, value); }
    remove { RemoveHandler(NewColorEvent, value); }
}

And we also need a raise event method such as:

C#
// This method raises the NewColor event
private void RaiseNewColorEvent()
{
     RoutedEventArgs newEventArgs = new RoutedEventArgs(NewColorEvent);
     RaiseEvent(newEventArgs);
}

And lastly we need to raise the event somewhere. I have chosen to do this in an override of the inherited ListBox OnSelectionChanged method; this is shown below:

C#
//raise the event with standard RoutedEventArgs event args
RaiseNewColorEvent();

And that's all there is to creating a custom event in a custom control. We just need to place the control somewhere now and subscribe to this lovely new event.

Referencing an external custom control in an XAML window

OK so you think you know how to make a reference to a DLL which contains a custom control. You just create a new tab on the toolbar, and browse to the DLL and any of the contained controls to the toolbar. Right. Well that didn't seem to work. So what do you have to do. Is Add a project reference (right click on references) and browse to the assembly (DLL) with the custom control(s), only one in this articles case.

So that's step 1. Then we actually want to use the custom control within a XAML window. So we have to add an extra directive to the XAML Windows root element. The important part to add is as follows:

If using a code file that you have the source code for

XML
xmlns:src="clr-namespace:NAMESPACE_NEEDED"

If using a external DLL

XML
xmlns:src="clr-namespace:NAMESPACE_NEEDED;assembly=ASSEMBLYNAME_NEEDED"

So for the attached example, where we have an external DLL which has a control we need to use, the root element would be changed to the following:

XML
<Window x:Class="ColorControlApp.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:src="clr-namespace:ColorPicker;assembly=ColorPickerControl"
    Title="ColorControlApp" Height="300" Width="300">

That's part of the story. We have now successfully referenced the external user control in the XAML, but we still don't have an instance of the control in the markup yet. So how do we do that. Well we do something like this:

C#
<src:ColorPickerControl HorizontalAlignment="Center"
                        VerticalAlignment="Center" Name="lstColorPicker"/>

OK so now we have done the XAML part, but what about the code behind file. We still need to do that part. So how is that done. Well, luckily that part is easier. Its just a normal using statement we need:

C#
using ColorPicker;

Where is the InitializeComponent() method anyway ?

Now we really do have a fully referenced external DLL, which contains a user control, which we now have an instance of within our XAML window, that is also now known about by the code behind logic because of the two steps just carried out.

But just how does the XAMLs code behind file know about the user control contained within the XAML file. Well the answer to that, is that when Visual Studio compiles a project, it creates a new generated source file, which is placed into the DEBUG\OBJ or RELEASE\OBJ (depends on how you are compiling the project). This file is called the same as the current XAML window file, but the extension will be g.cs for c# or g.vb for VB.

The following screen print shows this for the attached project:

Image 1

Can you see there is a Window1.g.cs (as I use C#) file there.

So what is that all about. Well let's have a look.

C#
//---------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:2.0.50727.42
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//---------------------------------------------------------------------------

using ColorPicker;
using System;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Media.TextFormatting;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace ColorControlApp 
{
    /// <summary>
    /// Window1
    /// </summary>
    public partial class Window1 : 
    System.Windows.Window, System.Windows.Markup.IComponentConnector 
    {
        internal System.Windows.Controls.StackPanel Stack;
        internal System.Windows.Controls.Label lblColor;
        internal ColorPicker.ColorPickerControl lstColorPicker;


        private bool _contentLoaded;
        /// <summary>
        /// InitializeComponent
        /// </summary>
        [System.Diagnostics.DebuggerNonUserCodeAttribute()]
        public void InitializeComponent() 
        {
            if (_contentLoaded) 
            {
                return;
            }
            _contentLoaded = true;
            System.Uri resourceLocater = 
            new System.Uri("/ColorControlApp;component/window1.xaml", 
            System.UriKind.Relative);
            System.Windows.Application.LoadComponent(this, resourceLocater);
        }

        [System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [System.ComponentModel.EditorBrowsableAttribute
        (System.ComponentModel.EditorBrowsableState.Never)]
        [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute
        ("Microsoft.Design", 
        "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
        void System.Windows.Markup.IComponentConnector.Connect
                (int connectionId, object target) 
        {
            switch (connectionId)
            {
            case 1:
            this.Stack = ((System.Windows.Controls.StackPanel)(target));
            return;
            case 2:
            this.lblColor = ((System.Windows.Controls.Label)(target));
            return;
            case 3:
            this.lstColorPicker = ((ColorPicker.ColorPickerControl)(target));
            return;
            }
            this._contentLoaded = true;
        }
    }
}

It can be seen that this source file provides the missing parts, most noticeably the InitializeComponent() method and also notice that there are a few instance fields which represent the components within the XAML file. So this is how both the code behind and XAML files are compiled to form a single assembly with all the required information.

Demo Of The Attached Application

The last thing I should show is the running app. That part is probably not so important, as it is the concepts I was trying to share really.

But for completeness sake, here is a screen shot.

remember the ColorPicker is simply a specialized ListBox really. Quite impressive no?

Image 2

That's it

Well although we've only created a simple control and used it within a single XAML page, I hope you can see that there are quite a few core concepts that were covered here.

So What Do You Think ?

I would just like to ask, if you liked the article please vote for it, as it lets me know if the article was at the right level or not.

Conclusion

I have quite enjoyed constructing this article. I hope you liked it. I think it will help you a lot when you get time to do some XAML / WPF type apps. I have only just started out with XAML and I truly believe it is set to totally change the sort of applications we are all going to see.

History

  • v1.1 01/03/07: Fixed the custom event with custom event args issue. Thanks very much to Steve Maier.
  • v1.0 01/03/07: Initial Issue

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
GeneralMy vote of 5 Pin
Member 86113565-Feb-12 0:34
Member 86113565-Feb-12 0:34 
GeneralThat's Bright Pin
Member 418359013-Jul-10 7:35
Member 418359013-Jul-10 7:35 
GeneralRe: That's Bright Pin
Sacha Barber13-Jul-10 9:14
Sacha Barber13-Jul-10 9:14 
GeneralSacha Barber Pin
man2tek7-Nov-09 17:31
man2tek7-Nov-09 17:31 
GeneralRe: Sacha Barber Pin
Sacha Barber7-Nov-09 20:23
Sacha Barber7-Nov-09 20:23 
GeneralConsuming internal custom controls Pin
Heath_H10-Jun-09 5:26
Heath_H10-Jun-09 5:26 
GeneralRe: Consuming internal custom controls Pin
Sacha Barber10-Jun-09 8:30
Sacha Barber10-Jun-09 8:30 
GeneralGood material to learn WPF Pin
FabianSilva2-Apr-09 11:49
FabianSilva2-Apr-09 11:49 
GeneralRe: Good material to learn WPF Pin
Sacha Barber2-Apr-09 21:36
Sacha Barber2-Apr-09 21:36 
GeneralRe: Good material to learn WPF Pin
man2tek7-Nov-09 17:34
man2tek7-Nov-09 17:34 
GeneralIt's really good staf! Pin
MadArtSoft12-Aug-08 23:00
MadArtSoft12-Aug-08 23:00 
GeneralRe: It's really good staf! Pin
Sacha Barber13-Aug-08 0:21
Sacha Barber13-Aug-08 0:21 
GeneralRe: It's really good staf! Pin
man2tek7-Nov-09 17:43
man2tek7-Nov-09 17:43 
GeneralI want cloud to come up when i move mouse on a button -- in WPF Pin
Dinesh Patel25-Jul-08 2:35
Dinesh Patel25-Jul-08 2:35 
GeneralRe: I want cloud to come up when i move mouse on a button -- in WPF Pin
Sacha Barber25-Jul-08 2:50
Sacha Barber25-Jul-08 2:50 
GeneralExpression Blend and Customer User Control Pin
BlitzPackage19-May-08 13:13
BlitzPackage19-May-08 13:13 
GeneralRe: Expression Blend and Customer User Control Pin
Sacha Barber19-May-08 20:09
Sacha Barber19-May-08 20:09 
GeneralRe: Expression Blend and Customer User Control Pin
BlitzPackage19-May-08 22:38
BlitzPackage19-May-08 22:38 
GeneralRe: Expression Blend and Customer User Control Pin
Sacha Barber20-May-08 2:44
Sacha Barber20-May-08 2:44 
QuestionWPF resources from dll Pin
dmitrdv9-Apr-08 18:49
dmitrdv9-Apr-08 18:49 
GeneralRe: WPF resources from dll Pin
Sacha Barber9-Apr-08 20:32
Sacha Barber9-Apr-08 20:32 
Questionadd it to the toolbox? Pin
ferdna24-Aug-07 7:34
ferdna24-Aug-07 7:34 
AnswerRe: add it to the toolbox? Pin
Sacha Barber24-Aug-07 21:35
Sacha Barber24-Aug-07 21:35 
GeneralGot your custom stuff working. Pin
Steve Maier1-Mar-07 7:57
professionalSteve Maier1-Mar-07 7:57 
GeneralRe: Got your custom stuff working. Pin
Sacha Barber1-Mar-07 9:06
Sacha Barber1-Mar-07 9:06 

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.