画笔和画刷是作图的基础,有静态的描画和利用鼠标动态画图。
一、画笔的种类和使用方法
画笔的使用和之前字体类似,首先用CreatePen()函数创建一个画笔对象,然后与设备文本绑定,同时得到旧的画笔。用完后删除新画笔,并且恢复旧画笔。画笔的种类:
- PS_SOLID:实现;
- PS_DASH:破折线;等等
vc中并不提供指定两点画线的功能,LineTo()函数从上一次画笔所在的位置作为起点,一直画到函数指定位置终止。如果要从指定位置画到另一指定位置就需要用到MoveToEx()函数。
MoveToEx()函数只移动画笔的位置,并不画线。
BOOL MoveToEx(
HDC hdc,//HDC
int X,//坐标
int Y,
LPPOINT lpPoint);//返回先前的位置
BOOL LineTo(
HDC hdc,
int X,
int Y);
HPEN CreatePen(
int fnPenstyle,
int nWidth,
COLORREF crColor);
利用画笔画线
HPEN hNewPen, hOldPen;
switch(message) {
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
hNewPen = CreatePen(PS_SOLID, 10, 0x0000FF);
hOldPen = (HPEN)SelectObject(hdc, hNewPen);
MoveToEx(hdc, 100, 100, NULL);
LineTo(hdc, 300, 100);
DeleteObject(hNewPen);
EndPaint(hWnd, &ps);
break;
二、画刷的种类和使用方法
画刷的使用和画笔相同,创建画刷有两个函数,CreateHatchBrush()函数创建带图案的画刷,而CreateSolidBrush()函数不带图案。画刷图案主要有6种,而空画刷不需要创建。
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
switch(message) {
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
DrawGr(hdc);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
int DrawGr(HDC hdc) {
HPEN hPen, hOldPen;
HBRUSH hBrush, hOldBrush;
hPen = CreatePen(PS_SOLID, 1, 0xF00000);
hOldPen = (HPEN)SelectObject(hdc, hPen);
hBrush = CreateHatchBrush(HS_CROSS, 0x00F000);
hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
Rectangle(hdc, 10, 10, 210, 110);
DeleteObject(hPen);
DeleteObject(hBrush);
return 0;
}
三、剖析鼠标消息
WM_LBUTTONDOWN、WM_LBUTTONUP、WM_MOUSEMOVE是鼠标的三个基本消息处理,分别表示鼠标左键按下、鼠标左键松开、鼠标移动。
lParam副消息存放的是鼠标的坐标位置,字节的低四位为x坐标,高四位为y坐标。wParam的高四位不用,第四位表示组合件的使用状态,第1位为”1“表示鼠标左键被按下,第2位为”1”表示鼠标右键被按下,第3位为”1”表示shift键被按下,第4位为”1”表示ctrl键按下,这四种状态可以组合使用。例如鼠标左右键同时按下,wParam为3;鼠标左右键和shift、ctrl键全部按下则wParam为F。
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_MOUSEMOVE:
HDC hdc;
RECT rc;
char strx[64];
char strAction[64];
int xPos, yPos;
xPos = LOWORD(lParam);
yPos = HIWORD(lParam);
wsprintf((LPSTR)strx, "鼠标位置: (%3d, %3d) wParam=%X ",xPos, yPos, wParam);
hdc = GetDC(hWnd);
GetClientRect(hWnd, &rc);
TextOut(hdc,10,10,strx, (int)strlen(strx));
if (wParam & 0x1) {
wsprintf((LPSTR)strAction, "鼠标左键: ON ");
} else {
wsprintf((LPSTR)strAction, "鼠标左键: OFF");
}
TextOut(hdc, 10, 25, strAction, (int)strlen(strAction));
if (wParam & 0x2) {
wsprintf((LPSTR)strAction, "鼠标右键: ON ");
} else {
wsprintf((LPSTR)strAction, "鼠标右键: OFF");
}
TextOut(hdc, 10, 40, strAction, (int)strlen(strAction));
if (wParam & 0x4) {
wsprintf((LPSTR)strAction, "Shift键: ON ");
} else {
wsprintf((LPSTR)strAction, "Shift键: OFF");
}
TextOut(hdc, 10, 55, strAction, (int)strlen(strAction));
if (wParam & 0x8) {
wsprintf((LPSTR)strAction, "Ctrl键: ON ");
} else {
wsprintf((LPSTR)strAction, "Ctrl键: OFF");
}
TextOut(hdc, 10, 70, strAction, (int)strlen(strAction));
ReleaseDC(hWnd, hdc);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
四、用鼠标作图
捕获、释放光标
首先掌握ClipCursor()函数,这个函数可以将光标限定在有效区内,注意该函数的参数用的坐标是整个屏幕的坐标,这时因为鼠标是全屏动作的。因此,我们还需要学会将窗口有效区坐标转换为屏幕坐标。ClientToScreen()函数可以实现这一目的,不过这一函数在MFC中的参数和SDK中的参数不一样,MFC中转换的是一矩形块,而SDK中装换的是一个点。所以SDK编程中,下面的例子是先转换窗口有效区的(0,0)点,再计算出矩形右下角的点。
LONG clsCur;
bool bDrawing = false; //画图状态为true
POINTS start; //开始点
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_LBUTTONDOWN:
bDrawing = true; //描画状态开始
start = MAKEPOINTS(lParam); //开始点保存于start中
//取得窗口有效区
RECT rect;
GetClientRect(hWnd,&rect);
//转换窗口有效区为屏幕座标
POINT point;
point.x = 0, point.y = 0;
ClientToScreen(hWnd,&point);
rect.top = point.y;
rect.left = point.x;
rect.bottom += rect.top;
rect.right += rect.left;
//将光标限定在窗口有效区内
ClipCursor(&rect); //rect一定要是屏幕座标
if (!clsCur) {
clsCur = GetClassLong(hWnd,GCL_HCURSOR); //取当前窗口的光标
}
SetClassLong(hWnd,GCL_HCURSOR,NULL); //设置当前窗口的光标为NULL
SetCursor(LoadCursor(NULL, IDC_CROSS)); //设置当前光标为十字形
break;
case WM_MOUSEMOVE:
HDC hdc;
hdc = GetDC(hWnd);
if (bDrawing) {
MoveToEx(hdc, start.x, start.y,NULL);
LineTo(hdc, LOWORD(lParam), HIWORD(lParam));
}
ReleaseDC(hWnd, hdc);
break;
case WM_LBUTTONUP:
bDrawing = false; //描画状态结束
ClipCursor(NULL); //使光标可以在屏幕任意位置移动
SetClassLong(hWnd,GCL_HCURSOR, clsCur); //设置窗口光标为原先的光标
SetCursor((HCURSOR)clsCur); //设置当前光标为原先的光标
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
消除移动轨迹
上例中,想要画一条线(即鼠标按下到松开的一条线),结果却将鼠标移动的“轨迹”画了出来,这些“轨迹”必须擦掉。VC不提供擦掉已经画好的图案的功能,所谓“擦掉”,就是选用某种方式重画,使得看上去象重画一样。通过SetROP2()函数就可以实现这个功能,这个函数将反转屏幕颜色,这样第二次画时就恢复为原来的颜色。注意:SetROP2()函数这是设置画图模式的函数,是设置前景与背景的关系。
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_LBUTTONDOWN:
bDrawing = true; //描画状态开始
prev = start = MAKEPOINTS(lParam); //开始点保存于start和prev中
RECT rect;
POINT point;
GetClientRect(hWnd,&rect); //取得窗口有效区
point.x = 0, point.y = 0;
ClientToScreen(hWnd,&point); //转换窗口有效区为屏幕座标
rect.top = point.y;
rect.left = point.x;
rect.bottom += rect.top;
rect.right += rect.left;
ClipCursor(&rect); //将光标限定在窗口有效区内
if (!clsCur) {
clsCur = GetClassLong(hWnd,GCL_HCURSOR);
}
SetClassLong(hWnd,GCL_HCURSOR,NULL);
SetCursor(LoadCursor(NULL, IDC_CROSS)); //设置光标为十字形
break;
case WM_MOUSEMOVE:
HDC hdc;
if (bDrawing) { //如果是描画状态
hdc = GetDC(hWnd); //取设备句柄
end = MAKEPOINTS(lParam); //当前点保存于end中
Draw(hdc, start, prev); //擦去前一次所画内容
Draw(hdc, start, end); //画开始点到当前点
prev = end; //当前点设置为旧的点
ReleaseDC(hWnd, hdc); //释放设备句柄
}
break;
case WM_LBUTTONUP:
hdc = GetDC(hWnd);
Draw2(hdc, start, end); //画开始点到当前点
bDrawing = false; //描画状态结束
ClipCursor(NULL); //使光标可以在屏幕任意位置移动
SetClassLong(hWnd,GCL_HCURSOR, clsCur);
SetCursor((HCURSOR)clsCur);
ReleaseDC(hWnd, hdc);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
//作图
void Draw(HDC hdc, POINTS beg, POINTS end)
{
SetROP2(hdc, R2_NOT); //设置前景配色方式为象素反转
MoveToEx(hdc, beg.x, beg.y, NULL); //移动描画点到按下鼠标键的地方
LineTo(hdc, end.x, end.y); //从描画点到(end.x, end.y)点
return;
}
鼠标松开后的处理
LONG clsCur;
bool bDrawing = false; //画图状态为true
POINTS start, end, prev; //开始点、结束点、前一个移动点
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_LBUTTONDOWN:
bDrawing = true; //描画状态开始
prev = start = MAKEPOINTS(lParam); //开始点保存于start和prev中
RECT rect;
POINT point;
GetClientRect(hWnd,&rect); //取得窗口有效区
point.x = 0, point.y = 0;
ClientToScreen(hWnd,&point); //转换窗口有效区为屏幕座标
rect.top = point.y;
rect.left = point.x;
rect.bottom += rect.top;
rect.right += rect.left;
ClipCursor(&rect); //将光标限定在窗口有效区内
if (!clsCur) {
clsCur = GetClassLong(hWnd,GCL_HCURSOR);
}
SetClassLong(hWnd,GCL_HCURSOR,NULL);
SetCursor(LoadCursor(NULL, IDC_CROSS)); //设置光标为十字形
break;
case WM_MOUSEMOVE:
HDC hdc;
if (bDrawing) { //如果是描画状态
hdc = GetDC(hWnd); //取设备句柄
end = MAKEPOINTS(lParam); //当前点保存于end中
Draw(hdc, start, prev); //擦去前一次所画内容
Draw(hdc, start, end); //画开始点到当前点
prev = end; //当前点设置为旧的点
ReleaseDC(hWnd, hdc); //释放设备句柄
}
break;
case WM_LBUTTONUP:
hdc = GetDC(hWnd);
Draw2(hdc, start, end); //画开始点到当前点
bDrawing = false; //描画状态结束
ClipCursor(NULL); //使光标可以在屏幕任意位置移动
SetClassLong(hWnd,GCL_HCURSOR, clsCur);
SetCursor((HCURSOR)clsCur);
ReleaseDC(hWnd, hdc);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
//作图
void Draw(HDC hdc, POINTS beg, POINTS end)
{
SetROP2(hdc, R2_NOT); //设置前景配色方式为象素反转
MoveToEx(hdc, beg.x, beg.y, NULL); //移动描画点到按下鼠标键的地方
LineTo(hdc, end.x, end.y); //从描画点到(end.x, end.y)点
return;
}
void Draw2(HDC hdc, POINTS beg, POINTS end)
{
HPEN hPen, hOldPen;
SetROP2(hdc, R2_COPYPEN); //设置前景配色方式为画笔颜色
hPen = CreatePen(PS_SOLID, 5, RGB(0, 100, 255)); //兰色实线画笔
hOldPen = (HPEN)SelectObject(hdc, hPen);
MoveToEx(hdc, beg.x, beg.y, NULL); //移动描画点到按下鼠标键的地方
LineTo(hdc, end.x, end.y); //从描画点到(end.x, end.y)点
SelectObject(hdc, hOldPen);
DeleteObject(hPen);
return;
}
完整程序
#include <windows.h>
#define IDI_QUAN 101
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
BOOL InitApp(HINSTANCE, LPCSTR);
BOOL InitInstance(HINSTANCE, LPCSTR, int);
void Draw(HDC hdc, POINTS beg, POINTS end);
void Draw2(HDC hdc, POINTS beg, POINTS end);
//主入口函数
int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,
LPSTR lpsCmdLine, int nCmdShow)
{
MSG msg;
char szClassName[] ="DrawRect"; //窗口名
//注册窗口类
if (!InitApp(hCurInst, szClassName))
return FALSE;
//初始化窗口
if (!InitInstance(hCurInst, szClassName, nCmdShow)) {
return FALSE;
}
//消息循环
while (GetMessage(&msg, NULL, NULL, NULL)) {
TranslateMessage(&msg); //消息解释
DispatchMessage(&msg); //消息发送
}
return (int)msg.wParam;
}
//登记窗口类
BOOL InitApp(HINSTANCE hInst, LPCSTR szClassName)
{
WNDCLASS wc;
ZeroMemory(&wc, sizeof(wc));
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInst;
wc.hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_QUAN));
wc.hCursor = LoadCursor(NULL, IDC_HELP);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = (LPCSTR)szClassName;
return (RegisterClass(&wc));
}
//初始化窗口类
BOOL InitInstance(HINSTANCE hInst, LPCSTR szClassName, int nCmdShow)
{
HWND hWnd;
hWnd = CreateWindowEx(0,
szClassName,
"MY first GDI",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
320, 320,
NULL, NULL,
hInst, NULL);
if (!hWnd) return FALSE;
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
LONG clsCur;
bool bDrawing = false; //画图状态为true
POINTS start, end, prev; //开始点、结束点、前一个移动点
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_LBUTTONDOWN:
bDrawing = true; //描画状态开始
prev = start = MAKEPOINTS(lParam); //开始点保存于start和prev中
RECT rect;
POINT point;
GetClientRect(hWnd,&rect); //取得窗口有效区
point.x = 0, point.y = 0;
ClientToScreen(hWnd,&point); //转换窗口有效区为屏幕座标
rect.top = point.y;
rect.left = point.x;
rect.bottom += rect.top;
rect.right += rect.left;
ClipCursor(&rect); //将光标限定在窗口有效区内
if (!clsCur) {
clsCur = GetClassLong(hWnd,GCL_HCURSOR);
}
SetClassLong(hWnd,GCL_HCURSOR,NULL);
SetCursor(LoadCursor(NULL, IDC_CROSS)); //设置光标为十字形
break;
case WM_MOUSEMOVE:
HDC hdc;
if (bDrawing) { //如果是描画状态
hdc = GetDC(hWnd); //取设备句柄
end = MAKEPOINTS(lParam); //当前点保存于end中
Draw(hdc, start, prev); //擦去前一次所画内容
Draw(hdc, start, end); //画开始点到当前点
prev = end; //当前点设置为旧的点
ReleaseDC(hWnd, hdc); //释放设备句柄
}
break;
case WM_LBUTTONUP:
hdc = GetDC(hWnd);
Draw2(hdc, start, end); //画开始点到当前点
bDrawing = false; //描画状态结束
ClipCursor(NULL); //使光标可以在屏幕任意位置移动
SetClassLong(hWnd,GCL_HCURSOR, clsCur);
SetCursor((HCURSOR)clsCur);
ReleaseDC(hWnd, hdc);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
//作图
void Draw(HDC hdc, POINTS beg, POINTS end)
{
SetROP2(hdc, R2_NOT); //设置前景配色方式为象素反转
MoveToEx(hdc, beg.x, beg.y, NULL); //移动描画点到按下鼠标键的地方
LineTo(hdc, end.x, end.y); //从描画点到(end.x, end.y)点
return;
}
void Draw2(HDC hdc, POINTS beg, POINTS end)
{
HPEN hPen, hOldPen;
SetROP2(hdc, R2_COPYPEN); //设置前景配色方式为画笔颜色
hPen = CreatePen(PS_SOLID, 5, RGB(0, 100, 255)); //兰色实线画笔
hOldPen = (HPEN)SelectObject(hdc, hPen);
MoveToEx(hdc, beg.x, beg.y, NULL); //移动描画点到按下鼠标键的地方
LineTo(hdc, end.x, end.y); //从描画点到(end.x, end.y)点
SelectObject(hdc, hOldPen);
DeleteObject(hPen);
return;
}