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

196 天前
 XIVN1987

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

''' 脚本修改 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() 方法,,是不是通过该方法发送个特定信号就行了??请大神指点,,谢谢。。

1098 次点击
所在节点    Python
11 条回复
NessajCN
196 天前
你应该直接问怎么修改 uvproj 和 uvopt 才能保证文件格式不变,而不是自己想个这么奇葩的方法来问怎么实现
建议直接把你修改文件的脚本贴上来
clemente
196 天前
获取进程号 subprocess.Popen 然后强制 kill
XIVN1987
196 天前
@NessajCN

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

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

https://stackoverflow.com/questions/6539891/python-library-for-editing-xml-preserving-formatting-and-comments
XIVN1987
196 天前
之所以想要让 keil 恢复格式,,是因为每次修改 uvproj 和 uvopt 后提交代码,,都会显示整个文件都完全改变了,,但实际上我只修改了其中几个字符而已。。
ysc3839
196 天前
FindWindow 找到对应窗口,然后发送 WM_CLOSE 消息
henix
196 天前
可能最好的办法是用 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
196 天前
@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
196 天前
@NessajCN

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

XML 是结构化文件,,相同的字符在不同的位置有不同的含义,,不是简单正则能处理的,,
NessajCN
196 天前
@XIVN1987 文件就是文件,只要不是二进制的你这么替换肯定没问题,你正则替换别去动其他字符,文件结构怎么会变?你用这个方法改了试试,不好用你找我
geelaw
196 天前
抽象层级错误的原因,点关闭按钮是图形用户界面的概念(更具体来说是 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
196 天前
@henix

感谢,,用 pywinauto 成功了。。

看来是我一开始找错了库啊。。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/989347

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX