目录
GDI:Graphical Device Interface 图形设备接口(windows上用来控制图形设备的一些函数),设备指显卡 显示器等硬件设备。
控制硬件设备的程序称为驱动程序。操作系统层有一些GDI GUI COM的库。应用程序则是程序员写的。
GDI的作用:可以让程序员在写代码的时候不用关心设备,只要调用GDI中的函数就行。
设备环境:(设备上下文DC:Device Context)就是屏幕
DC的分类:
- 整体窗口的设备DC(类型为HDC)
- 兼容设备DC:用来做缓冲的。例如你有两张图,需要先将两张图混合成一张,即这两张图需要先贴到兼容设备DC之上,然后将兼容设备DC贴到设备DC之上。
得到设备DC的两种方式:
- BeginPaint和EndPaint函数对:当窗口需要重绘时,即当客户区上出现“无效矩形”(需要重绘的矩形,比方说客户区有一部分被另一个窗口遮住后另一窗口又移开。这时重见天日的被遮部分就是无效矩形。)会自动发送WM_PAINT消息。如果程序员没有自己设计WM_PAINT消息,那么就会调用默认消息处理函数的WM_PAINT消息的处理方式,如下:
-
case WM_PAINT:{ PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd,&ps); EndPaint(hwnd,&ps); break; }
BeginPaint函数会用注册窗口类时注册的画刷(通常是白色),来擦除无效矩形的背景,即用该画刷来重绘无效矩形,这样无效矩形就会变成有效矩形,就不会再触发WM_PAINT消息。
-
BeginPaint的返回值便是设备环境句柄,但是它的绘制区域仅仅是无效矩形。即BeginPaint返回的句柄只能用来在无效矩形区域中绘制。也就是说BeginPaint只能在WM_PAINT消息中使用,因为只有收到这个消息才意味着存在无效矩形,如果是其他消息,那么就不存在无效矩形,BeginPaint就无用武之地。
-
而GetDc返回的设备环境句柄,它的裁剪区域是整个客户区,这意味着使用该句柄的GDI函数可以在整个客户区绘制。
-
BeginPaint只能在WM_PAINT消息中使用,而GetDc可以在鼠标消息,键盘消息等其他消息中都可以使用。
-
- 在WM_PAINT消息(窗口重绘时自动发送)的消息处理函数中:用BeginPaint(拿到设备DC)和 EndPaint(把设备DC放回去),BeginPaint会从消息队列里删除WM_PAINT消息。不会闪烁。不一定重新绘制。
- 如果在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步:(如果我们没有设置画笔,那么会使用系统默认的画笔。)
- 画笔:用来画简单的图形(由点和线组成的简单图形),即自己在程序中手写图形
- 画笔使用模型:
- 创建画笔:CreatePen
- 设置画笔到DC中 SelectObject:这里之所以是SelectObject 而不是SelectPen,是因为选择画笔选择画刷都是使用这个函数。这个函数就是一个接口。我们不需要画笔搞一个函数,画刷也搞一个函数。
- 画图 LineTo SetPixel Ellipse DrawRect
- 还原成以前的画笔:SelecObject;
- 删除画笔 DeleteObject;
- 画笔使用模型:
-
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画图形:圆形矩形等
- 画图形需要使用到画刷:
- 创建画刷
- CreateSolidBrush 实心画刷
- CreateHatchBrush 阴影画刷
- 设置当前画刷 SelectObject
- 画图
- 还原 SelectObject
- 删除画刷 DeleteObject
- 创建画刷
- 画阴影矩形:
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); }
可以实现的项目:
-
画一个可移动的乌龟
-
画一个眼睛,鼠标点一下闭眼,鼠标再点一下开眼。
-
- 截屏功能:
#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函数。
- 绘制图片:
- 注意:win32中能用的图片只有bmp格式的bitmap picture位图。jpg格式的图片可以通过画图软件转化为bmp格式。
- 思路:下面采用加载资源的方式,加载位图,首先,将图片添加为资源。图片资源是不能直接贴到客户区的设备上下文中的。(如何导入位图:视图>>解决方案资源管理器>>导入(选所有文件才能看到位图),然后在头文件>>Resource.h中可以看到导入的位图ID)
- 需要先创建一个兼容的设备上下文,
- 将图片选到兼容设备上下文中,
- 然后将兼容设备上下文贴到客户区的设备上下文中。
-
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;
}