• #Windows
  • #UI Automation
  • #Python
  • #uv
  • #自動化
  • #達人Cube
開発完了

達人Cube自動化の仮説 - Windows UI Automation APIを使ったアプローチ

背景

達人Cubeと所得税の達人には、ショートカットキーが割り当てられていないメニューが多数存在する。繰り返し操作を自動化することで、業務効率を大幅に改善できる可能性がある。

達人Cubeのメイン画面

所得税の達人

結論(仮説)

Windows UI Automation APIを使えば、達人Cubeのような.NET/Windows Formsアプリケーションを自動操作できる可能性が高い。Pythonの uiautomation ライブラリを使用してUI要素を取得・操作する。

自動化アプローチの選択肢

ツール特徴向いているケース
UI Automation APIWindows公式API、高信頼性今回のケース
Power Automate DesktopGUI操作、ノーコードRPAで十分な場合
AutoHotkey軽量、スクリプト言語キーボード/マウス自動化
Python + pyautogui座標ベース画像認識が必要な場合

UI Automation APIを選択した理由:

  • 座標ではなくUI要素を直接操作できる
  • ウィンドウ位置が変わっても動作する
  • Pythonから柔軟に制御可能

調査用スクリプト

環境セットアップ(uv使用)

# プロジェクト初期化
uv init tatsujin-automation
cd tatsujin-automation

# 依存関係の追加
uv add uiautomation

UI要素調査スクリプト

まず達人Cubeのウィンドウ構造を調査する。

# inspect_tatsujin.py
import uiautomation as auto


def find_tatsujin_windows():
    """達人関連のウィンドウを探す"""
    results = []
    for win in auto.GetRootControl().GetChildren():
        name = win.Name or ""
        if "達人" in name or "Cube" in name:
            results.append(win)
            print(f"Found: {name}")
    return results


def dump_controls(control, depth=0, max_depth=3):
    """ウィンドウの子要素を再帰的に表示"""
    if depth > max_depth:
        return

    indent = "  " * depth
    name = control.Name or "(no name)"
    ctrl_type = control.ControlTypeName
    auto_id = control.AutomationId or ""

    print(f"{indent}[{ctrl_type}] {name}")
    if auto_id:
        print(f"{indent}  AutomationId: {auto_id}")

    for child in control.GetChildren():
        dump_controls(child, depth + 1, max_depth)


def list_all_windows():
    """全ウィンドウを表示"""
    print("=== 現在のウィンドウ一覧 ===")
    for win in auto.GetRootControl().GetChildren():
        print(f"  - {win.Name}")


if __name__ == "__main__":
    windows = find_tatsujin_windows()

    if windows:
        for win in windows:
            print(f"\n=== {win.Name} の構造 ===")
            dump_controls(win, max_depth=4)
    else:
        print("達人Cubeが見つかりません")
        list_all_windows()

実行:

uv run python inspect_tatsujin.py

操作用スクリプト(仮)

調査結果を元に調整が必要になる。

# tatsujin_automation.py
import uiautomation as auto
import time


class TatsujinAutomation:
    def __init__(self):
        self.cube = None
        self.shotokuzei = None

    def connect_cube(self):
        """達人Cubeに接続"""
        self.cube = auto.WindowControl(searchDepth=1, SubName="達人Cube")
        if self.cube.Exists(3):
            print(f"接続成功: {self.cube.Name}")
            return True
        print("達人Cubeが見つかりません")
        return False

    def open_shotokuzei(self):
        """所得税の達人を開く"""
        if not self.cube:
            return False

        # 「所得税」ボタンを探してクリック
        # 実際の要素名は調査スクリプトで確認
        btn = self.cube.ButtonControl(Name="所得税")
        if not btn.Exists(2):
            btn = self.cube.Control(SubName="所得税")

        if btn.Exists(2):
            btn.Click()
            time.sleep(2)
            return True
        return False

    def connect_shotokuzei(self):
        """所得税の達人に接続"""
        self.shotokuzei = auto.WindowControl(searchDepth=1, SubName="所得税の達人")
        return self.shotokuzei.Exists(3)

    def click_menu(self, menu_path: list[str]):
        """メニューを開く

        Args:
            menu_path: メニューのパス(例: ['ファイル(F)', '新規作成'])
        """
        if not self.shotokuzei:
            return False

        current = self.shotokuzei
        for menu_name in menu_path:
            item = current.MenuItemControl(SubName=menu_name)
            if item.Exists(2):
                item.Click()
                time.sleep(0.3)
                current = item
            else:
                print(f"メニューが見つかりません: {menu_name}")
                return False
        return True

    def click_toolbar(self, button_name: str):
        """ツールバーボタンをクリック"""
        if not self.shotokuzei:
            return False

        btn = self.shotokuzei.ButtonControl(Name=button_name)
        if btn.Exists(2):
            btn.Click()
            return True

        # ToolBarの中を探す
        toolbar = self.shotokuzei.ToolBarControl()
        if toolbar.Exists():
            btn = toolbar.ButtonControl(Name=button_name)
            if btn.Exists(2):
                btn.Click()
                return True

        print(f"ボタンが見つかりません: {button_name}")
        return False


if __name__ == "__main__":
    ta = TatsujinAutomation()

    if ta.connect_cube():
        ta.open_shotokuzei()

        if ta.connect_shotokuzei():
            # メニュー操作の例
            ta.click_menu(["ファイル(F)", "開く"])

UI要素調査ツール

スクリプトで要素が見つからない場合は、GUIツールで調査する。

Accessibility Insights for Windows(推奨):

Inspect.exe(Windows SDK付属):

C:\Program Files (x86)\Windows Kits\10\bin\<version>\x64\inspect.exe

結果

この仮説を検証した結果を以下にまとめた。

所得税の達人をUI Automation APIで自動化する

達人CubeのUIはカスタム描画でUI Automationでは検出できなかったが、所得税の達人はショートカットから直接起動することで自動化に成功した。

次のステップ

  1. 調査スクリプトを実行してUI要素の構造を確認 → 完了
  2. 取得できる要素名・AutomationIdを記録 → 完了
  3. よく使う操作をリストアップ
  4. 操作スクリプトを実際の要素名に合わせて修正 → 完了
  5. ショートカット化(AutoHotkeyと連携など)

想定される課題

  • 要素が取得できない: アプリがUI Automationに対応していない可能性がある。その場合はpyautoguiで座標ベース操作にフォールバックする
  • 動的な要素: ダイアログの出現タイミングでtime.sleepExistsでの待機が必要になる
  • 権限の問題: 管理者権限で実行が必要な場合がある

参考リンク