共用方式為


Chapter 5: The Hilo Common Library

Hilo is made up of a number of Windows 7 applications and each has a main window and one or more child windows. Hilo implements a lightweight object orientated library to help to create and manage these windows and to handle messages sent to them. In this article we will introduce the Hilo Common Library so that future articles can focus on the features of the applications themselves and the Windows 7 features used to implement them.

Introducing the Hilo Common Library

The Hilo Common Library contains classes that are common to all the projects in the Hilo solution. These classes provide the basic infrastructure to access and provide reference-counted objects, to create windows, and to handle Windows messages.

Reference Counting

Hilo provides a template structure called ComPtr<> to handle reference counts. In spite of the name, this smart pointer class is not exclusive for COM interface pointers. It will work with any interface derived from IUnknown regardless of whether the object is activated by COM or runs in a COM apartment. The class implements a destructor that decrements the encapsulated interface pointer. This means that you do not need to worry about calling the IUnknown::Release method, the ComPtr<> template will do it for you when it is needed.

Many of the Hilo classes implement an interface derived from IUnknown. To create objects Hilo provides a template class called SharedObject<>. This is a mixin class: the class derives from the type provided as the template parameter so the mixin class has access to the public and protected members of the template parameter class. The SharedObject<> class provides static Create methods that create an instance of the class on the C++ heap. The Create methods query for an interface called IInitializable on the object and if it implements this interface the Create method calls the Initialize method on this interface. Code in Initialize is called after the object has been constructed but before it is first used. Listing 1 shows an example of the use of the SharedObject<> class.

The SharedObject<> implements the IUnknown::AddRef and IUnknown::Release methods to increment and decrement the reference count on the object. If the reference count reaches a value of zero then the IUnknown::Release method calls the delete operator to destroy the object.

The final method of the IUnknown method is QueryInterface. This method is called to request a specific interface on an object and it requires that the base class implement a helper function called QueryInterfaceHelper to check if the class implements the specified interface. If so, return an appropriate pointer.

Designing the Common Library

The lifetime of a Windows application is determined by the lifetime of the entrypoint function, WinMain. A Windows application is represented on screen by a window showing a frame and adornments like the close and minimize buttons, and typically closing the main window will cause the WinMain function to return and close the process. The process and the windows it shows are all Windows objects.

A C++ application provides C++ objects for the process and its windows. The lifetimes of the C++ objects are determined by C++ concepts like when the new and delete operators are called (for heap-based objects) and scope of the stack frame (for stack-based objects). With the Hilo Common Library a C++ object is used to provide message handlers for all the windows and this means that the lifetime of the C++ object must be longer than the lifetime of the window.

The entrypoint function for the Hilo Browser is shown in Listing 1. The RunMessageLoop method provides a standard message pump that is a while loop that gets messages from Windows and dispatches them to the appropriate message handler function in the process. This loop executes until a WM_QUIT message is received, at which point the RunMessageLoop method returns and the _tWinMain function completes, finishing the process.

Listing 1 Entrypoint function for Hilo Browser

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
    ComPtr<IWindowApplication> mainApp;
    HRESULT hr = SharedObject<BrowserApplication>::Create(&mainApp);
    if (SUCCEEDED(hr))
    {
        hr = mainApp->RunMessageLoop();
    }
    return 0;
}

In this example, the BrowserApplication object is created on the C++ heap by the call to SharedObject<>::Create, and the ComPtr<> smart pointer object ensures the object is deleted when the mainApp variable goes out of scope. The BrowserApplication object provides the code to handle messages sent to the main window, and to create the objects that handle the messages for its child windows.

Designing the Windows Classes

The Window class implements IWindow and is a thin wrapper over the Windows API. This class encapsulates a HWND value and is used to give simple access to basic Windows API windowing functions, for example to access the title, position and size of the window. Classes that provide message handlers implement the IWindowMessageHandler interface. The IWindow interface provides the methods shown in Listing 2 to give access to the object that handles the messages for a window.

Listing 2 Methods for the Windows Message Handler

HRESULT __stdcall GetMessageHandler(__out IWindowMessageHandler** messageHandler);
HRESULT __stdcall SetMessageHandler(__in IWindowMessageHandler* messageHandler);

Figure 1 shows the Universal Modelling Language (UML) class diagram for the main classes in the Hilo Common Library. The BrowserApplication class is specific to the Hilo Browser project and the AnnotatorApplication class is specific to the Annotator project, the rest of the classes and interfaces are shared between all the Hilo projects and are provided through a static library project called Common. Figure 1 shows a selection of the attributes and operations of these classes.

Figure 1 UML diagram for the basic windowing classes, the shaded classes are provided by the Common Library

Ff919194.5e6ec0cb-be5d-4339-95c7-849b8751bf25-thumb(en-us,MSDN.10).png

The WindowMessageHandler class implements a method called IWindowMessageHandler::OnMessageReceived to handle Windows messages sent to a window. The class does this by providing generic handling and accesses additional code by calling virtual member functions polymorphically (Figure 1 1WM_PAINT message is sent to a window the default handler code in the OnMessageReceived method performs standard handling of this method by calling the Windows API functions BeginPaint to prepare the window for painting and EndPaint to perform clean up. In between these function calls, the OnMessageReceived method calls the virtual method OnRender to call additional rendering code provided by the derived class. This is illustrated in Figure 2. Using virtual methods like this means that the WindowMessageHandler class provides default message handling for common messages and derived classes can provide additional handling.

Figure 2 Message handling in Hilo

Ff919194.f8f577c0-2d91-4e2d-adc0-0442a55eafbf-thumb(en-us,MSDN.10).png

The WindowApplication class implements the message loop and gives access to two objects: the main application window and a window factory object. The window factory (implemented by the WindowFactory class which will be covered in a moment) creates windows through calls to the Windows API function CreateWindow. The message handling for the main application is very basic: in effect it merely ensures that a WM_DESTROY message sent to the main window will supply a WM_QUIT message to the message pump, which will close the process as described above.

The class for the main window for each of the Hilo processes (Browser and Annotator) derives from WindowApplication and adds additional functionality for the main window. The BrowserApplication has two child windows that fill the client area of the main window and the layout of these windows is managed by an object that implements the IWindowLayout interface. This layout object holds IWindow interface pointers to both of these child windows, and the BrowserApplication object holds an index for each of these window so that it can access the window from the layout object. The AnnotatorApplication object has a window to edit images and it uses a Windows Ribbon to allow the user to access commands .

The child windows of the Hilo applications (to implement the Browser carousel and media pane, and the annotator editor) are implemented by classes derived from WindowMessageHandler but these are not shown in Figure 1.

Creating Windows

Typically a Windows application creates windows through a call to the CreateWindow function and provides the name of a registered Windows class. The Windows class is a structure that contains a pointer to a function that handles the Windows messages destined for the newly created window. The code to do this in the Hilo Common Library is a C++ class called WindowFactory. An instance of the WindowFactory class is created in the WindowApplication::Initialize method in order to create the application’s main window.

Figure 3 UML for the WindowFactory class

Ff919194.c3510df2-a0ea-4a3d-adb1-69fddf18e4ac(en-us,MSDN.10).png

The WindowFactory class is very simple, as can be seen by the UML in Figure 3. The IWindowFactory::Create method creates a window of a specified size and location, and with a specified message handler object. If the window is a child of another window the Create method can be passed the IWindow pointer to that window. The first action of the Create method is to call the private method RegisterClass which registers the static method WindowFactory::WndProc as the Windows procedure. This means that all windows created by the window factory object are handled by the same Windows procedure, but as you will see in a moment, the WndProc function forwards messages to the windows handler object for the window.

Once the Windows class has been registered the IWindowFactory::Create method creates a new instance of the Window object which will encapsulate the HWND of the new window once it is created. The Window object is then initialized with the message handler object through a call to IWindow::SetMessageHandler. Finally, the Create method calls the Windows API function, CreateWindow, to create the new window. The CreateWindow method is passed the Window object as private data through the lpParam parameter, and this means that this object is made available to the windows procedure. Note that at this point the encapsulated HWND in the Window object has not been assigned.

Listing 3 The WindowFactory Windows procedure

LRESULT CALLBACK WindowFactory::WndProc(
   HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam)
{
    LRESULT result = 0;
    ComPtr<IWindow> window;
    ComPtr<IWindowMessageHandler> handler;

    if (message == WM_NCCREATE)
    {
        LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
        window = reinterpret_cast<IWindow*>(pcs->lpCreateParams);
        window->SetWindowHandle(hWnd);

        ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, PtrToUlong(window.GetInterface()));
    }
    else
    {
        IWindow* windowPtr = reinterpret_cast<IWindow*>(
            ::GetWindowLongPtrW(hWnd, GWLP_USERDATA));

        if (windowPtr)
        {
            window = dynamic_cast<IWindow*>(windowPtr);
        }
    }

    if (window)
    {
        window->GetMessageHandler(&handler);
    }

    if (!window || !handler || FAILED(
         handler->OnMessageReceived(window, message, wParam, lParam, &result)))
    {
        result = ::DefWindowProc(hWnd, message, wParam, lParam);
    }

    return result;
}

Listing 3 shows the WndProc function that is called for all messages sent to all windows in the Hilo applications. This method has two purposes. When the window is first created, the method will be passed the WM_NCCREATE message, and the lpParam parameter will represent the private data that was passed as the last parameter of the CreateWindow function, that is, the pointer to the IWindow interface pointer of the newly created window. The function extracts the IWindow interface pointer from the lpParam parameter and initializes the Window object with the HWND of the newly created window by calling the IWindow::SetWindowHandle method. Next, the code attaches the IWindow interface pointer to the window as user data by calling the Windows API function SetWindowLongPtr. In these actions the method has attached the C++ Window object (and hence also the message handler object) to the window represented by the hWnd parameter.

The second purpose of the WndProc function is to forward all Windows messages to the message handler object for the window. After the first call to the WndProc function, subsequent calls to the function can retrieve the user data from the HWND with a call to the GetWindowLongPtr Windows API and cast to a pointer to the IWindow interface. From this interface pointer the method can access the message handler object and forwards all messages to the OnMessageReceived method on the message handler object.

The message handler object implements the IWindowMessageHandler interface. When a window is destroyed it is sent the WM_DESTROY message and the IWindowMessageHandler::OnMessageReceived method does two things. First it calls the virtual method OnDestroy and the default implementation in WindowApplication calls the PostQuitMessage Windows API function that posts the WM_QUIT message to the application window, which breaks the message pump and closes the process. The second action of the WM_DESTROY handler in the OnMessageReceived method calls IWindow::SetMessageHandler passing a nullptr. This has the effect of releasing the reference on the message handler object and destroying the object.

Conclusion

The Hilo applications use a lightweight library to provide basic Windows message handling. In this article you have seen the main classes in the library and how they interact. You have learned how these classes create and destroy windows, and how these objects relate to the C++ objects that handle Windows message. In the next article we will cover drawing on a window using the Windows 7 Direct2D API.

Putting It All Together: The Browser Application

Listing 1 shows the entrypoint function for the Browser, and shows that the main window is handled by the BrowserApplication class. The SharedObject::Create method creates an instance of this class on the C++ heap and then calls the BrowserApplication::Initialize method to perform post-construction initialization on the object. This method first calls the base class to create the windows factory object and uses this factory object to create the application’s main window. The Initialize method then calls two functions InitializeCarouselPane and InitializeMediaPane to create the carousel and media pane windows as child windows of the Browser application window and Figure 4 shows the windows provided by these classes.

The main window of the Browser application contains two child windows, one for the carousel and history list and another to show the photos. The BrowserApplication::Initialize method creates an instance of the WindowLayout class to manage the size an position of these child windows. The final action of the BrowserApplication::Initialize method is to call the Shell API to get the Pictures library as the first folder to show in the carousel.

Figure 4 Main classes in Hilo Browser

Ff919194.5e578e93-d5e7-4d0d-9e9a-5dbe01c74377-thumb(en-us,MSDN.10).png

Creating the Child Windows

The InitializeCarouselPane method creates a window as a child of the main Browser application window and an instance of the CarouselPaneMessageHandler class to handle the messages from that window. The OnCreate method of the CarouselPaneMessageHandler class is called when the carousel window is first created and this method initializes the Direct2D resources used to draw the orbital, the folder thumbnails, and the history item stack.

The InitializeMediaPane method creates a window as a child of the main Browser application window and an instance of the MediaPaneMessageHandler class to handles the messages for that window. When the MediaPaneMessageHandler object is first created the Initialize method is called, which creates a ThumbnailLayoutManager object to manage the items shown in the pane. The MediaPaneMessageHandler class does not provide an OnCreate method and so only default handling is performed for the WM_CREATE message. The media pane also uses Direct2D and the resources are initialized immediately before they are used by the Redraw method (which is called as part of the handling of the WM_PAINT message).

Using the Layout Object

When the Hilo Browser window is resized the child windows must be resized too and this is the function of the layout object. The most important method of this class is UpdateLayout. This method resizes and repositions the child window to fill the client area of the main Browser window. The layout object is initialized with the height of the carousel, which is the maximum size needed to show the inner orbital, including the folder icons and the folder text. The UpdateLayout method sets the carousel window the height and to the width of the client area of the main browser window and it positions the carousel at the top of the client area. The UpdateLayout method places the media pane immediately below the carousel pane and sizes it to have the same width as the client area of the browser window and the height of the remaining space in the client area.

The media pane contains the thumbnail layout manager that calculates the positions of the navigation arrows and the images from the size of the arrows, the thumbnails, and the size of the media pane. If the media pane is large then more than one row of thumbnails can be displayed.

Painting the Windows

The base class WindowMessageHandler handles the WM_PAINT message by calling the virtual OnRender method, and handing the responsibility to the derived class BrowserApplication. This derived method accesses the layout manager to get access to the message handler objects for the carousel and media pane and calls the IPane::Redraw method on these handler objects, which in turn calls a method called DrawClientArea that draws the window. The items within the carousel and media pane windows may be animated (for example, folders spin around the carousel orbit, or thumbnails moving in the media pane) and the animation is performed by altering the position of the items in the pane between refreshes. If animation is used then the positions of the items are retrieved by the DrawClientArea method from the animation manager.

Handling Mouse Moves and Key Presses

Touch screen touches are treated as left mouse button clicks, in addition, the user can use the mouse or the keyboard to interact with the application. Mouse and keyboard actions are handled by appropriately named virtual methods on the message handler classes.

The CarouselPaneMessageHandler class handles left mouse clicks in three ways. First, if the click is in the top left corner of the pane then it is used to expand the history stack or, if the back button is clicked, navigate back in the history list. Second, the click is used to select a folder (either in the inner orbital, or if the history stack is shown, one of the items in the history stack). The third type of click, and this is the reason for the majority of the code in the OnLeftMouseButtonDown, OnLeftMouseButtonUp and OnMouseMove methods, is to allow the user to spin the carousel by dragging an item on the inner orbital. The user can spin the carousel by moving the mouse wheel and the CarouselPaneMessageHandler handles this in the OnMouseWheel method. If the keyboard is used, the CarouselPaneMessageHandler class interprets the Left and Right Arrow keys as spinning the carousel left and right, and interprets pressing the Backspace key as a signal to navigate up the history stack.

The media pane shows the images and arrows to scroll through the image list. The MediaPaneMessageHandler class handles the touch or mouse click events to scroll the images left and right, as well as the mouse wheel events which will also scroll the list. A left mouse click on a navigation button will scroll the list in the appropriate direction. A mouse button double-click is interpreted by the OnLeftMouseButtonDoubleClick method to toggle between browsing and slide view mode. In browsing mode the top half of the main window shows the carousel, but in slideshow mode the carousel panel is hidden and the currently selected image is stretched to fill as much of the Browser window as is possible while maintaining the correct aspect ratio. The user can now browser through all the images one by one. The media pane handles key clicks in OnKeyDown. The Page Up and Page Down keys scroll the image list left and right (similar to clicking the left and right navigation arrows) and the Plus Sign and Minus Sign keys are used to change the size of the thumbnails.

Closing Down The Application

When the user clicks the close button a WM_CLOSE message is sent to the application window and the WindowMessageHandler class handles this by calling the virtual OnClose method. None of the Hilo classes override the OnClose method, and the default implementation does nothing other than calling the Windows default handing. Windows default handling of WM_CLOSE calls the DestroyWindow function which sends the WM_DESTROY message to the window. The WindowMessageHandler class handles this message by calling the OnDestroy virtual method. and the BrowserApplication::OnDestroy method calls a method called Finalize on the layout object. The WindowLayout::Finalize method iterates through all the child windows and calls a Finalize method on each one, which performs final cleanup on each window. Finally, the BrowserApplication::OnDestroy method then calls the base class implementation of OnDestroy, which calls the PostQuitMessage Windows API function that posts the WM_QUIT message to the application window and this message breaks the while loop of the message pump, which results in the process closing.

Next | Previous | Home