Windowsアプリケーションの自動化-Python編②-

Pythonで`win32gui`と、`win32con`を使ってWindowsアプリケーションのメニューバーを操作する
今回はフリーソフトの「サクラエディタ」を操作してみる

参考URL
 - [サクラエディタ](https://sakura-editor.github.io/) 
 - [Pythonで外部プログラムのメニューバーを操作する(WM_COMMAND)](http://bttb.s1.valueserver.jp/wordpress/blog/2017/10/05/python%e3%81%a7%e5%a4%96%e9%83%a8%e3%83%97%e3%83%ad%e3%82%b0%e3%83%a9%e3%83%a0%e3%81%ae%e3%83%a1%e3%83%8b%e3%83%a5%e3%83%bc%e3%83%90%e3%83%bc%e3%82%92%e6%93%8d%e4%bd%9c%e3%81%99%e3%82%8bwm_command/)
 - [Section6.3 メニューバー 応用編](http://www-higashi.ist.osaka-u.ac.jp/~k-maeda/vcpp/sec6-3menuapp.html) 
 - [(C++)外部アプリケーションのメニューバー操作](https://dixq.net/forum/viewtopic.php?t=16476) 

##作業環境
 - Windows10
 - Anaconda3 2020.11
 - Python3.8.5
 - サクラエディタ v2.2.0.1　←ちょっと古い

##メニューバーを操作して日付を挿入するイメージ
 - `編集(E)`メニューから`挿入(I)`→`日付挿入(D)`を選択するとPCの現在時刻が出力される
![sakura01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299201/c2c4a410-9ed6-333e-6933-47b2ef3cc715.png)
![sakura02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299201/a20d3c3e-1e4d-5e1a-8d87-f1be5fb5068a.png)

上記のメニューバー操作を、Pythonで自動化する

###メニューバー(タブメニュー)とそのサブメニューのハンドルを取得
メニューバーのハンドルは、子ウィンドウハンドルではなく`win32gui.GetMenu()`で取得できる
サブメニューや、サブメニューのサブメニューは`win32gui.GetSubMenu()`

```Python:winapi.py
import win32gui
import win32con

def run():
    p_hWnd = win32gui.FindWindow(None,"(無題)1 - sakura 2.2.0.1  ")  # 親ウィンドウハンドル(識別番号)を取得
    win32gui.SetForegroundWindow(p_hWnd)  # アプリケーションをデスクトップの前面に表示
    p_menu_hWnd = win32gui.GetMenu(p_hWnd)  # メニューバーのハンドルを取得
    p_menu_count = win32gui.GetMenuItemCount(p_menu_hWnd)  # メニューバー内のサブメニューの個数を取得
    
    # 各サブメニューのハンドルを取得
    s_menu_dict = {}
    for i in range(p_menu_count):
        s_menu_dict["s_menu_pos{}".format(i)] = win32gui.GetSubMenu(p_menu_hWnd, i)
    [print(k,v) for k,v in s_menu_dict.items()]  # サブメニュー辞書確認

if __name__ == '__main__':
    run()
```

- サブメニューのハンドルの例

|メニューバー|位置|SubMenu hWnd|
|:-:|:-:|:-:|
|ファイル(F)|0|15402645|
|編集(E)|1|459021|
|変換(C)|2|67635661|
|検索(S)|3|18022831|
|ツール(T)|4|31787639|
|設定(O)|5|5573367|
|ウィンドウ(W)|6|93652277|
|ヘルプ(H)|7|62851833|
それぞれのメニューのタブにハンドルが割り振られていることが確認できる

###サブメニューのサブメニューを取得する

```Python:winapi.py
import win32gui
import win32con

def run():
    p_hWnd = win32gui.FindWindow(None,"(無題)1 - sakura 2.2.0.1  ")  # 親ウィンドウハンドル(識別番号)を取得
    win32gui.SetForegroundWindow(p_hWnd)  # アプリケーションをデスクトップの前面に表示
    p_menu_hWnd = win32gui.GetMenu(p_hWnd)  # メニューバーのハンドルを取得
    p_menu_count = win32gui.GetMenuItemCount(p_menu_hWnd)  # メニューバー内のサブメニューの個数を取得
    
    # 各サブメニューのハンドルを取得
    s_menu_dict = {}
    for i in range(p_menu_count):
        s_menu_dict["s_menu_pos{}".format(i)] = win32gui.GetSubMenu(p_menu_hWnd, i)
    [print(k,v) for k,v in s_menu_dict.items()]  # サブメニュー辞書確認
    
    # 編集(E)メニューのサブメニューのIDを取得
    edit_menu = s_menu_dict["s_menu_pos1"]  # 編集(E)はpos1
    edit_menu_count = win32gui.GetMenuItemCount(edit_menu)  # 編集メニュー内のサブメニューの個数を取得
    edit_menu_dict = {}
    for i in range(edit_menu_count):
        s_menu_list = [win32gui.GetMenuItemID(edit_menu, i), win32gui.GetSubMenu(edit_menu, i)]
        edit_menu_dict["edit_menu_pos{}".format(i)] = s_menu_list
    [print(k,v) for k,v in edit_menu_dict.items()]
    
if __name__ == '__main__':
    run()
```

 - 編集メニューのIDとサブメニューの例(存在しない場合は0)

|編集メニュー|位置|ID|SubMenu hWnd|
|:-:|:-:|:-:|:-:|
|元に戻す(U)|0|30210|0|
|やり直し(R)|1|30211|0|
|(セパレーター)|2|0|0|
|切り取り(T)|3|30601|0|
|コピー(C)|4|30602|0|
|貼り付け(P)|5|30604|0|
|削除(D)|6|30221|0|
|すべて選択(A)|7|30401|0|
|(セパレーター)|8|0|0|
|再変換(R)|9|30285|0|
|(セパレーター)|10|0|0|
|CRLF改行でコピー(L)|11|30603|0|
|折り返し位置に改行をつけてコピー(H)|12|30608|0|
|矩形貼り付け(X)|13|30605|0|
|カーソル前を削除(B)|14|30222|0|
|(セパレーター)|15|0|0|
|挿入(I)|16|-1|1903099|
|高度な操作(V)|17|-1|57936345|
|移動(O)|18|-1|120392207|
|選択(S)|19|-1|56298477|
|矩形選択(F)|20|-1|290589067|
|整形(K)|21|-1|162268843|
このIDに対して`win32com.SendMessage()`が実行できる
ここでIDの取得に失敗して-1が返されているメニューはサブメニューが存在する
今回は日付挿入がしたいので、`挿入(I)`メニューのサブメニューからIDを取得する

###日付挿入メニューを実行する

```Python:winapi.py
import win32gui
import win32con

def run():
    p_hWnd = win32gui.FindWindow(None,"(無題)1 - sakura 2.2.0.1  ")  # 親ウィンドウハンドル(識別番号)を取得
    win32gui.SetForegroundWindow(p_hWnd)  # アプリケーションをデスクトップの前面に表示
    p_menu_hWnd = win32gui.GetMenu(p_hWnd)  # メニューバーのハンドルを取得
    p_menu_count = win32gui.GetMenuItemCount(p_menu_hWnd)  # メニューバー内のサブメニューの個数を取得
    
    # 各サブメニューのハンドルを取得
    s_menu_dict = {}
    for i in range(p_menu_count):
        s_menu_dict["s_menu_pos{}".format(i)] = win32gui.GetSubMenu(p_menu_hWnd, i)
    [print(k,v) for k,v in s_menu_dict.items()]  # サブメニュー辞書確認
    
    # 編集(E)メニューのサブメニューのIDを取得
    edit_menu = s_menu_dict["s_menu_pos1"]  # 編集(E)はpos1
    edit_menu_count = win32gui.GetMenuItemCount(edit_menu)  # 編集メニュー内のサブメニューの個数を取得
    edit_menu_dict = {}
    for i in range(edit_menu_count):
        s_menu_list = [win32gui.GetMenuItemID(edit_menu, i), win32gui.GetSubMenu(edit_menu, i)]
        edit_menu_dict["edit_menu_pos{}".format(i)] = s_menu_list
    [print(k,v) for k,v in edit_menu_dict.items()]
    
    # 挿入(I)メニューのサブメニューのIDを取得する
    ins_menu = edit_menu_dict["edit_menu_pos16"][1]  # 挿入(I)はpos16
    ins_menu_count = win32gui.GetMenuItemCount(ins_menu) # 挿入メニュー内のサブメニューの個数を取得
    ins_menu_dict = {}
    for i in range(ins_menu_count):
        ins_menu_dict["ins_menu_pos{}".format(i)] = win32gui.GetMenuItemID(ins_menu,i)
    [print(k,v) for k,v in ins_menu_dict.items()]

    # 日付挿入を実行
    win32gui.SendMessage(p_hWnd, win32con.WM_COMMAND, ins_menu_dict["ins_menu_pos0"], p_menu_hWnd) # 日付挿入(D)はpos0

    
if __name__ == '__main__':
    run()
```

- 挿入メニューのIDの例

|挿入メニュー|位置|ID|
|:-:|:-:|:-:|
|日付挿入(D)|0|30790|
|時刻挿入(T)|1|30791|
|コントロールコード入力(C)|2|30792|
ようやく目的のメニューIDを確認できた

- SendMessageの引数は下記のようにする

`win32gui.SendMessage([親ウィンドウハンドル],[win32con.WM_COMMAND],[対象メニューのID],[メニューバーのハンドル])`

とまあ、今回はこのくらいで終わり

##まとめ
メニューバーのハンドル値やIDをprint文で確認しながらなんとかサブメニューのサブメニューまで操作する方法は確認できましたが、メニューのテキストを直接参照できたらもう少し読み易くなると思いますね

:arrow_backward: [前回の記事](https://qiita.com/kurosworld/items/fb980fd0113a7979c68b) | [次回の記事] :arrow_forward: