I've been learning WPF for a while now (using WPF Unleashed - a great book by Adam Nathan), and it's really awesome. I'm guessing that soon enough there will be little reason to develop new smart-client applications using WinForms. WPF's API is really well-thought of and easy to work with, and it also makes for such pretty results.
One thing I had an annoying issue with was WPF's commands. Commands are actions in your window (or any other element) that accomplish something and are not specifically related to any button or menu click event.
For example, suppose you have an InkCanvas, which is basically a drawing pad which you can draw stuff on with your mouse, and you want to be able to clear it once in a while.
You could add a "clear" button to the window and clear the canvas on this button's click event, but what if you wanted to have a right-click menu that can do the same? And what if you want it get cleared on "Shift-C"? Using the Click event creates an undesired coupling between the button and the canvas in this case.
So I tried to do this using the Command feature, which allows you to have loose coupling in cases like this. At first, my code looked like this:
<Window x:Class="WindowsApplication1.CanvasExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WindowsApplication1" Height="300" Width="300"
>
<Window.CommandBindings>
<CommandBinding Command="ClearCanvas" Executed="ExecuteClearCanvas"/>
</Window.CommandBindings>
<InkCanvas x:Name="MainCanvas">
<InkCanvas.ContextMenu>
<ContextMenu>
<MenuItem Header="Clear" Command="ClearCanvasCommand" />
</ContextMenu>
</InkCanvas.ContextMenu>
</InkCanvas>
</Window>
With the code-behind being:
public partial class CanvasExample : System.Windows.Window
{
public CanvasExample()
{
InitializeComponent();
}
void ExecuteClearCanvas(object sender, ExecutedRoutedEventArgs e)
{
MainCanvas.Strokes.Clear();
}
}
The XAML part contains an InkCanvas, and inside it a context menu (a right click menu) with a "Clear" item. This "Clear" Item is bound to a "ClearCanvas" command, which calls the ExecuteClearCanvas method on its Executed event. Seems OK, no?
This would compile, but will not run. A very unpleasant (and unclear) XamlParseException was thrown at me and I had to duck (har har).
I understand that this is a new technology, but man, I hate it when I have to figure out my own stupidity on my own. I scratched my head for a while, and then I went and read some more about WPF commands.
Apparently, what I didn't realize was the wrongness of this line:
<CommandBinding Command="ClearCanvas" Executed="ExecuteClearCanvas"/>
The "Command" attribute here actually expects something that stands for a real object that implements ICommand. This does not mean that I have to implement ICommand on my own, but I do have to add the following lines to my code-behind class:
namespace WindowsApplication1
{
public partial class CanvasExample : System.Windows.Window
{
public readonly static RoutedUICommand ClearCanvasCommand;
static CanvasExample()
{
ClearCanvasCommand = new RoutedUICommand("ClearCanvas", "ClearCanvas", typeof(CanvasExample));
}
....
}
}
Here I'm adding the command object to my class, which is an instance of the RoutedUICommand object (this is why I don't have to implement ICommand myself, RoutedUICommand does this already). Now I just have to fix my XAML to refer to this object.
1 <Window x:Class="WindowsApplication1.CanvasExample"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:local="clr-namespace:WindowsApplication1"
5 Title="WindowsApplication1" Height="300" Width="300"
6 >
7
8 <Window.CommandBindings>
9 <CommandBinding Command="local:CanvasExample.ClearCanvasCommand" Executed="ExecuteClearCanvas"/>
10 </Window.CommandBindings>
11
12 <InkCanvas x:Name="MainCanvas">
13 <InkCanvas.ContextMenu>
14 <ContextMenu>
15 <MenuItem Header="Clear" Command="local:CanvasExample.ClearCanvasCommand" />
16 </ContextMenu>
17 </InkCanvas.ContextMenu>
18 </InkCanvas>
19
20 </Window>
21
Instead of the "ClearCanvas" string you see this - local:CanvasExample.ClearCanvasCommand. This is our way to refer to the actual command object we created on our code-behind class. The "local" refers to an XML Namespace, which we declared on line 4 - This allows us to import the WindowsApplication1 namespace to our XAML, so whenever we're using "local" we're actually referring to this CLR namespace. At this point everything worked and I was very pleased.
Now, if I wanted the canvas to get cleared on Shift-C, I would only need to add this to my XAML:
<Window.InputBindings>
<KeyBinding Command="local:CanvasExample.ClearCanvasCommand" Modifiers="Shift" Key="C"/>
</Window.InputBindings>
It's important to note that we don't have to create our own command objects for all the command we are using. WPF has many built-in commands, that reside in static classes such as ApplicationCommands and EditingCommands. They contain useful commands such as "Close", "Help" or "Copy" and "Paste".
To conclude, I feel that WPF commands can help you create nicely decoupled applications when you know how to use them. If you don't, don't expect nice error reporting from the current version...
This concludes my first post about WPF. Since I'm really hooked up on this technology, I guess you could expect more in the future. Oh, and This article has more about WPF commands. Enjoy!