DirectXTutorial.com
The Ultimate DirectX Tutorial
Sign In
Lesson 3: Window Events
Lesson Overview

We're off to a good start. We've started up the application and created a window. However, this is only one piece the picture. The window closed immediately, and if it had stayed open, there would have been no possible interaction with it.

In this lesson we are going to look at events. These are user actions which are detected by Windows. Every time they occur, we are sent a notification describing what happened. We need to change our program to read the notifications in a timely fashion, and process them when they arrive.

Handling Windows Events

Once we have created our window, we need to keep the window going so that it can be interacted with. Instead of exiting, our program should complete creating the window, launch the Run() function, and stay there, almost in an infinite loop.

As mentioned, Windows programming is based on events. This means that our window only needs to do something when Windows gives us the go. Otherwise, we just wait.

When Windows does pass us a message, several things immediately occur. The message is placed in an event queue. Our window then looks to see if we've written a function that handles that event, and calls the function if we have.

In case that went over your head, following is a diagram which illustrates the sequence in which this all occurs.

The Process of an Event Message

The Process of an Event Message

There are three parts to handling events:

1. Instructing the window to dispatch all events
2. Writing functions to receive the desired events
3. Informing Windows what these functions are

These three steps are surprisingly simple. Let's dig in and find out about each one.


1. Instructing the window to dispatch all events

Dispatching events is very simple. All we need to do is call the ProcessEvents() function.

virtual void Run()
{
    // Obtain a pointer to the window
    CoreWindow^ Window = CoreWindow::GetForCurrentThread();

    // Run ProcessEvents() to dispatch events
    Window->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessUntilQuit);
}

As you can see, this takes two lines of code.

We've already seen the first line in the previous lesson. We use GetForCurrentThread() to get a pointer to the window.

Once we have the pointer to the window, we can call ProcessEvents(). It's a member of Dispatcher, which is a member of the CoreWindow class.

The parameter is simple also. There are four different values you can use here, each of which changes the behavior of the function drastically. This table shows what each value is and what it does.

ValueDescription
ProcessOneIfPresentDispatch the next event waiting on the queue (if any).
ProcessAllIfPresentDispatch the next event waiting on the queue (if any) and repeat until all waiting events are handled, then return.
ProcessOneAndAllPendingDispatch all waiting events. If there are none waiting, wait until another one arrives.
ProcessUntilQuitDispatch all events, and repeat. Do not return until Windows shuts the program down.

These values are part of the enumeration CoreProcessEventsOption, so make sure you add that before any value used.

In this lesson, I'm using ProcessUntilQuit. This puts our application into a near-infinite loop, and keeps our program from exiting as soon as it's started.

And that's all there is to dispatching events! Now let's write code to do something once the events arrive.


2. Writing functions to receive the desired events

There are a lot of different events that can occur, and we aren't actually interested in all of them. Many of them have purposes that have nothing to do with us whatsoever.

Fortunately, we only have to handle events we need for our game. Instead of writing functions for all possible events, we only add event-handling functions as required.

I'm going to start with one event, then show you a bunch more later on.

This event is called PointerPressed. It occurs when you either click the mouse, or when the touchscreen is first touched with a pen or a finger.

Writing the function itself is rather trivial. It looks like a function. We write it in the App class.

ref class App sealed : public IFrameworkView
{
public:
    ...
    ...
    ...

    void PointerPressed()
    {
    }

};

Event functions have two required parameters.

The first parameter is always the same: CoreWindow^. You can call it anything you like, but it has to be a pointer to CoreWindow. This parameter allows us to access information about the window.

    void PointerPressed(CoreWindow^ Window)
    {
    }

The second parameter is different depending on which event you are working with. This event uses a PointerEventArgs class. It tells us all about the pointer-press, such as where on the screen it was made, what type of press it is (mouse, pen, touch), exactly when it was made, and much more. We'll talk more about this class in the next section.

    void PointerPressed(CoreWindow^ Window, PointerEventArgs^ Args)
    {
    }

Let's leave this function empty for now, and move along to step 3:


3. Informing Windows what these functions are

Once we've written a function to handle an event, we must inform Windows that we've written it. Otherwise, the ProcessEvents() function will have nothing to do when the event is called.

Telling Windows about an event function is easy. In fact, we've already done it in the last lesson. This time, however, we'll write the code inside the SetWindow() function.

virtual void SetWindow(CoreWindow^ Window)
{
    Window->PointerPressed += ref new TypedEventHandler
        <CoreWindow^, PointerEventArgs^>(this, &App::PointerPressed);
}

Just like last time, we are adding a TypedEventHandler to PointerPressed. Inside the template parameters, we put the types we are passing to the function (CoreWindow and PointerEventArgs). At the end, we put this and the address of the PointerPressed() function we just wrote.


Message Dialogs

At this point, we haven't learned any code that will let us draw anything on the window. A very simple tool to let us do this is a message dialog, or message box. In WinRT, the code to create a message dialog is obsurdly simple.

First, we create a MessageDialog object. The text of the dialog goes right in the constructor. Then we call the ShowAsync() function. And that's all!

    MessageDialog Dialog("Thank you for noticing this notice.", "Notice!");
    Dialog.ShowAsync();

Easy enough, right? Here's what it looks like:



The color of the background will be the same color as your Start screen.

Let's put this code into our PointerPressed() function so we can clearly see when the event is triggered.


Our New Program

All right! Let's take all the changes from this lesson so far and add them into last lesson's code. Everything new from this lesson has been highlighted in bold.

// Include the precompiled headers
#include "pch.h"

// Use some common namespaces to simplify the code
using namespace Windows::ApplicationModel;
using namespace Windows::ApplicationModel::Core;
using namespace Windows::ApplicationModel::Activation;
using namespace Windows::UI::Core;
using namespace Windows::UI::Popups;
using namespace Windows::System;
using namespace Windows::Foundation;
using namespace Windows::Graphics::Display;
using namespace Platform;


// the class definition for the core "framework" of our app
ref class App sealed : public IFrameworkView
{
public:
    virtual void Initialize(CoreApplicationView^ AppView)
    {
        AppView->Activated += ref new TypedEventHandler
            <CoreApplicationView^, IActivatedEventArgs^>(this, &App::OnActivated);
    }
    virtual void SetWindow(CoreWindow^ Window)
    {
        Window->PointerPressed += ref new TypedEventHandler
            <CoreWindow^, PointerEventArgs^>(this, &App::PointerPressed);

    }
    virtual void Load(String^ EntryPoint) {}
    virtual void Run()
    {
        // Obtain a pointer to the window
        CoreWindow^ Window = CoreWindow::GetForCurrentThread();

        // Run ProcessEvents() to dispatch events
        Window->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessUntilQuit);

    }
    virtual void Uninitialize() {}
        
    void OnActivated(CoreApplicationView^ CoreAppView, IActivatedEventArgs^ Args)
    {
        CoreWindow^ Window = CoreWindow::GetForCurrentThread();
        Window->Activate();
    }
    void PointerPressed(CoreWindow^ Window, PointerEventArgs^ Args)
    {
        MessageDialog Dialog("Thank you for noticing this notice.", "Notice!");
        Dialog.ShowAsync();
    }

};


// the class definition that creates our core framework
ref class AppSource sealed : IFrameworkViewSource
{
public:
    virtual IFrameworkView^ CreateView()
    {
        return ref new App();    // create the framework view and return it
    }
};

[MTAThread]    // define main() as a multi-threaded-apartment function

// the starting point of all programs
int main(Array<String^>^ args)
{
    CoreApplication::Run(ref new AppSource());    // run the framework view source
    return 0;
}

Well this is pretty neat, isn't it? We can now interact with our program somewhat. All you do is click the mouse on the blank screen, and the message dialog appears.

And this isn't nearly all we can do. Let's look at some more events and extend our interaction abilities even further.

Some More Window Events

Now that we've learned how to detect and handle an event, let's look at some more events. This time, let's learn more about how to get information from those events. Again, we don't have the ability to draw text yet, so we'll just use messageboxes for the time being.


KeyDown Event

In addition to the mouse, we also want to know when the keyboard is used, and how. The KeyDown event tells us when a keystroke has been made. More specifically, it tells us when the key is first pressed down.

We can set up the KeyDown event the same way we set up the PointerPressed event, except that we use a different type of argument: KeyEventArgs.

    virtual void SetWindow(CoreWindow^ Window)
    {
        Window->KeyDown += ref new TypedEventHandler
            <CoreWindow^, KeyEventArgs^>(this, &App::KeyDown);
    }

When we create the function to handle this event, we use the same parameter there as well.

    void KeyDown(CoreWindow^ Window, KeyEventArgs^ Args)
    {
    }

Of course, knowing that a key was pressed and knowing which key was pressed are very different things. We can use the Args parameter to determine which key was pressed. Args has a member called VirtualKey, and we can use it to detect any key on the keyboard. It works like this:

    void KeyDown(CoreWindow^ Window, KeyEventArgs^ Args)
    {
        if(Args->VirtualKey == VirtualKey::A)
        {
            // do something...
        }

    }

This code checks the keystroke associated with the event. If it's the 'A' key, it does something. This can be an upper-case 'A' or a lower-case 'a'.

It checks more than just letters too. Here's a full table of possible values you can use instead. (Note: the Alt key is listed as Menu).

As an exercise, try making your program trigger a message box whenever any of the W, A, S or D keys are pressed.


KeyUp Event

The KeyUp event is almost exactly like the KeyDown event, and I bet you can guess how it's different. That's right! You're so smart! :)

Here's what it looks like:

    virtual void SetWindow(CoreWindow^ Window)
    {
        Window->KeyUp += ref new TypedEventHandler
            <CoreWindow^, KeyEventArgs^>(this, &App::KeyUp);
    }

Detecting the key in question is done exactly the same way as KeyDown:

    void KeyUp(CoreWindow^ Window, KeyEventArgs^ Args)
    {
        if(Args->VirtualKey == VirtualKey::A)
        {
            // do something...
        }
    }

There isn't much else to talk about here. As an exercise, try modifying the program to keep track of whether a specific key is pressed or unpressed.


PointerPressed

Yes, we've already talked about this one, but there's more to it than what we've covered so far. Let's take a look at the information PointerEventArgs provides.

PointerEventArgs has a member called CurrentPoint. This is a class that has a vast amount of information about the touch or mouse-click. I've detailed some of the information in this table:

CurrentPoint MemberDescription
Position.XThe X-coordinate position (in pixels) of where the press occurs. If it is a finger touch, it is positioned at the center of the finger press.
Position.YThe Y-coordinate position of where the press occurs.
PointerIDAn integer that helps you keep track of touches. When you touch the screen or click, and then perform a drag, that entire motion is assigned this ID. This helps you keep track of where a user is moving their mouse or finger.
PropertiesThis is a large class within CurrentPoint which gives you a host of additional information about the touch. A table of available properties in this class can be found here.
PointerDeviceThis is another large class within CurrentPoint which gives you lots of information about the device that was used to make the touch. Here is a table of its properties.

This table hardly does this class justice. I could easy do a full tutorial covering everything this class is capable of, but we won't really need anything else for this tutorial, so I'll cut it short.


PointerMoved and PointerReleased

These two events are very similar to PointerPressed, they just occur at different times. PointerMoved occurs when the touch or mouse-drag changes its position. PointerReleased occurs when the touch ends.

Each of these events have access to PointerEventArgs, so we can also access CurrentPoint and all that it offers. CurrentPoint->PointerID stays the same for all events with a single touch or mouse-drag, which helps you track which events belong to which touch "instance".


PointerWheelChanged

This event triggers whenever you scroll the wheel on your mouse. It also uses PointerEventArgs.

Accessing information about the wheel change is pretty simple. It's stored in a single integer, like this:

    void PointerWheelChanged(CoreWindow^ sender, PointerEventArgs^ args)
    {
        int Wheel = args->CurrentPoint->Properties->MouseWheelDelta;
    }

MouseWheelDelta measures wheel changes is increments of 120. In other words, one "notch" of the mouse wheel is 120 in MouseWheelDelta. A positive value shows that the wheel is being rotated up (away from the user) and a negative value shows that it's being rotated down (toward the user).


These events have been mostly events having to do with user input. In the next lesson, I want to talk about more critical events, such as when the application suspends and resumes, or when the the window is closed or resized.

Onward!

Next Lesson: The Application Lifecycle

GO! GO! GO!