我的玻璃盒子

【原创】模拟点击Outlook命令栏中的某个按钮

几乎2年没来这blog了,欣慰的是居然原来的数据还在。。。。
正好今天帮同事解决了一个有意思的小问题,就随手写一篇blog,记录这个事情。

背景是这样的:
需要通过一个和Outlook没有关系的单独exe,来执行Outlook中的某个命令按钮,而不是靠手动点击去执行。有这个需求的原因是,他希望在用户按下按钮后,起一个线程去工作,但不能block住Outlook导致用户不能正常操作。当线程结束后,再自动触发Outlook Addin中的某个按钮命令做剩余的事情。给Office写过Addin的都知道,从后台线程中访问Outlook对象模型是不被支持的(参见:http://msdn.microsoft.com/en-us/library/office/dd278301(v=office.12).aspx),所以,这就给实现需求带来了困难。那么如何实现呢?

难点:
Office界面上的按钮不是一个普通的窗口,无法拿到句柄去发送WM_COMMAND消息,这就给模拟执行某个按钮命令带来了困难。

分析:
用Spy++看了Outlook 2003/2007/2010的界面(其中2003/2007相同,2010的Ribbon则又是另外一种情况),可以看到,在2003/2007中,工具栏的父窗口类名叫“MsoCommandBar”,2010则相对复杂,只能看到“NetUIHWND”这一层。
因为我们无法直接通过窗口拿到按钮,所以只能另外想办法——看到NetUIHWND,马上联想到DirectUI,继而就非常感谢微软提供了IAccessible这个方便的东西。可能有过IAccessible使用经验的朋友看到这里,马上就豁然开朗,没错。后面写的都是用这个东东来完成的。

解决思路:
先通过FindWindow一层层找到你能访问到的“最后”一个窗口。对于Outlook 2003/2007来说,路径应该是这样的:

rctrl_renwnd32 -> MsoCommandBarDock(窗口名MsoDockTop) -> MsoCommandBar(窗口名是你的插件工具栏名称)

而对于Outlook 2010来说则相对多一些:

rctrl_renwnd32 -> MsoCommandBarDock(窗口名MsoDockTop) -> MsoCommandBar(窗口名Ribbon) -> MsoWorkPane(窗口名Ribbon) -> NUIPane -> NetUIHWND

好了,这一层窗口句柄拿到了,接下来先获取这一层窗口的IAccessible接口,示例代码:

IAccessible* accTop = NULL;
HRESULT hr = AccessibleObjectFromWindow(hwndTOP, NULL, IID_IAccessible, (LPVOID*)&accTop);

拿到其IAccessible后,还需要继续遍历其所有子元素,2003/2007再访问两层就能拿到具体的一个按钮的IAccessible接口,而2010则要访问至少6层。

往下讲就没什么可说的了。我贴几个关键的函数吧,请自行建一个Win32进行测试。

 1 BOOL FindAccessible(IAccessible* accParent, IAccessible** accToFind, LPCTSTR lpctFindName)
 2 {
 3     VARIANT* vt_output = NULL;
 4     BOOL bRet = FALSE;
 5     if(accParent == NULL)
 6         return FALSE;
 7 
 8     long lChildCount = 0;
 9     HRESULT hr = accParent->get_accChildCount(&lChildCount);
10     if(FAILED(hr) || (lChildCount == 0))
11         return FALSE;
12 
13     vt_output = new VARIANT[lChildCount];
14     for(int i=0; i<lChildCount; i++)
15         VariantInit(&vt_output[i]);
16 
17     long lNewChildCount = 0;
18     hr = AccessibleChildren(accParent, 0, lChildCount, vt_output, &lNewChildCount);
19     if(FAILED(hr))
20         goto exit;
21 
22     for(int j=0; j<lNewChildCount; j++)
23     {
24         if(vt_output[j].vt == VT_DISPATCH)
25         {
26             IDispatch* disp = vt_output[j].pdispVal;
27             hr = disp->QueryInterface(IID_IAccessible, (void**)accToFind);
28             if(FAILED(hr))
29                 continue;
30 
31             VARIANT vChildID;
32             VariantInit(&vChildID);
33             vChildID.vt = VT_I4;
34             vChildID.lVal = CHILDID_SELF;
35 
36             BSTR name;
37             hr = (*accToFind)->get_accName(vChildID, &name);
38             if(FAILED(hr))
39             {
40                 SysFreeString(name);
41                 continue;
42             }
43 
44             if(name == NULL)
45                 continue;
46 
47             ODF(_T("get_accName=%s\n"), name);
48             if((lpctFindName != NULL) && _tcsicmp(name, lpctFindName) == 0)
49             {
50                 //yes, we found!
51                 //accToFind now hold the IAccessible pointer we need
52                 bRet = TRUE;
53                 SysFreeString(name);
54                 break;
55             }
56             else
57             {
58                 if( (lpctFindName == NULL)
59                     && (_tcslen(name) == 0) )
60                 {
61                     //ok, may be find a NAMELESS object
62                     bRet = TRUE;
63                     SysFreeString(name);
64                     break;
65                 }
66             }
67             SysFreeString(name);
68         }
69     }
70 exit:
71     if(vt_output)
72     {
73         for(int k=0; k < lChildCount; k++)
74             VariantClear(&vt_output[k]);
75         delete vt_output;
76     }
77     return bRet;
78 }

上面函数,给出一个父节点的IAccessible和要匹配的子节点名称,来获取子节点的IAccessible接口。

下面贴一个调用代码(for 2003/2007):

 1     HWND hwndOutlookWnd = FindWindow(_T("rctrl_renwnd32"), NULL);
 2     HWND hwndTopBarDock = FindChildWnd(hwndOutlookWnd, _T("MsoCommandBarDock"), _T("MsoDockTop"));
 3     HWND hwndOneClickBar = FindChildWnd(hwndTopBarDock, _T("MsoCommandBar"), _T("你的工具栏名称"));
 4 
 5     IAccessible* accTop = NULL;
 6     HRESULT hr = AccessibleObjectFromWindow(hwndOneClickBar, NULL, IID_IAccessible, (LPVOID*)&accTop);
 7     if(FAILED(hr))
 8         return FALSE;
 9 
10     IAccessible* accToFind = NULL;
11     if( FindAccessible(accTop, &accToFind, _T("工具栏名称")) )
12     {
13         //we found the OneClick toolbar IAccessbile pointer
14         //now we need to find 'PUSH BUTTON' IAccessible pointer
15         IAccessible* accBtn = NULL;
16         if( FindAccessible(accToFind, &accBtn, _T("PUSH BUTTON")) && (accBtn != NULL) )
17         {
18             //oh yes, we found the button
19             //now do its default action (push down)
20             VARIANT varID;
21             VariantInit(&varID);
22             varID.vt = VT_I4;
23             varID.lVal = CHILDID_SELF;
24             accBtn->accDoDefaultAction(varID);
25             VariantClear(&varID);
26 
27             bRet = TRUE;
28         }
29         SAFE_RELEASE_COM_POINTER(accBtn);
30     }
31     
32     SAFE_RELEASE_COM_POINTER(accTop);
33     SAFE_RELEASE_COM_POINTER(accToFind);

里面用到的几个辅助方法:

 1 HWND FindChildWnd(HWND hParent, LPCTSTR lpctClassName, LPCTSTR lpctWndName = NULL)
 2 {
 3     HWND hChild = NULL;
 4     int nCount = 0;
 5     while(nCount < 30)
 6     {
 7         if( NULL != (hChild = ::FindWindowEx(hParent, NULL, lpctClassName, lpctWndName)) )
 8         {
 9             break;
10         }
11         nCount++;
12     }
13     return hChild;
14 }

1 #define SAFE_RELEASE_COM_POINTER(ptr) \
2     { \
3         if( (ptr) != NULL ) \
4         { \
5             ptr->Release(); \
6             (ptr) = NULL; \
7         } \
8     }


最后要说的是,谢谢你, IAccessible 君。^_^

posted on 2013-03-18 18:06 深蓝色系统 阅读(3264) 评论(0)  编辑 收藏 引用 所属分类: ATL/COM/WTL


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


导航

<2011年5月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

统计

常用链接

留言簿(75)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜