Developing Hilo
Developing Hilo
Introduction
"Hilo" is a series of articles and sample applications that show how you can leverage the power of Windows 7, using the Visual Studio 2010 and Visual C++ development systems to build high performance, responsive rich client applications. Hilo provides both source code and written guidance to help you design and develop Windows applications of your own. The series covers many topics, including the key capabilities and features of Windows 7, the design process for the user experience, and application design and architecture. Source code is provided so that you can see firsthand how the accompanying sample applications were designed and implemented. You can also use the source code in your own projects to produce your own rich, compelling applications for Windows 7. The Hilo sample applications are designed for high performance and responsiveness and are written entirely in C++ using Visual C++. These articles describe the design and implementation of a set of touch-enabled applications that allow you to browse, select, and work with images. They will illustrate how to write applications that leverage some of the powerful capabilities that Windows 7 provides. You will see how the various technologies for Windows 7 can be used together to create a compelling user experience. Chapter 1: Introducing Hilo 2: Setting up the Hilo Development Environment 3: Choosing Windows Development Technologies 4: Designing the Hilo User Experience 5: The Hilo Common Library 6: Using Windows Direct2D 7: Using Windows Animation Manager 8: Using Windows 7 Libraries and Description The first Hilo sample applicationthe Hilo Browserimplements a touch-enabled user interface for browsing and selecting photos and images.
This chapter outlines how to set up a workstation for the development environment so that you can compile and run the Hilo Browser sample application.
This chapter describes the rationale for the choice of the development technologies used to implement the Hilo applications.
This chapter describes the process and thoughts when developing the Hilo User Experience.
This chapter introduces the Hilo Common Library, a lightweight object orientated library to help to create and manage Hilo-based application windows and handle messages sent to them. This chapter describes how hardware accelerated Direct2D and DirectWrite are used in the Hilo sample application.
This chapter explores the Windows 7 Windows Animation Manager, that handles the complexities of image changes over time.
Files from many different locations can be accessed through a single logical location according to their type even though they are stored in many different locations.
1
the Shell
Libraries are user defined collections of content that are indexed to enable faster search and sorting. Hilo uses the Windows 7 Libraries feature to access the users images. This chapter describes the Hilo Annotator application, which allows you to crop, rotate, and draw on the photographs you have selected. Hilo Annotator uses the Windows Ribbon Control to provide easy access to the various annotation functions, and the Windows Imaging Component to load and manipulate the images and their metadata. This chapter examines the use of the Windows Ribbon control, which is designed to help users find, use, and understand available commands for a particular application in a way thats more natural and intuitive than menu bars or toolbars. In this chapter you will learn how the Windows 7 Imaging Component is used in the Hilo Browser and Annotator applications. The Windows 7 Imaging Component (WIC) allows you to load and manipulate images and their metadata. The WIC Application Programming Interface (API) has built-in component support for all standard formats. In addition, the images created by the WIC can be used to create Direct2D bitmaps so you can use Direct2D to change images. In this chapter, well describe how the Hilo applications have been extended to allow you to share photos via an online photo sharing site. To do this, Hilo uses the Windows 7 Web Services application programming Interface (WSSAPI). The Hilo Browser application has also been updated to provide additional user interface (UI) and touch screen features, and the Hilo Annotator application has been extended to support Windows 7 Taskbar Jump Lists. This chapter provides an overview of these new features. In the final version of Hilo, the Annotator and Browser applications provide a number of enhanced user interface (UI) features. For example, the Hilo Browser now provides buttons to launch the Annotator application, to share photos via Flickr, and touch screen gestures to pan and zoom images. In this chapter we will see how these features were implemented.
9: Introducing Hilo Annotator 10: Using the Windows Ribbon 11: Using the Windows Imaging Component
13: Enhancing the Hilo Browser User Interface 14: Adding Support for Windows 7 Jump Lists & Taskbar Tabs 15: Using Windows HTTP Services 16: Using the Windows 7 Web Services API
The Hilo Browser and Annotator support Windows 7 Jump Lists and taskbar tabs. Jump Lists provide the user with easy access to recent files and provide a mechanism to launch key tasks. Taskbar tabs provide a preview image and access to additional actions within the Windows taskbar. In this Chapter we will see how the Hilo Browser and Annotator applications implement support for Windows 7 Jump Lists and taskbar tabs. The Hilo Browser application allows you to upload photos to the Flickr online photo sharing application. To do this, Hilo uses Windows HTTP Services. This chapter will explore how this library is used in the Hilo Browser to implement its photo sharing feature.
The Hilo Browser application allows you to share your photos via Flickr by using the Share dialog. The previous chapter showed how the Share dialog uses the Windows HTTP Services API to upload the selected photos to Flickr using a multi-part HTTP POST request. Before the photo can be uploaded the Hilo Browser must first be authenticated with Flickr by obtaining a session token (called a frob), and then authorized to upload photos by obtaining an access token. To accomplish these two steps, Hilo Browser uses the Windows 7 Web Services Application Programming Interface (WWSAPI) to access Flickr using web services. In this chapter we will explore how the Hilo Browser uses this library.
Additional Resources
This series is targeted at C++ developers. If you are a C++ developer but are not familiar with Windows
2
development, you may want to check out the Learn to Program for Windows in C++ [http://msdn.microsoft.com/en-us/library/ff381399.aspx] series of articles.
Copyright Notice
This document is provided as-is. Information and views expressed in this document, including URL and other Internet Web site references, may change without notice. You bear the risk of using it. This document does not provide you with any legal rights to any intellectual property in any Microsoft product. You may copy and use this document for your internal, reference purposes. 2010 Microsoft. All rights reserved. Microsoft, Visual C++, Visual Studio, and Windows are trademarks of the Microsoft group of companies. All other trademarks are property of their respective owners.
Using Hilo
The Hilo applications allow you to select, annotate and share collections of photos in a natural fashion. To do this, Hilo takes advantage of some of the key features in Windows 7: Libraries, Touch, Windows Ribbon, Direct2D, and Animation. Tablet computers allow you to interact with applications through a touch screen and to input data through a stylus. Hilo is designed to take advantage of both the touch and pen features while still being usable through computers that only have a mouse. The animation features of Windows 7 are used in Hilo to good effect, giving you a more natural paradigm to browse files and navigate. This design was chosen so as to move away from the table-based paradigm used by most Windows applications (for example, the Tree View and List View controls), and instead to present data in a format that facilitates the use of the Touch interface while making data access simpler and more natural. The central Hilo application is the Hilo Browser as shown in Figure 11. Figure 1 1 the Pictures folder has six subfolders and these are shown on the same orbital in the carousel. Through Windows 7 Touch you can rotate these folders around the orbital to bring the folder of your choice to the front. In this view the carousel will only show one complete orbital that you can spin, however, the carousel will also show partial orbitals representing the parent folders. For example, above the single, complete, orbital in Figure 1 1 the partial orbital of the Pictures folder.
5
Figure 1 The Hilo Browser showing the carousel and the media pane
The lower half of the Hilo Browser window is the media pane which shows the images in the selected folder. The media pane shows the first few images, and other images are accessible by touching (or with a mouse, clicking) the media pane arrows. The media pane is designed for a different paradigm: touch sensitive screens. Visually, the location of a finger touch on the screen is quite imprecise, so rather than adding a small scroll bar to the media pane, in the Hilo Browser, the media pane is the scroll bar. The scroll bar is redundant when you can drag the items themselves using touch. So in the Hilo Browser you have two ways to display additional items: you can either touch the scroll arrows on the left and right of the pane, or you can flick anywhere in the media pane in the direction that you want the image list to move. This is a very natural gesture, and illustrates how having a touch interface means that application designers have to change how they think users will interact with user interface (UI) items. While gestures are very natural for people accessing the UI through touch, such gestures are unnatural with a mouse, so mouse users will use the scroll arrows or the mouse scroll wheel. The Hilo Browser also allows you to scroll images using the Page Up and Page Down keys, illustrating how successful Windows 7 applications can provide input features for mouse and keyboard users as well as Touch users. Similarly, youll find that the rotating carousel is a natural action to perform with your finger on a touch screen. We are used to moving cards or photographs around a table, and the carousel provides a similar paradigm for accessing folders. Mouse users have full access to the carousel and they too can enjoy spinning the folders around an orbital. When you navigate through the folder structure, the current selected folder is placed on the stack in the top left. Figure 1 1, Pictures is at the top of the stack. When you select a subfolder from the carousel, for example Summer, this new folder is placed on the top of the stack, as shown in Figure 21 As you continue through the folder structure each parent folder is placed on the stack. The arrow beneath the stack allows you to move up a level. Figure 2 The Hilo Browser showing the stack in the top left corner
You can expand the stack at any time through the Touch interface or via a click with the mouse. When you expand the stack all the folders are shown (Figure 31in the stack. To the right of the breadcrumb trail is the grayed out orbital of the subfolders at the bottom of the trail. Figure 3 The Hilo Browser showing the stack expanded
The orbital of each parent folder is shown as part of the expanded stack, but only the folders that are in the breadcrumb trail are shown. Furthermore, you cannot rotate the carousel orbitals in this view. However, you can select any folder in the breadcrumb trail which gives you a quick way to move to another folder and display its subfolders in the carousel. Restricting the breadcrumb trail like this to show only the folder in the trail gives a clean and less cluttered interface than, for example, the Tree View used in Windows Explorer, and makes it much easier for you to focus on the relevant folders. To revert back to the previous carousel view you merely have to touch or click on a folder. In this way you can expand and collapse the stack, making navigation through the folder tree a simple and natural action.
7
stop. The user can stop the carousel while it is spinning by touching a folder with their finger. By building in such natural interactions with the application, the designers of the Hilo Browser make it easy for a first time user to learn.
Lastly, application development requires a powerful and fully featured development environment. Visual Studio 2010 offers an integrated environment where you, as a developer, can use your existing skills to code and debug your projects. Visual Studio provides powerful tools to help you deliver quality code quickly. There are four editions delivering varying levels of tool support which means that you can pay for the level of tool support you need.
Conclusion
This article gave an introduction to the Hilo Browser application. It showed you the features of the Hilo Browser and introduced the key Windows 7 technologies used to provide those features. The next article will cover how to set up your development environment, and explains how to install Visual C++ and the Windows 7 SDK so that you can compile and run the Hilo Browser source code.
10
11
Next you will be asked where to install the application (here you should use the defaults) and finally you will be shown a progress dialog (Figure 3) showing the components being downloaded and installed. As a guide, the minimum installation of Visual C++ will involve downloading 146 MB of data. Figure 3 Downloading Visual C++ 2010 Express
12
Although the Hilo application does not use SQL Server, you will notice that Visual C++ 2010 will download and install SQL Server Compact edition. This is a separate product and it is used by Visual C++ 2010 to create the C++ program database (in previous versions of Visual Studio this was the .ncb file, which is used to store IntelliSense data for your code).
The menu command gives the registration dialog shown in Figure 5. Figure 5 Registering Visual C++ 2010 Express
13
When you click on the button marked Obtain a registration key online your browser will open the registration page on the Microsoft website. To obtain a registration key you must first log on using a Windows Live ID (which requires registering some minimal details) and then the website will return a registration key. Figure 6 Obtaining a registration key
You should copy this key and paste it in the Registration Key box on the registration dialog (shown in Figure 5) and then click the Register Now button. Once you have registered the product the time restriction will be removed.
Using Help
One of the most important parts of a development environment is the help system. Visual C++ 2010 comes with two types of help documentation: local and online and these are configured through the Manage Help Settings item on the Help menu (Figure 7). Figure 7 Managing the source of the help documentation
14
The first time that you run this command you will be asked for the location of the local help system (Figure 8), accept the value suggested by clicking OK. Figure 8 Starting the Help Library Manager
Next you will see the actual Help Library Manager (Figure 9). Figure 9 Running the Help Library Manager
15
Visual Studio 2010 help is browser based, but you have to choose whether the help data is locally installed, or web based. If you select the option to use the online help system then you can use help immediately. The advantage of the online system is that each page has a Community Content section that may contain helpful comments from members of the online community, and indeed, you can even add your own tips. The local help system does not benefit from the community contributions, but since it is installed on your computer it means that you can use it even if you do not have a network connection. Before you can use local help you must install it. The help content is supplied as libraries of related APIs and to download and install this content you should use the Help Library Manager option Install content from online. When you select this option the manager will first search the web for available content and list the help topics in a table with an Add link in the Action column next to each help topic (Figure 10). Figure 10 Installing documentation from online
The following help topics are relevant to Visual C++ Express: Visual Studio C++ Visual Studio (All) Visual Studio Fundamentals Win32 and COM Development
16
When you have selected at least one topic you can click the Update button to download and install the documentation. This process may take a while (if you choose the libraries in the list above, about 1 GB will be downloaded) so now is definitely a good time to have a cup of coffee or two. Unfortunately the Manager does not warn you before the download starts how much disk space will be needed, so you should ensure that you have several gigabytes of free space to take into account the space needed for the final documentation and for the intermediate files. Once the Manager informs you that the documentation has been installed you can close the main Manager dialog by clicking Exit. The first time that you use the locally installed help system Visual C++ 2010 will start the Help Library Agent. This is a simple HTTP server running on port 47873 on your computer. When you press F1 in Visual C++ 2010 to obtain help for a feature or a keyword the help system will determine the web page and create a URL to the appropriate file on port 47873 on the loopback address. The Help Library Agent will then locate the page within the compressed help files and return them to your registered browser. Other than the community content section, there is no difference between the online and local help. Both help systems have a search box and show a tree view of the available help (to aid browsing).
The following pages request that you accept the license agreement and then provide the location to install the SDK, accept the defaults. The next page lists the items that can be installed, Figure 12.
17
The tool indicates the disk space required for the selected options and, if you are using the web tool, you will also see an estimate of the amount of data that will be downloaded. A full install requires 4.5 GB of disk space and will download 2.5 GB of data. If you have sufficient disk space and installing from the DVD then you can simply install the entire SDK. If you have limited disk space or you are installing from the web then the absolute minimum you should install is the Win32 Development Tools from the Development Tools, Windows Development Tools section. The Win32 Development Tools will take up 42 MB of disk space. Once you have selected the install options and clicked the Next button the setup program will download, decompress and install the items you selected, Figure 13. Figure 13 Installing the SDK
When the setup program has finished you must reboot the computer to complete the installation. You do not need to make any changes to your C++ projects since Visual C++ 2010 will automatically add the paths to the SDK tools to the search path it uses.
18
The SDK uses the Microsoft Document Explorer to display help, rather than the web browser based system used by Visual C++ 2010. The Microsoft Document Explorer is the help system used by previous versions of Visual C++, however, it is not integrated into the Visual C++ 2010 development environment.
Debugging
There are too many options to list here in detail, however, every developer is different and customizing the development environment to something that the developer is comfortable with is important, so we will list the common settings here.
This page allows you to change the font used for individual tool windows, or several related windows within the design environment. Through this dialog you are able to choose a font size and font type that you are comfortable using. It is perhaps one of the most important configuration settings because you will spend most of your time reading text.
The Environment category has a subcategory called Keyboard, shown in Figure 15. Figure 15 Configuration page used to change keyboard shortcuts
This page allows you to specify the keyboard shortcut keys that you can use to perform commands in the development environment. The commands mentioned in the list box are menu items. To change a command you click on the Press shortcut keys text box and press the appropriate key combination, the page will inform you if this key combination is already being used and allows you to choose to assign the key combination to the command. If you typically use another development environment you may be used to different keyboard shortcuts than the defaults for Visual C++ 2010 Express. The ability to change the keyboard shortcuts means that you can get started to using Visual C++ immediately without having to learn new keyboard combinations.
The Advanced options are about the database that will be created to enable code browsing, shown in Figure 17. Figure 17 The code editor Advanced options
20
For example, the Rescan Solution Interval (by default 60 minutes) indicates how often the solution is rescanned to refresh the database. The browser database contains information about the classes and global functions in the project and this information is used by Class View, the navigation bar at the top of code windows and by the search menu items like Go To Definition, Go To Declaration and Find All References. The term database is used figuratively and literally because Visual C++ Express uses a SQL Server Compact database (with the extension sdf) to hold this code browser information.
In addition to exposing the Class View, selecting Expert Settings also enables other items, including the Property Manager, exposes menu items that allow you to enable the Extension Manager, and give you access to external tools like the MSIL Disassembler or Ildasm (for managed projects) and the Error Lookup utility. The Property Manager allows you to share project configurations between projects, while the Extension Manager gives you access to the installed extensions and allow you to browse the Microsoft online extensions gallery and download extensions.
The Hilo Browser solution is available as a ZIP archive from the MSDN Code gallery [http://code.msdn.microsoft.com/Hilo] . On the Downloads page click the link for the code you wish to download and accept the license agreement. The ZIP file will then be downloaded to your hard disk. Once the download has completed, unzip the contents of the archive to a folder on your hard disk (for example, the Documents\My Documents\Visual Studio 2010\Projects library). Now browse the solution folder and double click on the Hilo.sln file to start Visual C++ 2010 Express and load the solution. For each project that is loaded you may see a security dialog, click OK to continue loading the project. When the projects have been loaded, you will see at the bottom of the window the status message Parsing included files. This indicates that the environment is scanning the files in the solution and building the browsing database. When the scan has completed you can use the Class View and other tools like the Find All References context menu item. To build the solution, click the Build menu and then click the Build Solution menu item. The results of the build operation will be shown in the Output tool window. If the installation of Visual C++ 2010 Express and the Hilo solution was successful then the solution should build without any errors or warnings. If youre using Visual C++ 2010 Express, however, you will receive a warning about the x64 build because Visual C++ 2010 Express does not support 64 bit projects. You can safely ignore this warning. To test the application, click the Debug menu and then click Start Without Debugging. The Hilo Browser application will start and show the contents of the Windows 7 Pictures library. You can now try out the application, spin the carousel to view the subfolders and touch (or with a mouse, click) on a folder to open it and view the images. Close the Hilo Browser when you have finished exploring its features.
The resource files are items like icon files and bitmaps and the resource compiler script file (.rc file). The Express edition of Visual C++ does not contain the resource editor, so to change the resources that will be bound to the
22
executable file you must edit the resource script manually. To do this, right click on the resource script in the Solution Explorer, and click View Code. The other resources, like bitmaps and icons, must be edited with external tools, to do this double click on the item in the Solution Explorer and Visual C++ 2010 Express will launch the editor registered with Windows to open the file type. On the disk, the Hilo Browser project is a subfolder of the solution folder and headers, source code files, and resource files are stored in this folder. The Browser project folder also has the project file, .vcxproj, a filters file, .vcxproj.filters and a user settings file, .vcxproj.user. Visual C++ 2010 uses the MSBuild build tool. The .vcxproj file lists the targets in the build and the compiler and linker options used by the compilers. If you run the MSBuild utility from the command line in the project directory it will automatically load the .vcxproj file and build the targets specified in it. The .vcxproj.filters file lists all the targets and the folder where they appear in the SolutionExplorer. This file is only used by the Solution Explorer and it is not used as part of the build. Similarly, the .vcxproj.user file contains user interface information for the current user. When you open the solution for the first time in Visual C++ Express the environment will parse the source files and generate the code browser database, the .sdf file, and a folder called ipch which contains the IntelliSense precompiled header files that are used to provide IntelliSense information as the developer types code. This means that if you intend to distribute the solution, the absolute minimum collection of files that you should include are the code and resource files, the .sln file, and the project files .vcxproj and .vcxproj.filters, all the other files will be generated by Visual C++ if they do not exist.
The four main tools used to build a project are in one of the following categories: C/C++ compiler, Linker, Manifest Tool, and Resources for the resource compiler. In all case you will have one or more property pages where you can change individual options, and a page called Command Line where you can type the command line switches for the options you want to use. The build tool values that you set through the project properties will be applied to all source files, if you wish to apply an option for just one file then you should use the property pages of that individual file.
23
In addition to the build tool options there are also property pages for build events and debugging, and a property page called VC++ Directories that allows you to provide additional search folders for the current project for the include folder, library folder and executable folders.
Setting a breakpoint is simple: click on the line where you wish to set a breakpoint and then through the Debug menu click Toggle Breakpoint. A red breakpoint glyph appears in the indicator margin on the left of the line, as shown in Figure 22. You can also toggle a breakpoint by clicking in the indicator margin. Figure 22 Toggling a breakpoint in the code editor
Once you have set breakpoints you can start debugging by clicking the Start Debugging option on the Debug menu. When a breakpoint is reached the debugger will pause at that point, Figure 23, and display a yellow arrow glyph above the breakpoint. Figure 23 Debugging the Hilo Browser
24
In addition several new tool windows will appear. The Autos window shows the values of the variables on the current line of code, the Locals window shows all the variables in the current scope. If the variable is a pointer to an object instance then you can expand the entry to show the values of the objects members. You may also change the value of a variable through one of these variable windows. When the application runs under the debugger the Debug toolbar will show (the toolbar is shown at the top of Figure 23). This toolbar allows you to control the debugging. Using this toolbar you can run the application until the next breakpoint, stop the application or single step. If you choose to single step then you can choose to step into the function at the current execution point, or step over it as a single action, or indeed, you can step out of the current function as a single action.
Conclusion
Now that your development environment is set up and you have compiled and run the Hilo Browser application, were ready to start walking through the code in more detail. In the next article, well discuss the technologies that were used to develop the Hilo Browser application.
25
Visual C++ 2010 also contains standard C++ language features, known as the C++0x features. Some of these features make C++ code a more readable, but others will have a significant effect on the code using them. For example, one C++0x keyword is nullptr [http://msdn.microsoft.com/en-us/library/4ex65770(v=VS.100).aspx] , this value is used to assign a null value to a pointer, and makes your code a bit more readable. However, lambda expressions [http://msdn.microsoft.com/en-us/library/dd293608(v=VS.100).aspx] , that allow you to define anonymous function objects bring a level of functional programming to native C++ and are a powerful feature. Lambda expressions are initially quite alien to untrained eyes, but they can make code less verbose, and therefore more readable.
featured Windows-based application can be a complicated beast. The aim of an application framework is to allow the developers to focus on the functionality of the application and not have to worry about the underlying plumbing code. However, choosing an application framework involves a series of trade-offs. On the one hand a framework can provide you with ready-made implementations which you can use as-is to speed up development significantly. On the other hand, a framework can often constrain what you can do or add complexity by requiring you to extend or modify the framework in order to implement the features or behavior you require, or substantially increase the overall size of your application. Since Hilo was developed using Visual C++ 2010 Express, the use of MFC (and ATL) which are not available with this edition was not an option. Furthermore, it was decided not to use WTL since this library is unsupported. In any case, the ethos behind the Hilo project is lightweight and flexible and so we decided to write a small common library that would be a thin wrapper above the Windows and Direct2d APIs. This provided a base on which we could write all of the Hilo applications. The Hilo Common Library provides numerous features that make writing the Hilo applications easier and faster, while maintaining the performance and size characteristics we require.
Componentization
Componentization means encapsulating state and code as objects. GDI is a non-object API, it has no componentization. GDI+ is object-based, but it is supplied through C functions (although the Windows SDK for Windows 7 provides a C++ class library [http://msdn.microsoft.com/en-us/library/ms533958(v=VS.85).aspx] that componentizes the API). The DirectX APIs are all object based and provided through COM-like interfaces. COM [http://msdn.microsoft.com/en-us/library/ms680573(v=VS.85).aspx] is a platform-independent, objectoriented system for creating binary software components. COM objects can be created with a variety of programming languages and these objects can be within a single process, in other processes, even on remote computers. The COM infrastructure handles the threading, re-entrancy, and inter-process calls and this provides
28
location transparency, that is, the client code is the same regardless of the location of the COM object. Perhaps the most important aspect of COM is interface programming. This is a common technique in many languages and it allows code to concentrate on the behavior of objects rather than the implementation. An interface is a collection of related methods that represents a behavior, and since a COM object can implement more than one interface it can have more than one type of behavior. All access to a COM object is through an interface pointer. Every COM object must implement an interface called IUnknown [http://msdn.microsoft.com/enus/library/ms680509(v=VS.85).aspx] and every COM interface must derive from IUnknown (that is, implement the methods of the IUnknown interface). The IUnknown interface has three methods, two are the IUnknown::AddRef [http://msdn.microsoft.com/en-us/library/ms691379(v=VS.85).aspx] and IUnknown::Release [http://msdn.microsoft.com/en-us/library/ms682317(v=VS.85).aspx] methods and the third is a method called QueryInterface [http://msdn.microsoft.com/en-us/library/ms682521(v=VS.85).aspx] . The IUnknown::AddRef and IUnknown::Release methods are used for reference counting. The rules of COM mandates that when an interface pointer is copied (including the first time it is returned when a COM object is activated) the IUnknown::AddRef method must be called to increment the reference count. The rules also state that when code has finished using a COM interface pointer the code must call the IUnknown::Release method to decrement the reference count. When the reference count reaches zero there are no more users of the interface pointer and typically the IUnknown::Release method deletes the COM object. Reference counting through the IUnknown::AddRef and IUnknown::Release methods allows you to define the lifetime of an object and ensure that the correct cleanup is performed when the object is no longer need. The IUnknown::QueryInterface method is the COM equivalent of the C++ reinterpret_cast<> [http://msdn.microsoft.com/enus/library/e0w9f63b(v=VS.80).aspx] operator. The IUnknown::QueryInterface method takes the COM name (a 128-bit identifier) of the requested interface and if the object implements the interface the method returns a pointer to the interface. Strictly speaking, a COM object must run in a COM apartment [http://msdn.microsoft.com/enus/library/ms693344(v=VS.85).aspx] . Apartments are the mechanism through which COM manages threading and aspects like re-entrancy. Some of the Windows API are provided with COM-like interfaces that derive from the IUnknown interface but do not provide other COM features like activation or run in COM apartments. This is the case for the DirectX objects and the Windows shell objects. Interface programming is a powerful technique and you may want to use it within your own applications. You do not have to use the IUnknown interface to use interface programming, but since the interface is standard and well-known, it is a good place to start. For more details about how to access COM objects with C++ refer to Learn to Program for Windows in C++: Module 2 Using COM in Your Windows Program [http://msdn.microsoft.com/enus/library/ff485848(v=VS.85).aspx] .
than that. Listing 1 Definition of the IUnknown interface using Visual C++ keywords C++ __declspec(uuid(00000000-0000-0000-C000-000000000046)) IUnknown { HRESULT QueryInterface(REFIID riid, void **ppvObject); ULONG AddRef(void); ULONG Release(void); }; Listing 1 shows the definition of the IUnknown interface using the Visual C++ keywords Listing 2 shows the same code using standard C++ keywords, illustrating that an __interface is a struct with pure virtual methods. Listing 2 Definition of the IUnknown interface using standard C++ keywords C++ struct IUnknown { virtual HRESULT QueryInterface(REFIID riid, void **ppvObject) = 0; virtual ULONG AddRef(void) = 0; virtual ULONG Release(void) = 0; };
{ ComPtr<IWindowApplication> mainApp; HRESULT hr = SharedObject<BrowserApplication>::Create(&mainApp); if (SUCCEEDED(hr)) { hr = mainApp->RunMessageLoop(); } return 0; } The ComPtr<> variable is declared on the stack, so when the variable goes out of scope (after the return statement is called) the destructor is called and this calls IUnknown::Release on the interface pointer, which in turn calls the delete operator on a pointer to the BrowserApplication object. This illustrates that the implementation of the IUnknown methods are closely coupled to the implementation of the code that creates the COM object (in this case SharedObject<>::Create) since the code used to delete the object must be appropriate for the code used to create it. The IWindowApplication interface contains a method called RunMessageLoop, and the implementation of this method on the BrowserApplication class uses this to provide a standard Windows message loop. In Listing 3 it appears that the RunMessageLoop method is called on an interface pointer, however, this is not the complete story. Since mainApp is a ComPtr<> object the code in Listing 3 actually calls operator > on the smart pointer class, which in turn calls the method on the encapsulated interface pointer.
Conclusion
This article discussed the rationale for choosing the technologies used to implement the Hilo applications. The next article discusses the process by which the user experience for the Hilo Browser application was designed. The design and implementation of the Hilo Common Library will be covered in the following article.
31
Initial Brainstorming
The purpose of the early sessions was for the team to look at the application through a task-oriented lens from the users perspective. Through these tasks the team could evaluate alternate designs, and then refine the designs that are chosen to make sure that they work across the various tasks and workflows that the user will undertake. The first brainstorming session brought out four generic modules, collections of related features and functionality. These modules were: organize, share, edit and acquire. From the modules, a bubble diagram was drawn to identify the interconnectivity and importance of the necessary tasks. Figure 1 shows the initial bubble diagram. The four modules are connected by arrows showing the workflow and these arrows are annotated with the task name and how many items are affected by the task. Figure 1 Initial Bubble Diagram
32
This was the first iteration of the design process and helped to focus the designers on the main features of the application. By focusing on the users tasks and workflow, the actual application features started to become apparent. These features were not designed in isolation, but were designed in the context of user tasks which can have a subtle but important influence on the overall user experience. The diagram shows that the application should be able to acquire items from different types of devices: cameras, USB thumb drives, or local drives like DVDs or hard disks; the user should also be able to acquire items from the internet. Once the items are obtained the bubble diagram shows that the user will be able to organize these items into collections, attach metadata to the collection, and add collections to the application item library. All items in the library can be shared with other users either through posting on an online photo sharing site or through a locally viewed slideshow. The final module on the bubble diagram shows that the user may edit individual items and add metadata, and then the edited file can either be saved, or shared, or the user can revert the changes. This bubble diagram was just a first iteration, and as you will see the feature set changed as the design process progressed.
Creating Storyboards
The initial bubble diagram gave the overall range of potential features, how they relate to other features, and the workflow of the data. The next step was to use this information to create storyboards. Storyboards are very quick and rough drawings used to mock up key layouts and transitions, and to find potential issues with the design early on. This technique has been used in the film industry and is now becoming more common in user interface design. One of the purposes of drawing everything out on a storyboard is to make sure that all the features and user interface items are represented. As the design goes through its numerous iterations, the features become more tightly integrated into the overall design and new relationships and dependencies are discovered. Figure 2 is a storyboard showing the actions to create a new collection of items. On this storyboard items are first acquired from a source (like a camera or a hard disk) and are displayed in the top half of the window. The user can then drag one or more items using the Touch interface of the computer screen to the collection tray in the lower half of the window. Once a collection has been created it can be organized by sorting or by manually moving items. You can see in this storyboard that the designer has thought of each option at each stage in the story and annotated the storyboard to show these options. Figure 2 Storyboard showing the creation of a new collection of items
33
Another example of a storyboard is Figure 3 which shows the actions used to edit an item. This shows that the user can use the Touch interface to crop items, annotate collections and items, and organize the collections into folders by dragging items around the window. Figure 3 Storyboard showing the actions to edit an item
Creating Mockups
The storyboards helped the design team to bring out details of how the user interacts with the application. The design team decided to produce a Silverlight prototype using Microsoft Expression Blend 3. Expression Blend is a great designers tool since it allows designers to iterate and deliver on designers ideas faster. By using Blend, a designer can demonstrate a vision for an application and bring an application to life, adding interactivity, animation and transitions without writing code. The mockup allowed the team to test out the user interface design in a running environment to gauge the overall experience. The value of using a rapid prototyping environment like Blend to do this is that there is little
34
development cost required to get to something that the user can see and touch. This means that the design team could receive feedback from the usability testers with a short turnaround. The Silverlight mockup of the user interface was created to test the interaction between the various modules in the application. One of the principal goals was to gauge the tactile response of the application how it behaves dynamically which is something that cannot be done in paper sketches. Figure 4 shows screenshots from this prototype. The mockup only included the user interface; there was no code behind the controls. Figure 4 Silverlight prototype
Figure 4 shows that the mockup has a tabbed layout to give access to the main modules and then a RibbonUI control to give access to the features of the selected module. The majority of the window contains the items and collections of items. Again, this mockup is just one iteration and several mockups may be created throughout a design process as new features are developed and tested.
35
This second model showed a much cleaner workflow, however, a few issues arose concerning redundancy of buttons and tools. Discussions over these issues led to the variation of a ribbon-based user interface (UI).
Applying Introspection
At this point the design team took a step back to get a better overall view of the project. The storyboards showed that the design had progressed into quite a complex application and that there were so many features that it would increase time dramatically to get to the application into production. Since this complexity was moving the project away from the primary design goal, that is, to create a less complex application that would excite developers, the design team decided that some simplification was necessary. Using the lessons learned in the previous storyboards and mockups, two brainstorming sessions were scheduled on consecutive days to approve a new design. For the first session the design team decided to focus solely on the browsing experience, that is, collecting items to be edited, uploaded, and so on. Four different browser designs were sketched up for discussion in the first meeting and one design is shown in Figure 6. Figure 6 Revised design for the browser
As a group, the design team did some storyboarding on the whiteboard and worked through some interaction examples. After discussing the pros and cons of each design, it was decided that the hierarchical carousel (right hand side, Figure 6) had the most potential. This concept was discussed and defined further, and many helpful suggestions were provided for the next iteration, such as switching from a stacking carousel into an orbital display model. After this meeting, numerous mockups were created to be shown the following day.
36
During the follow up meeting the design team worked through some more variations of the design, and ultimately decided on a dual orbital design with a results pane below. Items that were selected through this browser would be stored in a collection to be accessed through the other Hilo applications later on. The next step was to storyboard an example of a browsing experience to further refine the design, as shown in Figure 7. Figure 7 Orbital browser
The orbital display represents the file structure as it is viewed in Windows Explorer. Folders in the inner orbit are selected, causing that orbit to expand to take the place of the outer ring, which disappears. The selected folder is rotated to the bottom center of the outer orbit, and any subfolders of the selected folder appear in a new orbit in the center. The bottom pane displays the non-folder contents of the active directory, and updates anytime the current location changes. The storyboard shows the animation that occurs as the user interacts with the interface.
37
The touch and swipe zones highlighted in Figure 9 show that the user can spin the orbits with a finger or mouse gesture to bring another folder to the front, and then select this folder by touching it. In this version of the model in the upper left are icons for the Home folder and for the previously selected folder. If there are more items in the current folder than can be shown in the lower pane the user can use a finger drag gesture to bring more items into view. These mockups led to a discussion with one of the user researchers on the team on how to best maximize the usable space for the aspect ratio and the most commonly used functions. Another storyboard was created to flesh out this revised design and further mockups were created. Figure 10 shows the revised orbital model. The model shows two orbits as before, the orbit for the current folder and the orbit for the current folders subfolder. The refinement in this model is that the current folder is docked on the upper left and although the orbit for the current folder is present, it is only shown at the top. This still gives the impression of the current folder being in an orbit, but by leaving out the redundant lower half of the orbit there is more space for the inner orbit, which also allows for larger icons and more room for the thumbnail pane. Additional prototyping and testing led to the addition of a back arrow for quick navigation to the previous directory. While monitoring actual usage scenarios it was found to be used more often than the history stack. Figure 10 Orbital model refined
38
As the user drills down into the folder structure the bread crumb trail of folders is stacked up and docked at the upper left. By clicking on the stack the user can expand the bread crumb trail as shown in Figure 11, where each folder is shown as being in an orbit. The user can select any folder in the bread crumb trail to navigate to that folder. Figure 11 Expanded bread crumb trail
If a user drills down to a folder without subfolders then the only orbit that can be show is the current folder. Figure 12 shows this situation, and shows that additional screen real estate is freed up and the thumbnail display panel in the lower half of the screen maximizes upwards to fill the available space. Figure 12 Collapsed bread crumb trail with no subfolder orbit present
39
At this point the bulk of the design work had been completed.
The iterative design process of brainstorming, storyboards, and mockups allowed the design team to test various scenarios and to optimize the user experience through efficient use of screen real estate and utilizing the natural
40
Conclusion
This article discussed the process by which the user experience for the Hilo Browser application was designed. The user interface for the Hilo Browser evolved through an iterative process that involved brainstorming, storyboarding, mockups, usability studies, and incremental refinement. The next article discusses the design and implementation of the Hilo Common Library, which is used as the foundation for the development of the Hilo applications.
41
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 [http://msdn.microsoft.com/en-us/library/ms680509(VS.85).aspx] 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 [http://msdn.microsoft.com/en-us/library/ms682317(v=VS.85).aspx] 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 [http://msdn.microsoft.com/enus/library/ms682521(v=VS.85).aspx] . 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.
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 [http://msdn.microsoft.com/en-us/library/ms632641(VS.85).aspx] 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 C++ 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.
43
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 [http://msdn.microsoft.com/en-us/library/dd145213(VS.85).aspx] 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 [http://msdn.microsoft.com/enus/library/dd183362(v=VS.85).aspx] to prepare the window for painting and EndPaint [http://msdn.microsoft.com/en-us/library/dd162598(v=VS.85).aspx] 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
The WindowApplication class implements the message loop and gives access to two objects: the main
44
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 [http://msdn.microsoft.com/en-us/library/ms632679(VS.85).aspx] . The message handling for the main application is very basic: in effect it merely ensures that a WM_DESTROY [http://msdn.microsoft.com/enus/library/ms632620(VS.85).aspx] 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 [http://msdn.microsoft.com/en-us/library/dd371191(v=VS.85).aspx] 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 applications main window. Figure 3 UML for the WindowFactory class
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
45
C++ 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 [http://msdn.microsoft.com/en-us/library/ms632635(VS.85).aspx] 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 [http://msdn.microsoft.com/en-us/library/ms644898(VS.85).aspx] . 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 [http://msdn.microsoft.com/enus/library/ms633585(v=VS.85).aspx] 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
46
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 [http://msdn.microsoft.com/en-us/library/ms644945(VS.85).aspx] Windows API function that posts the WM_QUIT [http://msdn.microsoft.com/en-us/library/ms632641(VS.85).aspx] 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.
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).
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.
49
Using Direct2D
Direct2D [http://msdn.microsoft.com/en-us/library/dd370990(v=VS.85).aspx] is an object orientated library built using the Direct3D Application Programming Interface (API) that gives access to the drawing features of Graphics Processing Units (GPU) in modern graphics cards. The headers and libraries needed for C++ development for Direct2D are supplied as part of the Windows Software Development Kit (SDK) for Windows 7. The two main headers are d2d1.h and d2d1helper.h. The d2d1.h header file defines the main structures, enumerations, and interfaces in the library. The Direct2D objects are created through named methods on interfaces of other Direct2D objects, usually the D2D1 factory object created by a call to a global function called D2D1CreateFactory [http://msdn.microsoft.com/en-us/library/dd368034(v=VS.85).aspx] . The d2d1helper.h header file defines a C++ namespace called D2D1 [http://msdn.microsoft.com/enus/library/dd368134(v=VS.85).aspx] that contains helper classes and functions to make using Direct2D easier. For example, there is a class called D2D1::ColorF [http://msdn.microsoft.com/enus/library/dd370907(v=VS.85).aspx] that provides access to named color values and D2D1::Matrix3x2F [http://msdn.microsoft.com/en-us/library/dd372275(v=VS.85).aspx] that encapsulates the operations that can be carried out on a D2D1_MATRIX_3X2_F [http://msdn.microsoft.com/en-us/library/dd368132(VS.85).aspx] structure. Hilo also uses DirectWrite through the dwrite.h header file. Similar to Direct2D, DirectWrite objects are created through a factory object which itself is created through a global method called DWriteCreateFactory [http://msdn.microsoft.com/en-us/library/dd368040(v=VS.85).aspx] .
Direct2D and DirectWrite objects that are device dependent. Device dependent objects are created by the graphics card GPU and include the render target, brushes and bitmap objects. The render target object is where the drawing is carried out, the brushes and bitmaps are used to do the drawing. The lifetime of these GPU objects, and hence the wrapper Direct2D objects, is determined by the GPU. For performance reasons these objects should live as long as possible, but the GPU may indicate that these objects cannot be reused and must be recreated. Listing 1 Hilo pattern for drawing windows C++ // Create render target and other resources if they have not been created already HRESULT hr = CreateDeviceResources(); hr = m_renderTarget->BeginDraw(); // Do drawing here hr = m_renderTarget->EndDraw(); // If the GPU indicates the resources need re-creating, destroy the existing ones if (hr == D2DERR_RECREATE_TARGET) { DiscardDeviceResources(); } Listing 1 shows the code pattern used in Hilo. The CreateDeviceResources method creates the resources if they are not already created. All drawing using Direct2D resources must be performed between calls to the ID2D1RenderTarget::BeginDraw [http://msdn.microsoft.com/en-us/library/dd371768(v=VS.85).aspx] and ID2D1RenderTarget::EndDraw [http://msdn.microsoft.com/en-us/library/dd371924(v=VS.85).aspx] methods. The EndDraw method returns a value indicating whether the Direct2D objects can be reused. If this method indicates that the GPU requires that the device dependent objects should be destroyed, then DiscardDeviceResources is called. In this case, they will be recreated by the call to the CreateDeviceResources method when the code in Listing 1 is called to draw the window.
51
You can get the size of the window in DIPs by calling the ID2D1RenderTarget::GetSize [http://msdn.microsoft.com/en-us/library/dd316823(v=VS.85).aspx] method. The first time you call this method it will return the size calculated from the size of the windows client area when the render target was first created. If the window changes size then Direct2D will scale its drawing to the new size of the window. This can have undesirable effects because items like text will be stretched according to how the size of the window changes. To get around this problem you can call the ID2D1HwndRenderTarget::Resize [http://msdn.microsoft.com/enus/library/dd742774(v=VS.85).aspx] method and pass the size of the window in device pixel units. Many of the items in Hilo are animated, and so their positions, size, even the opacity of the item changes with time. The change of these positions is calculated by the Windows Animation Manager and this Windows 7 component will be covered in detail in the next chapter. For now we will just say that the Hilo classes covering animation (for example, CarouselThumbnailAnimation is provided to animate the position of a folder on the carousel) have methods to allow the position of the animated items to be returned. For more about Windows 7 coordinate system, see Learn to Program for Windows in C++ [http://msdn.microsoft.com/en-us/library/ff684173(v=VS.85).aspx] , in the MSDN Library.
if (currentPage > 0) { // Show the left arrow m_renderTarget->DrawBitmap( m_arrowBitmap, leftArrowRectangle, m_leftArrowSelected || m_leftArrowClicked ? 1.0f : 0.5f); } if (maxPages > 0 && currentPage < maxPages - 1) { // Show the right arrow by rotating the bitmap 180 deg m_renderTarget->SetTransform( D2D1::Matrix3x2F::Rotation(180.0f, D2D1::Point2F( rightArrowRectangle.left + (rightArrowRectangle.right - rightArrowRectangle.left) / 2.0f, rightArrowRectangle.top + (rightArrowRectangle.bottom - rightArrowRectangle.top) / 2.0f))); m_renderTarget->DrawBitmap( m_arrowBitmap, rightArrowRectangle, m_rightArrowSelected || m_rightArrowClicked ? 1.0f : 0.25f); }
{ hr = m_renderTarget->CreateLinearGradientBrush( LinearGradientBrushProperties( Point2F(m_renderTarget->GetSize().width, 0), Point2F(m_renderTarget->GetSize().width, m_renderTarget->GetSize().height)), gradientStopCollection, &m_backgroundLinearGradientBrush ); } gradientStopCollection = nullptr; Listing 5 defines two gradient stop structures and each structure gives details of the color and the position that this color extends along the gradient axis. The important details in Listing 5 are that the gradient starts with a light purple (BackgroundColor) which merges with the second color, white. The gradient stop for white is at 25 percent along the gradient axis so this means that the color is solid light purple at the top, and then fades to fully white at the 25 percent position, from the 25 percent position to the 100 percent position gradient is completely white. Figure 2 shows how gradient stops are related to the changes in color. The gradient axis will be defined in a moment; the gradient stops just determine how color changes along the axis. Figure 2 Illustrating the features of linear gradient brushes
You can have any number of stops and Direct2D will attempt to merge the colors you provide. To do this you create a stop collection object passing the information about the color stops to the ID2D1RenderTarget::CreateGradientStopCollection [http://msdn.microsoft.com/enus/library/dd368113(v=VS.85).aspx] method. Finally, the ID2D1RenderTarget::CreateLinearGradientBrush [http://msdn.microsoft.com/en-us/library/dd371845(v=VS.85).aspx] method is called to create the brush. This method requires information about the gradient axis. The gradient axis specifies in what direction the color changes. Hilo uses the helper method from the D2D1 namespace to initialize a D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES [http://msdn.microsoft.com/enus/library/dd368128(VS.85).aspx] structure. This structure simply has two points, one describing the start and the other describing the end of the gradient axis. The most obvious feature of the gradient axis is the angle of the axis. Listing 5 shows that the axis is a vertical line (the x position of the start point is the same as the end point), the code defines this line on the right side of the carousel window, but the same effect is achieved if the line had any x position. Figure 2 shows the gradient axis compared to the gradient stops. The gradient stops define the percentage change over the length of the gradient axis, but the CreateLinearGradientBrush method gives the actual length of the axis. However, the gradient axis has some subtle features. The CreateLinearGradientBrush method simply creates the brush, it does not do any drawing. Listing 6 shows the code from CarouselPaneMessageHandler::DrawClientArea that uses the gradient brush. This code shows that the brush is used to paint an area that is exactly the same height as the gradient axis. The ID2D1LinearGradientBrush [http://msdn.microsoft.com/en-us/library/dd371488(v=VS.85).aspx] interface has methods to get and set the end
55
points of the gradient axis, so you can change these points if the area is a different size at the time it is painted than when the brush is created. In the Hilo Browser code this situation will never occur and hence the brush is used with the same property values that it was created. Listing 6 Using a linear gradient brush C++ D2D1_SIZE_F size = m_renderTarget->GetSize(); m_renderTarget->BeginDraw(); m_renderTarget->SetTransform(Matrix3x2F::Identity()); m_renderTarget->FillRectangle(RectF( 0, 0, size.width, size.height), m_backgroundLinearGradientBrush); // Other code // End Direct2D rendering hr = m_renderTarget->EndDraw();
Drawing Text
Direct2D allows you to draw text on the render target through the ID2D1RenderTarget::DrawText [http://msdn.microsoft.com/en-us/library/dd742848(v=VS.85).aspx] or DrawTextLayout [http://msdn.microsoft.com/en-us/library/dd371913(v=VS.85).aspx] methods. Before you can draw any text you must first create an object that holds information about the font to use and information like line spacing and text alignment. These objects are called text format objects and to create one you need to use DirectWrite. DirectWrite is similar to Direct2D in that the objects have COM-like interfaces but are created through a factory object created by a global function. In Hilo the DirectWrite factory object is created through the Direct2DUtility::GetDWriteFactory static method. Once you have an IDWriteFactory object you can create a text format object by calling the IDWriteFactory::CreateTextFormat [http://msdn.microsoft.com/enus/library/dd368203(v=VS.85).aspx] method. Listing 7 shows the code used by the carousel panel in the Hilo Browser project to create the text format object. The parameters are self-explanatory, but it is worth pointing out that the size is in DIPs not points. Listing 7 Creating a Text Format object C++ // member variables: // ComPtr<IDWriteFactory> m_dWriteFactory; // ComPtr<IDWriteTextFormat> m_textFormat; hr = m_dWriteFactory->CreateTextFormat( L"Arial", nullptr, DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 12, L"en-us", &m_textFormat ); Once you have a text format object you can draw text to the render target with a call to DrawText. The simplest way to call this method is to pass five parameters: the string and its length, the text format object, a rectangle where to draw the text ,and a brush. The DrawTextLayout method is similar, but instead it is passed a text layout object that encapsulated the string, the text format object and the size of the area where to draw the text. Listing 8 shows the code from the CarouselThumbnail::CreateTextLayout method that creates a text layout object. Listing 8 Creating a Text Layout object C++
56
HRESULT hr = S_OK; // Set the text alignment m_renderingParameters.textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER); if (nullptr == m_textLayout) { hr = m_dWriteFactory->CreateTextLayout( m_thumbnailInfo.title.c_str(), static_cast<unsigned int>(m_thumbnailInfo.title.length()), m_renderingParameters.textFormat, m_rect.right - m_rect.left, 16, &m_textLayout ); } In this code the m_renderingParameters member variable is set by the carousel object with a reference to the rendering target, the text format object and the brush used to draw the text. The actual text for the object is drawn in the CarouselThumbnail::Draw method, shown in Listing 9. The first parameter is the location to draw the text (since the format object is set to center align text, Listing 8, this point is the center of the text). The second parameter is the text layout object which contains details of the text and the font to use, the third parameter is the brush and finally there is a parameter for options. Listing 9 Drawing text C++ m_renderingParameters.renderTarget->DrawTextLayout( D2D1::Point2F(m_rect.left, m_rect.bottom), m_textLayout, m_renderingParameters.solidBrush, D2D1_DRAW_TEXT_OPTIONS_CLIP);
Conclusion
In this chapter you have learned how to used Direct2D to draw in a window. You learned how to initialize the Direct2D environment, create resources and draw items and text. In the next chapter you will learn how to use the Windows Animation Manager to control the positions of the items that you draw in a window.
57
In the terminology of the Windows Animation Manager, these changes are called transitions, and they change a floating point value called a variable. The animation manager does not change pixels on the screen, it just changes the variable, and you have to provide the drawing code that uses the variable. A transition determines how a variable changes over a period of time (called the duration) and the variable will start the transition with an initial value. Transitions can be chained, that is, one transition describing the change of a variable is followed by another transition describing a different change to the same variable. At the hand over point between the transitions the variable will be changing in a particular way according to the first transition (called the velocity), and since some transitions depend on the velocity this value is made available to the next transition. Figure 2 shows a smooth stop transition and illustrates the initial and final values and the duration of the transition. Other transition types are supported by the Windows Animation Managerfor details see the Storyboard Overview [http://msdn.microsoft.com/en-us/library/dd756779(VS.85).aspx] in the Windows Animation Development Guide available on MSDN. The arrows indicate the initial velocity of the transition. Over the duration of the smooth stop transition the variable will decrease from the initial value to the final value. Figure 2 Illustrating the properties of a smooth stop transition
58
The animation manager will ensure that the variables value changes over the duration in the manner specified, and the application obtains the value of the variable during this time to render each frame. The variable clearly depends on the time since transition started, so the animation manager must have an accurate value of the time the transition starts, and the time when each frame is drawn. Windows provides a high precision timer object that can be used by the animation manager. In addition, Windows also defines several stock transitions through an object called the transition library. Animation involves more than just changing pixels on a screen. Careful attention must be made to prevent flicker through synchronization with the monitor refresh. Direct2D does this for you, and so the combination of the Windows Animation Manager and Direct2D are the best way to provide animation in a Windows 7-based application.
nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&g_transitionLibrary) ); } The animation manager must be updated periodically with the time and this is the reason for the animation timer object. The transition library is a factory object that is used to provide transition objects for most of the common transitions used in animation.
variable that it will change. Adding the transition to the storyboard means that the storyboard holds a reference to the transition object and so once the code has finished initializing the transition it releases its reference. The lifetime of the transition is now controlled by the storyboard object and the lifetime of the storyboard object is controlled by the animation manager. Listing 3 Initializing the Animation Manager with a storyboard C++ IUIAnimationStoryboard* storyboard; IUIAnimationTransition* transition; HRESULT hr = g_animationManager->CreateStoryboard(&storyboard); if (SUCCEEDED(hr)) { hr = g_transitionLibrary->CreateAccelerateDecelerateTransition( duration, endValue, accRatio, decRatio, &transition); if (SUCCEEDED(hr)) { hr = storyboard->AddTransition(m_variable, transition); } // Schedule the storyboard (see Listing 5) } transition->Release(); storyboard->Release(); The IUIAnimationTransitionLibrary::CreateAccelerateDecelerateTransition [http://msdn.microsoft.com/enus/library/dd371900(v=VS.85).aspx] method creates a transition that lasts for a fixed time, the duration parameter. The transition library also allows you to create transitions whose duration depends on values determined at runtime. For example, Listing 4 shows code that uses the IUIAnimationTransitionLibrary::CreateLinearTransitionFromSpeed [http://msdn.microsoft.com/enus/library/dd371925(v=VS.85).aspx] method. This method is initialized with just the initial rate of change of the variable (speed) and the expected final value. The duration of the transition will depend on the initial value of the variable immediately before the transition starts. Listing 4 Creating a transition without a known duration C++ hr = g_animationManager->CreateAnimationVariable(initialValue, &m_variable); if (SUCCEEDED(hr)) { hr = g_transitionLibrary->CreateLinearTransitionFromSpeed( speed, endValue, &transition); if (SUCCEEDED(hr)) { hr = storyboard->AddTransition(m_variable, transition); } }
61
The application can use the IUIAnimationTransition [http://msdn.microsoft.com/enus/library/dd371887(v=VS.85).aspx] interface to set the initial value of the variable and the initial velocity (rate of change) of the variable.
Chaining Transitions
A storyboard can contain more than one variable and a variable can be added to the storyboard associated with different transitions. Transitions added to a storyboard and associated with the same variable are run serially. That is, the first transition added to the storyboard is run for its duration and then the second transition is run. This is illustrated in Figure 3 where a variable 1 is added twice to the storyboard which means that the two transitions are performed in the order that they are added to the storyboard. Figure 3 Chaining two transitions on the same variable
If a storyboard is initialized with transitions that are associated with different variables then when the animation starts the two variables will be updated in parallel. This is illustrated by Figure 4 where three transitions are added to the storyboard and these are associated with two1X and Y. This means that when the animation starts the first transition associated with X and the first transition associated with Y are started at the same time and so the variables X and Y are changed in parallel. When you add a transition for another variable by using the IUIAnimationStoryboard::AddTransition [http://msdn.microsoft.com/en-us/library/dd371789(v=VS.85).aspx] method the two transitions will be scheduled to start at the same time. You can specify that a transition starts at a later time by creating a key frame. A key frame is essentially a defined point in time after the storyboard has started. You can create a key frame by calling the IUIAnimationStoryboard::AddKeyframeAtOffset [http://msdn.microsoft.com/enus/library/dd371784(v=VS.85).aspx] method. Once you have created a key frame you can add a transition at this point by calling the IUIAnimationStoryboard::AddTransitionAtKeyFrame [http://msdn.microsoft.com/enus/library/dd371795(v=VS.85).aspx] method. Figure 4 Illustrating two variables added to the storyboard
Scheduling a Storyboard
The code in Listing 3 creates and initializes the storyboard, to start the animation you have to schedule it, as shown in Listing 5.
62
Listing 5 Scheduling the storyboard C++ if (SUCCEEDED(hr)) { UI_ANIMATION_SECONDS secondsNow = static_cast<UI_ANIMATION_SECONDS>(0); if (SUCCEEDED(hr)) { hr = g_animationTimer->GetTime(&secondsNow); } if (SUCCEEDED(hr)) { hr = storyboard->Schedule(secondsNow); } } The IUIAnimationStoryBoard::Schedule [http://msdn.microsoft.com/en-us/library/dd371820(v=VS.85).aspx] method schedules the storyboard to run. If there is no other storyboard with the same variables already scheduled to run the storyboard will start immediately. Only one storyboard with the same collection of variables can run at any time, but a new storyboard can be initialized to cancel, trim, conclude, or compress conflicting storyboards.
Listing 8 Determining whether the animation has finished C++ UI_ANIMATION_MANAGER_STATUS status; hr = g_animationManager->GetStatus(&status); if (SUCCEEDED(hr)) { if (status == UI_ANIMATION_MANAGER_BUSY) { InvalidateRect(hWnd, NULL, FALSE); } } There are two possible status codes, UI_ANIMATION_MANAGER_IDLE indicates that all storyboards have completed and so the rendering code need not be called again. UI_ANIMATION_MANAGER_BUSY indicates that there is at least one storyboard running or scheduled to run, and hence the code in Listing 8 invalidates the window to ensure that the rendering code is called again to display the next frame of the animation.
Class CarouselAnimation
Description Rotates the folder thumbnails on the inner orbital. Called when you spin the carousel. In the animation, the folder thumbnails change size, opacity, and angular position on the orbital. Moves a folder thumbnail to the history stack. Called when you select a folder. In the animation, the folder thumbnails change their x and y position and opacity. Fills the media pane with images. Called when a folder is first opened, so old images fly out and new images fly in. In the animation, images move along a predefined path. Moves images in the media pane when you resize the Browser window. Images move to fill in free space as the window increases. In the animation the x and y position changes. Animates the size and position of the inner orbital. This Called when you select a folder and the new inner orbital comes into view, and when the history stack is expanded. In the animation, the position, the size and the opacity of the ellipse changes. Fills the media pane with images. Called when you move to another page so new images slide into view. In the animation the x position of the images changes.
CarouselThumbnailAnimation
FlyerAnimation
MoverAnimation
OrbitAnimation
SlideAnimation
The classes have a similar form, with a two stage construction pattern where there is an Initialize method to create the animation variables that will live the lifetime of the instance of the class. These classes have a method to create and schedule the storyboard (typically called Setup), and in the process create transitions and associate
64
them with the animation variables. Finally, they all have one or more methods to obtain the values of the animation variables during the animation. Instances of these classes are created by a separate class, AnimationFactoryImpl, which contains methods that wrap calls to SharedObject<>::Create to create instances of the appropriate class on the C++ heap. The Hilo Browser animates three types of objects: the carousel (the folder thumbnails and the inner orbital), the folder thumbnails on the history stack, and the photo thumbnails in the media pane.
instance of the CarouselAnimation class provides two types of animation for the thumbnails on the inner orbital. The m_innerOrbitalAnimation object, an instance of the OrbitalAnimation class, provides animation of the ellipse shown for the inner orbital. Instances of the CarouselThumbnailAnimation and OrbitalAnimation classes are created for each folder on the history stack and provide animation for expanding and contracting the stack. The CarouselThumbnailAnimation class provides thumbnail animation and the OrbitalAnimation class provides the animation of the orbitals. Figure 5 shows the members of the CarouselPaneMessageHandler class. Figure 5 Showing the animation members of the CarouselPaneMessageHandler class
The first type of animation provided by the CarouselAnimation class is when the items in the inner orbital rotate. When you drag one of the folders in the inner orbital the carousel will rotate in the direction you drag the item, but the rotation will continue after you lift your finger. This rotation continues but slows down until the carousel stops spinning. The transition used here is an accelerate-decelerate transition where the entire transition is a deceleration. The pertinent code from CarouselAnimation::SetupRotation is shown in Listing 10. The important point is that the third parameter is 0 and the fourth is 1 which means that there is no acceleration. This is a deceleration from the current value to the value represented by the rotation parameter, and this occurs over the period of time given by the duration parameter. The rotation value indicates by how much the carousel will rotate, and since this is an angle, the animation variable attached to the transition object will decrease if the rotation is clockwise and increase if the rotation is counter-clockwise. The deceleration refers to the rate of change to get to this final value. When the carousel is drawn the CarouselPaneMessageHandler::DrawOrbitalItems method is called and this obtains the rotation value from the m_carouselAnimation object and draws the currently selected folder at the specified position on the inner orbital. The DrawOrbitalItems method then draws the other items at equal angular positions around the orbital. Listing 10 Initializing the carousel rotation C++ hr = m_transitionLibrary->CreateAccelerateDecelerateTransition( duration, rotation, 0.0, 1.0, &transition); The other animation that the CarouselAnimation class provides occurs when the user expands the history stack. In this situation the inner orbital shrinks, moves to the right side and fades; during this animation the folder thumbnails on this orbital shrink to half size and their opacity reduces to 60 percent. When the expanded history list is restacked the inner orbital grows back to full size, moves to the center of the pane, and opacity changes back to 100 percent; during this animation the thumbnails grow back to full size and their opacity changes back to 100 percent. The CarouselAnimation class provides two transitions for the size and opacity of the thumbnails, both transitions are linear which means that the size or opacity variable changes at a constant rate. Listing 11 shows the code that does this in the CarouselAnimation::SetupScale method and a similar transition is created in the CarouselAnimation::SetupOpacity method. Both the SetupScale and SetupOpacity methods create a storyboard, add the transition to the storyboard, and schedule it. However, the code in CarouselPaneMessageHandler always calls these methods as a pairthat means that the two animations will always be performed at the same time: the inner orbital will shrink and get less opaque; or the orbital will grow and get more opaque. Listing 11 Initializing the carousel scaling C++
66
hr = m_transitionLibrary->CreateLinearTransition( duration, thumbnailScale, &transition); The carousel pane object has a member m_innerOrbitalAnimation which is an instance of the OrbitalAnimation class. As the name suggests this object provides the size and position of the inner orbital. When a user selects a folder on the carousel the folder is added to the history stack and if the folder has items, the inner orbital appears to grow from the centre to give the impression of zooming into the folder. This animation is provided by the m_innerOrbitalAnimation object which animates the size and opacity of the orbital. The changes are linear, so the OrbitalAnimation class creates linear transition objects.
67
The MediaPaneAnimation base class has two abstract methods called CreateAnimatedThumbnailCells and BuildStoryboard that are called in the Setup method to create the storyboard specific to the derived class animation. Figure 6 has another abstract class called LineAnimation which provides a base implementation to get the position of the animated photo thumbnails. The derived class implementations of the BuildStoryboard method create the storyboard, transitions, and animation variable objects. An instance of each class animates the positions of all the items shown in the media pane, so each such instance has a collection that contains information about each photo, including the animation variables for that particular photo. The SlideAnimation is the simplest of the three animations. This is used when the user views another page of photos and the animation involves the new page of photos sliding into view from either the left or the right. For each photo that is animated there are two animation variables, one each for the x and y coordinates. Since the slide is horizontal, the y coordinate does not change, so the IUIAnimationTransitionLibrary::CreateConstantTransition [http://msdn.microsoft.com/enus/library/dd371903(VS.85).aspx] method is called to create a constant transition. The x coordinate animation variable is associated with an acceleration-deceleration transition where half of the transition is acceleration and the other half a deceleration. The MoverAnimation class provides the animation that rearranges photos in the media pane when the window is resized. The photo thumbnails are rearranged so that the space available is filled with photos and this may mean adding extra rows. The MoverAnimation class calculates how the photo will move and creates a variable for the x and y coordinate for each photo. The AddTransition private method creates a parabolic transition on one coordinate so that the value decelerates to zero (by calling the IUIAnimationTransitionLibrary::CreateParabolicTransitionFromAcceleration
68
[http://msdn.microsoft.com/en-us/library/dd371934(VS.85).aspx] method), and it creates a accelerate-decelerate transition on the other coordinate where the first 30 percent is an acceleration and the last 30 percent is a deceleration. The final animation is provided by the FlyerAnimation class. This is called when you open a folder, the new photos fly in from the left and any photos in the media pane fly out to the right. Each thumbnail has a Direct2D graphics path defined for it and an animation variable that determines at which point on the path the photo is currently. The graphics path is an object with an ID2D1Geometry [http://msdn.microsoft.com/enus/library/dd316578(v=VS.85).aspx] interface and is an arc. The animation variable determines the position along this arc and changes according to a parabolic transition. This position is converted to actual x-y coordinates y calling the ID2D1Geometry::ComputerPointAtLength [http://msdn.microsoft.com/enus/library/dd316578(v=VS.85).aspx] method.
Conclusion
This article shows how you can create transition objects, which define how a variable changes over time, and how to use a storyboard object to specify the relationship between transitions. It also shows how you can use the animation manager object to obtain the value of the animation objects so that you can draw the animation. Because animation involves redrawing the screen, often at a fast rate, to prevent flicker you should use a graphics API that synchronizes drawing with the monitor refresh, like Direct2D. The Windows Animation Manager and the Direct2D API are the perfect combination to provide animation on Windows 7. The next chapter will cover how Hilo uses the Windows Library API to get access to photo files on the computer.
69
70
When you select a library, Windows Explorer displays an aggregate view of the files and folders that are part of the library, as shown in Figure 2. Figure 2 Showing the contents of the Documents library
Libraries are logical representations of user content. This means that you store files in the folders that are part of the library and not in the library itself. So for example, the Documents library is the default location for documents and contains the users documents in their My Documents folder and any documents in the Public Documents folder. Although Windows Explorer displays the Documents library as if it is a folder, no physical folder exists, so if a user saves a file to the Documents library then the file will actually be saved to the default save location (in Figure 1 this save location is set to My Documents). The Windows 7 Application Programming Interface (API) provides COM-based objects used to access the contents of the libraries. You can traverse through the logical hierarchy through these shell item objects without knowing the absolute storage location paths (although it is possible to obtain the system file path). All items in the shell are represented by an object with the IShellItem [http://msdn.microsoft.com/enus/library/bb761144(v=VS.85).aspx] interface, but specific shell items will implement other interfaces (for example, folder objects also implement the IShellFolder [http://msdn.microsoft.com/enus/library/bb775075(v=VS.85).aspx] interface). It is very important that Windows 7 applications use the Shell API
71
to access shell items rather than using absolute file system file paths. Equally important is that applications use the Windows 7 common file dialogs because these dialogs show the systems libraries and provide appropriate IShellItem objects selected through the dialog.
{ DWORD dwAttr = 0; hr = pItem->GetAttributes(SFGAO_FILESYSANCESTOR, &dwAttr); if (SUCCEEDED(hr)) { if (SFGAO_FILESYSANCESTOR == dwAttr) { wprintf(L"Item is a file system folder\n"); } } pItem->Release(); } You can also use the SHGetKnownFolderItem [http://msdn.microsoft.com/en-us/library/dd378429(VS.85).aspx] function to get a shell item for a known folder. This function also allows you to pass an access token so that you can access known folders restricted to users other than the current logged on user.
hr = pItem->BindToHandler(nullptr, BHID_EnumItems, IID_PPV_ARGS(&pEnum)); if (SUCCEEDED(hr)) { IShellItem* pChildItem = nullptr; ULONG ulFetched = 0; do { hr = pEnum->Next(1, & pChildItem, &ulFetched); if (FAILED(hr)) break; if (ulFetched != 0) { LPWSTR szChildName = nullptr; child->GetDisplayName(SIGDN_NORMALDISPLAY, &szChildName); wprintf(L"Obtained %s\n", szChildName); CoTaskMemFree(szChildName); pChildItem ->Release(); } } while (hr != S_FALSE); pEnum->Release(); } If the shell item is a file, it is up to you as the developer to determine how to load the data from the file. If you wish to use the Windows API functions like ReadFile [http://msdn.microsoft.com/enus/library/aa365467(VS.85).aspx] to read from the file then you must obtain a handle to the file. To do this you can pass the name returned from IShellItem::GetDisplayName for the SIGDN_FILESYSPATH name type to the CreateFile [http://msdn.microsoft.com/en-us/library/aa363858(v=VS.85).aspx] function. You can also open the shell item as a stream object by requesting the BHID_Stream handler in a call to the IShellItem::BindToHandler method.
if (SUCCEEDED(hr)) { CopyFileTo(pInitialItem, pSaveAsItem); // Call the code to Copy copy the actual file pSaveAsItem->Release(); } } else { // If the user clicks cancel the return value is 0x800704c7, that is // HRESULT_CODE(hr) == ERROR_CANCELLED } pShellDialog->Release(); } The Save As dialog shown in Listing 5 will also show the Libraries folder in the navigation pane. If the user selects a library then the shell item returned from the IFileSaveDialog::GetResult method will reference the actual file system location, if necessary using the default save location for the library.
// If the Pictures Library was not not found if (FAILED(hr)) { // Try obtaining the "Computer" known folder hr = ::SHGetKnownFolderItem( FOLDERID_ComputerFolder, static_cast<KNOWN_FOLDER_FLAG>(0), nullptr, IID_PPV_ARGS(&m_currentBrowseLocationItem)); } if (SUCCEEDED(hr)) { ComPtr<IPane> carouselPane; hr = carouselPaneHandler.QueryInterface(&carouselPane); if (SUCCEEDED(hr)) { hr = carouselPane->SetCurrentLocation(m_currentBrowseLocationItem, false); } } The message handler class for the carousel pane is updated with the current location by passing the shell item to the SetCurrentLocation method. This method enumerates the folders in the selected folder and updates the carousel to show these subfolders. The carousel pane handler class then calls the SetCurrentLocation method on the media pane which enumerates the image files in the selected folder and uses this to populate the thumbnails in the media pane. Whenever the user selects a folder in the carousel or in the history list the shell item for the newly selected item is passed to SetCurrentLocation of the carousel handler and hence the carousel and media pane are updated with the items in the selected folder.
Enumerating Folders
Hilo provides a utility class (in the Common project) called ShellItemsLoader. This class has one public static method called EnumerateFolderItems that is passed the shell item object of the folder to enumerate, a value indicating if the method should return the folders or image file items in the folder, and a parameter indicating whether the search is recursive or not. This method returns a vector of the shell item objects for the requested items. Listing 7 shows the first part of this method, 1 indicates the type of objects to search for and is used to add named values to the itemKinds vector. When the method enumerates items in the folder it obtains the type of each item, and if the type of the item is one of those in the itemKinds vector the item is added to the shellItems vector returned to the caller. Listing 7 Enumerating folders: initialization code C++ HRESULT ShellItemsLoader::EnumerateFolderItemsNonRecursive( IShellItem* currentBrowseLocation, ShellFileType fileType, std::vector<ComPtr<IShellItem> >& shellItems) { std::vector<std::wstring> itemKinds; if ((fileType & FileTypeImage) == FileTypeImage) { itemKinds.push_back(L"picture"); } if ((fileType & FileTypeImage) == FileTypeVideo) { itemKinds.push_back(L"video"); }
76
if ((fileType & FileTypeImage) == FileTypeAudio) { itemKinds.push_back(L"music"); } // Followed by code to do the enumeration The folder is enumerated by using a IShellFolder [http://msdn.microsoft.com/enus/library/bb775075(v=VS.85).aspx] handler object and Listing 8 shows the code that obtains this object by calling the BindToHandler method. Listing 8 Enumerating folders: creating the handler object C++ // Enumerate all objects in the current search folder ComPtr<IShellFolder> searchFolder; HRESULT hr = currentBrowseLocation->BindToHandler( nullptr, BHID_SFObject, IID_PPV_ARGS(&searchFolder)); if (SUCCEEDED(hr)) { // Enumeration code, see Listing 9 } The IShellFolder object has an enumeration object that implements the IEnumIDList [http://msdn.microsoft.com/en-us/library/bb761982(v=VS.85).aspx] interface, this interface enumerates item IDs rather than shell items, but it is possible to create a shell item from an ID by calling the SHCreateItemWithParent [http://msdn.microsoft.com/en-us/library/bb762137(v=VS.85).aspx] method. Listing 9 shows the main code that enumerates items in the specified folder. First the code initializes a SHCONTF [http://msdn.microsoft.com/en-us/library/bb762539(VS.85).aspx] flag to indicate whether the searched items should be folders or files. This flag is passed to the IShellFolder::EnumObjects [http://msdn.microsoft.com/enus/library/bb775066(v=VS.85).aspx] method that returns an enumeration object with the items. The code then repeatedly calls IEnumIDList::Next [http://msdn.microsoft.com/en-us/library/bb761983(v=VS.85).aspx] on this object to obtain the items ID and calls the SHCreateItemWithParent method to create the shell item object. Listing 9 Enumerating folders: enumerating items C++ bool const isEnumFolders = (fileType & FileTypeFolder) == FileTypeFolder; SHCONTF const flags = isEnumFolders ? SHCONTF_FOLDERS : SHCONTF_NONFOLDERS; ComPtr<IEnumIDList> fileList; if (S_OK == searchFolder->EnumObjects(nullptr, flags, &fileList)) { ITEMID_CHILD* idList = nullptr; unsigned long fetched; while (S_OK == fileList->Next(1, &idList, &fetched)) { ComPtr<IShellItem2> shellItem; hr = SHCreateItemWithParent(nullptr, searchFolder, idList, IID_PPV_ARGS(&shellItem)); if (SUCCEEDED(hr)) { // Check to see if the item should be added to the returned item vector // See Listing 10 } ILFree(idList); } }
77
} return hr; The shell item created in Listing 9 will either be for a folder or a nonfolder item. If the search is for folders then no further processing is necessary and the item is added to the shellItems vector. If the item is a nonfolder object, then the code must check to see if the item is one of the types of files requested, this code is shown in Listing 10. This code reads the PKEY_Kind property of the item to obtain the item type as a string and compares the returned value with the items in the shellItems vector. Listing 10 Enumerating folders: checking the item type C++ if (isEnumFolders) { shellItems.push_back(static_cast<IShellItem*>(shellItem)); } else { // Check if we the item is correct wchar_t *itemType = nullptr; hr = shellItem->GetString(PKEY_Kind, &itemType); if (SUCCEEDED(hr)) { auto found = std::find(itemKinds.begin(), itemKinds.end(), itemType); if (found != itemKinds.end()) { shellItems.push_back(static_cast<IShellItem*>(shellItem)); } ::CoTaskMemFree(itemType); } }
Conclusion
In this chapter you have seen how to use the shell API to access folders and items through Windows 7 libraries. In the next chapter, we will introduce the Annotator application which allows the user to easily edit their images.
78
::CloseHandle(processInfo.hProcess); ::CloseHandle(processInfo.hThread); return; } The Browser assumes that the Annotator executable is in the same folder as the Browser. The first part of the LaunchAnnotator method gets the full path to the Browser application by calling the GetModuleFileName [http://msdn.microsoft.com/en-us/library/ms683197(VS.85).aspx] function and then extracts the folder path. This folder path is used as the path to the Annotator application. The Browser then launches the Annotator by using the CreateProcess [http://msdn.microsoft.com/enus/library/ms682425(VS.85).aspx] function, and specifies the name of the photo to edit via the command line. The Annotator process accesses the command line in the AnnotatorApplication::Initialize method when the application first starts. Listing 2 shows the code to do this. First the code calls the GetCommandLineW [http://msdn.microsoft.com/en-us/library/ms683156(VS.85).aspx] method and then splits this into an array of pointers to the individual command line arguments by calling the CommandLineToArgvW [http://msdn.microsoft.com/en-us/library/bb776391(v=VS.85).aspx] function. The command line is part of the process environment, so the process does not need to provide storage for the string, nor provide code to deallocate the string buffer. The GetCommandLineW function is used because the lpCmdLine parameter of the process entry point function, WinMain [http://msdn.microsoft.com/en-us/library/ms633559(VS.85).aspx] , can only provide the command line as an ANSI string even if, as in the case of Hilo, the process is compiled for Unicode. Listing 2 Annotator code to access the command line parameters C++ int argumentCount = 0; ComPtr<IShellItem> currentBrowseLocationItem; if (SUCCEEDED(hr)) { // Process command line wchar_t ** commandArgumentList = CommandLineToArgvW( GetCommandLineW(), &argumentCount); if (argumentCount > 1) { hr = ::SHCreateItemFromParsingName( commandArgumentList[1], nullptr, IID_PPV_ARGS(¤tBrowseLocationItem)); } else { // Default to pictures library hr = ::SHCreateItemInKnownFolder( FOLDERID_PicturesLibrary, 0, nullptr, IID_PPV_ARGS(¤tBrowseLocationItem)); if (FAILED(hr)) { // Set to top-level computer folder hr = ::SHGetKnownFolderItem( FOLDERID_ComputerFolder, static_cast<KNOWN_FOLDER_FLAG>(0), nullptr, IID_PPV_ARGS(¤tBrowseLocationItem)); } } } Listing 2 also shows that if the process is started with a command line parameter then it is used to create a shell item object by calling the SHCreateItemFromParsingName [http://msdn.microsoft.com/en80
us/library/bb762134(VS.85).aspx] function. If the Annotator is called without a command line parameter, the Annotator obtains as the starting point the Pictures library, or failing that, the Computer folder. It is important to note that the shell item object can either be a file (the photo passed by the Browser) or a folder (Pictures library or Computer folder). Annotator uses this shell item to populate the editor pane (the equivalent of the Browsers media pane) with all the photos in the specified folder, or if the shell item object is a file, the selected photo. Other than the command line there is no other communication between Browser and Annotator. The Annotator is a separate process so the user can task switch to the Browser and continue to use it. The user can also create another instance of the Annotator. Note that since there is no direct communication with the Browser application if you change a photo in Annotator the cached image of the photo in Browser is not automatically updated to show the changes made in Annotator.
The Annotator process will be started by, and run under, the debugger. This means that because it is not started by the Browser it will not have the file path passed to it by the Browser. If you want to test how the Annotator handles command line parameters then you have to give the full path to a photo as the Command Arguments property on the Annotator Debugging property page, as shown in Figure 2. Figure 2 Specifying an image on the Annotator command line
81
Second, you may allow the Browser to start the Annotator process and then attach to the Annotator process with the Visual Studio debugger. To do this, select the Attach to Process menu item on the Debug menu. The Attach to Process dialog lists all the running processes on the computer. To attach the debugger double-click on the line for Annotator.exe as shown in Figure 3. This option is useful for attaching to any existing process, but any debugging can only be done from the point that you attach, which usually means that you cannot debug the WinMain function nor the code that creates and initializes the window. Figure 3 Attaching the debugger to the Annotator process
The third option is not available for Visual C++ Express but is available for Visual Studio Professional and above: use Just In Time (JIT) debugging. To do this you put a call to the DebugBreak [http://msdn.microsoft.com/enus/library/ms679297(VS.85).aspx] function (or the __debugbreak [http://msdn.microsoft.com/enus/library/f408b4et.aspx] intrinsic) at the point in your program where you want debugging to start. However, before you can use these functions you have to tell Windows 7 to allow the function call to start the debugger. These functions cause a software exception (an interrupt, int 3) and by default, Windows 7 security will treat all exceptions as faults in the program and will handle this by searching for a solution online, so you must disable this action for the Annotator process. If the assert C runtime library (CRT) function is called with a false condition the __debugbreak [http://msdn.microsoft.com/en-us/library/f408b4et.aspx] intrinsic is called, so if you have asserts in your code and you want them handled through JIT debugging you must disable Windows 7 problem
82
solving as explained below. To disable problem solving open the Action Center in the Control Panel, expand the Maintenance section (Figure 4), and click on the Settings link. Figure 4 Using the Action Center
This shows the Problem Reporting Settings page which lists the settings that will be used for all processes running on the computer (Figure 3). This dialog box also allows you to list the programs that will be excluded from problem reporting. Click on the Select programs to exclude from reporting link and then use the Add button to locate and select the debug build of the Annotator process (Figures 5 and 6). Figure 5 Using the Problem Reporting Settings dialog
Now whenever Annotator is run and there is a call to the DebugBreak function, Windows 7 will give you one or more dialog boxes similar to Figure 7. Then it will call the Visual Studio JIT Debugger, which will give you the option to start a new instance of Visual Studio or attach the debugger from a running instance. Figure 7 Windows 7 problem reporting allowing you to debug a process
The advantage of JIT debugging is that you can debug any code where you can put a call to the DebugBreak function, however, you must make sure that you remove this code when you have finished testing the application.
84
The image editor area takes up most of the applications window. It behaves in a similar way to the media pane in the Browser. You can scroll left or right by using the mouse, the left and right arrow keys, or by dragging a photo with your finger on a touch screen. The default zoom level shows one complete photo and a preview of the photos to the left and right. The Annotator fades the left and right photos by first rendering the images and then drawing over them with a white linear gradient brush (where the gradient is the alpha channel changing from fully opaque to transparent). Above the image editor is a Windows Ribbon control. This ribbon has two tabs, a menu, and a quick access toolbar. The Home tab has two groups and these have controls that allow you to crop, rotate, and draw on the photo in the image list. When you click either the Pencil or Crop buttons it selects the appropriate action that either allows you to draw with a pencil or crop the image. When you click the Rotate button the Annotator rotates the photo clockwise. The Rotate control also includes a dropdown menu giving additional transformation options: rotate counter-clockwise, mirror horizontally, or mirror vertically. The Color control is a standard control called a Dropdown Color Picker. When you click on this control a color swatch is displayed. The Size control is a drop down list that displays the four different pencil widths that are available. The View tab has three push button controls: zoom in, zoom out, and reset to 100% zoom. The ribbon has two menus. The main menu is a dropdown menu control that is to the left of the Home ribbon tab. This menu has the following items: Open, Save, Save A Copy As, and Exit. The other menu is the Quick Access Toolbar and by default this is shown on the title bar. The Quick Access Toolbar has four controls. The first three are buttons that generate commands to save the photo, to undo an action, or redo an action. The fourth control is a dropdown menu that allows you to customize the toolbar: show or hide the other buttons, change the location of the toolbar, and minimize the ribbon. The default position of the Quick Access Toolbar is on the title bar, but the customize menu has a menu item called Show below the Ribbon, when you click this item the toolbar moves to beneath the ribbon control, and the image list is resized accordingly. Clearly moving the toolbar to beneath the ribbon means that the area occupied by the photos is reduced. To give the image editor additional space you can select the Minimize the Ribbon item on the quick access toolbar. When the ribbon is minimized only the tab headers are shown. If you click on one of these headers the tab appears, but in front of the photo rather than above it.
85
When you have changed a photo, youll see that the Undo button is enabled (the third icon from the left on the title bar in Figure 9). Annotator keeps a list of every change that you make to a photo and the Undo button allows you to undo one of these steps (the Redo button allows you to redo a step that you have undone). When the Pencil button is selected the cursor becomes a pencil and when you use the stylus on a touch screen or move the mouse with the left button down the effect is to draw on the photo. If the Pencil button is deselected the cursor changes to an arrow and when you use the stylus or the mouse with the left button down the effect is to move the photo.This is useful if you have zoomed in so that the editor only shows part of the photo and you want to move to a different part. In some cases youll want to crop a photo and to do this you use the crop tool. When you click the Crop button the cursor changes to cross-hairs and the entire photo is grayed out. You can now use the stylus or the mouse to draw the new boundaries of the photo, Figure 10. Figure 10 Cropping a photo
The Annotator also allows you to rotate and mirror the photo. You do this through the items on the Rotate drop down menu. Figure 11, shows a rotation of 90 clockwise. When you perform one of these operations Annotator animates the operation so that you see the rotation or mirror occurring rather than simply the final result. In addition, the image is zoomed so that it completely fills the space available, so in Figure 11, since the cropped image is taller than it is wide after the rotation, it is zoomed out (and appears smaller) so that the height of the photo fits the height of the editor pane. If you rotate the photo by another 90 the image will now be wider than it is high so it will be zoomed so that the new height fills the height of the editor.
86
if you want to examine the details of a photo, there are various ways to zoom in or out. First you can hold down the CTRL key and use the mouse wheel, second you can use the Plus Sign and Minus Sign keys and third, you can use the Zoom In and Zoom Out buttons on the View tab shown in Figure 12. To zoom to 100% you can press the ESC key or click the 100% button. Figure 12 Zooming a photo
When you exit the Annotator any changes that you have made are saved automatically, but you may also save the changes at any time by using the Save or Save A Copy As menu items. When you save a photo the Annotator makes a backup copy of the original photo in a subfolder called AnnotatorBackup.
Conclusion
The Hilo Annotator is the second application in the Hilo suite of applications and is used to edit photos in various ways. The Annotator provides tools to draw, crop, and rotate a photo and access to these tools is given through an instance of the Windows Ribbon control. Programming the Windows Ribbon control is the subject of the next chapter in the series.
87
88
Adaptive layout is a consequence of the separation of presentation and logic. You design the user interface (UI) with a design tool that generates XAML, compile this to a BML file, and bind it to the application as a UIFILE resource. You do not have to write layout code, resizing code, child control creation, or initialization code. All of this is provided by the Ribbon control based on the markup information in the BML file, which is passed to the control when it is first initialized. The commands on the Ribbon control generate command messages, so you have to write code to handle these command messages. To do this you create a COM object called a command handler object. The XAML for the presentation is made up of two sections. One section defines the command names including a name for use in the XAML and a unique ID used to identify the command in the code. The command section also allows you to define command specific properties like a label, a tooltip, or image. The other section provides information about the controls that generate the command messages. More than one control can generate a command, and the practice of defining the commands separate from the controls means that all controls that generate a command will have the same label, tooltip, image, and so on. This is illustrated in Figure 2 where you can see that the Save menu item displays a tooltip, a label, and a large icon. The Save item on the Quick Access Toolbar displays a small icon and no label on the toolbar, however, the toolbar shows the same tooltip as the menu item and the same label is used on the quick access toolbar customize menu, indicating that the two items are associated with the same command. Figure 2 Illustrating command properties shown by Ribbon controls
89
Controls [http://msdn.microsoft.com/en-us/library/dd940497(v=VS.85).aspx] are not created on their own, instead they are hosted in a container called a view and they may be grouped together. The Ribbon control API supports two types of view: Ribbon View [http://msdn.microsoft.com/en-us/library/dd316811(v=VS.85).aspx] and ContextPopup View [http://msdn.microsoft.com/en-us/library/dd371654(v=VS.85).aspx] . The Ribbon View contains the application menu, tabs, and the Quick Access Toolbar; the ContextPopup View supports context menus and mini toolbars. These containers can have individual controls or have groups of controls. Grouping together controls helps the user by categorizing controls that perform similar tasks, and it helps the Ribbon control adaptive layout as shown in Figure 1. Each control will have properties that can be accessed at runtime and define the behavior of the control. Some of these properties can be set in the markup XAML code. For example the Button [http://msdn.microsoft.com/enus/library/dd940490(v=VS.85).aspx] control has properties for the label, tooltip, and icon that are provided by the command associated with the button. The Ribbon control framework provides access to property values through code.
<Application.Commands> </Application.Commands> <Application.Views> </Application.Views> </Application> To be useful each command element must have a name and symbol. The name is a string used by other XAML code to refer to the command, and the symbol is an identifier that code will use to identify the command. For example, Listing 2 is an extract from the markup code for Hilo Annotator. This markup describes the command that is used by the Open application menu item. The Name attribute gives the string, openFileMenu, that is used to associate the command with the menu item control. The Symbol attribute gives the name of an integer symbol that the XAML compiler will create in a header file and your code will use this to identify the command. If you do not provide a Symbol item then the XAML compiler will generate a symbol for you. Listing 2 shows several property elements, and these provide string values. Property elements are used rather than XML attributes because the element is used to provide an Id value so that the string will be placed in a string table resource as part of the executable. Listing 2 Defining a command XAML <Command Name="openFileMenu" Symbol="ID_FILE_OPEN" Comment="Open"> <Command.LabelTitle> <String Id ="520">Open</String> </Command.LabelTitle> <Command.LargeImages> <Image Id ="521">res/open_32.bmp</Image> </Command.LargeImages> <Command.SmallImages> <Image Id="522">res/open_16.bmp</Image> </Command.SmallImages> </Command> The <Application.Views> element is mandatory and you use this to provide one or more of the view elements. Listing 3 shows an extract from the markup for Hilo Annotator. Annotator only provides the <Ribbon> [http://msdn.microsoft.com/en-us/library/dd316811(v=VS.85).aspx] element for the <Application.Views> property. The Ribbon control provides definitions for the main application menu through the <Ribbon.ApplicationMenu> [http://msdn.microsoft.com/en-us/library/dd316796(v=VS.85).aspx] property element, a Quick Access Toolbar provided through the <Ribbon.QuickAccessToolbar> [http://msdn.microsoft.com/en-us/library/dd316816(v=VS.85).aspx] element, and information about the Ribbon control tabs through the <Ribbon.Tabs> [http://msdn.microsoft.com/en-us/library/dd316826(v=VS.85).aspx] element. Listing 3 Illustrating the Views property XAML <Ribbon> <Ribbon.ApplicationMenu> <ApplicationMenu CommandName="fileMenu"> <MenuGroup> <Button CommandName="openFileMenu" /> <Button CommandName="saveFileMenu" /> <Button CommandName="saveAsFileMenu" /> </MenuGroup> <MenuGroup> <Button CommandName="exitMenu" /> </MenuGroup> </ApplicationMenu> </Ribbon.ApplicationMenu> <Ribbon.Tabs> <!-- code omitted -->
91
</Ribbon.Tabs> <Ribbon.QuickAccessToolbar > <!-- code omitted --> </Ribbon.QuickAccessToolbar > </Ribbon> As you can see from Listing 3, the openFileMenu command defined in Listing 2 is used as one menu item on the application menu. The markup shows two unnamed <MenuGroup> [http://msdn.microsoft.com/enus/library/dd371700(v=VS.85).aspx] elements and in practice this will mean that the child items in these groups will be shown in the menu as if they are a single group. However, if you provide a CommandName property then the Ribbon control will display the string provided through the command objects LabelTitle property as an annotation to the menu items
The Ribbon control supports many different controls. Some are simple like Button and Check Box [http://msdn.microsoft.com/en-us/library/dd940491(v=VS.85).aspx] , but the Ribbon control also provides more complex controls through elements called galleries. Hilo Annotator uses a DropDownGallery control to provide the drop-down list of the pencil widths. The markup code for this control is very simple, as shown in Listing 5. This markup simply indicates that the control is shown to the user as a button and the sizeButton command gives the images that will be shown on the button. Listing 5 Declaring the markup code for Pencil size control XAML <DropDownGallery CommandName="sizeButton" TextPosition="Hide" Type="Items" ItemHeight="32" ItemWidth="128" HasLargeItems="true"> <DropDownGallery.MenuLayout> <VerticalMenuLayout Gripper="None" /> </DropDownGallery.MenuLayout> </DropDownGallery> When you click on a drop-down gallery button the gallery list is shown. The Gripper property has a value of None which indicates that the user cannot resize the gallery list since there is no gripper resizing handle. Figure 3 shows the drop-down gallery displayed when the user clicks the Size button. There is no information about the drop-down list in the markup in Listing 5 because this information is obtained at run time. The items in a DropDownGallery control are accessed through the IUICollection [http://msdn.microsoft.com/enus/library/dd371519(v=VS.85).aspx] interface implemented by the control, during the initialization of the Ribbon control the applications command handler object accesses the IUICollection interface and uses it to add the images. Figure 3 Displaying the size gallery
To add the XAML markup file and build step to a project: 1. Add the XML file to the project using Solution Explorer. 2. In Solution Explorer, right-click the XML file, and then click the Properties item to show the property page. On the General page, change the Item Type item to Custom Build Tool, and click the Accept button. This will ensure that the Custom Build Tool page is shown on the property page. 3. In the Configuration drop-down list, click All Configurations. 4. Click the Custom Build Tool page, and in the Command Line item type the following uicc.exe %(FullPath) %(Filename).bml /header:%(Filename).h /res: %(Filename).rc This indicates that the uicc.exe tool is used to compile the XML file and provides names for the BML file, the header file, and resource script. 5. Click OK, to save the values and close the property pages. 6. Now add a line to insert the generated resource script to the projects resource script. In Solution Explorer right-click the projects resource script file (for example in Hilo Annotator this is called Annotator.rc) and click View Code. 7. Scroll to the bottom of the file and add a line like the following where RibbonRes.rc is the name of the resource script created by uicc.exe #include "RibbonRes.rc" 8. Press Ctrl+S to save the resource script. After these steps when you build the project the XML file will be compiled and the resource bound to the executable. When you build the solution, if you get an error that uicc.exe is not a command (this will happen if you run Visual C++ 2010, a 32-bit application, on an Windows 7 x64-based system) then it means that you need to add the path to the SDK tools to the projects properties. To add the path to the SDK tools to the project properties: 1. In Solution Explorer right-click the project, and then click Properties. 2. In the Configuration category, click VC++ Directories. 3. Edit the Executable Directories item to add the path to the Bin folder of the Windows 7 SDK.
if (SUCCEEDED(hr)) { // window is the main application window hr = window->GetWindowHandle(&hWnd); } if (SUCCEEDED(hr)) { // Initilize the ribbon framework hr = m_ribbonFramework->Initialize(hWnd, this); } if (SUCCEEDED(hr)) { static const int MaxResStringLength = 100; wchar_t ribbonMarkup[MaxResStringLength] = {0}; // Obtain the name of the BML resource ::LoadString( HINST_THISCOMPONENT, IDS_RIBBON_MARKUP, ribbonMarkup, MaxResStringLength); hr = m_ribbonFramework->LoadUI(GetModuleHandle(NULL), ribbonMarkup); } After creating the Ribbon object the code in Listing 6 obtains the handle for the main application window and passes this to the Ribbon control by calling the IUIFramework::Initialize [http://msdn.microsoft.com/enus/library/dd371467(v=VS.85).aspx] method. The Ribbon control is not an ActiveX control so it does not need the application to implement control site interfaces. Instead the control is created as a child window of the window whose handle you pass as the first parameter. The second parameter, Initialize is a pointer to an IUIApplication [http://msdn.microsoft.com/en-us/library/dd371528(v=VS.85).aspx] interface implemented by the application the this pointer is passed because the AnnotatorApplication class implements this interface. Finally the InitializeRibbonFramework method calls IUIFramework::LoadUI [http://msdn.microsoft.com/enus/library/dd371471(v=VS.85).aspx] to provide information about the presentation of the Ribbon control. This method will only load a BML file bound as a resource to an executable (an EXE or DLL), so you have to provide the instance handle of the executable and the name of the BML file that has been bound as a UIFILE resource. The default name of this resource is APPLICATION_RIBBON and since Hilo Annotator stores this in the applications string table the InitializeRibbonFramework method calls the LoadString [http://msdn.microsoft.com/en-us/library/ms647486(VS.85).aspx] Windows 7 API to load this string. The IUIApplication [http://msdn.microsoft.com/en-us/library/dd371528(v=VS.85).aspx] interface is essentially the site interface of the application which the Ribbon control uses to initialize the handshake mechanism between the application and the control. There are three methods on this interface, the IUIApplication::OnCreateUICommand [http://msdn.microsoft.com/en-us/library/dd371531(v=VS.85).aspx] method is called for every command when the Ribbon control is first created, the IUIApplication::OnDestroyUICommand [http://msdn.microsoft.com/en-us/library/dd371534(v=VS.85).aspx] method is called for every command when the Ribbon control is destroyed, and the IUIApplication::OnViewChanged [http://msdn.microsoft.com/en-us/library/dd371537(v=VS.85).aspx] method is called when the state of one of the view objects changes. In Hilo Annotator the implementation of OnViewChanged is used to obtain the height of the Ribbon control by calling a method called GetRibbonHeight, as shown in Listing 7. This code illustrates how you can obtain the various views on the Ribbon control (the Ribbon or a ContextPopup [http://msdn.microsoft.com/enus/library/dd371654(v=VS.85).aspx] object). Listing 7 Obtaining the Ribbon control height when the view changes C++ unsigned int AnnotatorApplication::GetRibbonHeight() { unsigned int ribbonHeight = 0;
95
if (m_ribbonFramework) { ComPtr<IUIRibbon> ribbon; HRESULT hr = m_ribbonFramework->GetView(0, IID_PPV_ARGS(&ribbon)); if (SUCCEEDED(hr)) { hr = ribbon->GetHeight(&ribbonHeight); if (FAILED(hr)) { ribbonHeight = 0; } } } return ribbonHeight; } When the Ribbon control is created it calls the applications OnCreateUICommand method for each command mentioned in the BML file, requesting a command handler object to handle command messages from the command. Through this method, the Ribbon control passes the ID of the command as specified in the markup, and an ID that indicates the type of the command. The final parameter of this method is an out parameter which the application uses to return an interface pointer to the command handler object. Hilo Annotator has a class called UICommandHandler that implements the IUICommandHandler [http://msdn.microsoft.com/enus/library/dd371491(v=VS.85).aspx] interface. Annotator creates just one instance of this class when it first starts and returns this as the command handler object for every command, as shown in Listing 8. Listing 8 Returning the command handler object C++ HRESULT AnnotatorApplication::OnCreateUICommand( unsigned int, UI_COMMANDTYPE, IUICommandHandler** commandHandler) { // The ribbon uses only one command handler return m_commandHandler->QueryInterface(IID_PPV_ARGS(commandHandler)); } The majority of the communication between the application and the Ribbon control object occurs through the command object.
The commandId parameter will be one of the symbols in the header file created by the uuic.exe tool and it will be either a symbol that you specified in the markup, or if you did not specify then the uicc.exe will create a symbol for you. The verb parameter indicates the action of the user and can indicate that the command was executed, or that the control was previewed (the mouse is hovered over a control), or a preview was cancelled. Clearly a value of UI_EXECUTIONVERB_EXECUTE [http://msdn.microsoft.com/enus/library/dd371563(v=VS.85).aspx] , indicating that the command has executed, is the most important. Commands can be quite complex since a control can be composed of several controls, each of which will have values. These values are the control properties. The Execute method indicates the property that is the subject of the command through the key parameter and the value of the property through currentValue. The property identifier is a pointer to a PROPERTYKEY [http://msdn.microsoft.com/en-us/library/bb773381(VS.85).aspx] structure, and the values in the structure identify the type of the property and a unique identifier. You do not need to know the values used, all you need to know is the symbol. The uiribbon.h header file defines the properties that are used by the Ribbon control, so for example, the UI_PKEY_ENABLED symbol is used to identify the enabled property and indicates that this property is a Boolean. The actual value of the property is accessed through the currentValue parameter which is a pointer to a VARIANT. The final parameter of the Execute method is a pointer to a IUISimplePropertySet [http://msdn.microsoft.com/en-us/library/dd371358(v=VS.85).aspx] interface that you can use to access other properties of the command. Hilo Annotator provides an implementation of IUICommandHandler interface through the class UICommandHandler. The implementation in Annotator, UICommandHandler::Execute, has a large switch statement so that it can provide different code for each command and much of the code delegates the code to other objects in the application. For example, Listing 10 shows part of this switch statement, where the m_imageEditor variable is a pointer to the object that provides the code for the image editor child window in the lower half of the Annotator window. You will recognise the ID_FILE_OPEN symbol from Listing 2 where it is declared as the symbol for the openFileMenu command. Listing 10 Handling command messages C++ HRESULT hr = S_OK; switch (commandId) { case ID_FILE_OPEN: { hr = m_imageEditor->OpenFile(); break; } case ID_FILE_SAVE: { m_imageEditor->SaveFiles(); break; } case ID_FILE_SAVE_AS: { m_imageEditor->SaveFileAs(); break; } case ID_FILE_EXIT: { m_imageEditor->SaveFiles(); ::PostQuitMessage(0); break; } // other code omitted }
The controls [http://msdn.microsoft.com/en-us/library/dd940497(v=VS.85).aspx] that you can use on a Ribbon control are documented in the MSDN Library Each page lists the property keys that the control supports. The documentation describes how some of the properties are accessed through calls to the Ribbon controls IUIFramework interface (the IUIFramework::GetUICommandProperty [http://msdn.microsoft.com/enus/library/dd371370(v=VS.85).aspx] and IUIFramework::SetUICommandProperty [http://msdn.microsoft.com/en-us/library/dd371478(v=VS.85).aspx] methods). However, most of the properties are accessible through a process that the documentation calls invalidation. Invalidation means that the application tells the Ribbon control that a control property is invalid and then the Ribbon control calls the application to request the new value. In Hilo Annotator invalidation occurs in the image editor code whenever the Image Editor window is redrawn or if a command is executed. Listing 11 shows this code where the m_framework variable is the IUIFramework interface on the Ribbon control. Listing 11 Invalidating control properties C++ HRESULT ImageEditorHandler::UpdateUIFramework() { // After we're done drawing make sure to update framework buttons as necessary if (m_framework) { m_framework->InvalidateUICommand( ID_BUTTON_UNDO, UI_INVALIDATIONS_STATE, nullptr); m_framework->InvalidateUICommand( ID_BUTTON_REDO, UI_INVALIDATIONS_STATE, nullptr); m_framework->InvalidateUICommand( ID_FILE_SAVE, UI_INVALIDATIONS_STATE, nullptr); m_framework->InvalidateUICommand( pencilButton, UI_INVALIDATIONS_VALUE, nullptr); m_framework->InvalidateUICommand( cropButton, UI_INVALIDATIONS_VALUE, nullptr); } return S_OK; } The IUIFramework::InvalidateUICommand [http://msdn.microsoft.com/enus/library/dd371375(v=VS.85).aspx] method can be used to invalidate just one property of a command, or all properties of a particular type. The first parameter of this method is an identifier indicating the command to update, the second parameter indicates the type of data to update (a UI_INVALIDATIONS [http://msdn.microsoft.com/en-us/library/dd371573(v=VS.85).aspx] value), and the final parameter is the ID of a property if you wish to update a specific property. In Listing 11 the state is invalidated for the Undo, Redo, and Save buttons so that the Ribbon control requests that the application returns the enabled state (UI_PKEY_Enabled) of the Undo and Save buttons. The pencilButton and cropButton controls are Toggle Button [http://msdn.microsoft.com/enus/library/dd940509(v=VS.85).aspx] controls and in Hilo Annotator only one of them can be pushed in. This pushed-in state is a Boolean value so rather than requesting the change in property state, the call to the InvalidateUICommand method for these two controls requests that the values of the control properties are invalidated, and the application will check for, and return a value for the UI_PKEY_BooleanValue of these controls. Once you have invalidated a command, the Ribbon control cannot display the associated controls state, so it must ask the application for the property values by calling the IUICommandHandler::UpdateProperty [http://msdn.microsoft.com/en-us/library/dd371494(v=VS.85).aspx] method. This method is called with four parameters, the first three indicate the command, the property, and the current value of the property.The fourth is an out parameter for the new value of the property. In Hilo Annotator this method is implemented in the UICommandHandler class and has two purposes. The first is to return the state and value properties invalidated by the UpdateUIFramework method (Listing 11)1 The second purpose is to populate the DropDownGallery
98
control for the pencil widths with appropriate images. Listing 5 shows that the markup for the Gallery control does not provide the values to be shown by the control. When the control is initialized, the Ribbon control calls the UpdateProperty method to update the UI_PKEY_ItemsSource [http://msdn.microsoft.com/en-us/library/dd371348(v=VS.85).aspx] property. The UICommandHandler class implementation of this method accesses the IUICollection [http://msdn.microsoft.com/en-us/library/dd371519(v=VS.85).aspx] interface on this property and adds the images to be shown by the Gallery control.
Conclusion
In this chapter you learned how Hilo Annotator uses a Windows Ribbon control to give easy access to the image editor tools. In the next article you will learn how to use the Windows 7 Imaging Component to load and convert images.
99
methods that allow you to create decoders and encoders, bitmaps and streams; and it allows you to create objects to alter color palettes and metadata in an image file.
Handling Images
Conceptually bitmap graphics can be viewed as rows of pixels with the color of each pixel composed of color components such as red, blue, and green or cyan, magenta ,and yellow, and an alpha channel component to determine opacity. If these components have 256 values, involving 32-bits per pixel, this would mean that a 1000 by 1000 pixel image would take up a megabyte of storage. Since images of this resolution are fairly standard, and storage space is always at a premium, there is a need to compress the way that pixels are represented to reduce image size. In most images there are repetitions of pixels, and most images do not use the full gamut of colors, and so image compression techniques have been developed to reduce this redundancy. Some techniques are lossless, so that all the original pixels can be recreated during decompression and others are lossy meaning that to the human eye the decompressed image looks like the original but does not necessarily have the same pixels. A codec is code that compresses and decompresses images, and code that compresses an image is called an encoder and the code that decompresses images is called a decoder. Your graphics card will have its own image format and so a codec must be able to convert the image format used by an image file to and from a form that Windows 7 can use. The WIC decoders can provide raw pixel information that can be used with GDI functions. In addition, Direct2D can create a bitmap object (with an ID2D1Bitmap [http://msdn.microsoft.com/enus/library/dd371109(v=VS.85).aspx] interface) from a WIC image so you can use Direct2D to draw an image loaded by WIC. You can also create a Direct2D render target based on a WIC image, which means that you can use Direct2D drawing actions to change the WIC image. Images can be provided in several formats. Typically an image will be a file on a hard disk so WIC provides methods to load an image from a file name or a file handle. Images may also be stored as streams within other files (OLE documents) so WIC provides methods to load from an OLE stream (the IStream [http://msdn.microsoft.com/en-us/library/aa380034(VS.85).aspx] interface). Images may be stored as resources as part of executables and the WIC API provide methods to load images through HBITMAP or HICON data. In addition to pixel information most image formats contain metadata. Digital cameras use metadata to store information such as the camera settings (ISO, shutter speed, aperture, flash) and information about the photo (time, date, GPS reading). There are several standard metadata formats: Exif, XMP, IPTC, are three of the common formats supported by WIC. These formats determine how the metadata are stored in an image file and also define standard metadata items. It is clearly the responsibility of a codec to handle the metadata formats used by the image type. Some image formats support storing more than one image in a file, for example GIF images and some TIFF images. Many digital cameras also provide a thumbnail image as well as the full size image. WIC provides methods to get each of the images in a file as a frame, and it supports loading global thumbnails and frame-based thumbnails. The standard decoders do not support accessing global image data, so even if the image file only contains one image you still have to load that as a single frame and then use a frame decoder to get information about that frame.
The interface hierarchy means that wherever an IWICBitmapSource interface is required an instance of any of the other interfaces can be used. This means that you can pass an IWICBitmapScaler [http://msdn.microsoft.com/en-us/library/ee690168(v=VS.85).aspx] interface, implemented by a scaler object, to a function that requires an IWICBitmapSource. As the function reads information about the bitmap through the interface, the scaler object will perform scaling automatically on the information. Most of the child interfaces shown in Figure 1 simply add an Initialize method to the IWICBitmapSource interface to enable you to provide
101
information about how the object works, for example for the IWICBitmapScalar interface the Initialize [http://msdn.microsoft.com/en-us/library/ee690169(v=VS.85).aspx] method provides the new height and width to which the image will be scaled.
IWICImagingFactory::CreateDecoderFromFilename [http://msdn.microsoft.com/enus/library/ee690307(v=VS.85).aspx] method where the uri variable is the path to the image file. This method activates the appropriate decoder for the image type, loads the image file, and initializes the decoder with the image, so you do not need to call the Initialize method on the IWICBitmapDecoder interface returned from this method. The code in Listing 2 then calls GetFrame for the first frame in the bitmap to get the decoded image. The IWICBitmapFrameDecode interface uses the decoder object whenever information is read from the image. The pixel format of this image is unlikely to be the 32bppPBGRA format so Listing 2 creates a converter object. The IWICFormatConverter [http://msdn.microsoft.com/en-us/library/ee690274(v=VS.85).aspx] interface derives from IWICBitmapSource and in essence, it delegates calls to this interface to the raw image, performing conversions where necessary. Finally, the code calls the ID2D1RenderTarget::CreateBitmapFromWicBitmap [http://msdn.microsoft.com/en-us/library/dd371797(v=VS.85).aspx] method to provide an object with the ID2D1Bitmap interface using data from the converted IWICBitmapSource object. The returned variable (in Listing 2 this is the bitmap variable) can be rendered on an ID2D1RenderTarget object and a Direct2D render target can be created from this bitmap to allow you to change the image using the Direct2D drawing operations. When you call the Direct2D bitmap object, the call is passed to the format converter, which calls the frame decoder, which calls the decoder object. At each stage the data is converted.
} If the caller of the LoadBitmapFromFile method provides either a new width or a new height then a scalar object is created. If just one dimension is specified then the new image is scaled maintaining the aspect ratio of the image. If you want to change the aspect ratio then you can provide both the new height and the new width. Once the code has determined the new height and width, it creates the scalar object through the WIC factory object by calling the IWICImagingFactory::CreateBitmapScalar [http://msdn.microsoft.com/enus/library/ee690296(v=VS.85).aspx] method. This scalar object is then initialized by calling the IWICBitmapScalar::Initialize [http://msdn.microsoft.com/en-us/library/ee690169(v=VS.85).aspx] method providing the bitmap source (in this case the frame decoder) and the scaling parameters. The final parameter of this method indicates how the scaling is performed and in this case bicubic interpolation is used. Finally, the pixel format converter is initialized with the scalar, rather than the frame decoder, to add scaling when the image is accessed.
104
// Get the original bitmap rectangle in terms of the current crop D2D1_RECT_F originalBitmapRect = D2D1::RectF( 0, 0, Direct2DUtility::GetRectWidth(m_clipRect), Direct2DUtility::GetRectHeight(m_clipRect)); // When rotating images make sure that the point around which rotation occurs lines // up with the center of the rotated render target if (false == m_isHorizontal) { float offsetX; float offsetY; if (width > height) { offsetX = (width - height) / 2; offsetY = -offsetX; } else { offsetY = (height - width) / 2; offsetX = - offsetY; } D2D1_MATRIX_3X2_F translation = D2D1::Matrix3x2F::Translation(offsetX, offsetY); wicRenderTarget->SetTransform(translation); } // Update current render target to point to WIC render target m_currentRenderTarget = wicRenderTarget; // Draw updated image to WIC render target wicRenderTarget->BeginDraw(); DrawImage(originalBitmapRect, m_clipRect, true); wicRenderTarget->EndDraw(); The remainder of the Save method makes a backup copy of the original file in a subfolder called AnnotatorBackup and then calls the Direct2DUtility::SaveBitmapToFile method to save the WIC bitmap.
actual pixel data through calls to either the IWICBitmapFrameEncode::WritePixels [http://msdn.microsoft.com/en-us/library/ee690158(v=VS.85).aspx] method (to write an individual scan line with a buffer of bytes) or the IWICBitmapFrameEncode::WriteSource [http://msdn.microsoft.com/enus/library/ee690159(v=VS.85).aspx] method (to write the entire frame from an IWICBitmapSource object). When you have finished writing a frame you must call IWICBitmapFrameEncode::Commit [http://msdn.microsoft.com/en-us/library/ee690142(v=VS.85).aspx] method to indicate that the frame can be written to the stream. As mentioned earlier, a frame may have metadata and you can obtain a IWICMetadataQueryWriter [http://msdn.microsoft.com/en-us/library/ee719717(v=VS.85).aspx] object by calling the IWICBitmapFrameEncode::GetMetadataQueryWriter [http://msdn.microsoft.com/enus/library/ee690144(v=VS.85).aspx] method. However, if you already have a bitmap source object with metadata you can copy the metadata as a block by using metadata block readers and writers. The frame decoder supports block metadata reading through the IWICMetadataBlockReader [http://msdn.microsoft.com/enus/library/ee690327(v=VS.85).aspx] interface. Normally you would not use this interface directly; instead you use it to initialise a metadata block writer. A frame encoder implements the IWICMetadataBlockWriter [http://msdn.microsoft.com/en-us/library/ee690335(v=VS.85).aspx] interface and you can initialize the block writer by calling the IWICMetadataBlockWriter::InitializeFromBlockReader [http://msdn.microsoft.com/enus/library/ee690338(v=VS.85).aspx] method passing the block reader object. This method will then read all the metadata and write them to the image file stream when the frame is committed.
// Other code omitted factory->CreateEncoder(containerFormat, nullptr, &encoder); After creating the encoder you must initialize it with a stream that indicates where the data is to be written. Listing 8 shows the code to do this. The factory object is used to create a stream and the stream is initialized with the path to the output file. The stream is then used to initialize the encoder object. Listing 8 Initializing the encoder C++ ComPtr<IWICStream> stream; // Create a stream for the encoder factory->CreateStream(&stream); // Initialize the stream using the output file path stream->InitializeFromFilename(outputFilePath.c_str(), GENERIC_WRITE); // Create encoder to write to image file encoder->Initialize(stream, WICBitmapEncoderNoCache); At this point you have three objects: the decoder with the original data, the WIC bitmap with the changed bitmap, and the encoder that is initialized with a stream to the location to store the data. The final part of the SaveBitmapToFile method is to write the WIC bitmap as the first frame to the encoder and if the original file has more than one frame then all the other frames are read from the decoder and written to the encoder. Each time the SaveBitmapToFile method writes a frame it must write the metadata for that frame and the simplest way to do this is through metadata block readers and writers. Listing 9 shows the first part of the loop to write a frame. The first part of this code obtains the frame decoder for the specified frame and creates a new frame in the encoder, it then sets the size of the new frame and if this is the first frame the size is taken from the WIC bitmap. Otherwise it is taken from the frame read from the decoder from the original file. Next, the code reads the pixel format of the frame from the original file and uses this to set the frame encoder. Listing 9 Creating a frame using an encoder C++ ComPtr<IWICMetadataBlockWriter> blockWriter; ComPtr<IWICMetadataBlockReader> blockReader; for (unsigned int i = 0; i < frameCount && SUCCEEDED(hr); i++) { //Frame variables ComPtr<IWICBitmapFrameDecode> frameDecode; ComPtr<IWICBitmapFrameEncode> frameEncode; //Get and create image frame decoder->GetFrame(i, &frameDecode); encoder->CreateNewFrame(&frameEncode, nullptr); //Initialize the encoder frameEncode->Initialize(NULL); //Get and set size if (i == 0) { updatedBitmap->GetSize(&width, &height); } else { frameDecode->GetSize(&width, &height); }
108
frameEncode->SetSize(width, height); //Set pixel format frameDecode->GetPixelFormat(&pixelFormat); frameEncode->SetPixelFormat(&pixelFormat); The final part of the loop reads the data for the frame and writes the data through the encoder, as shown in Listing 10. First the code obtains the metadata block reader from the decoder and uses this to initialize the metadata block writer from the encoder so that the frames metadata is written in one action. Then the frame pixel data is written in one action with a call to IWICBitmapFrameEncode::WriteSource and passing either the WIC bitmap or the frame decoder object. The final part of the loop is to commit the frame, and then when all frames have been written, there is a call to IWICBitmapEncoder::Commit to write the entire image to the file. Listing 10 Writing a frame with an encoder C# //Check that the destination format and source formats are the same bool formatsEqual = false; GUID srcFormat; GUID destFormat; decoder->GetContainerFormat(&srcFormat); encoder->GetContainerFormat(&destFormat); formatsEqual = (srcFormat == destFormat) ? true : false; if (formatsEqual) { //Copy metadata using metadata block reader/writer frameDecode->QueryInterface(&blockReader); frameEncode->QueryInterface(&blockWriter); if (nullptr != blockReader && nullptr != blockWriter) { blockWriter->InitializeFromBlockReader(blockReader); } } if (i == 0) { // Copy updated bitmap to output frameEncode->WriteSource(updatedBitmap, nullptr); } else { // Copy existing image to output frameEncode->WriteSource(static_cast<IWICBitmapSource*> (frameDecode), nullptr); } //Commit the frame frameEncode->Commit(); } encoder->Commit();
Conclusion
The Windows 7 Imaging Component allows you to load images with standard image formats from files, resources, and streams. It will decode the image data into a format that you can use with GDI or Direct2D. Hilo uses the WIC in both the Browser application and the Annotator application to display images, and the Annotator application uses the WIC to save edited photos to disk. In the next chapter we will introduce the Hilo Share application, which allows you to share your images online.
109
In slideshow mode, the carousel disappears and the selected photo is maximized to fill the window. Only one photo is shown in slideshow mode. Arrows are shown on either side of the photo to allow you to pan to the photo to the left or right. On a touch screen you can also touch the photo and, with your finger still on the image, flick it left or right to scroll in that direction. This is known as a touch screen gesture. The Hilo Browser supports two types of gestures: pan (the flick gesture to change photo) and zoom. Figure 2 Resizing a photo with the zoom gesture
110
The zoom gesture requires two fingers (Figure 2) touching the screen. When you move your fingers apart to zoom in, the image size increases, and when you move your fingers together to zoom out, the image size decreases. Gestures like this can make an application feel more natural to use. Figure 3 The Share and Annotate Buttons in the Hilo Browser
The Hilo Browser, in browsing mode, now also provides two buttons in the top right corner, as shown in Figure 3. These buttons allow you to launch the Annotator application, or to share the selected photos via an online photo sharing site. These buttons display images to illustrate their purpose: the Share button has an arrow directing the movement of the photo document to the globe representing the Internet; the Annotator button has a photo and a pencil. Normally these buttons do not have captions, but when you hover the mouse cursor over a button, the button edge is highlighted and the caption appears beneath the button.
uses these features, however, your application will have to be registered with the Windows shell. Hilo provides a utility application to help with this process. When the Annotator first runs (for example, in response to you clicking on the Annotator button in the Browser) it checks to see if the application is registered and if not, it runs a utility called RegistrationHelper. This is a once-only action. RegistrationHelper is a console application and it needs Administrator privileges to run. If your account does not have these privileges then you will get an error message. Even if you run RegistrationHelper as Administrator you will get a User Access Control message asking you if the utility should be given permission to run (click Yes). After running the registration helper utility, it will inform you if the action was successful (Figure 4). From this point onwards, the Hilo Annotator application can display the Recent files Jump Lists on its Taskbar. Figure 4 Successful registration
The utility can also be used to unregister Annotator, for example if you want to remove Hilo from your computer. The command line help is shown in Listing 1. You have to provide at least six command line parameters. The first parameter determines whether you wish to register or unregister the application (use the string TRUE or FALSE). The next parameter is a ProgID (for Annotator use Microsoft.Hilo.AnnotatorProgID) and the utility will either add or remove this key from the HKEY_CLASSES_ROOT registry hive. The next three parameters are only needed if you are registering Annotator. The third parameter is the full path to the Annotator application, the fourth is the name that will be used to identify that a file type is edited by Annotator and the fifth parameter is an identifier called an application user model ID, which is essentially used to group together icons for an application as a single button on the taskbar (for Annotator use Microsoft.Hilo.Annotator). The remaining command line parameters are the extensions of the file types that Annotator will edit. Listing 1 Command line options for RegistrationHelper.exe
Usage: RegistrationHelper <Register:TRUE|FALSE> <ProgID> <FullPath><FriendlyDocumentName> <AppUserModelId> <ext1,ext2,ext3,...> If you wish to unregister the application then you must provide the ProgID and the file extensions, but you can provide empty strings for the other parameters, this is shown in Listing 2, where empty strings () are given for the path, the friendly name and the AppUserModelID parameter. Listing 2 Unregistering Annotator
RegistrationHelper FALSE Microsoft.Hilo.AnnotatorProgID "" "" "" .bmp .dib .jpg .jpeg .jpe .jfif .gif .tif .tiff .png The registration utility adds values to the registry to indicate that the Annotator application may be used to open files of the specified types. In addition, the registration associates the ProgID with the file type and since the ProgID is associated with the application user model ID this indicates to Windows 7 that these files can be edited by Annotator and can be shown on Annotators Jump List. There are two Recent Item lists that are relevant to Annotator. The first is the Start menu Recent Items list
112
(Figure 5). If you do not see the Recent Items list on your Start menu, you can enable this feature through the Start menu properties. The Recent Items list shows the recent files for all applications. In Figure 5 the two recent items are image files that were edited with Annotator. Figure 5 Showing the Start Menu Recent Items list
Each application can have a Recent files Jump List. Figure 6 shows the Jump List for the Annotator application which is displayed when you right-click the Windows 7 Taskbar button for Annotator, or with the touch screen when you touch the taskbar button with one finger and with your finger in place tap the screen with another finger. The bottom three items are standard for all application Jump Lists and are task items that allow you to start a new instance of the application, pin the icon to the taskbar, or close the specific window. The top part of the Jump List shows the files that have recently been edited with Annotator. Figure 6 Showing the Jump List for Annotator
If you compare Figure 5 with Figure 6 you can see that the files added to the Annotator Recent file list are added to the Start menu Recent Items list. When Annotator adds a file to the Recent file list it associates the file with the application user model ID (often abbreviated as AppID, but this is not the same as the GUID used by processes to specify DCOM parameters). The AppID allows the icons of multiple instances of the same application to be grouped together on the taskbar, however if you choose you can give different instances different AppIDs to provide separate icons on the taskbar for each different AppID. When you click on an item in the Recent file list, Windows 7 locates the ProgID for the AppID for the button, and uses the open verb defined in the ProgID to start a new instance of the application with the item. If instead, you right-click the item youll get a context menu which includes the option to view the property page for the item, Figure 7. This shows that the file type (the friendly type name) is Microsoft Hilo Annotator, rather than a generic description of the image type. The important point is that this file type is displayed only when the properties dialog is accessed through the Annotator Jump List. If the files properties are shown through Windows
113
Explorer then the default file type will be displayed. Figure 7 Property page for an item on the Annotator Jump List
The Recent file list is one of the standard categories that you can use on an applications Jump List. Figure 6 shows the standard tasks that are appended to all Jump Lists. You can add custom tasks and Browser adds a custom task to launch Annotator, Figure 8. In this case the task is a shell link object that starts the Annotator process. Figure 8 Browser custom task list
In Windows 7 the default action of hovering the mouse over (or holding your finger on) a taskbar button gives a thumbnail image of the applications window but developers can customize this view and even add controls to it. Hilo Annotator implements a custom preview window, as illustrated in Figure 9. The Annotator does two things: first it provides a preview of the selected photo, and second it provides two buttons which you can click to change the thumbnail (and the image in Annotator) with the image to the left or right. Figure 9 Annotator task bar preview
In order for any application to be able to upload photos to Flickr, it must first be registered with Flickr. Flickr then issues a two part Flickr API Key. One part of this key is used to identify the application, while the other part is used to sign and authenticate the data that it passes through the Flickr API. The Hilo Browser application passes this key to Flickr whenever it performs an action against the Flickr API. Once the Hilo application has been registered with Flickr, it can be used by any user to upload photos to their account. To enable this, however, the user has to specifically authorize the Hilo application so that it can access their account. Issuing a Flickr API Key means that Flickr knows about and trusts the Hilo application. But individual users then have to choose whether or not to trust the Hilo application.
Flickr will then provide you with two strings (Figure 11), one is the API key that uniquely identifies the application (Hilo in this case), and the other string is a shared secret string that Hilo will use to sign data sent to Flickr. This enables Flickr to verify the integrity of the parameters passed to it. You should make a note of the API key and secret strings because you will need to add them to the Hilo Browser source code, as described in the next section. Figure 11 Obtaining the API key for Flickr
115
116
Hilo will upload photos to your account once you acknowledge that Hilo has permission to do this. Once you click the Upload button on the Share dialog you will see the authorization dialog, Figure 13, requesting that you authorize Hilo. Figure 13 Authorizing Hilo to upload to your Flickr account
When you click the Authorize button Hilo launches your browser and opens the Flickr web page to authorize Hilo. The URL of this web page identifies Hilo by providing the API Key as one of the URLs GET parameters. If you are not logged in to Flickr you will be requested to login in before the authorization web page is shown, Figure 14. This page says that photos will be uploaded to your Flickr account , but before this can be done this account must authorize Hilo. Figure 14 Authorizing Hilo
117
There is no authorization callback to Hilo, so if you decide not to authorize Hilo to access your Flickr account Hilo will only find out is when it attempts to upload a photo and the access fails. So regardless of which button you click on the web page shown in Figure 14 you will see the confirmation dialog, Figure 15, which is used simply to block Hilo from uploading until authorization is complete. Figure 15 Confirming authorization
When you click the Authorization Complete button and the Flickr account has authorized Hilo, the upload process starts and progress is shown using the dialog shown in Figure 16. When the upload is complete you will see a message and a link to view the photos that you have uploaded as shown in Figure 17. Figure 16 Uploading the photo
118
The URL link is to the Flickr page which allows you to add a description and tags for the photo, Figure 18. Figure 18 Uploaded photo web page
119
Conclusion
The final versions of the Hilo applications add some additional UI features and the ability to upload photos to the Flickr photo sharing website. The Hilo Browser now supports touch screen gestures to pan and zoom photos, while the Hilo Annotator now provides Jump Lists to give quick access to recently edited files and key tasks. The Hilo Browser contains the code to upload photos to Flickr via the Flickr API web service. In the next chapter well cover the implementation of the user interface features in more detail.
120
The Hilo Browser provides two buttons: one to start the Share dialog and the other to start the Hilo Annotator application (Figure 2). Normally these two buttons appear as icons in the top right hand corner of Browser, but when you hover the mouse cursor over the icon the Browser shows the edge of the button and its caption. These two buttons are not button controls, in fact they are not even child windows, instead the images are Direct2D bitmaps and the click action is performed through hit testing. Figure 2 Hilo Browser showing the buttons to launch Annotator and Share
121
The top half of the client area for Browser is implemented by the CarouselPaneMessageHandler class. This class has two members called m_annotatorButtonImage and m_sharingButtonImage which are references to bitmap objects that implement the ID2D1Bitmap interface. Both members are initialized in the CarouselPaneMessageHandler::CreateDeviceResources method by loading the image from a bitmap resource bound to the Browser process (Figure 3). Figure 3 Icons for Browser's buttons
When the window is resized the message handler calls the CarouselPaneMessageHandler::CalculateApplicationButtonRects method to determine the position of each button image and a rectangle for the selection. When the window is redrawn the CarouselPaneMessageHandler::DrawClientArea method is called and this draws the images at these calculated positions using the ID2D1RenderTarget::DrawBitmap [http://msdn.microsoft.com/enus/library/dd371878(v=VS.85).aspx] method. However, an image on its own does not show user feedback nor handle mouse clicks. The user feedback is provided by testing to see if the mouse has hovered over the button and then drawing the selection rectangle, the following discussion is for the Annotator button but it also applies to the Share button. The first action, testing to see if the mouse is hovering over the button, is carried out in the CarouselPaneMessageHandler::CheckForMouseHover method in response to a mouse move message, and the code in CheckForMouseHover is shown in Listing 1. This code calls Direct2DUtility::HitTest which simply tests whether the mouse position is within the selection rectangle for the button. The result from the hit test is saved in the Boolean variable m_isAnnotatorButtonMouseHover which is used later on in the code. Listing 1 Hit testing for the Annotator button C++ if (Direct2DUtility::HitTest(m_annotateButtonSelectionRect.rect, mousePosition)) { if (!m_isAnnotatorButtonMouseHover) { needsRedraw = true; m_isAnnotatorButtonMouseHover = true; CalculateApplicationButtonRects(); } }
122
else { if (m_isAnnotatorButtonMouseHover) { needsRedraw = true; m_isAnnotatorButtonMouseHover = false; CalculateApplicationButtonRects(); } } When the carousel window is redrawn the m_isAnnotatorButtonMouseHover member variable is checked, and if it indicates that the mouse is hovering over the button the selection rectangle is outlined in a solid color and the rectangle is filled with the same color but with a 25% opacity as shown in Listing 2. There is no code to remove this rectangle when the mouse moves away from the button, in this situation the entire carousel window is redrawn without the selection. Listing 2 Code to draw the selection box around a button image C++ // Draw selection box for annotate button if (m_isAnnotatorButtonMouseHover) { m_selectionBrush->SetOpacity(1.0f); m_renderTarget->DrawRoundedRectangle(m_annotateButtonSelectionRect, m_selectionBrush); m_selectionBrush->SetOpacity(0.25f); m_renderTarget->FillRoundedRectangle(m_annotateButtonSelectionRect, m_selectionBrush); m_renderTarget->DrawTextLayout( D2D1::Point2(m_annotateButtonSelectionRect.rect.left, m_annotateButtonSelectionRect.rect.bottom), m_textLayoutAnnotate, m_fontBrush); } m_renderTarget->DrawBitmap(m_annotatorButtonImage, m_annotateButtonImageRect); Mouse clicks are handled in a similar way. Listing 3 shows an excerpt from the handler for the WM_LBUTTONUP [http://msdn.microsoft.com/en-us/library/ms645608(VS.85).aspx] mouse message. If the Browser history stack is expanded then button clicks are ignored, but if the history stack is not expanded then there is a check to see if the mouse is above one or other of the buttons, and if so either the MediaPaneMessageHandler::LaunchAnnotator or MediaPaneMessageHandler::ShareImages method is called. Listing 3 Handling Browser button mouse clicks C++ if (!clickProcessed) { if (m_isHistoryExpanded) { // other code } else { // Check if the user clicked the share or annotate application button if (m_isAnnotatorButtonMouseHover || m_isSharingButtonMouseHover) { ComPtr<IMediaPane> mediaPane; hr = m_mediaPane->QueryInterface(&mediaPane);
123
if (SUCCEEDED(hr)) { if (m_isAnnotatorButtonMouseHover) { mediaPane->LaunchAnnotator(); } else { mediaPane->ShareImages(); } } } } } The MediaPaneMessageHandler::LaunchAnnotator was covered in an earlier chapter [http://msdn.microsoft.com/en-us/library/ff951239.aspx] (it simply calls the Windows API function CreateProcess to start the Annotator application). The code to share photos is implemented in Browser. The MediaPaneMessageHandler::ShareImages method calls a static method on the ShareDialog class to show a modal dialog. The ShareDialog class will be covered in Chapter 15.
ullArguments
The type of gesture is identified through the dwID field of the GESTUREINFO structure (these values are shown in the following table) and the status of the gesture (whether the gesture has started or finished) is passed through the wFlags field.
124
Name GID_ZOOM
GID_PAN
GID_ROTATE
The rotation gesture. The twofinger tap gesture. The press and tap gesture.
The angle of rotation if the GF_BEGIN flag is set. Otherwise, the angle change since the rotation has started. The distance between the two fingers.
GID_TWOFINGERTAP
GID_PRESSANDTAP
The delta between the first finger and the second finger. This value is stored in the lower 32 bits of the ullArgument in a POINT structure.
Hilo provides processing for just two of these gestures: GID_PAN and GID_ZOOM. To do this there are two virtual methods OnPan and OnZoom on the WindowMessageHandler class that is the base class of the message handler class hierarchy. The WindowMessageHandler::OnMessageReceived method handles messages sent to a window and Listing 4 shows how the message is handled. At the start of the code you can see where the GetGestureInfo function is called to obtain information about the gesture and at the end is the call to the CloseGestureInfoHandle function to clean up the resources if the message is handled. Once the gesture information has been obtained, the handler code tests for the two gestures that can be handled. The handler then decodes the parameters appropriately before calling the virtual method to allow the child window message handler class to respond to the gesture. Listing 4 Code to handle gesture messages C++ case WM_GESTURE: { bool handled = false; GESTUREINFO info; info.cbSize = sizeof(info); if (::GetGestureInfo((HGESTUREINFO)lParam, &info)) { switch(info.dwID) { case GID_PAN: { D2D1_POINT_2F panLocation = Direct2DUtility::GetPositionForCurrentDPI(info.ptsLocation); hr = OnPan(panLocation, info.dwFlags); if (SUCCEEDED(hr)) { if (S_OK == hr) { handled = true;
125
} } break; } case GID_ZOOM: { static double previousValue = 1; switch(info.dwFlags) { case GF_BEGIN: hr = OnZoom(1.0f); break; case 0: hr = OnZoom(static_cast<float>(LODWORD(info.ullArguments) / previousValue)); break; } if (SUCCEEDED(hr)) { previousValue = LODWORD(info.ullArguments); if (S_OK == hr) { handled = true; } } break; } } } if (handled) { ::CloseGestureInfoHandle((HGESTUREINFO)lParam); *result = 0; } else { *result = 1; } break; }
pan. The Browser interprets a pan gesture to mean, move to the next photo in the following direction. Once the gesture has started, the Browser will always complete the action even if you change the finger movement. For example, if you pan to the left and then before removing your finger pan to the right, Browser will always pan to the left photo. The panning movement is carried out through an animation and will occur after the gesture has finished. Since the Browser will only pan one photo position at a time, it will not respond to the inertia messages for the gesture, in fact it treats them as indicating that the gesture has ended. The reason is that the Windows 7 inertia engine calculates the inertia response according to the speed of the finger movement and it could result in scrolling several photo positions. When the message is received indicating that the pan gesture has ended (or is generating inertia messages) the media pane completes the scrolling of the photos by rendering an acceleration-deceleration animation with a decelerating movement from the current position to the final position.
Conclusions
The Hilo Browser provides several UI features. This chapter explained how the Browser implements the buttons that allow you to launch the Annotator and Share applications, and it explained how the touch screen gestures were implemented. In the next chapter we will see how the Hilo applications provide jump lists and taskbar thumbnail images.
127
Chapter 14: Adding Support for Windows 7 Jump Lists & Taskbar Tabs
The Hilo Browser and Annotator support Windows 7 Jump Lists and taskbar tabs. Jump Lists provide the user with easy access to recent files and provide a mechanism to launch key tasks. Taskbar tabs provide a preview image and access to additional actions within the Windows taskbar. In this Chapter we will see how the Hilo Browser and Annotator applications implement support for Windows 7 Jump Lists and taskbar tabs.
The Windows 7 Shell API allows you to alter the Jump List in several ways. The simplest action is to add a destination to the Recent file list because in many cases Windows 7 will do this without any code, although you
128
can specifically add a destination to this list. The Shell API provides access through the ICustomDestinationList [http://msdn.microsoft.com/en-us/library/dd378395(v=VS.85).aspx] interface to allow you to customize the Jump List to add custom categories and tasks.
Windows 7 extends the concept of the Recent Items list to individual applications so that the SHAddToRecentDocs function not only adds a file to the systems Recent Items menu but also adds the file to the applications Recent Jump List. Figure 3 shows the Jump List for Annotator, the lower three items are tasks added to every Jump List and allow you to start a new instance of the application, pin the application item to the taskbar, and to close down an instance. The top items are the Recent file list and are recent files opened with Annotator. Items are added to the Recent list if your application opens a file with one of the Shell file APIs (the GetOpenFileName [http://msdn.microsoft.com/en-us/library/aa380072(v=VS.85).aspx] function or the IFileOpenDialog [http://msdn.microsoft.com/en-us/library/dd378402(VS.85).aspx] interface) or if your application explicitly calls the SHAddToRecentDocs function. Figure 3 Showing the Jump List for Annotator
Figure 3 shows that two image files have been opened recently with Annotator and Figure 2 shows that these files
129
are also displayed on the system-wide Windows 7 Recent Items menu. Since the SHAddToRecentDocs function adds files to the Recent Items list this raises the question of how Windows 7 knows that these files were opened by Annotator and not some other image editor. The answer lies in application user model IDs (or AppID for short). AppIDs are strings that identify an application and allow the Windows 7 shell to stack the tabs on the taskbar for multiple instances of the same application. Windows 7 can allocate an AppID for an application, but you can specify the string yourself which gives you greater flexibility. The API function to do this is SetCurrentProcessExplicitAppUserModelID [http://msdn.microsoft.com/en-us/library/bb761474(VS.85).aspx] and holds the current record for being the Windows 7 API function with the longest name. This function takes a single parameter which is the AppID as a Unicode string. AppIDs can be any unique string but the recommendation is to use the same format as ProgID strings, although clearly you should use a different value to your applications ProgID. Listing 1 shows the declaration of constant strings at the top of the AnnotatorApplication.cpp file which defines the ProgID and AppID strings. Listing 1 Declaration of the ProgID and AppID for Annotator C++ const std::wstring const std::wstring const std::wstring const std::wstring { L".bmp", L".dib", L".jpg", L".jpeg", L".jpe", L".jfif", L".gif", L".tif", L".tiff", L".png" }; ProgID = L"Microsoft.Hilo.AnnotatorProgID"; FriendlyName = L"Microsoft Hilo Annotator"; AppUserModelID = L"Microsoft.Hilo.Annotator"; FileTypeExtensions[] =
The SetCurrentProcessExplicitAppUserModelID function must be called before the first window is created and in Annotator this function is called in the AnnotatorApplication::Initialize method before the main window is created. This function associates the specified AppID with the application and its windows but it makes no connection between the application and the files that it can edit. That is the role of the ProgID registry setting. Listing 2 shows part of the registration that is required for Annotator to add files to the Recent file Jump List. The first few lines shows that Annotator has to be registered as a handler for the file types that it edits and Listing 2 only gives the registration for the .bmp file type (all the file types mentioned in the FileTypeExtensions array in Listing 1 will have similar values). The important point is that the file type has a string entry in its OpenWithProgIDs [http://msdn.microsoft.com/en-us/library/bb166549(VS.80).aspx] key indicating that the file can be opened with Annotator. Note that Annotator will not be the only application registered as a handler for the file type. Listing 2 Registration for Jump Lists C++ [HKEY_CLASSES_ROOT\.bmp\OpenWithProgids] "Microsoft.Hilo.AnnotatorProgID"="": [HKEY_CLASSES_ROOT\Microsoft.Hilo.AnnotatorProgID] "FriendlyTypeName"="Microsoft Hilo Annotator" "AppUserModelID"="Microsoft.Hilo.Annotator" [HKEY_CLASSES_ROOT\Microsoft.Hilo.AnnotatorProgID\CurVer] @="Microsoft.Hilo.AnnotatorProgID" [HKEY_CLASSES_ROOT\Microsoft.Hilo.AnnotatorProgID\Shell\Open\Command]
130
@="C:\\Hilo\\annotator.exe %1" The ProgID registration contains a value called AppUserModelID [http://msdn.microsoft.com/enus/library/bb775834(VS.85).aspx] that has the AppID for the application. This registration value links the AppID to the ProgID. For the shell to be able to launch an instance of the application there must be information about the location and command line format for the application. This is the purpose of the Shell\Open\Command subkey. The default value of this key contains the path and an indication that the data file should be the first command line parameter. Chapter 12 [http://msdn.microsoft.com/en-us/library/gg241211.aspx] described the RegistrationHelper utility that is called automatically when the Hilo Annotator application is first used. This utility does the registration to enable Annotator to use Jump Lists. There are three ways to load a file in Annotator: Through the command line. When you click on the Annotator button in the Hilo Browser it starts the Annotator with the full path to the currently selected photo. Through selecting another file in the Annotator image editor. When you start Annotator without a filename, the application shows all the photos in the Pictures library and you can scroll through the list of pictures for the photo you want to edit. Through the Open menu item on the main menu of Annotator. When you click the Open menu item it shows the standard File Open dialog through which you can browse for one or more files to edit. The last option in this list will use the standard File Open dialog through the IFileOpenDialog interface and when you open a file with this API the shell will automatically make a call to the SHAddToRecentDocs function. The other two methods to open a file do not use a Windows 7 API that adds the file to the Jump List and so Annotator must do this explicitly with a call the SHAddToRecentDocs. This call is made in the ImageEditorHandler::SaveFileAtIndex method, which is called when you click the Save or Save A Copy As menu items in Annotator and also when you close the application.
+ 1); externalFileName += L"annotator.exe"; JumpList jumpList(AppUserModeId); hr = jumpList.AddUserTask(externalFileName.c_str(), L"Launch Annotator", nullptr); Listing 4 shows the main code in the JumpList::AddUserTask method. First, this code creates the destination list object and identifies the Jump List to alter through a call to the ICustomDestinationList::SetAppID method. Next the code indicates that it will start changing the Jump List by calling the ICustomDestinationList::BeginList method. This method returns two items, the first is the number of items that will be shown in the Recent or Frequent destination lists (set through the Start menu properties dialog) and the other object is a collection of items that you have remove from the Jump List (by right-clicking on an item and clicking Remove from this list item on the context menu). Browser does not process the removed item list and so although it stores the array as a member of the JumpList class it does nothing with this interface pointer. The code then calls the JumpList::CreateUserTask method to add the task item before calling the ICustomDestinationList::CommitList method to complete the changes. Listing 4 Creating a new category for Browser C++ HRESULT hr = CoCreateInstance( CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_destinationList)); hr = m_destinationList->SetAppID(m_appId.c_str()); UINT cMinSlots; hr = m_destinationList->BeginList(&cMinSlots, IID_PPV_ARGS(&m_objectArray)); hr = CreateUserTask(applicationPath, title, commandLine); hr = m_destinationList->CommitList(); The JumpList::CreateUserTask method is shown in Listing 5. This code creates an object collection object (accessed through an IObjectCollection [http://msdn.microsoft.com/en-us/library/ms647591(VS.85).aspx] interface) that will contain the items to add to the Jump List. The code initializes this object by adding a shell link object to the task and then adding the collection to the user task list with a call to the ICustomDestinationList::AddUserTasks [http://msdn.microsoft.com/en-us/library/dd391704(v=VS.85).aspx] method. The user task list is a standard category called Tasks but you can create a custom category by calling the ICustomDestinationList::AppendCategory [http://msdn.microsoft.com/enus/library/dd378396(v=VS.85).aspx] method and adding the category items to the return object array. Listing 5 Creating a new category on Browser's Jump List C++ ComPtr<IObjectCollection> shellObjectCollection; HRESULT hr = CoCreateInstance( CLSID_EnumerableObjectCollection, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&shellObjectCollection)); // Create shell link first ComPtr<IShellLink> shellLink; hr = CreateShellLink(applicationPath, title, commandLine, &shellLink); hr = shellObjectCollection->AddObject(shellLink); // Add the specified user task to the Task category of a Jump List ComPtr<IObjectArray> userTask; hr = shellObjectCollection->QueryInterface(&userTask); hr = m_destinationList->AddUserTasks(userTask); Tasks are typically shell link objects because this allows you to provide the path to an application rather than a document. In Browser the shell link object is created by a call to the JumpList::CreateShellLink method which is shown in Listing 6. This code creates and initializes a shell link object (shellLink) which is returned to the calling
132
method, JumpList::CreateUserTask. The shell link object has several properties which include the path of the application that will be executed and the command line arguments passed to the application. Most of the properties are set through Set methods on the IShellLink interface, but there is no such method for the title that will be displayed by the Jump List. So to set this value the JumpList::CreateShellLink method obtains the extended properties of the shell link object by querying for the IPropertyStore [http://msdn.microsoft.com/enus/library/dd378459(VS.85).aspx] interface. The method then sets the PKEY_Title property with the task name. Properties are PROPVARIANT [http://msdn.microsoft.com/en-us/library/dd391701(v=VS.85).aspx] structures which are used in a similar fashion as VARIANT structures: they have to be initialized before use and then cleared when they are no longer needed. Listing 6 Creating a shell link object C++ ComPtr<IShellLink> shellLink; HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); // Set the path and file name of the shell link object hr = shellLink->SetPath(applicationPath); // Set the command-line arguments for the shell link object hr = shellLink->SetArguments(commandLine); // Set the name of the shell link object ComPtr<IPropertyStore> propertyStore; hr = shellLink->QueryInterface(&propertyStore); PROPVARIANT propertyValue; hr = InitPropVariantFromString(title, &propertyValue); hr = propertyStore->SetValue(PKEY_Title, propertyValue); hr = propertyStore->Commit(); hr = shellLink->QueryInterface(shellLinkAddress); PropVariantClear(&propertyValue);
133
The thumbnail is a portion of the main window of the application and by default the entire window is shown. but your code can provide a specific part of the window. In addition, you can add controls to the thumbnail window so that you can have basic access to the application even when the application is minimized. Annotator provides code to change the section of the window that is shown in the thumbnail and to add controls to allow you to scroll the images in the image editor. To see this, start Annotator on its own, either through the Start menu (type Annotator.exe in the Start menu search box and click the link returned) or through the Launch Annotator task on the Browser Jump List. In both cases Annotator will be started with more than one image in the image editor pane. At this point hover the mouse cursor over the taskbar tab for Annotator and you will see that the thumbnail for Annotator only has the image currently being edited, as shown in Figure 5. The thumbnail does not show the ribbon, and in addition, there are two buttons at the bottom of the thumbnail called Backward Button and Forward Button. When you click on either of these buttons the image in Annotator will change as if you pressed the keyboard left or right arrow keys. Figure 5 Showing the Taskbar thumbnail for Annotator
134
When you change the size of the image in the Annotator by using the zoom buttons, the image shown in the thumbnail is zoomed appropriately. The code that does most of the work is in a class called Taskbar. This class wraps access to the task bar list object that implements the ITaskbarList3 [http://msdn.microsoft.com/en-us/library/dd378403(v=VS.85).aspx] interface. The thumbnail shows the contents of a window and allows you to add child controls to the thumbnail. These controls will send command messages to a window and therefore many of the ITaskbarList3 interface methods need a HWND parameter. The Taskbar class constructor takes a HWND parameter which is stored as a member variable and this handle is passed to the object methods that need a window handle. The other important member variable is the interface pointer to the task bar list object and this is initialized by the Initialize method, which is called by all the public methods. The Taskbar::Initialize method is shown in Listing 7, this simply creates the task bar list object and initializes it. Listing 7 Creating the task bar list object C++ HRESULT Taskbar::Initialize() { HRESULT hr = S_OK; if (!m_taskbarList) { hr = CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_taskbarList)); if (SUCCEEDED(hr)) { hr = m_taskbarList->HrInit(); } } return hr; }
Thumbnail Image
135
Providing the thumbnail image is relatively straightforward: all you need to do is give the taskbar list object a rectangle that contains coordinates for the windows client area. Once you have done this, the taskbar will obtain the appropriate part of the window when necessary and draw it in the thumbnail. You do not have to do any additional drawing. In Annotator a request to redraw the application window is routed through to the ImageEditorHandler::OnRender method which calls the ImageEditorHandler::UpdateTaskbarThumbnail method. The majority of this method calculates the clipping rectangle for the current photo (in Listing 8 this rectangle is the rect variable). The code that sets the current clipping rectangle of the window to show in the thumbnail is shown in Listing 8. The Taskbar::SetThumbnailClip method simply calls the ITaskbarList3::SetThumbnailClip [http://msdn.microsoft.com/en-us/library/dd378402(VS.85).aspx] method passing the windows handle passed to the Taskbar class constructor (hWndParent the handle of the main window) and the clipping rectangle. Listing 8 Providing the thumbnail clip rectangle C++ static Taskbar taskbar(hWndParent); // Zoom the image to the thumbnail hr = taskbar.SetThumbnailClip(&rect);
Thumbnail Buttons
There are several steps needed to add a button to a thumbnail. First you need to indicate to the taskbar the images that will be used for the buttons, then you need to add the buttons and provide information like tooltip captions and an ID, and finally you have to provide code to handle the command messages generated when a button is clicked. In Annotator the Taskbar class does the work of adding buttons and their images to the taskbar and this code is called in the AnnotatorApplication::Initialize method, the code is shown in Listing 9. The Taskbar object is initialized with the handle of the main window of the Annotator application. This is used to indicate to the taskbar that the buttons will be shown for thumbnails for this particular window. The ThumbnailToolbarButton structure shown in this code is used to hold the identifier for the button and an indication as to whether the button is enabled (at this point, by default, both buttons are enabled). When you click on the button a WM_COMMAND [http://msdn.microsoft.com/en-us/library/bb774950(VS.85).aspx] message is sent to the handler window and the identifier of the button will be provided as part of the wParam of this message. Listing 9 Adding buttons to the thumbnail image C++ HWND hwnd; mainWindow->GetWindowHandle(&hwnd); Taskbar taskbar(hwnd); ThumbnailToobarButton backButton = {APPCOMMAND_BROWSER_BACKWARD, true}; ThumbnailToobarButton nextButton = {APPCOMMAND_BROWSER_FORWARD, true}; hr = taskbar.CreateThumbnailToolbarButtons(backButton, nextButton); The Taskbar::CreateThumbnailToolbarButtons method does the main work of adding the buttons to the thumbnail. The thumbnail button images are provided through a standard Windows 7 image list control and the first action of the CreateThumbnailToolbarButtons method is to initialize the task bar list object with an image list and the code to do this is provided by the Taskbar::SetThumbnailToolbarImage method shown in Listing 10. This code is straightforward; the resources of the Annotator contain two images arrow_toolbar_16.bmp and arrow_toolbar_24.bmp which are used when the system uses, respectively, 16x16 and 24x24 pixels icons. Each image contains two button images and the computers icon size setting determines which of these is used to initialize the imageList control through a call to the ImageList_LoadImage [http://msdn.microsoft.com/enus/library/bb761557(VS.85).aspx] function. Once the image list has been initialized it is associated with the taskbar thumbnail for Annotator with a call to the ITaskbarList3::ThumbBarSetImageList [http://msdn.microsoft.com/en-us/library/dd391692(v=VS.85).aspx] method. Listing 10 Setting the thumbnail image C++
136
// Get the recommended width of a small icon in pixels int const smallIconWidth = GetSystemMetrics(SM_CXSMICON); // Load the bitmap based on the system's small icon width HIMAGELIST imageList; if (smallIconWidth <= 16) { imageList = ImageList_LoadImage( HINST_THISCOMPONENT, MAKEINTRESOURCE(IDB_BITMAP_TOOLBAR_16), 16, 0, RGB(255, 0, 255), IMAGE_BITMAP, LR_CREATEDIBSECTION); } else { imageList = ImageList_LoadImage( INST_THISCOMPONENT, MAKEINTRESOURCE(IDB_BITMAP_TOOLBAR_24), 24, 0, RGB(255, 0, 255), IMAGE_BITMAP, LR_CREATEDIBSECTION); } // Add the tool bar buttons to the taskbar if (imageList) { hr = m_taskbarList->ThumbBarSetImageList(m_hWnd, imageList); } ImageList_Destroy(imageList); Once the image list for the thumbnail controls has been loaded, the Taskbar::CreateThumbnailToolbarButtons method, Listing 11, can create the buttons. This method calls the ITaskbarList3::ThumbBarAddButtons [http://msdn.microsoft.com/en-us/library/dd391705(v=VS.85).aspx] method with an array that has information about the index of the button image in the image list, the tooltip string, and the ID of the control that will be passed through the WM_COMMAND message when the button is clicked. Listing 11 Creating thumbnail buttons C++ // Set the icon/images for thumbnail toolbar buttons hr = SetThumbnailToolbarImage(); THUMBBUTTON buttons[2] = {}; // First button buttons[0].dwMask = THB_BITMAP | THB_TOOLTIP | THB_FLAGS; buttons[0].dwFlags = THBF_ENABLED | THBF_DISMISSONCLICK;; buttons[0].iId = backButton.buttonId; buttons[0].iBitmap = 0; StringCchCopyW(buttons[0].szTip, ARRAYSIZE(buttons[0].szTip), L"Backward Button"); // Second button buttons[1].dwMask = THB_BITMAP | THB_TOOLTIP | THB_FLAGS; buttons[1].dwFlags = THBF_ENABLED | THBF_DISMISSONCLICK; buttons[1].iId = nextButton.buttonId; buttons[1].iBitmap = 1; StringCchCopyW(buttons[1].szTip, ARRAYSIZE(buttons[1].szTip), L"Forward Button"); // Set the buttons to be the thumbnail toolbar hr = m_taskbarList->ThumbBarAddButtons(m_hWnd, ARRAYSIZE(buttons), buttons); Once the buttons have been created, you can change these values at a later stage by calling the ITaskbarList3:: ThumbBarUpdateButtons [http://msdn.microsoft.com/en-us/library/bb761144(VS.85).aspx] method. Annotator does this in the ImageEditorHandler::UpdateTaskbarThumbnail method (which is called when the main window is updated). In this method there is code to test the position of the current photo in the list of photos in the image editor pane. If the current photo is the first photo in the list then the Backward Button is disabled and if the photo is at the end of the list the Forward Button is disabled. This action of enabling and disabling the thumbnail buttons is carried out by calling Taskbar::EnableThumbnailToolbarButtons and the relevant code is
137
shown in Listing 12. If a button should be enabled, this method uses the THBF_ENABLED flag and if the button is to be disabled the button uses the THBF_DISABLED flag. Listing 12 Enabling thumbnail buttons C++ HRESULT Taskbar::EnableThumbnailToolbarButtons(ThumbnailToobarButton backButton, ThumbnailToobarButton nextButton) { HRESULT hr = Initialize(); if (SUCCEEDED(hr)) { THUMBBUTTON buttons[2] = {}; // First button buttons[0].dwMask = THB_BITMAP | THB_TOOLTIP | THB_FLAGS; if (backButton.enabled) { buttons[0].dwFlags = THBF_ENABLED | THBF_DISMISSONCLICK; } else { buttons[0].dwFlags = THBF_DISABLED; } buttons[0].iId = backButton.buttonId; buttons[0].iBitmap = 0; StringCchCopyW(buttons[0].szTip, ARRAYSIZE(buttons[0].szTip), L"Backward Button"); // Second button buttons[1].dwMask = THB_BITMAP | THB_TOOLTIP | THB_FLAGS; if (nextButton.enabled) { buttons[1].dwFlags = THBF_ENABLED | THBF_DISMISSONCLICK; } else { buttons[1].dwFlags = THBF_DISABLED; } buttons[1].iId = nextButton.buttonId; buttons[1].iBitmap = 1; StringCchCopyW(buttons[1].szTip, ARRAYSIZE(buttons[1].szTip), L"Forward Button"); // Update the buttons of the thumbnail toolbar hr = m_taskbarList->ThumbBarUpdateButtons(m_hWnd, ARRAYSIZE(buttons), buttons); } return hr; } When you click on the Forward Button or Backward Button the WM_COMMAND message is sent to the window associated with the thumbnail (in Annotator this is the main window). This message is routed to the image editor class and handled in the ImageEditorHandler::OnCommand method. This method calls the ImageEditorHandler::NextImage or ImageEditorHandler::PreviousImage method depending on which button is pressed. The code in Listing 12 provides the THBF_DISMISSONCLICK flag for enabled buttons to indicate that when you click on the thumbnail button, the thumbnail will disappear. If you omit this flag then the thumbnail will remain on screen and you will see the scrolling action performed within the thumbnail and the Annotator window.
Conclusions
In this chapter you have learned how to implement support for Windows 7 Jump Lists and a taskbar thumbnail for the application. In the next chapter you will see how to use the Windows 7 Web Services API to upload a photo to an online photo sharing application.
138
139
Figure 4 The display of the Share dialog when uploading has completed
140
When the user clicks the Upload button, the ShareDialog::UploadImages method creates an instance of the FlickrUploader class to perform the network access. First, the ShareDialog::UploadImages method calls the FlickrUploader::Connect method to obtain the session key (the frob value) and launch the system registered browser to show the Flickr logon page. The user logs on to the Flickr account where Hilo will upload the photos. Next the UploadImages method calls the FlickrUploader::GetToken method to get the access token associated with the account where the photos will be uploaded. Finally, a new thread is created to run the SharedDialog::ImageUploadThreadProc method asynchronously. This method uploads each photo by calling the FlickrUploader.UploadPhotos method passing the token and the path to the photo. As each photo is uploaded the progress bar is updated by one position. When each photo is uploaded Flickr returns an ID and once all photos have been uploaded the SharedDialog::ImageUploadThreadProc method creates a URL to display the uploaded images. The format of this URL is shown in Listing 1 where the ids parameter is a comma separated list of the IDs of the photos to show. This URL is provided as the link of the View Photos link control in Figure 4. Listing 1 Uploaded photos URL
http://www.flickr.com/photos/upload/edit/?ids=[comma separated list] At several stages in the upload process the code has to pause for user input. The first time this happens is when the ShareDialog::UploadImages method obtains the frob from Flickr. The UploadImages method calls the FlickrUploader::Connect method which launches the system registered browser to show the Flickr logon page and the user task switches to this browser page. During this time the UploadImages method must be paused until the logon process has completed. To do this the UploadImages method displays two Task Dialog [http://msdn.microsoft.com/en-us/library/bb787471(v=VS.85).aspx] windows (created by calling the TaskDialogIndirect [http://msdn.microsoft.com/en-us/library/bb760544(v=VS.85).aspx] function). Figure 5 Dialog informing the user that they must authorize Hilo
141
The first dialog (Figure 5) informs the user that they will have to authorize the Hilo applications access to their account. This lets the user know what is about to happen, and gives the user the opportunity to cancel the operation. When the user clicks the Authorize button on this first dialog, the FlickrUploader::Connect method is called to display the logon page and the second dialog (Figure 6) is shown in the background. This modal dialog blocks the UploadImages method and the user will see this dialog when they have finished the logon procedure and switched back to Browser. At this point the user can click the Authorization Complete button to unblock the UploadImages method and allow it to upload the photos by calling SharedDialog:: ImageUploadThreadProc method on a worker thread. Figure 6 Dialog used to block Browser until the user has authorized the Hilo Flickr application
Uploading a Photograph
A photo can be several megabytes of data. The Flickr API for uploading photos involves a multi-part HTTP POST request to the http://api.flickr.com/services/upload/ URI. Each part of the message is one of the arguments [http://www.flickr.com/services/api/upload.api.html] that describe the upload action. The action must be authenticated and authorized, and so the API key and the access token must be provided along with a message digest generated from them. The message digest (Flickr calls it the signature) is a hash generated from the method parameters and the secret known only by the Flickr application and Flickr. The digest is used by Flickr to determine the integrity of the parameters so that it can detect if the request has been altered en route, either
142
maliciously or by accident. The final part of the message is the photo provided as binary data. Listing 2 shows the general format of the POST request (the items in square brackets will be replaced with actual data). Listing 2 Example POST message to upload a photo C++ POST /services/upload/ HTTP/1.1 Content-Type: multipart/form-data; boundary=--EBA799EB-D9A2-472B-AE86-568D4645707E Host: api.flickr.com Content-Length: [data_length] --EBA799EB-D9A2-472B-AE86-568D4645707E Content-Disposition: form-data; name="api_key" [api_key_value] --EBA799EB-D9A2-472B-AE86-568D4645707E Content-Disposition: form-data; name="auth_token" [token_value] --EBA799EB-D9A2-472B-AE86-568D4645707E Content-Disposition: form-data; name="api_sig" [api_sig_value] --EBA799EB-D9A2-472B-AE86-568D4645707E Content-Disposition: form-data; name="photo"; filename="[filename]" [image_binary_data] --EBA799EB-D9A2-472B-AE86-568D4645707E
us/library/aa384110(v=VS.85).aspx] function passing the request handle. This function can be used to provide additional HTTP headers not provided by calls to WinHtttpAddRequestHeaders and any additional data required when the call is made for a PUT or POST request. The server will respond to the request, and you call the WinHttpReceiveResponse [http://msdn.microsoft.com/en-us/library/aa384105(v=VS.85).aspx] function with the request handle for the system to read the response headers from the server (which can be obtained by calling the WinHttpQueryHeaders [http://msdn.microsoft.com/en-us/library/aa384102(v=VS.85).aspx] function). Once the server has responded you can receive data from the server. To do this you call the WinHttpQueryDataAvailable [http://msdn.microsoft.com/en-us/library/aa384101(v=VS.85).aspx] function with the request handle to receive the number of bytes that can be downloaded and then call the WinHttpReadData [http://msdn.microsoft.com/en-us/library/aa384104(v=VS.85).aspx] function to read the data. If the request is made synchronously, the WinHttpQueryDataAvailable function will block until there is data available.
} SendWebRequest(&request, token, fileName); outputString = GetPhotoId(&request, errorFound); // Clean up if (proxyInfo.lpszProxy) { GlobalFree(proxyInfo.lpszProxy); } if (proxyInfo.lpszProxyBypass) { GlobalFree(proxyInfo.lpszProxyBypass); } WinHttpCloseHandle(request); WinHttpCloseHandle(connect); WinHttpCloseHandle(session); return outputString; } The FlickrUploader class will upload the photo though a web call that will be made through a proxy if one is set up for the local network. To do this, the UploadPhotos method calls the WinHttpGetProxyForUrl [http://msdn.microsoft.com/en-us/library/aa384097(v=VS.85).aspx] function to receive the contents of the Proxy Auto-Configuration (PAC) file discovered using DHCP and DNS queries. The results are returned in the proxyInfo variable, which has a proxy server list and a proxy bypass list, that are allocated on the system global heap.These fields are freed with calls to the GlobalFree function when the method completes. The proxy information is associated with the request by calling the WinHttpSetOption [http://msdn.microsoft.com/enus/library/aa384114(v=VS.85).aspx] method. At this point the method can make the web request by calling the FlickrUploader::SendWebRequest method shown in Listing 4. The majority of the SendWebRequest method is to construct the headers of the HTTP POST request. The upload parameters have to be signed with the Flickr API secret and so the first part of the SendWebRequest method concatenates the secret, the API key, and the token. It then generates a MD5 hash of this string by calling the FlickrUploader::CreateMD5Hash method (which is described later). This header is added to the request by calling WinHttpAddRequestHeaders. Listing 4 Making the web request C++ int FlickrUploader::SendWebRequest( const HINTERNET *request, const std::wstring& token, const std::wstring& fileName) { static const char* mimeBoundary = "EBA799EB-D9A2-472B-AE86-568D4645707E"; static const wchar_t* contentType = L"Content-Type: multipart/form-data; boundary=EBA799EB-D9A2-472B-AE86-568D4645707E\r\n"; // Parameters put in alphabetical order to generate the api_sig std::wstring params = flickr_secret; params += L"api_key"; params += flickr_api_key; params += L"auth_token"; params += token; std::wstring api_sig = CalculateMD5Hash(params); int result = ::WinHttpAddRequestHeaders( *request, contentType, (unsigned long)-1, WINHTTP_ADDREQ_FLAG_ADD); if (result) { std::wostringstream sb;
145
sb << L"--" << mimeBoundary << L"\r\n"; sb << L"Content-Disposition: form-data; name=\"api_key\"\r\n"; sb << L"\r\n" << flickr_api_key << L"\r\n"; sb << L"--" << mimeBoundary << L"\r\n"; sb << L"Content-Disposition: form-data; name=\"auth_token\"\r\n"; sb << L"\r\n" << token << L"\r\n"; sb << L"--" << mimeBoundary << L"\r\n"; sb << L"Content-Disposition: form-data; name=\"api_sig\"\r\n"; sb << L"\r\n" << api_sig.c_str() << L"\r\n"; sb << L"--" << mimeBoundary << L"\r\n"; sb << L"Content-Disposition: form-data; name=\"photo\"; filename=\"" << fileName << L"\"\r\n\r\n"; // Convert wstring to string std::wstring wideString = sb.str(); int stringSize = WideCharToMultiByte(CP_ACP, 0, wideString.c_str(), -1, nullptr, 0, nullptr, nullptr); char* temp = new char[stringSize]; WideCharToMultiByte(CP_ACP, 0, wideString.c_str(), -1, temp, stringSize, nullptr, nullptr); std::string str = temp; delete [] temp; // Add the photo to the stream std::ifstream f(fileName, std::ios::binary); std::ostringstream sb_ascii; sb_ascii << str; sb_ascii << f.rdbuf(); sb_ascii << "\r\n--" << mimeBoundary << "\r\n"; str = sb_ascii.str(); result = WinHttpSendRequest( *request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, (void*)str.c_str(), static_cast<unsigned long>(str.length()), static_cast<unsigned long>(str.length()), 0); } return result; } The remainder of the method adds the parameters to the request, a multi-part MIME message built as an ASCII string. First, the parts that indicate the API key, the token, and the signature are added using a Unicode buffer that is converted to an ASCII buffer before the contents of the photo are added as binary data. Finally the actual request is made to the server by calling the WinHttpSendRequest function. Once the FlickrUploader::UploadPhotos method has made the request to the server it calls FlickrUploader::GetPhotoId to read the response from the server as shown in Listing 5. The first action is to call the WinHttpReceiveResponse method, which will block until the server sends the response. The GetPhotoId method then calls the WinHttpQueryHeaders function to get all the response headers. These headers are read in a single buffer because they are not needed, what is needed is the data that follows the response headers and these are obtained by calling the WinHttpReadData function. Listing 5 Retrieving data from a web request C++ std::wstring FlickrUploader::GetPhotoId(const HINTERNET *request, bool* errorFound) { std::wstring outputString; int result = ::WinHttpReceiveResponse(*request, nullptr); unsigned long dwSize = sizeof(unsigned long);
146
if (result) { wchar_t headers[1024]; dwSize = ARRAYSIZE(headers) * sizeof(wchar_t); result = ::WinHttpQueryHeaders( *request, WINHTTP_QUERY_RAW_HEADERS, nullptr, headers, &dwSize, nullptr); } if (result) { char resultText[1024] = {0}; unsigned long bytesRead; dwSize = ARRAYSIZE(resultText) * sizeof(char); result =::WinHttpReadData(*request, resultText, dwSize, &bytesRead); if (result) { // Convert string to wstring int wideSize = MultiByteToWideChar(CP_UTF8, 0, resultText, -1, 0, 0); wchar_t* wideString = new wchar_t[wideSize]; result = MultiByteToWideChar(CP_UTF8, 0, resultText, -1, wideString, wideSize); if (result) { std::wstring photoId = GetXmlElementValueByName(wideString, L"photoid", errorFound); if (!(*errorFound)) { outputString = photoId; } } delete [] wideString; } } return outputString; } The response from the server will be an XML string. The FlickrUploader::GetXmlElementValueByName method uses the XMLLite API to create an XML reader object by calling the CreateXmlReader [http://msdn.microsoft.com/en-us/library/ms752822(VS.85).aspx] function and then iterating over every element until it finds the photoid element and returns the value of the element as the ID of the photo just uploaded. The SharedDialog::ImageUploadThreadProc method stores these IDs, and then when all photos have been uploaded it creates a URL to the page that display the uploaded images.
To open a provider you call the BCryptOpenAlgorithmProvider [http://msdn.microsoft.com/enus/library/aa375479(v=VS.85).aspx] function and provide the names of the algorithm, the provider, and any appropriate flags. The function returns a BCRYPT_ALG_HANDLE handle, which you can use to get or set the properties of the algorithm. When you have finished with the provider you must close it by passing the handle to the BCryptCloseAlgorithmProvider [http://msdn.microsoft.com/en-us/library/aa375377(v=VS.85).aspx] function. You can alter how a cryptographic algorithm works, or obtain information about the algorithm through properties. To obtain a property you call the BCryptGetProperty [http://msdn.microsoft.com/enus/library/aa375464(v=VS.85).aspx] function, passing the handle for an open provider, the name of the property [http://msdn.microsoft.com/en-us/library/aa376211(v=VS.85).aspx] , and a pointer to a caller allocated buffer to receive the property value. The function also has an in/out parameter for the size of the buffer. Some properties are of a known size (for example BCRYPT_KEY_LENGTH which returns the size of the cryptographic key as a 32bit value). Others are not known and so you can call BCryptGetProperty with a NULL value for the pointer to the buffer and the function will return the required buffer size through the pointer used to indicate the buffer size. Changing a property is similar: you call the BCryptSetProperty [http://msdn.microsoft.com/enus/library/aa375504(v=VS.85).aspx] function passing the provider handle, the name of the property, and a pointer to the buffer containing the new property value and use a parameter to indicate the size of this buffer. Once you have set the properties of the algorithm you may call one of the CNG functions to perform the cryptographic action. If you wish to encrypt or decrypt data then you will need to provide a key. To do this you can either create a key with a call to the BCryptGenerateSymmetricKey [http://msdn.microsoft.com/enus/library/aa375453(v=VS.85).aspx] function to create a symmetric (or secret) key or the BCryptGenerateKeyPair [http://msdn.microsoft.com/en-us/library/aa375451(v=VS.85).aspx] functions to create the asymmetric (or public-private) key pair. These functions return a BCRYPT_KEY_HANDLE handle and when you have finished with the handle you release the associated resources by calling the BCryptDestroyKey [http://msdn.microsoft.com/en-us/library/aa375404(v=VS.85).aspx] function. The key can then be passed, along with buffers for the input and output values to BCryptEncrypt [http://msdn.microsoft.com/enus/library/aa375421(v=VS.85).aspx] to encrypt data or to BCryptDecrypt [http://msdn.microsoft.com/enus/library/aa375421(v=VS.85).aspx] to decrypt data. Creating a hash is a cryptographic operation and you can provide a key to be used by the algorithm, but this is optional. Before you can hash data you have to create a hash object by calling the BCryptCreateHash [http://msdn.microsoft.com/en-us/library/aa375383(v=VS.85).aspx] function, which returns a hash object handle that must be closed with a call to the BCryptDestroyHash [http://msdn.microsoft.com/enus/library/aa375399(v=VS.85).aspx] function when you have finished performing the hash operation. You perform the hash by calling the BCryptHashData [http://msdn.microsoft.com/en-us/library/aa375468(v=VS.85).aspx] function passing the handle to the hash object and a pointer to a buffer with the data to hash (and a parameter indicating the size of the buffer). This function does not return the hashed data, in fact the data passed to the function is not modified, instead, the hash is maintained in memory by the hash object. The reason for this is you can call the BCryptHashData function more than once to hash the hashed data. To obtain the hashed value you call the BCryptFinishHash [http://msdn.microsoft.com/en-us/library/aa375443(v=VS.85).aspx] function passing the handle to the hash object and a user allocated buffer and its size, the function copies the hash from the hash object into the buffer. The size of the buffer is provided through the BCRYPT_HASH_LENGTH property.
std::wstring FlickrUploader::CalculateMD5Hash(const std::wstring& buffer) { // Convert wstring to string std::string byteString(buffer.begin(), buffer.end()); // Open an algorithm handle BCRYPT_ALG_HANDLE algorithm = nullptr; BCryptOpenAlgorithmProvider(&algorithm, BCRYPT_MD5_ALGORITHM, nullptr, 0); // Calculate the size of the buffer to hold the hash object unsigned long dataSize = 0; unsigned long hashObjectSize = 0; BCryptGetProperty( algorithm, BCRYPT_OBJECT_LENGTH, (unsigned char*)&hashObjectSize, sizeof(unsigned long), &dataSize, 0); // Allocate the hash object on the heap unsigned char* hashObject = nullptr; hashObject = (unsigned char*) HeapAlloc(GetProcessHeap (), 0, hashObjectSize); // Calculate the length of the hash unsigned long hashSize = 0; BCryptGetProperty( algorithm, BCRYPT_HASH_LENGTH, (unsigned char*)&hashSize, sizeof(unsigned long), &dataSize, 0); // Allocate the hash buffer on the heap unsigned char* hash = nullptr; hash = (unsigned char*)HeapAlloc (GetProcessHeap(), 0, hashSize); // Create a hash BCRYPT_HASH_HANDLE cryptHash = nullptr; BCryptCreateHash(algorithm, &cryptHash, hashObject, hashObjectSize, nullptr, 0, 0); The next part of the CalculateMD5Hash method is shown in Listing 7. This code calls the BCryptHashData function to hash the data passed as the second parameter. The hash object retains the actual hash which is then obtained by calling the BCryptFinishHash function passing the buffer previously allocated to hold the hash. Finally this binary data is converted to a hex string. Listing 7 Hashing the data C++ // Hash data BCryptHashData( cryptHash, (unsigned char*)byteString.c_str(), static_cast<unsigned long>(byteString.length()), 0); // Close the hash and get hash data BCryptFinishHash(cryptHash, hash, hashSize, 0); std::wstring resultString; // If no issues, then copy the bytes to the output string std::wostringstream hexString; for (unsigned short i = 0; i < hashSize; i++) { hexString << std::setfill(L'0') << std::setw(2) << std::hex << hash[i]; } resultString = hexString.str();
The last part of the CalculateMD5Hash method is shown in Listing 8. This shows the cleanup that is needed to
149
release the hash object and the cryptographic provider and to release the previously allocated memory. Listing 8 Cleaning up the objects and buffers used by the method C++ // Cleanup BCryptCloseAlgorithmProvider(algorithm, 0); BCryptDestroyHash(cryptHash); HeapFree(GetProcessHeap(), 0, hashObject); HeapFree(GetProcessHeap(), 0, hash); return resultString; }
Conclusion
In this chapter you have seen how the Hilo Browser uploads photos to the Flickr web site using the HTTP Services API to make a HTTP POST request, and how Hilo uses task dialogs to provide visual feedback about the upload operation to the user. You have also seen how the CNG functions can be used to create cryptographic hashes used by Flickr to verify the integrity of the parameters passed to it by a client. In the next chapter you will see how Hilo uses the Windows 7 Web Services API to call the Flickr Web Service to obtain an authentication session key and an access token.
150
The Flickr API allows an application to access a Flickr account. There are two players in this transaction: the Flickr application and the user account, and both need to have a Flickr account. In Hilo this means that you have to create a Flickr account for Hilo and obtain a Flickr API key that identifies Hilo to Flickr. Hilo uploads photos to a user specified account and as part of this mechanism the user has to authorize Hilo and indicate to Flickr that Hilo can access the specified account. This involves several authentication and authorization steps. When you apply for a Flickr API key (see Chapter 12 [http://msdn.microsoft.com/en-us/library/gg241211.aspx] ) you will be given two strings: one is the API key and the other is a secret;. The API key identifies the user: the Hilo Flickr application. The secret is known only by you and Flickr and is used to create message digests (as explained in the last chapter) so that Flickr can verify that the parameters were not corrupted (either maliciously, or by accident) during transmission. Before Hilo can upload a photo it has to logon to Flickr using its API key. To do this Hilo has to call the flickr.auth.getFrob [http://www.flickr.com/services/api/flickr.auth.getFrob.html] web service method with the API key as the parameter. This method returns a logon session key called a frob. The frob identifies the Hilo application just for the current session and is only valid for 60 minutes, but rather than holding onto a frob until it becomes invalid, Hilo takes the simpler path of obtaining a new frob every time the user wishes to upload a photo. Hilo will upload photos to a Flickr account and so Flickr must be informed that all actions performed with the current session (identified through the frob) must occur on a specified account. The Hilo application does not know what Flickr account should be used when uploading photos, nor should it, Hilo should only upload using the current session and allow Flickr to determine where the data is stored. The way to do this is to create a Flickr logon URL containing the frob and the API key and launch this URL in a browser. The user is then able to log on to whatever account they wish and authorize the Hilo Browser to have access to the account in the current session. Once the user has authorized the Hilo Browser to access the account, you need to obtain an access token to upload photos. To do this you call the flickr.auth.getToken [http://www.flickr.com/services/api/flickr.auth.getToken.html] web service method passing the API key and the frob as parameters and receive back an access token.
elements) that will be used to make the request and receive the responses. In the Flickr.wsdl file two ports are described. The abstract portType elements are FlickrFrobRequestPort and FlickrTokenRequestPort which give details of the request, response, and fault messages used when accessing the ports (these definitions are the same, since the same request and response messages are used by both web service methods). In addition to the message formats, these portType elements give the name of the method to call (flickr.auth.getFrob and flickr.auth.getToken). These definitions are all abstract, they define the format of the request and response but they do not give details about the protocol, nor the actual network endpoint. The binding, port and service elements are concrete in that they contain information about the actual protocols and endpoints that will be used. The binding elements, FlickrFrobRequestPortBinding and FlickrTokenRequestPortBinding reference the portType definition and provide information about the protocol that will be used. The binding is a concrete specification of the protocol and data format used for a particular portType. The service element contains the concrete definition of the collection of related endpoints, and each endpoint is given as a port element. In the Flickr.wsdl file the flickr.auth.getFrob and flickr.auth.getToken methods are declared as being part of two separate services, FlickrFrobRequestPortService and FlickrTokenRequestPortService respectively. Each service definition is a collection of port elements and each port element provides the binding element for a method and the endpoint where that web service method is implemented.
only responsibility is to create the heap object before you make any web service calls by calling the WsCreateHeap [http://msdn.microsoft.com/en-us/library/dd430499(v=VS.85).aspx] function and to release the heap object with a call to the WsFreeHeap [http://msdn.microsoft.com/en-us/library/dd430527(v=VS.85).aspx] function when you have completed all web service calls. The WSDL compiler creates functions to make the actual calls to the web service methods, with names derived from the binding elements and the method they bind to. For Flickr.wsdl the compiler creates two functions FlickrFrobRequestPortBinding_flickr_auth_getFrob and FlickrTokenRequestPortBinding_flickr_auth_getToken. These functions have parameters for the service proxy, the in parameters (used to make the request), pointers to buffers for the out parameters (for the response), handles for the heap object, and the error object that will be used.
}; if(SUCCEEDED(hr)) { hr = WsCreateServiceProxy( WS_CHANNEL_TYPE_REQUEST, WS_HTTP_CHANNEL_BINDING, nullptr, nullptr, 0, channelProperties, ARRAYSIZE(channelProperties), proxy, // out parameter, returns the proxy *error); } A web service is implemented on an endpoint so you have to call the WsOpenWebServiceProxy [http://msdn.microsoft.com/en-us/library/dd430577(v=VS.85).aspx] function to provide this endpoint to the proxy. The code to do this in the CreateWebProxy method is shown in Listing 2. Listing 2 Opening the proxy C++ static const wchar_t* flickr_soap_endpoint_url = L"http://api.flickr.com/services/soap/"; WS_ENDPOINT_ADDRESS address = { { static_cast<unsigned long>(wcslen(flickr_soap_endpoint_url)), const_cast<wchar_t*>(flickr_soap_endpoint_url) } }; if(SUCCEEDED(hr)) { hr = WsOpenServiceProxy(*proxy, &address, nullptr, *error); } After you have created the web service proxy you can make calls to the web service by calling the WsCall [http://msdn.microsoft.com/en-us/library/dd430485(v=VS.85).aspx] function. The WsCall function has parameters that describe the web service operation to call and the parameters to be passed to the service. The function will return the results from calling the web service. The call to the WsCall method is provided by the functions generated by the wsutil tool from the WSDL file. The CreateWebProxy method creates a heap and error object as well as the proxy object, and all of these are used to make calls to the web service. All of these objects allocate resources that must be released when you no longer need to use the proxy and this is done by calling the CloseWebProxy method, Listing 3. Listing 3 Releasing resources C++ void FlickrUploader::CloseWebProxy (WS_HEAP** heap, WS_SERVICE_PROXY** proxy, error) { if (proxy != nullptr && *proxy != nullptr) { WsCloseServiceProxy(*proxy, nullptr, nullptr); WsFreeServiceProxy(*proxy); } if (heap != nullptr && *heap != nullptr) { WsFreeHeap(*heap); }
155
WS_ERROR**
proxy, &request, &token, heap, nullptr, 0, nullptr, error); if (SUCCEEDED(hr)) { bool errorFound = false; std::wstring value = GetXmlElementValueByName(token, L"token", &errorFound); if (!errorFound) { outputString = value; } } } The GetToken method then calls the FlickrTokenRequestPortBinding_flickr_auth_getToken function that was created by the wsutil tool. This function makes the call to the WSSAPI WsCall function and returns the results as a SOAP packet. Finally, the GetToken method calls the GetXmlElementValueByName method that uses XmlLite to obtain the value of the token element that is returned from the GetToken method.
Conclusion
In this chapter you saw how to use the Windows 7 Web Services API to make requests to the Flickr web services in order to authenticate and authorize the Hilo Browser so that it can upload the selected photos. This chapter concludes this series of articles. In this series, weve described how the Hilo Browser and the Hilo Annotator were implemented using some of the powerful features and APIs provided by Windows 7. Weve described how to design a touch-enabled user interface, and how to implement it using Direct2D, the Windows Animation Manager, and the Windows Ribbon. Weve described how to integrate the application into the Windows Shell and how to use the Windows Imaging Component to manipulate images. And finally weve described how to share photos using HTTP and web services. We hope you have enjoyed this series of articles, and we hope that they will help you to build rich, compelling Windows applications of your own.
157