PythonWin32API窗口与鼠标操作

窗口模块与截图

什么是pywin32库

pywin32是一个第三方模块库,主要的作用是方便python开发者快速调用windows API的一个模块库。

同时pywin32也是绝大部分windows上第三方python模块库的前提,比如wmi,如果没有安装pywin32是无法正常使用wmi这个三方模块库的。

安装pywin32

由于pywin32是第三方模块库,Python并没有自带,所以我们使用之前必须先按照,安装也很简单,只需要再pycharm下方终端窗口中输入pip install pywin32 回车就可以自动下载安装了。

看到success这样的字样则是说明安装成功了。

如果你不放心,还可以通过 pip list 可以看到pywin32是否已经安装成功了

pywin32提供的模块

Pywin32提供的模块有很多,常用的几个如下:

  • win32api 提供了常用的用户API函数
  • win32gui 提供了有关windows用户界面图形操作的API函数
  • win32con 提供了定义windows Api内的宏
  • win32clipboard 提供了有关粘贴板的API函数
  • win32console 提供了有关控制台的API函数
  • win32service 提供了有关服务操作的API函数
  • win32file 提供了有关文件操作的API函数

我们平时最常用的的模块有3个, win32api, win32gui,win32con,下面我们就导入 win32api,win32con这两个模块写一个简单的弹出框,顺便验证下我们pywin32是否安装成功了。 上代码:

复制代码
import win32api
import win32con

win32api.MessageBox(0, "我是渣男教父", "我是标题", win32con.MB_OK | win32con.MB_ICONWARNING)

运行下看看:

导入模块后,我们仅用了一行代码就创建了自定义的弹出框。

什么是窗口句柄

这里我们要先说下句柄的概念,通俗点说句柄就是窗口的身份证号,是一串整数。举个栗子,你有你自己的身份证号,一报身份证号,你应该知道是你了。你也有名字,但是大家都知道名字并不是唯一的,你可以叫张三,别人也可以叫张三,而且没有数字来得方便。所以,窗口句柄就相当于身份证号,每个窗口都有一个唯一编号, 操作系统用这个编号来发送消息。这就是操作系统的消息机制。

获取到窗口句柄,我们就可以通过这个句柄对窗口进行操作了。

查找窗口句柄

pywin32是如何查找窗口句柄的,这里我们用到了win32gui模块,以及模块的FindWindow方法。直接看代码看看

复制代码
import win32gui

hwnd = win32gui.FindWindow(None, '雷电模拟器')
print(hwnd)
hwnd2 = win32gui.FindWindow(None, '英雄联盟')
print(hwnd2)

运行下看看:

复制代码
5506644
0

我们现在详细解析下win32gui.FindWindow(0, wdname)这个函数,这个函数其实就是根据窗口标题获取窗口句柄

复制代码
# 函数功能:该函数获得一个顶层窗口的句柄,该窗口的类名和窗口名与给定的字符串相匹配。这个函数不查找子窗口。在查找时不区分大小写
# 参数1 窗口类名
# 参数2 窗口标题--必须完整;如果该参数为None,则为所有窗口全匹配
# 返回值:如果函数成功,返回值为窗口句柄;如果函数失败,返回值为0

因为我电脑上现在打开了雷电模拟器,有这个窗口,所以结果打印了雷电模拟器窗口对应的句柄数字,我没有打开英雄联盟的窗口,所以没有得到相应的句柄,返回值为0。

激活窗口

我们大多数情况下操作窗口都是需要窗口在最前方,这样对窗口的操作才会行之有效。所以接下来我们来写一个通用的方法来查找并激活窗口,如果查找到就使之前台显示并返回句柄。我们将文件名保存为获取句柄激活.py 直接上代码:

python 复制代码
import win32gui
import time

def activate_window(title):
    hwnd = win32gui.FindWindow(None, title)
    if hwnd == 0:
        print('未找到游戏窗口')
        return 0
    if win32gui.GetForegroundWindow() != hwnd:
        win32gui.SetForegroundWindow(hwnd)
        time.sleep(1)
    return hwnd

def activate_emulator():
    return activate_window('雷电模拟器')

if __name__ == '__main__':
    print(activate_emulator())

运行下:

(先手动将窗口放置到后台,不要最小化,最小化会有问题。)

这样我们就实现了将后台窗口重新激活放置到前台了。

这里又用到了两个win32gui的新方法。

  • win32gui.GetForegroundWindow()这个方法是获取当前前台窗口的句柄;
  • win32gui.SetForegroundWindow(hwnd)这个方法则是将hwnd对应的窗口前台显示。

程序的逻辑也很简单,首先判断雷电模拟器的窗口是否存在,如果存在则检查当前前台窗口hwnd与雷电模拟器的窗口hwnd是否一致,如果一致则不需要处理,否则就将雷电模拟器前台显示,最后再返回hwnd信息。

截图

平时大家聊qq时经常会用到截图功能,这是一个相当实用的功能。那我们今天也用Python来写一个截图功能。我们先看下桌面截图的代码,这个代码是只截取了部分代码,并不完整,只是用于展示。后面会有完整代码。 上代码:

python 复制代码
import win32gui
import win32ui
import win32con
import numpy as np

def capture_screen(hwnd):
    # 根据窗口句柄获取窗口的设备上下文DC(Divice Context)
    desktop = win32gui.GetDesktopWindow()
    dc = win32gui.GetWindowDC(desktop)
    # 根据窗口的DC获取mfcDC
    mfc_dc = win32ui.CreateDCFromHandle(dc)
    # mfcDC创建可兼容的DC
    save_dc = mfc_dc.CreateCompatibleDC()
    # 创建bitmap准备保存图片
    save_bit_map = win32ui.CreateBitmap()
    left, top, right, bottom = win32gui.GetWindowRect(hwnd)
    w, h = right - left, bottom - top
    # 为bitmap开辟空间
    save_bit_map.CreateCompatibleBitmap(mfc_dc, w, h)
    # 高度saveDC,将截图保存到saveBitmap中
    save_dc.SelectObject(save_bit_map)
    # 截取从左上角(0,0)长宽为(w,h)的图片
    save_dc.BitBlt((0, 0), (w, h), mfc_dc, (left, top), win32con.SRCCOPY)
    signed_ints_array = save_bit_map.GetBitmapBits(True)
    im_opencv = np.frombuffer(signed_ints_array, dtype='uint8')
    im_opencv.shape = (h, w, 4)
    save_dc.DeleteDC()
    win32gui.DeleteObject(save_bit_map.GetHandle())
    win32gui.ReleaseDC(hwnd, dc)
    return im_opencv

frombuffer(buffer, dtype=float, count=-1, offset=0) 作用:将缓冲区data以流的形式读入转化成ndarray对象

Parameters:

  • buffer:目标缓冲区对象
  • dtype:默认浮点型
  • count = -1表示缓冲区中所有数据
  • offset =0,从0开始读取

截图方法最终返回的是一个ndarray对象,也就是一个数字组成的矩阵。

截图方法写好了,这个方法用到的知识点比较繁琐,大家暂时知道怎么用就好了,只需要传入窗口句柄,就可以得到相应的图像了。结合前面说到的获取句柄模块 【 获取句柄激活.py 】 ,我们来截图看看最终结果的ndarray是怎样的,上代码:

python 复制代码
import win32gui
import win32ui
import win32con
import numpy as np
import get_handle_activate as window

def capture_screen(hwnd):
    # 根据窗口句柄获取窗口的设备上下文DC(Divice Context)
    desktop = win32gui.GetDesktopWindow()
    dc = win32gui.GetWindowDC(desktop)
    # 根据窗口的DC获取mfcDC
    mfc_dc = win32ui.CreateDCFromHandle(dc)
    # mfcDC创建可兼容的DC
    save_dc = mfc_dc.CreateCompatibleDC()
    # 创建bitmap准备保存图片
    save_bit_map = win32ui.CreateBitmap()
    left, top, right, bottom = win32gui.GetWindowRect(hwnd)
    w, h = right - left, bottom - top
    # 为bitmap开辟空间
    save_bit_map.CreateCompatibleBitmap(mfc_dc, w, h)
    # 高度saveDC,将截图保存到saveBitmap中
    save_dc.SelectObject(save_bit_map)
    # 截取从左上角(0,0)长宽为(w,h)的图片
    save_dc.BitBlt((0, 0), (w, h), mfc_dc, (left, top), win32con.SRCCOPY)
    signed_ints_array = save_bit_map.GetBitmapBits(True)
    im_opencv = np.frombuffer(signed_ints_array, dtype='uint8')
    im_opencv.shape = (h, w, 4)
    save_dc.DeleteDC()
    win32gui.DeleteObject(save_bit_map.GetHandle())
    win32gui.ReleaseDC(hwnd, dc)
    return im_opencv

def get_emulator_image():
    hwnd = window.activate_emulator()
    return capture_screen(hwnd)

if __name__ == '__main__':
    image = get_emulator_image()
    print(image)

main函数作为代码入口,我们看看截图的流程,其实就是两步,第一步,找到并激活窗口获得它的hwnd,第二步根据它的hwnd截图,是不是很简单。

我们运行下看看效果:

复制代码
[[[ 65  63  60 255]
  [ 65  63  60 255]
  [ 65  63  60 255]
  ...
  [153 136 123 255]
  [195 170 144 255]
  [195 182 168 255]]
 [[ 65  63  60 255]
  [ 65  63  60 255]
  [ 65  63  60 255]
  ...以下省略

这个数据看起来比较复杂,这里我们先暂时不用管它,接下来我们还会讲到opencv的模块,到时候我们再来处理这个数据。

这里需要注意一点,电脑显示设置中的缩放与布局必须是100%,否则截图会不对。

ps:上面面代码中的桌面截图()方法以及激活窗口()方法是可以复用的,因为我们后面会大量用到激活雷电模拟器和截取模拟器的图像,所以我们又封装了激活模拟器窗口()跟获取模拟器图像()两个函数。这个文件可保存为窗口截图.py。

本章小结

这一章咱们重新梳理了Windows窗口操作和鼠标模拟的核心知识,来捋一捋重点:

pywin32是啥玩意儿? 就是个让Python能调用Windows API的库。装上它,Python就能操控Windows的各种功能,比如操作窗口、模拟鼠标、访问剪贴板等等。最常用的是三兄弟:win32api(常用API函数)、win32gui(窗口图形操作)、win32con(Windows常量定义)。

窗口句柄怎么理解的? 句柄就是窗口的身份证号,一串唯一的整数。每个窗口都有一个编号,操作系统用这个编号来发送消息和操作窗口。用 win32gui.FindWindow(类名, 标题) 就能查到句柄,找不到返回0。

怎么激活窗口? 很多操作都得窗口在前台才有效。用 SetForegroundWindow(hwnd) 就能把窗口提到最前面。但要注意窗口不能是最小化状态,最小化了会有问题。可以先用 GetWindowPlacement() 检查窗口状态,如果最小化了先用 SendMessage() 恢复。

截图咋实现的? 截图流程看着复杂,其实就几步:拿到窗口句柄 → 获取设备上下文 → 创建兼容DC和位图 → 把窗口内容拷贝到位图 → 转成numpy数组。返回的是一个四维矩阵,每个像素点包含RGBA四个通道的值。这个numpy数组后面配合OpenCV就能做各种图像处理了。

鼠标点击怎么模拟的? 一套完整的点击分三步:SetCursorPos() 移动鼠标到目标位置 → mouse_event(MOUSEEVENTF_LEFTDOWN) 按下左键 → mouse_event(MOUSEEVENTF_LEFTUP) 释放左键。这三步缺一不可。

屏幕坐标和窗口坐标啥区别? 屏幕坐标以整个屏幕左上角为(0,0),窗口坐标以窗口左上角为(0,0)。想点击窗口里的某个位置,得用 ClientToScreen(hwnd, 窗口坐标) 把窗口坐标转成屏幕坐标,不然鼠标会点错地方。

核心套路是啥? 就一句话:找到窗口 → 激活窗口 → 截图/操作。这三板斧练熟了,后面写游戏自动化、做脚本工具,都是这个套路。

这些知识点是游戏自动化的基础,后面学OpenCV图像处理、模板匹配、轮廓检测,都是在这个基础上进行的。把基础打牢了,后面的内容就水到渠成了。

动手试试

选择题

1、获取窗口句柄的函数是哪个?

  • A. GetWindow()
  • B. FindWindow()
  • C. SearchWindow()
  • D. LocateWindow()

答案:B

解析: win32gui.FindWindow(类名, 标题) 是用来获取窗口句柄的函数。第一个参数是窗口类名,一般传None就行;第二个参数是窗口标题,必须完整匹配。如果找到了就返回句柄(一个整数),找不到返回0。还有个类似的函数叫 FindWindowEx(),是用来查找子窗口的,比 FindWindow() 多一个父窗口参数。另外要注意,FindWindow() 查找时不区分大小写。


2、将窗口置前的函数是哪个?

  • A. SetWindowPos()
  • B. SetForegroundWindow()
  • C. BringWindowToTop()
  • D. ShowWindow()

答案:B

解析: SetForegroundWindow(hwnd) 是用来把窗口提到最前面的函数,传入窗口句柄就行。这个函数会让窗口获得焦点并显示在最前面。那其他几个呢?SetWindowPos() 功能更强大,可以设置窗口位置、大小、层级等;BringWindowToTop() 是把窗口提到Z序顶部,但不一定获得焦点;ShowWindow() 是控制窗口的显示状态(显示、隐藏、最小化、最大化等)。一般情况下用 SetForegroundWindow() 就够了。


编程挑战

3、编写一个程序,获取当前活动窗口的句柄并截图保存。

参考代码:

python 复制代码
import win32gui
import win32ui
import win32con
import numpy as np
import cv2 as cv
import time

def get_active_window_handle():
    """获取当前活动窗口的句柄"""
    hwnd = win32gui.GetForegroundWindow()
    if hwnd == 0:
        print('未找到活动窗口')
        return None
    
    # 获取窗口标题
    title = win32gui.GetWindowText(hwnd)
    print(f'当前活动窗口:{title},句柄:{hwnd}')
    return hwnd

def capture_window(hwnd, save_path='screenshot.png'):
    """根据窗口句柄截图并保存"""
    # 获取窗口位置
    left, top, right, bottom = win32gui.GetWindowRect(hwnd)
    w = right - left
    h = bottom - top
    
    # 获取设备上下文
    hwnd_dc = win32gui.GetWindowDC(hwnd)
    mfc_dc = win32ui.CreateDCFromHandle(hwnd_dc)
    save_dc = mfc_dc.CreateCompatibleDC()
    
    # 创建位图
    bitmap = win32ui.CreateBitmap()
    bitmap.CreateCompatibleBitmap(mfc_dc, w, h)
    save_dc.SelectObject(bitmap)
    
    # 截图
    save_dc.BitBlt((0, 0), (w, h), mfc_dc, (0, 0), win32con.SRCCOPY)
    
    # 转成numpy数组
    signed_ints_array = bitmap.GetBitmapBits(True)
    img_array = np.frombuffer(signed_ints_array, dtype='uint8')
    img_array.shape = (h, w, 4)
    
    # 清理资源
    save_dc.DeleteDC()
    win32gui.DeleteObject(bitmap.GetHandle())
    win32gui.ReleaseDC(hwnd, hwnd_dc)
    
    # 去掉Alpha通道,转成BGR格式(OpenCV默认格式)
    img_bgr = cv.cvtColor(img_array, cv.COLOR_BGRA2BGR)
    
    # 保存
    cv.imwrite(save_path, img_bgr)
    print(f'截图已保存到 {save_path},分辨率:{w}x{h}')
    return img_bgr

if __name__ == '__main__':
    print('请在3秒内切换到要截图的窗口...')
    time.sleep(3)
    
    # 获取当前活动窗口
    hwnd = get_active_window_handle()
    
    if hwnd:
        # 截图保存
        capture_window(hwnd, '活动窗口截图.png')
        
        # 显示截图
        img = cv.imread('活动窗口截图.png')
        cv.imshow('活动窗口截图', img)
        print('按任意键关闭窗口')
        cv.waitKey(0)
        cv.destroyAllWindows()

思路解析:

这道题考察的是窗口操作和截图的综合运用。

首先,获取当前活动窗口用 win32gui.GetForegroundWindow() 就行,这个函数返回当前在最前面的窗口句柄。跟 FindWindow() 不一样,GetForegroundWindow() 不需要知道窗口标题,直接就能拿到当前活动窗口。用 GetWindowText(hwnd) 还能拿到窗口的标题文字。

其次,截图的流程跟前面学的一样:获取设备上下文 → 创建兼容DC → 创建位图 → 拷贝内容 → 转numpy数组。这里多了一步处理:截图返回的是RGBA四通道的数据,但OpenCV默认用的是BGR三通道格式,所以要用 cv.cvtColor(img_array, cv.COLOR_BGRA2BGR) 去掉Alpha通道,不然保存的图片会有问题。

然后,代码里加了个3秒倒计时,让你有时间切换到要截图的窗口。这个在实际使用中很实用,比如要截图某个弹出窗口,先运行脚本再快速切换过去。

最后,用 cv.imwrite() 保存图片,再用 cv.imshow() 显示出来看看效果。cv.waitKey(0) 表示等待按键,按任意键后关闭窗口。

几个实用小技巧:

  • 截图前确保窗口不是最小化状态,最小化的窗口截图会是空的或者内容不对。
  • 电脑显示设置中的缩放与布局必须是100%,否则截图尺寸会不对。
  • 如果截图保存后打不开,检查一下路径里有没有中文,OpenCV不支持含中文路径的文件保存。
相关推荐
人道领域1 小时前
【面试八股速通篇01】锁与并发工具
java·开发语言·面试·c#
kimnoic1 小时前
PHP 8.3 数据库备份失败怎么排查
开发语言·php
bnmoel1 小时前
C++ 基础入门篇(六)类和对象-第三篇:运算符重载
开发语言·c++·算法·语法·运算符重载
MoRanzhi12031 小时前
第 9 篇 · 数值计算与图像处理:NumPy、SciPy、Pillow
图像处理·python·数值计算·numpy·pillow·信号处理·scipy
CRMEB系统商城1 小时前
CRMEB开源商城系统(PHP)v8.0,打造全新AI二开商城底座!
开发语言·人工智能·小程序·开源·php
2601_962885721 小时前
如何用 Python 计算BRAR 情绪指标(AR/BR)?
python·ar·restful
宇卿.1 小时前
C#字段、属性、索引器、常量
开发语言·计算机视觉·c#
DeepVisionary1 小时前
几句人话就能拆解任何软件,几天破十万星的开源工具让开发者慌了
python·自动化
菜鸟~noob2331 小时前
【电子战】第31篇:3D AOA 定位——方位+俯仰联合
开发语言·matlab·3d