2007年1月31日星期三

自绘边框窗口

<a href=http://www.vczx.com/article/show.php?id=742>参考</a>

自绘窗口需要响应如下的几个消息:
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了。

没有评论: