Duilib中让弹出窗口整体能被拖动的两种方法,设计超强仿QQ自动伸缩窗口
分类:web前端

**一、观察

第一种方法:

基础知识:鼠标在窗口内移动,点击或者释放时都会产生WM_NCHITTEST消息,响应函数OnNcHitTest会返回一个枚举值,系统会根据这个枚举值进行相应的处理。当返回值为HTCAPTION时,系统会认为此时鼠标位于标题栏上,因而当鼠标按下并移动时就会执行拖动操作。

  • 在Duilib中在设置caption高度就能能让用户拖动窗口,其实就是当鼠标按下时在OnNcHitTest消息响应里面返回HTCAPTION,让系统默认为此时鼠标位于标题栏。
LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    POINT pt;
    RECT rcClient;
    RECT rcCaption;

    rcCaption = m_pm.GetCaptionRect();
    GetClientRect(m_pm.GetPaintWindow(), &rcClient);
    pt.x = GET_X_LPARAM(lParam);
    pt.y = GET_Y_LPARAM(lParam);
    ::ScreenToClient(m_pm.GetPaintWindow(), &pt);

    //xml中设置bottom为-1时,整个窗口区域都可以拖动  
    if (-1 == rcCaption.bottom) 
    {
        rcCaption.bottom = rcClient.bottom;
    }

    if ((pt.x >= rcClient.left)
        && (pt.x < rcClient.right)
        && (pt.y >= rcCaption.top)
        && (pt.y < rcCaption.bottom))
    {
            return HTCAPTION;
    }

    return __super::OnNcHitTest(uMsg, wParam, lParam, bHandled);
}
  • 最后,在窗口xml中指定caption="0,0,0,-1",不管窗口大小如何变,整个窗口就可以拖动了。其实这种方法也相当于把caption的bottom设置成窗口的高度。
  • 但是,这样做有个明显的缺点,就是这个窗口的其他事件消息都无法处理了。如果窗口中有一个编辑框就无法编辑了。

图片 1

VC++开发垃圾文件清理软件(下)

  

  转自:

1. 绘制对话框的背景位图

  绘制对话框背景位图本文采用的是处理对话框的WM_PAINT消息,该消息初始化时候对对话框进行绘制,从而绘制背景位图。绘制背景位图的主要代码如下:

CRect   rect;
    CPaintDC   dc(this);
    GetClientRect(&rect); //获取客户区    
//设置对话框背景颜色        
dc.FillSolidRect(rect,RGB(14,94,157));   //设置为窗口背景

2. 在指定的区域中输出位图

  为了能够在指定的区域中输出位图,需要使用设备上下文CDC类的StretchBlt方法。由于我们需要在窗口的非客户区域绘制位图,因此需要使用CWindowDC类的StretchBlt方法, CWindowDC类派生与CDC类,它提供了在窗口非客户区域绘制位图的功能。该方法数从源矩形中复制一个位图到目标矩形,必要时按目前目标设备设置的模式进行图像的拉伸或压缩。输出位图的主要实现代码如下:

    CRect winRC;
    CDC* pDC=GetWindowDC();//获取窗口设备上下文
    CDC memDC;
    memDC.CreateCompatibleDC(pDC);//创建兼容内存位图
    BITMAPINFO bmpInfo;
    CBitmap bmp;    //定义位图对象
    GetWindowRect(&winRC);
    bmp.LoadBitmap(nID);//加载位图
    bmp.GetObject(sizeof(BITMAPINFO),&bmpInfo);//获取位图信息
    int nBmpCX = bmpInfo.bmiHeader.biWidth;//获取位图宽度
    int nBmpCY = bmpInfo.bmiHeader.biHeight;//获取位图高度
    memDC.SelectObject(bmp);//选中位图对象
    pDC->StretchBlt(x,y,w,h,
        &memDC,0,0,nBmpCX,nBmpCY,SRCCOPY);//在窗口中绘制位图
    bmp.DeleteObject();//释放位图对象
    ReleaseDC(pDC);//释放DC

3. 对话框界面设计与绘制的实现:

  在对话框重绘的设计与实现过程中,一般需要绘制的对话框区域主要有标题部分、边框部分和客户区部分。具体的区域划分如下图所示。

图片 2

  既然要对多个区域进行位图显示输出,所以我们先封装一个bmp位图显示输出函数如下:

void CClearTmpFileDlg::DisplayBmp(int x,int y,int w,int h,int nID)
{//nID 表示位图资源的ID
    CRect winRC;
    CDC* pDC=GetWindowDC();
    CDC memDC;
    memDC.CreateCompatibleDC(pDC);
    BITMAPINFO bmpInfo;
    CBitmap bmp;    
    GetWindowRect(&winRC);
    bmp.LoadBitmap(nID);
    bmp.GetObject(sizeof(BITMAPINFO),&bmpInfo);
    int nBmpCX = bmpInfo.bmiHeader.biWidth;
    int nBmpCY = bmpInfo.bmiHeader.biHeight;
    memDC.SelectObject(bmp);
    pDC->StretchBlt(x,y,w,h,
        &memDC,0,0,nBmpCX,nBmpCY,SRCCOPY);//在窗口中绘制位图
    bmp.DeleteObject();
    ReleaseDC(pDC);
}

  然后就是对各个区域进行位图输出重绘。由于标题栏以及边框主要都是非客户区域绘制,因此应该在WM_NCPAINT 消息中绘制。当然得先通过添加资源的方式将所用到的bmp位图资源导入到项目中。

  在 WM_NCPAINT消息对于的 方法OnNcPaint()中调用对话框绘制方法DrawDialog()。该方法的功能就是绘制对话框各个区域的位图。主要代码如下:

void CClearTmpFileDlg::DrawDialog()
{//重绘对话框标题栏、边框、最小化按钮、最大化按钮和关闭按钮等界面
    m_nFrameCY = GetSystemMetrics(SM_CYFIXEDFRAME);//获取对话框边框的高度
    m_nFrameCX = GetSystemMetrics(SM_CXDLGFRAME);//获取对话边框的宽度
    if(GetStyle()&WS_BORDER)//获取对话框是否有边框
    {
        m_nBorderCY = GetSystemMetrics(SM_CYBORDER) + m_nFrameCY;
        m_nBorderCX = GetSystemMetrics(SM_CXBORDER) +m_nFrameCX; 
    }
    else
    {        
        m_nBorderCY = m_nFrameCY;
        m_nBorderCX = m_nFrameCX;
    }
    m_nTitleBarCY = GetSystemMetrics(SM_CYCAPTION) + m_nBorderCY;//计算标题栏高度
    m_nTitleBarCX =m_nBorderCX;

    CRect winRect,factRect;
    GetWindowRect(&winRect);
    factRect.CopyRect(CRect(0,0,winRect.Width(),winRect.Height()));
    CWindowDC windowsDC(this);//获取窗口设备上下文
    //获取整个MFC窗口的高度和宽度
    m_nWinWidth = winRect.Width();//=781
    m_nWinHeight = winRect.Height();//=459
    //绘制对话框左标题栏位图    
    DisplayBmp(0,0,100,m_nTitleBarCY,IDB_LEFTTITLE);
    //绘制对话框标题栏左端的logo图标
    DisplayBmp(3,0,26,m_nTitleBarCY,IDB_APPICON);
    //绘制对话框右标题栏位图    
    DisplayBmp(m_nWinWidth-100,0,100,m_nTitleBarCY,IDB_RIGHTTITLE);
    //绘制对话框中标题栏位图    
    DisplayBmp(100,0,m_nWinWidth-200,m_nTitleBarCY,IDB_MIDTITLE);
    //绘制对话框左边框位图
    DisplayBmp(0,m_nTitleBarCY,m_nBorderCX,m_nWinHeight-m_nBorderCY,IDB_LEFTBAR);
    //绘制对话框底边框位图
    DisplayBmp(m_nBorderCX,m_nWinHeight-m_nBorderCX,m_nWinWidth-2*m_nBorderCX,m_nBorderCX,IDB_BOTTOMBAR);
    //绘制对话框左边框位图
    DisplayBmp(m_nWinWidth-m_nBorderCX,m_nTitleBarCY,m_nBorderCX,m_nWinHeight-m_nBorderCY,IDB_RIGHTBAR);
    //给对话框绘制最小化按钮
    DisplayBmp(m_nWinWidth-26*3-5,0,26,26,IDB_MINBTN1);
    //给对话框绘制最大化按钮
    DisplayBmp(m_nWinWidth-26*2-5,0,26,26,IDB_MAXBTN1);
    //给对话框绘制关闭按钮
    DisplayBmp(m_nWinWidth-26-5,0,26,26,IDB_CLOSEBTN1);

    ReleaseDC(&windowsDC);
    //ReleaseDC(&memDC);
    DrawTitleBarText();//输出标题栏文本
}

  上面代码中最后的绘制对话框标题文本的方法DrawTitleBarText(),主要是用来显示标题栏的文本,其主要代码如下:

CString strTitle ="小蔡垃圾清理器3.0";
CDC* pDC= GetWindowDC();
pDC->SetBkMode(TRANSPARENT);
pDC->SetTextColor(RGB(255,255,255));
pDC->SetTextAlign(TA_CENTER);
CRect rect;
GetClientRect(&rect);
CSize szText = pDC->GetTextExtent(strTitle);
CFont* font,*fOldFont;
font = new CFont;
font->CreateFont(12,0,0,0,FW_BOLD,FALSE,FALSE,0,ANSI_CHARSET,     OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,FF_SWISS,_T("宋体"));
fOldFont = pDC->SelectObject(font);
pDC->TextOut(100,6.5,strTitle,18);
pDC->SelectObject(fOldFont);
ReleaseDC(pDC); 

  在完成对话框相应区域的位图后,并没有完成任务,还需要处理标题栏按钮的热点效果,以及按钮的单击事件。首先得处理鼠标在非客户区域移动时的事件,即WM_NCMOUSEMOVE消息,在其消息处理函数中判断当前的鼠标点是否位于标题栏的按钮区域,如果是则设置按钮的热点效果,并且记录当前的按钮状态,及鼠标点在哪个按钮上。同样的,处理对话框非客户区域的单击事件,即WM_NCLBUTTONDOWN消息,在其消息处理函数中完成单击事件操作。这部分的代码比较简单,在此不予显示。

  上面,我简单的说了对话框界面的设计与实现,下面将介绍按钮控件重绘的实现:

  在MFC下编程,很多时候对于标准的按钮控件不是很满意,想要弄的美观些。这就需要按钮重绘。重绘按钮一般的实现方法就是重写CButton类。

       首先给工程添加一个自绘按钮类MyDrawButton,基类为CButton。要想让按钮具备自绘功能,就要为按钮添加BS_OWNERDRAW属性。为类CButton重载PreSubclassWindow虚函数。在该函数中添加如下一行代码:

SetButtonStyle(GetButtonStyle() | BS_OWNERDRAW);

  当按钮控件具有了自绘功能之后,每次控件状态改变都会触发DrawItem函数,在该函数中来绘制按钮的形态外观,所以第二步就要重载DrawItem虚函数。在这个函数中就可以自由发挥了,比如绘制背景,底色,按钮标题,绘制文本字体样式等等。

       一般都会为按钮定义几种不同状态时的外观,比如光标滑过时的状态,按钮按下时的状态,按钮禁用时的状态,以及按钮的正常状态等等。这就要为新的按钮添加几种重要的消息响应。比如WM_MOUSELEAVE消息,WM_MOUSEHOVER消息和WM_MOUSEMOVE消息等等,值得一提的是前两个消息的响应函数需要自己手动添加,微软提供了一个TrackMouseEvent函数在光标离开一个窗口时投递WM_MOUSELEAVE消息,光标滑过窗口时投递WM_MOUSEHOVER消息。一般来说可以在WM_MOUSEMOVE消息响应函数中调用TrackMouseEvent函数来投递WM_MOUSELEAVE消息和WM_MOUSEHOVER消息。然后在WM_MOUSELEAVE消息的响应函数中标记“光标已经离开按钮”,然后调用InvalidateRect函数让按钮重绘。在WM_MOUSEHOVER消息的响应函数中标记“光标正在按钮上方”,并调用InvalidateRect函数让按钮重绘。

       在本文中,重绘按钮分为3个部分。

(1)绘制按钮背景样式,即绘制背景bmp位图,使得按钮具有自定义的样式,同时在绘制按钮背景的输出位图时采用TransparentBlt()函数,该函数的作用是使窗体上显示位图的背景与窗体背景色融为一体,不仅可以显示按钮bmp位图样式,而且还可以使背景透明。

(2)就是绘制按钮上的文本。主要绘制按钮上文本的样式,包括字体大小,字体样式,字体颜色等属性。

(3)实现不同状态下的按钮的外观样式,主要包括WM_MOUSEMOVE和WM_MOUSELEAVE两个消息的消息处理函数。分别实现鼠标在按钮区域上和不在按钮区域上的状态。为了标记鼠标移动到按钮区域内停留,需要用到一个定时器来标记鼠标是否还在按钮区域内停留。在WM_MOUSEMOVE内启动定时器,触发WM_MOUSELEAVE消息时结束定时器即销毁定时器。定时器的主要代码如下:

void MyDrawButton::OnTimer(UINT_PTR nIDEvent)
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值
    if(nIDEvent != 24)
        return;
    CPoint    point;
    CRect    rect;
    GetWindowRect(&rect);    
    GetCursorPos(&point);
    // 如果鼠标离开按钮区域,重绘按钮
    if (!rect.PtInRect(point) && m_bMove)
    {
        KillTimer (24);
        m_DrawState=ST_MOVEOUT;
        m_bMove=FALSE;
        Draw();
    }
    CButton::OnTimer(nIDEvent);
}

  重绘按钮类MyDrawButton的主要实现代码如下:

  定义的一些重绘用到的变量:

    #define ST_MOVEIN        0//绘制状态—在按钮区域上
    #define ST_MOVEOUT        1 //绘制状态—不在按钮区域上
    int m_DrawState;//绘制状态    
    int m_nBmpID;//当前显示的背景bmp位图的资源ID
    bool m_bMove;//鼠标是否进入按钮区域
    COLORREF m_clText;//当前文本颜色
    COLORREF m_clActiveText;//鼠标进入按钮区域时文本颜色
    COLORREF m_clNormalText;//鼠标离开按钮区域时文本颜色

  消息处理函数和定义的函数以及实现:

void MyDrawButton::PreSubclassWindow()
{
    SetButtonStyle(GetButtonStyle() | BS_OWNERDRAW);
}
void MyDrawButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    Draw();//绘制按钮
}
void MyDrawButton::Draw()//绘制按钮
{
    DrawBackground();//绘制按钮bmp位图,并使背景透明化
    DrawText();//绘制按钮上的文本
}
void MyDrawButton::DrawText()
{//绘制按钮上的文本的字体大小、样式等
    CString        itemString;
    CRect  clientRect;
    CClientDC  dc(this);
    GetClientRect(&clientRect);
    GetWindowText(itemString);
    if(itemString)
    {
        CSize size=dc.GetTextExtent (itemString);//获得所选字体中指定字符串的高度和宽度
        int rectwidth=clientRect.Width();
        int rectheight=clientRect.Height();
        int textwidth=size.cx ;
        int textheight=size.cy ;        
        int x,y; // 文本的位置
        // 计算文本的输出位置
        x=(rectwidth-textwidth)/2;//水平居中
        y=(rectheight-textheight)/2;//垂直居中            
        switch(m_DrawState)
        {
        case ST_MOVEIN://鼠标进入按钮区域
            m_clText=m_clActiveText;
            break;
        case ST_MOVEOUT://鼠标离开按钮区域
            m_clText=m_clNormalText;
            break;
        default:
            m_clText=m_clNormalText;
            break;
        }
        dc.SetTextColor(m_clText);
        dc.SetBkMode(TRANSPARENT);
        CFont *font ;
        font =new CFont();
        int fontSize = 14;       
        font->CreateFont(fontSize,0,0,0,FW_BOLD,FALSE,FALSE,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,FF_SWISS,_T("宋体"));
        dc.SelectObject(font);
        dc.TextOut (x,y,itemString);
    }
}
void MyDrawButton::SetBkBmp(int nBmpID)
{//设置按钮bmp位图样式
    m_nBmpID = nBmpID;
}
void MyDrawButton::DrawBackground()
{//绘制按钮bmp位图,并使背景透明化
    CRect winRC;
    CDC* pDC=GetWindowDC();
    CDC memDC;
    memDC.CreateCompatibleDC(pDC);
    BITMAPINFO bmpInfo;
    CBitmap bmp;    
    GetWindowRect(&winRC);

    bmp.LoadBitmap(m_nBmpID);
    bmp.GetObject(sizeof(BITMAPINFO),&bmpInfo);
    int nBmpCX = bmpInfo.bmiHeader.biWidth;
    int nBmpCY = bmpInfo.bmiHeader.biHeight;
    memDC.SelectObject(bmp);
    pDC->TransparentBlt(0,0,nBmpCX,nBmpCY,&memDC,0,0,
        nBmpCX,nBmpCY,RGB(14,94,157));//在窗口中绘制位图,RGB(14,94,157)是透明色
    bmp.DeleteObject();
    ReleaseDC(pDC);    
}

  到此,按钮的自定义重绘完成了,接下来就可以使用自己重绘的按钮类MyDrawButton了。首先往对话框中添加一个按钮控件(以立即扫描按钮为例),假设它的ID值为IDC_TEST。进入类向导(Class Wizard)的成员变量属性页,为IDC_ BEGIN添加一个变量m_btnBegin。如下:

MyDrawButton m_btnBegin;

  然后就可以调用MyDrawButton的方法来设置按钮的样式了。如下:

m_btnBegin.SetBkBmp(IDB_BTN210x95,IDB_BTN210x95_3);//IDB_BTN210x95,IDB_BTN210x95分别为默认位图和鼠标在按钮区域时的位图。

  到现在为止,按钮类的重绘完成了,可以随意定义自己喜欢的样式的按钮了。现在相对完善成形的一个垃圾清理工具软件就开发完了。

  最后软件的主要成果界面如下:

图片 3

                                         软件打开准备就绪界面

图片 4

 

                                             正在运行截面图

图片 5

 

                                              扫描完成界面图:

 

图片 6

                                               清理完成界面图

 源代码下载地址。

 

 

**  模仿前最重要的一步就是观察,经过半天对QQ的摆弄和摸索,总结出了以下一些特点:

第二种方法

基础知识:我们可以模拟在win32中窗口移动的函数处理过程。简单的说,我们只需要在自己的窗口中对WM_LBUTTONDOWN、WM_MOUSEMOVE、WM_LBUTTONUP这三个消息进行处理即可。在WM_LBUTTONDOWN中记录鼠标左键被按下时的信息,WM_MOUSEMOVE中记录鼠标移动距离,WM_LBUTTONUP记录鼠标左键弹起。

LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    //记录鼠标按下
    is_lbutton_down_ = true;

    //鼠标按下时的坐标
    start_point_.x = GET_X_LPARAM(lParam);
    start_point_.y = GET_Y_LPARAM(lParam);

    bHandled = TRUE;
    return 0;
}

LRESULT OnLButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    //左键弹起,改变鼠标状态
    is_lbutton_down_ = false;
    bHandled = TRUE;
    return 0;
}

LRESULT OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) 
{
    if (is_lbutton_down_ == true)
    {
        POINT point;

        //获取当前鼠标的位置
        ::GetCursorPos(&point);
        ::ScreenToClient(m_pm.GetPaintWindow(), &point);

        //获取新的位置
        int Dx = point.x - start_point_.x;
        int Dy = point.y - start_point_.y;

        start_rect_.left += Dx;
        start_rect_.right += Dx;
        start_rect_.top += Dy;
        start_rect_.bottom += Dy;

        //将窗口移到新的位置  
        SetWindowPos(m_hWnd, HWND_TOP, start_rect_.left, start_rect_.top, 0, 0, SWP_NOSIZE);
    }

    bHandled = TRUE;
    return 0;
}
  • 但是,这种方法也存在一个明显的bug,当你拖动窗口一直到任务栏,然后松开鼠标左键,这时窗口就会自动跟着鼠标移动。

  • 解决这个bug的方法就需要响应WM_MOUSELEAVE消息,在该消息中记录鼠标已经移出窗口。

  • 默认情况下,窗口是不响应WM_MOUSELEAVE和WM_MOUSEHOVER消息的,所以要使用_TrackMouseEvent函数来激活这两个消息。调用这个函数后,当鼠标在指定窗口上停留超过一定时间或离开窗口后,该函数会Post这两个消息到指定窗口。

MSDN:The _TrackMouseEvent function posts messages when the mouse pointer leaves a window or hovers over a window for a specified amount of time. This function calls TrackMouseEvent if it exists, otherwise it emulates it.

  • 具体方法如下:

    • 在窗口类中定义一个变量来标识是否追踪当前鼠标状态,之所以要这样定义是要避免鼠标已经在窗体之上时,一移动鼠标就不断重复产生WM_MOUSEHOVER消息。
    BOOL is_mouse_track_=TRUE ;
    
    • 在OnMouseMove中调用_TrackMouseEvent函数
     if (is_mouse_track_)
     {
          TRACKMOUSEEVENT csTME;
          csTME.cbSize = sizeof (csTME);
          csTME.dwFlags = TME_LEAVE|TME_HOVER;
          csTME.hwndTrack = m_hWnd ;
          csTME.dwHoverTime = 10;  // 鼠标在按钮上停留超过10ms ,才认为状态 HOVER
          ::_TrackMouseEvent (&csTME);
    
          is_mouse_track_=FALSE ; 
     }
    
    • 在 OnMouseLeave 中再次允许追踪鼠标状态
    is_mouse_track_=TRUE ;
    

图片 7

  1、窗口开始粘附时,检测的是鼠标坐标与桌面边界的距离,特别地,粘附在下面的时候,检测的是与任务栏的距离;

使用MFC实现上面的按钮半透明效果能看到父窗口中的内容,上面是效果图(一个是带背景图片的、另一个是不带的)。

  2、在向上移动窗口时,窗口边界永远不会超出桌面上面边界;

控件继承自CWnd类(彩色的部分是窗口的背景图片、按钮是PNG图片,第二个图标是鼠标指向时的效果)。

  3、窗口是个 TopMost 风格;

图标的绘制使用GDI+绘制PNG图片,在此不多说了(处理WM_PAINT消息):

  4、当窗口粘附在上面、左边或右边并显示时,你把鼠标移动到最顶端,光标变成改变窗口大小的图标,而单单是把窗口的top坐标设置为0是不行的;

 1 void PNGButton::OnPaint()
 2 {  
 3  CPaintDC dc(this);    
 4  Graphics g(dc.m_hDC);   
 5  if(DrawBorder){   
 6     g.DrawImage(hoverBg,0,0);//画鼠标指向时的亮色背景  
 7  } 
 8  g.DrawImage(this->bg,0,0);//画按钮图标
 9   g.ReleaseHDC(dc.m_hDC);
10 }

  5、粘附在下面的时候,当处于移动状态,那么窗口的底边是与任务栏顶边对齐的,但从隐藏到显示的时候,窗口的底端是与屏幕底边对齐的;

 

  6、隐藏后显露出来的那条线可能是一个Border,但肯定的是绝不包含Client区域;

透明的关键:注意后面调用此方法的代码

  7、关于响应鼠标的进入与移出窗口,绝对不是WM_MOUSEMOVE、WM_MOUSELEAVE。证明:你以及其慢的速度接触隐藏状态的QQ边界,你会发现几乎是“一触即发”,你又以及其慢的速度移出显示状态的QQ,你会发现它的收缩反而不是“一触即发”的,而是离边缘10象素左右。而WM_MOUSEMOVE,WM_MOUSELEAVE,只有在进入、移出Client区域才响应,明显和QQ不同,其实从第6点也可以知道;

关键在于InvalidateRect函数:通知父窗口重新绘制特定区域,执行此函数后按钮所在区域就被父窗口绘制的内容覆盖.在父窗口绘制完成后,
按钮也会收到WM_PAINT消息,执行上面的一段OnPaint代码.

  8、粘附在两边的时候,高度会调整为桌面上边界到任务栏下边界的距离;

1 void PNGButton::PaintParent()
2 {  
3  CRect   rect; 
4  GetWindowRect(&rect); 
5  GetParent()-> ScreenToClient(&rect); 
6  GetParent()-> InvalidateRect(&rect);
7 }

  9、在“拖动时显示窗口内容”模式下(桌面属性-外观-效果),粘附在两边的拖动出来时;如果收缩之前高度比收缩后小则回复原来高度,在非“拖动时显示窗口内容”模式下,光栅会回复原来高度,但释放左键时,高度却是收缩时调整后的高度,一开始我以为这是个BUG,但我编写时同样出现这个问题,发现这两种模式会影响WM_MOVING参数的意义;

捕获鼠标指向或移出事件(处理WM_MOUSEMOVE,WM_MOUSEOVER,WM_MOUSELEAVE消息):

  10、粘附在两边的时候当你设置任务栏自动隐藏,QQ窗口会自动调整高度充满屏幕高度;

 

  11、窗口显示或隐藏不是一瞬间的,这点在第9点提到的两种模式下,会有所不同;

 1 void PNGButton::OnMouseHover(UINT nFlags, CPoint point)
 2 {    
 3  DrawBorder=true;
 4  PaintParent();//通知父窗口重绘特定区域,会引发控件自身的重绘
 5 }
 6 
 7 
 8  void PNGButton::OnMouseLeave()
 9 {    
10  m_is_mouse_over =   false; 
11  m_is_tracked =   false;  
12  DrawBorder=false;
13  PaintParent();  //通知父窗口重绘特定区域,会引发控件自身的重绘
14  CWnd::OnMouseLeave();
15 }
16 
17 
18  void PNGButton::OnMouseMove(UINT nFlags, CPoint point)
19 {
20  m_is_mouse_over   =   true;  
21  if(!m_is_tracked) 
22  { 
23   TRACKMOUSEEVENT   tme; 
24   tme.cbSize  =   sizeof(TRACKMOUSEEVENT); 
25   tme.dwFlags  =   TME_LEAVE|TME_HOVER;  
26   tme.hwndTrack   =   GetSafeHwnd(); 
27   tme.dwHoverTime =   80; 
28   _TrackMouseEvent(&tme);  
29   m_is_tracked   =   true;    
30  }   
31  CWnd::OnMouseMove(nFlags, point);
32 }

  12、任务栏并不显示QQ窗口;

附:

  二、编写代码

从资源加载PNG图片

  观察完毕,就开始编写了。

图片 8图片 9View Code

  首先新建一个基于对话框的MFC程序,命名为QQHideWnd,在对话框属性的styles页把border改为Resizing,你也可同时把Entended styles 的 tool window 钩上,对于这点我在程序了动态修改了。
在QQHideWndDlg.h头文件添加以下成员函数:

 1 #pragma once
 2 #include "stdafx.h"
 3  using namespace Gdiplus; 
 4 
 5  static bool ImageFromIDResource(UINT nID, LPCTSTR sTR,Image * &pImg)
 6 {
 7      HINSTANCE hInst = AfxGetResourceHandle();
 8      HRSRC hRsrc = ::FindResource (hInst,MAKEINTRESOURCE(nID),sTR); // type
 9       if (!hRsrc)
10       return FALSE;
11      // load resource into memory
12       DWORD len = SizeofResource(hInst, hRsrc);
13      BYTE* lpRsrc = (BYTE*)LoadResource(hInst, hRsrc);
14      if (!lpRsrc)
15       return FALSE;
16      // Allocate global memory on which to create stream
17       HGLOBAL m_hMem = GlobalAlloc(GMEM_FIXED, len);
18      BYTE* pmem = (BYTE*)GlobalLock(m_hMem);
19      memcpy(pmem,lpRsrc,len);
20      IStream* pstm;
21      CreateStreamOnHGlobal(m_hMem,FALSE,&pstm);
22      // load from stream
23       pImg=Gdiplus::Image::FromStream(pstm);
24      // free/release stuff
25       GlobalUnlock(m_hMem);
26      pstm->Release();
27      FreeResource(lpRsrc);
28      return TRUE;
29 }
protected:
//修正移动时窗口的大小
void FixMoving(UINT fwSide, LPRECT pRect);
//从收缩状态显示窗口
void DoShow();
//从显示状态收缩窗口
void DoHide();
//重载函数,只是为了方便调用,实际调用CWnd的SetWindowPos(…)
BOOL SetWindowPos(const CWnd* pWndInsertAfter,LPCRECT pCRect, UINT nFlags = SWP_SHOWWINDOW);

平铺图片的代码

  继续添加成员变量:

 1     CPaintDC dc(this); 
 2     CRect rect; 
 3     GetClientRect(rect);
 4     CBrush bs(RGB(240,240,240));//窗口背景色
 5      dc.FillRect(&rect,&bs);        //窗口着色
 6     //填充背景图片:平铺
 7      Graphics g(dc.m_hDC);    
 8     if(has_bg) g.DrawImage(this->bg,0,0); 
 9     Gdiplus::TextureBrush bbs(this->img);
10     g.FillRectangle(&bbs,0,0,rect.Width(),this->img->GetHeight()); 
11     g.ReleaseHDC(dc.m_hDC);
12     //TRACE(L"CMainFrame::OnPaintrn"); 
private::BOOL m_isSizeChanged;//窗口大小是否改变了
BOOL m_isSetTimer;//是否设置了检测鼠标的Timer
INTm_oldWndHeight;//旧的窗口宽度INTm_taskBarHeight;//任务栏高度INTm_edgeHeight;//边缘高度
INTm_edgeWidth;//边缘宽度
INTm_hideMode;//隐藏模式
BOOL m_hsFinished;//隐藏或显示过程是否完成
BOOL m_hiding;//该参数只有在!m_hsFinished才有效
//真:正在隐藏,假:正在显示

附:PNGButton类代码下载

  增加消息响应,需要注意的是有些消息你只有把右下角的 Filter for message设置为window才能看到。

WM_ NCHITTEST
WM_MOVING
WM_CREATE
WM_TIMER

  然后来到对应的cpp文件,在头部定义一些宏:

//收缩模式#define HM_NONE0//不收缩
#define HM_TOP1//向上收缩
#define HM_BOTTOM2//向下收缩
#define HM_LEFT3//向左收缩
#define HM_RIGHT4//向右收缩
#define CM_ELAPSE200 //检测鼠标是否离开窗口的时间间隔
#define HS_ELAPSE5//伸缩过程每步的时间间隔
#define HS_STEPS10//伸缩过程分成多少步完成
#define INTERVAL20//触发粘附时鼠标与屏幕边界的最小间隔,单位为象素
#define INFALTE10//触发收缩时鼠标与窗口边界的最小间隔,单位为象素

  然后在构造函数初始化成员变量:

m_isSizeChanged = FALSE;
m_isSetTimer = FALSE;m_hsFinished = TRUE;
m_hiding = FALSE;m_oldWndHeight = MINCY;
m_taskBarHeight = 30;
m_edgeHeight = 0;
m_edge;
m_hideMode = HM_NONE;

  完成了一些初始的工作,那么就开始进入关键的函数实现了。首先是在OnCreate做些窗口的初始化和获得一些系统信息。

  【代码一】

int CQQHideWndDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDialog::OnCreate(lpCreateStruct) == -1)
return -1;

// TODO: Add your specialized creation code here

//获得任务栏高度
CWnd* p;
p = this->FindWindow("Shell_TrayWnd",NULL);
if(p != NULL)
{
CRect tRect;
p->GetWindowRect(tRect);
m_taskBarHeight = tRect.Height();
}

//修改风格使得他不在任务栏显示
ModifyStyleEx(WS_EX_APPWINDOW, WS_EX_TOOLWINDOW);
//去掉关闭按键(如果想画3个按键的话)
//ModifyStyle(WS_SYSMENU,NULL);

//获得边缘高度和宽度
m_edgeHeight = GetSystemMetrics(SM_CYEDGE);
m_edgeWidth = GetSystemMetrics(SM_CXFRAME);

return 0;
}

  接着如何知道鼠标进入或移出窗口呢?在前面我已经证明了WM_MOUSEMOVE和WM_MOUSELEAVE不符合我们的要求,于是我用了WM_ NCHITTEST这个消息,你可以看到我在这个消息响应函数中用了两个SetTimer,一个用于检测鼠标是否离开,一个用于伸缩过程,不管你喜欢不喜欢,要达到第7点和第11点,这个是必须的,考虑的效率问题,在不需要的时候关闭这些Timer就好了。

  【代码二】

UINT CQQHideWndDlg::OnNcHitTest(CPoint point)
{
// TODO: Add your message handler code here and/or call default
CString str;
str.Format("Mouse (%d,%d)",point.x,point.y);
GetDlgItem(IDC_CURSOR)->SetWindowText(str);
if(m_hideMode != HM_NONE && !m_isSetTimer &&
//防止鼠标超出屏幕右边时向右边收缩造成闪烁
point.x < GetSystemMetrics(SM_CXSCREEN) + INFALTE)
{ //鼠标进入时,如果是从收缩状态到显示状态则开启Timer
SetTimer(1,CM_ELAPSE,NULL);
m_isSetTimer = TRUE;

m_hsFinished = FALSE;
m_hiding = FALSE;
SetTimer(2,HS_ELAPSE,NULL); //开启显示过程
}
return CDialog::OnNcHitTest(point);
}

  然后在OnTimer中

  【代码三】

void CQQHideWndDlg::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
if(nIDEvent == 1 )
{
POINT curPos;
GetCursorPos(&curPos);

CString str;
str.Format("Timer On(%d,%d)",curPos.x,curPos.y);
GetDlgItem(IDC_TIMER)->SetWindowText(str);

CRect tRect;
//获取此时窗口大小
GetWindowRect(tRect);
//膨胀tRect,以达到鼠标离开窗口边沿一定距离才触发事件
tRect.InflateRect(INFALTE,INFALTE);

if(!tRect.PtInRect(curPos)) //如果鼠标离开了这个区域
{
KillTimer(1); //关闭检测鼠标Timer
m_isSetTimer = FALSE;
GetDlgItem(IDC_TIMER)->SetWindowText("Timer Off");

m_hsFinished = FALSE;
m_hiding = TRUE;
SetTimer(2,HS_ELAPSE,NULL); //开启收缩过程
}
}

if(nIDEvent == 2)
{
if(m_hsFinished) //如果收缩或显示过程完毕则关闭Timer
KillTimer(2);
else
m_hiding ? DoHide() : DoShow();
}
CDialog::OnTimer(nIDEvent);
}

  暂时不管OnTimer中的DoHide(); DoShow();
先来看看核心的函数之一的 FixMoving,该函数在OnMoving中被调用,FixMoving通过检测鼠标位置和窗口位置来决定窗口的收缩模式,并修正粘附边界时窗口的位置,从而达到像移动QQ时出现的效果。

  【代码四】

void CQQHideWndDlg::FixMoving(UINT fwSide, LPRECT pRect)
{
POINT curPos;
GetCursorPos(&curPos);
INT screenHeight = GetSystemMetrics(SM_CYSCREEN);
INT screenWidth = GetSystemMetrics(SM_CXSCREEN);
INT height = pRect->bottom - pRect->top;
INT width = pRect->right - pRect->left;

if (curPos.y <= INTERVAL)
{ //粘附在上边
pRect->bottom = height - m_edgeHeight;
pRect->top = -m_edgeHeight;
m_hideMode = HM_TOP;
}
else if(curPos.y >= (screenHeight - INTERVAL - m_taskBarHeight))
{ //粘附在下边
pRect->top = screenHeight - m_taskBarHeight - height;
pRect->bottom = screenHeight - m_taskBarHeight;
m_hideMode = HM_BOTTOM;
}
else if (curPos.x < INTERVAL)
{ //粘附在左边
if(!m_isSizeChanged)
{
CRect tRect;
GetWindowRect(tRect);
m_oldWndHeight = tRect.Height();
}
pRect->right = width;
pRect->left = 0;
pRect->top = -m_edgeHeight;
pRect->bottom = screenHeight - m_taskBarHeight;
m_isSizeChanged = TRUE;
m_hideMode = HM_LEFT;
}
else if(curPos.x >= (screenWidth - INTERVAL))
{ //粘附在右边
if(!m_isSizeChanged)
{
CRect tRect;
GetWindowRect(tRect);
m_oldWndHeight = tRect.Height();
}
pRect->left = screenWidth - width;
pRect->right = screenWidth;
pRect->top = -m_edgeHeight;
pRect->bottom = screenHeight - m_taskBarHeight;
m_isSizeChanged = TRUE;
m_hideMode = HM_RIGHT;
}
else
{ //不粘附
if(m_isSizeChanged)
{ //如果收缩到两边,则拖出来后会变回原来大小
//在"拖动不显示窗口内容下"只有光栅变回原来大小
pRect->bottom = pRect->top + m_oldWndHeight;
m_isSizeChanged = FALSE;
}
if(m_isSetTimer)
{ //如果Timer开启了,则关闭之
if(KillTimer(1) == 1)
m_isSetTimer = FALSE;
}
m_hideMode = HM_NONE;
GetDlgItem(IDC_TIMER)->SetWindowText("Timer off");
}
}

  收缩模式和位置决定后,剩下的工作就由最后两个核心函数完成了:实现收缩的DoHide(),实现伸展的DoShow()。在这两个过程中m_hsFinished,m_hiding 这两个变量起到很重要的控制作用。由于伸缩过程没完成时,hsFinished始终为FALSE,所以Timer 2 不会关闭,于是在OnTimer中会重复调用这两个函数之一,在这两个函数体内,窗口位置有规律地递减或递增就可以达到QQ的“抽屉”效果了,有趣的是即使伸缩过程还没完成,你也可以在这个过程中改变m_hiding这个值来决定他是伸还是缩,正如QQ一样。你可以把Timer 2 的事件间隔调大一点,然后在窗口伸缩时,鼠标来回地进出窗口就会很容易看到这样有趣的效果(还没缩进去又被拉了出来,或者还没拉出来又缩进去了)。

  【代码五】

void CQQHideWndDlg::DoHide()
{
if(m_hideMode == HM_NONE)
return;

CRect tRect;
GetWindowRect(tRect);

INT height = tRect.Height();
INT width = tRect.Width();

INT steps = 0;

switch(m_hideMode)
{
case HM_TOP:
steps = height/HS_STEPS;
tRect.bottom -= steps;
if(tRect.bottom <= m_edgeWidth)
{ //你可以把下面一句替换上面的 ...+=|-=steps 达到取消抽屉效果
//更好的办法是添加个BOOL值来控制,其他case同样.
tRect.bottom = m_edgeWidth;
m_hsFinished = TRUE; //完成隐藏过程
}
tRect.top = tRect.bottom - height;
break;
case HM_BOTTOM:
steps = height/HS_STEPS;
tRect.top += steps;
if(tRect.top >= (GetSystemMetrics(SM_CYSCREEN) - m_edgeWidth))
{
tRect.top = GetSystemMetrics(SM_CYSCREEN) - m_edgeWidth;
m_hsFinished = TRUE;
}
tRect.bottom = tRect.top + height;
break;
case HM_LEFT:
steps = width/HS_STEPS;
tRect.right -= steps;
if(tRect.right <= m_edgeWidth)
{
tRect.right = m_edgeWidth;
m_hsFinished = TRUE;
}
tRect.left = tRect.right - width;
tRect.top = -m_edgeHeight;
tRect.bottom = GetSystemMetrics(SM_CYSCREEN) - m_taskBarHeight;
break;
case HM_RIGHT:
steps = width/HS_STEPS;
tRect.left += steps;
if(tRect.left >= (GetSystemMetrics(SM_CXSCREEN) - m_edgeWidth))
{
tRect.left = GetSystemMetrics(SM_CXSCREEN) - m_edgeWidth;
m_hsFinished = TRUE;
}
tRect.right = tRect.left + width;
tRect.top = -m_edgeHeight;
tRect.bottom = GetSystemMetrics(SM_CYSCREEN) - m_taskBarHeight;
break;
default:
break;
}

SetWindowPos(&wndTopMost,tRect);
}

  【代码六】

void CQQHideWndDlg::DoShow()
{
if(m_hideMode == HM_NONE)
return;

CRect tRect;
GetWindowRect(tRect);
INT height = tRect.Height();
INT width = tRect.Width();

INT steps = 0;

switch(m_hideMode)
{
case HM_TOP:
steps = height/HS_STEPS;
tRect.top += steps;
if(tRect.top >= -m_edgeHeight)
{ //你可以把下面一句替换上面的 ...+=|-=steps 达到取消抽屉效果
//更好的办法是添加个BOOL值来控制,其他case同样.
tRect.top = -m_edgeHeight;
m_hsFinished = TRUE; //完成显示过程
}
tRect.bottom = tRect.top + height;
break;
case HM_BOTTOM:
steps = height/HS_STEPS;
tRect.top -= steps;
if(tRect.top <= (GetSystemMetrics(SM_CYSCREEN) - height))
{
tRect.top = GetSystemMetrics(SM_CYSCREEN) - height;
m_hsFinished = TRUE;
}
tRect.bottom = tRect.top + height;
break;
case HM_LEFT:
steps = width/HS_STEPS;
tRect.right += steps;
if(tRect.right >= width)
{
tRect.right = width;
m_hsFinished = TRUE;
}
tRect.left = tRect.right - width;
tRect.top = -m_edgeHeight;
tRect.bottom = GetSystemMetrics(SM_CYSCREEN) - m_taskBarHeight;
break;
case HM_RIGHT:
steps = width/HS_STEPS;
tRect.left -= steps;
if(tRect.left <= (GetSystemMetrics(SM_CXSCREEN) - width))
{
tRect.left = GetSystemMetrics(SM_CXSCREEN) - width;
m_hsFinished = TRUE;
}
tRect.right = tRect.left + width;
tRect.top = -m_edgeHeight;
tRect.bottom = GetSystemMetrics(SM_CYSCREEN) - m_taskBarHeight;
break;
default:
break;
}

SetWindowPos(&wndTopMost,tRect);
}

BOOL CQQHideWndDlg::SetWindowPos(const CWnd* pWndInsertAfter, LPCRECT pCRect, UINT nFlags)
{
return CDialog::SetWindowPos(pWndInsertAfter,pCRect->left, pCRect->top,
pCRect->right - pCRect->left, pCRect->bottom - pCRect->top, nFlags);
}

  到此,程序终于完成了。在我的源代码中还有对WM_SIZING的处理和定义了与之相关的宏,这些主要是控制窗口在调整大小时不能超过最小的宽度和高度,与QQ的自动伸缩无关,所以不在这里提及了。

  三、结束语

  虽然还不能算是完美的模仿,但效果已经非常非常的接近了。也许有人会奇怪为什么要用Tool Window 风格,这是因为,这样在任务栏中不会显示窗口。从QQ的标题栏高度也可以判断出他也是这种风格,但这样一来就不能拥有最小化、最大化按键了。实际上QQ的最大化、最小化和关闭按键都是用DC画上去的。如何在Caption上增加按键,外国一些开源网站有源代码,我下载并看了一下,发现里面有个知识点很有趣,那就是更改消息路由,有兴趣的可以去下载来学习一下。

  QQ的成功很大部分在于他的界面比较人性化(用了MSN后深有感受),而这些界面实现起来原理也许很简单,难的是观察东西心要细、设计东西心要密、开发东西心要异。

本文由10bet手机官网发布于web前端,转载请注明出处:Duilib中让弹出窗口整体能被拖动的两种方法,设计超强仿QQ自动伸缩窗口

上一篇:没有了 下一篇:中调用DLL中的函数,VC中创建DLL动态连接库的方法
猜你喜欢
热门排行
精彩图文