当前位置:C++技术网 > 精选软件 > 鼠标移动时的背景透明文字不透明的实时提示框效果实现

鼠标移动时的背景透明文字不透明的实时提示框效果实现

更新时间:2015-12-25 16:48:05浏览次数:1+次

    使用常规的窗口作为提示框的完美实现,请阅读《win32鼠标移动实时提示框漂离主窗口的问题分析和解决办法》。事实上,这一篇是最后解决的一个文章,从而完整的实现了实时提示框的功能。要完整看完,需要先看完这些文章提到的文章,这是开发实现的顺序。
    那么本文的基础部分,比如计时器的使用等,也是基于前面的提示框的实现思路,所以也不再重复讲述。你可以看前面的这一系列文章,熟悉功能开发流程和分析思路。
    为什么要写这样的一个提示框呢?因为使用窗口提示毕竟有它的不足的地方,那就是不够灵活自由,而且界面还不够好看。同时还要处理诸如焦点的问题,要处理提 示框漂离主窗口太远的问题,还有窗口激活状态的问题。虽然后面的这些问题在之前的文章都分析解决了,然而界面不好看这是不太好解决的。当然不是不能解决, 你可以创建一个自己的窗口,然后自己给提示框绘图,也可以将提示框搞得个性化不规则等。创建不规则窗口,可以参考《创建不规则窗口-心形窗口》和《实现窗口透明(透明窗口和不规则窗口)的原理分析》。
    然而尽管如此,对于一个小小提示框,实在有点大动干戈了。所以,我另外想到一个实现方案就是直接绘图。也就是在鼠标光标移动时,在光标附近绘制一个图案, 在图案输出文字提示。然后鼠标移走后,还原刚才的绘图。想起来容易,做起来难。我们不仅要敢想,更要不怕麻烦,要敢做。而且要做就要做到最好。
    所以在实现这个效果时,不仅是在白色的纯色背景下实现提示效果,而且还要在格子线背景下实时提示。结果就是,这个可以应用于各种各样背景的实时提示。我做到了!
    然而在这个实现过程中,遇到了很多问题。比如文字提示部分不要透明,文字以外的背景部分要透明。提示之后,鼠标移走后,留下来的影子擦除对于不是纯色的背景要如何做到不影响原来的背景。那么这一系列问题才是这个技术实现的问题点。
    实际上,对于这些问题的解答,实际上就是在讲解win32的绘图原理的一部分。回归到本质就是绘图的原理问题,同时要对消息的处理机制比较熟悉。对于每一个出现的问题都能够分析到位,这样才能精确的找到问题,然后针对性的高效解决。

    我们先来看看实现的效果图:

鼠标移动时的背景透明文字不透明的实时提示框效果图

【鼠标移动时的背景透明文字不透明的实时提示框效果图】
    自定义绘图方式实现提示的基本思路是:鼠标移动消息中我们要提示出当前的位置信息。所谓的椭圆背景就是在要提示文字时,先在这个位置画一个椭圆,然后在这 个位置继续输出文字,文字自然就画在了椭圆上。这样看上去椭圆就是提示的背景了。文字本来就有背景色,所以设置一下颜色就可以了。
    那么实现椭圆透明效果和提示文字的不透明效果,其实就是相对于背景的格子线来说的。如果绘制了椭圆,依然在椭圆图形上可以看到格子线,自然感觉椭圆就是透明的。而文字提示部分,则看不到格子线,自然就认为是不透明的。
    而鼠标移动后,要如何擦除上一次绘制的提示框呢?这个问题就是用白色的同样大小同样位置上画一个白色的椭圆。因为文字在椭圆内部,所以,不需要重新画一次文字覆盖。通过这样的覆盖方式擦除,就不必让客户区重画,这样也就提高了绘制的效率。
    然而,这样覆盖必然用背景颜色来绘制。如果是纯色背景,用背景色自然就OK了。然而背景有格子线,用背景色绘制椭圆后,这一块就留下来白色的擦痕。然而我 们是在移动的时候通过绘制背景色的椭圆覆盖原来的位置,然后在新的位置绘制新椭圆。然后马上紧接着让客户区重画。重绘客户区也就重新绘制格子线。因为之前 的椭圆绘制成了背景色,所以,格子线重画之后,背景色的椭圆和原本的背景色就融为一起了,都是一种颜色嘛。格子线直接在上面画,所以背景色的椭圆和背景色 客户区已经不能分辨你我了,所以就消失了。而有颜色的椭圆依然保留,所以格子线直接画在了有颜色的椭圆上,这样格子线在椭圆上看得到,就实现了椭圆的透 明。
    为什么在客户区重画的时候有颜色的椭圆(相对于背景色的白色而言,当然白色也是一种颜色)还会存在呢?因为这个并不是自然产生的客户区重绘,而是我们调用了:
InvalidateRect(hwnd,NULL,FALSE);
    使客户区无效从而产生了客户区重绘,而在第三个参数中我们指明了FALSE表示不擦除背景,这样之前绘制的椭圆还是存在的。上一次的椭圆消失是因为他是白色的背景色,所以看不见了而已。如果对于InvalidateRect函数不明白,请阅读《InvalidateRect无效矩形的图文分析和在字符串中移动光标》和《InvalidateRect是否删除背景效果分析以及实现下划线删除线》,在此就不重复讲了。
    那么最后一个问题就是,提示自动消失的问题。自然这在前的用窗口实现的提示框里讲过了消失的计时器的用法。然而这里并不是窗口,所以还是用背景色覆盖的方式擦掉提示框。擦除后一定要重新绘制一下格子线。这样就完美的实现了。
    这里每次绘制格子线就是恢复现场,所以也适合于其他的背景的恢复。当然需要注意一点,如果是图片背景,则椭圆的透明效果就无法实现了。因为贴图后会让图片完全盖住椭圆。所以这个时候,也只好在贴出后重新画椭圆了,就没有椭圆背景透明的效果了。
    下面是完整的实现代码,代码有详细的注释,更详细的还是上面的原理讲解。
#include "windows.h"
#include <Windowsx.h>//GET_X_LPARAM,GET_Y_LPARAM宏需要这个头文件
#include <tchar.h>
// - 项目是Unicode字符集
HINSTANCE g_hInstance=NULL;
LRESULT CALLBACK WinProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    TCHAR Info[100]=_T("C++技术网http://www.cjjjs.com");
    RECT rect;
    static POINT ptOld,ptNew;
    static bool bIsFirst=true;
    static TCHAR msg[100]=_T("");
    switch (message)
    {
     case WM_PAINT:
         hdc = BeginPaint(hwnd,&ps);
         //绘制背景格子线,因为之前在鼠标移动时画了椭圆,所以此处的作画在椭圆上一层,所以这样椭圆看上去就是透明的。
         GetClientRect(hwnd,&rect);
         SelectObject(hdc,CreatePen(PS_SOLID,0,RGB(120,120,120)));//系统没有灰色的画笔,就只有自己创建咯
         for (int i=0;i<rect.right;i+=50)
         {
             MoveToEx(hdc,i,0,NULL);
             LineTo(hdc,i,rect.bottom);
         }
         for (int i=0;i<rect.bottom;i+=50)
         {
             MoveToEx(hdc,0,i,NULL);
             LineTo(hdc,rect.right,i);
         }
         DeleteObject(SelectObject(hdc,GetStockObject(WHITE_PEN)));
         //绘制提示文字,在格子线后画,就叠在线上,就看似不透明的效果。
         if (!bIsFirst)//-在窗口第一次显示时,没有鼠标移动,不应该产生提示框,所以做一个判断
         {
             //画提示的文字
             SetBkColor(hdc,RGB(255,244,10));//设置文字背景
             wsprintf(msg,_T("  %d ,%d "),ptNew.x,ptNew.y);
             SetTextColor(hdc,RGB(0,0,0));//设置文字颜色
             TextOut(hdc,ptNew.x+15,ptNew.y+10,msg,lstrlen(msg));//输出文字
         }
         else
         {
             bIsFirst=false;
         }
         EndPaint(hwnd,&ps);
         return 0;
     case WM_MOUSEMOVE:
         {
             ptNew.x = GET_X_LPARAM(lParam);
             ptNew.y = GET_Y_LPARAM(lParam);
             //在这里绘制的椭圆,下一次的客户区重绘就会画在椭圆上,就实现椭圆背景的透明
             hdc = GetDC(hwnd);

            //-擦掉上次的椭圆
            SelectObject(hdc,GetStockObject(WHITE_BRUSH));//创建画刷
            SelectObject(hdc,GetStockObject(NULL_PEN));//需要设置空画笔占位,否则就用了默认的黑画笔画边框线了
            Ellipse(hdc,ptOld.x,ptOld.y,ptOld.x+100,ptOld.y+35);//擦除上次的椭圆
             
             //画本次的椭圆
             SelectObject(hdc,CreateSolidBrush(RGB(200,150,10)));//创建画刷
             SelectObject(hdc,GetStockObject(NULL_PEN));//不画边框线
             Ellipse(hdc,ptNew.x,ptNew.y,ptNew.x+100,ptNew.y+35);//画提示框的椭圆背景
             DeleteObject(SelectObject(hdc,GetStockObject(WHITE_BRUSH)));//删除画刷

             //更新位置,本次的光标位置成为下一次的上一次光标位置
             ptOld.x = ptNew.x;
             ptOld.y = ptNew.y;

             //额外的处理
             InvalidateRect(hwnd,NULL,FALSE);//让客户区绘画,进而画出不透明的文字提示和透明的椭圆
             SetTimer(hwnd,0,2000,NULL);//设置和重置计时器,这样鼠标只有停下才可以累积时间
             ReleaseDC(hwnd,hdc);
         }
         return 0;
     case WM_TIMER:
         hdc = GetDC(hwnd);
         //-擦掉上次的椭圆,因为椭圆区域涵盖了文字部分,所以文字不需要再擦除一次
         SelectObject(hdc,GetStockObject(WHITE_BRUSH));//创建画刷
         SelectObject(hdc,GetStockObject(NULL_PEN));//创建画刷
         Ellipse(hdc,ptOld.x,ptOld.y,ptOld.x+100,ptOld.y+35);

         //重新客户区所有的背景格子线,否则提示框擦除后就留下来空白痕迹
         GetClientRect(hwnd,&rect);
         SelectObject(hdc,CreatePen(PS_SOLID,0,RGB(120,120,120)));
         for (int i=0;i<rect.right;i+=50)
         {
             MoveToEx(hdc,i,0,NULL);
             LineTo(hdc,i,rect.bottom);
         }
         for (int i=0;i<rect.bottom;i+=50)
         {
             MoveToEx(hdc,0,i,NULL);
             LineTo(hdc,rect.right,i);
         }
         ReleaseDC(hwnd,hdc);
         return 0;
     case WM_DESTROY:
         PostQuitMessage(0);
         return 0;
     default:
         return DefWindowProc(hwnd, message, wParam, lParam);
    }
}

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrev,LPSTR lpCmd,int iShow)
{
    TCHAR ClassName[] = _T("MyClass");
    TCHAR title1[] = _T("C++技术网http://www.cjjjs.com");
    WNDCLASS wndClass;
    wndClass.cbClsExtra=0;
    wndClass.cbWndExtra=0;
    wndClass.hbrBackground= (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
    wndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
    wndClass.hInstance = hInstance;
    wndClass.lpfnWndProc = WinProc;
    wndClass.lpszClassName = ClassName;
    wndClass.lpszMenuName=NULL;
    wndClass.style=CS_HREDRAW|CS_VREDRAW;

    if(!RegisterClass(&wndClass))
    {
        return 0;
    }
    g_hInstance = hInstance;
    HWND hwnd = CreateWindow(ClassName,title1,WS_OVERLAPPEDWINDOW,0,0,440,400,NULL,NULL,hInstance,NULL);
    ShowWindow(hwnd,SW_SHOWNORMAL);

    MSG msg;
    while (GetMessage(&msg,NULL,0,0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}