Click here to Skip to main content
Click here to Skip to main content

Full Screen WPF Application with a Low-Level Keyboard Hook

, 26 Nov 2008
Rate this:
Please Sign up or sign in to vote.
This application displays shapes of random sizes and colors. It's perfect for babies and toddlers who love to sit and press away at the keyboard.

ShapeShowMain.gif

Introduction

After becoming a father, I found that it didn't take long for my daughter to want to do everything I did. And, since I spend a significant amount of time typing away at the keyboard, she started wanting to use the computer before she even turned one. So, I had a great idea, I'll just open up Notepad and let her type away. And that worked, for all of about 10 seconds, until she somehow ended up in the Control Panel with the Add Hardware wizard open. So, I searched the web for a baby proof application that she'd like. I found one on the Code Project (linked at the bottom). My daughter has used the program and loved it for quite some time. As I decided to start learning WPF, I needed a small project I could tackle to get started. And, rewriting the shape displaying application that my daughter liked so much seemed like a great candidate. So I did, and called it ShapeShow. I hope you're able to find this article useful for development purposes as well as for providing some fun for your kids.

Prerequisites

Running this application requires:

  • Windows XP or Vista
  • .NET Framework 3.5
  • Visual Studio 2008 (to open the project)

The Shape Library

The first thing I wanted to do was define the possible shapes that would display on the screen. So, I created a shape library project that contained classes for each of the shapes to be displayed - circle, triangle, and square. Although, the circle and square classes also ended up representing ellipses and rectangles.

The goal of the shape library is to provide a random shape of a random color and size. In order to do this, I really didn't need to know anything about the actual shapes. I only need a class that could create these shapes. That sounded like a good opportunity to use the Factory pattern. So, I decided to create an interface for a shape and implement that interface for each of the shape types. Although, a base class may have worked better in this case, since each shape implements its members in almost the same way. Below is the method called from the WPF application to get new random shapes.

public static IShape CreateShape()
{
    return CreateShape((ShapeType)RandomNumber.Next(0, ShapeCount));
}

It calls an overloaded method that takes a shape type. The shape type is an enumeration, and is randomly selected by casting a random integer to a shape type. This, in turn, calls another overloaded CreateShape method that selects a random color for the shape. And finally, the shape is created in the method shown below:

public static IShape CreateShape(ShapeType shapeType, ShapeColor shapeColor)
{
    IShape shape = CreateShapeFromType(shapeType);

    shape.Height = RandomNumber.NextDouble() * 
                  (SystemParameters.VirtualScreenHeight / 2) + MinHeight;
    shape.Width = RandomNumber.NextDouble() * 
                 (SystemParameters.VirtualScreenWidth / 2) + MinWidth;
    shape.Top = RandomNumber.NextDouble() * 
               (SystemParameters.VirtualScreenHeight - shape.Height);
    shape.Left = RandomNumber.NextDouble() * 
                (SystemParameters.VirtualScreenWidth - shape.Width);
    shape.FillBrush = CreateBrushFromColor(shapeColor);

    return shape;
}

The shape type and color are selected through a simple set of Case statements which select the correct shape to create and the correct brush to fill with. The shape size is bound by the screen size, and has a minimum height and width as well.

The WPF Application

The WPF application doesn't have a lot of work to do. It only needs to request a new shape each time a key is pressed and display that shape on the screen. In order to do this, I simply override the OnKeyDown method. But, I also wanted to do other things there, mainly, give the user a way to close the application through a special key combination. And, that's exactly what I've done, through an Options menu. Below is the code that overrides the OnKeyDown method:

protected override void OnKeyDown(KeyEventArgs e)
{
    if (IsRequestingOptions)
    {
        ShowOptions();
    }
    else if (CanDraw(e))
    {
        DrawShape(ShapeFactory.CreateShape());
    }

    base.OnKeyDown(e);
}

And, drawing the shape is as simple as adding the shape to a canvas element, which I've called the DrawingSurface. I've also set a limit on the number of shapes that can be displayed on the screen at once. This helps to keep the screen from getting too cluttered, and keeps memory usage down as well.

private void DrawShape(IShape shape)
{
    if (DrawingSurface.Children.Count == MaxShapeCount)
    {
        DrawingSurface.Children.RemoveAt(0);
    }

    DrawingSurface.Children.Add(shape.UIElement);
}

As I noted earlier, since the application is full screen, there are no close or minimize buttons. So, I've provided a special key combination that can open an Options screen. The Options screen displays three options: Clear Screen, Return to ShapeShow, and Exit ShapeShow. The user can bring up the options screen by pressing Ctrl+Alt+O. To show the options, we just set the options user control's Visibility property to be Visible. And, I also lower the opacity on the drawing surface to visually signal that the drawing surface is deactivated. The user control is a simple StackPanel with three buttons orientated vertically. Below is the code and a picture of the options control:

ShapeShowOptions.gif

<StackPanel x:Name="ButtonPanel">
    <Button Name="ClearScreen" Content="Clear Screen" Height="60" Width="Auto"
        Style="{DynamicResource OptionsButton}"></Button>
    <Button Name="ReturnButton" Content="Return To ShapeShow" Height="60" Width="Auto"
        Style="{DynamicResource OptionsButton}"></Button>
    <Button Name="CloseButton" Content="Exit ShapeShow" Height="60" Width="Auto"
        Style="{DynamicResource OptionsButton}"></Button>
</StackPanel>

As you can see, I also create my own buttons. This is amazingly simple in WPF. I simply declare a Button as usual and apply a Style. Styles are extremely flexible and easy to use. I've put my button styles in a separate file called Resources.xaml. The style for these buttons, called OptionsButton, is shown below:

<Style x:Key="OptionsButton" TargetType="Button">
    <Setter Property="Margin" Value="10" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid Height="{TemplateBinding Height}" Width="{TemplateBinding Width}">
                    <Rectangle x:Name="ButtonRect" RadiusX="10" RadiusY="10"
                                   StrokeThickness="2" Stroke="#555555"
                                   Style="{StaticResource OptionsButtonUp}" />

                    <ContentPresenter x:Name="ButtonContent"
                                          Content="{TemplateBinding Content}"
                                          HorizontalAlignment="Center"
                                          VerticalAlignment="Center"
                                          TextElement.FontSize="20"
                                          TextElement.Foreground="#C8C8C8"/>
                </Grid>

                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter TargetName="ButtonRect" Property="Style"
                                    Value="{StaticResource OptionsButtonOver}" />
                        <Setter TargetName="ButtonContent" Property="TextElement.Foreground"
                                    Value="#FFFFFF" />
                    </Trigger>
                    
                    <Trigger Property="IsPressed" Value="True">
                        <Setter TargetName="ButtonRect" Property="Style"
                                    Value="{StaticResource OptionsButtonDown}" />
                        <Setter TargetName="ButtonContent" Property="TextElement.Foreground"
                                    Value="#AAAAAA" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

While this may look a little complicated at first, it's really not. First, a Style element must be declared, with a Key and TargetType. In this case, the style is applied to buttons in the options menu, so the Key is OptionsButton and the TargetType is Button. The properties are then set by declaring a Setter element that defines the property to set and the value to use. Setting the margin property is a simple example. But, notice the amazing amount of flexibility available in setting the button template. Each button can be composed of whatever UI elements you'd like. In this case, I've used a Rectangle (enclosed by a Grid) as my button content. I've also set the Rectangle's Fill property to be a static resource defined in the same file. This allows me to easily change the look of the button on the mouse over and mouse down events. These are controlled by the Trigger elements shown above. Also note the TemplateBindings above. These allow properties to be set based on the actual button properties. For instance, in the example above, the Grid takes on the height specified by the button element in our ButtonControl class.

The final step is to add the user control to the main window and hide or show it when appropriate. The XAML for the main window is shown below:

<Window x:Class="ShapeShow.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:uc="clr-namespace:ShapeShow"
      Title="ShapeShow" ResizeMode="NoResize" 
      WindowStyle="None" WindowState="Maximized" Topmost="True">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        
        <TextBlock Foreground="Gray" Grid.Row="0" 
           Padding="10,0">Press Ctrl+Alt+O to show options.</TextBlock>
        <Canvas x:Name="DrawingSurface" Grid.Row="1" />
        <uc:OptionsControl x:Name="Options" 
           Grid.RowSpan="2" Visibility="Collapsed" />
    </Grid>
    
</Window>

When adding a user control, you have to be sure to add an XML namespace for that control, even if the control is in the same assembly, like this one. Note the xmlns:uc="clr-namespace:ShapeShow" line that does this. And, ensuring that the window displays in full screen and stays on top is fairly simple, you just set the WindowStyle to None, the WindowState to Maximized, and TopMost to true. Also note the Height="*" for the second row definition. This tells the second row to take up all remaining space. And that is where the drawing surface resides. Finally, we're able to get the options menu to display over both rows by simply giving it a RowSpan of 2.

The Keyboard Hook

Low-level keyboard hooks aren't the focus of this article. It is much better explained in many other places. If fact, Emma's article (linked at the bottom) covers it well. However, I'll go ahead and provide a bit of general information on how it works.

Even though I have overridden the OnKeyDown method, there are still some key combinations that I don't have control over. These are system keys such as the Windows keys or Alt+Tab. In order to stop these keys from being processed, I had to catch them with a system wide low-level keyboard hook. This is actually fairly easy to do. All that's needed is a function to handle the key press events and a few calls to user32.dll functions to set the hook. These functions are shown below:

[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, 
                      HookHandlerDelegate callbackPtr,
IntPtr hInstance, uint dwThreadId);

[DllImport("user32.dll", SetLastError = true)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);

[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, 
                      IntPtr wParam, ref KBHookStruct lParam);

Now, we only need to define a callback function for the hook and pass along the keys that we want to allow. We pass along keys by using the CallNextHookEx function. And, we can discard keys by simply returning from the function without the call to CallNextHookEx. The callback function must have a specific signature as shown below:

private static IntPtr KeyboardHookHandler(int nCode, IntPtr wParam, 
                      ref KBHookStruct lParam)
{
    if (nCode == 0)
    {
        if (((lParam.vkCode == 0x09) && (lParam.flags == 0x20)) ||  // Alt+Tab
        ((lParam.vkCode == 0x1B) && (lParam.flags == 0x20)) ||      // Alt+Esc
        ((lParam.vkCode == 0x1B) && (lParam.flags == 0x00)) ||      // Ctrl+Esc
        ((lParam.vkCode == 0x5B) && (lParam.flags == 0x01)) ||      // Left Windows Key
        ((lParam.vkCode == 0x5C) && (lParam.flags == 0x01)) ||      // Right Windows Key
        ((lParam.vkCode == 0x73) && (lParam.flags == 0x20)) ||      // Alt+F4
        ((lParam.vkCode == 0x20) && (lParam.flags == 0x20)))        // Alt+Space
        {
            return new IntPtr(1);
        }
    }

    return CallNextHookEx(hookPtr, nCode, wParam, ref lParam);
}

And That's It

We've got a complete full screen WPF application. Feel free to ask questions or make suggestions. I'd like to extend this in the future to also display other shapes, such as numbers or letters. So, if you have any other interesting ideas, please let me know. Thanks for viewing my first CodeProject article, and I hope you've found something useful.

Here's a link to the article that inspired this one, by Emma Burrows - Low-level Windows API hooks from C# to stop unwanted keystrokes.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Barry Dorman
Software Developer
United States United States
Barry Dorman is a software engineer from the Birmingham, AL area. His primary focus is ASP.NET. Barry graduated with a BS in Electrical Engineering from The University of Alabama at Birmingham in 2006. Since then, he has kept himself busy working in the healthcare industry.

Comments and Discussions

 
GeneralGreat Work. PinmemberKelvin Armstrong16-Oct-10 2:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 26 Nov 2008
Article Copyright 2008 by Barry Dorman
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid