RSS 2.0
# Thursday, August 02, 2007

This segment looks at localization of button toolips and the descriptive text that appears in the statusbar when a menu item or button is active. The code for this segment is available at http://www.cavinconsulting.com/Code/Scribble11.zip.

The general idea is that we want to give helpful hints to our user when if comes to deciphering the little icons on the screen. Normally, we attach tooltips to tell the user what each icon does. However, some application go the extra step to make it clear what a command does by placing a brief description of the actions in the status bar. This description appears when the menu item or toolbar button becomes "active" and goes away when the button becomes inactive.

For our purposes, Active will mean the mouse is over a button and or that a menu item is the focus item on in the application (the latter lets us display description information even if the menu becomes active via Alt+f,arrow keys).

To implement the GotFocus and LostFocus event handlers for the MenuItems, I went down a number of paths including style triggers, control templates, and adding event handlers to each MenuItem individually. The implementation I landed on was to apply an EventSetter to the MenuItem style like this:

<Window.Resources>
  <Style TargetType="{x:Type MenuItem}" x:Key="{x:Type MenuItem}" >
    <EventSetter Event="GotFocus" Handler="OnGotFocus"/>
    <EventSetter Event="LostFocus" Handler="OnLostFocus"/>
  </Style>
</Window.Resources>

From there, we change the declaration of each MenuItem to look like this:

<MenuItem Header="{x:Static scribble:ScribbleCommands.FileMenuText}">
  <MenuItem Command="{x:Static scribble:ScribbleCommands.New}" />
  <MenuItem Command="{x:Static scribble:ScribbleCommands.Open}" />
  <MenuItem Command="{x:Static scribble:ScribbleCommands.Save}" />
  <MenuItem Command="{x:Static scribble:ScribbleCommands.SaveAs}" />
  <Separator />
  <MenuItem Command="{x:Static scribble:ScribbleCommands.Exit}" />
</MenuItem>

Now, all we have to do is implement the event handlers:

public void OnGotFocus(object sender, RoutedEventArgs e)
{
  DisplayCommandDescription(sender);
}

public void OnLostFocus(object sender, RoutedEventArgs e)
{
  ClearCommandDescription(sender);
}

Now, the implementation of the Display and Clear functions can look something like this:

private void DisplayCommandDescription(object sender)
{
  MenuItem item = sender as MenuItem;
  if ((null != item) && (null != item.Command))
  {
    ScribbleCommand command = item.Command as ScribbleCommand;
    if (null != command)
    {
      StatusText = command.Description;
    }
  }
  ScribbleButton button = sender as ScribbleButton;
  if (null != button)
  {
    StatusText = button.Description;
  }
}

private void ClearCommandDescription(object sender)
{
  StatusText = ScribbleCommands.StatusbarText;
}

Of course, if you compile and run at this point, you'll get an error about StatusText not existing. That's because it doesn't. Of course, you could just change that to _statusBar.Content, but that would be directly manipulating the UI from code. According to the rumor, it's better to set a property somewhere that notifies the observers that it has changed. So, that's what we'll do, by adding a dependency property to our main window:

static MainWindow()
{
  StatusTextProperty =
    DependencyProperty.Register(
    "StatusText", typeof(string), typeof(MainWindow), 
    new FrameworkPropertyMetadata(String.Empty, FrameworkPropertyMetadataOptions.AffectsRender));
}

public static DependencyProperty StatusTextProperty;
public string StatusText
{
  get
  {
    return (string)GetValue(StatusTextProperty);
  }
  set
  {
    SetValue(StatusTextProperty, value);
  }
}

Now, we can databind the status bar's content to the main window's StatusText dependency property (we've added x:Name="main" to the Window tag at the top):

<StatusBar DockPanel.Dock="Bottom" 
  Visibility="{Binding Path=IsChecked, Converter={StaticResource BooleanToVisibilityConverter}, ElementName=_viewStatusBar}">
  <Label x:Name="_statusBarText" Content="{Binding ElementName=main, Path=StatusText}" />
</StatusBar>

Now, a little bit about the ScribbleCommand class. The ScribbleCommands class was getting a little tedious, so I wrote the ScribbleCommand class to encapsulate all of the information we need in order to attach to a menu item or a button. It inherits from RoutedUICommand so it plays nicely with the expectations of WPF. Really, it's just a RoutedUICommand with additional properties for Description and Tooltip.

For the status bar text on the buttons, I tried a different approach. Rather than checking for a command of a particular type in the activate and deactivate handlers, I checked if it was one of my own buttons that had a Description property. This helped in a few ways, one of which was that my inherited class was able to override the Tooltip property with that of the attached command. I'm not sure if this is a good approach or a blatant violation of some of the separation principles we were hoping to instill with the command pattern. I'll lay it out, anyway, and we'll see what we can make of it later.

The ScribbleButton inherits from Button, and has a text dependency property called Description (just like StatusText above). Also, it listens to the OnPropertyChanged event to see when the Command property is changed. If we get attached to a ScribbleCommand, then we set our tooltip and description according to the ScribbleCommand.

static ScribbleButton()
{
  DescriptionProperty = DependencyProperty.Register(
    "Description", typeof(string), typeof(ScribbleButton),
    new FrameworkPropertyMetadata(String.Empty, FrameworkPropertyMetadataOptions.AffectsRender));
}

public static DependencyProperty DescriptionProperty;
public string Description
{
  get
  {
    return (string)GetValue(DescriptionProperty);
  }
  set
  {
    SetValue(DescriptionProperty, value);
  }
}

protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
  base.OnPropertyChanged(e);
  if(0==string.Compare(e.Property.Name,"Command"))
  {
    ScribbleCommand newCommand = e.NewValue as ScribbleCommand;
    if (null!=newCommand)
    {
      ToolTip = newCommand.Tooltip;
      Description = newCommand.Description;
    }
  }
}

We do something similar to the GotFocus, LostFocus trick to hook up mouse focus:

<Style TargetType="{x:Type scribble:ScribbleButton}" x:Key="{x:Type scribble:ScribbleButton}" >
  <EventSetter Event="MouseEnter" Handler="OnMouseEnter"/>
  <EventSetter Event="MouseLeave" Handler="OnMouseLeave"/>
</Style>

And that's it. We finally have an application that looks like an application, localizes nicely, and takes advantage of some of the new features of WPF. I think this is probably the end of the Scribble tutorial, as such. I'm still not entirely convinced I've learned the localization model MS is trying to get across. Every example I see uses that LocBaml example and editing values in Excel. I'm not entirely convinced that's the correct route either.

Thursday, August 02, 2007 9:40:13 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
WPF
# Monday, July 16, 2007

Here we are on the 10th installment of what I thought would be a pretty simple walk-through into writing standard, distributable applications using WPF. This walk-through started out mirroring the old Scribble tutorial (http://msdn2.microsoft.com/en-us/library/aa314520(VS.60).aspx) but has recently turned into me complaining about things I don't understand very well. Specifically, I'm just not getting my head around the localization story with WPF and how it interacts with the built-in ApplicationCommands.X classes and keyboard access (mnemonics in  MFC). So, rather than complaining, let's just solve the problem.

The problem is this: ApplicationCommands.X classes give the developer a nice, automatically localized hook to use in hooking up UI elements to business logic to be executed. These commands are automatically localized based on the currently selected UI culture. These commands don't have access keys for use in MenuItem UI elements, so if I use the ApplicationCommands.New command in the file menu, I can't invoke it via Alt+f,s (something I've been doing from the old DOS days and am not interested in changing).

Solution 1 -- Implementing our own ScribbleCommands class from scratch

Here's a possible example. We could just implement our own ApplicationCommands-like class to serve up all of the commands we're interested in using from the UI. After all, we already implemented ScribbleCommands.Exit, so we can do something like that for the rest of the commands. This has the downside of requiring us to localize all of the text ourselves.

Solution 2 -- Implementing our own ScribbleCommands class as an adapter around ApplicationCommands

This is the one we're going to pursue. Since we already have ScribbleCommands and since MS already gives us much of the plumbing for the built-in commands classes, we'll just enhance that built-in functionality to convince it to do what we want. Here's the idea: for commands that don't have built-in ApplicationCommands.X implementation, we create string resources for all UI related aspects of the command (Name, Text, Gextures, Gesture display text, Tooltip, and Description). For commands that do have a built-in ApplicationCommands.X implementation, we create string resources for the bits of UI related aspects we want to override (mnemonic character, Tooltip, and Description).

The Scribble.Exit command looks like this:

public static RoutedUICommand Exit
{
	get
      {
      	return EnsureCommand(
			Properties.Resources.ExitName,
			Properties.Resources.ExitText,
			Properties.Resources.ExitGestures,
			Properties.Resources.ExitGesturesDisplayText);
	}
}
public static string ExitTooltip
{
	get
	{
		return Properties.Resources.ExitTooltip;
	}
}
public static string ExitDescription
{
	get
	{
		return Properties.Resources.ExitDescription;
	}
}

The Scribble.New command looks like this:

public static RoutedUICommand New
{
	get
	{
		RoutedUICommand command = ApplicationCommands.New;
		string commandText = command.Text;
		int accessIndex = 
			commandText.IndexOf(Properties.Resources.NewAccessKey);
		if ((accessIndex >= 0) && (commandText.IndexOf('_') < 0))
		{
			command.Text = commandText.Insert(accessIndex, "_");
		}
		return command;
	}
}
public static string NewTooltip
{
	get
	{
		return Properties.Resources.NewTooltip;
	}
}
public static string NewDescription
{
	get
	{
		return Properties.Resources.NewDescription;
	}
}

Notice, we have to use our own Tooltip and Description properties here since the built-in commands don't have these items provided. Also, notice we are searching the returned command.Text for a pre-existing _ character. This is so we don't forcibly overwrite any mnemonic that may already be set. Of course, you could just remove it instead.

Using any of these commands in the UI looks exactly the same as before

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

There it is. The code (in Spanish and English) is available at http://www.cavinconsulting.com/Code/Scribble10.zip. To run it in Spanish, just un-comment the marked lines in App.xaml.cs. If you're running Windows XP, be sure to install the Spanish language pack for the .NET 3.0 Framework (http://www.microsoft.com/downloads/details.aspx?FamilyID=41c61d2a-d411-4dde-9013-bb08eb688bb6&DisplayLang=en). Unfortunately, if you're running Vista, it isn't as simple as just installing the language pack. If you have Ultimate or Enterprise edition, you should see the language packs on Utimate Updates. I didn't, however, and ended up downloading it directly from here.

I'm still not sure what I think about this approach. It does salvage the automatic localization provided by the runtime. It allows each language to define their own mnemonics or to fall back on the default mnemonic (if the latter exists in the command). However, on windows Vista, the localization only appears supported on Ultimate or Enterprise editions, and the installation of the appropriate MUI packs doesn't seem as simple as the old language pack model. Also, it limits my target audience to only those fortunate enough to be running an expensive version of Windows.

Monday, July 16, 2007 9:39:29 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
WPF
# Friday, July 06, 2007

Up to this point, we've been concentrating on the infrastructure aspect of what it takes to make a "standard" application using WPF. We aren't quite there yet. The localization story for WPF applications is pretty fuzzy as is the keyboard accessibility framework. More importantly, getting the two to work together is questionable, at best.

The story so far:

WPF makes heavy use of the Command pattern for hooking execution logic to different UI elements. The WPF framework provides us with a number of built-in commands for our use (like ApplicationCommands.New, and EditingCommands.AlignCenter. Here are my impressions on using these commands out of the box:

The Good

  • UI elements which are "command aware" can have many of their properties localized to the current UI culture without the programmer having to do anything.
  • Having so many canned commands makes it all the more likely that developers will actually use the command pattern for their own commands.
  • Most commands are decorated with commonly KeyGestures so things like Ctrl+C or Shift+Delete invokes the ApplicationCommands.Copy command without the programmer having to do anything.

The Bad

  • The localized commands do not have a method (that I have found) to designate a mnemonic (now called an access key) for easy, localizable keyboard access inside a MenuItem. So, I haven't found a way to attach the ApplicationCommands.Save command to an item under the _File MenuItem and have the key sequence Alt+f,s invoke the save command. This is a big problem for me because I routinely use Alt+f,s to save rather than Ctrl+s. I may be the only one in the world, since it's a throwback to the old DOS editor, but I still use it.
  • There doesn't seem to be a really friendly way to extend or specialize the built-in commands overriding just the information you want to specialize. Instead, I resort to duplicating the built-in command, thereby loosing the automatic localization capabilities.

The localization story in WPF is also a little confusing to me. What with all of the different types of resources available (resource dictionaries in App.xaml, and Window.xaml along with the standard Properties.Resource available in .NET 2.0) it is unclear to me how a developer is expected to make robust, localizable applications. So far, I have encountered several discussions on how it can be done, but most of them rely on a pretty flimsy example application (LocBaml), this article http://msdn2.microsoft.com/en-us/library/ms753931.aspx and some clumsy hand-editing of both ApplicationSettings.cs and the .csproj file. Moreover, the localization mechanism is by way of replacing the localizable strings in a .csv file. What happened to the localizable resource editor we had in MFC 10 years ago? Moreover, we already have a mechanism for localizing resources for a .NET 2.0 application. Ultimately, the Baml information just gets stored as a localizable resource in the executable. Why is there no tool support here?

To be fair, I'm sure Microsoft hasn't forgotten that agencies other than Microsoft produce multi-cultural applications. On the contrary, I'd like to think that easy localization of an application is very important to Microsoft. However, I'm just not seeing that coming out of their new toolsets. It doesn't look like there will be anything of the sort in the Orcas timeframe either.

Why all the fuss? I started this project out simply trying to create a "generic" desktop application using the WPF framework. By generic, I mean just what the original MFC Scribble tutorial was -- a simple, standard, Windows application with all of the appropriate Windowsy goodness like tooltips, keyboard access, shortcuts, and localization. When it comes down to it, writing such an application in WPF appears to be a lot more work than I think it should be.

I still plan on pursuing this project as I think it is important that developers understand the importance of a "standard" look and feel even though our new toolsets open up the possibility to diverge from that standard (or create a new one). Also, I think it is always important to remember that there are other languages besides English. By remembering this, and designing for it, we can ensure the ability to localize our applications for markets we may otherwise be ineligible to join.

Friday, July 06, 2007 9:38:56 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
WPF
# Thursday, June 21, 2007

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

By now, we've gotten through most of the Scribble framework (writing a real Windows-like application using WPF). Of course, I'm sure there are better ways to do what I'm doing, but that's fine. Here's a short installment on vector icons instead of .bmp/.png icons.

In our previous steps, we displayed our toolbar buttons with something like this:

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

That's fine, except that WPF is all about vectors, and as soon as you start adding bitmaps, things get pixilated when you show them on your 24" wide-screen monitor. So, we'll switch the bitmap out into something drawn with the WPF vector system. The first step is to find or create some vector drawings for use as your button graphics. You can draw things in Adobe Illustrator and use the Illustrator to xaml plugin (http://www.mikeswanson.com/XAMLExport/), you can use Expression Studio to draw the items by hand, or you can find a source on the Internet for nice, friendly toolbar buttons in xaml (http://www.grafile.com/presentation/Vista_Toolbar_library.html).

The library that I started with has each icon separated into its own xaml file. Unfortunately, it also has each item drawn on an 800x600 panel, which doesn't lend itself too well to scaling onto a square button. So, there is a little work to be done before we can apply these icons to our buttons. I accomplished the task in Expression Blend. The first thing I did was generate a resource dictionary for our application. This is pretty much just a dictionary of useful bits of xaml that you can apply other places in the application. Do this by adding icons.xaml to the project and editing the App.xaml to look like this:

<Application.Resources>
  <ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
      <ResourceDictionary Source="Icons.xaml" />
    </ResourceDictionary.MergedDictionaries>
  </ResourceDictionary>
</Application.Resources>

The next step is to paste in the icons of interest from their respective xaml files. I guess I could leave them in their individual xaml files and just include each one as a resource dictionary, but I didn't think of it until now, and it's too late.

In order to use the icons as button images, we store them in the resource dictionary as ControlTemplates. To do this, create a ControlTemplate tag in the icons.xaml file and paste in the xaml of the button of interest.

<ControlTemplate x:Key="New">
  <!-- Pasted text from the icon of interest -->
</ControlTemplate>

Once you have the ControlTemplate resource setup, the changes to the button definition are trivial:

<Button Command="ApplicationCommands.New" Width="32" Height="32">
  <ContentControl Template="{StaticResource New}" />
</Button>

Unfortunately, since the icons I used are on an 800x600 canvas, I have to do a little alteration to make them work on a square button face. First, I wrap the entire icon in a ViewBox to make it scale nicely. Then, I wrap the original canvas in a square canvas 345x345 (it seemed like a good number at the time). Finally, I add a render transform to the original canvas to translate the original image to be centered in the new, parent canvas. The whole thing looks something like this:

<ControlTemplate x:Key="New">
  <Viewbox Stretch="Uniform">
    <Canvas Width="345" Height="345">
      <Canvas Width="800" Height="600" Canvas.Left="0" Canvas.Top="0">
        <Canvas.RenderTransform>
          <TransformGroup>
            <ScaleTransform ScaleX="1" ScaleY="1"/>
            <SkewTransform AngleX="0" AngleY="0"/>
            <RotateTransform Angle="0"/>
            <TranslateTransform X="-220" Y="-157"/>
          </TransformGroup>
        </Canvas.RenderTransform>
        <!-- Actual drawing code -->
      </Canvas>
    </Canvas>
  </Viewbox>
</ControlTemplate>

That's it. Now, you have classy, WPF-like, xamlized, vector toolbar buttons. Some of them might be ugly, but they're scalable. I'm sure there is a better way to accomplish this, but we're one step closer to a real-world Windows-like application using WPF. We still don't have all of those things that I think should be inherent in a Windows application (like tooltips, updating of the status bar with command help-text, etc.) and we can't take advantage of the automatic localization available with the ApplicationCommands classes since we want to have our own mnemonics for keyboard access to each command.

I think we'll tackle the ToolTips and StatusBar text next. Specifically, we'll take a look at how to make these items easily localizable so we don't have a bunch of English phrases locked into our code.

Thursday, June 21, 2007 9:36:33 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
WPF
# Wednesday, May 30, 2007

This one turned out to be a lot simpler than I had expected. As a result, the code isn't available. You'll have to do it yourself.

The question was one of attaching a property of our UI (say, the checked property of a MenuItem) to one of the clever Resources.Properties values that are taken care of for us by VS 2005. In our Scribble application, there are a few things we'll want to persist between sessions (on a per-user basis) such as toolbar visibility and window size (and maybe window state).

First, we add values to the Settings class for the values we would like to persist. You can do this in the properties for the project or by double-clicking on the Settings.settings class (does anyone know why there are so many ways to accomplish the same task in VS2005?).

I added MainWindowWidth and MainWindowHeight as user-scoped, integer values. I also added InkToolbarVisible, FileToolbarVisible, and StatusbarVisible as bool values. I set these with reasonable defaults.

Now, we have to hook these settings up to the properties of the UI. We will use similar syntax to how we hooked up our Exit command.

For the window size, we set width and height like this:

<Window
    Height="{Binding Source={x:Static scribble:Properties.Settings.Default}, 
        Path=MainWindowHeight, Mode=TwoWay}"
    Width="{Binding Source={x:Static scribble:Properties.Settings.Default}, 
        Path=MainWindowWidth, Mode=TwoWay}">

For the toolbar and statusbar visibility, we bind the IsChecked property (since that's where the toolbar and statusbar get their visibility). We set all three values with the same syntax:

<MenuItem Header="_File Toolbar" 
          x:Name="_viewFileBar" 
          IsCheckable="True" 
          IsChecked="{Binding Source={x:Static scribble:Properties.Settings.Default}, 
              Path=FileToolbarVisible, Mode=TwoWay}"/>

That's it, except for saving the values when the application closes. To to this, we override the Windows OnClosing method in the Scribble.xaml.cs file. Here are the contents:

protected override void OnClosing(CancelEventArgs e)
{
    Properties.Settings.Default.Save();
    base.OnClosing(e);
}

Compile and run a few times, changing the toolbar visibility and the window size. The application will remember these values each time you run.

Wednesday, May 30, 2007 9:14:21 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0] -
WPF
# 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
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)