QA@IT

C# 他アプリで選択したメニューの項目名を取得したい。

8042 PV

お世話になります。

他アプリの動作を監視し、キーボードのみでアプリケーションを操作した際、
ユーザがそのアプリケーションでどの様なメニュー操作をしたのかを
取得したいと思っています。

具体的には、例えばメモ帳で「Alt + F」「↓キー」「ENTERキー」の順に入力すると、
「ファイル」メニューの「開く」が選ばれる訳ですが、ENTER押下のタイミングで
”開く”という、選択されたメニューアイテムの文字列を取得したいと思っています。

そこでどこまで取得出来るか、以下の様なコードを組んでみました。

Accessibility コンポーネントを参照設定

[DllImport("oleacc.dll")]
public static extern int AccessibleObjectFromWindow(IntPtr hwnd, uint id, ref Guid iid,
[In, Out, MarshalAs(UnmanagedType.IUnknown)] ref object ppvObject);

private static IntPtr GetFocusedHandleFromProcessWithWindow(IntPtr window)
{
    var threadId = GetWindowThreadProcessId(window, IntPtr.Zero);
    var info = new GuiThreadInfo();
    info.cbSize = Marshal.SizeOf(info);
    if (!GetGUIThreadInfo(threadId, ref info)) return window;
    return info.hwndFocus;
}

private static void GetActiveControlName()
{
    // アクティブなウィンドウハンドルの取得
        IntPtr hWnd = GetForegroundWindow();
    IntPtr hWndControl = GetFocusedHandleFromProcessWithWindow(hWnd);
    CKeyControl ctrl = new CKeyControl();
    string retVal = String.Empty;
    Accessibility.IAccessible acc = null;
    object childId = null;

    Guid guid = new Guid("{618736E0-3C3D-11CF-810C-00AA00389B71}");
    object obj = null;
    int num = AccessibleObjectFromWindow(hWndControl, (uint)OBJID.OBJID_MENU, ref guid, ref obj);
    acc = (Accessibility.IAccessible)obj;
    int childCount = acc.accChildCount;

    for (int i = 0; i < childCount; i++)
    {
        string controlType = Enum.ToObject(typeof(System.Windows.Forms.AccessibleRole), acc.get_accRole(i)).ToString();
        string controlText = acc.get_accName(i);
        Debug.WriteLine("controlType : " + controlType + ", ControlText : " + controlText);
    }
..
}

しかしこれでは、どのキー操作のタイミングでも

controlType : MenuBar
ControlText : アプリケーション

controlType : MenuItem
ControlText : ファイル(F)

controlType : MenuItem
ControlText : 編集(E)

controlType : MenuItem
ControlText : 表示(V)
...

といった、メニューバー本体の内容までしか取得出来ず困っています。

ENTERを押した時点の、メニューでフォーカスがあるアイテムの
テキストを得るために、何かアイデアなどございますでしょうか。

宜しくお願い致します。

回答

少々状況が変わりましたので質問を若干変えさせていただきます。
最初の質問ではメニューバーの内容のみしか取得が出来ませんでしたが、
その後、子のアイテムを取得すれば良いことが判りました。

private static CKeyControl GetControlInfo()
{
    // アクティブなウィンドウハンドルの取得
    IntPtr hWnd = GetForegroundWindow();
    // フォーカスを持つウィンドウハンドルの取得
    IntPtr hWndControl = GetFocusedHandleFromProcessWithWindow(hWnd);
    IAccessible acc = null;

    AccessibleObjectFromWindow(hWndControl, OBJID.ID_MENU, out acc);
    int count = acc.accChildCount;
    for (int i = 1; i <= count; i++)
    {
        try
        {
            string ctrlName = acc.get_accName(i);
            string ctrlType = Enum.ToObject(typeof(System.Windows.Forms.AccessibleRole), acc.get_accRole(i)).ToString();
            string ctrlState = Enum.ToObject(typeof(System.Windows.Forms.AccessibleStates), acc.get_accState(i)).ToString();
            Debug.WriteLine(String.Format("[ACC{0}: CTRL-Name[{1}], Type[{2}], State[{3}]", i, ctrlName, ctrlType, ctrlState));
        }
        catch { }
    }

    ListAccObject(acc);
}

private static void ListAccObject(IAccessible acc)
{
    if (acc.accChildCount <= 0) return;

    int num = 0;
    object[] obj = GetAccessibleChildren(acc, out num);

    foreach (IAccessible child in obj)
    {
        string ctrlName = String.Empty;
        string ctrlState = String.Empty;
        string ctrlType = String.Empty;

        int count = child.accChildCount;
        Debug.WriteLine("count = " + count);

        for (int i = 0; i < count; i++)
        {
            try
            {
                ctrlName = child.get_accName(i);
                ctrlType = Enum.ToObject(typeof(System.Windows.Forms.AccessibleRole), child.get_accRole(i)).ToString();
                ctrlState = Enum.ToObject(typeof(System.Windows.Forms.AccessibleStates), child.get_accState(i)).ToString();

                Debug.WriteLine(String.Format("{0}: CTRL-Name[{1}], Type[{2}], State[{3}]", i, ctrlName, ctrlType, ctrlState));
            }
            catch { }
        }

        ListAccObject(child);
    }
}

これでGetControlInfo()内のループでメニューバーのアイテム名(「ファイル」「編集」「書式」「表示」「ヘルプ」)が取得でき、
ListAccObject()内で「ファイル」メニューの各項目名と、その中で選ばれている(ハイライトしている)項目が判別できるようになりました。
しかし、これで取得出来たのは「ファイル」(先頭のメニュー)の子のみであり、「編集」や「表示」といったメニューの項目が得られずです。

そこで、accNavigateなるメソッドの存在を知り、
対象を右へ移動しながら子を得れば良いのかと考え、
GetControlInfo()内を以下の様に書き換えました。

AccessibleObjectFromWindow(hWndControl, OBJID.ID_MENU, out acc);
object child = acc.accNavigate(NAVDIR_FIRSTCHILD, 0);
while (child != null)
{
    ListAccObject((IAccessible)child);
    child = acc.accNavigate(NAVDIR_RIGHT, child);
}

ですが目的通りに動作してくれずにつまづいています。
accNavigateで、対象を「ファイル」「編集」「書式」・・・と切り替えるためには
この方法ではNGでしょうか。

何かアドバイスをいただけると大変助かります。
宜しくお願い致します。

編集 履歴 (1)
ウォッチ

この質問への回答やコメントをメールでお知らせします。