MSAA也就是Microsoft© Active Accessibility,是微软90年代推出的一套技术。事实上,这套东西的初衷是为了供残疾人使用windows系统,比如放大镜,鼠标事件等等。这套东西后来被广泛使用在自动化测试中,也就是我们常说的IAccessible 接口,由于它为自动化测试提供了方便,IBM也扩展了这套接口,IAccessible2(如果没记错的话);web版本的,也就是ActiveX。说到这些,就必须提到ole这门技术,我的理解是微软内部一开始只是为了自己自动化方便,做了一套最原始的ole automation的东西(主要技术是边界值检测和引用计数),后来发现这东西很好用,甚至被盖茨钦点,最终发展成后来的com,从某种角度来说,这东西可以说是90年代技术的巅峰。当然,IAccessible由oleacc.dll在com组件中提供。它的鸡肋就是接口太少,只能访问UI元素的名字,位置,描述等,一些其他的操作并不支持——这也是后来微软发展UI Automation的理由之一(扩展了两个接口,一个用来实现基本的处理,另一个实现外部调用和msaa的兼容性,在.net平台有效)。自己当初在office组的时候正巧负责mac os版中的oleauto模块迁移,深感这套技术的厉害之处(我们叫它mac上的伪com),可以看到从8位到64位的整个迁移过程。由于这里主要是讨论MSAA在python中的实现和架构设计,至于本源问题个人觉得没必要深究。当然UIS和MSAA相比,各有各的特点,相互之间通过代理和桥打交道。http://blogs.msdn.com/b/lixiong/archive/2009/03/28/msaa-uia-brief-explanation.aspx,这个链接里清楚的描述了他们的关系。我上面的简单描述并不一定正确,可以自行深入了解。下来开始讨论正格朗的东西(UIA的使用和实现有时间再讨论)。

IAccessible接口的中文资料应该不是很难找,但是python使用和封装的具体方法,好像没有什么中文资料(英文的我也没找到,只好自己封装了)。

通过oldacc.dll得到的IAccessible返回一个UI的引用,如果这个元素支持IAccessible接口,意味着这个元素是由程序来完成接口的实现,一般的标准win32控件都属于这类,也就是标准win32控件原生支持msaa的访问。就如同标准的win32控件和wpf控件支持UIA一个道理,如果不支持微软就是打自己的嘴了。

如果一个元素并不支持IAccessible接口,一些类名如user和COMCTL32的元素,oldacc.dll会自动创建代理为其实现IAccessible接口,大多数情况下可以得到它的引用。一些其他的UI元素比如directUI和其他自定义UI元素,如要得到IAccessible的支持,则需要完成相关代理的开发。鱼与熊掌不可兼得,寻求速度和美感的同时,必须要有一些牺牲,UIA中同样需要自己去完成provider来支持对UIAutomation的访问。Chrome开源代码里有一套在chrome浏览器中实现IAccessible的现成示例,以后有时间会专门讨论chrome浏览器源代码中的自动化测试的相关代码。一般实现需要3个步骤:继承IAccessible_base,注册WM_GETOBJECT的OnGetObject事件,完成OnGetObject事件使外部代理可以获取IAccessible实例;完成成员各自的IAccessible接口和子节点的派发。

整体来说,思路是这样的:

封装一个UIElement或者UIObject的元素类,通过初始化获取其IAccessible接口;

获取元素相关信息,如name,role,location,description等等;

获取其子元素个数和子元素的IAccessible接口;

提供遍历子元素方法;

提供子元素查找方法;

现在我们分别完成这些步骤。

获取IAccessible接口的获取:

MSAA提供了三个得到IAccessible接口的方法:AccessibleObjectFromWindow,AccessibleObjectFromPoint和AccessibleObjectFromEvent,这3个函数中,基本上常用的以第一个为主,我们使用这个对UIElement进行初始化。剩下的两个参照文档和示例可以自己去完成,下面这个链接包含大部分用户方法:http://msdn.microsoft.com/en-us/library/windows/desktop/dd742692(v=vs.85).aspx。

确定了我们要使用AccessibleObjectFromWindow,现在来研究一下它。我们要研究的东西其实很简单,就是from window的这个window是哪个window。事实上,IAccessible支持的元素,比窗口要多——也就是说,搜索同样的窗口和搜索这个窗口上的IAccessible,前者的效率会高一些。所以我们会先使用win32函数找到窗口句柄,然后从句柄中获取IAccessible的接口。

一些必要的win32和com模块需要导入:

importcomtypes, comtypes.automation, comtypes.clientimport ctypes, ctypes.wintypes

然后是类的初始化函数:

classElement(object):def __init__(self,objHandle,iObjectId=0):def_get_hwnd(objHandle):if objHandle in (0,None):#无窗口的情况,使用根窗口

hwnd=ctypes.windll.user32.GetDesktopWindow()returnhwndelif isinstance(objHandle,basestring):#传入窗口名字或类名

objHandle=unicode(objHandle)

hwnd= ctypes.windll.user32.FindWindowW(objHandle, None) orctypes.windll.user32.FindWindowW(None, objHandle)if hwnd>0:returnhwndelif isinstance(objHandle,(int,long)):#直接传入hwnd

if objHandle>0:returnobjHandleelse:passhwnd=_get_hwnd(objHandle)if hwnd and hwnd>0:

IAccessible=ctypes.POINTER(comtypes.gen.Accessibility.IAccessible)()

ctypes.oledll.oleacc.AccessibleObjectFromWindow(hwnd, 0, ctypes.byref(comtypes.gen.Accessibility.IAccessible._iid_), ctypes.byref(IAccessible))

self.IAccessible=IAccessible

self.iObjectId= iObjectId#默认objectid,后面有用处子节点

截图显示,我们已经获取到了IAccessible的接口,接下来,我们获取元素的各部分信息,我们并没必要实现所有信息的获取(如果有必要方法相似),必要的信息包括,元素名称,元素类型(msaa中的acc_role),元素位置,元素值,元素描述。

这些元素当中,最重要的应该是类型——acc_role。因为在后面的查询中,用到最多的就是这个类型了。Msaa中支持的类型有63个,内部由1~63代替,可以做成一个字典以便后面映射方便。这些类型的值包括:

roles=[

u'TitleBar',

u'MenuBar',

u'ScrollBar',

u'Grip',

u'Sound',

u'Cursor',

u'Caret',

u'Alert',

u'Window',

u'Client',

u'PopupMenu',

u'MenuItem',

u'Tooltip',

u'Application',

u'Document',

u'Pane',

u'Chart',

u'Dialog',

u'Border',

u'Grouping',

u'Separator',

u'ToolBar',

u'StatusBar',

u'Table',

u'ColumnHeader',

u'RowHeader',

u'Column',

u'Row',

u'Cell',

u'Link',

u'HelpBalloon',

u'Character',

u'List',

u'ListItem',

u'Outline',

u'OutlineItem',

u'PageTab',

u'PropertyPage',

u'Indicator',

u'Graphic',

u'Text',

u'EditableText',

u'PushButton',

u'CheckBox',

u'RadioButton',

u'ComboBox',

u'DropDown',

u'ProgressBar',

u'Dial',

u'HotKeyField',

u'Slider',

u'SpinBox',

u'Diagram',

u'Animation',

u'Equation',

u'DropDownButton',

u'MenuButton',

u'GridDropDownButton',

u'WhiteSpace',

u'PageTabList',

u'Clock',

u'SplitButton',

u'IPAddress',

]

下面是一个映射:

>>> RoleMapps=dict([(i+1,roles[i]) for i in range(63)])>>>RoleMapps

{1: u'TitleBar', 2: u'MenuBar', 3: u'ScrollBar', 4: u'Grip', 5: u'Sound', 6: u'Cursor', 7: u'Caret', 8: u'Alert', 9: u'Window', 10: u'Client', 11: u'PopupMenu', 12: u'MenuItem', 13: u'Tooltip', 14: u'Application', 15: u'Document', 16: u'Pane', 17: u'Chart', 18: u'Dialog', 19: u'Border', 20: u'Grouping', 21: u'Separator', 22: u'ToolBar', 23: u'StatusBar', 24: u'Table', 25: u'ColumnHeader', 26: u'RowHeader', 27: u'Column', 28: u'Row', 29: u'Cell', 30: u'Link', 31: u'HelpBalloon', 32: u'Character', 33: u'List', 34: u'ListItem', 35: u'Outline', 36: u'OutlineItem', 37: u'PageTab', 38: u'PropertyPage', 39: u'Indicator', 40: u'Graphic', 41: u'Text', 42: u'EditableText', 43: u'PushButton', 44: u'CheckBox', 45: u'RadioButton', 46: u'ComboBox', 47: u'DropDown', 48: u'ProgressBar', 49: u'Dial', 50: u'HotKeyField', 51: u'Slider', 52: u'SpinBox', 53: u'Diagram', 54: u'Animation', 55: u'Equation', 56: u'DropDownButton', 57: u'MenuButton', 58: u'GridDropDownButton', 59: u'WhiteSpace', 60: u'PageTabList', 61: u'Clock', 62: u'SplitButton', 63: u'IPAddress'}

获取和设置类型值,可以直接调用com方法:

def accValue(self, objValue=None):

objChildId=comtypes.automation.VARIANT()

objChildId.vt=comtypes.automation.VT_I4

objChildId.value=self.iObjectId

objBSTRValue=comtypes.automation.BSTR()if objValue isNone:

self.IAccessible._IAccessible__com__get_accValue(objChildId, ctypes.byref(objBSTRValue))returnobjBSTRValue.valueelse:

objBSTRValue.value=objValue

self.IAccessible._IAccessible__com__set_accValue(objChildId, objValue)return objBSTRValue.value

当然如果为了使用方便,可以再提供只读的属性返回映射中的值:

defaccRoleName(self):try:

iRole=self.accRole()returnAccRoleNameMap.get(iRole)except:return None

同样的几个只读元素属性,位置,子元素个数,描述,父元素,状态,帮助等,以及几个读写元素名字,值,选中操作等,使用同样的方法完成(代码未测试):

defaccLocation(self):

objChildId=comtypes.automation.VARIANT()

objChildId.vt=comtypes.automation.VT_I4

objChildId.value=self.iObjectId

objL, objT, objW, objH=ctypes.c_long(), ctypes.c_long(), ctypes.c_long(), ctypes.c_long()

self.IAccessible._IAccessible__com_accLocation(ctypes.byref(objL), ctypes.byref(objT), ctypes.byref(objW), ctypes.byref(objH), objChildId)return(objL.value, objT.value, objW.value, objH.value)defaccChildCount(self):#之前提到的objectid,0代表本元素的IAccessible的接口

if self.iObjectId ==0:returnself.IAccessible.accChildCountelse:return0defaccDescription(self):

objChildId=comtypes.automation.VARIANT()

objChildId.vt=comtypes.automation.VT_I4

objChildId.value=self.iObjectId

objDescription=comtypes.automation.BSTR()

self.IAccessible._IAccessible__com__get_accDescription(objChildId, ctypes.byref(objDescription))returnobjDescription.valuedefaccParent(self):returnself.IAccessible.accParent()defaccState(self):

objChildId=comtypes.automation.VARIANT()

objChildId.vt=comtypes.automation.VT_I4

objChildId.value=self.iObjectId

objState=comtypes.automation.VARIANT()

self.IAccessible._IAccessible__com__get_accState(objChildId, ctypes.byref(objState))returnobjState.valuedefaccHelpTopic(self):return self.IAccessible.accHelpTopic()

读写元素:

def accName(self, objValue=None):

objChildId=comtypes.automation.VARIANT()

objChildId.vt=comtypes.automation.VT_I4

objChildId.value=self.iObjectIdif objValue isNone:

objName=comtypes.automation.BSTR()

self.IAccessible._IAccessible__com__get_accName(objChildId, ctypes.byref(objName))returnobjName.valueelse:

self.IAccessible._IAccessible__com__set_accName(objChildId, objValue)def accValue(self, objValue=None):

objChildId=comtypes.automation.VARIANT()

objChildId.vt=comtypes.automation.VT_I4

objChildId.value=self.iObjectId

objBSTRValue=comtypes.automation.BSTR()if objValue isNone:

self.IAccessible._IAccessible__com__get_accValue(objChildId, ctypes.byref(objBSTRValue))returnobjBSTRValue.valueelse:

objBSTRValue.value=objValue

self.IAccessible._IAccessible__com__set_accValue(objChildId, objValue)returnobjBSTRValue.valuedefaccSelect(self, iSelection):#iSelection代表select 的flag,1,2,4,8,16,常用的是1和2,代表聚焦和选中

ifself.iObjectId:returnself.IAccessible.accSelect(iSelection, self.iObjectId)else:return self.IAccessible.accSelect(iSelection)

大部分元素的读写就完成了,如果还需要其他的元素可以用相应的接口实现。下面具体讨论一下对子元素的操作,包括读取,遍历和查询。

一个元素的子元素的特点是:

和父元素是聚合关系;

和父元素类型一致;

所以我们需要解决的第一是如何组织这种聚合结构关系,第二是如何实例化子元素的Element。

对于第一个问题,一般来说面向对象的设计最容易想到就是直接将一个所以子元素一次性放到一个集合中,使用的时候在进行处理;另外就是可以采用发生和迭代的方法,将子元素动态的依附到父元素实例上,直接通过实例进行枚举。Python中推荐后一种,因为python的开发中架构上不是那么精确,一般都是一个大概样子,各种架构实现大部分也都是技巧性的(当然包括类的实现亦如此),但是相对前一种编程难一些,不过时间效率上会高。当然这个需要看情况,有些时候需要将子元素中资源存放,以便再次使用的时候方便——这种对聚合元素的调用决定了结构的设计,说开了就是一个缓存,但是缓存的度决定了哪种设计。0缓存就是第二种方法,最大缓存(子元素完全缓存)就是第一种方法,当然如果涉及到所有子元素的递归,那么很明显不适合这样的大缓存,递归的查找和匹配只需要将关键位缓存就可以了。类中尽可能少的去使用空间,至于需要多少就交给外部调用的人来决定。

对于第二个问题,就需要先讨论一下之前代码中提到的iObjectId。我们一开始的设计Element类的初始化方法中,使用了这个这个带默认值的参数,这个参数有它特定的含义,还要从IAccessible接口的获取谈起——这个内容是不可避免的,我了解到的很多国内想使用这个东西的人,都找不到相关的中文材料,英文的材料也是解释很多示例很少,虽然解释很清晰,但是大部分比较晦涩——跟Python的情况差不多,没有特别好的文档和示例,都是大家摸索着来。之前初始化的时候我们最终使用的数据其实是两个,一个是IAccessible,一个是iObjectId,这个也是接口要求的——事实上很多资料上写的比较混乱,但是一个元素的表示确实由这两个值决定的,决定的方法是这样的:

任何元素都可以使用接口+ID的方式表示,这种表示方法有两种途径:如果接口属于本元素,则ID为0;如果接口是父元素,则ID为元素在其父元素中的次序,即 父接口+序号或本接口+0。怎么去选择方法呢?当一个子元素不支持IAccessible的接口时,我们只有用父接口+序号的方法去表示,当子元素支持IAccessible的接口时,我们显然要选取一种,我个人的建议是这样的:如果只是单独的想获取这个子元素的属性信息,大可不必获取它的IAccessible接口,直接使用父接口+序号的方法,效率会很高;如果想递归去的获取所有支持IAccessible接口元素的属性,则需要设置好这种结构,使用本IAccessible+0的方式结构上会比较统一,当然在这里会判断一下,不支持IAccessible接口的子元素我们直接用父接口+序号的形式进行标示。

def __iter__(self):#为自身迭代设置临界值

if self.iObjectId >0:raiseStopIteration()#定义子元素数量和容器,像之前讨论过的一样,这部分也可以专门指定一个结合完成聚合结构,如果有必要的话

objAccChildArray= (comtypes.automation.VARIANT *self.IAccessible.accChildCount)()

objAccChildCount=ctypes.c_long()#通过接口获取数量和元素集合

ctypes.oledll.oleacc.AccessibleChildren(self.IAccessible, 0, self.IAccessible.accChildCount, objAccChildArray, ctypes.byref(objAccChildCount))#循环生成对应的Element实例并直接返回

for i inxrange(objAccChildCount.value):

objAccChild=objAccChildArray[i]if objAccChild.vt ==comtypes.automation.VT_DISPATCH:yieldElement(objAccChild.value.QueryInterface(comtypes.gen.Accessibility.IAccessible), 0)#即comtypes.automation.VT_I4的情况,不支持IAccessible的接口

else:yield Element(self.IAccessible, i+1)

基本上,我们对于一个Element的封装,主要部分就差不多了,当然这里主要是给出一种思路和方法,没有太多的时间进行验证,一般自动化的底层设计验证起来比较麻烦,因为上层可能遇到各种各样的问题,这个只能按自己的项目需求进行判断了。一般来说,这样的基础类,还需要提供一些查询匹配子元素的方法,查询匹配的方式,就是提供父元素和子元素的属性条件,返回父元素下满足子元素过滤条件的所有匹配子元素。当然,由于我们上面的示例代码中,只是列出了各种属性值的获取方式,元素的方法等其他属性并没有实现,下面的匹配方法中,也只给出来对属性的过滤,当然并不完善,只是一个思路,部分异常没有处理。

首先定义一个匹配判断的方法:

def match(self, **kwargs):

flag=Truetry:for strProperty inkwargs:try:

attr=getattr(self, strProperty)exceptAttributeError:continue

try:

value=attr()except:

value=Noneelse:if type(value) !=str:

value= value.encode('gbk')if value.lower() !=kwargs[strProperty].lower():

flag=Falsebreak

exceptException, ex:

flag=Falsereturn flag

有了判断的标准,就可以对子元素进行过滤了,如下:

def finditer(self, **kwargs):

temp=list(self)whiletemp:

objElement=temp.pop(0)if objElement.match(**kwargs):yieldobjElementif objElement.IAccessible.accChildCount >0:

temp.insert(0, list(objElement))

外部查询接口:

def findall(self, **kwargs):try:return list(self.finditer(**kwargs))except:pass

好了,基本上一个automation element元素类就构造好了,也可以投入使用,但是里面还包含着一些问题,主要有:

效率问题:如果递归过滤一个元素中的所有自元素,效率相对较低,可以采取一些集合作为缓冲;当然如果外围使用按层次调用,也可以每次只过滤直接子元素;

异常问题:基本上只做了一半的异常处理,一些其他的异常处理没有去做;

方法成员没有完成,只完成了属性成员;

没有调试。。。

有兴趣的朋友们可以自己去完善和改善,python的世界就是这么一点一点的完美的。

IAccessible的接口常用的基本上也就这么多了,有时间可以再讨论一下UIAutomation的使用,让然UIA的部分相对容易一些,毕竟资料还是很容易搜到的。

python仿真电力系统自动化技术_基于MSAA的自动化封装和设计—python版(转)相关推荐

  1. 基于python的网络爬虫技术_基于python的网络爬虫技术的研究

    龙源期刊网 http://www.qikan.com.cn 基于 python 的网络爬虫技术的研究 作者:刘文辉 李丽

  2. 单片机节日彩灯实训报告_基于单片机控制的节日彩灯设计.pdf

    ELECTRONICS WORLD 技术 交流 ・ 基于单片机控制的节日彩灯设计 徐州生物工程职业技术学院 魏建玮 [摘要] 为了增添节日气氛,大部分城市都会有节日彩灯,而节日彩灯大部分是单一循环的, ...

  3. C++课程设计实训_基于多态书籍信息管理系统的设计与实现、附源码、有过程截图

    C++课程设计实训_基于多态书籍信息管理系统的设计与实现.附源码,有过程截图 书籍信息管理系统的设计与实现(基于多态) 学生姓名: 学 号: 指导老师: 所 在 系: 专 业: 班 级: C++课程设 ...

  4. 电子秤c语言编程,基于AT89C51的数字电子秤的设计最终版(样例3)

    <基于AT89C51的数字电子秤的设计.doc>由会员分享,可免费在线阅读全文,更多与<基于AT89C51的数字电子秤的设计(最终版)>相关文档资源请在帮帮文库(www.woc ...

  5. python 仿真 电力系统自动化_电力系统自动化技术的应用及发展方向

    龙源期刊网 http://www.qikan.com.cn 电力系统自动化技术的应用及发展方向 作者:甘红艺 来源:<中国科技博览> 2014 年第 19 期 [ 摘 要 ] 随着国家经济 ...

  6. python 机器人运动仿真_基于ros平台的移动机器人的设计与运动仿真-创新创业训练计划.pdf...

    基于ros平台的移动机器人的设计与运动仿真-创新创业训练计划 基于ROS 平台的移动机器人的设计与运动仿真 陈勇林 朱应钦 杜政恒 张玉林  (重庆大学城市科技学院电气信息学院,重庆 永川 402 ...

  7. python实现数据可视化软件_基于Python实现交互式数据可视化的工具

    作者:Alark Joshi 翻译:陈雨琳 校对:吴金笛 本文2200字,建议阅读8分钟. 本文将介绍实现数据可视化的软件包. 这学期(2018学年春季学期)我教授了一门关于数据可视化的数据科学硕士课 ...

  8. 高中python课程实施方法_基于学科核心素养的高中信息技术Python教学探析

    龙源期刊网 http://www.qikan.com.cn 基于学科核心素养的高中信息技术 Python 教学探析 作者:张明华 来源:<新课程 · 上旬> 2020 年第 07 期 摘 ...

  9. python 按需加载_基于python的opcode优化和模块按需加载机制研究(学习与个人思路)(原创)...

    基于python的opcode优化和模块按需加载机制研究(学习与思考) 姓名:XXX 学校信息:XXX 主用编程语言:python3.5 文档转换为PDF有些图片无法完全显示,请移步我的博客查看 完成 ...

  10. python图书馆管理系统实验报告_基于python图书馆管理系统设计实例详解

    写完这个项目后,导师说这个你完全可以当作毕业项目使用了,写的很全,很多的都设计考虑周全,但我的脚步绝不止于现在,我想要的是星辰大海!与君共勉! 这个项目不是我的作业, 只是无意中被拉进来了,然后就承担 ...

最新文章

  1. 【程序猿猴年金牌福利包】让你远离Bug,紧跟春天的脚步
  2. 少儿编程150讲轻松学Scratch(七)-Scratch学习中需要注意的地方
  3. 【POJ - 1995】Raising Modulo Numbers(裸的快速幂)
  4. 多种语言打印Hello World
  5. jrebel离线激活_jrebel激活
  6. 计算机专业色弱限制,体检标准变成建议 色盲色弱能报高校计算机专业
  7. 技术笔试面试题(下)
  8. 监控Linux磁盘情况,进行邮件告警
  9. java正则表达式控制格式_“如果Java受到一两个大型供应商的控制,则可能会遭受挫折”...
  10. 新浪微博热搜榜“背后的男人”讲述热搜背后的秘密
  11. MT4MT5EA编程跟单系统EA
  12. 中国苹果树苗市场产量需求与投资收益预测报告2022-2028年
  13. 手把手教你怎么使用云服务器
  14. Proteus VSM Studio汇编 + 8位数码管时钟按键可调
  15. 2021年焊工(技师)考试资料及焊工(技师)试题及解析
  16. LiteOS内核开发(一)
  17. 微信小程序之图片、音频、视频上传(附前后端代码示例)
  18. 【​观察】“Cloud Ready”计划背后 全面云化时代联想的行与思
  19. ActiveMQ--ActiveMQ简介
  20. oracle maxdatafiles,Oracle db_files和maxdatafiles参数

热门文章

  1. Linux连接Internet
  2. error: failed to attach to process ID 0问题解决
  3. 15条SQLite3语句
  4. baidu搜索出现错误提示页面
  5. java opencv 计算滑块横移距离
  6. jquery中ajax的使用例子($.ajax())
  7. tomcat搭建之Jenkins环境
  8. .NET微信扫码支付模式二API接口开发测试
  9. 1.Java集合-HashMap实现原理及源码分析
  10. Swift 再等等?我的答案是:快上车