Posted 595 days ago
It'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.
Posted 604 days ago
Overview
Rubber-banding is a very simple and familiar concept in most graphical applications where the outline of a shape to be drawn is painted to the screen, following the mouse cursor so the end user can visualize exactly where the shape will be placed.
This tutorial will guide you through implementing a very simple WPF window containing a canvas, with rubber-banding rectangles. Adding additional shapes is then a trivial matter, which I'll address in a follow up to this post.
Posted 621 days ago
Microsoft has officially released the .NET Framework 3.0! This release comes with a handful of new technologies that I'm pretty pumped about, particularly WPF - The Windows Presentation Foundation. WPF is, to greatly oversimplify it, their new framework for developing Windows GUI applications.
I've been teaching WPF this semester at Clarkson and am really thrilled with its capabilities. It's by far the most well thought out and put together GUI API I've had the pleasure of using. Now that I've got my website back up and running, I do plan to post several articles and projects in WPF so you all can get started too.
You can find download links for the framework, SDKs, and visual Studio extensions on this blog post on the .NET FX3 website.
add to
del.icio.us