用ATL编写MMC Snapin的Sample

    技术2022-05-11  61

     

    MMC相关的编程接口MMC 实现了下面的接口:IColumnData (new in MMC 1.2) IConsole2 IConsoleNameSpace2 IConsoleVerb IContextMenuCallback IContextMenuProvider IControlbar IDisplayHelp IHeaderCtrl2 (new in MMC 1.2) IImageList IMenuButton IPropertySheetCallback IPropertySheetProvider IRequiredExtensions IResultData IToolbar Snap-ins 实现了下面的接口:IComponent IComponentData IEnumTASK IExtendContextMenu IExtendControlbar IExtendPropertySheet2 IExtendTaskPad IRequiredExtensions IResultDataCompare IResultDataCompareEx (new in MMC 1.2) IResultOwnerData ISnapinAbout ISnapinHelp2IComponent接口表示了右边的结果栏(Result pane)里的对象,MMC需要和结果栏里的对象通信时使用这个接口;IComponentData接口表示了左边的目录栏(scopet pane)里的对象(这些对象可以称之为节点),MMC需要和目录栏里的对象通信时使用这个接口;IExtendContextMenu接口,无论用户用鼠标右键点击了左边的节点(scope pane item)还是右边的项(result pane item),MMC都会弹出一个默认的关联菜单,这时MMC会使用这个接口询问该对象(Item),是否需要添加自己的菜单项。IExtendControlbar和IExtendPropertySheet2也是类似的道理。ISnapinAbout实现了一个关于对话框的接口,MMC通过这个接口了解Snapin的版本、描述信息、提供者信息、对话框图标,并且特别重要的是,MMC通过这个接口得到了Snapin的所有结点、项的默认图标,要修改根结点的图标也可以通过这个函数GetStaticFolderImage来向MMC提供。详尽的描述请参考Platform SDK。Snapin不需要实现全部接口,Sample仅实现了其中的:IComponent IComponentData IExtendContextMenu IExtendControlbar IExtendPropertySheet2 ISnapinAbout MMC的ATL实现支持类简介Sample的模型图ATL实现了几乎全部的接口,提供了默认的处理代码。ATL Wizzard生成的代码提供了基本的编程框架,Snapin的特性实现依赖于对CmySnapinData的某些函数的重载来修改默认行为。上图中,CMySnapinComponent的基类IExtendContextMenuImpl,IExtendContextPropertySheet,也同CMySnapin的一样,依赖于对CMySnapinData的几个同名函数的调用,只是因为线条太乱才没有画出。Snapin DLL Sample的代码生成Step by Step1,    新建ATL工程,如果需要使用MFC,可以使用Wizzard选项支持MFC工程命名test6;2,    “New ATL Object”,选择“MMC Snapin”;3,    命名“MySnapin”,并在第二个属性页“MMC Snapin”上,选择IextendContextMenu,IextendControlbar,IextendPropertySheet,IsnapinAbout;并且去掉对持久性的支持;4,    确定后,ATL Wizzard为我们生成了CMySnapin, CCMySnapinComponent, CMySnapinData,CMySnapinAbout, CMySnapinPage;5,    编译;然后运行mmc.exe,添加一个管理单元“MySnapin”,另存为MySnapin.msc;代码简介CMySnapin实现了IComponentData,IExtendContextMenu,,IExtendPropertySheet三个接口。CMySnapinComponent实现了IComponent,IExtendContextMenu,IExtendControlbar,IExtendPropertySheet 四个接口,CMySnapinAbout则实现了ISnapinAbout接口。DLL向外部暴露了CMySnapin和CMySnapinAbout两个对象。这样MMC就可以找到ISnapinAbout和IComponentData,IExtendContextMenu,,IExtendPropertySheet三个接口,然后MMC通过IComponentData的CreateComponent方法创建了CMySnapinComponent对象,从而又提供了CMySnapinComponent的IComponent,IExtendContextMenu,IExtendControlbar,IExtendPropertySheet 四个接口。从图中可以看到,ATL的接口实现类实现了部分方法的默认行为,然后把几个重要方法如Notify等传递到CMySnapinData对象的同名函数,让其继续处理。CMySnapinData从CSnapinItemImpl继承,CSnapinItemImpl实现了CSnapinItem接口,该接口定义了Item对象(左边的节点(scope pane item)还有右边的项(result pane item))的所有行为。CMySnapin在构造函数里创建了CMySnapinData作为根结点。一个Item的所有行为都可以通过重载某些方法来修改默认行为。比如在根结点下面添加几个子节点,可以在重载的Notify方法中处理MMCN_EXPAND消息等等。MSDN中的MMC Snap-In WizardMMC Snap-In Wizard讲了MMC Snap-In FAQ:1.    How Do I Add a Toolbar Resource to the Snap-In Object?2.    How Do I Add Menu Items, Control Bar Buttons, and a Property Sheet to the Snap-In Object?3.    How Do I Enumerate the Child Items of the Snap-In Object?4.    How Do I Add Custom Item Types to the Snap-In Object? 以上内容请自行参考msdn,按照以上的方法,可以做出一个完整功能的Snapin了。但是,非常遗憾的是,ATL Wizzard提供的无论是菜单、工具栏还是属性页都是所有的Item公用的,无论是Scope Item还是Result Item。特别是在有多个不同种类的节点的时候,这样几乎是不合适宜的。按需定制的Item实现QA如何生成子节点(Scope Item)?    在按照以上步骤生成代码框架以后,运行MySnapin.msc可以看到只有一个根结点,名字是MySnapin。修改这个根结点的名字,只需要修改MySnapin.cpp文件中const OLECHAR* CMySnapinData::m_SZDISPLAY_NAME = OLESTR("MySnapin");这一行即可。    前面讲过ATL把Item抽象成CSnapinItem接口,CSnapinItemImpl类实现了这个接口的大部分默认行为,因此,所有的Item都应该从这个类继承。考虑到CMySnapinData作为我们的根结点,ATL已经为我们生成了一些代码,从CMySnapinData继承是更好的选择。(能够重用的代码其实只有CMySnapinData构造函数中的初始化代码,不过从CMySnapinData继承的最大好处是可以将公共的实现放在CMySnapinData类中,而不需要每个Item类都实现一份相同的代码。),本文将从CMySnapinData类继承,也由于CMySnapinData将作为基类实现,因此,我们需要另外做一个根结点。    接下来我们将生成三个一级子节点,分别为类似“服务”、“事件查看器”、“帐户管理”,然后在“事件查看器”和“帐户管理”中再生成二级子节点,基本上包含了定制菜单、属性页、工具栏等。    分别从CMySnapinData继承,生成CItemRoot, CServicesContainer, CEventsContainer, CAccountsContainer。注意,在MySnapin.h文件中加入一行 #include "test6.h"。    在CMySnapin的构造函数里,我们把根结点改为CItemRoot。    在CItemRoot类中,重载Notify方法,增加代码处理MMCN_EXPAND,这个消息给了我们添加子节点(Scope Item)的机会。    case MMCN_EXPAND:        {            CComQIPtr<IConsoleNameSpace, &IID_IConsoleNameSpace> spConsoleNameSpace(spConsole);                        m_pServicesContainer->m_scopeDataItem.mask |= SDI_PARENT;            m_pServicesContainer->m_scopeDataItem.relativeID = param;            spConsoleNameSpace->InsertItem(&m_pServicesContainer->m_scopeDataItem);            m_pEventsContainer->m_scopeDataItem.mask |= SDI_PARENT;            m_pEventsContainer->m_scopeDataItem.relativeID = param;            spConsoleNameSpace->InsertItem(&m_pEventsContainer->m_scopeDataItem);            m_pAccountsContainer->m_scopeDataItem.mask |= SDI_PARENT;            m_pAccountsContainer->m_scopeDataItem.relativeID = param;            spConsoleNameSpace->InsertItem(&m_pAccountsContainer->m_scopeDataItem);                        hr = S_OK;        }        break;如何生成结果栏的项(Result Item)    从CMySnapinData继承,生成CEventEntry类,表示一个事件内容。在CEvent类中修改Notify方法的MMCN_SHOW消息,这个消息给了我们添加Result Item的机会。如何修改子节点的名字?    每一个结点都从CMySnapinData继承,其父类CSnapinItemImpl有一个成员变量CComBSTR m_bstrDisplayName。MMC调用IComponentData或者IComponent 的GetDisplayInfo来获得显示信息,包括结点名字,GetDisplayInfo把调用传递到GetScopePaneInfo或者GetResultPaneInfo函数,GetScopePaneInfo和GetResultPaneInfo函数默认返回m_bstrDisplayName。因此只要在构造函数里修改m_bstrDisplayName的值即可达到目的。如何修改子节点的图标?    在CMySnapinData中实现Notify方法的MMCN_ADD_IMAGES消息,这这里我们把所有的图标全部加载。这样其他的Item类就不需要再实现这部分代码了,只要把Notify中没有处理的消息让CMySnapinData的Notify方法处理一下即可。    对于每个Snapin都有一个IImageList接口可供使用,MMC利用这个图标列表来保存所有需要用到的图标,以后每个Item只需要指出它的图标在图标列表的索引即可:m_scopeDataItem.nImage = 2;m_scopeDataItem.nOpenImage = 2;    但是Scope栏中的图标却不是通过MMCN_ADD_IMAGES消息来加载的,而必须在CMySnapin类的Initialize方法中首先加载所有必要的图标。    还有,以上的方法对根结点的图标都没有作用。根结点的图标的修改必须通过修改CMySnapinAbout类GetStaticFolderImage方法才可以。也可以顺便为About对话框添加一个ICON。    如何修改子节点在结果栏中的默认显示?    Scope Item在Result pane中默认仅仅显示一个名称。比如,在选中“MySnapin”时,默认行为是在Result pane中显示一级子节点。下面我们让“事件查看器”显示更详尽的信息。    在CEventsContainer的Notify方法中修改MMCN_SHOW消息:case MMCN_SHOW:        {            CComQIPtr<IResultData, &IID_IResultData> spResultData(spConsole);                        spHeader->InsertColumn(0, L"日志", LVCFMT_LEFT, 100);            spHeader->InsertColumn(1, L"描述", LVCFMT_LEFT, 200);            hr = S_OK;        }        break;    然后在CEvent中重载GetResultPaneColInfo方法,提供每一列的显示字符串。    由于GetResultPaneColInfo是非虚函数,因此我们还得在CEvent重载GetResultPaneInfo函数。或者修改CMySnapinData的GetResultPaneColInfo函数为虚函数,那么就可以不用再重载GetResultPaneInfo了。如何给某一个子节点添加自己独特的菜单?    由于从CSnapinItemImpl以模版方式实现,因此从CMySnapinData继承的Item类,将需要重载相应的处理函数,而不能最大限度地重用CSnapinItemImpl的实现。    所以,我们让CService等几个需要更多功能的类从CSnapinItemImpl<CService>继承。给“服务”项目添加“启动”、“停止”菜单。1,    在菜单资源中添加菜单IDR_MYSNAPIN_MENU1,添加菜单项“启动”、“停止”到第一个下拉菜单中,如果需要在其他菜单项上,也可以添加到其他的下拉菜单中;2,    使用宏SNAPINMENUID(IDR_MYSNAPIN_MENU1)来告诉ATL菜单资源的ID;3,    使用宏BEGIN_SNAPINCOMMAND_MAP(CService, FALSE)            SNAPINCOMMAND_ENTRY(IDM_START, OnStart)            SNAPINCOMMAND_ENTRY(IDM_STOP, OnStop)    END_SNAPINCOMMAND_MAP()    来接收命令消息。4,    并使用HRESULT OnStart(bool& bHandled, CSnapInObjectRootBase* pObj);    HRESULT OnStop(bool& bHandled, CSnapInObjectRootBase* pObj);来处理消息。5,    如有必要,重载UpdateMenuState函数修改菜单状态;如何给某一个子节点添加自己独特的工具栏?给“服务”项目添加“启动”、“停止”工具栏按钮。1,    资源中添加工具栏,添加按钮“启动”、“停止”; 2,    使用宏BEGIN_SNAPINTOOLBARID_MAP(CService)            SNAPINTOOLBARID_ENTRY(IDR_TOOLBAR1)    END_SNAPINTOOLBARID_MAP()    来接收命令消息。3,    如有必要,重载UpdateToolbarButton函数修改按钮状态;4,    如有必要,在处理消息后,可以通过IToolbar接口修改工具栏按钮状态;如何给某一个子节点添加自己独特的属性页?给“帐户”添加属性页。1,    在CAccount类中重载Notify方法。2,    在Notify方法中处理MMCN_SELECT消息:CComPtr<IConsoleVerb> spConsoleVerb;            if(spConsole->QueryConsoleVerb(&spConsoleVerb) != S_OK)            {                ATLTRACE("==>Query ConsoleVerb faild./n");                return S_FALSE;            }            else            {                spConsoleVerb->SetVerbState(MMC_VERB_PROPERTIES, ENABLED, TRUE);                hr = S_OK;        }3,    重载CreatePropertyPages、QueryPagesFor方法,创建属性页,可以使用ATL Wizzard为我们生成的CMySnapinPage,也可以自行定制一个类似的类。4,    如有必要,可以在属性页的OnApply函数中发出属性改变通知CAccout;5,    CAccout相应地更新自己的显示。注意点:SCOPEDATAITEM/RESULTDATAITEM 结构的lParam,ATL特殊用法    lParam在ATL中有特殊意义,IComponentImpl和IComponentDataImpl的GetDisplayInfo函数都假设SCOPEDATAITEM/RESULTDATAITEM 结构的lParam就是CSnapInItem对象,因此,CMySnapinData的构造函数中有m_scopeDataItem.lParam = (LPARAM) this;和m_resultDataItem.lParam = (LPARAM) this;    因此,如果我们的Item类不从CMySnapinData继承的话,那么也要同样做这些工作。QueryPagesFor方法    如果Snapin支持属性页,那么必须在Root结点上提供一个QureyPagesFor的方法来支持,否则无法通过MMC控制台“增加”这个管理结点。


    最新回复(0)