【计算机图形学】创建和显示窗口并实现简易画笔

1、注册窗口类

  • 我们需要基于窗口类来定义窗口的行为,窗口类是一种数据结构,需要在运行中向系统进行注册。为了注册新的窗口类,我们首先需要定义一个WNDCLASS结构。
// Register the window class.
	const wchar_t CLASS_NAME[] = L"Sample Window Class";

	WNDCLASS wc = { };

	wc.lpfnWndProc = WindowProc;
	wc.hInstance = hInstance;
	wc.lpszClassName = CLASS_NAME;

定义完成之后,我们使用RegisterClass来注册窗口类

RegisterClass(&wc);
  • 定义窗口类之后,我们需要调用CreateWindowEx 函数来创建窗口的新实例:
HWND hwnd = CreateWindowEx(
		0,                              // Optional window styles.
		CLASS_NAME,                     // Window class
		L"Learn to Program Windows",    // Window text
		WS_OVERLAPPEDWINDOW,            // Window style

		// Size and position
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

		NULL,       // Parent window    
		NULL,       // Menu
		hInstance,  // Instance handle
		NULL        // Additional application data
	);

if (hwnd == NULL)
{
    return 0;
}

CreateWindowEx 返回新窗口的句柄,如果函数失败,则返回零。为了显示窗口,将窗口句柄传递给ShowWindow 函数:

ShowWindow(hwnd, nCmdShow);

2、窗口消息

对于GUI应用程序来说,程序必须响应来自用户和操作系统的事件。来自用户的事件包括人可以与程序交互的所有方式:鼠标单击、击键、触摸屏手势等。来自操作系统的事件包括可能影响程序行为方式的程序“外部”的任何内容。例如,用户可能会插入新的硬件设备,或者 Windows 可能会进入低功耗状态(睡眠或休眠)。为了响应这些事件,Windows 使用了消息传递模型。操作系统通过向其传递消息来与您的应用程序窗口进行通信,消息只是指定特定事件的数字代码。例如,如果用户按下鼠标左键,则窗口会收到一条消息代码WM_LBUTTONDOWN

  • 定义一个消息循环,来处理这些信息。
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

3、编写窗口

窗口过程(Window Procedure) 定义了窗口的大部分行为,我们在注册窗口类的时候定义的

WindowProc 就是执行窗口过程函数的指针。在消息循环中捕获到 msg 后,程序会调用窗口过程来进行消息处理,接下来我们需要完成之前声明的 WindowProc 的定义。我们可以将消息参数直接传递给DefWindowProc 函数,这个函数可以对消息执行默认操作。

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg) {
	case WM_DESTROY: // 捕获到窗口关闭事件则退出
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

将上述代码整合写入main.cpp文件,此时我们可以成功运行代码,得到一个空白窗口。

4、实现简易画笔

通过在窗口中设置像素来实现简易画笔,首先我们需要在WindowProc 接收鼠标左键按下的事件,然后对这个事件进行处理。

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
	LPARAM lParam) {
	switch (uMsg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_LBUTTONDOWN: // 捕获鼠标按下事件
		int pixelX = GET_X_LPARAM(lParam);
		int pixelY = GET_Y_LPARAM(lParam);
		HDC hdc = GetDC(hwnd); // 获取当前 device context(DC)
		SetPixel(hdc, pixelX, pixelY, RGB(210, 80, 50)); // RGB宏用于定义颜色
		ReleaseDC(hwnd, hdc);
		return 0;
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

运行代码之后,按下鼠标左键,我们就可以在窗口对应的位置绘制像素点。

为了实现鼠标左键移动并移动的时候可以绘制线条,我们可以在函数内部设置一个静态局部变量is_draw来检测鼠标左键的状态,在左键按下时将is_draw设置为true,左键放开时设置为false, 同时 WM_MOUNSEMOVE 会捕获鼠标移动事件,在 is_drawtrue 时在鼠标位置 SetPixel

将上述代码修改:

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static bool isDraw = false;
	switch (uMsg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		
	case WM_LBUTTONUP:
		isDraw = false;
		return 0;

	case WM_LBUTTONDOWN:
		isDraw = true;

	case WM_MOUSEMOVE:
		if (isDraw) {
			int pixelX = GET_X_LPARAM(lParam);
			int pixelY = GET_Y_LPARAM(lParam);
			HDC hdc = GetDC(hwnd); // 获取当前窗口 HDC
			SetPixel(hdc, pixelX, pixelY, RGB(210, 80, 50));
			ReleaseDC(hwnd, hdc);
		}
		return 0;

	case WM_PAINT:
		PAINTSTRUCT ps;
		HDC hdc = BeginPaint(hwnd, &ps);
		FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));

		EndPaint(hwnd, &ps);
		return 0;
	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

实验的关键在于检测鼠标状态,通过设置状态变量is_draw实现状态的检测, 在左键左键按下时将is_draw设置为true,左键放开时设置为false, 在 is_drawtrue 时在鼠标位置 SetPixel,这样我们就可以在窗口对应的位置连续绘制像素点,从而实现简易画笔。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值