如何编写类似于Word97的工具栏

本文所讨论的工具栏类是同标准的 MFC CToolBar 一同工作的。

    注意:你必须有新的 COMCTL32.DLL (版本4.7或更高)。它是随 Internet Explorer 3 一同发行,并且将做为 Windows 98 的标准组件。如果你使用的是 VC++ 5,则你已经有该动态库了。

    所谓类似 Word 97 的增强工具栏具有平面外观,它的左边带有“gripper”并且各组间带有分隔线。当鼠标移动到上面时,按钮就会突出来。

               MFC 使用样式位来控制其平面外观。所以你不能在建立工具栏时来设置这种样式,你必须建立之后使用SetFlatLookStyle()函数来修改其样式。

    平面外观工具栏是透明绘制的。不幸的是,MFC 没有介绍该如何编写这种透明的工具栏,所以需要你重绘背景。这要通过变尺寸和移动信息来实现,例如当你拖动可移动的工具栏。你也可以通过其按钮样式改变时来实现。例如,当按钮从按状态变成释放状态时,背景需要重新绘制。

    工具栏控制本身并不在各组按钮间绘制分隔线,只是在其间增加一个空格。该类将截取 WM_PAINT 消息,并在正确的位置添加分隔线。

    工具栏控制也不支持在起左边或顶部的gripper。该类将调整其用户区并绘制相应的gripper。

    使用本类,只要简单的把你的 CToolBar 变成 CFlatToolBar,并在建立工具栏后调用 SetFlatLookStyle() 函数 (既当工具栏位图装入之后 )。

 // FlatToolBar.h
// (c) 1997, Roger Onslow

class CFlatToolBar : public CToolBar 
{
   DECLARE_DYNAMIC(CFlatToolBar);

   public:
      void SetFlatLookStyle();
      void RepaintBackground();
      void DrawSeparators();
      void DrawSeparators(CClientDC* pDC);
      void EraseNonClient();
      void DrawGripper(CWindowDC *pDC, CRect& rectWindow);

   protected:
      // ClassWizard generated virtual function overrides
      //{{AFX_VIRTUAL(CFlatToolBar)
      virtual void OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler);
      //}}AFX_VIRTUAL

   // 消息处理函数
   protected:
   //{{AFX_MSG(CFlatToolBar)
   afx_msg void OnWindowPosChanging(LPWINDOWPOS lpWndPos);
   afx_msg void OnPaint();
   afx_msg void OnNcPaint();
   afx_msg void OnNcCalcSize( BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp );
   //}}AFX_MSG

   DECLARE_MESSAGE_MAP();
};
 //***************************************************************
// FlatToolBar.cpp

#include "stdafx.h"
#include "flattoolbar.h"

#ifdef _DEBUG
#undef THIS_FILE
#define new DEBUG_NEW
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

BEGIN_MESSAGE_MAP(CFlatToolBar, CToolBar)
//{{AFX_MSG_MAP(CFlatToolBar)
ON_WM_WINDOWPOSCHANGING()
ON_WM_PAINT()
ON_WM_NCPAINT()
ON_WM_NCCALCSIZE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

IMPLEMENT_DYNAMIC(CFlatToolBar,CToolBar)
 // 必须在建立之后,因为MFC要清除多余的样式位
void CFlatToolBar::SetFlatLookStyle() 
{
   // 设置平面样式(透明的)
   ModifyStyle(0,TBSTYLE_FLAT);
   
   // others are...
   // #define TBSTYLE_TOOLTIPS 0x0100
   // #define TBSTYLE_WRAPABLE 0x0200
   // #define TBSTYLE_ALTDRAG 0x0400
   // #define TBSTYLE_FLAT 0x0800
   // #define TBSTYLE_LIST 0x1000
}
 // 因为按钮是透明的,所以我们需要重新绘制背景

void CFlatToolBar::RepaintBackground() 
{
   CRect rc; GetWindowRect(&rc); // 获取工具栏的矩形区域
   CWnd* pParent = GetParent(); // 获取父窗口
   pParent->ScreenToClient(&rc); // 转换为父窗口的坐标
   pParent->InvalidateRect(&rc); // 绘制其下面的矩形
}
 // 在用户区中绘制分隔线

void CFlatToolBar::DrawSeparators() 
{
   CClientDC dc(this); // get a dc for the client area
   DrawSeparators(&dc); // draw the separators on it
}
 // 绘制分隔线
void CFlatToolBar::DrawSeparators(CClientDC* pDC) 
{
   // 水平与垂直
   bool ishorz = (m_dwStyle & CBRS_ORIENT_HORZ) != 0;

   // 获取按钮数目
   int nIndexMax = (int)DefWindowProc(TB_BUTTONCOUNT, 0, 0);
   int nIndex;

   // 试一下每个按钮
   for (nIndex = 0; nIndex < nIndexMax; nIndex++) 
   {
      UINT dwStyle = GetButtonStyle(nIndex);
      UINT wStyle = LOWORD(dwStyle);

      // 如果是分隔线
      if (wStyle == TBBS_SEPARATOR) 
      {
         // 获取它的矩形和宽度
         CRect rect;
         GetItemRect(nIndex,rect);

         // 如果对分隔线足够用
         int w = rect.Width();
         if (w <= 8) 
         {
            if (ishorz) 
            {
               // 在中间绘制分隔线
               CRect rectbar = rect;
               int x = (rectbar.left+rectbar.right)/2;
               rectbar.left = x-1; rectbar.right = x+1;
               pDC->Draw3dRect(rectbar,::GetSysColor(COLOR_3DSHADOW),
               ::GetSysColor(COLOR_3DHILIGHT));
            }
            else 
            {
               // 在中间绘制分隔线
               CRect rectbar = rect;
               rectbar.left = rectbar.left - m_sizeButton.cx;
               rectbar.right = rectbar.left + m_sizeButton.cx;
               rectbar.top = rectbar.bottom+1;
               rectbar.bottom = rectbar.top+3;
               int y = (rectbar.top+rectbar.bottom)/2;
               rectbar.top = y-1; 
               rectbar.bottom = y+1;
               pDC->Draw3dRect(rectbar,::GetSysColor(COLOR_3DSHADOW),
               ::GetSysColor(COLOR_3DHILIGHT));
            }
         }
      }
   }
}
 // 在左边或顶部绘制gripper
void CFlatToolBar::DrawGripper(CWindowDC *pDC, CRect& rectWindow) 
{
   CRect gripper = rectWindow;
   gripper.DeflateRect(1,1);
   if (m_dwStyle & CBRS_FLOATING) 
   {
      // 无grippers
   } 
   else if (m_dwStyle & CBRS_ORIENT_HORZ) 
   {
      // gripper在左边
      gripper.right = gripper.left+3;
      pDC->Draw3dRect(gripper,::GetSysColor(COLOR_3DHIGHLIGHT),
      ::GetSysColor(COLOR_3DSHADOW));
      gripper.OffsetRect(+4,0);
      pDC->Draw3dRect(gripper,::GetSysColor(COLOR_3DHIGHLIGHT),
      ::GetSysColor(COLOR_3DSHADOW));
      rectWindow.left += 8;
   } 
   else 
   {
      // gripper在顶部
      gripper.bottom = gripper.top+3;
      pDC->Draw3dRect(gripper,::GetSysColor(COLOR_3DHIGHLIGHT),
      ::GetSysColor(COLOR_3DSHADOW));
      gripper.OffsetRect(0,+4);
      pDC->Draw3dRect(gripper,::GetSysColor(COLOR_3DHIGHLIGHT),
      ::GetSysColor(COLOR_3DSHADOW));
      rectWindow.top += 8;
   }
}
 // 擦除非用户区(边框) - 从MFC中复制来实现
void CFlatToolBar::EraseNonClient() 
{
   // 获取剪切非用户区域的窗口 DC
   CWindowDC dc(this);
   CRect rectClient;
   GetClientRect(rectClient);
   CRect rectWindow;
   GetWindowRect(rectWindow);
   ScreenToClient(rectWindow);
   rectClient.OffsetRect(-rectWindow.left, -rectWindow.top);
   dc.ExcludeClipRect(rectClient);

   // 绘制非用户区的边界
   rectWindow.OffsetRect(-rectWindow.left, -rectWindow.top);
   DrawBorders(&dc, rectWindow);

   // 擦除非绘制部分
   dc.IntersectClipRect(rectWindow);
   SendMessage(WM_ERASEBKGND, (WPARAM)dc.m_hDC);
   DrawGripper(&dc, rectWindow); // 增加的绘制gripper
}
 // 因为按钮是透明的,所以当样式改变时我们需要重绘背景
void CFlatToolBar::OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler) 
{
   static CUIntArray styles;
   // 保存样式
   int nIndexMax = (int)DefWindowProc(TB_BUTTONCOUNT, 0, 0);
   int nIndex;
   for (nIndex = 0; nIndex < nIndexMax; nIndex++) 
   {
      UINT dwStyle = GetButtonStyle(nIndex);
      styles.SetAtGrow(nIndex,dwStyle);
   }
   // 缺省处理
   CToolBar::OnUpdateCmdUI(pTarget,bDisableIfNoHndler);
   // make checked button appear pushed in
   for (nIndex = 0; nIndex < nIndexMax; nIndex++) 
   {
      UINT dwStyle = GetButtonStyle(nIndex);
      if (dwStyle & TBBS_CHECKBOX) 
      {
         if (dwStyle & TBBS_CHECKED) 
            dwStyle |= TBBS_PRESSED;
         else 
            dwStyle &= ~TBBS_PRESSED;
         SetButtonStyle(nIndex,dwStyle);
      }
   }
    // 检查样式是否改变(按钮按下或释放)
   for (nIndex = 0; nIndex < nIndexMax; nIndex++) 
   {
      UINT dwStyle = GetButtonStyle(nIndex);
      if (styles[nIndex] != dwStyle) 
      {
         RepaintBackground();    // 需要处理按钮背景
         Invalidate();    // 重绘工具栏(不仅仅是该按钮)
         break;
      }
   }
}
 // 因为按钮是透明的, 所以我们需要在尺寸变化或移动时重新绘制背景
void CFlatToolBar::OnWindowPosChanging(LPWINDOWPOS lpwp) 
{
   // 缺省处理
   CToolBar::OnWindowPosChanging(lpwp);

   // 当尺寸变化或移动时重绘背景
   RepaintBackground();
   PostMessage(WM_NCPAINT);
}
 // 绘制工具栏
void CFlatToolBar:: OnPaint() 
{
   // 标准工具栏
   CToolBar::OnPaint();

   // 添加分隔线
   DrawSeparators();
}
 // 擦除非用户区(边框) - 从MFC中复制来实现
void CFlatToolBar:: OnNcPaint() 
{
   EraseNonClient();
}
 // 计算非用户区域 - 用于调整grippers
void CFlatToolBar::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp) 
{
   CToolBar::OnNcCalcSize(bCalcValidRects,lpncsp);

   // 为左边或顶部的gripper调整非用户区域
   if (m_dwStyle & CBRS_FLOATING) 
   {
      // 无gripper
   }
   else if (m_dwStyle & CBRS_ORIENT_HORZ) 
   {
      lpncsp->rgrc[0].left += 2;
      lpncsp->rgrc[0].right += 2;
   } 
   else 
   {
      lpncsp->rgrc[0].top += 4;
      lpncsp->rgrc[0].bottom += 4;
   }
}