导航

自绘窗口边框和标题栏

Posted on 2011-11-30 15:54  hcfalan  阅读(704)  评论(0编辑  收藏  举报

自绘窗口需要响应如下的几个消息:

1、WM_NCCALCSIZE
这个是用来返回NC区域的。windows系统根据这个消息的返回,决定矩形区域中,NC区域在哪里。如下的代码是一个实现:
void CSIPanel::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS FAR* lpncsp)
{
   RECT r;
   // TODO: Add your message handler code here and/or call default
   if (m_psi)
   {
      if (m_psi->m_bCustomWindow)
      {
          memcpy(&r,&lpncsp->rgrc[0],sizeof(RECT));
          r.top = r.top+m_psi->m_NcRect.top;
          r.bottom = r.bottom - m_psi->m_NcRect.bottom;
          r.left = r.left+m_psi->m_NcRect.left;
          r.right = r.right-m_psi->m_NcRect.right;
          memcpy(&lpncsp->rgrc[0],&r,sizeof(RECT));
          return;
       }
   }
   CWnd::OnNcCalcSize(bCalcValidRects, lpncsp);
}
在这个代码中,m_psi->m_bCustomWindow是用来标志是否是自绘窗口的。如果不是自绘窗口,才需要调整大小。至于bCalcValidRects这个参数,说老实话我也不知道它的用处。我查过很多的代码,都没有使用这个参数(比如BCGPRO的代码等),而实际情况下也是不使用也没有问题的。完全只需要更改lpncsp->rgrc[0]就可以了。

2、需要响应NCPAINT消息
(如果在窗口非active的情况下,并不绘制ncpaint或者调用原来的ncpaint,那么系统就会画出以前的难看的窗口边框和标题)很简单,代码:
void CSIPanel::OnNcPaint()
{
   CWindowDC dc(this);
   if (m_psi == NULL)
      return;
   if (m_psi->m_bCustomWindow == FALSE)
      return;
   p_PaintCorners(&dc);
   p_PaintIcons(&dc);
   p_PaintPushedIcons(&dc);
   p_PaintTitleLine(&dc);
}

3、还需要响应CREATE消息
为何响应Create消息?因为窗口是不规则的(不是直角矩形而是园角矩形的)
int CSIPanel::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
   if (CWnd::OnCreate(lpCreateStruct) == -1)
      return -1;
   // TODO: Add your specialized creation code here
   ResetWindowRgn();
   SetWindowPos(NULL,0,0,0,0,SWP_FRAMECHANGED|SWP_NOOWNERZORDER|
         SWP_NOMOVE|SWP_NOSIZE);
   return 0;
}
SetWindowPos是强制让Window重新计算大小(NCCALCSIZE)和绘制。
/////////////////////////////////////////////////////////////////////////////
// CSIPanel message handlers
void CSIPanel::ResetWindowRgn()
{
   int iRet;
   BOOL bRet;
   CRgn m_rgn1,m_rgn2,m_rgn3;
   RECT r;
   GetWindowRect(&r);
   OffsetRect(&r,-r.left,-r.top);
   bRet = m_rgn1.CreateRoundRectRgn(r.left,r.top,r.right+1,r.top+45,12,12);
   m_rgn2.CreateRoundRectRgn(r.left,r.top+18,r.right+1,r.bottom+2,12,12);
   iRet = m_rgn2.CombineRgn(&m_rgn1,&m_rgn2,RGN_OR);
   iRet = SetWindowRgn(m_rgn2,TRUE);
}

4、还需要响应WM_SIZE
void CSIPanel::OnSize(UINT nType, int cx, int cy)
{
   CWnd::OnSize(nType, cx, cy);
   // TODO: Add your message handler code here
   ResetWindowRgn();
   Invalidate();
}

5、响应ACTIVE
用于绘制不同情况下的标题
void CSIPanel::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
{
   CWnd::OnActivate(nState, pWndOther, bMinimized);
   if (nState == WA_INACTIVE)
   {
      if (m_bTraceFlag)
      {
         ReleaseCapture();
         m_bTraceFlag = FALSE;
      }
      m_bActive = FALSE;
   }
   else
      m_bActive = TRUE;
   SetWindowPos(NULL,0,0,0,0,SWP_FRAMECHANGED|SWP_NOOWNERZORDER|
        SWP_NOMOVE|SWP_NOSIZE);
}
这里为何要检测一个鼠标的情况呢?因为如果在标题行上有自绘的小按钮,那么,我们需要跟踪鼠标才行(响应NCMOUSELCLICK是不行的)所以,如果本窗口失去焦点,应该RelaseCapture。

6、响应NCLBUTTONDOWN
为了能够在标题行绘制自己的最大最小按钮,并且能够响应它,就绪要响应在标题行上的鼠标左键。但是为了能够像window操作一样,鼠标左键按下的时候不触发,而是鼠标左键抬起的时候触发,这就需要在NCLBUTTONDOWN的时候做SetCapture.
void CSIPanel::OnNcLButtonDown(UINT nHitTest, CPoint point)
{
   // TODO: Add your message handler code here and/or call default
   RECT r;
   RECT r1;
   BOOL bRet;
   int iSizeX,iSizeY;

   if (m_bTraceFlag == TRUE)
      return;
   m_bMinPushed = FALSE;
   m_bMaxPushed = FALSE;
   m_bClosePushed = FALSE;
   m_bSysPushed = FALSE;
   GetWindowRect(&r);
   if (m_bActive == FALSE)
      goto L_DEFAULT;
   r.bottom = r.top + (m_psi->m_rLT.bottom - m_psi->m_rLT.top);
   bRet = PtInRect(&r,point);
   if (bRet == FALSE)
      goto L_DEFAULT;
   /* ----------------------------------------------------------
   * 判定是否在按钮上按下的.
   * ---------------------------------------------------------- */
   if (m_bIconSys)
   {
      r1.left = r.left + 5;
      r1.top = r.top + 5;
      r1.right = m_psi->m_rSYS.right - m_psi->m_rSYS.left + r1.left;
      r1.bottom = m_psi->m_rSYS.bottom- m_psi->m_rSYS.top + r1.top;
      bRet = PtInRect(&r1,point);
      if (bRet == TRUE)
     {
         m_bSysPushed = TRUE;
         goto L_FOUND;
      }
   }
   if (m_bIconMin)
   {
      iSizeX = m_psi->m_rCLOSE.right - m_psi->m_rCLOSE.left;
      iSizeY = m_psi->m_rCLOSE.bottom - m_psi->m_rCLOSE.top;
      r1.left = r.right - 6 - 3 * iSizeX - 3 * 2;
      r1.top = r.top+5;
      r1.right = r1.left + iSizeX;
      r1.bottom = r1.top + iSizeY;
      bRet = PtInRect(&r1,point);
      if (bRet == TRUE)
      {
         m_bMinPushed= TRUE;
         goto L_FOUND;
      }
   }
   if (m_bIconMax)
   {
      iSizeX = m_psi->m_rCLOSE.right - m_psi->m_rCLOSE.left;
      iSizeY = m_psi->m_rCLOSE.bottom - m_psi->m_rCLOSE.top;
      r1.left = r.right - 6 - 2 * iSizeX - 2 * 2;
      r1.top = r.top+5;
      r1.right = r1.left + iSizeX;
      r1.bottom = r1.top + iSizeY;
      bRet = PtInRect(&r1,point);
      if (bRet == TRUE)
      {
         m_bMaxPushed= TRUE;
         goto L_FOUND;
      }
   }
   if (m_bIconClose)
   {
      iSizeX = m_psi->m_rCLOSE.right - m_psi->m_rCLOSE.left;
      iSizeY = m_psi->m_rCLOSE.bottom - m_psi->m_rCLOSE.top;
      r1.left = r.right - 6 - iSizeX - 2;
      r1.top = r.top+5;
      r1.right = r1.left + iSizeX;
      r1.bottom = r1.top + iSizeY;
      bRet = PtInRect(&r1,point);
      if (bRet == TRUE)
      {
         m_bClosePushed= TRUE;
         goto L_FOUND;
      }
   }
L_DEFAULT:
   CWnd::OnNcLButtonDown(nHitTest, point);
   return;
L_FOUND:
   m_bTraceFlag = TRUE;
   SetCapture();
   RepaintIcons();
   return;
}

7、当然要响应LBUTTONUP消息
为何是LBUTTONUP而不是NCLBUTTONUP?因为在NCLBUTTONDOWN的时候SetCapture之后,就是LBUTTONUP消息了。(应该不会有NCLBUTTONUP消息的)。
void CSIPanel::OnLButtonUp(UINT nFlags, CPoint point)
{
   // TODO: Add your message handler code here and/or call default
   RECT r;
   CWnd::OnLButtonUp(nFlags, point);
   if (m_bTraceFlag == FALSE)
      return;
   ReleaseCapture();
   GetWindowRect(&r);
   m_bTraceFlag = FALSE;
   if (m_bSysPushed == TRUE)
   {
      // PostMessage(WM_SYSCOMMAND,SC_MOUSEMENU,r.left<<16|r.top);
     PostMessage(WM_SYSCOMMAND,SC_KEYMENU,r.left<<16|r.top);
   }
   else if (m_bMinPushed == TRUE)
   {
      PostMessage(WM_SYSCOMMAND,SC_MINIMIZE,r.left<<16|r.top);
   }
   else if (m_bMaxPushed == TRUE)
   {
      PostMessage(WM_SYSCOMMAND,SC_MAXIMIZE,r.left<<16|r.top);
   }
   else if (m_bClosePushed == TRUE)
   {
      PostMessage(WM_SYSCOMMAND,SC_CLOSE,r.left<<16|r.top);
   }
   m_bMinPushed = FALSE;
   m_bMaxPushed = FALSE;
   m_bClosePushed = FALSE;
   m_bSysPushed = FALSE;
}

8、剩下的就是响应MOUSEMOVE
为何响应MOUSEMOVE?因为我们注意到,普通windows按钮的性格是按下的时候BUTTON PUSHED,保持鼠标左键按下,移开鼠标的时候BUTTON UP,然后再到这个BUTTON上抬起鼠标左键才触发,为了能做的差不多像,我们有:
void CSIPanel::OnMouseMove(UINT nFlags, CPoint px)
{
// TODO: Add your message handler code here and/or call default
RECT r,r1,rc;
BOOL bRet;
int iSizeX,iSizeY;
POINT point;
CWnd::OnMouseMove(nFlags, px);
if (m_bTraceFlag == FALSE)
return;
GetWindowRect(&r);
GetClientRect(&rc);
ClientToScreen(&rc);
point.x = rc.left + px.x;
point.y = rc.top + px.y;
/* ----------------------------------------------------------
* 判定是否在按钮上按下的.
* ---------------------------------------------------------- */
if (m_bIconSys)
{
r1.left = r.left + 5;
r1.top = r.top + 5;
r1.right = m_psi->m_rSYS.right - m_psi->m_rSYS.left + r1.left;
r1.bottom = m_psi->m_rSYS.bottom- m_psi->m_rSYS.top + r1.top;
bRet = PtInRect(&r1,point);
if (bRet == TRUE)
{
m_bSysPushed = TRUE;
goto L_FOUND;
}
}
if (m_bIconMin)
{
iSizeX = m_psi->m_rCLOSE.right - m_psi->m_rCLOSE.left;
iSizeY = m_psi->m_rCLOSE.bottom - m_psi->m_rCLOSE.top;
r1.left = r.right - 6 - 3 * iSizeX - 3 * 2;
r1.top = r.top+5;
r1.right = r1.left + iSizeX;
r1.bottom = r1.top + iSizeY;
bRet = PtInRect(&r1,point);
if (bRet == TRUE)
{
m_bMinPushed= TRUE;
goto L_FOUND;
}
}
if (m_bIconMax)
{
iSizeX = m_psi->m_rCLOSE.right - m_psi->m_rCLOSE.left;
iSizeY = m_psi->m_rCLOSE.bottom - m_psi->m_rCLOSE.top;
r1.left = r.right - 6 - 2 * iSizeX - 2 * 2;
r1.top = r.top+5;
r1.right = r1.left + iSizeX;
r1.bottom = r1.top + iSizeY;
bRet = PtInRect(&r1,point);
if (bRet == TRUE)
{
m_bMaxPushed= TRUE;
goto L_FOUND;
}
}
if (m_bIconClose)
{
iSizeX = m_psi->m_rCLOSE.right - m_psi->m_rCLOSE.left;
iSizeY = m_psi->m_rCLOSE.bottom - m_psi->m_rCLOSE.top;
r1.left = r.right - 6 - iSizeX - 2;
r1.top = r.top+5;
r1.right = r1.left + iSizeX;
r1.bottom = r1.top + iSizeY;
bRet = PtInRect(&r1,point);
if (bRet == TRUE)
{
m_bClosePushed= TRUE;
goto L_FOUND;
}
}
m_bMinPushed = FALSE;
m_bMaxPushed = FALSE;
m_bClosePushed = FALSE;
m_bSysPushed = FALSE;
L_FOUND:
RepaintIcons();
return;
}
恩。到这里,自绘边框的窗口就ok了。