Python 使用UIAutomation 实现DataGridView中复选框的多选

本人是一名计算机老师,平时喜欢写写脚本吗,小程序啥的,负责某软件,但是某软件在工作的时候操作其实蛮不方便的,很多时候需要大批量点击DataGridView中的复选框,而且无顺序。
我就想写一个程序,学生将号码无序的告诉我,我在软件中找到对应的复选框进行自动点击,这样能省不少人力。
我最开始使用的是Win32API,发现Win32Api太老了,很多微软新的控件并不支持,spy++无法访问DataGridView控件中的内容。
通过上网搜索(很痛苦的过程),发现了微软较为新的版本的自动化软件,也就是UIAutomation,但大多数都是c#版本的,c#用来写脚本不方便,感觉太繁琐,要是写.Net程序还可以,于是查找Python 的uiautomation,教程太少了,而且很多代码根本不好使。
一、uiautomation方法

1、WindowContrl(searchDepth,ClassName,SubName) 查找窗口中的程序,如果有中文则需用Unicode;可用window.Exists(maxSearchSeconds)来判断此窗口是否存在;

2、EditControl(searchFromControl) 查找编辑位置,找到后可用DoubleClick()来改变电脑的focus;edit.SetValue(“string”)输入值;

3、Win32API.SendKeys(“string”) 如果已在编辑位置,则可用此方法来输入值,{Ctrl}为ctrl键,其他类似;{@ 8}格式可输入8个@,对于数字也可实现此功能,但对于字母不能…;

4、MenuItemControl(searchFromControl,Name) 查找菜单按钮;

5、ComboBoxControl(searchFromControl,AutomationI) 查找下拉框,然后在此基础上用Select(“name”)方法来选择需要的选项;

6、BottonControl(searchFromControl,Name,SubName) 查找按钮;

7、automation.FindControl(firefoxWindow,
lambda c:(isinstance(c, automation.EditControl) or isinstance(c, automation.ComboBoxControl)) and c.Name == ‘Enter your search term’ ) 按条件搜索handle,并且有效的关键字只有以下几项(看源码看到的)
‘ControlType’, ‘ClassName’, ‘AutomationId’, ‘Name’, ‘SubName’, ‘RegexName’, ‘Depth’, ‘Compare’ Compare指的应该是Lamda表达式,但是UIAutomaton的Lamda表达式我带进去始终有错误,说一个参数为止不能放两个参数,但是我单独在python中使用是没问题的,希望大神们有知道的可以给我解答一下。
这些代码执行的前提是必须得有一句 window.setActive();如果不激活窗口,所有控件都找不到。这是我遇到的第一个坑。
后来我尝试过各种算法去按照Name属性查找控件,发现这个插件竟然是一个一个控件遍历的,一个班的学生50人左右,他要遍历所有控件50遍,每一行又有许许多多根本用不上的控件,他都要遍历,这样我查50个人的时间甚至还不如点50下的时间要短。
获取控件Value值得办法,网上也找不到
value=控件.GetPattern(UIAutomation.PatternId.ValuePattern).Value//获取控件Value属性的办法
PatternId.中还有很多模式,例如TextValue是文本框的值,ToggleValue是复选框的值,但是DataGridView中的复选框用不了,因为他的ContentType是DataItemControl,并不是Checkedbox,每一行的控件是CustomControl
控件.GetPattern(UIAutomation.PatternId.ValuePattern).SetValue(‘True’)//设置复选框为True的办法,这都是我自己一个一个试出来的(凭借我的编程经验)
由于效率过慢,我就想能不能重写其遍历方法,后来我找到了WalkControl()这个函数(太不易了)
https://blog.csdn.net/dennyli1986/article/details/101233134 在这找到的
然后可以对每一个控件指定遍历的对象和层数。不多说了,我直接上代码。

import uiautomation as auto
window=auto.WindowControl(Name = '窗口名称')
window.SetActive()
bmxx=window.WindowControl(AutomationId = 'FM_KSBMXX')
dgv=bmxx.TableControl(AutomationId = "dgdv_ksxx")
bmxh="序号"
xz="选择"
controls=[]
values=['11110302', '11110303', '11110304', '11110305','11110308','11110323','11110345']
end=50
count=0
for controll,depth in auto.WalkControl(dgv,maxDepth=1):
    if controll.Name=="水平滚动条" or controll.Name=="垂直滚动条" or controll.Name=="首行":
        continue
    if isinstance(controll,auto.CustomControl):
        if count>end:
            break
        count+=1
        thiscontrol=None
        for control,depth in auto.WalkControl(controll,maxDepth=1):
            if isinstance(control,auto.DataItemControl):
                if xz in control.Name:
                    thiscontrol=control
                if bmxh in control.Name:
                   value=control.GetPattern(auto.PatternId.ValuePattern).Value
                   if value in values:
                       controls.append(thiscontrol)
                   break
for control in controls:
    control.GetPattern(auto.PatternId.ValuePattern).SetValue('True')

这是我呕心沥血得出的东西(希望大神可以指正),这遍历是通过数据结构遍历树的方式遍历的,孩子兄弟表示法,具体的执行代码在DLL中,由于本人逆向水平有限(正在努力学)就只能做到这了,上述代码直接拿走,改变改变就可以用(没有错误)
还有我之前呕心沥血做的效率低下的代码也拿出来供大家参考一下:

import uiautomation as auto
import win32api
import win32con
import time
class student:
    def __init__(self,bmxh,itshandle,row):
        self.bmxh=bmxh
        self.itshandle=itshandle
        self.row=row
students=[None]*100
points=[]
pointsvalue=[]
startsvalue=[]
groups=[]
def readrows(strings,first,x,dgv):
    k=0
    for j in range(len(points)):
        for i in range(startsvalue[j]+1,points[j]):#获取一个班的数据
            if k>=len(groups):
                break
            while strings[k] in pointsvalue:
                k+=1
                if k>=len(strings):
                    break
            if k>=len(groups):
                break
            if(groups[k]>j+1):
                break
            print("正在加载第"+str(i)+"行")
            row=dgv.CustomControl(Name = '行 '+str(i))
            xhr=row.Control(Name = '报名序号 行 '+str(i))
            value=xhr.GetPattern(auto.PatternId.ValuePattern).Value
            if value==strings[k]:
                selectrow=row.Control(Name = '选择 行 '+str(i))
                evestudent=student(strings[k],selectrow,i)
                students[k]=evestudent
                print("获取到第"+str(k+1)+"个 "+strings[k])
                k+=1
def erfenfa(low,high,count):#拆分出关键点
    if count>=3:
        return
    mid=int((low+high)/2)
    points.append(mid)
    count+=1
    erfenfa(low,mid-1,count)
    erfenfa(mid+1,high,count)
def getindex(strings,value):
    k=0
    for string in strings:
        if string==value:
            return k;
        k+=1
def readkeypoint(strings,dgv,first):
    for i in range(len(points)):
        print("正在读取第"+str(points[i])+"行")
        row=dgv.CustomControl(Name = '行 '+str(points[i]))
        xhr=row.Control(Name = '序号 行 '+str(points[i]))
        value=xhr.GetPattern(auto.PatternId.ValuePattern).Value
        pointsvalue.append(value)
        if value in strings:
            selectrow=row.Control(Name = '选择 行 '+str(points[i]))
            k=getindex(strings,value)
            print(strings[k]+"关键点被找到")
            evestudent=student(strings[k],selectrow,points[i])
            students[k]=evestudent
def releasestart(first,end):
    startsvalue.append(first-1)
    for i in range(len(points)):
        startsvalue.append(points[i])
    points.append(end-1)
    print("起终查询段落如下:")
    for i in range(len(startsvalue)):
        if i==0:
            print(str(startsvalue[i]+1)+" "+str(points[i]-1)+" 第"+str(i+1)+"段")
        else:
            print(str(startsvalue[i])+" "+str(points[i]-1)+" 第"+str(i+1)+"段")
def sperategroup(strings,pointsvalue):
    for string in strings:
        group=0
        for pointvalue in pointsvalue:
            group+=1
            if string<pointvalue:
                break
        groups.append(group)
    for i in range(len(strings)):
        print(strings[i]+" "+str(groups[i]))
def readModel(string,first,end,dgv):#first是具体数字减1,end要比正常大一,x是一页显示的个数
    strings=string.split(' ')
    for i in range(0,len(strings)):
        strings[i]="1111"+strings[i]
    #readrows(strings,first,end,0,x,dgv)#first end 0是变量
    erfenfa(first,end,0)
    print("关键点已找到")
    points.sort()#my_list.sort(reverse=True)从大到小
    print(points)
    releasestart(first,end)
    readkeypoint(strings,dgv,first)
    sperategroup(strings,pointsvalue)
    readrows(strings,first,dgv)
def printf():
    k=0
    for evestudent in students:
        if evestudent!=None:
            print(str(k)+" "+evestudent.bmxh+" "+evestudent.itshandle.Name)
        k+=1
def click():
    for evestudent in students:
        if evestudent ==None:
            break
        evestudent.itshandle.GetPattern(auto.PatternId.ValuePattern).SetValue('True')
def calc(page,first,end,pagenum):
    first=first-(page-1)*pagenum-1
    end=end-(page-1)*pagenum
    return  first,end
if __name__ == '__main__':
    window=auto.WindowControl(Name = '窗口名称')
    window.SetActive()
    bmxx=window.WindowControl(AutomationId = 'FM_KSBMXX')
    dgv=bmxx.TableControl(AutomationId = "dgdv_ksxx")
    string="0102 0103 0108 0109 0111 0120 0134 0141"
    first,end=calc(3,122,180,60)
    readModel(string,first,end,dgv)
    printf()
    click()

后来本人想想,如果遍历的控件在最后这个怎么办,这个我目前也没想到太好的办法,因为树的遍历本身就是先遍历第一个孩子,然后遍历他的兄弟,只能从UIAutomation获取树的源码中进行改变?这个可能得需要对windows程序内核进行了解了,本人水平还不够,还会继续努力(在逆向方面)

def stringCompare(str1, str2):
    if str1 in str2:
        print("yes1")


# index指str2在str1中的开始下标,为-1则证明str1中不包含str2
def stringCompare2(str1, str2):
    if str1.index(str2) > -1:
        print("yes2")


# 同样是查询str2在str1中的开始下标
def stringCompare3(str1, str2):
    if str1.find(str2) > -1:
        print("yes3")

这是字符串比较的几种方式

def WalkControl(control: Control, includeTop: bool = False, maxDepth: int = 0xFFFFFFFF):
    """
    control: `Control` or its subclass.
    includeTop: bool, if True, yield (control, 0) first.
    maxDepth: int, enum depth.
    Yield 2 items tuple (control: Control, depth: int).
    """
    if includeTop:
        yield control, 0
    if maxDepth <= 0:
        return
    depth = 0
    child = control.GetFirstChildControl()
    controlList = [child]
    while depth >= 0:
        lastControl = controlList[-1]
        if lastControl:
            yield lastControl, depth + 1
            child = lastControl.GetNextSiblingControl()
            controlList[depth] = child
            if depth + 1 < maxDepth:
                child = lastControl.GetFirstChildControl()
                if child:
                    depth += 1
                    controlList.append(child)
        else:
            del controlList[depth]
            depth -= 1

这是WalkControl的Python源码,具体的功能在UIAutomation那个DLL里面,我还学到了一个yield关键字,以前学过小一阵的Unity3D,c#的由于大学课业比较繁重就放弃了,但yield在Unity里是协程的意思,python中就是每次执行到这里都暂停,等着下次遍历,或者python 的next函数调用那个函数
例如:

def foo():
    print("starting...")
    while True:
        res = yield 4
        print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(next(g))

到这里你可能就明白yield和return的关系和区别了,带yield的函数是一个生成器,而不是一个函数了,这个生成器有一个函数就是next函数,next就相当于“下一步”生成哪个数,这一次的next开始的地方是接着上一次的next停止的地方执行的,所以调用next的时候,生成器并不会从foo函数的开始执行,只是接着上一步停止的地方开始,然后遇到yield后,return出要生成的数,此步就结束。
本段转载自 https://blog.csdn.net/mieleizhi0522/article/details/82142856)
这就是我这段时间学习Python UIAutomation的理解,希望大家批评指正,我还会继续努力学习逆向和windows内核的,争取能看到DLL中的源码和争取进入到Windows内核的学习阶段。

已标记关键词 清除标记
表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页