承接上文-win32-带你一步一步编写绘画板(1)
在上次我们已经创建好了窗口,接下来我们将要在原有窗口上编写绘画板,首先是展示我们这次编写之后所达到的效果,看图,
初始化界面:将客户区(Client)分为上下两部分,上部分用于工具栏(Tool),下部分用于绘画(Drawing),且下部分设计为带有边界的纸张。上部分可以随意点击更换画笔的颜色,选中的颜色会显示到第一个“颜色”控件中。这就是本次我们所要完成的功能。
首先定义几个需要用到的全局变量,代码如下:
static RECT rcClient; //客户区
static RECT rcTools; //工具区
static RECT rcDrawing; //绘画区
static int nWindthRcTool = 80; //工具区宽度(上下高度)
static int nPaperBorderSapce = 10;//绘画区边界宽度
然后获取客户区的大小,定义一个函数
//获取客户区的大小
void GetCurClientSize(HWND hWnd)
{
GetClientRect(hWnd, &rcClient);
}
在获取客户区大小之后,我们就可以对客户区进行分割,
//初始化界面
//1.上部分为画笔颜色,形状,即工具栏
//2.下部分为绘画区
LRESULT InitWindows(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
//工具区大小
rcTools = rcClient;
rcTools.bottom = nWindthRcTool;
//工具区背景
SelectObject(ps.hdc, GetStockObject(DC_BRUSH));
SetDCBrushColor(ps.hdc, RGB(243, 243, 243));
Rectangle(ps.hdc, rcTools.left, rcTools.top, rcTools.right, rcTools.bottom);
//绘画区大小
rcDrawing = rcClient;
rcDrawing.top = nWindthRcTool;
//设置绘画区为带边界的纸张效果
RECT rcImage(rcDrawing);
SelectObject(ps.hdc, GetStockObject(DC_BRUSH));
SetDCBrushColor(ps.hdc, RGB(128, 138, 135));
Rectangle(ps.hdc, rcDrawing.left, rcDrawing.top, rcDrawing.right, rcDrawing.bottom);
//中间区域为白色
//左上角坐标(left,top),右下角(right,bottom)
rcImage.left += nPaperBorderSapce;//左侧边框
rcImage.top += nPaperBorderSapce;//顶部边框
rcImage.right -= nPaperBorderSapce;//右侧边框
rcImage.bottom -= nPaperBorderSapce;//底部边框
SelectObject(ps.hdc, GetStockObject(DC_BRUSH));
SetDCBrushColor(ps.hdc, RGB(255, 255, 255));
Rectangle(ps.hdc, rcImage.left, rcImage.top, rcImage.right, rcImage.bottom);
EndPaint(hwnd, &ps);
return TRUE;
}
我们知道窗口创的时候会调用WM_CREATE消息,因此我们在之前的WindowProc回调函数中,添加一个WM_CREATE消息, 然后将这两个函数放到WM_CREATE消息中执行,代码如下:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CLOSE:
//所有xxxWindow结尾的消息,不会进入消息队列,而是直接执行
DestroyWindow(hwnd); //DestroyWindow 发送另一个消息 WM_DESTROY
break;
case WM_DESTROY:
PostQuitMessage(NULL);
break;
case WM_CREATE:
GetCurClientSize(hwnd);
InitWindows(hwnd, uMsg, wParam, lParam);
break;
default:
break;
}
//返回值默认处理方式
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
运行一下试试看,不出意外可以得到下面的效果。
为什么没有得到我们想要的结果?
WM_CREATE与WM_PAINT之间是什么关系
wm_paint 消息详细解析
这是因为我们对客户区进行分割,染色是对窗口进行的重绘操作,而并不是所谓的“初始化操作”,在我们进行操作之前,系统已经帮我们创造好了窗口,此时我们对窗口的这些绘制操作放在WM_CREATE中是无效的。因此我们需要将这些操作放到WM_PAINT中执行。这样我们想要的效果就出现了。
此时你放大缩小,隐藏一下窗口看一下。依然是这个效果,这是因为窗口放大缩小这类操作为触发WM_PAINT消息,发生重绘,然后再执行一次我们写的代码。
接来下我们要在工具栏区创建那些五颜六色的方块。考虑到以后我们需要可以点击某种颜色的方块,然后就可以画某种颜色的图形,需要一个点击事件,因此我们可以用创建“按钮”控件来实现。
首先定义22个宏,代表20个可选择的颜色方块,和一个已选择的颜色及“颜色”标识。
#define BTNCOLOR_1 100
#define BTNCOLOR_2 101
#define BTNCOLOR_3 102
#define BTNCOLOR_4 103
#define BTNCOLOR_5 104
#define BTNCOLOR_6 105
#define BTNCOLOR_7 106
#define BTNCOLOR_8 107
#define BTNCOLOR_9 108
#define BTNCOLOR_10 109
#define BTNCOLOR_11 110
#define BTNCOLOR_12 111
#define BTNCOLOR_13 112
#define BTNCOLOR_14 113
#define BTNCOLOR_15 114
#define BTNCOLOR_16 115
#define BTNCOLOR_17 116
#define BTNCOLOR_18 117
#define BTNCOLOR_19 118
#define BTNCOLOR_20 119
#define BTNCOLOR 120 //颜色一控件
#define STACOLOR 121 //颜色一静态框
接下来定义一个函数,用于生成这些按钮。如下:
//创建工具栏
LRESULT CreateTools(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
//在工具栏创建二十种颜色的按钮
for (int i = BTNCOLOR_1, j = 0; i <= BTNCOLOR_10; ++i, ++j)
{
CreateWindow(TEXT("button"), TEXT(""), WS_CHILD | WS_VISIBLE | BS_FLAT | BS_OWNERDRAW, 60 + 35 * j, 10, 25, 25, hwnd, (HMENU)i, ((LPCREATESTRUCT)lParam)->hInstance, 0);
}
for (int i = BTNCOLOR_11, j = 0; i <= BTNCOLOR_20; ++i, ++j)
{
CreateWindow(TEXT("button"), TEXT(""), WS_CHILD | WS_VISIBLE | BS_FLAT | BS_OWNERDRAW, 60 + 35 * j, 10 + 35, 25, 25, hwnd, (HMENU)i, ((LPCREATESTRUCT)lParam)->hInstance, 0);
}
//创建已选择的颜色画板
static HWND hBtnColor = CreateWindow(TEXT("button"), TEXT(""), WS_OVERLAPPED | WS_CHILD | WS_VISIBLE | BS_OWNERDRAW | BS_NOTIFY | BS_PUSHBUTTON, 10, 10, 30, 40, hwnd, (HMENU)BTNCOLOR, ((LPCREATESTRUCT)lParam)->hInstance, 0);
CreateWindow(TEXT("static"), TEXT("颜色"), WS_CHILD | WS_VISIBLE | SS_CENTER, 10, 55, 30, 20, hwnd, (HMENU)STACOLOR, ((LPCREATESTRUCT)lParam)->hInstance, 0);
//创建分割线
CreateWindow(TEXT("button"), TEXT(""), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 410, 0, 1, 80, hwnd, (HMENU)1000, ((LPCREATESTRUCT)lParam)->hInstance, 0);
return TRUE;
}
将此函数放置到WM_CREATE消息(按钮也是一种窗口,由于是创建子窗口,因此放到WM_CREATE)中,运行一下可以得到下面的结果:
此时你仔细看,按钮其实是已经创建出来了,只不过不太明显而已,这是因为创建按钮时用了一些特殊的风格。
接下来就是对按钮进行染色。在此之前,我们需要知道是哪些颜色,为此我们定义
//二十种不用颜色
static COLORREF color[] =
{
RGB(0, 0, 0),
RGB(128, 128, 128),
RGB(128, 0, 0),
RGB(255, 0, 0),
RGB(255, 128, 0),
RGB(255, 255, 0),
RGB(0, 128, 0),
RGB(0, 128, 255),
RGB(0, 0, 128),
RGB(255, 255, 255),
RGB(128, 0, 128),
RGB(192, 192, 192),
RGB(128, 64, 0),
RGB(255, 128, 255),
RGB(255, 128, 0),
RGB(239, 228, 178),
RGB(128, 255, 0),
RGB(0, 255, 255),
RGB(128, 255, 255),
RGB(200, 191, 231),
RGB(255, 255, 255),
RGB(255, 255, 255)
};
第一个颜色块是有一个默认颜色的,因此定义
HBRUSH hBrush;
COLORREF selColor = RGB(0, 0, 0);//已选择的颜色,默认为黑色
然后在WindowProc回调函数中,添加一个WM_CTLCOLORBTN消息,并在其中添加如下代码:
case WM_CTLCOLORBTN: //设置工具栏颜色
{
hdc = (HDC)wParam;
int id = GetWindowLong((HWND)lParam, GWL_ID);
if (id == BTNCOLOR)
{
SetBkColor(hdc, selColor);
hBrush = CreateSolidBrush(selColor);
return(LONG)hBrush;
}
for (int i = BTNCOLOR_1, j = 0; i <= BTNCOLOR_20; ++i, ++j)
{
if (id == i)
{
SetBkColor(hdc, color[j]);
hBrush = CreateSolidBrush(color[j]);
return (LONG)hBrush;
}
}
}
break;
之后我们会陆续完成画图形,按钮点击事件。这次的完整代码(包含上一次)。未完待续。。。
#include //底层实现窗口的头文件
#define BTNCOLOR_1 100
#define BTNCOLOR_2 101
#define BTNCOLOR_3 102
#define BTNCOLOR_4 103
#define BTNCOLOR_5 104
#define BTNCOLOR_6 105
#define BTNCOLOR_7 106
#define BTNCOLOR_8 107
#define BTNCOLOR_9 108
#define BTNCOLOR_10 109
#define BTNCOLOR_11 110
#define BTNCOLOR_12 111
#define BTNCOLOR_13 112
#define BTNCOLOR_14 113
#define BTNCOLOR_15 114
#define BTNCOLOR_16 115
#define BTNCOLOR_17 116
#define BTNCOLOR_18 117
#define BTNCOLOR_19 118
#define BTNCOLOR_20 119
#define BTNCOLOR 120 //颜色一控件
#define STACOLOR 121 //颜色一静态框
//二十种不用颜色
static COLORREF color[] =
{
RGB(0, 0, 0),
RGB(128, 128, 128),
RGB(128, 0, 0),
RGB(255, 0, 0),
RGB(255, 128, 0),
RGB(255, 255, 0),
RGB(0, 128, 0),
RGB(0, 128, 255),
RGB(0, 0, 128),
RGB(255, 255, 255),
RGB(128, 0, 128),
RGB(192, 192, 192),
RGB(128, 64, 0),
RGB(255, 128, 255),
RGB(255, 128, 0),
RGB(239, 228, 178),
RGB(128, 255, 0),
RGB(0, 255, 255),
RGB(128, 255, 255),
RGB(200, 191, 231),
RGB(255, 255, 255),
RGB(255, 255, 255)
};
HBRUSH hBrush;
COLORREF selColor = RGB(0, 0, 0);//已选择的颜色,默认为黑色
RECT rcClient; //客户区
RECT rcTools; //工具区
RECT rcDrawing; //绘画区
int nWindthRcTool = 80; //工具区宽度
int nPaperBorderSapce = 10;//绘画区边界宽度
//获取客户区的大小
static void GetCurClientSize(HWND hWnd)
{
GetClientRect(hWnd, &rcClient);
}
//初始化界面
//1.上部分为画笔颜色,形状,即工具栏
//2.下部分为绘画区
LRESULT InitWindows(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
//工具区大小
rcTools = rcClient;
rcTools.bottom = nWindthRcTool;
//工具区背景
SelectObject(ps.hdc, GetStockObject(DC_BRUSH));
SetDCBrushColor(ps.hdc, RGB(243, 243, 243));
Rectangle(ps.hdc, rcTools.left, rcTools.top, rcTools.right, rcTools.bottom);
//绘画区大小
rcDrawing = rcClient;
rcDrawing.top = nWindthRcTool;
//设置绘画区为带边界的纸张效果
RECT rcImage(rcDrawing);
SelectObject(ps.hdc, GetStockObject(DC_BRUSH));
SetDCBrushColor(ps.hdc, RGB(128, 138, 135));
Rectangle(ps.hdc, rcDrawing.left, rcDrawing.top, rcDrawing.right, rcDrawing.bottom);
//中间区域为白色
//左上角坐标(left,top),右下角(right,bottom)
rcImage.left += nPaperBorderSapce;//左侧边框
rcImage.top += nPaperBorderSapce;//顶部边框
rcImage.right -= nPaperBorderSapce;//右侧边框
rcImage.bottom -= nPaperBorderSapce;//底部边框
SelectObject(ps.hdc, GetStockObject(DC_BRUSH));
SetDCBrushColor(ps.hdc, RGB(255, 255, 255));
Rectangle(ps.hdc, rcImage.left, rcImage.top, rcImage.right, rcImage.bottom);
EndPaint(hwnd, &ps);
return TRUE;
}
//创建工具栏
LRESULT CreateTools(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
//在工具栏创建二十种颜色的按钮
for (int i = BTNCOLOR_1, j = 0; i <= BTNCOLOR_10; ++i, ++j)
{
CreateWindow(TEXT("button"), TEXT(""), WS_CHILD | WS_VISIBLE | BS_FLAT | BS_OWNERDRAW, 60 + 35 * j, 10, 25, 25, hwnd, (HMENU)i, ((LPCREATESTRUCT)lParam)->hInstance, 0);
}
for (int i = BTNCOLOR_11, j = 0; i <= BTNCOLOR_20; ++i, ++j)
{
CreateWindow(TEXT("button"), TEXT(""), WS_CHILD | WS_VISIBLE | BS_FLAT | BS_OWNERDRAW, 60 + 35 * j, 10 + 35, 25, 25, hwnd, (HMENU)i, ((LPCREATESTRUCT)lParam)->hInstance, 0);
}
//创建已选择的颜色画板
static HWND hBtnColor = CreateWindow(TEXT("button"), TEXT(""), WS_OVERLAPPED | WS_CHILD | WS_VISIBLE | BS_OWNERDRAW | BS_NOTIFY | BS_PUSHBUTTON, 10, 10, 30, 40, hwnd, (HMENU)BTNCOLOR, ((LPCREATESTRUCT)lParam)->hInstance, 0);
CreateWindow(TEXT("static"), TEXT("颜色"), WS_CHILD | WS_VISIBLE | SS_CENTER, 10, 55, 30, 20, hwnd, (HMENU)STACOLOR, ((LPCREATESTRUCT)lParam)->hInstance, 0);
//创建分割线
CreateWindow(TEXT("button"), TEXT(""), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 410, 0, 1, 80, hwnd, (HMENU)1000, ((LPCREATESTRUCT)lParam)->hInstance, 0);
return TRUE;
}
//6、窗口过程
/*
HWND hwnd, 消息所属的窗口句柄
UINT uMsg, 具体消息名称 WM_xxxxxxxx 消息名
WPARAM wParam, 键盘附加消息
LPARAM lParam 鼠标附加消息
*/
// #define CALLBACK __stdcall
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HDC hdc;
switch (uMsg)
{
case WM_CLOSE:
//所有xxxWindow结尾的消息,不会进入消息队列,而是直接执行
DestroyWindow(hwnd); //DestroyWindow 发送另一个消息 WM_DESTROY
break;
case WM_DESTROY:
PostQuitMessage(NULL);
break;
case WM_CREATE:
CreateTools(hwnd, uMsg, wParam, lParam);
break;
case WM_CTLCOLORBTN: //设置工具栏颜色
{
hdc = (HDC)wParam;
int id = GetWindowLong((HWND)lParam, GWL_ID);
if (id == BTNCOLOR)
{
SetBkColor(hdc, selColor);
hBrush = CreateSolidBrush(selColor);
return(LONG)hBrush;
}
for (int i = BTNCOLOR_1, j = 0; i <= BTNCOLOR_20; ++i, ++j)
{
if (id == i)
{
SetBkColor(hdc, color[j]);
hBrush = CreateSolidBrush(color[j]);
return (LONG)hBrush;
}
}
}
break;
case WM_PAINT:
GetCurClientSize(hwnd);
InitWindows(hwnd, uMsg, wParam, lParam);
break;
default:
break;
}
//返回值默认处理方式
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
//Windows程序入口函数
//WINAPI代表_stdcall(一个宏) 参数的传递顺序:从右到左 依次入栈,并且在函数返回前 清空堆栈
//hInstance 当前应用程序实例句柄
//hPrevInstance 上一个应用程序句柄,在哪win32 环境下,参数一般为NULL
//lpCmdLine 即 char* argv[]
//nShowCmd 显示方式,例如最大化,最小化,正常
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
//1、设计窗口
//2、注册窗口
//3、创建窗口
//4、显示和更新
//5、通过循环取消息
//6、处理消息(窗口过程)
//1、设计窗口
WNDCLASS wnc;//窗口类
wnc.cbClsExtra = 0;//类 额外内存
wnc.cbWndExtra = 0;//窗口 额外内存
wnc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//背景
wnc.hCursor = LoadCursor(NULL, IDC_ARROW); //光标。第一个参数为NULL,代表使用系统提供的光标
wnc.hIcon = LoadIcon(NULL, IDI_APPLICATION); //图标
wnc.hInstance = hInstance;//应用程序的实例句柄,传入WinMain中的形参
wnc.lpfnWndProc = WindowProc; //窗口过程函数,回调函数
wnc.lpszClassName = TEXT("WIN");//窗口的类名称
wnc.lpszMenuName = NULL;//菜单名
wnc.style = CS_HREDRAW | CS_VREDRAW;//横向重绘和纵向重绘,设置之后才会出现WM_PAINT消息
//2、注册窗口
RegisterClass(&wnc);
//3、创建窗口
/*
lpClassName 类名,和上面保持一致
lpWindowName 窗口标题名称
dwStyle 风格 WS_OVERLAPPEDWINDOW混合风格
x 坐标 CW_USEDEFAULT默认值
y 坐标
nWidth 宽
nHeight 高
hWndParent 父窗口
hMenu 菜单
hInstance 当前实例句柄
lpParam 附加值 鼠标附加值
*/
HWND hwnd = CreateWindow(wnc.lpszClassName, TEXT("绘画板-Win32 by 小黄"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, wnc.hInstance, NULL);
//4、显示和更新
ShowWindow(hwnd, SW_SHOWNORMAL);
UpdateWindow(hwnd);
//5、通过循环取消息
/*
MSG 结构体
HWND hwnd; 主窗口句柄
UINT message; 具体的消息
WPARAM wParam; 附加消息,通常是键盘消息
LPARAM lParam; 附加消息,通过是鼠标消息
DWORD time; 消息产生的时间
POINT pt; 附加消息,鼠标坐标信息
*/
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) //循环获取消息
{
/*
LPMSG lpMsg, // address of structure with message
HWND hWnd, // handle of window,为NULL时,代表接收所有的窗口的消息
UINT wMsgFilterMin, // first message ,最大和最小的过滤的消息,一般填入0
UINT wMsgFilterMax // last message. 填0代表接收所有的消息
*/
//翻译消息
//有的消息不能直接执行,
//比如ctrl+C是复制消息,因此需要翻译,并且重新进入消息队列排队
TranslateMessage(&msg);
//分发消息
DispatchMessage(&msg);
}
//6、窗口过程
//对回调函数的编写
return 0;
}