Part B - Direct3D

Event Iteration

Create the main application window
Construct the event iteration for processing messages to the Application

Sample | Framework | Coordinator | Window | UserInput | References



The second step in developing a digital game, after identifying the user's selected configuration, is setting up the application's main window accordingly and handling user initiated changes that the operating system sends to that window.  The user may suspend focus, restore it, or request a configurational change.

Every Windows application interacts with the operating system through its own dedicated window.  This dedicated window is called the main application window.  The application determines the space that it occupies.  The operating system sends all messages destined for the application to this main window.  Messages include ones to re-configure the window, suspend the application, restore it, and terminate execution.  The procedure for this window processes the messages on a first-come first-served basis.  The application uses event-driven logic in processing the stream of incoming messages.  This logic consists of a top-level iteration that extracts each message from the message queue and a low-level window procedure that processes that message. 

This chapter presents code for creating the main application window, retrieving messages from the message queue, and handling the messages themselves.  It covers full-screen and windowed configurations, window resizing, suspension, restoration, and re-configuration. 


Event Iteration Sample

The Event Iteration Sample either displays an empty window with a caption if the user has selected the "run in a window" option or does not change the displayed data change if the user has selected a full-screen option. 

event iteration blank window

This sample can processes any of the following requests:

  • re-configure the application - the user has pressed the 'F1' key
  • switch out of or into the application - the user has pressed the 'Alt-Tab' key pair
  • terminate the application's execution - the user has pressed the 'Esc' key or the 'Alt-F4' key pair

For running in a window, the framework uses default dimensions set in the Translation.h file.  Both the window border and its title bar are outside the client area defined by these dimensions. 


Framework

Enhancements to the framework affect both the Model Layer and the Translation Layer. 

event iteration sample map

They include:

  • an upgraded Coordinator component that creates the Window component
  • a new Window component, which contains the APIWindow class.  This class sets up the main application window and retrieves messages from the Windows message queue
  • a minor modification to the UserInput component

The Windows APIs, unlike DirectX, are not built on COM technology.  Accordingly, they do not provide access to methods on interfaces and do not require client applications to participate in reference counting.  Instead, the Windows APIs expose global functions to their client applications. 

System Events

These enhancements accommodate and support:

  • the handling of messages send to and sent from the application
  • reconfiguration of the application at the user's request
  • loss and restoration of focus at the user's request
  • termination of execution at the user's request

We introduce a uniform naming convention for all framework methods that implement these system events.

Message Handling

Windows messages destined for the application can originate with the user, the application, or the operating system. 

The framework extracts each message from the message queue for the main application window within the framework's event iteration.  It dispatches the retrieved message to the window procedure associated with the main application window.  The window procedure receives the message, processes it, and passes it to the operating system.  The window procedure is the clearing house for all messages to the application, regardless of origin. 

The window procedure for the main application window is distinct from the window procedure for the dialog box, which collects the user's selected configuration. 

Re-Configuration

The window procedure for the main application window handles a re-configuration request by responding to an 'F1' key press.  Changes to the configuration may involve a change in the display device, its resolution mode, or both, each of which requires a release of the current configuration. 

There are three distinct steps: releasing the current configuration, retrieving the user's newly selected configuration, and setting up that new configuration.  Releasing the configuration involves calling the Release() method on the COM objects that specifically support the old configuration.  Re-interrogating the user involves re-displaying the Dialog Box and collecting the user's new choices.  Setting up the new configuration involves retrieving a new interface to the supporting COM objects (recalling the Create...() functions for those COM objects). 

The window procedure initiates this three-step process by posting two separate messages: one to suspend the application and one to reset its configuration.  The Coordinator object handles the suspension and resetting independently.

Loss and Restoration of Focus

The window procedure for the main application window responds to loss and restoration of focus.  The Coordinator object handles suspension and restoration wherever required throughout the framework.  It saves or turns off certain features before the loss of focus and restarts or resetting connections just after restoration.

Termination of Execution

The window procedure for the main application window responds to any request to terminate execution.  On termination, the window procedure closes down the window and subsequently initiates its destruction. 

Naming Convention

The methods that implement these events use the following naming convention:

  • configure() - save the new configuration parameters in preparation for setup
  • setup() - setup an object for the current configuration parameters
  • suspend() - suspend an object just before loss of focus
  • restore() - restore an object just after restoration of focus
  • release() - release an object from all connections to the hardware

Coordinator

The Coordinator component works with two components, both in the Translation Layer:

  1. UserInput - presents the configuration options to the user and holds the user's selected configuration
  2. Window - creates the main application window, retrieves messages from the message queue, and handles the messages

The iCoordinator interface exposes five new methods to the framework:

 // iCoordinator.h

 class iCoordinator {
   public:
     virtual void reset()                    = 0;
     virtual void resize()                   = 0;
     virtual int  run()                      = 0;
     virtual void suspend()                  = 0;
     virtual void restore()                  = 0;
     virtual void release()                  = 0;
 };
 iCoordinator* CoordinatorAddress();

The reset() method reconfigures the application.  The resize() method resizes the main application window.  The suspend() method suspends execution.  The restore() method resumes execution.  The release() method releases the current configuration. 

The Coordinator class definition adds three instance variables:

  • a class pointer to the Coordinator object itself
  • a pointer to the APIWindow object
  • a flag identifying the activity status

and defines six new methods:

  • Address() - returns the object's address
  • getConfiguration() - configures the framework
  • reset() - re-configures the framework
  • resize() - resizes the application window
  • suspend() - suspends execution
  • restore() - resumes execution
  • release() - release the configuration
 // Coordinator.h

 class Coordinator : public iCoordinator {

     static iCoordinator* coordinator; // points to the Coordinator object
     iAPIWindow*          window;      // points to the window object
     iAPIUserInput*       userInput;   // points to the user input object
     bool                 active;      // application is active?
     Coordinator(const Coordinator& s);            // prevents copying
     Coordinator& operator=(const Coordinator& s); // prevents assignment
     bool getConfiguration();

   protected:
     virtual ~Coordinator();

   public:
     static iCoordinator* Address() { return coordinator; }
     Coordinator(void*, int);
     void reset();
     int  run();
     void resize();
     void suspend();
     void restore();
     void release();
 };

Implementation

Construct

The constructor creates the APIWindow object and stores its address:

 Coordinator::Coordinator(void* hinst, int show) {
     coordinator = this;
     window      = CreateAPIWindow(hinst, show);
     userInput   = CreateAPIUserInput(hinst);
 }

Get Configuration

The getConfiguration() method retrieves the user's newly selected configuration, releases the old configuration, configures the APIWindow object for the new choices and finally sets it up: 

 bool Coordinator::getConfiguration() {
     bool rc = false;
     if (userInput->getConfiguration()) {
         release();
         window->configure(userInput->getWindowMode());
         rc = window->setup();
     }
     return rc;
 }

Reset

The reset() method starts re-configuration of the application: 

 void Coordinator::reset() { getConfiguration(); }

Run

The run() method retrieves the initial configuration and initiates the event iteration.  If no messages are left in the message queue, the iteration checks the application's activity state.  If it is inactive, the iteration places it in a wait state. 

 int Coordinator::run() {
     int  rc = 0;
     bool keepgoing = getConfiguration();
     while (keepgoing) {
         if (window->processMessages(rc, keepgoing))
             ; // intentional
         else if (!active)
             window->wait();
     }
     return rc;
 }

Iteration ceases once the getMessage() method on the APIWindow object returns a keepgoing flag of false.  The rc argument holds the return code to be passed to the operating system on termination of execution.

Resize, Suspend, Restore, and Release

The resize() method resizes the window if the application is active and in 'run in a window' mode:

 void Coordinator::resize() {
     if (active && userInput->getWindowMode())
         window->resize();
 }

The suspend() method simply deactivates the application:

 void Coordinator::suspend() { active = false; }

The restore() method activates the application:

 void Coordinator::restore() { active = true; }

The release() method releases the APIWindow object:

 void Coordinator::release() {
     window->release();
 }

Destroy

The destructor deletes the two objects by calling their Delete() methods: 

 Coordinator::~Coordinator() {
     userDialog->Delete();
     window->Delete();
     coordinator = nullptr;
 }

Window

The Window component manages the communications between the main application window and the Windows API.  These communications take the form of messages.  The figure below shows the flow of control during the processing of messages, which have been waiting in the message queue.  The processing starts within the event iteration of the Coordinator::run() method and ends in the window procedure. 

Message Processing Map

The component's APIWindow object creates the main application window, retrieves messages from the message queue, dispatches these messages to the window procedure and finally processes them in that window procedure.  Processing consists of calling the appropriate method on the Coordinator object.

Only if there is no message in the message queue, the Coordinator object puts the application into a wait state.

The iAPIWindow interface exposes eight methods to the Coordinator class:

  • configure() - configures the APIWindow object for setup
  • setup() - sets up the main application window
  • processMessage() - retrieves a message from the message queue and dispatches it
  • resize() - resizes the main application window
  • messageBox() - pops up a message box
  • wait() - places the application in a wait state
  • release() - destroys the application window
  • Delete() - destroys the APIWindow object

 // iAPIWindow.h

 class iAPIWindow {
     virtual void  configure(bool, int, int)                 = 0;
     virtual bool  setup()                                   = 0;
     virtual bool  processMessage(int& rc, bool& done) const = 0;
     virtual void  resize()                                  = 0;
     virtual void  messageBox(const wchar_t*) const          = 0;
     virtual void  wait()                                    = 0;
     virtual void  release()                                 = 0;
     virtual void  Delete() const                            = 0;
     friend class Coordinator;
 };
 iAPIWindow* CreateAPIWindow(void*, int);

The APIWindow class derives from the this interface and includes the following instance variables:

  • a pointer to the application instance
  • a pointer to the main application window
  • parameters that describe the window and its style
 // APIWindow.h

 class APIWindow : public iAPIWindow {
     void* application;     // points to the application
     void* hwnd;            // points to the main app window
     int   show;            // how to display the application window
     int   width;           // width of the client area
     int   height;          // height of the client area
     int   clientWidth;     // width of client area
     int   clientHeight;    // height of client area
     int   oldClientWidth;  // width of old client area in a window
     int   oldClientHeight; // height of old client area in a window
     bool  runinwndw;       // run in a window?
     APIWindow(const APIWindow&);
     APIWindow& operator=(const APIWindow&);
     virtual ~APIWindow();
   public:
     APIWindow(void*, int);
     void configure(bool, int, int);
     bool setup();
     bool processMessage(int&, bool&) const;
     void resize();
     void messageBox(const wchar_t*) const;
     void wait();
     void release();
     void Delete() const { delete this; }
 };

The APIWindow object saves the most recent running in a window dimensions for possible restoration after switching from full-screen to run in a window mode.

Implementation

The local macros used to creating windows are: 

 #define WND_FULL_NAME WND_CAPTION L" (2 Dimensional)"
 #define CLASS_NAME    L"fwk4gps"
 #define WND_STYLE_W   WS_OVERLAPPEDWINDOW
 #define WND_EXSTYLE_W 0
 #define WND_STYLE     WS_POPUP
 #define WND_EXSTYLE   WS_EX_TOPMOST

Construct

The constructor stores the integer identifying how to show the window initially and the address of the application instance 's address, initializes the window dimensions, and registers the class used by the operating system to create the main application window: 

 APIWindow::APIWindow(void* h, int s) : show(s) {
     application     = h;
     clientHeight    = WND_WIDTH;
     clientWidth     = WND_HEIGHT;
     oldClientWidth  = WND_WIDTH;
     oldClientHeight = WND_HEIGHT;
     registerAPIWindowClass((HINSTANCE)application);
 }

Configure

The configure() method sets the dimensions of the new client area in preparation for setup: 

 void APIWindow::configure(bool r, int w, int h) {
     runinwndw = r;
     if (runinwndw) {
         clientWidth  = oldClientWidth;
         clientHeight = oldClientHeight;
     }
     else {
         width  = w;
         height = h;
         clientWidth  = width;
         clientHeight = height;
     }
 }

Setup

The setup() method creates the main application window using the configuration stored in the instance variables and begins displaying the window: 

 bool APIWindow::setup() {
     int wndStyle;   // window style
     int wndExStyle; // window style extension
     int wndWidth;   // entire window width
     int wndHeight;  // entire window height

     if (runinwndw) {
         // set the window styles
         wndStyle    = WND_STYLE_W;
         wndExStyle  = WND_EXSTYLE_W;
         RECT rect;
         rect.left   = 0;
         rect.top    = 0;
         rect.right  = clientWidth;  // desired width of the client area
         rect.bottom = clientHeight; // desired height of the client area
         AdjustWindowRectEx(&rect, wndStyle, FALSE, wndExStyle);
         wndWidth    = rect.right  - rect.left;
         wndHeight   = rect.bottom - rect.top;
     } else {
         wndStyle   = WND_STYLE;
         wndExStyle = WND_EXSTYLE;
         wndWidth   = clientWidth;
         wndHeight  = clientHeight;
     }
     // (re)create the main application window
     if (!(hwnd = CreateWindowEx(wndExStyle, CLASS_NAME, WND_FULL_NAME, wndStyle,
      0, 0, wndWidth, wndHeight, nullptr, nullptr, (HINSTANCE)application,
      nullptr)))
         error(L"APIWindow::10 Failed to create application window");
     else {
         ShowWindow((HWND)hwnd, show);
         UpdateWindow((HWND)hwnd);
     }
     return hwnd != nullptr;
 }

The AdjustWindowRectEx() function increases the configuration dimensions to accommodate the border and titlebar so that they do not infringe on the client area requested by the user.  The ShowWindow() function defines how to display the newly created window.  The UpdateWindow() function sends the first message to the newly created window. 

Resize

The resize() method stores the dimensions of the resized client area: 

 void APIWindow::resize() {

     // determine the new size of the entire window
     RECT rect;
     GetClientRect((HWND)hwnd, &rect);
     clientWidth     = rect.right  - rect.left;
     clientHeight    = rect.bottom - rect.top;
     oldClientWidth  = clientWidth;
     oldClientHeight = clientHeight;
     width           = clientWidth;
     height          = clientHeight;
 }

The GetClientRect() function returns in its second argument the dimensions of the window pointed to by the first argument. 

Process Message

The processMessage() method extracts the next message from the message queue, if any messages exists, translates that message, and dispatches it to the window procedure.  If this method finds a message in the queue, it returns true.  If there is no message in the queue, it returns false.  If the message is a WM_QUIT message, it sets the value in its first parameter to the value of the message's wParam member and turns off the keepgoing flag:

 bool APIWindow::processMessage(int& rc, bool& keepgoing) const {
     bool retrievedMessage = false;
     MSG msg;
     if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
         if (msg.message == WM_QUIT) {
             rc = msg.wParam;
             keepgoing = false;
         }
         else {
             TranslateMessage(&msg);
             DispatchMessage(&msg);
         }
         retrievedMessage = true;
     }
     return retrievedMessage;
 }

PeekMessage() looks at the next message in the queue and, if there is one, stores it in msg.  The PM_REMOVE flag directs PeekMessage() to remove the message that it has read from the queue.  TranslateMessage() translates any virtual-key message into a character message.  DispatchMessage() sends the retrieved and translated message to the window procedure. 

Wait

The wait() method places the application in a wait state: 

 void APIWindow::wait() { WaitMessage(); }

WaitMessage() transfers control to another application and does not return until a message existsin the message queue for the main application window. 

Message Box

The messageBox() method displays a message box containing the received string: 

 void APIWindow::messageBox(const wchar_t* str) const {
     if (hwnd) MessageBox((HWND)hwnd, str, L"Error", MB_OK);
 }

The MessageBox() function pops up a message box displaying the string at the received address with the caption "Error" and an OK button.  Pressing the button closes the message box.

Release

The release() method destroys the main application window: 

 void APIWindow::release() {
     if (hwnd) {
         DestroyWindow((HWND)hwnd);
         show = SW_SHOW;
         hwnd = nullptr;
     }
 }

The DestroyWindow() function simply puts a destroy window message in the message queue. 

Destroy

The destructor release the current object before destroying it: 

 void APIWindow::~APIWindow() { release(); }

Register a Window Class

The registerWindowClass() helper function defines the structure of the main application window in the system registry.  This definition includes the address of the window procedure associated with the main application window: 

 bool registerWindowClass(HINSTANCE application) {
     WNDCLASS wc;

     wc.style = CS_HREDRAW | CS_VREDRAW;
     wc.lpfnWndProc = ::wndProc; // this function will be called...
     wc.cbClsExtra = 0;           // ...to handle messages
     wc.cbWndExtra = 0;
     wc.hInstance = application;
     wc.hIcon = LoadIcon(application, IDI_APPLICATION);
     wc.hCursor = nullptr;
     wc.hbrBackground = nullptr;
     wc.lpszMenuName = nullptr;
     wc.lpszClassName = CLASS_NAME;
     // Register wc for all subsequent calls to create the window
     return RegisterClass(&wc) != 0;
 }

The lpfnWndProc member of WNDCLASS holds the address of the window procedure.  The lpszClassName member of WNDCLASS holds the name that the system registry will use to access the window.  (CLASS_NAME is defined in the local macros for the implementation file.)

The class defining the structure of the main application window must be registered with the operating system before the system can create the window. 

Window Procedure

The window procedure handles all of the messages dispatched by the processMessage() method.  These include:

  • WM_CREATE - creating the window
  • WM_SETCURSOR - setting the cursor's appearance
  • WM_ACTIVATEAPP - de/activating the application
  • WM_KEYDOWN - pressing a key - (Escape or F1 here)
  • WM_SIZE - resizing the window
  • WM_CLOSE - closing the window
  • WM_DESTROY - destroying the window
 LRESULT CALLBACK wndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
     static bool   quit        = false;
     iCoordinator* coordinator = CoordinatorAddress();

     switch (msg) {
       case WM_CREATE:    // called once when the window is first created
       case WM_SETCURSOR: // called whenever the mouse is moved to ...
         SetCursor(NULL); // cursor is invisible
         break;
       case WM_ACTIVATEAPP:
         if (wp) coordinator->restore();
         else    coordinator->suspend();
         break;
       case WM_KEYDOWN:
         switch (wp) {
           case VK_ESCAPE:
             PostMessage(hwnd, WM_CLOSE, 0, 0);
             quit = true;
             break;
           case VK_F1:
             PostMessage(hwnd, WM_ACTIVATEAPP, 0, 0);
             PostMessage(hwnd, WM_USER, 0, 0);
             break;
         }
         break;
       case WM_USER:
         coordinator->reset();
         break;
       case WM_SIZE:
         coordinator->resize();
         break;
       case WM_CLOSE:
         quit = true;
       case WM_DESTROY:
         if (quit) PostQuitMessage(0);
         break;
     }
     return DefWindowProc(hwnd, msg, wp, lp);
 }

The WM_ACTIVEAPP message identifies a change in focus for the application.  The value of wp indicates the type of change: 0 for loss of focus, non-zero for restoration of focus.  The PostMessage() function posts the message identified by the second argument.  Trapping the WM_DESTROY message ensures that the main application window closes down properly.  The PostQuitMessage() function posts a WM_QUIT message to the message queue with a return code as specified in the argument to this function.  The DefWindowProc() function sends the message to the Windows operating system for further processing.


User Input

The UserInput component exposes the window mode requested by the user to the framework.  The iAPIUserInput interface adds the method: 

 // iAPIUserDialog.h

 class iAPIUserInput {
   public:
     // ...
     virtual bool getWindowMode() const = 0;
     // ...
 };
 // ...

The APIUserInput class definition returns the window mode requested by the user: 

 
 class APIUserInput : public iAPIUserInput {
     // ...
   public:
     // ...
     bool getWindowMode() const      { return runinwndw; }
     // ...
 };

Reference




Previous Reading  Previous: Display Modes Next: Background Image   Next Reading


  Designed by Chris Szalwinski   Copying From This Site   
Logo