RSS 2.0
# 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
Comments are closed.
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)