RSS 2.0
# Friday, May 25, 2007

The source code for this segment is available at http://www.cavinconsulting.com/Code/Scribble6.zip.

Now that we have a functioning application that loads and saves scribble documents, it's time to take advantage of some of the new capabilities of WPF. You have to admit, we haven't really done anything so far that couldn't be accomplished by the old non-declarative programming practice. Since someone (Don Box) tells us that the "declarative" programming model is "better" than the non-declarative model, we plan on embracing it whole-heartedly. So, what can we do with it?

We have already seen some examples in our previous steps. Using the Command= syntax, we allow the code behind to ignore how that code is being invoked. We use the same command handler whether File->New is clicked on a menu or the user clicks the NewFile button in a toolbar. Big deal. We could have done that before.

How about this? How about we use some clever WPF tidbits to allow UI elements to manage other UI elements? Here is an area that has long been a problem. A designer says, "I want the background color of my button to be the same as what the user chooses for his font color." Then, a programmer goes into a dim cubicle for a couple of days, writes some code, drinks some coffee, and viola, a masterpiece of interactive UI is produced. But, should a bunch of code behind really be necessary for the designer to apply logic that is strictly UI related? Shouldn't the programmers be busy doing things like drinking coffee and automatically updating the Customers table whenever they get a Twitter notification?

Clearly, the answers are no/yes (sorry, non-parallel sentence construction). No, designers shouldn't have to wait on programmers to add UI related code. Yes, programmers shouldn't be spending a bunch of time writing UI related code. Or, at least, that's what we are told.

Take our scribble tutorial. Let's add the standard View menu. These menus usually have entries to turn on or off optional toolbars and status bars. Items that are visible usually have checks next to them. Items that are invisible usually have no checks next to them.

We could implement this by harnessing the MenuItem's Checked and Unchecked events and writing 3 lines of code to set the appropriate toolbar/statusbar visibility according to whether the MenuItem just got checked or unchecked. Clearly, this is unacceptable. The UI is beholden to codebehind for simple UI interaction. We can accomplish this whole process within XAML, and it's pretty simple (though not as simple as it should be, given the version of the designer I'm using).

First, we need to add our view menu:

<MenuItem Header="_View">
  <MenuItem Header="_File Toolbar" x:Name="_viewFileBar" IsCheckable="True" IsChecked="True"/>
  <MenuItem Header="_Ink ToolBar" x:Name="_viewInkBar" IsCheckable="True" IsChecked="True"/>
  <MenuItem Header="Status _Bar" x:Name="_viewStatusBar" IsCheckable="True" IsChecked="True"/>
</MenuItem>

From here, we need to "bind" the visibility of each toolbar/statusbar to the IsChecked property of the appropriate MenuItem. Unfortunately, IsChecked is a boolean and the Visibility property of a MenuItem is a Visibility enum. So, Microsoft was kind enough to provide us with a mechanism to convert between booleans and visibilities. They give us a type converter called BooleanToVisibilityConverter. In order to use this converter, however, we have to put these lines somewhere in the Window element of our xaml file:

<Window.Resources>
  <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>

Now, we can make use of this converter by adding the following Visibility property to the toolbars and status bar:

<ToolBar Visibility="{Binding Path=IsChecked, 
  Converter={StaticResource BooleanToVisibilityConverter},
  ElementName=_viewFileBar}">
<ToolBar Visibility="{Binding Path=IsChecked, 
  Converter={StaticResource BooleanToVisibilityConverter}, 
  ElementName=_viewInkBar}">
<StatusBar DockPanel.Dock="Bottom" Visibility="{Binding Path=IsChecked, 
  Converter={StaticResource BooleanToVisibilityConverter}, 
  ElementName=_viewStatusBar}">

That's it. No code. Compile and run, and checking the view items will change the visibility of the corresponding toolbars or statusbars. The syntax is a little odd, and I'm hoping that there will be better tool support for this in the future. But, for now, it isn't that horrible to decipher the {...} syntax. This can be extended to include all sorts of properties, including font size, background color, whether or not the text flashes, etc.

Next time, I'm hoping that we can look at how to attach WPF properties (like IsChecked) to .NET 2.0 Settings (Properties.Settings) and have them dynamically save and load so the application remembers the user preferences each time the application starts and stops.

Friday, May 25, 2007 1:55:35 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
WPF
# Tuesday, May 22, 2007

The source for this segment is available at http://www.cavinconsulting.com/Code/Scribble5.zip.

The original Scribble tutorial had two options for pen thickness, thick or thin. Since this is a WPF application, we should probably strive for something a little more modern with respect to our drawing capabilities. So, let's add toolbar buttons (and menu items) to allow the user to choose pen thickness and pen color.

The first thing we need to do is add the commands we'll invoke to our ScribbleCommands class -- we'll call them PenColor and PenWidth. Add the following lines to ScribbleCommands.cs:

private static RoutedUICommand penWidth = null;
public static RoutedUICommand PenWidth
{
    get
    {
        lock (syncRoot)
        {
            if (null == penWidth)
            {
                penWidth = new RoutedUICommand(
                    Properties.Resources.PenWidthText,
                    Properties.Resources.PenWidthName,
                    typeof(ScribbleCommands));
                penWidth.InputGestures.Add(new KeyGesture(Key.W, ModifierKeys.Control | ModifierKeys.Shift));
            }
        }
        return penWidth;
    }
}
private static RoutedUICommand penColor = null;
public static RoutedUICommand PenColor
{
    get
    {
        lock (syncRoot)
        {
            if (null == penColor)
            {
                penColor = new RoutedUICommand(
                    Properties.Resources.PenColorText,
                    Properties.Resources.PenColorName,
                    typeof(ScribbleCommands));
                penColor.InputGestures.Add(new KeyGesture(Key.C, ModifierKeys.Control | ModifierKeys.Shift));
            }
        }
        return penColor;
    }
}

Add the appropriate entries into the Resources string table for PenColorName, PenColorText, PenWidthName, and PenWidthText.

Now, we add the command handlers to the window1.xaml.cs file:

public void OnPenWidth(object sender, RoutedEventArgs e)
{
    NotImplemented();
}

public void OnPenColor(object sender, RoutedEventArgs e)
{
    NotImplemented();
}

And route the commands to our command handlers in the window1.xaml file:

<Window.CommandBindings>  
  ... existing command bindings ...
  <CommandBinding Command="{x:Static scribble:ScribbleCommands.PenWidth}" 
                  Executed="OnPenWidth" 
                  CanExecute="CanAlwaysExecute"/>
  <CommandBinding Command="{x:Static scribble:ScribbleCommands.PenColor}" 
                  Executed="OnPenColor" 
                  CanExecute="CanAlwaysExecute"/>
</Window.CommandBindings>

Finally, we can add some UI elements to trigger our commands. First, we'll add a menu by adding another MenuItem below the File MenuItem we already have in the window1.xaml file:

<MenuItem Header="_Pen">
  <MenuItem Command="{x:Static scribble:ScribbleCommands.PenWidth}" />
  <MenuItem Command="{x:Static scribble:ScribbleCommands.PenColor}" />
</MenuItem>

Now, we'll add another toolbar with buttons to perform the same actions:

<ToolBar>
  <Button Command="{x:Static scribble:ScribbleCommands.PenWidth}">
    <Image Width="20" Height="20" Source="Resources/penwidth.png" />
  </Button>
  <Button Command="{x:Static scribble:ScribbleCommands.PenColor}">
    <Image Width="20" Height="20" Source="Resources/pencolor.png" />
  </Button>
</ToolBar>

That's it for the framework. Now let's handle the commands by adding something to OnPenColor and OnPenWidth.

Unfortunately, in their haste to get the .NET Framework 3.0 out the door, Microsoft did a woefully inadequate job of supporting the sorts of common dialogs a modern programmer expects. For example, I have yet to find an out of the box .NET 3.0 Color Picker (of course, it's entirely possible that there is one, I just can't find it). We have two choices to fix this: 1) p/Invoke the ChooseColor function, or 2) Call the old System.Windows.Forms.ColorDialog. I'm choosing 2 for the time being. However, since the InkCanvas speaks in Color terms of System.Windows.Media.Color objects and the color dialog speaks in terms of System.Drawing.Color, we have some translation to do:

public void OnPenColor(object sender, RoutedEventArgs e)
{
    System.Windows.Forms.ColorDialog colorDialog = new System.Windows.Forms.ColorDialog();
    Color penColor = inkCanvas.DefaultDrawingAttributes.Color;
    colorDialog.Color = System.Drawing.Color.FromArgb(
        penColor.A,penColor.R,penColor.G,penColor.B);
    if (colorDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
    {
        System.Drawing.Color selectedColor = colorDialog.Color;
        penColor = Color.FromArgb(
            selectedColor.A, selectedColor.R, 
            selectedColor.G, selectedColor.B);
        inkCanvas.DefaultDrawingAttributes.Color = penColor;
    }
}

Not exactly pretty, but functional never the less. Let's move on to the pen width. First, we'll have to add a form to show the user, allowing them to change the pen width. Add a new Xaml Window and name it PenWidthForm. In the designer for the PenWidthForm.xaml (it finally works again since we don't have any internal references to commands). But now we spend a bunch of time understanding the new layout framework in .NET 3.0. Gone are the .NET WinForms resizing/anchoring that we finally understand. Now, we have a whole new collection of layout panels, layout-specific properties, attached (parent) properties, and the like. It looks like we have our work cut out for us.

We'll start with attempting a form with a label, text box, ok, and cancel. What could be easier?

Here's what I ended up with. You, of course, may choose a different path. I chose to replace the default with a . From here, I can dock the OK and Cancel buttons to the bottom and fill the remainder of the area with the payload of the form. The skeleton looks like this:

<DockPanel>
  <StackPanel DockPanel.Dock="Bottom" HorizontalAlignment="Right" Orientation="Horizontal">
  </StackPanel>
  <Grid>
  </Grid>
</DockPanel>

Notice that I dock a to the bottom. This is so I can add buttons to it and they will stack (horizontally) across the panel. To add the buttons, I do this:

<DockPanel>
  <StackPanel DockPanel.Dock="Bottom" HorizontalAlignment="Right" Orientation="Horizontal">
    <Button Margin="4" IsDefault="True" TabIndex="2">_OK</< span>Button>
    <Button  Margin="4" IsCancel="True" TabIndex="3">_Cancel</< span>Button>
  </StackPanel>
  <Grid>
  </Grid>
</DockPanel>

Now we can add the payload. For this, I chose to use a  with three rows and two columns. The left column width will be set to Auto while the right column width is set to *. The middle row height is set to Auto, while the first and third rows are set to *. The result of the grid section looks like this:

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="Auto"/>
    <ColumnDefinition Width="*" />
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
    <RowDefinition Height="*" />
    <RowDefinition Height="Auto" />
    <RowDefinition Height="*" />
  </Grid.RowDefinitions>
  <Label Grid.Row="1" Grid.Column="0" TabIndex="0">Pen _Width</< span>Label>
    <TextBox Grid.Row="1" Grid.Column="1" TabIndex="1" Name="_penWidthBox"></< span>TextBox>
</Grid>

Now, we hook up the event handlers, and the whole thing looks like this:

<DockPanel>
  <StackPanel DockPanel.Dock="Bottom" HorizontalAlignment="Right" Orientation="Horizontal">
    <Button Margin="4" IsDefault="True" TabIndex="2" Click="OnOk">_OK</< span>Button>
    <Button  Margin="4" IsCancel="True" TabIndex="3">_Cancel</< span>Button>
  </StackPanel>
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto"/>
      <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition Height="*" />
      <RowDefinition Height="Auto" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Label Grid.Row="1" Grid.Column="0" TabIndex="0">Pen _Width</< span>Label>
    <TextBox Grid.Row="1" Grid.Column="1" TabIndex="1" Name="_penWidthBox"></< span>TextBox>
  </Grid>
</DockPanel>

We will have to add a few lines of code to the codebehind of the form:

private double _penWidth;
public double PenWidth
{
    get { return _penWidth; }
    set { _penWidth = value; }
}
public PenWidthForm(double penWidth)
{
    _penWidth = penWidth;
    InitializeComponent();
    _penWidthBox.Text = penWidth.ToString();
}
public void OnOk(object sender, RoutedEventArgs e)
{
    _penWidth = Convert.ToDouble(_penWidthBox.Text);
    this.DialogResult = true;
}

And we call the form from the PenWidth command:

public void OnPenWidth(object sender, RoutedEventArgs e)
{
    DrawingAttributes drawingAttributes = inkCanvas.DefaultDrawingAttributes;
    PenWidthForm penWidthForm = new PenWidthForm(drawingAttributes.Width);
    bool? result = penWidthForm.ShowDialog();
    if (result.HasValue && result.Value == true)
    {
        inkCanvas.DefaultDrawingAttributes.Width = penWidthForm.PenWidth;
        inkCanvas.DefaultDrawingAttributes.Height = penWidthForm.PenWidth;
    }
}

That's it. Pretty simple, but the layout mechanism is definitely something I'm going to have to get used to. Considering that a simple modal dialog box with and OK and Cancel button is something we write all the time, I would think the mechanism for writing something like this would be a little more simple than it is. Of course, I just got really comfortable with the .NET 2.0 layout controls, so maybe I'm a little slow.

Tuesday, May 22, 2007 7:36:03 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
WPF

Source code for this segment is available at http://www.cavinconsulting.com/Code/Scribble4.zip.

After our first few segments concentrating on framework, it's time to attach some actions to our commands. We have a decision to make. Do we support multiple open scribble documents or do we only allow a single item. For now, let's stick with only a single document. We can add multiple document support later.

Let's start with Save. We'll use the built-in support from the InkCanvas to load and save the InkCanvas' strokes collection. I know Jean-Paul Boodhoo will lie awake at night due to the lack of separation between presentation and data. Should a windows control rerally be the keeper of the document data? I have a feeling we'll revisit this when we want to add multiple views on the same stroke collection. Of course, we'll see.

Here's the code I wrote corresponding to the OnFileSaveAs:

private string _filename = string.Empty;
public void OnFileSaveAs(object sender, RoutedEventArgs e)
{
    SaveAs();
}
private bool SaveAs()
{
    SaveFileDialog saveDialog = new SaveFileDialog();
    saveDialog.DefaultExt = ".isf";
    saveDialog.AddExtension = true;
    saveDialog.Title = "Save Ink";
    saveDialog.OverwritePrompt = true;
    saveDialog.ValidateNames = true;
    saveDialog.CheckPathExists = true;
    saveDialog.CreatePrompt = false;
    saveDialog.Filter = "Ink Files (*.isf)|*.isf|All Files (*.*)|*.*";

    bool? saveResult = saveDialog.ShowDialog(this);
    if (saveResult.HasValue && saveResult.Value == true)
    {
        _filename = saveDialog.FileName;
        return SaveInk();
    }
    return false;
}
private bool SaveInk()
{
    try
    {
        using (FileStream writer = new FileStream(_filename, FileMode.OpenOrCreate))
        {
            inkCanvas.Strokes.Save(writer);
            writer.Close();
        }
    }
    catch (Exception ex)
    {
        Trace.Fail(ex.Message, ex.StackTrace);
        return false;
    }
    return true;
}

From here, it's easy to add the functionality for OnFileSave:

public void OnFileSave(object sender, RoutedEventArgs e)
{
    Save();
}
private bool Save()
{
    if (String.IsNullOrEmpty(_filename))
    {
        return SaveAs();
    }
    else
    {
        return SaveInk();
    }
}

Now, let's add OnFileNew:

private bool _dirty = false;
public void OnFileNew(object sender, RoutedEventArgs e)
{
    ClearCanvas();
}
public void OnStrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e)
{
    _dirty = true;
}
private bool ClearCanvas()
{
    bool eraseAnyway = true;
    if (_dirty)
    {
        // Prompt for save
        MessageBoxResult result = MessageBox.Show(
            "Do you want to save changes?",
            "Modifications Detected",
            MessageBoxButton.YesNoCancel);

        if ((result == MessageBoxResult.Cancel) ||
            ((result == MessageBoxResult.Yes) && (!Save())))
        {
            eraseAnyway = false;
        }
    }
    if (eraseAnyway)
    {
        inkCanvas.Strokes.Clear();
        _dirty = false;
        return true;
    }
    return false;
}

Change the InkCanvas declaration in xaml to this:

<InkCanvas Name="inkCanvas" Grid.Row="2" StrokeCollected="OnStrokeCollected" />

And finally, OnFileOpen:

public void OnFileOpen(object sender, RoutedEventArgs e)
{
    if (ClearCanvas())
    {
        OpenFileDialog openDialog = new OpenFileDialog();
        openDialog.DefaultExt = ".isf";
        openDialog.AddExtension = true;
        openDialog.Title = "Load Ink";
        openDialog.CheckFileExists = true;
        openDialog.Filter = "Ink Files (*.isf)|*.isf|All Files (*.*)|*.*";

        bool? openResult = openDialog.ShowDialog(this);
        if (openResult.HasValue && openResult.Value == true)
        {
            _filename = openDialog.FileName;
            LoadInk();
        }
    }
}
private bool LoadInk()
{
    try
    {
        using (FileStream reader = new FileStream(_filename, FileMode.Open))
        {
            inkCanvas.Strokes = new StrokeCollection(reader);
            reader.Close();
        }
    }
    catch (Exception ex)
    {
        inkCanvas.Strokes.Clear();
        _dirty = false;
        Trace.Fail(ex.Message, ex.StackTrace);
        return false;
    }
    return true;
}

Pretty straight forward. We're just using the built-in serialization methods of the InkCanvas to load and save the strokes stored in the canvas. We also attach an event handler to the Canvas' StrokeCollected event to set the dirty bit (to prompt the user to save if there are changes).

Of course, there are some validations we ought to add, and Brad Abrams will get really mad when he sees the catch (Exception ex) line since you're not supposed to catch System.Exception. But, it loads and save the canvas so we can take comfort in our ability to create inky masterpieces and be able to retrieve them sometime in the future.

Tuesday, May 22, 2007 2:32:26 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
WPF
# Thursday, May 17, 2007

OK. So the problem with the mnemonics in the custom command was due to an error in my code. I was explicitly settings the Header attribute as well as binding to our custom Command class. Since the explicitly set attribute trumps the command's text property, we didn't get the benefit of the mnemonic under the 'x'. So, if you change the the contents of Properties.Resources.ExitText to "E_xit" the application will respond to Alt+F,x just like we want it to.

Now, I'm not sure how to add a mnemonic after the fact to the built-in ApplicationCommands.X objects. I can understand a reluctance on the part of the framework developers.  They have no way to determine which commands will be in the same menu, so they have no way to guarantee that each menu item has a unique mnemonic. I just wish I could find the a way to set the keyboard shortcut for the built-in commands without explicitly setting the text. That would sort of kill the automatic localization aspect of the built-in commands.

Thursday, May 17, 2007 12:11:49 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
WPF
# Friday, May 11, 2007

The source code for this segment is available at http://www.cavinconsulting.com/Code/Scribble3.zip.

OK. Last time we took a perfectly understandable (though difficult to localize) version of a menu and toolbar with mnemonics but without shortcuts and turned it into a more complex, easier to localize version of a menu and toolbar without mnemonics but with shortcuts.

Mnemonics (in the old MFC speak) are the keys you press to activate a menu item from the keyboard once a menu has focus. Shortcuts are the key sequence you press to activate a menu item, even if the menu is not active. For example, Ctrl+s is the shortcut usually corresponding to Save. However, if the File menu is open (dropped down), then the user can just hit the s key. The mnemonic for a menu item is usually a letter in the item that is underlined. I say usually because new versions of windows and frameworks have stopped underlining mnemonics unless you strategically press the Alt key at some point. I haven't figured out what triggers the visibility of the underlines. I just know when I open the File menu with Alt+f, the mnemonics are displayed as underlined characters. When I open the File menu by clicking on it, the mnemonics are not displayed.

Anyway, that is not the point of this post (mainly because I haven't figured out how to fix it yet). Instead, the point of this post is to make the Exit menu look like the rest of the menu items. That is, our current implementation of the file menu looks like this:

<MenuItem Header="_File">
  <MenuItem Command="ApplicationCommands.New" />
  <MenuItem Command="ApplicationCommands.Open" />
  <MenuItem Command="ApplicationCommands.Close" />
  <MenuItem Command="ApplicationCommands.Save" />
  <MenuItem Command="ApplicationCommands.SaveAs" />
  <Separator />
  <MenuItem Header="E_xit"
            InputGestureText="Alt-F4"
            Click="OnFileExit"
            ToolTip="Click here to exit"
            Name="fileExit"/>
</MenuItem>

I'd like it to look more like this:

<MenuItem Header="_File">
  <MenuItem Command="ApplicationCommands.New" />
  <MenuItem Command="ApplicationCommands.Open" />
  <MenuItem Command="ApplicationCommands.Close" />
  <MenuItem Command="ApplicationCommands.Save" />
  <MenuItem Command="ApplicationCommands.SaveAs" />
  <Separator />
  <MenuItem Command="ScribbleCommands.Exit" />
</MenuItem>

See the difference? Correct. The second implementation is more consistent. Consistency, of course, implies readability and maintainability. At least, that's what I've read a few places, and who am I to contradict?

First, a little about commands. WPF gives us a bunch of commands to use out-of-the-box. Some of them are in the ApplicationCommands class, but there are others as well. None of the built-in commands handles application Exit. I'm not sure why that is. That's okay, because it gives us the opportunity to learn about writing custom commands. There is a pretty comprehensive discussion about the Command Pattern and its use in WPF here. I'll wait while you read it. Done? Good. Now, in order to allow us to implement our Exit command, we will need to create something that implements ICommand and put that in between the menu item and the code to be executed.

First, let's create the command class. We'll do this by creating a new class containing our custom commands. Create a new class named ScribbleCommands. This class should look like this:

public static class ScribbleCommands
{
    private static object syncRoot = new object();

    private static RoutedUICommand exit = null;
    public static RoutedUICommand Exit
    {
        get
        {
            lock (syncRoot)
            {
                if (null == exit)
                {
                    exit = new RoutedUICommand("Exit", "Exit", typeof(ScribbleCommands));
                    exit.InputGestures.Add(new KeyGesture(Key.Q, ModifierKeys.Control));
                }
            }
            return exit;
        }
    }
}

Notice that this class exposes a static read-only property for the commands we are allowed to use -- everything else is private. Also, since WPF is inherently multi-threaded, we lock the null check and creation section so we don't accidentally return two different instances of the command object. Finally, the two hard-coded "Exit" strings should be read from the application Resources in order to make this more easily localizable. After all, that was one of the reasons we started adding these command things in the first place. This has been implemented with a Resource lookup in the attached source code.

Next, we need to hook our new command up to the Exit menu. Unfortunately, here is where things get more complicated. Sure, it was easy to attach the built-in framework commands to a button or menu. Connecting your own, however, requires a little more work. First, we have to add a reference to our own namespace in window1.xaml. I agree, this seems a bit ridiculous, but open up window1.xaml and add this line to the Window tag at the top:

xmlns:scribble="clr-namespace:Scribble" 

This tells the compiler that we can use the scribble xml namespace to designate types in our own assembly. Unfortunately, it has the side-effect of breaking the VS designer.  You'll now get the big Whoops! window any time you click in the designer. Bummer.

Next, we have to make the MenuItem point to our Exit command. To do this, change the Exit MenuItem definition to this:

<MenuItem Header="Exit" Command="{x:Static scribble:ScribbleCommands.Exit}" />

The funny {x: syntax is required to let the compiler know that we are referencing things in a non-framework location. I'm sure there is more to it than that, but for our purposes, the description is adequate.

Finally, we need to tell the application where to route the Exit event. To do this, add the following line to the Window.CommandBindings list:

<CommandBinding Command="{x:Static scribble:ScribbleCommands.Exit}" 
	Executed="OnFileExit" CanExecute="CanAlwaysExecute"/>

There you go. Even though the designer is broken, the application compiles and executes as expected. It's a real bummer about the fragility of the designer, however. I guess that's why it's still a CTP and not a released product.

A couple of things to note here: 1) I still haven't figured out how to add mnemonics to the commands. Adding the '_' character to the command text does not work. 2) Adding references custom classes can be a painful process. With the buggy support in the VS designer, you never know until you run the application if the you have the references correct. If you have them wrong, you get to crawl through a bunch of InnerExceptions until you find what class failed to load and why.

What's next? How about actually adding some code to the event handlers and finally getting a "complete" application?

Friday, May 11, 2007 4:45:50 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
WPF
# Thursday, May 10, 2007

Source for this entry is available at http://www.cavinconsulting.com/Code/Scribble2.zip.

OK. Up to this point we've managed to get an application running and it actually acts a little like the old MFC Scribble tutorial. Let's continue on this path adding some functionality to the application. The original tutorial starts out by designing a document class named CScribbleDoc. The goal of this class is to store a collection of stroke objects in a serializable manner (can load from and save to disk). This was there to introduce the user to the MFC serialization framework and also because MFC didn't have anything like the new Ink Canvas control.

Without sparking a religious debate about whether the data should be stored in a UI object (the InkCanvas) or in a more tier-friendly business object (CScribbleDoc), I'm going to defer adding the complexity of a document object. I plan on adding some discussion on the different forms of document serialization in future installments. Feel free to agree or disagree, this is just the path I plan on taking.

Today, we add the ability to load and save your scribbled masterpiece. The first step is to add menu items for New, Open, Save, and Save As. To do this we could open window1.xaml in the designer select the File menu item and edit the Items collection in the properties window, but the version of the designer I have (codename "Cider," November CTP) fails with all sorts of cryptic messages. So, we resort to editing the xaml by hand. We turn this:

<MenuItem Header="_File">
  <MenuItem Header="E_xit"
            InputGestureText="Alt+F4"
            Click="OnFileExit"
            ToolTip="Click here to exit" 
            Name="FileExit"/>        
  </MenuItem>
</MenuItem>

into this: 

<MenuItem Header="_File">
  <MenuItem Header="_New"
            Name="fileNew" />
  <MenuItem Header="_Open"
            Name="fileOpen" />
  <MenuItem Header="_Close"
            Name="fileClose"/>
  <MenuItem Header="_Save"
            Name="fileSave" />
  <MenuItem Header="Save _As..."
            Name="fileSaveAs"/>
  <Separator />
  <MenuItem Header="E_xit"
            InputGestureText="Alt+F4"
            Click="OnFileExit"
            ToolTip="Click here to exit" 
            Name="fileExit"/>
</MenuItem>

Compiling and running at this point generates a nice looking menu complete with some of the keyboard shortcuts we all know and love (I love them, anyway -- once you take the time to learn them, you'll love them, too). I say some of the keyboard shortcuts because I can use Alt+f,s to save (that's what I have used ever since the old DOS text editor), but I can't use Ctrl+s. So far as I can tell, there is no way to attach a shortcut combination directly to a menu item. We will ignore this problem for now...

Of course, what are menu items without something happening when the user clicks on them? We can implement these commands using the same event handler mechanism we used for the Exit menu item. The resulting xaml looks something like this:

<MenuItem Header="_File">
  <MenuItem Header="_New"
            Name="fileNew" 
            Click="OnFileNew"
            ToolTip="Creates a new Scribble document" />
  <MenuItem Header="_Open"
            Name="fileOpen"
            Click="OnFileOpen"
            ToolTip="Opens an existing Scribble document" />
  <MenuItem Header="_Close"
            Name="fileClose"
            Click="OnFileClose"
            ToolTip="Closes the current Scribble document" />
  <MenuItem Header="_Save"
            Name="fileSave" 
            Click="OnFileSave"
            ToolTip="Saves the current Scribble document" />
  <MenuItem Header="Save _As..."
            Name="fileSaveAs"
            Click="OnFileSaveAs"
            ToolTip="Saves the current Scribble document under a different name"/>
  <Separator />
  <MenuItem Header="E_xit"
            InputGestureText="Alt-F4"
            Click="OnFileExit"
            ToolTip="Click here to exit" 
            Name="fileExit"/>
</MenuItem>

and the resulting code behind will have this:

public void OnFileNew(object sender, RoutedEventArgs e)
{
    // TODO:
}
public void OnFileOpen(object sender, RoutedEventArgs e)
{
    // TODO:
}
public void OnFileClose(object sender, RoutedEventArgs e)
{
    // TODO:
}
public void OnFileSave(object sender, RoutedEventArgs e)
{
    // TODO:
}
public void OnFileSaveAs(object sender, RoutedEventArgs e)
{
    // TODO:
}
public void OnFileExit(object sender, RoutedEventArgs e)
{
    this.Close();
}

Of course, windows application is complete without a toolbar (except Office 2007 -- it's too good for a toolbar). Most applications have toolbar buttons for common operations. In our list of operations above, we will have a toolbar button for New, Open, and Save. In order to add a toolbar to our UI, we will need another row in our master layout grid. The Height property of the new row will be set to "Auto" and we will put a ToolbarTray control inside the new row. A ToolbarTray is a WPF control that allows one or more toolbars to be contained inside of it and allows the standard drag and drop actions we have all come to expect from windows applications. The interesting part of window1 xaml looks like this:

<ToolBarTray Grid.Row="1">
  <ToolBar>
    <Button ToolTip="Creates a new Scribble document"
            Click="OnFileNew"
            Name="fileNewButton">
      <Image Source="Resources/new.png" />
    </Button>
    <Button ToolTip="Opens an existing Scribble document"
            Click="OnFileOpen" 
            Name="openFileButton">
      <Image Source="Resources/open.png" />
    </Button>
    <Button ToolTip="Saves the current Scribble document"
            Click="OnFileSave" 
            Name="saveFileButton">
      <Image Source="Resources/save.png" />
    </Button>
  </ToolBar>
</ToolBarTray>

The first thing to note with this code is that the tooltips and event handlers are duplicated between the menu item and the corresponding toolbar button. The fact that they use the same event handler makes a lot of sense. After all, how many times do you want to write the save logic. The duplication of the tooltip, on the other hand, indicates the likelihood for spelling mistakes and information inconsistencies to creep into your delivered product. Additionally, life becomes very difficult if you decide to localize your application to elbonian. 

The second thing to note is that the paths to the icon buttons are paths relative to the installation directory of the application. This means, in order to deploy the application, you have to distribute the icons also. In order for life to work well in the development environment, you'll have to add a build event to copy the resources to the bin directory. Since this is an unacceptable situation, we will fix this before addressing the duplication between the menus and toolbars.

To permanently attach images into our application, I am going to use the Resources tab under our project properties. Right-click on the project, select Properties and click on the Resources tab. Click Add Resource and choose Add Existing File. Search for a bitmap you would like to use for the images on the button (I find the VS2005 image library extremely useful for this), and click Open. This will copy the images to a Resources directory within your project. It will also auto-generate a class allowing you to access these images from code. It does not, however, create any way for you to access these images from the designer. You can, however, mark the newly imported images as resources for the application by changing the Build Action to Resource. I'm not sure if this embeds two copies of the same bitmap in the final assembly or not, but it appears to work.

Now, we address the code duplication between the menu items and the toolbar buttons. It turns out that the clever designers of WPF already took this into account and solved the problem by using a Command Pattern. Without going too deeply into what the Command Pattern is, let me say that it simplifies the plumbing in between what gets done (like loading a file) and all of the various ways a user is able to trigger that thing happening (like File->Open or clicking on the toolbar). Now, for the Patterns and Practices purists out there, this explanation is completely inadequate and quite possible slightly untrue. To them, never mind. To the rest of us, let us see how this applies.

Instead of hooking up event handlers directly from a button or a menu item to a specific event handler in our application, the kind folks over at WPF have created a series of commands we can use to perform the same task. "What's the difference," you might ask. The difference is that the command handles a whole bunch of the common properties necessary to be useful from anywhere (button, menu, mouse gesture, key gesture, etc.). Here's how it works. Instead of defining a button like this:

<Button ToolTip="Creates a new Scribble document"
        Click="OnFileNew"
        Name="fileNewButton">
  <Image Source="Resources/new.png" />
</Button>

We can define it like this:

<Button Command="ApplicationCommands.New">
  <Image Source="Resources/new.png" />
</Button>

Who cares? What does this buy us? Well, we can also define the File->New menu item like this:

<MenuItem Command="ApplicationCommands.New" />

What does that get us? For starters, there is no mention of the literal string "New" in there anywhere. That's right, the ApplicationCommand.New class takes care of describing itself to the file menu item and the toolbar button. Moreover, the command class is multilingual and automatically translates itself depending on the locality of the machine it is running on. Also, the command responds to standard input gestures (that's what they like to call keyboard shortcuts now). So, the File->New menu item proudly displays Ctrl+N next to it in the menu, and is invoked when the user types Ctrl+N on the keyboard.

So, we can rewrite our menu and toolbar sections to look like this:

<DockPanel Grid.Row="0" Name="dockPanel1">
  <Menu Name="menu1">
    <MenuItem Header="_File">
      <MenuItem Command="ApplicationCommands.New" />
      <MenuItem Command="ApplicationCommands.Open" />
      <MenuItem Command="ApplicationCommands.Close" />
      <MenuItem Command="ApplicationCommands.Save" />
      <MenuItem Command="ApplicationCommands.SaveAs" />
      <Separator />
      <MenuItem Header="E_xit"
                InputGestureText="Alt-F4"
                Click="OnFileExit"
                ToolTip="Click here to exit"
                Name="fileExit"/>
    </MenuItem>
  </Menu>
</DockPanel>
<ToolBarTray Grid.Row="1">
  <ToolBar>
    <Button Command="ApplicationCommands.New">
      <Image Source="Resources/new.png" />
    </Button>
    <Button Command="ApplicationCommands.Open">
      <Image Source="Resources/open.png" />
    </Button>
    <Button Command="ApplicationCommands.Save">
      <Image Source="Resources/save.png" />
    </Button>
  </ToolBar>
</ToolBarTray>

If you compile and run now, you'll see all of the same things you saw before with a couple of exceptions. First, all of the commands are disabled except the exit command. Second, there aren't any tooltips. We'll solve this problem shortly. Since tooltips were one of the main reasons I gave for not using the previous version, I'll do my best to address that in the next segment.

In order to attach the application commands to our existing event handlers, we have to add a mapping between the commands and the methods we will use to execute them. Just above the first Grid definition, add the following:

<Window.CommandBindings>
  <CommandBinding Command="New" Executed="OnFileNew" CanExecute="CanAlwaysExecute"/>
  <CommandBinding Command="Open" Executed="OnFileOpen" CanExecute="CanAlwaysExecute"/>
  <CommandBinding Command="Close" Executed="OnFileClose" CanExecute="CanAlwaysExecute"/>
  <CommandBinding Command="Save" Executed="OnFileSave" CanExecute="CanAlwaysExecute"/>
  <CommandBinding Command="SaveAs" Executed="OnFileSaveAs" CanExecute="CanAlwaysExecute"/>
</Window.CommandBindings>

Add the following method to the code behind:

void CanAlwaysExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = true;
}

These two things are all it takes to hook up our menu items and our commands to our command handlers. "So what," you ask yourself, "Is this any better than what we had before?" That's a valid question. On one hand, I can now save my document with Ctrl+s. On the other hand, I can no longer save my document with Alt+f,s. Great. I've automatically added keyboard shortcuts (automatically localized to the client machine), but I have lost the ability to set Access Keys. We've added a level of indirection between the menus/buttons to the commands that get processed, and we've encapsulated the text seen by the user into a self-localizing package. Unfortunately, with the additional layer, I have no idea where to look to add back my keyboard shortcut thing (it used to be called mnemonic). We may have to skip this for now.

In the next segment, we'll take a look at making the Exit menu item look like the rest of the menu items. I just hate inconsistency.

Thursday, May 10, 2007 10:02:03 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
WPF

Complete source code for this example is available at http://www.cavinconsulting.com/Code/Scribble1.zip.

The first step in the old Scribble tutorial was to create a new application with the AppWizard. With WPF and VS 2005, this means File->New->Project. Choose C#->.NET Framework 3.0->Windows Application (WPF).

This creates a single application window with empty contents, looking something like this:  

<Window x:Class="Scribble.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Scribble" Height="300" Width="300" >
    <Grid>
    </Grid>
</Window>

If you compile and run at this point, you get a blank application window which you can close by clicking the ‘X’ in the upper right-hand corner.

OK. Here I’m going to diverge from the original Scribble tutorial a little bit and show a simple solution to the task at hand. We’re trying to make our application accept “ink” input from the user. Back in my day, that took several steps in the tutorial. Now, however, WPF has an Ink Canvas control. So, our first implementation will be to use the Ink Canvas.

In the designer view for Window1, open the toolbox and select the “Common Controls” category. Click the Ink Canvas. Then, click somewhere on the empty Window1 window. This will insert the Ink Canvas in the window. Unfortunately, it will also automatically add a whole bunch of formatting xaml that isn’t necessarily what you want. It will default to a fixed sized with a location based on where you clicked. In our case, we want the canvas to fill the entire window. So far, I have found no easy way to do this using the clever visual designer tools in Visual Studio. The new Expression series of tools make this a little easier, but since we're doing this in Visual Studio, we'll have to deal with what we're given. We can solve the problem by editing the code by hand, changing the auto-generated xaml from something like this:

<Window x:Class="Scribble.Window1"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                Title="Scribble" Height="300" Width="300">
    <Grid>
        <InkCanvas Height="250" HorizontalAlignment="Left" Margin="-269,-226,0,0" Name="inkCanvas1" VerticalAlignment="Top" Width="292" />
    </Grid>
</Window>

to this:

<Window x:Class="Scribble.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Scribble" Height="300" Width="300">
    <Grid>
        <InkCanvas Name="inkCanvas1" />
    </Grid>
</Window>
Run and you get something like this: 

OK. That wasn’t so hard, but it doesn’t look like a windows application. So, let’s make it look like a windows application. Windows applications have menus, and status bars and things.

To do this, we’ll need someplace to put them (layout information for the controls). To make it look like a standard windows application, we’ll make the menu dock at the top and the status bar dock at the bottom. To do this, we will take advantage of the grid’s layout properties. We’ll add a single column to our grid and 3 rows.

The top and bottom rows (row 0 and 2) will be set to automatically resize based on content. That will make them only as tall as their contained controls. Row 1 will be set to * which means something like fill all the rest.

Here's another place where the VS designer is a little tedious to use. To add rows and columns to a grid, select the grid in design view. This is easier said than done. By clicking on the big white area in the window1 designer, you select the Ink Canvas, not the grid. To select the grid, open the properties window and select System.Windows.Controls.Grid in the drop-down at the top. Then, in the properties window choose ColumnDefinitions. Click Add and OK. This will add a single column to the grid. Then, choose RowDefinitions. Click Add 3 times. for the top and bottom rows set their Height property to Auto. You may have to type this in by hand. Then, hit OK. This will change the form's xaml to something like this:

<Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition />
    <RowDefinition Height="Auto" />
  </Grid.RowDefinitions>
  <Grid.ColumnDefinitions>
    <ColumnDefinition />
  </Grid.ColumnDefinitions>
  <InkCanvas Name="inkCanvas1" />
</Grid> 
 

Now, we have to make the Ink Canvas live in row 1. We do this by setting its Grid.Row property. Select the canvas in the designer and find Grid.Row in the property window. Set this value to 1. This will change the InkCanvas line (above) to this:

<InkCanvas Name="inkCanvas1" Grid.Row="1" />

Next, we add a status bar to the bottom. Here, again, the VS designer is less than helpful. To add a status bar to the bottom of the window, you could open the toolbox, open the Menus and Toolbars category, click on Status Bar, and click somewhere on the window1 design surface. This would place the status bar with default properties somewhere on the page. It would also add a bunch of grid related properties that would end looking pretty confusing. I have found it less stressful to add these sorts of things by hand rather than trying to undo all of the "helpful" things the designer adds. So, to add a status bar to the bottom row of our grid, add the following lines directly beneath the InkCanvas line:

<StatusBar Name="statusBar1" Grid.Row="3">
  <TextBlock Name="statusText">Ready</TextBlock>
</StatusBar>

Status bars can contain just about any WPF control (like progress bars, buttons, pictures, etc.). In the standard case, we only want to host text in the status bar. In this case, we add a TextBlock control as the status bar’s content. We name the text block "statusText" with the default value of “Ready”.

Now, let’s add the menu to the top row. First, we have to add a container for our many Menus. In our case, we'll use a DockPanel to contain all of our menus. You can add this from the toolbox under the "Common Containers" category. Click DockPanel and click somewhere in the window . Once again, delete we delete nearly all of the xaml produced by the designer and change it to this:

<DockPanel Grid.Row="0" Name="dockPanel1" />

Now, we add a Menu to the DockPanel. This is a real pain to do in the current designer, so typing the xaml directly at this point is probably more useful.

<DockPanel Grid.Row="0" Name="dockPanel1">
  <Menu Name="menu1">
    <MenuItem Header="_File">
      <MenuItem Header="E_xit"/>
    </MenuItem>
  </Menu>
</DockPanel>

The '_' characters in the Header= section indicates a keyboard shortcut. For those of us who are keyboard users, these shortcuts are invaluable additions to the accessibility of an application. By placing the '_' in front of the file menu, a user can activate the file menu by pressing Alt + f. Similarly, the user can activate the Exit command by pressing the x key when the File menu is active (dropped down).

If we compile and run, we now have something that resembles a windows application and has the scribble functionality we want. Now, we need to make the Exit menu actually do something (like close the application). There are a variety of ways to accomplish this. For this step, we'll use an Event Handler. We can do this by changing the Exit MenuItem's definition to this:

<MenuItem Header="E_xit"
          InputGestureText="Alt-F4"
          Click="OnFileExit"
          ToolTip="Click here to exit" 
          Name="FileExit"/>

Next, we need to respond to the click event in code. We do this by adding the following code to the window1 class in the window1xaml.cs file:

public void OnFileExit(object sender, RoutedEventArgs e)
{
    this.Close();
}

Now, you should be able to compile and run. The result will be an application that looks and acts a lot like a regular windows application. Draw on the canvas, and when you’re ready to quit, you should be able to use all of the standard windows commands to exit including: File->Exit, Red X, Alt+F4, and my personal favorite Alt+F followed by the x key.

Thursday, May 10, 2007 4:34:05 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
WPF

Long, long ago, Microsoft shipped a multi-step tutorial with their C++ compiler called scribble (http://msdn2.microsoft.com/en-us/library/aa314520(VS.60).aspx). Scribble is a tiny drawing application wherein the user is allowed to draw freehand lines on the screen. It had a variety of preferences designed to show off the technology including: thin or thick pens, multiple open scribble documents (MDI - now, seemingly defunct), split window view, print, print preview, and email.

The purpose of this tutorial was to walk a developer through many of the common steps required to build a windows application using the current release of the compiler. In this case, this included MFC version something, Microsoft Visual C++ version something, and some version of the Microsoft Messaging Application Programming Interface (MAPI) in order to email your scribble masterpiece to friends and family.

In attempting to learn the wealth of tools and technologies thrown at me recently, I decided it might be wise to take a step back and go through that old tutorial with a new perspective.

In the entries to follow I hope to walk through the old Scribble tutorial step by step using WPF as the user interface technology. I will make all of the code available somehow (we’ll see if live spaces supports it or not).

If you want to run or modify the project, you’ll need to install a few things including .NET Framework 3.0, the new version of the Windows SDK, some form of Visual Studio 2005, and the VS 2005 extensions for WPF.

Thursday, May 10, 2007 3:14:24 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
WPF
Navigation
Categories
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2010
Cavin Consulting
Sign In
Statistics
Total Posts: 28
This Year: 0
This Month: 0
This Week: 0
Comments: 4
All Content © 2010, Cavin Consulting
DasBlog theme 'Business' created by Christoph De Baene (delarou)