📄 2control.html
字号:
<html>
<head>
<title>Windows Techniques</title>
<meta name="description" content="Introduction to Windows programming">
<meta name="keywords" content="windows, programming, window, class, message loop">
<link rel="stylesheet" href="rs.css" tppabs="http://www.relisoft.com/book/rs.css">
</head>
<body background="margin.gif" tppabs="http://www.relisoft.com/book/images/margin.gif" bgcolor="#ffffe0">
<!-- Main Table -->
<table cellpadding="6">
<tr>
<td width="78">
<td>
<h2>Controlling Windows through C++</h2>
<h3>Creating a Namespace</h3>
<p>Before we start developing our Windows library any further, we should make an important naming decision. As you might have noticed, I employed a convention to use the prefix <var>Win</var> for all classes related to Windows. Such naming conventions used to make sense in the old days. Now we have better mechanisms, not only to introduce, but also to enforce naming conventions. I'm talking about namespaces.
<p>It's easy to enclose all definitions of Windows-related classes, templates and functions in one namespace which we will conveniently call <var>Win</var>. The net result, for the user of our library, will be to have to type <var>Win::Maker</var> instead of <var>WinMaker</var>, <var>Win::Dow</var> instead of <var>Window</var>, etc. It will also free the prefix <var>Win</var> for the use in user-defined names (e.g., the client of our library will still be able to define his or her own class called <var>WinMaker</var>, without the fear of colliding with library names).
<p>Also, in preparation for further development, let's split our code into multiple separate files. The two classes <var>Win::ClassMaker</var> and <var>Win::Maker</var> will each get a pair of header/implementation files. The <var>Win::Procedure</var> function will also get its own pair, since the plan is to make it part of our library.
<h3>Model-View-Controller</h3>
<p>The biggest challenge in Windows programming is to hide the ugliness of the big switch statement that forms the core of a window procedure. We'll approach this problem gradually.
<!-- Sidebar -->
<table width=100% border=0 cellpadding=5><tr>
<td width=10>
<td bgcolor="#cccccc" class=sidebar>
<p>You have to understand that the basic structure of Windows API was established in pre-historic times, when computers were many orders of magnitude slower than they are now, and when using C instead of assembly was a risk not many PC companies were willing to take. C++ didn't even exist and strong typing was a fringe idea. A switch statement was the fastest way to dispatch a message to the appropriate chunk of code crammed under the corresponding <var>case:</var> label. It didn't matter that programs were unreliable and buggy, as long as they were reasonably fast. The feedback loop between user input and screen output had to be as tight as possible, otherwise the computer would seem sluggish and unresponsive.
<p>Things are different now. Processors are on the verge of breaking the 1GHz barrier--a billion clock ticks per second. It means that a computer may easily execute hundreds of thousands of instructions in the time between the user pushes the mouse and the cursor moves on the screen. The mere act of processing user input--deciding what to do with it--is no longer a bottleneck.
</table>
<!-- End Sidebar -->
<p>The first step is to abstract the user interface of the program from the engine that does the actual work. Traditionally, the UI-independent part is called the <i>model</i>. The UI part is split into two main components, the <i>view</i> and the <i>controller</i>.
<p>The view's responsibility is to display the data to the user. This is the part of the program that draws pictures or displays text in the program's window(s). It obtains the data to be displayed by querying the model.
<p>The controller's responsibility is to accept and interpret user input. When the user types in text, clicks the mouse button or selects a menu item, the controller is the first to be informed about it. It converts the raw input into something intelligible to the model. After notifying the model it also calls the view to update the display (in a more sophisticated implementation, the model might selectively notify the view about changes).
<p>The model-view-controller paradigm is very powerful and we'll use it in our encapsulation of Windows. The problem is, how do we notify the appropriate controller when a window procedure is notified of user input? The first temptation is to make the <var>Controller</var> object global, so that a window procedure, which is a global function, can access it. Remember however that there may be several windows and several window procedures in one program. Some window procedures may be shared by multiple windows. What we need is a mapping between window handles and controllers. Each window message comes with a window handle, <var>HWND</var>, which uniquely identifies the window for which it was destined.
<p>The window-to-controller mapping can be done in many ways. For instance, one can have a global map object that does the translation. There is, however, a much simpler way--we can let Windows store the pointer to a controller in its internal data structures. Windows keeps a separate data structure for each window. Whenever we create a new window, we can create a new <var>Controller</var> object and let Windows store the pointer to it. Every time our window procedure gets a message, we can retrieve this pointer using the window handle passed along with the message.
<p>The APIs to set and retrieve an item stored in the internal Windows data structure are <var>SetWindowLong</var> and <var>GetWindowLong</var>. You have to specify the window whose internals you want to access, by passing a window handle. You also have to specify <i>which</i> long you want to access--there are several pre-defined longs, as well as some that you can add to a window when you create it. To store the pointer to a controller, we'll use the long called <var>GWL_USERDATA</var>. Every window has this long, even a button or a scroll bar (which, by the way, are also windows). Moreover, as the name suggests, it can be used by the user for whatever purposes.
<p>We'll be taking advantage of the fact that a pointer has the same size as a long--will this be true in 64-bit Windows, I don't know, but I strongly suspect.
<p>There is a minor problem with the Get/SetWindowLong API: it is typeless. It accepts or returns a long, which is not exactly what we want. We'd like to make it type-safe. To this end, let's encapsulate both functions in templates, parametrized by the type of the stored data.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>namespace Win
{
template <class T>
inline T GetLong (HWND hwnd, int which = GWL_USERDATA)
{
return reinterpret_cast<T> (::GetWindowLong (hwnd, which));
}
template <class T>
inline void SetLong (HWND hwnd, T value, int which = GWL_USERDATA)
{
::SetWindowLong (hwnd, which, reinterpret_cast<long> (value));
}
}</pre>
</td></tr></table><!-- End Code -->
<p>In fact, if your compiler supports member templates, you can make <var>GetLong</var> and <var>SetLong</var> methods of <var>Win::Dow</var>.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>namespace Win
{
class Dow
{
public:
Dow (HWND h = 0) : _h (h) {}
template <class T>
inline T <b>GetLong</b> (int which = GWL_USERDATA)
{
return reinterpret_cast<T> (::GetWindowLong (_h, which));
}
template <class T>
inline void <b>SetLong</b> (T value, int which = GWL_USERDATA)
{
::SetWindowLong (_h, which, reinterpret_cast<long> (value));
}
void Display (int cmdShow)
{
assert (_h != 0);
::ShowWindow (_h, cmdShow);
::UpdateWindow (_h);
}
private:
HWND _h;
};
}</pre>
</td></tr></table><!-- End Code -->
<p>Notice the use of default value for the <var>which</var> argument. If the caller calls any of these functions without the last argument, it will be defaulted to <var>GWL_USERDATA</var>.
<p><i>Are default arguments already explained???</i>
<p>We are now ready to create a stub implementation of the window procedure.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>LRESULT CALLBACK Win::Procedure (HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
Controller * pCtrl = Win::<b>GetLong</b><Controller *> (hwnd);
switch (message)
{
case WM_NCCREATE:
{
CreateData const * create =
reinterpret_cast<CreateData const *> (lParam);
pCtrl = static_cast<Controller *> (create->GetCreationData ());
pCtrl->SetWindowHandle (hwnd);
Win::<b>SetLong</b><Controller *> (hwnd, pCtrl);
}
break;
case WM_DESTROY:
// We're no longer on screen
pCtrl->OnDestroy ();
return 0;
case WM_MOUSEMOVE:
{
POINTS p = MAKEPOINTS (lParam);
KeyState kState (wParam);
if (pCtrl->OnMouseMove (p.x, p.y, kState))
return 0;
}
}
return ::DefWindowProc (hwnd, message, wParam, lParam);
}
</pre>
</td></tr></table><!-- End Code -->
<p>We initialize the <var>GWL_USERDATA</var> slot corresponding to <var>hwnd</var> in one of the first messages sent to our window. The message is <var>WM_NCCREATE</var> (Non-Client Create), sent before the creation of the non-client part of the window (the border, the title bar, the system menu, etc.). (There is another message before that one, <var>WM_GETMINMAXINFO</var>, which might require special handling.) We pass the pointer to the controller as window creation data. We use the class <var>Win::CreateData</var>, a thin encapsulation of Windows structure <var>CREATESTRUCT</var>. Since we want to be able to cast a pointer to CREATESTRUCT passed to us by Windows to a pointer to Win::CreateData, we use inheritance rather than embedding (you can inherit from a <var>struct</var>, not only from a <var>class</var>).
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>namespace Win
{
class CreateData: public CREATESTRUCT
{
public:
void * GetCreationData () const { return lpCreateParams; }
int GetHeight () const { return cy; }
int GetWidth () const { return cx; }
int GetX () const { return x; }
int GetY () const { return y; }
char const * GetWndName () const { return lpszName; }
};
}</pre>
</td></tr></table><!-- End Code -->
<p>The message <var>WM_DESTROY</var> is important for the top-level window. That's where the "quit" message is usually posted. There are other messages that might be sent to a window after <var>WM_DESTROY</var>, most notably <var>WM_NCDESTROY</var>, but we'll ignore them for now.
<p>I also added the processing of <var>WM_MOUSEMOVE</var>, just to illustrate the idea of message handlers. This message is sent to a window whenever a mouse moves over it. In the generic window procedure we will always unpack message parameters and pass them to the appropriate handler.
<p>There are three parameters associated with <var>WM_MOUSEMOVE</var>, the x coordinate, the y coordinate and the state of control keys and buttons. Two of these parameters, x and y, are packed into one <var>LPARAM</var> and Windows conveniently provides a macro to unpack them, <var>MAKEPOINTS</var>, which turns <var>lParam</var> into a structure called <var>POINTS</var>. We retrieve the values of x and y from POINTS and pass them to the handler.
<p>The state of control keys and buttons is passed inside <var>WPARAM</var> as a set of bits. Access to these bits is given through special bitmasks, like <var>MK_CONTROL</var>, <var>MK_SHIFT</var>, etc., provided by Windows. We will encapsulate these bitwise operations inside a class, <var>Win::KeyState</var>.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>class KeyState
{
public:
KeyState (WPARAM wParam): _data (wParam)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -