VC++ 拖拽和Hover效果


效果就是gif所展示的,我做这个是想模仿游戏里面拖动物品到方格上部署的这么一个效果,用纯windowsapi实现起来还真的有那么一丢丢困难。主要涉及以下这几个点:

1、双缓冲绘图:缓冲绘图十分重要,可以屏蔽掉InvalidateRect清除背景,如果直接先清除再重画,那么一定会看到屏闪。所以一般不对hdc进行直接操作,所有操作都是对内存DC操作,比如移动,我先让原位置的物体用背景替代,然而在新的位置绘制即可,最后再将内存DC拷贝给HDC,期间就没有擦除背景这个过程,而是对于原图的直接覆盖。这个也就在比较底层的设计中会看到吧,MFC中都有封装好的

2、Invalidate与wm_paint的延迟:我用双缓冲的时候移动物体线条居然会闪,那么考虑的就是刷新区域的问题,下面注释中写得有

3、渐变效果,动画的控制,alphablend混合

4、碰撞(距离)检测,:block什么时候变色,可以像物理引擎中碰撞检测一样,或者我这就很简单的检测中心的距离作为判断依据

5、重叠与复原:一般图形变化都是由一个基础图形经过一系列的转化形成的,那么重要的就是保存这个基础图形,然后再对其进行不一样的数值变化操作


还是先上代码,注释我写得很详细,上面主要的几点都在注释中写得有,我这就不再过多的讲解了

// Square.cpp: 定义应用程序的入口点。
//

#include "stdafx.h"
#include "Square.h"
#include <condition_variable>
#include <deque>

#define MAX_LOADSTRING 100
#define WM_INVALIDATE 104
#pragma comment(lib, "Msimg32.lib")  

using namespace std;

// 全局变量: 
HINSTANCE hInst;                                // 当前实例
WCHAR szTitle[MAX_LOADSTRING];                  // 标题栏文本
WCHAR szWindowClass[MAX_LOADSTRING];            // 主窗口类名

// 此代码模块中包含的函数的前向声明: 
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

HDC hSrcDc;
HBITMAP hSrc;

RECT clientRect;
HDC hMainDc;
HBITMAP hMain;

HWND mWnd;
mutex m;
condition_variable cv;
bool animate = false;
bool isAlive = true;

int rectStatus = 0;//深色渐变/恢复
int ori = 0;//透明度
bool init = 0;//paint初始化状态标志
RECT mOriRect;//按下鼠标 物体位置
RECT mRect;//物体实时位置
bool isDown = false;
POINT downPoint;//按下鼠标点
bool drawRect = false;
HBRUSH bkBrush;//背景刷
int areaStatus = 0;//0初始状态 1重叠状态 2分离状态 3无效态

//线程控制动画效果,我这1S大概60FPS
unsigned int _stdcall Animate(void *ch)
{
	while(isAlive)
	{
		unique_lock<mutex> l(m);
		cv.wait(l);
		while(animate)
		{
			SendMessage(mWnd, WM_INVALIDATE, 0, 0);
			Sleep(16);
		}
	}
	return 0;
}


int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{

	_beginthreadex(0, 0, Animate, 0, 0, 0);
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: 在此放置代码。

    // 初始化全局字符串
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_SQUARE, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // 执行应用程序初始化: 
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_SQUARE));

	

    MSG msg;

    // 主消息循环: 
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

	isAlive = false;
	cv.notify_all();

    return (int) msg.wParam;
}



//
//  函数: MyRegisterClass()
//
//  目的: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SQUARE));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
	HBRUSH bkBrush = CreateSolidBrush(RGB(255, 255, 255));
    wcex.hbrBackground  = bkBrush;
    wcex.lpszMenuName   = 0;
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
    return RegisterClassExW(&wcex);
}

//
//   函数: InitInstance(HINSTANCE, int)
//
//   目的: 保存实例句柄并创建主窗口
//
//   注释: 
//
//        在此函数中,我们在全局变量中保存实例句柄并
//        创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // 将实例句柄存储在全局变量中

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);
   mWnd = hWnd;
   return TRUE;
}

//
//  函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目的:    处理主窗口的消息。
//
//  WM_COMMAND  - 处理应用程序菜单
//  WM_PAINT    - 绘制主窗口
//  WM_DESTROY  - 发送退出消息并返回
//
//

void ControlAnimate()
{
	if (drawRect)
	{
		if (!animate)
		{
			animate = true;
			cv.notify_all();
		}
		return;
	}
	//动画开关条件
	if (rectStatus == 0 && ori>0 || rectStatus == 1 && ori<125)
	{
		if (!animate)
		{
			animate = true;
			cv.notify_all();
		}
	}
	else
	{
		animate = false;
	}
}

void DrawBlock(int drawType=0)
{
	if(drawType==1)
	{
		BitBlt(hMainDc, 100, 100, 200, 200, hSrcDc, 0, 0, SRCCOPY);
		return;
	}
	else if(drawType==2)
	{
		BitBlt(hMainDc, 100, 100, 200, 200, hSrcDc, 0, 0, SRCCOPY);
		//将红色与block混合,使其表现为重叠状态
		HDC hBitmapDc = CreateCompatibleDC(hMainDc);
		HBITMAP hBitmap = CreateCompatibleBitmap(hMainDc, 100, 100);
		SelectObject(hBitmapDc, hBitmap);
		RECT bitmapRect{ 0,0,100,100 };
		HBRUSH b = CreateSolidBrush(RGB(255, 0, 0));
		FillRect(hBitmapDc, &bitmapRect, b);

		BLENDFUNCTION bf;
		bf.BlendOp = AC_SRC_OVER;
		bf.BlendFlags = 0;
		bf.AlphaFormat = 0;
		bf.SourceConstantAlpha = 128;
		AlphaBlend(hMainDc, 100, 100, 100, 100, hBitmapDc, 0, 0, 100, 100, bf);

		DeleteObject(hBitmap);
		DeleteDC(hBitmapDc);
		DeleteObject(b);
		return;
	}
	else if(drawType==0)
	{
		if (rectStatus == 0 && ori <= 0 || rectStatus == 1 && ori>125)
		{
			return;
		}
	}
	
	//拷贝原型到 hMirrorDc
	HDC hMirrorDc = CreateCompatibleDC(hMainDc);
	HBITMAP hMirror = CreateCompatibleBitmap(hMainDc, 100, 100);
	SelectObject(hMirrorDc, hMirror);
	BitBlt(hMirrorDc, 0, 0, 100, 100, hSrcDc, 0, 0, SRCCOPY);

	//将镜像与纯黑混合,透明度动态变化,使其为动画效果
	HDC hBitmapDc = CreateCompatibleDC(hMainDc);
	HBITMAP hBitmap = CreateCompatibleBitmap(hMainDc, 100, 100);
	SelectObject(hBitmapDc, hBitmap);
	SetBkColor(hBitmapDc, RGB(255, 255, 255));

	BLENDFUNCTION bf;
	bf.BlendOp = AC_SRC_OVER;
	bf.BlendFlags = 0;
	bf.AlphaFormat = 0;
	if (rectStatus == 0) {
		bf.SourceConstantAlpha = ori--;
	}
	else
	{
		bf.SourceConstantAlpha = ori++;
	}
	AlphaBlend(hMirrorDc, 0, 0, 100, 100, hBitmapDc, 0, 0, 100, 100, bf);

	BitBlt(hMainDc, 100, 100, 200, 200, hMirrorDc, 0, 0, SRCCOPY);

	ControlAnimate();

	DeleteObject(hBitmap);
	DeleteDC(hBitmapDc);
	DeleteObject(hMirror);
	DeleteDC(hMirrorDc);
}

void DrawRect(int drawType=0)
{
	FillRect(hMainDc, &mRect, bkBrush);//如果背景是纯色,那直接brush就行,如果是图片,那么就需要将图片对应的区域拷贝回来
	if (drawType == 0) {
		POINT point;
		GetCursorPos(&point);
		ScreenToClient(mWnd, &point);
		LONG difX = point.x - downPoint.x;
		LONG difY = point.y - downPoint.y;
		mRect.left = mOriRect.left + difX;
		mRect.right = mOriRect.right + difX;
		mRect.top = mOriRect.top + difY;
		mRect.bottom = mOriRect.bottom + difY;
		double x = mRect.left + 50 - 150;
		double y = mRect.top + 50 - 150;
		if (x*x + y*y<100 * 100 * 2)//碰撞检测这个地方我写的比较水,因为这地方可以根据实际情况来写的,我也就没必要写那么复杂了
		{
			ori = 0;
			rectStatus = 0;
			DrawBlock(2);//重叠先画下面的,再画上面的
			areaStatus = 1;
		}
		else if (areaStatus == 1)//离开后还需要一次额外绘制,确保block的完整性
		{
			areaStatus = 2;
		}
	}
	else if(drawType==1)
	{
		mRect.left = 100;
		mRect.right = 200;
		mRect.top = 100;
		mRect.bottom = 200;
	}
	Rectangle(hMainDc, mRect.left, mRect.top, mRect.right, mRect.bottom);
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	POINT point;
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // 分析菜单选择: 
            switch (wmId)
            {
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: 在此处添加使用 hdc 的任何绘图代码...
			if(!init)
			{
				//创建缓冲DC
				GetClientRect(hWnd, &clientRect);
				hMainDc = CreateCompatibleDC(hdc);
				hMain = CreateCompatibleBitmap(hdc, clientRect.right - clientRect.left, clientRect.bottom - clientRect.top);
				SelectObject(hMainDc, hMain);
				bkBrush = CreateSolidBrush(RGB(255, 255, 0));
				FillRect(hMainDc, &clientRect, bkBrush);

				//绘制一个矩形,并且与对应位置背景做半透混合,使其作为原型存储在内存dc中
				hSrcDc = CreateCompatibleDC(hdc);
				hSrc = CreateCompatibleBitmap(hdc, 100, 100);
				SelectObject(hSrcDc, hSrc);

				Rectangle(hSrcDc, 0, 0, 100, 100);

				BLENDFUNCTION bf;
				bf.BlendOp = AC_SRC_OVER;
				bf.BlendFlags = 0;
				bf.AlphaFormat = 0;
				bf.SourceConstantAlpha = 128;
				AlphaBlend(hSrcDc, 0, 0, 100, 100, hMainDc, 100, 100, 200, 200, bf);

				//将原型绘制到缓冲DC中
				BitBlt(hMainDc, 100, 100, 200, 200, hSrcDc, 0, 0, SRCCOPY);

				//物体
				mRect = { 400,100,500,200 };
				Rectangle(hMainDc, 400, 100, 500, 200);

				init = true;
			}
			if(areaStatus ==2)//若为分离态,物体离开block后确保block的完整性,额外一次绘制block
			{
				DrawBlock(1); 
				areaStatus++;
			}
			else//否则检测透明度,看是否需要重绘
			{
				DrawBlock();
			}

			if (drawRect)
			{
				if (!isDown)
				{
					if (areaStatus == 1) {
						DrawRect(1);
					}
					drawRect = false;
					animate = false;
				}
				else
				{
					DrawRect();
				}
			}
			BitBlt(hdc, 0, 0, clientRect.right, clientRect.bottom, hMainDc, 0, 0, SRCCOPY);
            EndPaint(hWnd, &ps);
        }
        break;
	case WM_LBUTTONDOWN:
		areaStatus = 0;//重置状态
		isDown = true;
		mOriRect = mRect;
		GetCursorPos(&downPoint);//记录按下点,推断移动位置
		ScreenToClient(hWnd, &downPoint);
		break;
	case WM_LBUTTONUP:
		isDown = false;
		break;
	case WM_MOUSEMOVE:
		//大致思路就是,只有渐变和移动需要animate,那么我就在这两种状态下激活,剩下关闭的逻辑控制交给paint
		if (!isDown&&areaStatus != 1) {//如果没有按下鼠标和没有覆盖才去检测渐变区域
			GetCursorPos(&point);
			ScreenToClient(hWnd, &point);
			if (point.x > 100 && point.x < 200 && point.y>100 && point.y < 200)
			{
				if (rectStatus == 0) {
					rectStatus = 1;
					ControlAnimate();
				}
			}
			else
			{
				if (rectStatus == 1)
				{
					rectStatus = 0;
					ControlAnimate();
				}
			}
		}

		if(isDown&&drawRect==false)
		{
			if(downPoint.x>mOriRect.left&&downPoint.x<mOriRect.right&&downPoint.y>mOriRect.top&&downPoint.y<mOriRect.bottom)
			{
				drawRect = true;
				ControlAnimate();
			}
		}
		break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
	case WM_INVALIDATE:
		RECT r;
		if(drawRect)
		{
			//这地方计算区域其实是有问题的,由于Invalidate与WM_PAINT之间存在延时,当Invalidate触发WM_PAINT将之放入消息队列时,可能此时队列中已经有了多个WM_PAINT消息却还未处理,但我内存DC中却在不断保持最新,那么区域自然就不一样了
//			vRect.left = (vRect.left < mRect.left ? vRect.left : mRect.left)-10;
//			vRect.right = (vRect.right < mRect.right ? mRect.right : vRect.right)+10;
//			vRect.top = (vRect.top < mRect.top ? vRect.top : mRect.top)-10;
//			vRect.bottom = (vRect.bottom < mRect.bottom ? mRect.bottom : vRect.bottom)+10;
			InvalidateRect(hWnd, 0, false);
		}
		else
		{
			r.left = 100;
			r.right = 200;
			r.top = 100;
			r.bottom = 200;
			InvalidateRect(hWnd, &r, false);
		}
		break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}




猜你喜欢

转载自blog.csdn.net/nightwizard2030/article/details/78309499
今日推荐