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.
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.