工具条研究手记(5)- 自定义工具条的实现
/*****************************************************************/
/* 工具条研究手记(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;
}
...... //其它代码
}
//-------------------------------------------------------------------
工具条研究手记(5)- 自定义工具条的实现相关推荐
- 工具条研究手记(2) - 认识CToolBar工具条类
工具条研究手记(2) - 认识CToolBar工具条类 作者:佚名 文章来源:VC知识库 点击数: <script src="http://www.91tech.net/A ...
- xml工具生成工具_研究当前的XML工具
xml工具生成工具 常用缩略语 API:应用程序编程接口 DITA:达尔文信息键入体系结构 DTD:文档类型定义 HTML:超文本标记语言 IDE:集成开发环境 URL:统一资源定位符 W3C:万维网 ...
- android进度条脚本,GEE引擎自定义进度条和自定义属性示例脚本..
GEE引擎自定义进度条和自定义属性示例脚本.. [@main] #SAY \\ 自定义装备进度条 \\ 自定义装备属性 \\ ;==================================== ...
- iOS源码学习-导航条加阴影,自定义导航条的背景图片
介绍: 给导航条的下方加上阴影,自定义导航条的背景图片. 适用环境:Xcode 4.3, iOS 5.0. 下载地址:github.com 更多源码,请访问开源苹果 运行截图:
- python如何设计工具类_Python基础之自定义工具类
class ListHelper: @staticmethod def find_all(target, func_condition): """ 查找列表中满足条件的所 ...
- java 让进度条动起来_自定义进度条动起来
效果图: image.png image.png xml 布局: ··· xmlns:app="http://schemas.android.com/apk/res-auto" x ...
- 记录《时间进度》(人生进度条)(包含自定义进度、小插件)的开发
前言 开发的本意是想看到可视化的时间,让我们心存敬畏,珍惜时间,毕竟时间一去不复返! 最近灵感突突然袭来,想着做一个时间精度条:今天度过了多少,这周度过了多少,这个月度过了多少,今年度过了多少(以及这 ...
- android 自定义下载器,表盘自定义工具下载-表盘自定义工具 安卓版v4.1.0-pc6手机下载...
表盘自定义工具是一款让你自己就能够轻松的实现对小米手环4的自定义软件,表盘自定义工具没有特别复杂的操作只需要你按照教程来一步步进行操作,即可像Apple Watch一样自定义的控制你小米手环的表盘.欢 ...
- html自定义工具条,为Autodesk Viewer添加自定义工具条的更好方法
上一篇文章中我介绍了使用Autodesk Viewer的UI API来给viewer添加自定义工具条的方法,看起来很简单是吧.不过有个问题,就是关于自定义工具条的信息(包括按钮的文本.图标.样式.ca ...
最新文章
- rancher2.0安装mysql_高可用rancher集群搭建
- 服务器修改kb,Microsoft KB2344941:操作系统即插即用方式的改变,提高iSCSI Initiator的适应力...
- Open WebRTC Toolkit实时视频分析系统
- 解决vue中路由跳转同一个路径报错
- 【转】TCP/IP协议--TCP的超时和重传
- 韦小宝适合做IT人的8大理由
- e2 android,魅蓝E2做工怎么样?魅蓝手机E2拆机全过程图解
- springmvc5中设计模式
- python 正则表达式 分组_正则表达式之分组的用法
- 各种手机处理器排行榜_11月新机性能排行榜:荣耀V30第四,vivo S5上榜
- matlab 提取图像轮廓(图像边缘提取)
- Apache配置站点与虚拟目录
- Fiddler 手机抓包详解
- 迎新:Apache IoTDB 喜迎 2 位新 Committer
- 图片加载失败替代文字_替代艺术:为图像编写出色的描述性文字
- Spring JPA 随手记
- Halting Problem的讨论
- Java等额本息实现
- writeup 攻防世界 Decrypt-the-Message
- CADD课程学习(10)-- 模拟不同体系与蛋白-蛋白相互作用(ZDOCK)
热门文章
- cmd下简单的目录操作
- 开发汉澳即时通信网,2006年上线,QQ死期到了
- 自媒体怎么入门?过来人的三点建议,快看看
- 帮助做自媒体的0基础学员,如何去水印?
- 经济学人:智能机器人需要相应规则管理
- matlab adpcm编码,求助 求G.721 ADPCM语音编码
- mate30安卓还是鸿蒙,华为高管表示,mate30继续采用安卓,不太可能有鸿蒙手机!...
- html 圆圈里一个c,是否可以不使用.arc()或任何其他HTML标记制作一个圆圈?
- tab栏圆点按钮_zzx-tabs tab选项卡可动态设置小圆点
- 淮南市外观设计专利申请条件,简要说明要写哪些内容?