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

Full Screen WPF Application with a Low-Level Keyboard Hook

By , 26 Nov 2008
 

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
Member
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.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionCtrl + Alt + DelmemberMember 825372821 Mar '13 - 17:14 
Hi, how to hook Ctrl Alt Del key press? Thanks!
GeneralMy vote of 5memberaggarwal_p28 Apr '11 - 20:44 
Excellent Part on Disabling Key Strokes
GeneralGreat Work.memberKelvin Armstrong16 Oct '10 - 2:25 
My vote of 5.
I am sorry to see that there are so many derogatory remarks about this app. Mad | :mad:
It is a great piece of work, and something beginners will easily be able to get into. Cool | :cool:
 
Thumbs Up | :thumbsup: Thank you for posting.
GeneralThanks Barrymemberbnewcomb30 Aug '09 - 10:01 
Needing to get up to speed quick with WPF as I am working on a KIOSK application.. this is a great headstart! Thanks for taking the time to develop and publish.
GeneralQuick Question on your Hook methodmemberAlexander Colson8 Aug '09 - 8:29 
First off, i find myself slightly addicted to the idea of drawing shapes on screen thanks to your application ...
 
Nevertheless, i have been on a mad quest for over four months now to find a simple way to implement hooks in a program of mine and then along comes your article.
 
Case in point, when i take your code and go to build it, the compiler states that the HookHandlerDelagate and KBHookStruct arguments from the DLL Import are missing a reference and won't let me debug the application.
 
Any thoughts as to why?
 
-Alex
GeneralRe: Quick Question on your Hook methodmemberBarry Dorman8 Aug '09 - 9:14 
Both of those are defined in the same file as the DLLImports. Are you trying to compile the project included with the source code? Or have you copied parts of the code to another project? HookHandlerDelegate and KBHookStruct are both defined in the KeyboardManager.cs file. If you use the DLLImports in another project you'll need to make sure these items are defined in the same namespace.
QuestionRe: Quick Question on your Hook methodmemberAlexander Colson9 Aug '09 - 17:30 
Yeah i realized that as soon as i thought, but i ran into one other issue.
 
In the KeyboardManager.cs file, in the void DisableSystemKeys(), the line is
IntPtr hInstance = Marshal.GetHINSTANCE(Application.Current.GetType().Module);
 
To what is the Current referencing? I assume it refers the instance of the program, but the debugger seems to not like this and i tried Disabling the Visual Studio hosting process.
 
I appreciate your help,
Alex
GeneralGood articlemembersub-star26 Dec '08 - 10:13 
I think it was a good article, I rated it a 4. I think you have your back covered pretty good, concerning the criticism, although you probably wouldn't have gotten such a hard time from some people if you had done a little googling before publishing the article. And who cares if it has been done before? If you couldn't create a program just because somebody had created something similar before you, there would have been a lot less programs and virtually no competition between software companies! Anyway, happy holidays, and I hope everybody enjoys the holidays with their families! =)
GeneralRe: Good articlemembercanadian_bacon1 Jan '09 - 20:16 
Agreed. Sure BabySmash! is out there, but you can never have to many WPF samples Smile | :)
 
Article looks good!
GeneralMy vote of 1membermariad854 Dec '08 - 5:43 
it's silly
GeneralRe: My vote of 1memberStephen Hewitt26 May '10 - 13:57 
It's for kids. I would assume it's not expected to amuse adults.
Steve

GeneralThanks!memberdig_dug_d1 Dec '08 - 19:25 
Half the people bitching probably didn't even read the article but some how found the time to post comments. I read it... and it was clear from the author that there were things like this out there (and gave link)... and it was also clear this was a "get your feet wet" kind of application using WPF... not the next frickin Napster. I haven't done any WPF so any simple demos are helpful. Thanks for taking the time to put this up and I hope you post more WPF stuff down the road.
GeneralRe: Thanks!memberElizabeth Connolly2 Dec '08 - 1:32 
I agree with this post - thank you for the article and please ignore the ranters. You gave plenty of credit to the original article, and this looks like a great "baby step" for getting your feet wet with WPF. I recall seeing the original article, but since it was Winforms and my own "baby" is now 6'2" tall, I didn't need it and ignored it. But I'm going to play with yours, as a nice practical intro to WPF.
GeneralRe: Thanks!memberBarry Dorman2 Dec '08 - 16:01 
Your welcome, and thanks for the positive comments. I do hope to post some more WPF articles at some point.
GeneralMy vote of 1memberwvalters27 Nov '08 - 5:47 
Ripoff of BabySmash
GeneralRe: My vote of 1memberBarry Dorman27 Nov '08 - 18:39 
As hard as it may be to believe, I was unaware of the existence of BabySmash. But that isn't even relevant. I've clearly mentioned in the article that I didn't come up with the idea. And it's not as if it's a completely original idea in the first place. BabySmash wasn't the first application to use the concept. Do you call that application a rip off of BabyBash [^], which originated 8 years ago? It's one thing to use someone else's idea and it's another to use their source code. And if you'd take the time to view the source for both, you'd see that they are vastly different.
GeneralRe: My vote of 1memberPascal Ganaye20 Sep '11 - 3:49 
I noticed that when you click on a 'My vote of 1' message you're very likely to get to a member that has only voted once or twice and only with a vote of 1.
 
Not the most helpful and positive members of our community.
GeneralBabySmashmemberWilliam E. Kempf26 Nov '08 - 9:43 
I've not looked at the code, so I'm not going to accuse you of any outright stealing, but you claim to have Googled for an application like this and didn't find BabySmash [^]? Really?
 
William E. Kempf

GeneralRe: BabySmashmemberBarryDorman26 Nov '08 - 10:49 
I searched for the application over 2 years ago. It looks like BabySmash has only been around for a few months. And I didn't search again before writing the application in WPF. The point of the project was a learning experiment. So no, I didn't find BabySmash. It does look like a very nice application though. Thanks for the comment, despite its condescending tone.
GeneralRe: BabySmashmemberWilliam E. Kempf26 Nov '08 - 10:54 
Sorry, it just seemed like an unlikely coincidence, since BabySmash was showcased at the PDC.
 
William E. Kempf

GeneralRe: BabySmashmemberAndrewSmith27 Nov '08 - 12:23 
what about this article http://www.codeproject.com/KB/system/CSLLKeyboard.aspx[^] which seems to be identical to another person's work.
GeneralRe: BabySmashmemberBarry Dorman27 Nov '08 - 18:13 
I mentioned that article three times and linked to it at the bottom. Besides, the code for these two articles is completely different. Emma's article is written in WinForms, this is WPF.
RantRe: BabySmashmemberMiguel Madero1 Jan '09 - 23:29 
Com'on give him some credit. I reckon the idea is the same to Baby Smash, but it's pretty likely many people thought of this also, I don't think it would be a rip off. Besides there are good learning points in the article, which ultimately is parallel purpose of both apps. So just take what it's useful...
SuggestionRe: BabySmashmemberPascal Ganaye20 Sep '11 - 3:55 
This is a bit unlucky, I wish the article title had been : 'Here is how BabySmash works' then no one would have complained.
 
It is not a rip off if you did not see the original source.
I am one of the person who wondered how baby smash was doing some of the things described in your article.
 
You got my vote of 5 for this article.
It is a very useful first article, please continue writing more.
GeneralProgrammemberDmitri Nesteruk26 Nov '08 - 9:01 
Random shapes are a good start. But I reckon the best type of computer for a kid is a ruggedized touchscreen type where they can actually poke the shapes, press buttons, et cetera. I'm pretty sure such things exist already.

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

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