工具条研究手记(5)- 自定义工具条的实现

来源:互联网 发布:js。push字符串 编辑:程序博客网 时间:2024/06/05 18:07

/*****************************************************************/
/*          工具条研究手记(5)- 自定义工具条的实现              */
/*****************************************************************/


一、简介

Windows 的工具条内嵌了用户自定义功能,允许用户通过拖拽改变工具条上面按钮的位置,或者添加/删除工具条按钮。只要工具条添加CCS_ADJUSTABLE属性,就允许用户自定义工具条。

通常可以通过双击工具条弹出“自定义工具栏”对话框,使用这个对话框,可以轻松地添加/删除/重新排列工具条按钮。程序还可以通过发送TB_CUSTOMIZE消息或者调用MFC工具条类的Customize成员函数来显示这个对话框。
还有一种方式,不用弹出“自定义工具栏”对话框,也可以动态改变工具条上面的按钮。就是按下SHIFT键,然后直接用鼠标拖拽工具条上面的
按钮。可以改变按钮的位置,或者删除某个按钮,但是无法添加按钮。

在用户设置工具条的过程中,工具条会向父窗口发送一系列通知消息。某些操作会根据父窗口消息处理函数的返回值决定是否继续进行该项操作。用户可以通过鼠标拖拽来动态设置工具条按钮。拖拽期间,工具条会捕捉鼠标的输入,直到用户释放鼠标,然后工具条控件通过鼠标的位置判断应该执行何种操作。例如,如果鼠标位置位于工具条之外,则发送TBN_QUERYDELETE消息,由父窗口决定是否可以删除该按钮,如果父窗口返回FALSE,则结束拖拽操作,如果返回TRUE,则删除拖拽的按钮。如果鼠标位置位于另一个按钮上,工具条会发送TBN_QUERYINSERT消息给父窗口,请求在目标按钮左侧插入该按钮,如果父窗口返回TRUE,则插入该按钮,如果返回FALSE,则取消该操作。每个拖拽完毕,工具条还发送TBN_TOOLBARCHANGE消息,通常父窗口响应这个消息,调用GetParentFrame()->RecalcLayout();重新布局工具条。

如果用户开始拖拽的时候,没有按下SHIFT键,则工具条向父窗口发送TBN_BEGINDRAG消息,应用程序可以在这个消息的响应函数中启动自己的拖拽操作。脱拽完毕,工具条会发送TBN_ENDDRAG消息。通常这两个消息没有很大的价值,不用父窗口响应。

启动自定义工具栏对话框之前,工具条会先发送TBN_BEGINADJUST消息给父窗口,然后生成该对话框。接下来工具条发送一系列TBN_QUERYINSERT通知消息来判断每个按钮是否允许插入。返回TRUE表示允许插入该按钮。如果父窗口对每个按钮都没有返回TRUE,则工具条销毁该对话框。

接下来,工具条控件针对每个按钮发送TBN_QUERYDELETE来判断该按钮是否可以删除,父窗口返回TRUE表示可以删除该按钮,否则返回FALSE。不能删除的按钮在“自定义工具栏”对话框中是灰色的,无法进行拖拽。

还有一个消息很重要,就是TBN_GETBUTTONINFO消息。工具条控件通过这个消息,传递一个按钮的序号,以及一个TBNOTIFY结构的地址,父窗口必须响应这个消息,然后用该按钮相应的信息填充这个结构,否则无法设置工具条。

“自定义工具栏”对话框还包含一个帮助按钮和一个Reset按钮,如果用户点击帮助按钮,则工具条控件发送TBN_CUSTHELP消息,父窗口应该响应该消息,并显示帮助信息。如果用户点击Reset按钮,则工具条控件会发送TBN_RESET消息,表示工具条要重新初始化该对话框。通常这两个消息也不用响应。

所有这些消息都是通过 WM_NOTIFY消息传递。可以在父窗口的消息循环中添加如下的消息映射入口来处理该类消息:

 ON_NOTIFY( wNotifyCode, idControl, memberFxn )

 wNotifyCode 是通知消息代码,例如TBN_BEGINADJUST.
 idControl 是发送通知消息的控件的ID,对于工具条一般是AFX_IDW_TOOLBAR 
 memberFxn 是消息响应函数,它的原型应该是如下形式:

 afx_msg void memberFxn( NMHDR * pNotifyStruct, LRESULT * result );

 返回值应该通过 result返回。

对于每个通知消息,pNotifyStruct指向一个 NMHDR结构或者 TBNOTIFY结构:

typedef struct tagNMHDR {
HWND hwndFrom;  // 发送消息的控件的句柄
UINT idFrom; // 发送消息的控件的ID
UINT code;   // 通知消息的代码
} NMHDR;

typedef struct {
NMHDR hdr;   // 所有WM_NOTIFY消息的通用消息头
int iItem;   // 消息相关的按钮的序号
TBBUTTON tbButton;  // 消息相关的按钮的信息
int cchText;     // 按钮文字的字符数
LPSTR lpszText;  // 按钮文字的地址
} TBNOTIFY, FAR* LPTBNOTIFY;

工具条状态改变以后,可以保存在注册表里面,下次程序运行的时候,重新根据该状态生成工具条,从而再现上次程序运行时刻的工具条。
保存/读取工具条的状态可以通过CToolBarCtrl类的成员函数:SaveState/RestoreState。如果使用了这两个函数,则框架类函数SaveBarState/LoadBarState就没有必要用了。

调用SaveState/RestoreState函数的时候,可以用自己设定的注册表入口。因此可以在工具条派生类中重载Create函数,给出自己的注册表入口,然后保存在派生类变量中。然后使用自己的注册表入口保存工具条的状态。

二、消息总结

下面总结一下对每个通知消息的处理情况,以及必要的准备:

1、必须要响应的消息

(1)、TBN_GETBUTTONINFO
   在显示“自定义对话栏”对话框之前,工具条发送这个消息,获取每个工具条按钮的信息。父窗口应该维持一组信息,然后根据这个消息传递过来的按钮的序号,用该按钮的信息填充消息中的结构。一般信息应该如下表示:
   class CToolBarInfo
   {
   public:
 TBBUTTON  tbButton; // 按钮相关信息
 LPCTSTR   btnText; // 按钮的文字
   };
在框架窗口类cpp文件中定义一组static变量:

// MainFrm.cpp
......
static CToolBarInfo  mainToolBar[] =
{
 {{0, ID_FILE_NEW  , TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0}, "New"     },
 {{1, ID_FILE_OPEN , TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 1}, "Open"    },
 {{2, ID_FILE_SAVE , TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 2}, "Save"    },
 {{0, ID_SEPARATOR , TBSTATE_ENABLED, TBSTYLE_SEP   , 0, 3}, ""        },
 {{3, ID_EDIT_CUT  , TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 4}, "Cut"     },
 {{4, ID_EDIT_COPY , TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 5}, "Copy"    },
 {{5, ID_EDIT_PASTE, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 6}, "Paste"   },
 {{0, ID_SEPARATOR , TBSTATE_ENABLED, TBSTYLE_SEP   , 0, 7}, ""        },
 {{6, ID_FILE_PRINT, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 8}, "Print"   },
 {{7, ID_APP_ABOUT , TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 9}, "About..."}
};
  响应TBN_GETBUTTONINFO消息的时候,使用这个结构数组的数据。

(2)、TBN_ENDADJUST
  响应这个消息,然后保存工具条的状态
(3)、TBN_QUERYINSERT
  响应这个消息,允许按钮的插入。

2、可选响应消息

(1)、TBN_QUERYDELETE
   响应这个消息,决定是否可以删除某个按钮。如果没有响应这个消息,则当前工具条中的所有按钮都不可以删除(在对话框中显示是灰色的)。一般应响应这个消息,对个别按钮设置成不能删除。参考后面的例子代码。
(2)、TBN_RESET
   响应这个消息,从注册表恢复工具条的状态。如果不响应这个消息,则Reset按钮不起作用。
   !注意,如果用SetSizes函数或者SetButtonText函数设置了工具条按钮的大小/图像大小/文字。则重置以后,所有参数都变成缺省参数,文字变成‘New’,这种情况下就需要响应这个消息进行处理。
(3)、TBN_CUSTHELP
   响应这个消息,提供帮助信息。如果不响应,则Help按钮不起作用。
(4)、TBN_TOOLBARCHANGE
   响应这个消息,使父窗口重新进行工具条布局,重绘工具条。

3、一般不响应的消息

   TBN_BEGINADJUST
   TBN_BEGINDRAG
   TBN_CUSTHELP
   TBN_ENDDRAG

三、关于启动“自定义工具栏”对话框

  一般来说,允许自定义的工具条可以通过双击直接打开“自定义工具栏”对话框,但是如果工具条是浮动的,也就是说,框架类添加了如下的代码:
 m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
 EnableDocking(CBRS_ALIGN_ANY);
 DockControlBar(&m_wndToolBar);
则双击工具条会切换工具条的浮动状态,而无法弹出对话框。这个时候,必须在菜单栏提供“编辑工具条”的命令,主动调用m_wndToolBar.GetToolBarCtrl().Customize();函数启动编辑工具条对话框。或者工具条派生类提供鼠标右键函数,重载OnContextMenu 函数,弹出“编辑工具条”命令菜单,然后自己响应菜单命令,启动编辑。

四、一个封装好的可编辑工具条类

上述所有功能都可以封装在CToolBar派生类中,这样使用起来比较方便。下面给出一个工具条派生类的代码,实现工具条自定义以及状态的保存。注意派生类处理的消息都是反射消息。

//头文件
// EditToolBar.h : header file
//

/////////////////////////////////////////////////////////////////////////////
// CEditToolBar window
#ifndef __CEDITTOOLBAR__
#define __CEDITTOOLBAR__

class CToolBarInfo
{
public:
 TBBUTTON tbButton;
 LPCTSTR  btnText;
};

class CEditToolBar : public CToolBar
{
//构造函数
public:
 CEditToolBar();
 //重载 Create函数,允许添加注册表键
 BOOL  Create(CWnd *pParentWnd,
  DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_TOP,
  UINT nID = AFX_IDW_TOOLBAR,
  CToolBarInfo *tbInfo = NULL, //这个信息位于使用工具条的父窗口类中
  CString regSubKey = "",
  CString regValue = "",
  HKEY regKey = HKEY_CURRENT_USER); //缺省设置在USER键下

 //重载 LoadToolBar ,从而提供自动从注册表恢复工具条的状态
 inline BOOL LoadToolBar(UINT idResource, BOOL restore = FALSE)
  {
    BOOL success;
    success = CToolBar::LoadToolBar(idResource);
    nButtons = GetToolBarCtrl().GetButtonCount();//记录按钮数目

    if (restore) RestoreState();
    return success;
  }
//接口函数
public:
 void  SaveState();  //保存工具条状态
 void  RestoreState(); //恢复工具条状态
public:
 virtual ~CEditToolBar();

//数据
protected:
 CToolBarInfo *toolBarInfo; //按钮的信息表
 int  nButtons;  //按钮数目
 HKEY  registryKey;  //工具条信息所在的注册表键
 CString  registrySubKey; //注册表子键
 CString  registryValue;  //值

 //{{AFX_MSG(CEditToolBar)
 //}}AFX_MSG
 afx_msg void OnToolBarGetButtonInfo(NMHDR *notify, LRESULT *result);
 afx_msg void OnToolBarQueryInsert(NMHDR *notify, LRESULT *result);
 afx_msg void OnToolBarQueryDelete(NMHDR *notify, LRESULT *result);
 afx_msg void OnToolBarEndAdjust(NMHDR *notify, LRESULT *result);
 afx_msg void OnToolBarReset(NMHDR *notify, LRESULT *result);

 DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////
#endif

//cpp文件

#include "stdafx.h"
#include "EditToolBar.h"
#include "resource.h"
/////////////////////////////////////////////////////////////////////////////
// CEditToolBar消息,注意都是反射消息
BEGIN_MESSAGE_MAP(CEditToolBar, CToolBar)
 //{{AFX_MSG_MAP(CEditToolBar)
 //}}AFX_MSG_MAP
 ON_NOTIFY_REFLECT(TBN_GETBUTTONINFO, OnToolBarGetButtonInfo)
 ON_NOTIFY_REFLECT(TBN_QUERYDELETE, OnToolBarQueryDelete)
 ON_NOTIFY_REFLECT(TBN_QUERYINSERT, OnToolBarQueryInsert)
 ON_NOTIFY_REFLECT(TBN_ENDADJUST, OnToolBarEndAdjust)
 ON_NOTIFY_REFLECT(TBN_RESET, OnToolBarReset)
END_MESSAGE_MAP()

//接口函数
CEditToolBar::CEditToolBar()
{
}

CEditToolBar::~CEditToolBar()
{
}

BOOL CEditToolBar::Create(CWnd *pParentWnd, DWORD dwStyle, UINT nID,
   CToolBarInfo *tbInfo, CString regSubKey,
   CString regValue, HKEY regKey)
{
 BOOL  success = FALSE;
 if (CToolBar::Create(pParentWnd, dwStyle, nID))
 {
  success = TRUE;
  //设置可编辑属性
  ModifyStyle(0, CCS_ADJUSTABLE);
  toolBarInfo = tbInfo; //保存按钮信息指针
  if (!tbInfo)
  {
   nButtons = 0;
  }
 }
 // 保存注册表信息
 registryKey = regKey;
 registrySubKey = regSubKey;
 registryValue = regValue;

 return success;
}

//保存工具条状态
void CEditToolBar::SaveState()
{
 GetToolBarCtrl().SaveState(registryKey, registrySubKey, registryValue);
}

//恢复工具条状态
void CEditToolBar::RestoreState()
{
 GetToolBarCtrl().RestoreState(registryKey, registrySubKey, registryValue);
}

/////////////////////////////////////////////////////////////////////////////
//消息处理函数
void CEditToolBar::OnToolBarGetButtonInfo(NMHDR *notify, LRESULT *result)
{
 TBNOTIFY *tbStruct = (TBNOTIFY *)notify;

 if (0 <= tbStruct->iItem && tbStruct->iItem < nButtons)
 {
  //复制按钮信息
  tbStruct->tbButton = toolBarInfo[tbStruct->iItem].tbButton;
  //复制按钮文字
  strcpy(tbStruct->pszText, toolBarInfo[tbStruct->iItem].btnText);
  *result = TRUE;
 }
 else
 {
  *result = FALSE;
 }

}
void CEditToolBar::OnToolBarQueryDelete(NMHDR *notify, LRESULT *result)
{
 // 举例:不能删除“打印”按钮
 TBNOTIFY* pn = (TBNOTIFY*)notify;
 if(pn->tbButton.idCommand == ID_FILE_PRINT)
  *result = FALSE;
 else
  *result = TRUE;
}

void CEditToolBar::OnToolBarQueryInsert(NMHDR *notify, LRESULT *result)
{
 // 举例:不能在第二个按钮之前插入按钮
 TBNOTIFY* pn = (TBNOTIFY*)notify;
 if(pn->iItem == 1)  //序号从0开始
  *result = FALSE;
 else
  *result = TRUE;
}

void CEditToolBar::OnToolBarEndAdjust(NMHDR *notify, LRESULT *result)
{
 //保存工具条状态
 SaveState();
}


void CEditToolBar::OnToolBarReset(NMHDR *notify, LRESULT *result)
{
 //恢复工具条状态
 RestoreState();
}

/////////////////////////////////////////////////////////////////////////
// cpp文件结束

五、可编辑工具条类的具体使用方法

1、框架窗口类中,用派生类定义工具条

// MainFrm.h 
#include "EditToolBar.h" //包含工具条派生类的头文件

class CMainFrame : public CMDIFrameWnd
{
...
protected:  //内嵌控制条对象
 CStatusBar    m_wndStatusBar;
 CEditToolBar    m_wndToolBar;
...
};

2、主框架类cpp文件添加必要的工具条按钮信息,然后生成工具条

// MainFrm.cpp : implementation of the CMainFrame class
......
static CToolBarInfo  mainToolBar[] =
{
 {{0, ID_FILE_NEW  , TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0}, "New"     },
 {{1, ID_FILE_OPEN , TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 1}, "Open"    },
 {{2, ID_FILE_SAVE , TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 2}, "Save"    },
 {{0, ID_SEPARATOR , TBSTATE_ENABLED, TBSTYLE_SEP   , 0, 3}, ""        },
 {{3, ID_EDIT_CUT  , TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 4}, "Cut"     },
 {{4, ID_EDIT_COPY , TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 5}, "Copy"    },
 {{5, ID_EDIT_PASTE, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 6}, "Paste"   },
 {{0, ID_SEPARATOR , TBSTATE_ENABLED, TBSTYLE_SEP   , 0, 7}, ""        },
 {{6, ID_FILE_PRINT, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 8}, "Print"   },
 {{7, ID_APP_ABOUT , TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 9}, "About..."}
};

//状态条信息
static UINT indicators[] =
{
 ID_SEPARATOR,
 ID_INDICATOR_CAPS,
 ID_INDICATOR_NUM,
 ID_INDICATOR_SCRL,
};

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
 if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
  return -1;
 if (!m_wndToolBar.Create(this, WS_CHILD | WS_VISIBLE | CBRS_TOP,
  AFX_IDW_TOOLBAR, //工具条ID
  mainToolBar,  //工具条按钮信息
  "Software\\MySoft\\Settings\\Toolbar", //注册表入口
  "ToolBar Settings") ||  //注意加载的时候,从注册表读工具条状态
     !m_wndToolBar.LoadToolBar(IDR_MAINFRAME, TRUE))
 {
  TRACE0("Failed to create toolbar\n");
  return -1;
 }
 ...... //其它代码
}


//-------------------------------------------------------------------

原创粉丝点击