質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.83%

他のアプリケーションのFormに実装されたDataGridViewのDataGridViewCellの値をIAccesibleオブジェクトから取得したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 995

smorking_area

score 24

宜しくお願いいたします。

進捗

IAccesibleで検索して来た方ごめんなさい。UIAutomationでやってます。C#なので・・・。

現状のコードはこんな感じというのをQiitaに投げつけときました。見やすいかなあと思って。

今のところCacheRequestしてから値を取ろうとすると上手くいっていないのでそこらへんにも取り組んでみます。

前提・実現したいこと

他のアプリケーションのFormに実装されたDataGridViewDataGridViewCellの値をIAccesibleオブジェクトを利用して取得したい。
以前の質問で提案してもらったAccessiblityを利用したアプリケーションの外部操作を実現しようとしています。

Reflectionなる仕組みが.NETで構築したアプリでは有効らしく実際自作のDataGridViewにいい加減な値が入ってるだけのバカチョンアプリを外部操作できたのですが、対象アプリは起動時に何らかの引数を必要としているためにそもそもAssembly.LoadFrom()で起動させられず頓挫したので今度はAccessibilityを利用した方法を試そうとしています。

  • 対象アプリはC#で作られたマルチウィンドウアプリ
  • IAccesssibleを使う理由は対象アプリが社外品でPGと細かい話や要求が出来ないため無理やり情報を抜き出す必要があるため
  • 対象アプリには一切手出し出来ない
  • IAccessible縛りではない。もっと他に便利な仕組みがあればそちらに移行してもよい。使えれば何でもOK

現状IAccessible.accNameとかIAccessible.accValueとかの値を覗き見ることが出来てはいるのですが、以下の問題が発生しています。

発生している問題・エラーメッセージ

DataGridViewCellにあたるオブジェクトを抜き出せていない。

該当のソースコード

// テストコードの概要
        private void ReadDataGirdView(IntPtr dataGridViewHandler)
        {   // DataGirdViewのハンドラを渡して、IAccessibleのaccNameとかaccValueの内容を列挙したものをMessageBoxで表示させる
            var accessibleObj 
                = AccessibleObjectExtractor.ExtractAccessibleObjectFromWindow(dataGridViewHandler);
            string msg;
            msg = MakeMsg(accessibleObj);
            MakeChildrenMsg(accessibleObj, ref msg);
            MessageBox.Show(msg);
        }

        private string MakeMsg(Accessibility.IAccessible accObj)
        {
            // IAccessibleの情報を列挙させてみる
            return "name = \"" + accObj.accName + "\"\t, value = \"" + accObj.accValue + "\"\t, type = \"" + accObj.GetType().ToString() + "\"\r\n";
        }

        private void MakeChildrenMsg(Accessibility.IAccessible accObj, ref string msg)
        {
            // 子要素の内容も書き出す
            Object[] accObjArr = AccessibleObjectExtractor.GetChildrenList(accObj);
            foreach (var childObj in accObjArr)
            {
                if (childObj == null) continue;
                if (childObj is int) continue; // Int32型で1とか2,3,4とかが取れてしまうのでここで弾く

                Accessibility.IAccessible childAccessible = (Accessibility.IAccessible)childObj;

                msg += MakeMsg(childAccessible);
                MakeChildrenMsg(childAccessible, ref msg); // 再帰的に見ていく
            }

        }
// IAccessibleのヘルパ 名前が良くない
using Accessibility;
using System.Runtime.InteropServices;

    class AccessibleObjectExtractor
    {
        #region  "constants and enums"

        ///<seealso cref="https://referencesource.microsoft.com/#UIAutomationClientsideProviders/MS/Win32/UnsafeNativeMethods.cs,5c8db4073b33b1b9,references"/>
        internal static Guid IID_IAccessible = new Guid(0x618736e0, 0x3c3d, 0x11cf, 0x81, 0x0c, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
        internal enum OBJID : uint
        {
            WINDOW = 0x00000000,
            SYSMENU = 0xFFFFFFFF,
            TITLEBAR = 0xFFFFFFFE,
            MENU = 0xFFFFFFFD,
            CLIENT = 0xFFFFFFFC,
            VSCROLL = 0xFFFFFFFB,
            HSCROLL = 0xFFFFFFFA,
            SIZEGRIP = 0xFFFFFFF9,
            CARET = 0xFFFFFFF8,
            CURSOR = 0xFFFFFFF7,
            ALERT = 0xFFFFFFF6,
            SOUND = 0xFFFFFFF5,
        }
        #endregion
        #region functions
        [DllImport("oleacc.dll")]
        internal static extern int AccessibleObjectFromWindow(
            IntPtr hwnd,
            uint id,
            ref Guid iid,
            [In, Out, MarshalAs(UnmanagedType.IUnknown)] ref object ppvObject);


        [DllImport("oleacc.dll")]
        public static extern uint AccessibleChildren(IAccessible paccContainer, int iChildStart, int cChildren, [Out] object[] rgvarChildren, out int pcObtained);

        public static IAccessible ExtractAccessibleObjectFromWindow(IntPtr handle)
        {
            object obj = null;
            //int retVal = AccessibleObjectFromWindow(handle, (uint)OBJID.WINDOW, ref IID_IAccessible, ref obj);
            int retVal = AccessibleObjectFromWindow(handle, (uint)OBJID.CLIENT, ref IID_IAccessible, ref obj);
            return (IAccessible)obj;
        }

        public static Object[] GetChildrenList(IAccessible parent)
        {
            var array = new Object[parent.accChildCount];
            int obtained; // C#では列挙対の最大数が不明でもforeachしたりできるため戻り値に不要

            AccessibleChildren(parent, 0, parent.accChildCount - 1, array, out obtained);

            return array;
        }
        #endregion
    }

対象のDataGridViewと現状の出力

対象のDataGridViewの状態

hoge1 fuga2
piyo3 nyan4

出力

name = "DataGridView"       , value = ""           , type = "System.__ComObject"
name = "トップの行"          , value = "トップの行"  , type = "System.__ComObject"
name = "左上のヘッダー セル"  , value = ""           , type = "System.__ComObject"
name = "colHead"            , value = "colHead"    , type = "System.__ComObject"
name = "行 0"               , value = "hoge1;fuga2", type = "System.__ComObject"
name = "行 0"               , value = ""           , type = "System.__ComObject"
name = "colHead 行 0"       , value = "hoge1"      , type = "System.__ComObject"
  • まずどれがセルなのかわからない。

プログラムで判別するプロパティを見つけるなりする予定。

  • セルっぽいのが2つある(1行目っぽいのと1セル目っぽいの?)

2行目とか2セル目が見られていないのでこのままでは全く役に立たない。

方針

  • UIAutomationを利用
  • AutomationIDプロパティ経由でのAutomationElement取得はしない
    できればAutomationIDを取得したいが開発環境では肝心の対象プログラムが動かず、開発用に提供された本番機にはWindowsSDKを入れるなとの指示が降りている。(開発機では環境上の都合でソフト自体が起動しない。)

補足情報(FW/ツールのバージョンなど)

  • Visual Studio 2015 SP1
  • Windows 7 64bit
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

取り急ぎ伝えておくと前回の回答では IAccessible オブジェクトからコントロールの参照が取れると言ってましたが、アンマネージドの世界からは不可能でした。すみません。ソースで確認しました。
https://referencesource.microsoft.com/#system.windows.forms/winforms/managed/system/winforms/control.cs,13029

この WM_GETOBJECT のハンドラで返される InternalAccesibleObject はコントロールに対する参照を取得できません。取得可能なのは UIAutomation の方です。とりあえずは UIAutomation を調べると良いかもしれません。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/07/10 09:57

    ありがとうございます
    フックの処理について少し勉強してみようかと思います。
    EntryPoint.Invokeでの起動はSetCompatibleTextRenderingDefault must be called before the first と例外を投げられて起動ができずにいます。引数自体もどういった値を渡すべきか不明ですので、半ば諦めています

    キャンセル

  • 2019/07/10 23:04 編集

    SetCompatibleTextRenderingDefault はウィンドウが先に作成されている際に例外を出力します。該当のアセンブリを起動する前に自身のコード内でフォームを表示していませんか?

    起動引数については該当アプリのファイル名を変更して、自分のアプリが起動されるようにして、自分のアプリに渡された引数で相手のアプリを起動するような処理にしてはどうでしょうか?実際にはレジストリの設定で起動するアプリを切り替える方法があります。
    https://troushoo.blog.fc2.com/blog-entry-143.html
    この方法で起動引数を読みとってそのまま渡して起動する感じですね。

    キャンセル

  • 2019/07/11 16:39

    ありがとうございます
    SetCompatibleTextRenderingDefaultについては、Formの呼び出し後に行っていたので起動開始直後に該当のアセンブリを起動するように変更してみます。
    起動引数の件についてもその方向でアプローチしてみます。
    また、UIAutomationのほうでSPY++を導入せずとも自作アプリでDataGridViewなどのAutomationIDを取得できそう(該当アプリ側でDataGridViewに表示をさせる手順がかなり大変なので後日テスト)なので一旦、ベストアンサーを付けます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.83%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • トップ
  • C#に関する質問
  • 他のアプリケーションのFormに実装されたDataGridViewのDataGridViewCellの値をIAccesibleオブジェクトから取得したい