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

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

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

Win32 API

Win32 APIはMicrosoft Windowsの32bitプロセッサのOSで動作するAPIです。

Q&A

解決済

2回答

2551閲覧

既存のアプリでDataGridViewなどを用いて表示されている内容を取得したい

退会済みユーザー

退会済みユーザー

総合スコア0

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

Win32 API

Win32 APIはMicrosoft Windowsの32bitプロセッサのOSで動作するAPIです。

0グッド

2クリップ

投稿2019/06/12 05:24

編集2019/06/19 01:41

よろしくお願いいたします。

解決コード

まだ途中だけど

c#

1using System.Threading; 2using System.Reflection; 3 4public partial class Form1 : Form 5{ 6 ThreadUITarget m_uiAutomationTargetThread; 7 DataGridView m_uiAutoTargetDGV; 8 9 public Form1() 10 { 11 InitializeComponent(); 12 } 13 14 private void Form1_Load(object sender, EventArgs e) 15 { 16 // Assembly.EntryPoint.Invoke()するとAssembly.Load()したやつにスレッドを占有されちゃうのでサブスレッドを生成 17 new Thread(new ThreadStart( RunApp)); 18 } 19 20 private void RunApp() 21 { 22 // このプログラムのプロジェクトからexeファイルを参照したtargetAppって名前のWindows Formアプリを立ち上げる 23 Assembly assemblyTargetApp = Assembly.Load("targetApp"); 24 assemblyTargetApp.EntryPoint.Invoke(null, new object[0]); 25 } 26 27 private void button1_Click(object sender, EventArgs e) 28 { 29 FormCollection fc = Application.OpenForms; 30 // FormCollectionには自分も含まれちゃうので外部操作を行いたいアプリの親フォームを捜してあげる 31 Form targetForm = null; 32 foreach(Form f in fc) 33 { 34 // 外部操作を行いたいアプリのFormのTextプロパティに"targetApp"が登録されてる前提 35 if (f.Text == "targetApp") 36 { 37 targetForm = f; 38 break; 39 } 40 } 41 // 必ず見つかる(確信) 42 foreach (Control c in targetForm.Controls) 43 { 44 if (c is DataGridView) 45 { 46 string msg; 47 DataGridView dgv = (DataGridView)c; 48 foreach( DataGridViewRow r in dgv.Rows) 49 foreach( DataGridViewCell c in r.cells) 50 msg += c.Value.ToString() + "\n"; // DataGridViewはDataGridViewCells.Value==nullなセルが発生することがあるけどここでは無視 51 52 Messagebox.Show(msg); 53 } 54 else if(c is Button) 55 { 56 c.Invoke(b.PerformClick.GetType, EventArgs.Empty); // まだ動いてないけどこれもやりたいので一応 57 } 58 } 59} 60

ToDo:
Application.Idleを使ってコントロールをメンバに保持
ButtonやMenuStripを操作できるように調査

前提・実現したいこと

C#を用いて作成したアプリケーションから、既存のアプリでDataGridViewを用いて表示されている内容を取得したい。

既存のアプリのButtonSendMessageでクリックしたり、TextBoxの内容を取得したりするアプリケーションを開発中です。
言い方を変えればウィンドウハンドル頼りで他アプリを乗っ取る自動実験用アプリのようなものを実運用で使おうとしています。

既存のアプリはC#をベースにサードパーティのActiveXやOCXを子ダイアログにちりばめたマルチダイアログなアプリです。

既存のアプリの操作や情報取得にはFindWindowEx()GetWindowText()などWinAPIを利用しています。

その既存のアプリの中で(おそらく)DataGridViewコントロールで表示をしている部分があったのでDataGridViewCellの内容を取得しなくてはならなくなりました。
ところが仮に作ってみたテスト用アプリをMicrosft Spy++で見てみたところ、
DataGridViewコントロールには子ウィンドウがなく、DataGridViewCellの内容を覗き見る方法に行き詰ってしまいました。

Webにもこれといった情報が見つけられず、途方にくれているような状態です。
こういう方針でなら除き見る方法があるよ、などの情報をご教授いただきたいです。
投げっぱなしな質問ですが皆さんのご経験から問題解決のヒントを分けて頂けたら大変助かります。

今分かっていること

テスト用に作成したプログラム上のDataGridViewをSPI++で見たときのクラス名

WindowsForms10.Window.8.app.0.141b42a_r14_ad1

既存アプリのDataGridViewらしきコントロールのクラス名

WindowsForms10.Window.8.app.0.141b42a_r14_ad1
  • DataGridViewは.NET独自の描画でありWin32 APIが役に立たない。

  • 社外品のアプリであり直接改造ができない。

  • すでにDataGridView以外の代理操作はウィンドウハンドラで作ってありあまり変更したくない。

  • 運用上の開発案件であり、(残念ながら)お客も(私も)この方法でなんら問題がないものと思い込んでいるため(嫌な予感はするが)既存ソフトの上から覆いかぶさって人間の操作を自動化する

###行動方針

  • 既存のアプリを参照により開発アプリに取り込んで操作する

課題:DataGridView以外の操作はウィンドウハンドラ経由で行っていたが、参照を利用して開発アプリのプロセスに取り込むため全て変更する必要がある。
利点:既存アプリを乗っ取る形になるためウィンドウハンドラを利用する方法より色々手出しできるようになる?

  • Active Accessibilityを利用する

課題:(往々にあることだけど)肝心の関数の引数がことごとく理解不能。
利点:ほかプロセスから手出しするため今までどおりの方針を維持できる。

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

  • Windows7 (64bit)
  • VisualStudio 2015 (SP1)
  • C#

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

takabosoft

2019/06/12 07:18

動作チェックの自動化でもやっているんでしょうか? たぶん直にアクセスする方法は無いんだろうなとは思いますが、もしコピペ機能がそのDataGridViewにあるのでしたら、フォーカスを当てて全選択+コピーでクリップボードの値を見れば良さそうな気はしました。
退会済みユーザー

退会済みユーザー

2019/06/12 07:48

hihijijiさん。 既存のアプリは社外品なので作り直すことは出来ません。 ManagedSpyというものがあるのははじめて知りました。ありがとうございます。
退会済みユーザー

退会済みユーザー

2019/06/12 07:57

takabosoftさん。 実はテストではなく現場で動かすソフトなのですがお客様からの指示でもあり、ほかにどうしようも無いです。 デフォルト設定で配置した場合は全選択からのコピーでtab区切りのデータが取れるようです。既存ソフト側でもいけそうならこの方法を採用します。
guest

回答2

0

ベストアンサー

実行可能なアセンブリも参照できます。
参照した上で Main メソッドを取得して呼んでみてください。
プライベートなメンバーにアクセスするにはリフレクションが必要です。

追記

ConsoleApp1.exe を ConsoleApp2.exe と同じディレクトリに置き、次のコードを実行することで ConsoleApp1.exe の Main メソッドを呼び出せます。

C#

1using System; 2using System.Reflection; 3 4namespace ConsoleApp3 5{ 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 var assembly = Assembly.Load("ConsoleApp1"); 11 assembly.EntryPoint.Invoke(null, new object[] { new string[] { assembly.Location } }); 12 Console.ReadKey(); 13 } 14 } 15}

コードが間違っていたので修正しました。

投稿2019/06/12 08:14

編集2019/06/17 01:58
Zuishin

総合スコア28660

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2019/06/17 01:25 編集

ありがとうございます。 最初のところから躓いてしまったので質問させてください。 まず、リフレクションは オブジェクトを取得 → 型を取得 → FieldInfoを取得 → GetValueで対象メンバを取得 → キャストしてDataGridView型にしてセルを指定し参照 という流れを想定していると考えたのですが、それだとMainメソッドを取得という提案と食い違ってしまいます。 Formアプリを生成したときVisualStudioが自動生成してくれるMainメソッドとはProgram.Main()のことだと考えてはいますがProgramクラスを参照する方法が検討もついていない状態です。 手間のかかるお願いしてしまい恐縮ですがもう少し詳細な手順を教えていただけませんか?
Zuishin

2019/06/17 01:41

追記しました。
退会済みユーザー

退会済みユーザー

2019/06/17 02:59

ありがとうございます。 ご提示いただいたコードをFormアプリに対して試したところ、「パラメーター カウントが一致しません」などと怒られうまくいかなかったため、一応経過としてご報告いたします。 英語が分かる訳ではないのですが以下のURL先 https://social.msdn.microsoft.com/Forums/vstudio/en-US/27495079-fce4-4190-8b7a-31ad58603759/troubles-with-invoking-entrypoint-of-assembly?forum=netfxbcl を見る限り、Windows Formアプリに関してはMain()へ引数を与える辺りで問題があるようで実行不能であるという風に読めました。 Main()を呼び出すことが可能である前提でのお話であるようなので、どうやら何かしらアレンジしてみる必要がありそうです。取っ掛かりは示していただけたのでいろいろ試してみます
Zuishin

2019/06/17 03:05

パラメーターカウントというのは、Main 関数にわたる引数の数で、エントリポイントが Main(string[] args) ならば実行できることを確認しています。 そうでない場合、assembly.EntryPoint で返される MethodInfo を調べて引数を確認すればできるはずです。
Zuishin

2019/06/17 03:09

たとえば Main(string[] args) の場合、var parameters = assembly.EntryPoint.GetParameters() とすると parameters.Length == 1 となり、parameters[0].ParameterType == typeof(System.String[]) となります。
退会済みユーザー

退会済みユーザー

2019/06/17 04:38

ありがとうございます。 おかげさまで Assembly.Load("FormAppli1")EntryPoint.Invoke(null, new object[0]); としてAssemblyに対象のアプリを捉えることができそうなことが確認できました。 ただ、Invoke()したところで対象アプリを閉じるまでプログラムは停止してしまうので肝要のDataGridViewからの情報取得はまだできていません。 別スレッドからDataGridViewの参照を行うことが前提になるのでしょうか?
Zuishin

2019/06/17 04:42

そうなりますね。ただし別スレッドからコントロールを操作することはできないので、操作する瞬間は Control.Invoke メソッドでそのコントロールの作られたスレッドに入る必要があります。
atata0319

2019/06/17 04:48 編集

それでは手間が増えそうです。起動前にApplication.Idleイベントを登録して、起動後にイベントハンドラ内でApplication.OpenFormsで該当フォームを検索して後は子ウィンドウをControlsから探す感じですね。
Zuishin

2019/06/17 04:52

そう言えば Application が共有されますね。うっかりしていました。
退会済みユーザー

退会済みユーザー

2019/06/17 09:15

ご提示ありがとうございます。 Idleイベントならば断続的にデータ収集を行えそうなので試してみようと思います。 そのためにまずイベントのフックから行おうと思います。 [メインスレッド] --(呼び出し)--> [別スレッドでAssemblyで既存アプリを起動するクラス] のような関係性で、[メインスレッド]内でイベントのフックを行う作戦です。 参考: https://docs.microsoft.com/ja-jp/dotnet/framework/reflection-and-codedom/how-to-hook-up-a-delegate-using-reflection ご報告まで。 なおOpenForms()からDataGridViewコントロールを探し出す方法については今のところ無策です。
Zuishin

2019/06/17 09:18

Control には Controls プロパティがあり、そこに子コントロールが格納されています。フォームからこのツリーをたどって型が DataGridView の物を探せばみつかるのではないかと思います。
退会済みユーザー

退会済みユーザー

2019/06/18 08:59 編集

ありがとうございます。 Controlsプロパティからコントロールの型やNameなどの重要な情報が取得でき、DataGridViewの内容を取得することもできています。 現状Application.Idleイベントハンドラあたりの実装が済んでいません。 今のタイミングで重ねてお聞きしたいことがあります。 Assembly.Load()を使用して呼び出した既存アプリのControlsから目当てのDataGridViewを探し当てて、開発アプリ側でDataGridView型関数に情報を保持してもDataGridViewの表示情報を取得できます。(=演算子でコピーじゃなくて参照してる?) あえてApplication.Idleイベントハンドラを利用することをご提示いただいたのは、既存アプリ側がDataGridViewを更新処理などで他スレッドからの命令をブロックする処理を持っている場合への安全策なのでしょうか?
Zuishin

2019/06/18 09:02

DataGridView が保持しているデータをどのように取得しているかがわからないためです。例えば何かボタンを押したタイミングでデータを読み込む仕様なら、ボタンを押さなければいけません。この辺りはそのアプリの仕様次第なので、もしかしたらメッセージループを回す必要はなかったかもしれません。
atata0319

2019/06/18 16:04

例えば Idle ハンドラからボタンをクリックするとボタンクリックの処理が完了したらまた Idle ハンドラが呼び出されます。RPA 等の自動制御用ソフトウェアで実装されている外部スクリプトから UI 操作を同期させるようなやり方に非常に向いています。逆に同期させる必要がない場合、Idle ハンドラで処理する必要はあんまりありません。
退会済みユーザー

退会済みユーザー

2019/06/19 01:23

ありがとうございます。 既存アプリはボタンを押すことで内容が更新されるのでIdleイベントは都合がよさそうです。 有益な情報大変助かります。
guest

0

Zuishin さんの回答を支持しますが、以下のような手法もあります。

1.Active Accessibility

商用 RPA 製品で .NET のフォームアプリケーションの自動化をサポートしているものはたいてい Accessibility オブジェクト経由でコントロールにアクセスしています。API を直接呼び出しているなら、DataGridView のウィンドウハンドルに対して AccessibleObjectFromWindow を呼び出すことにより、AccessibleObject を取得できます。多分、.NET でも直接取得できるんじゃないかと思いますが、調べていません。

2.コントロールに外部プロセスから直接アクセスする

1で取得した Accessibility オブジェクトからコントロールの参照を直接取得することができるものがあります。と言うか、たいていのコントロールはサポートしているんじゃないかと思いますが、検証している時間がないので情報のみの提供です。

投稿2019/06/12 18:02

atata0319

総合スコア881

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2019/06/17 03:03

ありがとうございます。 経過報告として DllImportしているコードを見つけてきてコピペするも、引数がどこからどう用意すれば良いのか分からずAccessibility()が使えないという状態です。 他の回答者様に提示いただいた方法もなかなか上手くいっていないので平行して調査していきます
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問