win32-带你一步一步编写绘画板(2) by小黄

win32-带你一步一步编写绘画板(2)

  • 一、效果预览
  • 二、初始化界面
  • 三、创建工具栏
  • 四、工具栏染色
  • 五、总结

承接上文-win32-带你一步一步编写绘画板(1)

一、效果预览

在上次我们已经创建好了窗口,接下来我们将要在原有窗口上编写绘画板,首先是展示我们这次编写之后所达到的效果,看图,
win32-带你一步一步编写绘画板(2) by小黄_第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;
}
  • 1、工具区的高度是我们先前定义的宽度nWindthRcTool =
    80,其他三个参数和客户区一致,同理,绘画区的高度是客户区高度减去工具区的高度,也就是左上角顶点的top = nWindthRcTool。
  • 2、由于工具栏的20种可选颜色中有白色,而工具栏区的背景颜色也为白色,不易区分。因此需要对工具栏区的背景进行染色,代码如工具区背景所示。
  • 3、对绘画区(Drawing)进行装饰,使其显示为带边界的纸张效果。其原理是首先将这个绘画区染成边界的颜色,然后将中间区域染成白色。

我们知道窗口创的时候会调用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);
}

运行一下试试看,不出意外可以得到下面的效果。
win32-带你一步一步编写绘画板(2) by小黄_第2张图片
为什么没有得到我们想要的结果?
WM_CREATE与WM_PAINT之间是什么关系
wm_paint 消息详细解析
这是因为我们对客户区进行分割,染色是对窗口进行的重绘操作,而并不是所谓的“初始化操作”,在我们进行操作之前,系统已经帮我们创造好了窗口,此时我们对窗口的这些绘制操作放在WM_CREATE中是无效的。因此我们需要将这些操作放到WM_PAINT中执行。这样我们想要的效果就出现了。
win32-带你一步一步编写绘画板(2) by小黄_第3张图片
此时你放大缩小,隐藏一下窗口看一下。依然是这个效果,这是因为窗口放大缩小这类操作为触发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)中,运行一下可以得到下面的结果:
win32-带你一步一步编写绘画板(2) by小黄_第4张图片
此时你仔细看,按钮其实是已经创建出来了,只不过不太明显而已,这是因为创建按钮时用了一些特殊的风格。

四、工具栏染色

接下来就是对按钮进行染色。在此之前,我们需要知道是哪些颜色,为此我们定义

//二十种不用颜色
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;

运行看看,就是我们想要的效果。
win32-带你一步一步编写绘画板(2) by小黄_第5张图片

五、总结

之后我们会陆续完成画图形,按钮点击事件。这次的完整代码(包含上一次)。未完待续。。。

#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;
}

你可能感兴趣的:(visual,studio,c++,c语言,经验分享)