Posted 527 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 536 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 912 days ago
I ported the pyBlosxom trackback plugin this morning, so I now have trackback support re-enabled! Very exciting.
Now that Blosxonomy has comments and trackback support, I'm going to release version 0.5 at Blosxonomy.com
Posted 913 days ago
I implemented a simple comments plugin tonight for Blosxonomy - loosely based on the pyBlosxom comment plugin. pyBlosxom's comment plugin is a little more involved than I was shooting for, as one of Blosxonomy's goals is simplicity. Mine simply checks the post data for a comment, verifies all the information is there, and writes out a comment file with a simple format:
Comment Author Author's email Comment body
The plugin requires the current code base, which I'm going to post over at Blosxonomy sometime tomorrow (I still need to do some sanity checking on it). In the meantime though, feel free to start leaving comments!
Posted 915 days ago
I recently decided it'd be fun to learn the Ruby language, and that a worthy undertaking to do so would be to port pyBlosxom - which I've been using for a little over a year now. The result is something I'm quite proud of, and am still actively working on -- Blosxonomy.
Posted 925 days ago
I wrote up an ATOM 1.0 renderer for pyBlosxom tonight based on Blake Winton / Will Guaraldi's RSS2Renderer plugin.
AtomRenderer renders a valid ATOM 1.0 feed without the need for ATOM flavour templates. You can access mine at http://www.timfanelli.com/atom. You can validate my output here.
You can download AtomRenderer here
Update: (Nov 1) Just got an email from Will Guaraldi letting me know that pyBlosxom 1.3 will have a native ATOM flavour for generating feeds -- so this plugin is really only useful for pyBlosxom 1.2 and earlier :).
Posted 925 days ago
I added a feature to folksonomy tonight that lets you explicitly define relationships between stories using Folksonomy. To do this, just add a comma separated list of / for the entries you want to be related to. For instance, I'm forcing this entry to be related to my Folksonomy Update post, by adding the following to its metadata section:
#related item/folksonomy_update.txt
You'll notice that it is the first entry in my related stories section -- explicit relations are evaluated before any others are inferred, and therefore take precedence.
This feature is available in Folksonomy 1.4
Posted 926 days ago
I wrote a quick plugin this morning to generate links to related books (or other items) at Amazon.com using Amazon's Web Services (AWS), based on your entry's tags.
The plugin queries AWS's ItemSearch for each tag in an entry, and then sorts out the results in decreasing order of how many tags a product was returned in. Links are then generated using Amazon's product images for the top N results, where N is some configurable number of products. All the links are generated using your own Amazon Associates ID as well.
You can download it here
Posted 927 days ago
Folksonomy 1.2 is avaialble for download
Folksonomy is a pyBlosxom plugin that infers relationships between your blog entries based on tags; providing links to related stories, links to related tags, a tag cloud, and the ability to search for entries by tag.
What's New: As of Folksonomy 1.2, there are no longer any dependencies on other plugins. Folksonomy is now a complete tagging solution for pyBlosxom. If you're already using Tags or Tag Cloud, simple replace those plugins with Folksonomy, and you'll still have all the same functionality you had before!
Posted 934 days ago
I installed the tags plugin tonight, and tagged most of the entries on my site. I decided to replace categories all together, and as such, you'll notice a tag cloud on the left side of my site.
I wrote a simple pyBlosxom plugin to generate the tag cloud, to be used in conjunction with the tags plugin. You can download it here: tag cloud plugin.
Posted 937 days ago
That's right... word counter. Jeanna's students will know what I'm talking about.
For those of you that don't, this word counter, for one whole semester, was my pride and joy. Jeanna used this toy example program to help demonstrate how even the simplest programs, if not properly thought out and designed, can cause you massive headaches in a world where software consumers can't make up their minds. Jeanna would change the definition of what constitutes a "word" weekly, and off we'd go to adjust our counters - keeping in mind the end goal of having the fastest and most correct counter.
I'm pleased to say that my counter -- fully object oriented and extremely well designed -- was the second fastest in the class; beat out only by a man who some know as a maniac, others as my then-nemesis (due to our massively conflicting approaches to this project). He wrote his counter as a state machine, driven by goto's, in C. While unbeatable in speed, the low-level implementation required a complete re-write with each iteration. Mine only required a single line change, perhaps two or three depending how drastic the definition change was. But at most, I spent minutes each week on this project after my initial implementation*.
This application was also the start of my years-long const correctness frenzy. Check out the source -- it's const correct
to the extreme!
You can download it here: word counter
* not including the time spent doing optimizations.
add to
del.icio.us