subprocess.Popen 能不能模拟点击应用窗口右上角的叉号关闭应用程序? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
XIVN1987
V2EX    Python

subprocess.Popen 能不能模拟点击应用窗口右上角的叉号关闭应用程序?

  •  
  •   XIVN1987 2023-11-07 09:25:01 +08:00 1703 次点击
    这是一个创建于 704 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我的需求和实现代码如下:

    ''' 脚本修改 uvproj 和 uvopt 会导致文件格式变化,执行此函数打开工程再关闭,可恢复原本格式 ''' def open_close_uvproj(path): print(f'\n{path}') for file in os.listdir(path): if file.endswith('.uvproj') or file.endswith('.uvprojx'): proc = subprocess.Popen(fr'D:\Program\Keil\UV4\UV4.exe {os.path.join(path, file)}') time.sleep(10) proc.terminate() 

    现在能够打开指定的 Keil 工程,,也能在延时 10s 后自动关闭工程,,但工程文件没有恢复格式。。

    猜测是因为只有通过点 Keil 右上角的叉号关闭,,Keil 才能写工程文件,,用 terminate() 和 kill() 杀死进程时,,Keil 不会保存工程文件

    请问 Popen 有什么方法可以实现模拟点应用窗口右上角叉号关闭应用的方法吗??

    我知道 Popen 有个 send_signal() 方法,,是不是通过该方法发送个特定信号就行了??请大神指点,,谢谢。。

    11 条回复    2023-11-07 11:41:21 +08:00
    NessajCN
        1
    NessajCN  
       2023-11-07 09:36:45 +08:00
    你应该直接问怎么修改 uvproj 和 uvopt 才能保证文件格式不变,而不是自己想个这么奇葩的方法来问怎么实现
    建议直接把你修改文件的脚本贴上来
    clemente
        2
    clemente  
       2023-11-07 09:49:09 +08:00
    获取进程号 subprocess.Popen 然后强制 kill
    XIVN1987
        3
    XIVN1987  
    OP
       2023-11-07 09:54:39 +08:00
    @NessajCN

    uvproj 和 uvopt 文件都是 XML 格式的。。python 似乎没有能够修改 xml 文件同时保持格式完全不变。。

    比如下面这个提问,,十多年了也没有简单的解决方案。。

    https://stackoverflow.com/questions/6539891/python-library-for-editing-xml-preserving-formatting-and-comments
    XIVN1987
        4
    XIVN1987  
    OP
       2023-11-07 09:59:16 +08:00
    之所以想要让 keil 恢复格式,,是因为每次修改 uvproj 和 uvopt 后提交代码,,都会显示整个文件都完全改变了,,但实际上我只修改了其中几个字符而已。。
    ysc3839
        5
    ysc3839  
       2023-11-07 10:01:00 +08:00 via Android
    FindWindow 找到对应窗口,然后发送 WM_CLOSE 消息
    henix
        6
    henix  
       2023-11-07 10:19:39 +08:00   2
    可能最好的办法是用 Python 调用 Keil 的命令行工具,实现你要的操作。因为 subprocess.Popen 主要是针对命令行程序。但我对 Keil 不了解,不知道能否纯用命令行实现。

    如果你要自动化地打开一个 GUI 程序再关闭,可以在 Win32 的层面使用 Win32 API 来自动化。

    大致的原理是:

    1. 使用 FindWindow 或其他方法得到窗口句柄
    2. 向该窗口发送 WM_CLOSE 消息

    参考:

    https://learn.microsoft.com/zh-cn/windows/win32/learnwin32/closing-the-window
    https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-findwindowa
    https://stackoverflow.com/questions/5402158/using-sendmessage-to-send-wm-close-to-another-process

    但是以上是 C++ 层面的 API ,如果用 Python 又该怎么做呢?可以使用 pywinauto 。代码大概如下:

    ```py
    import time
    from pywinauto.application import Application

    app = Application().start("???.exe")
    time.sleep(10)
    app.kill(soft=True)
    ```

    另一种方法是找到顶层窗口之后 close:

    ```py
    import time
    from pywinauto.application import Application
    from pywinauto.controls.hwndwrapper import HwndWrapper

    app = Application().start("???.exe")
    time.sleep(10)
    root: HwndWrapper = app.top_window()
    root.set_focus()
    root.close()
    ```
    NessajCN
        7
    NessajCN  
       2023-11-07 10:23:13 +08:00
    @XIVN1987 你这需求甚至不需要用到任何库呀,简单的一个 re 正则替换不就完了
    with open("/path/to/uvproj", "r") as fp:
    lines = fp.readlines()
    with open("/path/to/uvproj"", "w") as fp:
    for line in lines:
    fp.write(re.sub(r'<regexp>', '<string>', line))
    XIVN1987
        8
    XIVN1987  
    OP
       2023-11-07 10:38:15 +08:00
    @NessajCN

    用正则表达式修改 XML 文件??我感觉这不是好办法。。

    XML 是结构化文件,,相同的字符在不同的位置有不同的含义,,不是简单正则能处理的,,
    NessajCN
        9
    NessajCN  
       2023-11-07 10:43:03 +08:00
    @XIVN1987 文件就是文件,只要不是二进制的你这么替换肯定没问题,你正则替换别去动其他字符,文件结构怎么会变?你用这个方法改了试试,不好用你找我
    geelaw
        10
    geelaw  
       2023-11-07 11:07:14 +08:00 via iPhone
    抽象层级错误的原因,点关闭按钮是图形用户界面的概念(更具体来说是 user32 ),结束进程是进程( kernel32 )的概念,进程不一定创建窗口,自然不可能期待操作进程的代码能够处理用户界面。

    正确的方法,根据 .NET 里的 Process.CloseMainWindow:

    1. 获得目标窗口的句柄
    2. 判断目标窗口是否是禁用状态( GetWindowLong ),如果是,则不可关闭
    3. 否则,用 PostMessage (不等待回复)或者 SendMessage (等待回复)发送 WM_CLOSE 到目标窗口

    但我个人的意见是不需要第二步,因为它不能保证第三步操作的时候窗口依然处于非禁用状态。另外第三步,如果目标窗口是对话框则无效,此时正确的操作是 WM_COMMAND ;第三步也可以考虑 WM_SYSCOMMAND 和 SC_CLOSE 。

    最后,如果目标程序提供 COM (例如 Office 系列),则应该优先采用 COM 操作。

    P.S. 如果有人搜索“禁用窗口”并看到了这条回复,有必要提示改变窗口禁用/启用状态不可以用 SetWindowLong ,而是要用 EnableWindow 。
    XIVN1987
        11
    XIVN1987  
    OP
       2023-11-07 11:41:21 +08:00
    @henix

    感谢,,用 pywinauto 成功了。。

    看来是我一开始找错了库啊。。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     870 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 20:27 PVG 04:27 LAX 13:27 JFK 16:27
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86