// Draw a divider using XOR mode
UpdateCanvas canvas(_hwndParent);
ModeSetter mode(canvas, R2_NOTXORPEN);
canvas.Line (_dragX, 0, _dragX, _cy - 1);
}
Когда левая кнопка мыши нажата над клиентской областью расщепителя, мы выполняем следующие задачи. Сначала мы фиксируем мышь. Пользователь может и, возможно будет, перемещают курсор мыши вне полоски расщепителя. Фиксация мыши гарантирует, что все ее сообщения будут теперь направлены к нам, даже в том случае, когда курсор мыши будет блуждать по всему экрану.
Затем мы преобразуем локальные координаты сплиттера в координаты родительского окна. Мы делаем это конвертацией начальных координат родителя к экранным координатам координатам дисплея и преобразованием начальные координаты нашего расщепителя также к экранным координатам дисплея. Разница дает нам начальное положение относительно клиентской области родителя. Мы делаем дополнительные арифметические вычисления, чтобы найти координату x центра расщепителя, потому что это - то, что мы будем перемещать.
Чтобы предоставить пользователю, перемещающему сплиттер, обратную связь, мы рисуем одиночную вертикальную линию, которую будем перемещать поперек клиентской области родительского окна. Обратите внимание на две важных детали. Холст, который мы создаем, связан с родителем — мы используем дескриптор окна родителя. Режим перерисовки использует логическую операцию xor (исключающее или). Это означает, что пикселы, которые мы рисуем, — конвертируются с первоначальными пикселами с использованием xor. Логическая операция xor имеет полезную особенность. Если Вы примените ее дважды, Вы восстановите оригинал. Этот самый старый прием в компьютерной графике — метод простой анимации. Вы рисуете что-то, используя xor режим и стираете простым рисованием его же снова в xor режиме. Итак, рассмотрим код перемещения, представленный ниже.
void SplitController::LButtonDrag(POINTS pt) {
if (_hwnd.HasCapture()) {
// Erase previous divider and draw new one
UpdateCanvas canvas(_hwndParent);
ModeSetter mode(canvas, R2_NOTXORPEN);
canvas.Line(_dragX, 0, _dragX, _cy - 1);
_dragX = _dragStart + pt.x;
canvas.Line(_dragX, 0, _dragX, _cy - 1);
}
}
Мы рисуем вертикальную линию в xor режиме, используя предыдущую сохраненную позицию. Так как это рисование происходит во второй раз, мы рисуем эту линию в том же самом месте и, в результате, будут восстановлены первоначальные пикселы из-под этой линии. Затем мы рисуем новую линию в новой позиции, также в xor режиме. Мы запоминаем эту позицию, чтобы в следующий раз, когда вызовется LButtonDrag, мы могли стереть ее также. И так далее, пока пользователь не отпустит кнопку мыши.
void SplitController::LButtonUp(POINTS pt) {
// Calling ReleaseCapture will send us the WM_CAPTURECHANGED
_hwnd.ReleaseMouse();
_hwndParent.SendMessage(MSG_MOVESPLITTER, _dragStart + pt.x);
}
В этой точке мы должны стереть вертикальную строку в последний раз. Однако, мы не делаем это непосредственно — мы используем здесь небольшой прием. Мы прекращаем сбор данных мыши. Как побочный эффект, Windows посылает нам сообщение WM_CAPTURECHANGED. Во время обработки этого сообщения, мы фактически и стираем вертикальную строку.
void SplitController::CaptureChanged() {
// We are losing capture
// End drag selection -- for whatever reason
// Erase previous divider
UpdateCanvas canvas(_hwndParent);
ModeSetter mode(canvas, R2_NOTXORPEN);
canvas.Line(_dragX, 0, _dragX, _cy - 1);
}
Почему мы делаем это таким образом? Потому, что Windows может заставить нас, прекратить обработку данных от мыши прежде, чем пользователь опустит ее кнопку. Это может случиться, например, когда другое приложение внезапно решает распахнуть его окно, в то время как пользователь находится в процессе перемещения. В этом случае наше окно никогда не получило бы сообщение об отжатии кнопки. Если бы мы не были так умны, мы бы не были способны чисто перемещаться. К счастью, перед завершением обработки данных мыши, Windows сумеет послать нам сообщение WM_CAPTURECHANGED, и мы примем его к сведению, чтобы сделать нашу очистку.
Вернемся снова к LBUTTONUP — когда пользователь заканчивает перемещение, мы посылаем родителю наше специальное сообщение MSG_MOVESPLITTER, передавая ему новую позицию центра полосы расщепителя, измеряемой в координатах клиентской области родителя. Вы уже видели клиентский код, реагирующий на это сообщение.
Здесь показано, как можно определить клиентское сообщение.
// Reserved by Reliable Software Library
const UINT MSG_RS_LIBRARY = WM_USER + 0x4000;
// wParam = new position wrt parent's left edge
const UINT MSG_MOVESPLITTER = MSG_RS_LIBRARY + 1;
В заключение представлен фрагмент из очень полезного класса HWnd, который инкапсулирует многие базисных функции API Windows, имеющие дело с окнами. В частности, рассмотрите методы MoveDelayPaint и ForceRepaint, который мы использовали в перерисовке полоски расщепителя.
class HWnd {
public:
void Update() {
::UpdateWindow(_hwnd);
}
// Moving
void Move(int x, int y, int width, int height) {
::MoveWindow(_hwnd, x, y, width, height, TRUE);
}
void MoveDelayPaint(int x, int y, int width, int height) {
::MoveWindow(_hwnd, x, y, width, height, FALSE);
}
// Repainting
void Invalidate() {
::InvalidateRect(_hwnd, 0, TRUE);
}
void ForceRepaint() {
Invalidate();
Update();
}
private:
HWND _hwnd;
};
Как обычно, Вы можете загрузить полный исходный текст приложения, которое использовалось в этом примере.
Далее: Следующая обучающая программа рассказывает о растрах.
In this tutorial we'll learn how to load bitmaps from resources and from files, how to pass them around and blit them to the screen. We'll also see how to create and use an empty bitmap as a canvas, draw a picture on it and then blit it to the screen. Finally, we'll combine these techniques to write a simple program that uses double-buffering and timer messages to show a simple animation involving sprites.
First of all, in most cases Windows provides storage for bitmaps and takes care of the formatting of bits. The programmer gets access to the bitmap through a handle, whose type is HBITMAP. (Remember to set the STRICT flag when compiling Windows programs, to make sure HBITMAP is a distinct type, rather than just a pointer to void.)
Since a bitmap is a resource (in the Resource Management sense), the first step is to encapsulate it in a “strong pointer” type of interface. Notice the transfer semantics of the constructor and the overloaded assignment operator, characteristic of a resource that can have only one owner at a time.
We instruct Windows to release the resources allocated to the bitmap by calling DeleteObject.
class Bitmap {
public:
Bitmap() : _hBitmap (0) {}
// Transfer semantics
Bitmap(Bitmap& bmp) : _hBitmap(bmp.Release()) {}
void operator=(Bitmap& bmp) {
if (bmp._hBitmap != _hBitmap) {
Free();
_hBitmap = bmp.Release();
}
}
HBITMAP Release() {
HBITMAP h = _hBitmap;
_hBitmap = 0;
return h;
}
~Bitmap() {
Free();
}
// implicit conversion for use with Windows API
operator HBITMAP() {
return _hBitmap;
}
protected:
Bitmap(HBITMAP hBitmap) : _hBitmap(hBitmap) {}
void Free() {
if (_hBitmap) ::DeleteObject(_hBitmap);
}
HBITMAP _hBitmap;
};
Now that the management issues are out of the way, we can concentrate on loading bitmaps. The simplest way to include a bitmap in your program is to add it to the resource file. In the resource editor of your development environment you can create new bitmaps or import them from external files. You can either give them names (strings) or numerical ids. When you want to access such a bitmap in your program you have to load it from the resources. Here are two methods that do just that. You have to give them a handle to the program instance.
void Bitmap::Load(HINSTANCE hInst, char const * resName) {
Free();
_hBitmap = (HBITMAP)::LoadImage(hInst, resName, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
if (_hBitmap == 0) throw WinException("Cannot load bitmap from resources", resName);
}
void Bitmap::Load(HINSTANCE hInst, int id) {
Free();
_hBitmap = (HBITMAP)::LoadImage(hInst, MAKEINTRESOURCE(id), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
if (_hBitmap == 0) throw WinException("Cannot load bitmap from resources");
}
Loading a bitmap directly from a file is also very simple and can be done using the same API, LoadImage. Remember, it will only work if the file is a Windows (or OS/2) bitmap — such files usually have the extension .bmp. There is no simple way of loading other types of graphics files, .gif, .jpg, .png, etc. You have to know their binary layout and decode them explicitly (there are other web sites that have this information).
void Bitmap::Load(char* path) {
Free();
_hBitmap = (HBITMAP)::LoadImage(0, path, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if(_hBitmap == 0) throw WinException("Cannot load bitmap from file", path);
}
Once you got hold of a bitmap, you may want to enquire about its dimensions. Here's how you do it.
void Bitmap::GetSize(int& width, int& height) {
BITMAP bm;
::GetObject(_hBitmap, sizeof(bm), &bm);
width = bm.bmWidth;
height = bm.bmHeight;
}
Finally, you might want to create an empty bitmap and fill it with your own drawings programmatically. You have to specify the dimensions of the bitmap and you have to provide a device context (Canvas) for which the bitmap is targeted. Windows will create a different type of bitmap when your target is a monochrome monitor or printer, and different when it's a graphics card set to True Color. Windows will create a bitmap that is compatible with the target device.