Adding Custom Controls to XAML

In C#, Programming, WPF by timfanelliLeave a Comment

‘s often useful to either extend WPF controls or create custom controls to supplement to functionality provided to you by the WPF classes. In this brief tutorial, we’ll refactor the code from the first part of the Drawing a RubberBand in WPF tutorial and see how we can add use a custom class in an XAML document.

Refactoring

We’ve already built a simple window class that contains a System.Windows.Controls.Canvas instance, and registered several mouse handlers with it to draw the rubber band. Instead of instantiating a base Canvas instance though and telling it how to behave (i.e., by registing external handlers), we’ll extend the Canvas class, and move the handlers inside it so that’ll it’ll know how to behave.

The first step is to create a new User Control (WPF) type to our project. This will create an XAML document and associated code-behind file for a class that extends UserControl. Simply changing references to UserControl to Canvas will result in a class that extends Canvas, like so:

RubberBandingCanvas.xaml:
<Canvas x:Class="Rubber_Band_Tutorial.RubberBandingCanvas"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	Background="White">

</Canvas>

RubberBandingCanvas.xaml.cs:
public partial class RubberBandingCanvas : Canvas
{
    public RubberBandingCanvas()
    {
        InitializeComponent();
    }
}

As you can see, the XML root element in our XAML is a Canvas type, which matches the base-class type specified in our partial class definition in the code-behind. Also notice the addition of the Background="White" attribute to the canvas in the XAML… recall from part 1 that a canvas with no background does not respond to mouse event handlers (still haven’t figured that one out, btw).

The next thing to do is move our mouse handlers out of the Window class, and into our new Canvas-derived RubberBandingCanvas. I’ll also update the methods to be static, which’ll help minimize the application footprint if I were to allow use of multiple canvases.RubberBandingCanvas.xaml.cs: public partial class RubberBandingCanvas : Canvas
{
public RubberBandingCanvas()
{
InitializeComponent();
this.MouseLeftButtonDown += OnLeftDown;
this.MouseLeftButtonUp += OnLeftUp;
this.MouseMove += OnMouseMove;
}

private Point mouseLeftDownPoint;
private Shape rubberBand = null;

protected static void OnLeftDown(object sender, MouseEventArgs args)
{
RubberBandingCanvas canvas = sender as RubberBandingCanvas;
if (!canvas.IsMouseCaptured)
{
canvas.mouseLeftDownPoint = args.GetPosition(canvas);
canvas.CaptureMouse();
}
}

protected static void OnLeftUp(object sender, MouseEventArgs args)
{
RubberBandingCanvas canvas = sender as RubberBandingCanvas;
Shape rubberBand = canvas.rubberBand;
if (canvas.IsMouseCaptured && rubberBand != null)
{
canvas.Children.Remove(rubberBand);
rubberBand = null;
canvas.ReleaseMouseCapture();
}
}

protected static void OnMouseMove(object sender, MouseEventArgs args)
{
RubberBandingCanvas canvas = sender as RubberBandingCanvas;
if (canvas.IsMouseCaptured)
{
Point currentPoint = args.GetPosition(canvas);
Point mouseLeftDownPoint = canvas.mouseLeftDownPoint;
if (canvas.rubberBand == null)
{
canvas.rubberBand = new Rectangle();
canvas.rubberBand.Stroke = new SolidColorBrush(Colors.LightGray);
canvas.Children.Add(canvas.rubberBand);
}

double width = Math.Abs(mouseLeftDownPoint.X - currentPoint.X);
double height = Math.Abs(mouseLeftDownPoint.Y - currentPoint.Y);
double left = Math.Min(mouseLeftDownPoint.X, currentPoint.X);
double top = Math.Min(mouseLeftDownPoint.Y, currentPoint.Y);
canvas.rubberBand.Width = width;
canvas.rubberBand.Height = height;
Canvas.SetLeft(canvas.rubberBand, left);
Canvas.SetTop(canvas.rubberBand, top);
}
}
}

The code is almost identical to the handlers we wrote last time, with the exception that member variables are resolved against the canvas object that raised the event (i.e., the sender paramter of the handler method).

Adding a Custom Class to a XAML

The only thing left to do is to update my main window’s XAML to use the new RubberBandingCanvas class instead of the WPF’s base Canvas class.

This is a simple two step process… the first is to add an XML Namespace declaration that will map my CLR-Namespace to an XML namespace, effectively giving me access to my class names as valid XML element names.

Window1.xaml:
<Window x:Class="TimFanelli.WPF.Tutorials.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Rubber_Band_Tutorial;assembly="
    Title="Rubber_Band_Tutorial" Height="300" Width="300"
    >
	<DockPanel LastChildFill="True">
		<ToolBarTray DockPanel.Dock="Top" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
			Removed for brevity...
		</ToolBarTray>

		<local:RubberBandingCanvas/>
	</DockPanel>
</Window>

The first bold line give me access to public members of the Rubber_Band_Tutorial clr-namespace via the local xml namespace. Then to add a RubberBandingCanvas to the windows dock-panel, I simply add it the same way I would any other element.

Conclusion

Most of this post was concerned with setting up the derived class we used, however the real goal was to add my custom class to the Window1 XAML document, which you can see if a very simple task. This method will work for not only any custom or derived controls you write, but indeed any CLR object with a default constructor.

It’s also worth noting, since we didn’t use the feature explicitly, any public CLR property (or dependency property) you expose in your objects can be set using standard XML syntax as well, with the property name used as an xml-attribute name on the element in question.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.