win32Day05:GDI绘图

本文详细介绍GDI编程的基础知识,包括画像素点、线条、图形、文字及图片的绘制方法,探讨了设备环境、画笔、画刷的概念及其使用,同时介绍了二级缓存和三级缓存的防闪烁技术在游戏开发中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

0x00画一个像素点:

0x01画线:

0x03画图形:圆形矩形等

0x04在客户区绘制文字:

0x05三级缓存防闪烁


GDI:Graphical Device Interface 图形设备接口(windows上用来控制图形设备的一些函数),设备指显卡 显示器等硬件设备。

控制硬件设备的程序称为驱动程序。操作系统层有一些GDI GUI COM的库。应用程序则是程序员写的。

GDI的作用:可以让程序员在写代码的时候不用关心设备,只要调用GDI中的函数就行。

设备环境:(设备上下文DC:Device Context)就是屏幕

DC的分类:

  1. 整体窗口的设备DC(类型为HDC)
  2. 兼容设备DC:用来做缓冲的。例如你有两张图,需要先将两张图混合成一张,即这两张图需要先贴到兼容设备DC之上,然后将兼容设备DC贴到设备DC之上。

得到设备DC的两种方式:

  1. BeginPaint和EndPaint函数对:当窗口需要重绘时,即当客户区上出现“无效矩形”(需要重绘的矩形,比方说客户区有一部分被另一个窗口遮住后另一窗口又移开。这时重见天日的被遮部分就是无效矩形。)会自动发送WM_PAINT消息。如果程序员没有自己设计WM_PAINT消息,那么就会调用默认消息处理函数的WM_PAINT消息的处理方式,如下:
    1. 	case WM_PAINT:{
      			PAINTSTRUCT ps;
      			HDC hdc = BeginPaint(hwnd,&ps);
      			EndPaint(hwnd,&ps);
      			break;
      		}

      BeginPaint函数会用注册窗口类时注册的画刷(通常是白色),来擦除无效矩形的背景,即用该画刷来重绘无效矩形,这样无效矩形就会变成有效矩形,就不会再触发WM_PAINT消息。

    2. BeginPaint的返回值便是设备环境句柄,但是它的绘制区域仅仅是无效矩形。即BeginPaint返回的句柄只能用来在无效矩形区域中绘制。也就是说BeginPaint只能在WM_PAINT消息中使用,因为只有收到这个消息才意味着存在无效矩形,如果是其他消息,那么就不存在无效矩形,BeginPaint就无用武之地。

    3. 而GetDc返回的设备环境句柄,它的裁剪区域是整个客户区,这意味着使用该句柄的GDI函数可以在整个客户区绘制。

    4. BeginPaint只能在WM_PAINT消息中使用,而GetDc可以在鼠标消息,键盘消息等其他消息中都可以使用。

  2. 在WM_PAINT消息(窗口重绘时自动发送)的消息处理函数中:用BeginPaint(拿到设备DC)和 EndPaint(把设备DC放回去),BeginPaint会从消息队列里删除WM_PAINT消息。不会闪烁。不一定重新绘制。
  3. 如果在WM_PAINT消息的消息处理函数外:用GetDc 和 ReleaseDc。不会从消息队列中删除WM_PAINT消息。会闪烁。一定重新绘制

用二级缓存、甚至三级缓存去防闪烁。

0x00画一个像素点:

	case WM_PAINT:{
			PAINTSTRUCT ps;
			HDC hdc = BeginPaint(hwnd,&ps);
			SetPixel(hdc,30,30,RGB(255,0,0));
			EndPaint(hwnd,&ps);
			break;
		}

0x01画线:

		case WM_PAINT:{
		#if 1
			PAINTSTRUCT ps;
			HDC hdc = BeginPaint(hwnd,&ps);
			MoveToEx(hdc,0,0,NULL);//设置焦点(起点)
			LineTo(hdc,100,100);//画线
			EndPaint(hwnd,&ps);
		#endif
		
		}

如果想画其他颜色的线条,该怎么办呢?这就需要使用到画笔,使用画笔分为4步:(如果我们没有设置画笔,那么会使用系统默认的画笔。)

  1. 画笔:用来画简单的图形(由点和线组成的简单图形),即自己在程序中手写图形
    1. 画笔使用模型:
      1. 创建画笔:CreatePen
      2. 设置画笔到DC中 SelectObject:这里之所以是SelectObject 而不是SelectPen,是因为选择画笔选择画刷都是使用这个函数。这个函数就是一个接口。我们不需要画笔搞一个函数,画刷也搞一个函数。
      3. 画图 LineTo SetPixel Ellipse DrawRect
      4. 还原成以前的画笔:SelecObject;
      5. 删除画笔 DeleteObject;
  2. 	case WM_PAINT:{
    		#if 1
    			PAINTSTRUCT ps;
    			HDC hdc = BeginPaint(hwnd,&ps);
    			HPEN hPenSolid = CreatePen(PS_SOLID,3,RGB(255,0,0));
    			HPEN hPenDot = CreatePen(PS_DOT,1,RGB(0,0,255));
    			HGDIOBJ hPenOld = SelectObject(hdc,hPenSolid);
    			MoveToEx(hdc,0,0,NULL);
    			LineTo(hdc,100,100);
    			HGDIOBJ hPenNew = SelectObject(hdc,hPenDot);
    			MoveToEx(hdc,200,200,NULL);
    			LineTo(hdc,100,100);
    			EndPaint(hwnd,&ps);
    		#endif
    		
    		}

    0x03画图形:圆形矩形等

    1. 画图形需要使用到画刷:
      1. 创建画刷
        1. CreateSolidBrush 实心画刷
        2. CreateHatchBrush 阴影画刷
      2. 设置当前画刷 SelectObject
      3. 画图
      4. 还原 SelectObject
      5. 删除画刷 DeleteObject
    2. 画阴影矩形:
      		case WM_PAINT:{
      			PAINTSTRUCT ps;
      			HDC hdc = BeginPaint(hwnd,&ps);
      			HBRUSH hBrushSolid = CreateSolidBrush(RGB(0,0,255));
      			HBRUSH hBrushHatch = CreateHatchBrush(HS_DIAGCROSS,RGB(0,251,252));
      			SelectObject(hdc,hBrushHatch);
      			Rectangle(hdc,10,10,300,300);
      			EndPaint(hwnd,&ps);
      		
      		}
      		

      可以实现的项目:

      1. 画一个可移动的乌龟

      2. 画一个眼睛,鼠标点一下闭眼,鼠标再点一下开眼。

    3. 截屏功能:
      #if 1
      		case WM_COMMAND:{
      			int wmId = LOWORD(wParam);
      			int wmEvent = HIWORD(wParam);
      			switch(wmId){
      				case 2001:
      					if(wmEvent == STN_CLICKED){
      						HDC hdc = GetDC(hwnd);
      						HDC hScreenDc = CreateDC("DISPLAY",NULL,NULL,NULL);
      //获取当前屏幕句柄
      						BitBlt(hdc,0,0,300,300,hScreenDc,30,30,SRCCOPY);
      						//从屏幕hScreenDc 的30,30开始取大小的为(0,0,300,300)的截图,贴到hdc上。 
      						ReleaseDC(hwnd,hdc);
      					}
      				break;
      			}
      			break;
      		} 
      		#endif

      可以做一个截图软件:当点击截图按钮后,就实时动态的获取当前鼠标坐标,当鼠标右键按下,再松开的时候,就计算矩形的大小。调用BitBlt函数。

    4. 绘制图片:
      1. 注意:win32中能用的图片只有bmp格式的bitmap picture位图。jpg格式的图片可以通过画图软件转化为bmp格式。
      2. 思路:下面采用加载资源的方式,加载位图,首先,将图片添加为资源。图片资源是不能直接贴到客户区的设备上下文中的。(如何导入位图:视图>>解决方案资源管理器>>导入(选所有文件才能看到位图),然后在头文件>>Resource.h中可以看到导入的位图ID)
        1. 需要先创建一个兼容的设备上下文,
        2. 将图片选到兼容设备上下文中,
        3. 然后将兼容设备上下文贴到客户区的设备上下文中。
          1. void draw(HWND hwnd) {
            	HDC hdc = GetDC(hwnd);
            	bk = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_YANG));
            	RECT rect;
            	GetWindowRect(hwnd,&rect);
            	int width = rect.right - rect.left;
            	int height = rect.bottom - rect.top;
            	HDC hCompatibalDc = CreateCompatibleDC(hdc);//创建兼容DC
            	SelectObject(hCompatibalDc, bk);//将背景图片选到兼容DC中
            	BitBlt(hdc, 0, 0, width,height ,hCompatibalDc, 0, 0, SRCCOPY);//将兼容DC贴到DC中
            	ReleaseDC(hwnd, hdc);
            	return;
            }
            
            
            //消息处理函数
                  case WM_PAINT:
                    {
                        PAINTSTRUCT ps;
                        HDC hdc = BeginPaint(hWnd, &ps);
            	    draw(hWnd);
                        EndPaint(hWnd, &ps);
                    }
                    break;

            兼容设备环境可以理解为包裹图片的一层泡沫,直接将图片贴到客户区设备环境(看成一个箱子)无法实现,但是给图片包上一层泡沫后,图片能完美地嵌入到箱子中去了。

0x04在客户区绘制文字:

重绘窗口时绘制文字

方法一:

	case WM_PAINT:{
			PAINTSTRUCT ps;
			HDC hdc = BeginPaint(hwnd,&ps);
			TextOut(hdc,30,30,"西电",strlen("西电")); 
			EndPaint(hwnd,&ps);
			break;
		}

方法二:

case WM_PAINT: {
		PAINTSTRUCT ps;
		RECT rect;
		GetClientRect(hWnd, &rect);
		HDC hDc = BeginPaint(hWnd, &ps);
		DrawText(hDc, TEXT("Hello Windows"), -1, &rect, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
		EndPaint(hWnd, &ps);
	}
	break;

在其他消息触发时绘制文字:比如当点击按钮时绘制文字

case WM_COMMAND:{
			int wmId = LOWORD(wParam);
			int wmEvent = HIWORD(wParam);
			switch(wmId){
				case 2001:
					if(wmEvent == STN_CLICKED){
						HDC hDC = GetDC(hwnd);
						TextOut(hDC,30,80,"于锦菲",strlen("于锦菲")); 
						ReleaseDC(hwnd,hDC);
					}
				break;
			}
			break;
		} 

0x05二级缓冲实现游戏人物移动

全局变量:

#define PERSON_WIDTH 480/8
#define PERSON_HEIGHT 216/2 
HANDLE move[4];
HDC hDc;
HDC hBuffDc;//兼容DC
HDC hTempDc;//第二个兼容DC
int x = 300;
int y = 300;//人物位置
int idx = 0;
int n = 1;

WM_CREATE:

void OnCreate(HWND hwnd) {
	hDc = GetDC(hwnd);
	bk = LoadImage(hInst, TEXT("bk.bmp"), IMAGE_BITMAP, 641, 480, LR_LOADFROMFILE);
	hBuffDc = CreateCompatibleDC(hDc);
	SelectObject(hBuffDc, bk);
	move[0] = LoadImage(hInst, TEXT("move0.bmp"), IMAGE_BITMAP, 480, 216, LR_LOADFROMFILE);
	move[1] = LoadImage(hInst, TEXT("move1.bmp"), IMAGE_BITMAP, 480, 216, LR_LOADFROMFILE);
	move[2] = LoadImage(hInst, TEXT("move2.bmp"), IMAGE_BITMAP, 480, 216, LR_LOADFROMFILE);
	move[3] = LoadImage(hInst, TEXT("move3.bmp"), IMAGE_BITMAP, 480, 216, LR_LOADFROMFILE);
	hTempDc = CreateCompatibleDC(hDc);
	
}
case WM_CREATE:
		SetTimer(hWnd, 1001, 50, NULL);
		OnCreate(hWnd);
		PlaySound(TEXT("C://kong.wav"), NULL, SND_FILENAME | SND_ASYNC);
		break;

WM_CREATE:

注意:加载位图有两种方式

方式1:加载资源方式:将位图先导入为资源,然后用LoadBitmap添加资源

方式2:图片文件方式:直接LoadImage通过文件名加载,这里采用图片文件方式加载背景图

如何贴透明图?

计算机用一个32位的二进制数来表示一种颜色。因为有32位,所以一共能表示2的32次方-1种颜色。

其中0xFFFF FFFF 表示白色,0x0000 0000 表示黑色。

与运算:

白色 & 任何颜色 = 任何颜色

黑色 & 任何颜色 = 黑色

或运算:

白色 | 任何颜色 = 白色

黑色 | 任何颜色 = 任何颜色

所以,我们的思路是:

1.先在背景图上贴上 人黑背白 的图,和背景图 AND与运算(SRCAND) ====》 背景保留,人物为黑

2.然后再在上面贴上 人彩背黑 的图,OR运算(SRCPAINT) ====》 背景保留,人物为彩

draw函数:

void draw(HWND hWnd) {
	SelectObject(hTempDc, move[n]);
	BOOL is = BitBlt(hDc, 0, 0, 641, 480, hBuffDc, 0, 0, SRCCOPY);
	BitBlt(hDc, x, y, PERSON_WIDTH, PERSON_HEIGHT, hTempDc, idx*PERSON_WIDTH, PERSON_HEIGHT, SRCAND);
	BitBlt(hDc, x, y, PERSON_WIDTH, PERSON_HEIGHT, hTempDc, idx*PERSON_WIDTH, 0, SRCPAINT);
	idx++;
	if (idx >= 8) idx = 0;
}

如何让人物变成动态?

用BitBlt在同一个位置不断贴不同动作的图。

可以将贴一次透明图封装在一个draw函数中,然后设置一个定时器,每过多少秒就draw一次,每次draw不同动作。

case WM_TIMER: {
		int id = LOWORD(wParam);
		switch (id) {
		case 1001:
			draw(hWnd);
			break;
		default:
			break;
		}
	
		break;

如何实现人物移动:

case WM_KEYDOWN: {
		switch (wParam) {
		case VK_UP:
			n = 0;
			y -= 3;
			break;
		case VK_LEFT:
			n = 2;
			x -= 3;
			break;
		case VK_DOWN:
			n = 1;
			y += 3;
			break;
		case VK_RIGHT:
			n = 3;
			x += 3;
			break;
		}
	}

0x06三级缓存防闪烁

void OnCreate(HWND hwnd) {
	hDc = GetDC(hwnd);
	bk = LoadImage(hInst, TEXT("bk.bmp"), IMAGE_BITMAP, 641, 480, LR_LOADFROMFILE);
	hBuffDc = CreateCompatibleDC(hDc);
	move[0] = LoadImage(hInst, TEXT("move0.bmp"), IMAGE_BITMAP, 480, 216, LR_LOADFROMFILE);
	move[1] = LoadImage(hInst, TEXT("move1.bmp"), IMAGE_BITMAP, 480, 216, LR_LOADFROMFILE);
	move[2] = LoadImage(hInst, TEXT("move2.bmp"), IMAGE_BITMAP, 480, 216, LR_LOADFROMFILE);
	move[3] = LoadImage(hInst, TEXT("move3.bmp"), IMAGE_BITMAP, 480, 216, LR_LOADFROMFILE);
	hTempDc = CreateCompatibleDC(hDc);
	HBITMAP hCompatibleBitmap = CreateCompatibleBitmap(hDc,GetSystemMetrics(SM_CXSCREEN),GetSystemMetrics(SM_CYSCREEN));
	//1.设置兼容位图到hBuffDc中
	SelectObject(hBuffDc, hCompatibleBitmap);
	
}
void draw(HWND hWnd) {
	//2 背景图设置到hTempDc中
	SelectObject(hTempDc, bk);
	//3 背景图画到hBuffDc中
	BitBlt(hBuffDc, 0, 0, 641, 480, hTempDc, 0, 0, SRCCOPY);
	//4 人物图设置到hTempDc中
	SelectObject(hTempDc, move[n]);
	//5 人物图画到hBufferDc中
	BitBlt(hBuffDc, x, y, PERSON_WIDTH, PERSON_HEIGHT, hTempDc, idx*PERSON_WIDTH, PERSON_HEIGHT, SRCAND);
	BitBlt(hBuffDc, x, y, PERSON_WIDTH, PERSON_HEIGHT, hTempDc, idx*PERSON_WIDTH, 0, SRCPAINT);
	idx++;
	if (idx >= 8) idx = 0;
	//6 hBufferDc画到hDc中
	BitBlt(hDc, 0, 0, 641, 480, hBuffDc, 0, 0, SRCCOPY);
}


 

ps:游戏产业

  • 游戏开发:指软件开发
    • 游戏策划:设计这个游戏怎么玩
    • 游戏美术:动漫师
    • 游戏软件开发
  • 游戏运营:指宣传推广盈利

作业:

使用字体:

创建字体:CreateFont

设置字体:SelectObject

选做:

用鼠标控制人物移动

#include <windows.h>
#include "stdafx.h"
#include "Resource.h"
#include <stdio.h>
#include <cstdio>
#include <string.h>
#include <cstring>

HINSTANCE g_hInstance;
HCURSOR g_hCursor1, g_hCursor2;//光标句柄
MSG msg;//声明一个MSG类型的消息结构体
HANDLE g_hConsole;//定义一个HANDLE类型的全局变量,之后要将命令行窗口句柄赋值给它,命名时养成习惯:如果是全局变量以g_开头命名它。
HMENU hRight;
int flag = 0;
int i = 0;
HDC hBuffDc;
HDC hMemoryDc;
HDC hDc;
HANDLE hBitmap[5];
HANDLE hBitmap1;
int x = 100;
int y = 100;
int n = 1;
void zhubei() {
	//1把背景图放入hBuffDc中
	SelectObject(hBuffDc, hBitmap1);
	//2把背景图画入memory中
	BitBlt(hMemoryDc, 0, 0, 641, 480, hBuffDc, 0, 0, SRCCOPY);
	//3把人物图画入memory中
	 //3.1人物图选入hBuffDc中
	SelectObject(hBuffDc, hBitmap[n]);
	//3.2人物图画入memory中
	BitBlt(hMemoryDc, x, y, 30, 108, hBuffDc, 60 * i, 108, SRCAND);
	BitBlt(hMemoryDc, x, y, 30, 108, hBuffDc, 60 * i, 0, SRCPAINT);
	i++;
	//4 把memory当图片画入hDc中
	BitBlt(hDc, 0, 0, 641, 480, hMemoryDc, 0, 0, SRCCOPY);

}
void tietu(HWND hWnd) {
	if (i >= 8) i = 0;
    hDc = GetDC(hWnd);
	//两个兼容DC
	hMemoryDc = CreateCompatibleDC(hDc);//用来放人物
	hBuffDc = CreateCompatibleDC(hDc);//用来放背景
	/*三级缓冲
	先把背景选到hBuffDc中,再把hBuffDc和人物贴到hMemoryDc中,然后把hMemoryDc贴到hDc(屏幕)中
	
	*/
	//创建兼容位图
	HBITMAP hCompatibleBitmap=CreateCompatibleBitmap(hDc, GetSystemMetrics(SM_CXFULLSCREEN), GetSystemMetrics(SM_CYFULLSCREEN));//创建到hDC中,宽高为窗口的宽,窗口的高
	//设置兼容位图到memoryDc
	SelectObject(hMemoryDc, hCompatibleBitmap);
	hBitmap[0] = LoadImage(g_hInstance, L"move0.bmp", IMAGE_BITMAP, 243, 212, LR_LOADFROMFILE);
	hBitmap[1] = LoadImage(g_hInstance, L"move1.bmp", IMAGE_BITMAP, 243, 212, LR_LOADFROMFILE);
	hBitmap[2] = LoadImage(g_hInstance, L"move2.bmp", IMAGE_BITMAP, 243, 212, LR_LOADFROMFILE);
	hBitmap[3] = LoadImage(g_hInstance, L"move3.bmp", IMAGE_BITMAP, 243, 212, LR_LOADFROMFILE);
	hBitmap1 = LoadImage(g_hInstance, L"bk.bmp", IMAGE_BITMAP, 641, 480, LR_LOADFROMFILE);

	zhubei();
	/*
	SelectObject(hMemoryDc, hBitmap);
	//贴到哪里 坐标(x,y) 宽高  从哪里贴,从哪的坐标 贴图方式    
	BitBlt(hDc, 80, 108,30,108,hMemoryDc,60*i,108, SRCAND);//将下方的黑色图片按位与去贴。那么黑色遇到任何颜色保持黑色。白色遇到任何颜色仍旧是任何颜色
	BitBlt(hDc, 80, 108, 30, 108, hMemoryDc, 60 * i, 0, SRCPAINT);//将上方的彩色图像按位或去贴。
	i++;
	*/
	/*
	  透明贴图先用SRCAND的方式去贴,然后用SRCPAINT的方式去贴
	  SRCAND 按位与方式,任何东西与上0(黑色)是0(黑色),任何东西与上1(白色)是任何东西自身(不变)。
	  SRCPAINT 按位或方式去贴,彩色 或 黑色= 彩色 黑色 或 任何东西 =任何东西。
	
	
	
	*/
}
//写一个windows窗口 
//5定义消息处理函数 
void OnCreate(HWND hWnd,WPARAM wParam,LPARAM lParam) {
#if 0
	//一 系统菜单
	HMENU hSystemMenu=GetSystemMenu(hWnd, 0);//获取系统菜单句柄
	//删除系统菜单项:系统菜单有七项,其中分隔符也算菜单项
	for (int i = 0; i < 6; i++) {
		DeleteMenu(hSystemMenu, 0, MF_BYPOSITION);//按照索引的方式删除,0表示当前状况的第一个,是变化的
		//DeleteMenu(hSystemMenu, 0, MF_BYCOMMAND);//按照下标的方式删除,0表示初期情况下菜单栏的第一个(即分隔符),是固定的。

	}
	//追加系统菜单项:追加就一定在最后追加
	AppendMenu(hSystemMenu, MF_SEPARATOR, 1111, NULL);//第二个参数决定菜单项的样式:分隔符(MF_SEPARATOR)还是字符串,如果第二个参数选分隔符,那么第三个参数就是分隔符的id
	AppendMenu(hSystemMenu, MFT_STRING, 1112, L"(ง •_•)ง(M)");//第二个参数如果是字符串,则最后一个参数为字符串的内容。
	//这里只能决定菜单的样子,而真正地处理才是关键。
	/*真正的处理在消息处理函数中,系统菜单的消息由WM_COMANND消息管理*/
#endif
	//二、顶层菜单
	HMENU hTopMenu = CreateMenu();//创建顶层菜单,返回顶层菜单的句柄
	//创建弹出式菜单
	HMENU hChinese = CreatePopupMenu();
	HMENU hJapan = CreatePopupMenu();
	HMENU hAmerican = CreatePopupMenu();
	HMENU hSearch = CreatePopupMenu();
	//将弹出式菜单添加到顶层菜单中
	AppendMenu(hTopMenu,MF_POPUP,(UINT)hChinese,L"国产");//第三个参数需要弹出式菜单的id,我们将句柄强制转化成id类型
	AppendMenu(hTopMenu, MF_POPUP, (UINT)hJapan,L"日本");
	AppendMenu(hTopMenu, MF_POPUP, (UINT)hAmerican,L"欧美");
	AppendMenu(hJapan, MF_POPUP, (UINT)hSearch,L"搜索");
	//添加菜单项到弹出式菜单中
	//添加到国产中
	AppendMenu(hChinese, MF_STRING,2511, L"土肥圆矮穷挫");//第三个参数为你设置的这个菜单项的id
	AppendMenu(hChinese, MF_STRING, 2512, L"艾栗栗");
	AppendMenu(hChinese, MF_STRING, 2513, L"萌琪琪");
	AppendMenu(hChinese, MF_STRING, 2514, L"张柏芝艳照门");
	//添加到日本中
	AppendMenu(hJapan, MF_STRING, 2521, L"波多野结衣");//第三个参数为你设置的这个菜单项的id
	AppendMenu(hJapan, MF_STRING, 2522, L"泷泽萝拉");
	AppendMenu(hJapan, MF_STRING, 2523, L"桃谷绘里香");
	AppendMenu(hJapan, MF_STRING, 2524, L"桃乃木香奈");
	AppendMenu(hJapan, MF_STRING, 2524, L"其他");
	AppendMenu(hJapan, MF_STRING, 2525, L"选中泷泽萝拉");

	//添加到欧美中
	AppendMenu(hAmerican, MF_STRING, 2531, L"安洁莉卡");
	//添加菜单项到搜索中
	AppendMenu(hSearch, MF_STRING, 2541, L"搜索番号");
	AppendMenu(hSearch, MF_STRING, 2541, L"搜索女优");
	AppendMenu(hSearch, MF_STRING, 2541, L"搜索男优");
	//显示顶层菜单
	SetMenu(hWnd, hTopMenu);
	//获取菜单项句柄,现在只有我自己设置的菜单项的id
	HMENU hTemp = GetSubMenu(hJapan, 0);//得到波多野结衣菜单项的句柄
    //设置菜单项
	EnableMenuItem(hJapan, 2521,MF_GRAYED);//第一个参数为菜单项所在弹出式菜单的句柄,第二参数该菜单项的id,设置菜单项“波多野结衣”为灰色
	//三、创建右键菜单
	hRight = CreatePopupMenu();
	AppendMenu(hRight, MF_STRING, 3001, L"打开男人团");
	AppendMenu(hRight, MF_STRING, 3002, L"打开福利档");
	AppendMenu(hRight, MF_STRING, 3003, L"打开torrentkitty");



}
LRESULT CALLBACK WndProc(HWND hWnd,//窗口句柄  
//CALLBACK表示是一个回调函数,LRESULT是函数返回值的类型,可以通过查看wc.lpfnWndProc的定义查看 
UINT code,//消息  msg.message
WPARAM wParam,//消息附加信息 
LPARAM lParam//消息附加信息 
) {
	HMENU hGet;
	RECT rect;
	

	//用switch case 来判断是什么消息 
	switch (code) {
	case WM_CONTEXTMENU:
		GetWindowRect(hWnd, &rect);//获取当前窗口客户区的矩形,传参给rect
		TrackPopupMenu(hRight, TPM_RIGHTBUTTON, LOWORD(lParam), HIWORD(lParam), 0, hWnd, &rect);//右键点击时显示菜单
		break;
#if 0
	case WM_RBUTTONUP://当单击右键并弹起时,会收到此消息
		//TrackPopupMenu()
		GetWindowRect(hWnd, &rect);//获取当前窗口客户区的矩形,传参给rect
		TrackPopupMenu(hRight, TPM_CENTERALIGN, rect.left+LOWORD(lParam),rect.top+HIWORD(lParam), 0, hWnd, &rect);//右键点击时显示菜单
		break;//用这种方式右键菜单弹出的位置有点问题,因为WM_RBUTTONUP的lParam中存储的是相对于桌面的xy坐标,需要用当前窗口即rect.left+LOWOR(lParam)转化成相对于窗口的。
#endif
		break;
	case WM_SYSCOMMAND://点击系统菜单项就会产生WM_SYSCOMMAND消息
		switch (wParam) {//不同的系统菜单项会产生不同的WM_SYSCOMMAND消息,这些消息的wParam中保存的菜单项id号不同
		case 1112://之前我们自己追加的菜单项id号为1112
			MessageBox(hWnd, L"(ง •_•)ง", L"系统菜单项", NULL);
			break;
		default:
			break;
		}

		break;
	case WM_KEYUP:
		break;
		
	case WM_KEYDOWN:
		switch (wParam) {
		case VK_UP:
			y -= 10;
			n = 0;
			break;
		case VK_DOWN:
			y += 10;
			n = 1;
			break;
		case VK_LEFT:
			x -= 10;
			n = 2;

			break;
		case VK_RIGHT:
			x += 10;
			n = 3;
			break;

		}

		break;
	case WM_COMMAND://当点击菜单项时会发送WM_COMMAND消息,由于不同的菜单项我们设置了不同的id,所以可以根据菜单项的id来区分
		switch (wParam) {// 点击不同的菜单项,WM_COMMAND的附加消息wParam中存储的id号不同
		case 2522:
			MessageBox(hWnd, L"泷泽萝拉的作品目前还没有", L"抱歉", MB_OK);
			break;
		//点击 桃乃木香奈 2524 菜单项 设置 波多野结衣菜单项 2521 可选中(由灰色变成黑色)
		//点击 波多野结衣 2521 菜单项 设置 桃谷绘里香 2523 菜单项选中
		case 2524:
			hGet =GetSubMenu(GetMenu(hWnd),1);//GetMenu拿到的是窗口句柄为hWnd的窗口的顶层菜单的菜单句柄;以之作为参数去拿顶层菜单的弹出式菜单
			EnableMenuItem(hGet, 2521, MF_ENABLED);//设置波多野结衣菜单项为黑色
			break;
		case 2521:
			hGet = GetSubMenu(GetMenu(hWnd), 1);
			//通过id号来选中桃谷绘里香菜单项
			//CheckMenuItem(hGet, 2523, MF_CHECKED);//设置桃谷绘里香被选中
			//通过相对位置来选中桃谷绘里香 菜单项
			CheckMenuItem(hGet, 3, MF_CHECKED | MF_BYPOSITION);
			break;
		case 2525:
			hGet = GetSubMenu(GetMenu(hWnd), 1);
			switch (flag) {
			case 0:
				CheckMenuItem(hGet, 2522, MF_CHECKED);
				flag = 1;
				break;
			case 1:
				CheckMenuItem(hGet, 2522, MF_UNCHECKED);
				flag = 0;
				break;
			}
			break;


		}
		break;


	case WM_MOUSEMOVE:
		break;

	case WM_CREATE://代表窗口创建消息 
		OnCreate(hWnd, wParam, lParam);
		//帧:一秒钟切换图片数。
		//人眼可以识别的流畅动画,一秒钟16张图片以上。
		SetTimer(hWnd, 6666, 50, NULL);//定时器id6666,间隔50毫秒,
	
		break;
	case WM_TIMER:
		tietu(hWnd);//因为每隔50毫秒,发送一次定时器消息,所以每隔50毫秒切一次图
		break;
	case WM_PAINT://绘图消息
	{
		PAINTSTRUCT ps;
		HDC hdc = BeginPaint(hWnd, &ps);//拿到设备DC,返回值为DC设备句柄
#if 0
		//1.创建画笔
		HPEN hPen0=CreatePen(PS_DASH, 10, RGB(255, 20, 20));//返回画笔对象的句柄
		HPEN hPen1 = CreatePen(PS_SOLID, 3, RGB(10, 10, 30));
		//2.设置画笔:即拿这支笔
		HGDIOBJ hOldpen=SelectObject(hdc, hPen0);
		
		
		/*
		HGDIOBJ 是一个类,是windows上所有GDI类型的基类,基类指针可以指向派生类的对象。HGDIOBJ x=HPEN hpen没有问题
		
		*/
		//3.画图
		
		MoveToEx(hdc, 30, 30, NULL);//画线,设置起始点
		LineTo(hdc, 600, 30);//lineto函数划线,只会设置划线的终点。
		//4.还原画笔
		SelectObject(hdc,hOldpen);
		//5删除画笔
		DeleteObject(hPen0);
		DeleteObject(hPen1);


#endif
/*
		//1创建画刷
		HBRUSH hBrush1=CreateSolidBrush(RGB(255, 0, 0));
		HBRUSH hBrush2 = CreateHatchBrush(HS_CROSS, RGB(0, 255, 0));//第一个参数为画刷的风格
        //2设置画刷到DC中
		HGDIOBJ hOldBrush = SelectObject(hdc, hBrush1);
		//3使用画刷
		RoundRect(hdc, 30, 30, 100, 100, 100, 100);//画椭圆矩形
		SelectObject(hdc, hBrush2);
		Rectangle(hdc, 150, 150, 300, 300);
		//4还原画刷
		SelectObject(hdc, hBrush2);
		//5删除画刷
		DeleteObject(hBrush2);
		EndPaint(hWnd, &ps);
*/
		
#if 0
//截屏:拿到操作系统当前屏幕某个区域内的图像
		HDC hScreenDc = CreateDC(TEXT("DISPLAY"), NULL, NULL, NULL);//第一个参数为设备,TEXT(“DISPLAY”)表示输出设备
		for (int y = 0; y < 300; y++) {
			for (int x = 0; x < 300; x++) {
				BitBlt(hdc, 0, 0, 300, 300,hScreenDc,0,0,SRCCOPY);//把hScreenDc中的内容画到hdc里面,从(0,0)画到(300,300),从hScreenDc的(0,0)开始去拿。画的方式SRCCOPY直接拷贝。
			}
		}
#endif
		//贴图
		//1.有图片
		//HBITMAP hBitmap1=LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_BITMAP1));//加载位图,只能加载bmp类型的图片
		HANDLE hBitmap1 = LoadImage(g_hInstance, L"bk.bmp", IMAGE_BITMAP, 641, 480, LR_LOADFROMFILE);
		//2.创建兼容DC
		HDC hBuffDc = CreateCompatibleDC(hdc);
		//3.图片放入兼容DC//将图片里面的东西,放到内存中
		SelectObject(hBuffDc, hBitmap1);
	    //参数:当前程序实例句柄,资源id
		//loadImage()之前已经讲过了
		//4.贴图:将内存中的东西放到窗口中
		BitBlt(hdc, 0, 0,641,480,hBuffDc,0,0,SRCPAINT);
		//贴到哪里 贴到窗口的哪个坐标(x,y)  贴图的宽高  从哪里获取图片,从获取图片的哪个坐标开始截图 贴图方式      SRCAND透明贴图
		break;

	}


	case WM_DESTROY:
		PostQuitMessage(0);//PostQuitMessage函数发送一个WM_QUIT消息到线程消息队列并且立即返回
		break;
	default:
		return DefWindowProc(hWnd, code, wParam, lParam);//意味着开始下一次消息循环 
	}
	return DefWindowProc(hWnd, code, wParam, lParam);//意味着开始下一次消息循环 
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hParentInstance, LPSTR lpCmd, int len) {
	g_hInstance = hInstance;


	//1.写主函数
	//2.在主函数中注册窗口类  WNDCLASS WNDCLASSEX(64位扩展的) 
	WNDCLASSEX wc;//wc是一个窗口类 类型的 结构体,它里面有很多成员变量,一个一个给它们初始化就可以了
	wc.cbSize = sizeof(WNDCLASSEX);//窗口类大小 
	wc.cbClsExtra = NULL;//窗口类附加信息 即 对窗口类的说明 等于NULL表示没有附加信息 
	wc.cbWndExtra = NULL;//窗口附加信息 即对窗口的说明 
	wc.lpszClassName = L"番号搜索大法";//窗口类名,主要是为了标志版权 用spy++工具可以查看类名 
	//wc.hbrBackground=NULL;//背景 设置背景颜色
	wc.hbrBackground = (HBRUSH)GetStockObject(3);//百度查,记不住 
	wc.hCursor = NULL;
																	//wc.hIcon = NULL;//窗口的图标(任务栏) 用loadicon函数来加载图标:加载一个图标资源,并且返回这个图标资源的句柄。 
	wc.hIcon = NULL;
	//两种方式loadIcon和(HICON)LoadImage(hInstance,MAKEINTRESOURCE(IDI_ICON2),IMAGE_ICON,32,32,LR_LOADTANSPARENT);
	//加载方式:LR_LOADTANSPARENT资源方式加载,文件方式加载LOADFROMFILE

	wc.hIconSm = NULL;//小图标
	wc.hInstance = hInstance;//当前应用程序的实例句柄
	wc.lpfnWndProc = WndProc; //消息处理函数=你自己定义的消息处理函数的名字 
	wc.lpszMenuName = NULL; //菜单
	wc.style = CS_HREDRAW | CS_VREDRAW;//窗口风格=水平/垂直滚动条
	RegisterClassEx(&wc); //正式注册窗口类,只要把结构体wc的地址传入即可。 


	//3.创建窗口 CreateWindow  //HWND 窗口句柄类型 Handle Window 
	HWND hWnd = CreateWindowEx( //因为创建窗口函数会返回创建的窗口的句柄,所以首先定义好窗口句柄类型的变量。 
		NULL,//窗口的附加风格 
		wc.lpszClassName, //窗口类名 
		L"番号搜索大法",//窗口名 
		WS_OVERLAPPEDWINDOW,//窗口的风格 
		1000, 100,//窗口出现的位置 (x,y)
		600, 600,//窗口的宽高 
		NULL,//父窗口实例句柄 
		NULL,//窗口的菜单的句柄 
		hInstance,//窗口的实例句柄(当前应用程序实例句柄 
		NULL);//附加信息 消息是个结构体,消息有两个附加信息:lParam wParam 


		 /*如果创建窗口失败,则CreateWindow的返回值为NULL即0;
		  *如果创建窗口成功,则CreateWindow的返回值为所创建窗口的句柄
		  */
		  /*
		 if(NULL==hWnd){
			MessageBox(NULL,"创建窗口失败","警告",MB_OK);
			return -1;
		 }
		 else{
			MessageBox(NULL,"创建窗口成功","恭喜",MB_OK);
		 }
		*/

		//4.显示刷新窗口 ShowWindow updateWindow
	ShowWindow(hWnd, 5);//传入窗口的句柄即可。 
	UpdateWindow(hWnd); //传入窗口的句柄即可。 
	AllocConsole();//得到命令行窗口使用权限
	g_hConsole = GetStdHandle(STD_OUTPUT_HANDLE);//用函数得到命令行窗口句柄,赋值给全局变量g_hConsole

	//5.定义窗口的消息处理函数

	//消息循环是一个死循环,永远不会结束,除非。。。你写一个语句break 
	while (1) {
		//获取消息 
		//GetMessage(&msg, NULL, NULL, NULL);//GetMessage函数是一个阻塞函数,如果接收一个消息所用的时间过长,在此期间内,如果有另一个消息被发送,就会接受不到这个消息
		if (PeekMessage(&msg, NULL, NULL, NULL, NULL))//最后一个参数表示是否删除消息
		/*PeekMessage的工作原理:非阻塞方式接受消息,主要查看是否有消息,不会将消息存入MSG结构体中,GetMessage函数负责将消息存入MSG结构体中。
		Peekmessage会检查消息队列,如果消息队列中有消息,函数就会查看最后一个参数,
		如果最后一个参数为false或者NULL,直接返回1
		如果最后一个参数为true,从消息队列中删除消息,然后返回1
		如果消息队列中没有消息,直接返回0

		*/ {
			if (0 == GetMessage(&msg, NULL, NULL, NULL)) break;
			/*GetMessage工作原理:
			GetMessage从消息队列中获取一个消息存放到MSG结构体中。
			如果消息队列中有消息,把消息存入MSG,删除当前程序消息队列中的消息,返回非0
			直到遇到WM_QUIT消息才返回0.注意:关闭窗口不会发送WM_QUIT消息,只会发送WM_DESTROY消息,只有在消息处理函数中定义当收到WM_DESTROY时,向消息队列中添加WM_QUIT消息,
			才能通过if(0 == GetMessage(&msg, NULL, NULL, NULL)) break;退出循环。
			如果当前程序消息队列中没有消息,就去系统消息队列中看有没有消息。如果系统消息队列
			里面有消息,就把系统消息队列中的消息放入当前程序消息队列。如果系统消息队列中没有消息,就
			检查窗口是否需要重新绘制。如果需要重新绘制,操作系统发送WM_PAINT消息,如果不需要重新绘制
			查看是否有定时器,如果有定时器,处理定时器消息,如果没有定时器,优化资源,处理内存,继续等待。
			消息循环处理消息的次数可能小于消息处理函数处理消息的次数,因为其他应用程序的消息也可能发送给消息处理函数处理。


			*/
			/*
			if (msg.message == WM_QUIT) { //WM_QUITE消息表示整个窗口退出
				break;
			}
			*/

			//翻译消息 
			TranslateMessage(&msg);//翻译消息
			/*消息的翻译主要针对键盘消息,键盘消息只有两个:
			WM_TDOWN
			WM_TUP
			if(msg.message==WM_KEY 按键消息){
			   if(是否可见字符){//可见字符即代表字符有回显,例如F1F2..PgUp等不是可见字符。
			   //如果是可见字符,代表应该告诉应用程序,用户按键是用来编辑,发送的是一个字符,否则,就认为用户按键是发送一个指令。
			   //如果用户发送的是一个字符,就需要翻译一下
				 if(Caps Lock是否按下){
					PostMessage(大写字母消息);//发送大写字母消息
				 }else{
				   PostMessage(小写字母消息);//发送小写字母消息
				 }

			   }
			 }
			 return;
			*/
			DispatchMessage(&msg);//派发消息,群发消息,给当前所有的应用程序都发送。而PostMessage表示只对当前应用程序的消息处理函数发送消息。


		}

	}
	//6.消息循环    循环 接受消息 翻译消息 派发消息

	//点击动作 操作系统产生消息 发给窗口应用程序,应用程序里面的“消息循环”接收消息,调用对应的消息处理函数,产生对应的响应动作。 

	return 0;

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值