2008年(884)
分类: C/C++
2008-08-06 09:52:49
LRESULT CPlApplet(HWND hwnd, UINT msg, LPARAM lp1, LPARAM lp2);
为了使本文的例子代码尽可能的具有可重用性,我用C 对控制面板的接口函数进行了封装。
做了一个迷你型的控制面板应用程序开发框架,利用它开发控制面板扩展程序易如反掌。
控制面板程序除了是个特别的DLL外,还有一个特点是其扩展名必须为 *.cpl,而不是*.dll。当Windows的控制面板管理程序
(CONTROL.EXE)启动后,它会在系统目录(如:windows\system或者winnt\system32)中寻找名为XXX.cpl的文件,然后加载每一个DLL并以不同的消息
参数调用CPlApplet函数。例如,当控制面板第一次启动时,它用消息msg=CPL_INIT调用
CPlApplet函数,当用户双击控制面板中的应用程序图标时,它用消息msg=CPL_DBLCLK调用CPlApplet函数,然后控制面板应用程序显示相应的对话框,每个控制面板DLL都能支持一个以
上的图标或应用。通过对消息CPL_GETCOUNT的响应,可以让控制面板知道DLL中有多少个应用,通过发送CPL_INQUIRE 或 CPL_NEWINQUIRE消息,控制面板可以请求
与每一个应用有关的信息。
图一是用一个跟踪程序(TraceWin)显示的TRACE Dump,从中可以看出控制面板对消息的处理情况。
图一 使用TraceWin 显示的 TRACE Dump
由于大多数控制面板和DLL之间的交互都有固定的套路,所以可以被封装在一个框架里。本文提供了两个类,CControlPanelApp 和 CCPApplet,实现了上述的封装。为了说明这两个类的使用方法,
本文还编写了示范的控制面板程序应用DLL:MyCtrlPanel.dll,它实现了两个控制面板应用,图二是本文例子程序运行后在控制面板里创建的两个图标
,这两个图标一个是对话框形式(如图三)、一个是属性页
形式(如图四)。
图二 例子程序图标
图三
图四
例子程序的实现代码很象典型的MFC文档/视图应用,所不同的是它的APP类派生于CControlPanelApp,而不是CWinApp,
并且不用改写InitInstance来添加文档模板,它用一个名为OnInit函数创建控制面板应用,OnInit创建了两个面板程序:
BOOL CMyControlPanelApp::OnInit() { AddApplet(new CCPApplet(IDR_MYAPPLET1, RUNTIME_CLASS(CMyDialog))); AddApplet(new CCPApplet(IDR_MYAPPLET2, RUNTIME_CLASS(CMyPropSheet))); return CControlPanelApp::OnInit(); }
CCPApplet是个很通用的类
,在例子程序中使用它时都不必再派生新类,其运行机制也很透明。真正需要自己编写代码的部分是对话框本身。MyCtrlPanel实现一个对话框CMyDialog和一个属性页CMyPropSheet。不管你相不相信,就这么简单,
创建一个对话框,并象上述那样重载CControlPanelApp::OnInit,剩下的事情都交给迷你框架来做。
到这里我们只完成了一部分工作,下面我们要描述由框架负责的那部分工作,比如:在哪里获取图标以及描述性信息、CPlApplet函数
的实现在哪里?CPL消息的处理例程等等。所有这些工作都由CControlPanelApp
和 CCPApplet来完成。CPanel.cpp中有一个CPlApplet函数负责将CPL消息转换成虚拟函数调用。当控制面板以消息CPL_INIT
调用 CPlApplet时,CPlApplet再调用CControlPanelApp::OnCplMsg,然后依次将控制传到CControlPanelApp::OnInit。OnCplMsg是CWnd::WindowProc的模拟,OnInit
类似于消息处理函数,如OnCreate。有些CPL消息如CPL_INQUIRE、CPL_DBLCLK等都有面板程序号(索引),用lParam1进行传递,这些消息被传到索引指示的程序。(记住:单个控制面板扩展
可以实现一个以上的图标或应用)。此时CControlPanelApp::OnCplMsg将消息处理路由到CCPApplet类的某个虚拟函数,而非CControlPanelApp。
以上我们介绍了一大堆的类代码运行逻辑,将底层的DLL调用和消息代码映射到较高级C 雷和虚拟函数。可光有逻辑是不行的,要实现这个逻辑才有价值。CControlPanelApp 和 CCPApplet
便是最终的结果。它们根据给定的静态信息实现了需要的处理。当你创建一个新的控制面板应用时,只要给构造函数一个资源ID和一个MFC运行时类:
AddApplet(new CCPApplet(IDR_MYAPPLET2, RUNTIME_CLASS(CMyPropSheet)));这就是框架实现控制面板应用时需要的全部信息。AddApplet将应用添加到m_lsApplets列表。默认的CPL_GETCOUNT消息处理函数可以返回列表中 应用的个数。当控制面板发送CPL_INQUIRE 或 CPL_NEWINQUIRE消息时,CCPApplet使用资源ID来获得应用的图标、名字和描述。名字和描述被解析为主资源串中的子串。
STRINGTABLE PRELOAD DISCARDABLE? BEGIN IDR_MYAPPLET3 "Intergalactic\n Intergalactic settings for space cadets\n\n" END这类似于MFC使用IDR_MAINFRAME处理串资源情况,如应用程序名、文当类型、COM ProgID等。只要按规范定义图标和资源串,就不必再实现OnInqure 或者 OnNewInqure,调用默认的实现即可。另外,这里要对CPL_INQUIRE 和 CPL_NEWINQUIRE消息的处理要做一点说明,CPL_NEWINQUIRE是新增的消息。一般说来,一个应用只要实现OnInqure就可以了,但如果 面板应用程序的信息从一个SESSION到另一个SESSION的过程中是可变的(似乎有点不可思议),那么就只需实现OnNewInquire,如果是这样,应将CCPApplet::m_bDynamic赋值为TRUE; 以便告诉框架旁路掉对CPL_INQUIRE消息的处理,也就是让它返回FALIED,从而让控制面板程序去处理CPL_NEWINQUIRE消息。 是不是有点神奇啊!就是为什么你能忽略所有的那些细节,仅仅使用资源串就能搞掂的缘故。 当用户双击控制面板中的应用图标时,Windows发送CPL_DBLCK消息。 这个消息被映射到CCPApplet::OnLaunch,此函数用对话框或者属性页的运行时类来创建一个实例,并调用DoModal:
LRESULT CCPApplet::OnLaunch(CWnd* pWndCpl, LPCSTR lpCmdLine) { CWnd* pw = (CWnd*)m_pDialogClass->CreateObject(); if (pw) { if (pw->IsKindOf(RUNTIME_CLASS(CPropertySheet))) { CPropertySheet* ps = (CPropertySheet*)pw; ps->SetActivePage(lpCmdLine ? atoi(lpCmdLine) : 0); ps->DoModal(); } else { if (pw->IsKindOf(RUNTIME_CLASS(CDialog))) { CDialog* pd = (CDialog*)pw; pd->DoModal(); } } return pw==NULL; } }一定要用DECLARE_DYNCREATE来声明对话框类和属性页类。如果不这样做Create会调用失败,而且可能还会有MFC的TRACE诊断错。除此之外,还要记住重 写对话框和属性页的OnPostNcDestroy函数,加入代码“delete this”。这是因为,通常创建对话框是在栈(stack)上进行的,代码如下:
CMyDialog dlg; dlg.DoModal();这种情况不用关心delete。而CPApplet是在堆(heap)上创建的对话框和属性页,在对话框和属性页被destroy掉以后,必须要进行delete操作,否则造成内存溢出。
CPlApplet 消息 | 框架 class::function | 是否需要改写? |
CPL_INIT |
CControlPanelApp::OnInit |
是,每个控制面板程序都调用 AddApplet 添加 |
CPL_GETCOUNT |
没有 |
否,CControlPanelApp 决定控制面板程序数量 |
CPL_INQUIRE | CCPApplet:: OnInquire | 很少用 |
CPL_NEWINQUIRE | CCPApplet::OnNewInquire | 很少用 |
CPL_DBLCLK | CCPApplet::OnLaunch | 很少用,仅用于没有对话框和属性页界面的情况 |
CPL_SELECT (已废掉) | CCPApplet::OnSelect | 否 |
CPL_STOP | CCPApplet::OnStop | 很少用,除非控制面板程序都进行垃圾收集,但这种情况最好在程序的析构函数中进行 |
CPL_EXIT | CControlPanelApp::OnExit | 很少用,用 ExitInstance 代替 |
CPL_STARTWPARAMS (Windows 98 或者Windows NT 4.0) |
CCPApplet::OnLaunch | 很少用,仅用于没有对话框和属性页界面的情况 |