Part V高级对话框用户界面类.docx
《Part V高级对话框用户界面类.docx》由会员分享,可在线阅读,更多相关《Part V高级对话框用户界面类.docx(19页珍藏版)》请在冰豆网上搜索。
PartV高级对话框用户界面类
MFC程序员的WTL指南:
PartV-高级对话框用户界面类
原作:
MichaelDunn[英文原文]
翻译:
Orbit(桔皮干了)[]
下载演示程序代码
本章内容
∙第五章介绍
∙特别的自画和外观定制类
oCOwnerDraw
oCCustomDraw
∙WTL的新控件
oCBitmapButton
oCCheckListViewCtrl
oCTreeViewCtrlEx和CTreeItem
oCHyperLink
∙对话框中控件的UIUpdating
∙DDV
o处理DDV验证失败
∙改变对话框的大小
∙继续
∙参考
∙修改记录
第五章介绍
在上一篇文章我们介绍了一些与对话框和控件有关的WTL的特性,它们和MFC的相应的类作用相同。
本文将介绍一些新类实现高级界面特性新类:
控件自画和自定外观控件,新的WTL控件,UIupdating和对话框数据验证(DDV)。
特别的自画和外观定制类
由于自画和定制外观控件在图形用户界面中是很常用的手段,所以WTL提供了几个嵌入类来完成这些令人厌烦的工作。
我接着就会介绍它们,事实上我们在上一个例子工程ControlMania2的结尾部分已经这么做了。
如果你正随着我的讲解用应用程序生成向导创建新工程,请不要忘了使用无模式对话框,为了使正常工作必须使用无模式对话框,我会在对话框中控件的UIUpdating部分详细解释为什么这样作。
COwnerDraw
控件的自画需要响应四个消息:
WM_MEASUREITEM,WM_DRAWITEM,WM_COMPAREITEM,和WM_DELETEITEM,在atlframe.h头文件中定义的COwnerDraw类可以简化这些工作,使用这个类就不需要处理这四个消息,你只需将消息链入COwnerDraw,它会调用你的类中的重载函数。
如何将消息链入COwnerDraw取决与你是否将消息反射给控件,两种方法有些不同。
下面是COwnerDraw类的消息映射链,它使得两种方法的差别更加明显:
templateclassCOwnerDraw
{
public:
BEGIN_MSG_MAP(COwnerDraw)
MESSAGE_HANDLER(WM_DRAWITEM,OnDrawItem)
MESSAGE_HANDLER(WM_MEASUREITEM,OnMeasureItem)
MESSAGE_HANDLER(WM_COMPAREITEM,OnCompareItem)
MESSAGE_HANDLER(WM_DELETEITEM,OnDeleteItem)
ALT_MSG_MAP
(1)
MESSAGE_HANDLER(OCM_DRAWITEM,OnDrawItem)
MESSAGE_HANDLER(OCM_MEASUREITEM,OnMeasureItem)
MESSAGE_HANDLER(OCM_COMPAREITEM,OnCompareItem)
MESSAGE_HANDLER(OCM_DELETEITEM,OnDeleteItem)
END_MSG_MAP()
};
注意,消息映射链的主要部分处理WM_*消息,而ATL部分处理反射的消息,OCM_*。
自画的通知消息就像WM_NOTIFY消息一样,你可以在父窗口处理它们,也可以将它们反射会控件,如果你使用前一种方法,消息被直接链入COwnerDraw:
classCSomeDlg:
publicCOwnerDraw,...
{
BEGIN_MSG_MAP(CSomeDlg)
//...
CHAIN_MSG_MAP(COwnerDraw)
END_MSG_MAP()
voidDrawItem(LPDRAWITEMSTRUCTlpdis);
};
当然,如果你想要控件自己处理这些消息,你需要使用CHAIN_MSG_MAP_ALT宏将消息链入ALT_MSG_MAP
(1)部分:
classCSomeButtonImpl:
publicCOwnerDraw,...
{
BEGIN_MSG_MAP(CSomeButtonImpl)
//...
CHAIN_MSG_MAP_ALT(COwnerDraw,1)
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
voidDrawItem(LPDRAWITEMSTRUCTlpdis);
};
COwnerDraw类将对消息传递的参数展开,然后调用你的类中的实现函数。
上面的例子中,我们自己的类实现DrawItem()函数,当有WM_DRAWITEM或OCM_DRAWITEM消息被链入COwnerDraw时,这个函数就会被调用。
你可以重载的方法有:
voidDrawItem(LPDRAWITEMSTRUCTlpDrawItemStruct);
voidMeasureItem(LPMEASUREITEMSTRUCTlpMeasureItemStruct);
intCompareItem(LPCOMPAREITEMSTRUCTlpCompareItemStruct);
voidDeleteItem(LPDELETEITEMSTRUCTlpDeleteItemStruct);
如果你不想处理某个消息,你可以调用SetMsgHandled(false),消息会被传递给消息映射链中的其他响应者。
SetMsgHandled()事实上是COwnerDraw类的成员函数,但是它的作用和在BEGIN_MSG_MAP_EX()中使用SetMsgHandled()一样。
对于ControlMania2,它从ControlMania1中的树控件开始,添加了自画按钮处理反射的WM_DRAWITEM消息,下面是资源编辑器中的新按钮:
现在我们需要一个新类实现自画按钮:
classCODButtonImpl:
publicCWindowImpl,
publicCOwnerDraw
{
public:
BEGIN_MSG_MAP_EX(CODButtonImpl)
CHAIN_MSG_MAP_ALT(COwnerDraw,1)
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
voidDrawItem(LPDRAWITEMSTRUCTlpdis);
};
DrawItem()使用了像BitBlt()这样的GDI函数向按钮的表面画位图,代码应该很容易理解,因为WTL使用的类名和函数名都和MFC类似。
voidCODButtonImpl:
:
DrawItem(LPDRAWITEMSTRUCTlpdis)
{
//NOTE:
m_bmpisaCBitmapinit''edintheconstructor.
CDCHandledc=lpdis->hDC;
CDCdcMem;
dcMem.CreateCompatibleDC(dc);
dc.SaveDC();
dcMem.SaveDC();
//Drawthebutton''sbackground,redifithasthefocus,blueifnot.
if(lpdis->itemState&ODS_FOCUS)
dc.FillSolidRect(&lpdis->rcItem,RGB(255,0,0));
else
dc.FillSolidRect(&lpdis->rcItem,RGB(0,0,255));
//Drawthebitmapinthetop-left,oroffsetby1pixelifthebutton
//isclicked.
dcMem.SelectBitmap(m_bmp);
if(lpdis->itemState&ODS_SELECTED)
dc.BitBlt(1,1,80,80,dcMem,0,0,SRCCOPY);
else
dc.BitBlt(0,0,80,80,dcMem,0,0,SRCCOPY);
dcMem.RestoreDC(-1);
dc.RestoreDC(-1);
}
我们的按钮看起来是这个样子:
CCustomDraw
CCustomDraw类使用和COwnerDraw类相同的方法处理NM_CUSTOMDRAW消息,对于自定绘制的每个阶段都有相应的重载函数:
DWORDOnPrePaint(intidCtrl,LPNMCUSTOMDRAWlpNMCD);
DWORDOnPostPaint(intidCtrl,LPNMCUSTOMDRAWlpNMCD);
DWORDOnPreErase(intidCtrl,LPNMCUSTOMDRAWlpNMCD);
DWORDOnPostErase(intidCtrl,LPNMCUSTOMDRAWlpNMCD);
DWORDOnItemPrePaint(intidCtrl,LPNMCUSTOMDRAWlpNMCD);
DWORDOnItemPostPaint(intidCtrl,LPNMCUSTOMDRAWlpNMCD);
DWORDOnItemPreErase(intidCtrl,LPNMCUSTOMDRAWlpNMCD);
DWORDOnItemPostEraset(intidCtrl,LPNMCUSTOMDRAWlpNMCD);
DWORDOnSubItemPrePaint(intidCtrl,LPNMCUSTOMDRAWlpNMCD);
这些函数默认都是返回CDRF_DODEFAULT,如果想自画控件或返回一个不同的值,就需要重载这些函数:
你可能注意到上面的屏幕截图将“道恩”(Dawn:
女名)显示成绿色,这是因为CBuffyTreeCtrl将消息链入CCustomDraw并重载了OnPrePaint()和OnItemPrePaint()方法。
向树控件中添加节点时,节点的itemdata字段被设置成1,OnItemPrePaint()检查这个值,然后改变文字的颜色。
DWORDCBuffyTreeCtrl:
:
OnPrePaint(intidCtrl,
LPNMCUSTOMDRAWlpNMCD)
{
returnCDRF_NOTIFYITEMDRAW;
}
DWORDCBuffyTreeCtrl:
:
OnItemPrePaint(intidCtrl,
LPNMCUSTOMDRAWlpNMCD)
{
if(1==lpNMCD->lItemlParam)
pnmtv->clrText=RGB(0,128,0);
returnCDRF_DODEFAULT;
}
CCustomDraw类也有SetMsgHandled()函数,你可以像在COwnerDraw类那样使用这个函数。
WTL的新控件
WTL有几个新控件,它们要么是其他封装类的扩展(像CTreeViewCtrlEx),要么是提供windows标准控件没有的新功能(像CHyperLink)。
CBitmapButton
WTL的CBitmapButton类声明在atlctrlx.h中,它比MFC的同名类使用起来要简单的多。
WTL的CBitmapButton类使用imagelist而不是单个的位图资源,你可以将多个按钮的图像放到一个位图文件中,减少GDI资源的占用。
这对于使用很多图片并需要在Windows9X系统上运行的程序很有好处,因为使用太多的单个位图将会很快耗尽GDI资源并导致系统崩溃。
CBitmapButton是一个CWindowImpl派生类,它又很多特色:
自动调整控件的大小,自动生成3D边框,支持hot-tracking,每个按钮可以使用多个图像分别表示按钮的不同状态。
在ControlMania2中,我们对前面的例子创建的自画按钮使用CBitmapButton类。
现在CMainDlg对话框类中添加CBitmapButton类型的变量m_wndBmpBtn,调用SubclassWindow()函数或使用DDX将其和控件联系起来,将位图装载到imagelist并告诉按钮使用这个imagelist,还要告诉按钮每个图像分别对应按钮的什么状态。
下面是OnInitDialog()函数中建立和使用这个按钮的代码段:
//Setupthebitmapbutton
CImageListiml;
iml.CreateFromImage(IDB_ALYSON_IMGLIST,81,1,CLR_NONE,
IMAGE_BITMAP,LR_CREATEDIBSECTION);
m_wndBmpBtn.SubclassWindow(GetDlgItem(IDC_ALYSON_BMPBTN));
m_wndBmpBtn.SetToolTipText(_T("Alyson"));
m_wndBmpBtn.SetImageList(iml);
m_wndBmpBtn.SetImages(0,1,2,3);
默认情况下,按钮只是引用imagelist,所以OnInitDialog()不能delete它所创建的imagelist。
下面显示的是新按钮的一般状态,注意控件是如何根据图像的大小来调整自己的大小。
因为CBitmapButton是一个非常有用的类,我想介绍一下它的公有方法。
CBitmapButtonmethods
CBitmapButtonImpl类包含了实现一个按钮的所有代码,除非你想重载某个方法或消息处理,你可以对控件直接使用CBitmapButton类。
CBitmapButtonImplconstructor
CBitmapButtonImpl(DWORDdwExtendedStyle=BMPBTN_AUTOSIZE,HIMAGELISThImageList=NULL)
构造函数可以指定按钮的扩展样式(这与窗口的样式不冲突)和图像列表,通常使用默认参数就足够了,因为可以使用其他的方法设定这些属性。
SubclassWindow()
BOOLSubclassWindow(HWNDhWnd)
SubclassWindow()是个重载函数,主要完成控件的子类化和初始化控件类保有的内部数据。
Bitmapbuttonextendedstyles
DWORDGetBitmapButtonExtendedStyle()
DWORDSetBitmapButtonExtendedStyle(DWORDdwExtendedStyle,DWORDdwMask=0)
CBitmapButton支持一些扩展样式,这些扩展样式会对按钮的外观和操作方式产生影响:
BMPBTN_HOVER
使用hot-tracking,当鼠标移到按钮上时按钮被画成焦点状态。
BMPBTN_AUTO3D_SINGLE,BMPBTN_AUTO3D_DOUBLE
在按钮图像周围自动产生一个三维边框,当按钮拥有焦点时会显示一个表示焦点的虚线矩形框。
另外如果你没有指定按钮按下状态的图像,将会自动生成一个。
BMPBTN_AUTO3D_DOUBLE样式生成的边框稍微粗一些,其他特征和BMPBTN_AUTO3D_SINGLE一样。
BMPBTN_AUTOSIZE
按钮调整自己的大小以适应图像大小,这是默认样式。
BMPBTN_SHAREIMAGELISTS
如果指定这个样式,按钮不负责销毁按钮使用的imagelist,如果不使用这个样式,CBitmapButton的析构函数会销毁按钮使用的imagelist。
BMPBTN_AUTOFIRE
如果设置这个样式,在按钮上按住鼠标左键不放将会产生连续的WM_COMMAND消息。
调用SetBitmapButtonExtendedStyle()时,dwMask参数控制着那个样式将被改变,默认值是0,意味着用新样式完全替换旧的样式。
Imagelistmanagement
HIMAGELISTGetImageList()
HIMAGELISTSetImageList(HIMAGELISThImageList)
调用SetImageList()设置按钮使用的imagelist。
Tooltipmanagement
intGetToolTipTextLength()
boolGetToolTipText(LPTSTRlpstrText,intnLength)
boolSetToolTipText(LPCTSTRlpstrText)
CBitmapButton支持显示工具提示(tooltip),调用SetToolTipText()指定显示的文字。
Settingtheimagestouse
voidSetImages(intnNormal,intnPushed=-1,intnFocusOrHover=-1,intnDisabled=-1)
调用SetImages()函数告诉按钮分别使用imagelist的拿一个图像表示那个状态。
nNormal是必须的,其它是可选的,使用-1表示对应的状态没有图像。
CCheckListViewCtrl
CCheckListViewCtrl类在atlctrlx.h中定义,它是一个CWindowImpl派生类,实现了一个带检查框的listview控件。
它和MFC的CCheckListBox不同,CCheckListBox只是一个listbox,不是listview。
CCheckListViewCtrl类非常简单,只添加了很少的函数,当然,它使用了一个新的辅助类CCheckListViewCtrlImplTraits,它和CWinTraits类的作用类似,只是第三个参数是listview控件的扩展样式属性,如果你没有定义自己的CCheckListViewCtrlImplTraits,它将使用没默认的样式:
LVS_EX_CHECKBOXES|LVS_EX_FULLROWSELECT。
下面是一个定义listview扩展样式属性的例子,加入了一个使用这个样式的新类。
(注意,扩展属性必须包含LVS_EX_CHECKBOXES,否则会因起断言错误消息。
)
typedefCCheckListViewCtrlImplTraits<
WS_CHILD|WS_VISIBLE|LVS_REPORT,
WS_EX_CLIENTEDGE,
LVS_EX_CHECKBOXES|LVS_EX_GRIDLINES|LVS_EX_UNDERLINEHOT|
LVS_EX_ONECLICKACTIVATE>CMyCheckListTraits;
classCMyCheckListCtrl:
publicCCheckListViewCtrlImplCMyCheckListTraits>
{
private:
typedefCCheckListViewCtrlImplCMyCheckListTraits>baseClass;
public:
BEGIN_MSG_MAP(CMyCheckListCtrl)
CHAIN_MSG_MAP(baseClass)
END_MSG_MAP()
};
CCheckListViewCtrlmethodsSubclassWindow()
当子类化一个已经存在的listview控件时,SubclassWindow()查看CCheckListViewCtrlImplTraits的扩展样式属性并将之应用到控件上。
未用到前两个参数(窗口样式和扩展窗口样式)。
SetCheckState()andGetCheckState()
这些方法实际上是在CListViewCtrl中,SetCheckState()使用行的索引和一个布尔类型参数,该布尔参数的值表示是否check这一行。
GetCheckState()以行索引未参数,返回改行的checked状态。
CheckSelectedItems()
这个方法使用item的索引作为参数,它翻转这个item的check状态,这个item必须是被选定的,同时还将其他所有被选择的item设置成相应状态(译者加:
多选状态下)。
你大概不会用到这个方法,因为CCheckListViewCtrl会在checkbox被单击或用户按下了空格键时设置相应的item的状态。
下面是ControlMania2中的CCheckListViewCtrl的样子:
CTreeViewCtrlExandCTreeItem
有两个类使得树控件的使用简化了很多:
CTreeItem类封装了HTREEITEM,一个CTreeItem对象含有一个HTREEITEM和一个指向包含这个HTREEITEM的树控件的指针,使你不必每次调用都引用树控件;CTreeViewCtrlEx和CTreeViewCtrl一样,只是它的方法操作CTreeItem而不是HTREEITEM。
例如,InsertItem()函数返回一个CTreeItem而不是HTREEITEM,你可以使用CTreeItem操作新添加的item。
下面是一个例子:
//UsingplainHTREEITEMs:
HTREEITEMhti,hti2;
hti=m_wndTree.InsertItem("foo",TVI_ROOT,TVI_LAST);
hti2=m_wndTree.InsertItem("bar",hti,TVI_LAST);
m_wndTree.SetItemData(hti2,100);
//UsingCTreeItems:
CTreeItemti,ti2;
ti=m_wndTreeEx.InsertItem("foo",TVI_ROOT,TVI_LAST);
ti2=ti.AddTail("bar",0);
ti2.SetData(100);
CTreeViewCtrl对HTREEITEM的每一个操作,CTreeItem都有与之