ATL/WTL 对话框和控件_wtl 停靠控件-程序员宅基地

技术标签: C++  listview  mfc  windows  class  command  通讯录管理  

  选择自 titilima 的 Blog

第四章 对话框和控件

对于Win32 GUI的程序设计来说,其实大部分的情况下我们都不需要自己进行窗口类的设计,而是可以使用Win32中与用户交互的标准方式——对话框(Dialog Box)。我们可以在VC IDE的资源设计器中设计对话框资源,并在其上放置各种控件资源——的确是非常方便。在本章里,李马将要向诸位介绍如何利用ATL来操作对话框,以及如何操作对话框上的各种控件。

题外话先

ATL,是的,正是由于我所讲的是“ATL的GUI程序设计”,所以我才可能将内容直接经由CWindowImpl过渡到CDialogImpl——而不是过渡到你先前所熟悉的CFrameWnd和Doc/View体系。况且,即使这之后我深入到了CDialogImpl之中,我也不会讲到你所熟悉的DDX/DDV机制。再三考虑之下,我还是决定把这些东西在CDialogImpl前一并当作题外话说出来,先。

再来回顾一下ATL的性质。它是一个被设计用来开发COM组件的Framework,所以对GUI部分的支持——套用一句2006年的流行语来说:那是相~~当~~(加重且延长声音地)少。于是,它没有“框架窗口”这个概念,更不会有Doc/View体系。其实我对MFC的这一设计特点感觉不错,毕竟它可以通过一个简单的CFrameWnd类来实现一个标准的SDI/MDI框架,而且其中带有工具栏、状态栏和一个用来容纳视图的标准的工作区域。我们可以通过控制框架窗口中的View及其相关的Doc类型来完成特定文档类型的读写与显示。——但是,很不幸,这一切都只属于伟大的MFC;在ATL中,我们什么都没有。

另外,在对话框的技术领域中,使用ATL的我们也不会享有数据交换与验证(DDX/DDV)的支持。这一所谓的缺憾我并不想多加评价,一是因为我并不了解MFC中DDX/DDV的内部机制,二是因为我直觉上认为这是影响MFC效率的罪魁之一。在MFC中,我们可以通过向导的支持轻易地为表单的输入域加入输入校验与限制,而且表现在源代码上的仅仅是几个宏而已——我自认天下没有免费的午餐,这几个简单的宏既然能为我们包办一切,那我们势必会相应地失去些东西,要不然忒便宜了也就。

题外话的最后不免落入俗套,我将会向诸位介绍解决以上缺憾的方法。——也许你猜到了,就是从WTL中寻找解决方案。WTL是对ATL的扩展,所以它的很多代码可以直接拿过来用(当然可能需要一些小小的修改)。而且,不知道WTL的设计者是不是为了拉拢MFC的开发人员,总之它里面添加了很多与MFC相似的元素,例如以上所说的框架窗口和DDX/DDV。

CDialogImpl

与ATL窗口类CWindowImpl相对应,ATL的对话框类名为CDialogImpl。它的定义如下:

template <class T, class TBase = CWindow>
class ATL_NO_VTABLE CDialogImpl : public CDialogImplBaseT< TBase >
{
    // ...
};

你可以从上面的代码看到,CDialogImpl与CWindowImpl类似,也经历了一系列的继承链。不过,它较之CWindowImpl的模板参数要简单得多——毕竟是标准对话框,有些东西是不用操心的。

CDialogImpl的使用方法大致如下:

class CYourDlg : public CDialogImpl< CYourDlg >
{
public:
    enum { IDD = IDD_YOUR_DLG };
public:
    BEGIN_MSG_MAP( CYourDlg )
        // 消息映射
    END_MSG_MAP()
public:
    // 消息响应函数
///
// 其余的部分...
};

和CWindowImpl不一样,CDialogImpl不需要使用DECLARE_WND_CLASS来定义窗口类。在原来DECLARE_WND_CLASS的位置,一个枚举代替了原来窗口类定义的部分。这里的枚举列表必须有一个被命名为IDD,并且它的值要被设置为相应的对话框资源ID。呃……写到这里,我仿佛已经感觉到了你的不快,但CDialogImpl的实现即是如此(以CDialogImpl<T>::DoModal为例):

// from CDialogImpl<T>::DoModal
return ::DialogBoxParam(_Module.GetResourceInstance(), MAKEINTRESOURCE(T::IDD),
    hWndParent, (DLGPROC)T::StartDialogProc, dwInitParam);

当然,如果你不喜欢这么做的话,也可以自己从CDialogImplBaseT派生出属于你的对话框类。

再回到CDialogImpl的话题上来。这个类主要有以下几个常用的成员函数:

成员函数 说明
DoModal 显示一个模态对话框
EndDialog 销毁一个模态对话框
Create 创建一个非模态对话框
DestroyWindow 销毁一个非模态对话框

这样看来是不是和MFC十分相似?事实上,如果你已经定义好了一个对话框类,那么它的使用和MFC的对话框类的确没什么两样:

CYourDlg dlg;
dlg.DoModal();

控件的使用

从与用户交互的角度来看,控件是对话框上必不可少的元素。在Win32 GUI程序设计中,对控件的操作大可归为两个方面:一是对控件进行操作,二是响应控件的事件。排除子类化的事件响应(后面我会专门介绍如何在ATL中进行控件的子类化),那么这两方面的具体实现就是:

  • 使用窗口操作的API函数或发送消息来操作控件。
  • 处理WM_COMMAND或WM_NOTIFY来响应控件的事件。

根据顺序,李马来为大家介绍一下如何对控件进行操作先。这通常可以经由CWindow及其派生类实现,以下代码示范了如何禁用一个控件:

CWindow ctrl = GetDlgItem( IDC_CONTROL );
ctrl.EnableWindow( FALSE );

如果你要操作的控件需要用到特定的特性(也就是通过发送消息来实现的特有行为),当然你可以通过使用CWindow::SendMessage来实现,不过我并不推荐你使用这种方法,因为SendMessage是不会对消息参数进行类型检查的。而且,考虑到代码的可复用性,你可以对CWindow进行派生以达到目的。例如,对于列表控件的封装可以是类似下面这个样子:

class CListBox : public CWindow
{
public:
    int AddString( LPCTSTR lpszString )
    {
        return ::SendMessage( m_hWnd, LB_ADDSTRING, 0, (LPARAM)lpszString );
    }
};

然后,这样进行调用:

CListBox list;
list.Attach( GetDlgItem( IDC_LIST ) );
list.AddString( _T("This is a test line") );

可能你会有所疑问:为什么CWindow的例子直接使用了“=”来进行赋值,而CListBox则要使用Attach来初始化。当然,其实这两者并没有实质上的区别,只不过是CWindow重载了operator=操作符,而CListBox没有这样做罢了(严格说来,派生自CWindow的CListBox当然继承了CWindow的operator=,但是它并不能用于CListBox对象,如果强行使用则会得到一个“error C2679: binary '=' : no operator defined which takes a right-hand operand of type 'struct HWND__ *' (or there is no acceptable conversion)”的错误)。如果你也希望CListBox支持operator=的初始化方式,可以这样来对CListBox进行封装:

class CListBox : public CWindow
{
public:
    CListBox& operator=( HWND hWnd )
    {
        m_hWnd = hWnd;
        return *this;
    }
public:
    int AddString( LPCTSTR lpszString )
    {
        return ::SendMessage( m_hWnd, LB_ADDSTRING, 0, (LPARAM)lpszString );
    }
};

下面来介绍对控件事件的处理。通常控件在某些事件发生时会以发送WM_COMMAND(普通控件)或WM_NOTIFY(公共控件)消息的方式通知其父窗口,然后我们在其父窗口的窗口过程中处理这些消息即可。WM_COMMAND和WM_NOTIFY的参数意义如下:

  WM_COMMAND WM_NOTIFY
wParam HIWORD(wParam)为通知消息代码,LOWORD(wParam)为控件ID 发生通知消息的控件ID,不过仍建议使用lParam参数中的ID
lParam 发生通知消息的控件句柄 一个指向NMHDR结构的指针,这个结构中包含了通知消息的各种信息

在ATL中,可以使用如下的宏来进行各种消息的分流(在此将Windows消息分流的宏也一并加上):

消息分流宏 说明
MESSAGE_HANDLER 用于将某个特定消息分流至一个消息处理函数。
MESSAGE_RANGE_HANDLER 用于将某个范围内的消息一并分流至同一个消息处理函数。
COMMAND_HANDLER 用于将来自特定ID、特定通知码的WM_COMMAND消息分流至一个消息处理函数。
COMMAND_ID_HANDLER 用于将来自特定ID的WM_COMMAND消息分流至一个消息处理函数。
COMMAND_CODE_HANDLER 用于将来自特定通知码的WM_COMMAND消息分流至一个消息处理函数。
COMMAND_RANGE_HANDLER 用于将来自某个ID范围内的WM_COMMAND消息分流至一个消息处理函数。
NOTIFY_HANDLER 用于将来自特定ID、特定通知码的WM_NOTIFY消息分流至一个消息处理函数。
NOTIFY_ID_HANDLER 用于将来自特定ID的WM_NOTIFY消息分流至一个消息处理函数。
NOTIFY_CODE_HANDLER 用于将来自特定通知码的WM_NOTIFY消息分流至一个消息处理函数。
NOTIFY_RANGE_HANDLER 用于将来自某个ID范围内的WM_NOTIFY消息分流至一个消息处理函数。

另外,处理Windows消息、WM_COMMAND消息、WM_NOTIFY消息的消息处理函数应该分别满足如下规格要求:

// atlwin.h
// Handler prototypes:
//  LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
//  LRESULT CommandHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
//  LRESULT NotifyHandler(int idCtrl, LPNMHDR pnmh, BOOL& bHandled);

李马牌通讯录管理系统

别误会,这并不是什么正儿八经的所谓“信息管理系统”,而只是我为本章写下的一个简单示例而已。这里面并不涉及数据的存储,而只是为演示本章的内容而实现了必要的流程而已。在此李马并不打算对这个程序的代码进行过多解说,仅仅点出几点需要特殊说明的。

  1. 由于程序中使用了公共控件ListView,所以在WinMain的开头需要对公共控件库进行初始化:
    // 初始化公共控件先
    INITCOMMONCONTROLSEX init;
    init.dwSize = sizeof( init );
    init.dwICC  = ICC_LISTVIEW_CLASSES;
    InitCommonControlsEx( &init );
    在此我有必要指出,对公共控件库的初始化应该尽量使用InitCommonControlsEx,即使InitCommonControls貌似更加方便一些。我曾经做过测试,一个使用了DateTime控件并由InitCommonControls初始化的应用程序在WinXP sp2 + VC 6.0编译完成后,在Win2K下是不能运行的。
  2. CMainDlg::OnRadioSex是为了演示COMMAND_RANGE_HANDLER而写的一个消息处理函数,其实针对这个示例并不用编写之——因为Windows系统会自动对Radio按钮进行检选状态的处理;但如若考虑到多组Radio按钮存在的情况,CMainDlg::OnRadioSex这样的处理函数便会凸显出它的用处。
  3. LListView::GetSelectionMark并不能用来准确判断ListView的选中项,尤其是在选中项被删除之后。

点这里下载本章配套代码

 
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/niying/article/details/1227631

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签