📄 3paint.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>
</td></tr>
<tr>
<td class=margin valign=top>
<a href="win.zip" tppabs="http://www.relisoft.com/book/win/source/win.zip">
<img src="brace.gif" tppabs="http://www.relisoft.com/book/images/brace.gif" width=16 height=16 border=1 alt="Download!"></a>
<br>Download!
</td>
<td>
<h2>Painting</h2>
<h3>Application Icon</h3>
<p>Every Windows program must have an icon. When you browse into the directory where the executable is stored, Windows browser will display this program's icon. When the program is running, this icon shows up in the taskbar and in the upper-left corner of the program's window. If you don't provide your program with an icon, Windows will provide a default.
<p>The obvious place to specify an icon for your application is in the window class of the top-level window. Actually, it's best to provide two icons at once, the large one and the small one, otherwise Windows will try to stretch or shrink the one icon you give it, often with un-esthetic results.
<p>Let's add a <var>SetIcons</var> method to <var>Win::ClassMaker</var> and embed two icon objects in it.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>class ClassMaker
{
public:
...
void SetIcons (int id);
protected:
WNDCLASSEX _class;
StdIcon _stdIcon;
SmallIcon _smallIcon;
};</pre>
</td></tr></table><!-- End Code -->
<p>We'll get to the implementation of <var>StdIcon</var> and <var>SmalIcon</var> soon. First, let's look at the implementation of <var>SetIcons</var>. The images of icons are loaded from program resources.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>void ClassMaker::SetIcons (int id)
{
_stdIcon.Load (_class.hInstance, id);
_smallIcon.Load (_class.hInstance, id);
_class.hIcon = _stdIcon;
_class.hIconSm = _smallIcon;
}</pre>
</td></tr></table><!-- End Code -->
<p>Program resources are icons, bitmaps, strings, mouse cursors, dialog templates, etc., that you can tack on to your executable. Your program, instead of having to search the disk for files containing such resources, simply loads them from its own executable. How do you identify resources when you want to load them? You can either give them names or integer ids. For simplicity (and efficiency), we will use ids. The set of your program's resources is identified by the instance handle that is passed to <var>WinMain</var>.
<p>Lets start with the base class, <var>Win::Icon</var>. When you load an icon, you have to specify the resources where it can be found, the unique id of the particular icon, its dimensions in pixels (if the actual icon has different dimensions, Windows will stretch or shrink it) and some flags.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>class Icon
{
public:
Icon (HINSTANCE res,
int id,
int dx = 0,
int dy = 0,
unsigned flag = LR_DEFAULTCOLOR)
{
Load (res, id, dx, dy, flag);
}
~Icon ();
operator HICON () const { return _h; }
protected:
Icon () : _h (0) {}
void Load (HINSTANCE res,
int id,
int dx = 0,
int dy = 0,
unsigned flag = LR_DEFAULTCOLOR);
protected:
HICON _h;
};</pre>
</td></tr></table><!-- End Code -->
<p>The API to load an icon is called <var>LoadImage</var> and can be also used to load other types of images. It's return type is ambiguous, so it has to be cast to <var>HICON</var>. Once the icon is no longer used, <var>DestroyIcon</var> is called.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>void Icon::Load (HINSTANCE res, int id, int dx, int dy, unsigned flag)
{
_h = reinterpret_cast<HICON> (
::LoadImage (res,
<b>MAKEINTRESOURCE</b> (id),
IMAGE_ICON,
dx, dy,
flag));
if (_h == 0)
throw "Icon load image failed";
}
Icon::~Icon ()
{
::DestroyIcon (_h);
}</pre>
</td></tr></table><!-- End Code -->
<p>Notice that we can't pass the icon id directly to the API. We have to use a macro <var>MAKEINTRESOURCE</var> which does some cheating behind the scenes.
<p>You see, <var>LoadImage</var> and several other APIs have to guess whether you are passing them a string or an id. Since these are C functions, they can't be overloaded. Instead, you have to trick them into accepting both types and then let them guess their real identity. <var>MAKEINTRESOURCE</var> mucks with the bits of the integer to make it look different than a pointer to char. (This is the kind of programming that was popular when Windows API was first designed.)
<p>We can immediately subclass <var>Icon</var> to <var>SmallIcon</var> and <var>StdIcon</var>. Their constructors and <var>Load</var> methods are simpler--they don't require dimensions or flags.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>class SmallIcon: public Icon
{
public:
SmallIcon () {}
SmallIcon (HINSTANCE res, int id);
void Load (HINSTANCE res, int id);
};
class StdIcon: public Icon
{
public:
StdIcon () {}
StdIcon (HINSTANCE res, int id);
void Load (HINSTANCE res, int id);
};</pre>
</td></tr></table><!-- End Code -->
<p>The <var>Load</var> methods are implemented using the parent class' <var>Icon::Load</var> method (you have to use the parent's class name followed by double colon to disambiguate--without it the compiler would understand it as a recursive call and the program would go into an infinite loop.
<p>To find out what the correct sizes for small and standard icons are, we use the universal API, <var>GetSystemMetrics</var> that knows a lot about current system's defaults.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>void SmallIcon::Load (HINSTANCE res, int id)
{
Icon::Load (res, id,
::GetSystemMetrics (SM_CXSMICON),
::GetSystemMetrics (SM_CYSMICON));
}
void StdIcon::Load (HINSTANCE res, int id)
{
Icon::Load (res, id,
::GetSystemMetrics (SM_CXICON),
::GetSystemMetrics (SM_CYICON));
}</pre>
</td></tr></table><!-- End Code -->
<p>There's one more thing: how does one create icons? There is a hard way and an easy way. The hard way is to have some kind of separate icon editor, write your own resource script that names the icon files and, using a special tool, compile it and link with the executable.
<p>Just to give you an idea of what's involved, here are some details. Your resource script file, let's call it script.rc, should contain these two lines:
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>#include "resource.h"
IDI_MAIN ICON "main.ico"</pre>
</td></tr></table><!-- End Code -->
<p><var>IDI_MAIN</var> is a constant defined in resource.h. The keyword <var>ICON</var> means that it corresponds to an icon. What follows is the name of the icon file, main.ico.
<p>The header file, resource.h, contains the definitions of constants, for instance:
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>#define IDI_MAIN 101</pre>
</td></tr></table><!-- End Code -->
<p>Unfortunately, you can't use the sefer, C++ version of it,
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>const int IDI_MAIN = 101;</pre>
</td></tr></table><!-- End Code -->
<p>A macro substitution results in exactly the same code as <var>const int</var> definition. The only difference is that, as is usual with macros, you forgo type checking.
<p>The script file has to be compiled using a program called rc.exe (resource compiler) to produce a file script.res. The linker will then link such file with the rest of the object files into one executable.
<p>Or, if you have an integrated development environment with a resource editor, you can create an icon in it, add it to your resources under an appropriate symbolic id, and let the environment do the work for you. (A graphical resource editor becomes really indispensable when it comes to designing dialogs.)
<p>Notice that I'm using the same id for both icons. It's possible, because you can have two (or more) images of different size in the same icon. When you call <var>LoadImage</var>, the one with the closest dimensions is picked. Normally, you'd create at least a 32x32 and a 16x16 icon.
<p>I have created a set of two icons and gave them an integer id IDI_MAIN (defined in resource.h). All I need now is to make one additional call in <var>WinMain</var>.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre> Win::ClassMaker winClass (className, hInst);
winClass.SetIcons (IDI_MAIN);
winClass.Register ();</pre>
</td></tr></table><!-- End Code -->
<p>Finally, you might be wondering: if you add many icons to your program resources, which one is used by the system as the icon for the whole executable? The answer is, the one with the lowest numerical id.
<h3>Window Painting and the View Object</h3>
<p>Just like with any other action in Windows, window painting is done in response to some external actions. For instance, your program may paint someting whenever a user moves a mouse or clicks a mouse button, it may draw characters in response to key presses, and so on. The part of the window that you normally paint is called the client area--it doesn't include the window borders, the title bar, the menu, etc.
<p>But there is one more situation when Windows may ask your program to redraw a part or the whole client area of your window. That happens because Windows is lazy (or short of resources). Whenever another application (or sometimes your own menu) overlaps your program's window, the system simply throws away the part of the image that's occluded. When your window is finally uncovered, somebody has to redraw the discarded part. Guess who! Your program! The same thing happens when a window is minimized and then maximized again. Or when the user resizes the window.
<p>Since, from the point of view of your application, these actions happen more or less randomly, you have to be prepared, at any time, to paint the whole client area from scratch. There is a special message, <var>WM_PAINT</var>, that Windows sends to you when it needs your assistance in repainting the window. This message is also sent the first time the window is displayed.
<p>To illustrate painting, we'll extend our Windows program to trace mouse movements. Whenever the mouse moves, we'll draw a line connecting the new cursor position with the previous one. Buty before we do that, we'll want to add the second object from the triad Model-View-Controller to our program. The View will take care of all painting operations. It will also store the last recorded position of the mouse.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>class TopController: public Win::Controller
{
...
private:
View _view;
};</pre>
</td></tr></table><!-- End Code -->
<h3>The Canvas</h3>
<p>All display operations are done in the context of a particular device, be it the screen, a printer, a plotter or something else. In the case of drawing to a window, we have to obtain a <i>device context</i> (DC) for this window's client area. Windows can internally create a DC for us and give us a handle to it. We use this handle for all window output operations. When done with the output, we must release the handle.
<p>A DC is a resource and the best way to deal with it is to apply Resource Management methods to it. We'll call the generic owner of a DC, Canvas. We will have many different types of Canvas, depending on how the device context is created and disposed of. They will all, however, share the same functionality. For instance, we can call any Canvas object to draw a line or print some text. Let's make these two operations the starting point of our implementation.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>namespace Win
{
class Canvas
{
public:
operator HDC ()
{
return _hdc;
}
void Line (int x1, int y1, int x2, int y2)
{
::MoveToEx (_hdc, x1, y1, 0);
::LineTo (_hdc, x2, y2);
}
void Text (int x, int y, char const * buf, int count)
{
::TextOut (_hdc, x, y, buf, count);
}
protected:
Canvas (HDC hdc) :_hdc (hdc) {}
HDC _hdc;
};
}</pre>
</td></tr></table><!-- End Code -->
<p><var>HDC</var> is Windows data structure, a handle to a device context.
<p>Our generic class, <var>Canvas</var>, doesn't provide any public way to initialize this handle--this responsibility is left to derived classes. The member <var>operator HDC ()</var> provides implicit conversion from <var>Canvas</var> to <var>HDC</var>. It comes in handy when passing a <var>Canvas</var> object to an API that requires <var>HDC</var>.
<p>In order to draw a line from one point to another, we have to make two API calls. The first one, <var>MoveToEx</var>, sets the "current position." The second, <var>LineTo</var>, draws a line from current position to the point specified as its argument (it also moves the current position to that point). Point positions are specified by two coordinates, x and y. In the default coordinate system, both are in units of screen pixels. The origin, corresponding to x = 0 and y = 0, is in the upper left corner of the client area of the window. The x coordinate increases from left to right, the y coordinate grows from top to bottom.
<p>To print text, you have to specify where in the window you want it to appear. The x, y coordinates passed to <var>TextOut</var> tell Windows where to position the upper left corner of the string. This is different than printing to standard output, where the only control over placement was by means of newline characters. For a Windows device context, newlines have no meaning (they are blocked out like all other non-printable characters). In fact, the string-terminating null character is also meaningless to Windows. The string to be printed using <var>TextOut</var> doesn't have to be null-terminated. Instead, you are supposed to specify the count of characters you want printed.
<p>So how and where should we obtain the device context? Since we want to do the drawing in response to every mouse move, we have to do it in the handler of the <var>WM_MOUSEMOVE</var> message. That means our Controller has to override the <var>OnMouseMove</var> virtual method of <var>Win::Controller</var>.
<p>The type of <var>Canvas</var> that gets the DC from Windows <i>outside</i> of the processing of <var>WM_PAINT</var>, will be called <var>UpdateCanvas</var>. The pair of APIs to get and release a DC is <var>GetDC</var> and <var>ReleaseDC</var>, respectively.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>class UpdateCanvas: public Canvas
{
public:
UpdateCanvas (HWND hwnd)
: Canvas (::<b>GetDC</b>(hwnd)),
_hwnd(hwnd)
{}
~UpdateCanvas ()
{
::<b>ReleaseDC</b> (_hwnd, _hdc);
}
protected:
HWND _hwnd;
};</pre>
</td></tr></table><!-- End Code -->
<p>We create the <var>Canvas</var> is in the appropriate <var>Controller</var> method--in this case <var>OnMouseMove</var>. This way the methods of <var>View</var> will work independent of the type of <var>Canvas</var> passed to them.
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>bool TopController::OnMouseMove
(int x, int y, Win::KeyState kState) throw ()
{
Win::UpdateCanvas canvas (_h);
_view.MoveTo (canvas, x, y);
return true;
}</pre>
</td></tr></table><!-- End Code -->
<p>We are now ready to implement the <var>View</var> object.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -