![]() |
For this snip I thought I'd try something a little more complicated, and ended up with something a lot more complicated. This snip started as a simple, game (i.e., DirectX) oriented window library. It still is, but it's a just a bit more complicated than I thought. A few words of warning: I make use of the Loki (for Singletons) and boost (for smart pointers) libraries here, in addition to some standard libraries. You'll need those libraries to use what's here. Also, I make an assumption that you already have some background in Windows programming. I don't explain things like window handles or messages. If you need help with these topics, then I recommend picking up a copy of Charles Petzold's Programming Windows.
Throughout this snip I've tried to focus more on using the wrapper rather than the implementation. For the gory details of how it works, download the code and take a look.
Here's the problem: for Windows programs, every program needs a main window (well, there's exceptions, but we can ignore them for this snip). Depending on your compiler, there's really two ways to do this:
Normally I prefer to use a built-in library: they're well-tested, feature-rich, and let me do almost anything I want. It's the "almost" part that got me started down the path of choosing option #2.
The problem I have with the libraries is two-fold. The first is control: the libraries force you into a particular programming model, wherein you instantiate their classes and all the Windows stuff happens behind the scenes. Normally this is a good thing, but in game programming you generally want control over the main windows loop. The second problem is their feature-richness. Again, normally this is a good thing, but for a game this is entirely too much weight to drag around; usually those features just aren't needed, and the ones that are usually require you to jump through some hoops to really get them the way you want without killing performance. So I started down the only reasonable path open to me: rolling my own.
Rolling your own really requires you to take care of three things: creating the window, handling messages, and a main windows loop. Each are addressed below.
If you've played any games, you've noticed some that run full screen, and some that run windowed, and some that let you switch back and forth. Ignoring the DirectX aspects of this, the windows we create are going to have to handle these cases.
"But," you ask, "I don't want to switch modes, I always want to be full screen." We can handle those cases, too, by using policies.
Policies are just a way of using C++ to specify design. The idea of a policy is that, given a set of mutually exclusive options, which one do I use for this program? The idea is that you select the options in your code, and only "pay for" the options you use - the alternatives don't get compiled in. Moreover, they're easy enough to switch that if you change your options (say, you debug with one set and release with another), it's a minimal change to your code to implement the new policy. In fact, usually it's just a template parameter. For more information on policy-based programming (and a whole lot more), check out Andrei Alexandrescu's Modern C++ Design.
So, for our purposes, we have two possible policies: whether or not the window is full screen, and whether or not the window can be switched between full screen and windowed mode. Each policy will become a class. The policies will be tied together with a class template that knows how to use the policies, and some generic stuff will be added to a base class of the class template. Here are the policies:
| Creation Policies | |
| class WindowedCreator | Creates a non-sizeable window, with a caption and close button |
| class FullscreenCreator | Creates a full screen window with no caption |
| Switching Policies | |
| class NoModeSwitch | Mode switching is not allowed |
| class ModeSwitch | Mode switching is alllowed |
These policies are wrapped in a class template called ApplicationWindow. Here's it's declaration:
template <typename CreationPolicy, typename SwitchingPolicy>
class ApplicationWindow:
public WindowBase,
public CreationPolicy,
public SwitchingPolicy
{
public:
ApplicationWindow()
:WindowBase(),
SwitchingPolicy(CreationPolicy::FullscreenMode) {}
virtual void create(void)
{
hwnd( create_window(this, icon()) );
reset_position();
::ShowWindow(hwnd(), SW_SHOW);
::UpdateWindow(hwnd());
}
virtual void adjust(bool fullscreen, unsigned width, unsigned height)
{
adjust_window(hwnd(), fullscreen, width, height);
reset_position();
::ShowWindow(hwnd(), SW_SHOW);
}
private:
ApplicationWindow(const ApplicationWindow &disabled);
ApplicationWindow& operator=(const ApplicationWindow &disabled);
};
As you can see, the template itself is pretty simple. It includes only two methods: create(), for creating the window, and adjust(), for switching modes. If the NoModeSwitch policy is selected, then calling this function is an error. There's also a WindowBase class mentioned as a base class; I'll get to that later. The most important parts of it to know now is that it provides the reset_position() and hwnd() functions.
So how do you use this? Here's a simple example:
ApplicationWindow<WindowedCreator, NoModeSwitch> win; win.create();
This creates a non-switchable, windowed window for display. We need to handle steps two and three before we can actually display it.
For details of the policy implementations, see the code. Here's some important details for writing your own policies:
That's it for the window wrapper. Note that since the template inherits from the policies, the policies can add some functionality to the window. For example, the WindowedCreator policy provides an init_size() method to allow the user to initialize the size of the window prior to create() being called.
So now we've wrapped up window creation, except for one little piece. The operating system requires that each window have a window procedure. This procedure receives and processes messages from the operating system. This procedure can't be a class method, it must be a standalone function or a static class method. This is where step #2 comes in.
Most of the next two steps are handled using this Singleton class:
class WindowManagerImpl;
class WindowManager
{
public:
static LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
// window procedure used by all windows
bool service_msg_queue(bool active) const;
// the message pump; returns true if a quit message is pulled off the queue. This
// function uses PeekMessage if active is true, GetMessage if it's false
private:
friend class Loki::CreateUsingNew<WindowManager>;
WindowManager();
~WindowManager();
WindowManager(const WindowManager &disabled);
WindowManager& operator=(const WindowManager &disabled);
boost::scoped_ptr<WindowManagerImpl> impl;
};
typedef Loki::SingletonHolder<WindowManager> SingletonWindowManager;
There isn't much to this class, since most of the interesting bits are handled behind the scenes. For now I'll concentrate on the WindowProc() method.
As I said earlier, Windows requires each window to have a window procedure. What we want is for each window to be able to handle their own messages, and for users of this window to add their own handlers in a way that doesn't require them to derive from the ApplicationWindow template they instantiate. But Windows doesn't let us do that directly, since the window procedure must be a standalone function or a static class method. In our case, we'll use the static class method WindowProc to handle and route messages to the appropriate windows. Each window is then free to route messages to registered message handlers.
There's a lot of code that goes into the WindowProc, but there's not really a lot to it. It really does three things:
The real complexity here is in the WM_CREATE, in that it requires a WindowBase-derived object pointer to be passed in with the CREATESTRUCT, which must be set up during the ::CreateWindowEx() call. That's all wrapped for you though, so it's easy!
There's a potential bottleneck here. Whenever a message is received, I need to look up the associated window handle in the message router to find the WindowBase-derived object that will handle it. I've used a std::hash_map container from the Visual C++ .net library. If you don't have a hash_map in your implementation, you can just use a std::map instead, but it will be slower if you have a lot of windows.
WindowBase is the base class for the ApplicationWindow template discussed earlier. It enables all this message management to work, by providing it's own window procedure, and allowing other users to register their own message handlers. Here's the relevant parts of it's declaration:
class WindowBaseImpl;
class WindowBase
{
public:
WindowBase();
virtual ~WindowBase();
HWND hwnd(void) const;
// window handle
virtual LRESULT message_handler(HWND, UINT, WPARAM, LPARAM);
// processes windows messages
void register_handler(UINT msg, boost::shared_ptr<AbstractMessageHandler> handler);
// registers an object to handle the specified message
void unregister_handler(UINT msg, boost::shared_ptr<AbstractMessageHandler> handler);
// unregisters an object for handling the specified message
private:
WindowBase(const WindowBase &disabled);
WindowBase& operator=(const WindowBase &disabled);
boost::scoped_ptr<WindowBaseImpl> impl; // hidden implementation
};
I've cut out a lot of the utility methods that let you access the size and position of the window, among others, because I want to focus on message handling. The message_handler() method receives messages passed on by the WindowManager::WindowProc() method, and disseminates them accordingly. Some of those messages, such as WM_SIZE and WM_ACTIVATE, are handled locally since they provide information vital to the proper operation of the class. Other messages are routed to message handlers, registered through register_handler().
register_handler() allows you to register any AbstractMessageHandler derived class to be called on to process any message. unregister_handler() let's you change your mind. Here's it's interface:
class AbstractMessageHandler
{
public:
virtual ~AbstractMessageHandler() {}
virtual bool message_handler(HWND, UINT, WPARAM, LPARAM, LRESULT &) = 0;
};
Note here that this is only an interface; it's up to the derived classes to implement the message_handler() method. It returns true if it handles the message, and provides a return value for the message in the fifth (reference) parameter.
Why would you want this facility? Suppose, for example, you're going to have more than one window, with one of them being the main window. When that window closes, you want to quit the application. So you might define a message handler that processes WM_DESTROY by posting a quit message, something like:
class QuitOnDestroy: public AbstractMessageHandler
{
public:
virtual bool message_handler(HWND, UINT, WPARAM, LPARAM, LRESULT &result)
{
::PostQuitMessage(0);
result = 1;
return true;
}
};
You could then register an object of this class using something like:
win.register_handler(WM_QUIT, boost::shared_ptr<QuitOnDestroy>(new QuitOnDestroy));
Since I expect that this will be a pretty common operation, I've provided a template function to streamline the syntax. To register an AbstractMessageHandler-derived object, you can use the register_handler() template function, like so:
register_handler<QuitOnDestroy>(win, WM_QUIT);
Now that we've gotten through all the complicated stuff, the main loop is all that's left, and it's pretty easy.
The windows main loop, also called the message pump, exists to service the message queue's of all the windows in your program. Normally it's an infinite loop that you write once an ignore. For a game, though, this isn't ideal - it'd be much better to poll the queue to see if anything needs to be done, and if not, then to go do some game stuff. That's the idea behind the service_msg_queue() method of the WindowManager class.
Here's the idea. In your application, call service_msg_queue() whenever you're at the top of your main loop. It'll process the message queue and return true if a quit message was encountered. If your window is active (i.e., it isn't hidden behind other windows), it'll use ::PeekMessage() to see if there's any messages to service; ::PeekMessage() returns immediately so you don't have to wait if there aren't any messages. If your window is not active, then it uses ::GetMessage(), which waits for a message to arrive before returning.
Here's a simple program that ties all this together to put a very basic window on the screen:
#include <boost/shared_ptr.hpp>
#include "winmanager.h"
using namespace winwrap;
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
ApplicationWindow<WindowedCreator, NoModeSwitch> win;
register_handler<QuitOnDestroy>(win, WM_DESTROY);
win.create();
while ( !SingletonWindowManager::Instance().service_msg_queue(true) ) {}
return 0;
}
In case you missed it earlier, here's where you can get the code. Questions, comments? Send 'em on!
| Latest Releases |
| Webifier 1.1.0 - 11/14/2007 - Show your pictures to the world with this web photo gallery creator. |
| PictureSaver 4.2.5 - 03/10/2006 - Turn your pictures into a slideshow screensaver; great with digital cameras! |
| RandomSaver 2.0.3 - 06/14/2002 - Got a lot of screen savers? Why watch just one? RandomSaver lets you watch 'em all! |
| GullBlaster 1.1 - 05/12/2002 - Tired of them seagulls? Now you can get even! Blast them before they blast you! |
| FontViewer 1.1.1 - 06/13/2002 - Preview all the installed fonts on your system. |
| Monitor page for changes |
|
powered by ChangeDetection |