C# UIAutomation入門|Windowsアプリの自動操作・要素取得・クリック実装までサンプルコードで解説

はじめに

C#でWindowsアプリを自動操作したい場合、標準的な選択肢の一つが**UIAutomation(UI Automation)**です。

UIAutomationを使うと、画面上のボタン、テキストボックス、チェックボックス、リスト、メニューなどのUI要素をC#コードから取得し、クリック、入力、値取得といった操作を自動化できます。

たとえば、次のような処理をC#で実装できます。

C#
// ボタンを取得してクリックする
var button = window.FindFirst(
TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "OK"));

var invokePattern = button.GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;
invokePattern.Invoke();

C# UIAutomationは、Windowsアプリの業務自動化、UIテスト、簡易RPAの実装に向いています。特に、社内システムや古いWindowsデスクトップアプリを自動操作したい場合に便利です。

この記事では、C# UIAutomationの基本概念、開発環境の準備、ウィンドウ取得、UI要素取得、クリック・入力・値取得の実装方法まで、サンプルコードを交えながら解説します。

1. C# UIAutomationとは?Windowsアプリを自動操作できる仕組み

C# UIAutomationとは、Microsoftが提供しているWindows向けのアクセシビリティAPIであるUI AutomationをC#から利用する方法です。

本来UI Automationは、スクリーンリーダーなどの支援技術がアプリケーションのUI情報を取得するために使われます。しかし、C#から利用することで、WindowsアプリのUI要素を検索し、自動操作することもできます。

対象アプリの内部実装を直接操作するのではなく、Windowsが公開しているUI情報を通じて、ボタンやテキストボックスなどを認識します。そのため、ソースコードを持っていない外部アプリでも、UIAutomationに対応していれば操作できます。

1-1. UIAutomationでできること:要素取得・クリック・入力・値取得

C# UIAutomationでは、主に次のような操作ができます。

UI要素の取得では、ウィンドウ、ボタン、テキストボックス、ラベル、チェックボックス、リスト項目などを検索できます。検索条件には、Name、AutomationId、ClassName、ControlTypeなどを使います。

クリック操作では、ボタンやメニュー項目に対してInvokePatternを使い、ユーザーがクリックしたのと同じような操作を実行できます。

文字入力では、テキストボックスに対してValuePatternを使い、値を設定できます。対応していない場合は、フォーカスを当ててSendKeysで入力する方法もあります。

値取得では、テキストボックスの入力値やラベルの表示文字列などを取得できます。業務アプリの画面に表示された結果を取得して、ログ出力や後続処理に利用できます。

1-2. UIAutomationが向いている用途:業務自動化・UIテスト・簡易RPA

C# UIAutomationは、次のような用途に向いています。

業務自動化では、毎日同じWindowsアプリにログインしてデータを入力する作業や、複数画面を開いて情報を転記する作業を自動化できます。

UIテストでは、Windowsデスクトップアプリのボタン操作や画面遷移を自動実行し、期待したUI要素が表示されるかを確認できます。

簡易RPAでは、高機能なRPAツールを導入するほどではない小規模な定型作業を、C#のコンソールアプリやWindowsアプリとして実装できます。

ただし、UIAutomationは画面構造に依存するため、対象アプリのUIが変更されると動作しなくなることがあります。安定運用するには、AutomationIdを優先して要素を特定し、待機処理や例外処理を丁寧に実装することが重要です。

1-3. SeleniumやWinAppDriver、Power Automateとの違い

C# UIAutomationと混同されやすい技術に、Selenium、WinAppDriver、Power Automateがあります。

Seleniumは主にWebブラウザの自動操作に使います。ChromeやEdgeなどのブラウザ上で動くWebアプリを操作する場合は、UIAutomationよりSeleniumの方が適しています。

WinAppDriverは、WindowsアプリをWebDriverプロトコルで操作するためのツールです。Appiumなどと組み合わせてUIテストを行う場合に使われます。テスト自動化の仕組みとしては強力ですが、環境構築がやや重くなることがあります。

Power Automateは、GUIベースで業務フローを作成できる自動化ツールです。プログラミングに慣れていない人でも扱いやすい一方、細かい制御や独自ロジックをC#で自由に書きたい場合はUIAutomationの方が向いています。

C# UIAutomationは、C#コードだけでWindowsアプリの自動操作を組み込みたい場合に便利です。既存のC#アプリやバッチ処理に組み込みやすい点が大きな特徴です。

1-4. C# UIAutomationで操作できるアプリと操作できないケース

C# UIAutomationで操作しやすいのは、標準的なWindows UIコントロールを使って作られたアプリです。たとえば、WPF、WinForms、一部のUWP、標準的なダイアログやメモ帳などは比較的扱いやすい対象です。

一方で、次のようなケースでは操作が難しくなることがあります。

独自描画のアプリでは、画面上にボタンのように見えていても、UIAutomation上は単なる画像やカスタム領域として扱われることがあります。この場合、ボタン要素を取得できないことがあります。

ゲームや高頻度描画のアプリは、DirectXなどで描画されていることが多く、UIAutomationで個別の要素を取得できない場合があります。

管理者権限で実行されているアプリを、通常権限の自動化プログラムから操作しようとすると、要素取得や操作に失敗することがあります。この場合、自動化側も管理者権限で実行する必要があります。

また、対象アプリがUIAutomationに十分な情報を公開していない場合、AutomationIdやNameが取得できないこともあります。その場合は、ControlTypeやClassNameを組み合わせて検索条件を工夫します。

2. C# UIAutomationを使う前に知っておきたい基本用語

C# UIAutomationを使うには、いくつかの基本用語を理解しておく必要があります。

特に重要なのは、AutomationElement、UI Automation Tree、TreeScope、プロパティ、Control Patternです。これらを理解すると、ウィンドウやボタンをどのように取得し、どう操作するのかが分かりやすくなります。

2-1. AutomationElementとは

AutomationElementは、UIAutomationで扱うUI要素を表すクラスです。

ウィンドウ、ボタン、テキストボックス、ラベル、チェックボックス、メニュー項目など、画面上の要素は基本的にAutomationElementとして取得します。

たとえば、対象ウィンドウを取得するときも、ボタンを取得するときも、戻り値はAutomationElementです。

C#
AutomationElement element = AutomationElement.RootElement;

取得したAutomationElementからは、Name、AutomationId、ClassName、ControlTypeなどの情報を確認できます。

C#
Console.WriteLine(element.Current.Name);
Console.WriteLine(element.Current.AutomationId);
Console.WriteLine(element.Current.ClassName);
Console.WriteLine(element.Current.ControlType.ProgrammaticName);

UIAutomationでは、まずAutomationElementを取得し、その要素が対応しているControl Patternを使って操作する、という流れが基本です。

2-2. UI Automation Treeとは

UI Automation Treeとは、Windows上のUI要素をツリー構造で表したものです。

一番上にはデスクトップ全体を表すAutomationElement.RootElementがあります。その下にアプリのウィンドウがあり、さらにその中にボタン、テキストボックス、メニュー、リストなどの要素があります。

イメージとしては、次のような階層です。

Desktop
├─ Window: メモ帳
│ ├─ MenuBar
│ ├─ Edit
│ └─ Button
└─ Window: 電卓
├─ Button: 1
├─ Button: 2
└─ Button: =

UI要素を検索するときは、このツリーのどの範囲を探すかを指定します。対象ウィンドウの中だけを探すのか、子要素だけを探すのか、子孫要素すべてを探すのかによって、検索速度や安定性が変わります。

2-3. TreeScopeの使い方:Children・Descendants・Element

TreeScopeは、UI要素を検索する範囲を指定する列挙型です。

よく使うのは、TreeScope.ChildrenTreeScope.DescendantsTreeScope.Elementです。

TreeScope.Childrenは、指定した要素の直下の子要素だけを検索します。検索範囲が狭いため高速ですが、階層が深い要素は見つかりません。

C#
var child = window.FindFirst(TreeScope.Children, condition);

TreeScope.Descendantsは、指定した要素の子孫要素すべてを検索します。階層が深い要素も見つけやすいですが、検索範囲が広くなるため処理が重くなることがあります。

C#
var descendant = window.FindFirst(TreeScope.Descendants, condition);

TreeScope.Elementは、指定した要素自身を対象にします。取得済みの要素が特定の条件を満たすか確認したい場合などに使います。

C#
var self = window.FindFirst(TreeScope.Element, condition);

実装では、まず対象ウィンドウを取得し、そのウィンドウ配下をDescendantsで探すことが多いです。ただし、画面が大きいアプリでは検索が遅くなるため、できるだけ検索範囲を狭めることが大切です。

2-4. NameProperty・AutomationIdProperty・ClassNamePropertyの違い

UIAutomationで要素を検索するときは、プロパティを条件にします。代表的なプロパティが、NamePropertyAutomationIdPropertyClassNamePropertyです。

NamePropertyは、画面に表示される文字列やアクセシビリティ名を表します。ボタンの「OK」や「キャンセル」、ラベルの文字列などが入ることがあります。

C#
new PropertyCondition(AutomationElement.NameProperty, "OK")

ただし、Nameは表示言語や画面状態によって変わることがあります。日本語版と英語版で値が違う場合もあるため、安定性には注意が必要です。

AutomationIdPropertyは、開発者がUI要素に設定する識別子です。表示名が変わってもAutomationIdは変わらないことが多いため、要素特定では最も優先したいプロパティです。

C#
new PropertyCondition(AutomationElement.AutomationIdProperty, "btnOK")

ただし、対象アプリによってはAutomationIdが設定されていない場合があります。

ClassNamePropertyは、UI要素のクラス名です。WinFormsやWPF、標準コントロールでは有効な手がかりになることがあります。

C#
new PropertyCondition(AutomationElement.ClassNameProperty, "Edit")

実務では、AutomationIdを優先し、必要に応じてName、ControlType、ClassNameを組み合わせます。

2-5. InvokePattern・ValuePattern・SelectionPatternなどのControl Pattern

Control Patternとは、UI要素が提供する操作機能を表す仕組みです。

ボタンにはクリック操作を表すInvokePattern、テキストボックスには値の設定や取得を表すValuePattern、チェックボックスにはオン・オフ操作を表すTogglePattern、リストやコンボボックスには選択操作を表すSelectionPatternSelectionItemPatternがあります。

ボタンをクリックする場合は、次のようにInvokePatternを取得してInvoke()を呼び出します。

C#
var pattern = button.GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;
pattern.Invoke();

テキストボックスに入力する場合は、ValuePatternを取得してSetValue()を呼び出します。

C#
var pattern = textBox.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;
pattern.SetValue("入力テキスト");

すべてのUI要素がすべてのPatternに対応しているわけではありません。ボタンにValuePatternは使えませんし、独自コントロールではInvokePatternに対応していないこともあります。

そのため、実装ではTryGetCurrentPatternを使い、対象要素がPatternに対応しているか確認してから操作するのが安全です。

C#
if (button.TryGetCurrentPattern(InvokePattern.Pattern, out object patternObj))
{
var invoke = patternObj as InvokePattern;
invoke.Invoke();
}

3. C# UIAutomationの開発環境を準備する

C# UIAutomationを始めるには、Visual StudioでC#プロジェクトを作成し、UIAutomation関連の参照を追加します。

ここでは、コンソールアプリを例に説明します。Windowsアプリの自動操作を試すだけであれば、まずはコンソールアプリで十分です。

3-1. Visual StudioでC#プロジェクトを作成する

Visual Studioを起動し、新しいプロジェクトを作成します。

プロジェクトの種類は、C#のコンソールアプリを選択します。対象フレームワークは、利用環境に合わせて選びます。

UIAutomationを使う場合、Windows環境で実行する必要があります。クロスプラットフォームの.NETアプリとして作成しても、UIAutomation自体はWindows UIを操作するための仕組みなので、LinuxやmacOSでは利用できません。

まずは次のような構成で始めると分かりやすいです。

プロジェクト種類:コンソールアプリ
言語:C#
実行環境:Windows
用途:WindowsアプリのUI操作テスト

3-2. UIAutomationClientとUIAutomationTypesを参照に追加する

System.Windows.Automation名前空間を使うには、UIAutomation関連の参照が必要です。

.NET Frameworkのプロジェクトでは、参照設定から次のアセンブリを追加します。

UIAutomationClient
UIAutomationTypes

Visual Studioで追加する場合は、プロジェクトを右クリックし、「参照の追加」からアセンブリを探して追加します。

SDK形式のプロジェクトで参照が見つからない場合は、Windowsデスクトップ向けの構成になっているか、対象フレームワークが適切かを確認してください。

3-3. using System.Windows.Automationを追加する

参照を追加したら、C#ファイルの先頭に次のusingを追加します。

C#
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Automation;

これで、AutomationElementPropertyConditionTreeScopeInvokePatternValuePatternなどのクラスを使えるようになります。

最小構成のコードは次のようになります。

C#
using System;
using System.Windows.Automation;

class Program
{
static void Main()
{
AutomationElement root = AutomationElement.RootElement;
Console.WriteLine(root.Current.Name);
}
}

3-4. 操作対象アプリを管理者権限で実行する場合の注意点

対象アプリが管理者権限で実行されている場合、通常権限で起動したC#プログラムからは操作できないことがあります。

これは、権限レベルが異なるプロセス間でUI操作が制限されるためです。

たとえば、業務アプリを「管理者として実行」している場合、自動化プログラムも同じく管理者権限で実行する必要があります。

対処法は次のとおりです。

まず、Visual Studio自体を管理者として起動します。その状態でデバッグ実行すれば、自動化プログラムも管理者権限で起動されます。

または、作成したexeファイルを右クリックして「管理者として実行」します。

ただし、常に管理者権限で実行する設計はセキュリティ上の注意が必要です。業務環境で使う場合は、必要最小限の権限で動作するように設計しましょう。

3-5. Inspect.exeでUI要素の情報を確認する方法

C# UIAutomationで安定して要素を取得するには、対象アプリのUI要素がどのようなプロパティを持っているか確認する必要があります。

その際に便利なのがInspect.exeです。

Inspect.exeを使うと、画面上のUI要素にマウスを合わせるだけで、Name、AutomationId、ClassName、ControlType、対応しているControl Patternなどを確認できます。

確認したい項目は次のとおりです。

Name
AutomationId
ClassName
ControlType
IsEnabled
IsOffscreen
Supported Patterns

たとえば、ボタンを取得したい場合は、そのボタンにAutomationIdが設定されているかを確認します。AutomationIdがあれば、C#コードではAutomationIdPropertyを使って検索できます。

AutomationIdがない場合は、NameやControlTypeを使って検索します。

Inspect.exeで事前にUI構造を確認しておくと、要素がnullになる原因を特定しやすくなります。

4. C# UIAutomationの基本実装フロー

C# UIAutomationの基本的な実装フローは、次の流れです。

まず、操作対象アプリのウィンドウを取得します。次に、そのウィンドウ配下からボタンやテキストボックスなどのUI要素を検索します。取得した要素に対してControl Patternを使い、クリックや入力などの操作を実行します。

この流れを理解しておくと、メモ帳、電卓、業務アプリなど、さまざまなWindowsアプリに応用できます。

4-1. 操作対象アプリのウィンドウを取得する

最初に行うのは、操作対象アプリのメインウィンドウを取得することです。

起動済みアプリを探す場合は、AutomationElement.RootElementからウィンドウを検索します。

C#
var condition = new PropertyCondition(
AutomationElement.NameProperty,
"無題 - メモ帳");

var window = AutomationElement.RootElement.FindFirst(
TreeScope.Children,
condition);

RootElement直下には、デスクトップ上に表示されているトップレベルウィンドウが並びます。そのため、ウィンドウを探す場合はTreeScope.Childrenを使うのが基本です。

4-2. AutomationElement.RootElementから要素を検索する

AutomationElement.RootElementは、UIAutomation Treeのルート要素です。

ここからウィンドウを検索し、さらに取得したウィンドウの中から子要素を検索します。

C#
AutomationElement root = AutomationElement.RootElement;

AutomationElement window = root.FindFirst(
TreeScope.Children,
new PropertyCondition(AutomationElement.NameProperty, "対象ウィンドウ名"));

ウィンドウを取得できたら、その中のボタンやテキストボックスを検索します。

C#
AutomationElement button = window.FindFirst(
TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "OK"));

RootElementからいきなりすべての要素をDescendantsで検索すると、検索範囲が広くなりすぎます。まずウィンドウを取得し、そのウィンドウ配下を検索する方が安定します。

4-3. PropertyConditionで条件を指定する

PropertyConditionは、UI要素のプロパティに対して検索条件を指定するためのクラスです。

AutomationIdで検索する場合は、次のように書きます。

C#
var condition = new PropertyCondition(
AutomationElement.AutomationIdProperty,
"textBoxUserName");

Nameで検索する場合は、次のように書きます。

C#
var condition = new PropertyCondition(
AutomationElement.NameProperty,
"ログイン");

ControlTypeで検索する場合は、次のように書きます。

C#
var condition = new PropertyCondition(
AutomationElement.ControlTypeProperty,
ControlType.Button);

PropertyConditionは単体でも使えますが、複数条件を組み合わせることで、より正確に要素を特定できます。

4-4. FindFirstとFindAllの使い分け

UI要素を検索するメソッドには、FindFirstFindAllがあります。

FindFirstは、条件に一致する最初の要素を1つだけ取得します。特定のボタンやテキストボックスを1つ取得したい場合に使います。

C#
var button = window.FindFirst(
TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "OK"));

FindAllは、条件に一致する要素をすべて取得します。同じ種類のボタンを一覧表示したい場合や、画面内のテキスト要素をすべて確認したい場合に使います。

C#
var buttons = window.FindAll(
TreeScope.Descendants,
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button));

foreach (AutomationElement button in buttons)
{
Console.WriteLine(button.Current.Name);
}

要素が1つだけ必要な場合はFindFirst、候補を確認したい場合や一覧処理をしたい場合はFindAllを使います。

4-5. 取得した要素のName・AutomationId・ControlTypeを確認する

要素を取得できたら、まずプロパティを出力して確認します。

C#
static void PrintElementInfo(AutomationElement element)
{
if (element == null)
{
Console.WriteLine("要素が見つかりません。");
return;
}

Console.WriteLine($"Name: {element.Current.Name}");
Console.WriteLine($"AutomationId: {element.Current.AutomationId}");
Console.WriteLine($"ClassName: {element.Current.ClassName}");
Console.WriteLine($"ControlType: {element.Current.ControlType.ProgrammaticName}");
}

UIAutomationでは、取得したつもりの要素が実は別の要素だった、ということがよくあります。

そのため、操作前にName、AutomationId、ClassName、ControlTypeをログ出力すると、トラブルシューティングがしやすくなります。

5. サンプルコードで学ぶ:Windowsアプリのウィンドウを取得する

ここからは、実際のサンプルコードを使ってC# UIAutomationの実装方法を見ていきます。

最初に、Windowsアプリのウィンドウを取得する方法を解説します。ウィンドウ取得は、すべてのUI操作の出発点です。

5-1. アプリを起動してProcessから取得する方法

自動化対象のアプリをC#コードから起動する場合は、Process.Startを使います。

起動したプロセスのメインウィンドウハンドルから、AutomationElement.FromHandleでウィンドウを取得できます。

C#
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Automation;

class Program
{
static void Main()
{
Process process = Process.Start("notepad.exe");

process.WaitForInputIdle();

AutomationElement window = AutomationElement.FromHandle(process.MainWindowHandle);

Console.WriteLine(window.Current.Name);
}
}

WaitForInputIdle()は、アプリが入力待ち状態になるまで待つためのメソッドです。起動直後にウィンドウを取得しようとすると、まだウィンドウが作成されていない場合があるため、待機処理を入れることが重要です。

ただし、アプリによってはMainWindowHandleがすぐに取得できないこともあります。その場合は、後述する条件付き待機を使います。

5-2. 既に起動しているアプリをウィンドウ名で取得する方法

既に起動しているアプリを操作する場合は、ウィンドウ名を条件にして検索します。

C#
AutomationElement window = AutomationElement.RootElement.FindFirst(
TreeScope.Children,
new PropertyCondition(AutomationElement.NameProperty, "無題 - メモ帳"));

if (window == null)
{
Console.WriteLine("ウィンドウが見つかりません。");
}
else
{
Console.WriteLine($"取得成功: {window.Current.Name}");
}

トップレベルウィンドウを探す場合は、RootElementの直下を検索するため、TreeScope.Childrenを使います。

ウィンドウ名はアプリの状態によって変わることがあります。たとえばメモ帳では、ファイル名を保存すると「無題 - メモ帳」ではなく「sample.txt - メモ帳」のように変わります。

そのため、完全一致で見つからない場合は、FindAllでウィンドウ一覧を出力して確認するとよいです。

5-3. ウィンドウが表示されるまで待機する処理

UIAutomationでは、タイミング問題が非常によく発生します。

アプリを起動した直後、画面遷移の直後、ダイアログ表示の直後などは、目的の要素がまだ生成されていないことがあります。

固定でThread.Sleep(3000)のように待つ方法もありますが、安定運用を考えると、条件を満たすまで繰り返し検索する待機処理がおすすめです。

C#
static AutomationElement WaitForElement(
AutomationElement parent,
TreeScope scope,
Condition condition,
int timeoutMilliseconds = 10000)
{
var stopwatch = Stopwatch.StartNew();

while (stopwatch.ElapsedMilliseconds < timeoutMilliseconds)
{
var element = parent.FindFirst(scope, condition);

if (element != null)
{
return element;
}

Thread.Sleep(200);
}

return null;
}

使用例は次のとおりです。

C#
var window = WaitForElement(
AutomationElement.RootElement,
TreeScope.Children,
new PropertyCondition(AutomationElement.NameProperty, "無題 - メモ帳"),
10000);

if (window == null)
{
Console.WriteLine("タイムアウトしました。");
}
else
{
Console.WriteLine("ウィンドウを取得しました。");
}

この方法なら、要素が早く表示された場合はすぐに次の処理へ進めます。固定待機よりも効率的で、環境差にも強くなります。

5-4. 複数ウィンドウがある場合の検索条件の絞り込み

同じアプリを複数起動している場合、Nameだけでは目的のウィンドウを特定できないことがあります。

その場合は、ProcessId、ClassName、ControlTypeなどを組み合わせて絞り込みます。

C#
var condition = new AndCondition(
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window),
new PropertyCondition(AutomationElement.NameProperty, "無題 - メモ帳"));

var window = AutomationElement.RootElement.FindFirst(
TreeScope.Children,
condition);

プロセスIDで絞り込む場合は、ProcessIdPropertyを使います。

C#
Process process = Process.Start("notepad.exe");
process.WaitForInputIdle();

var condition = new PropertyCondition(
AutomationElement.ProcessIdProperty,
process.Id);

var window = AutomationElement.RootElement.FindFirst(
TreeScope.Children,
condition);

プロセスIDを使うと、自分で起動したアプリのウィンドウを特定しやすくなります。

5-5. ウィンドウ取得に失敗する原因と対処法

ウィンドウ取得に失敗する主な原因は、次のとおりです。

アプリがまだ起動途中の場合、ウィンドウが作成されていないため取得できません。条件付き待機を使って、ウィンドウが表示されるまで待ちましょう。

ウィンドウ名が想定と違う場合、NamePropertyで検索しても見つかりません。FindAllで現在表示されているウィンドウ一覧を出力し、正しいNameを確認します。

対象アプリが管理者権限で実行されている場合、通常権限の自動化プログラムから取得できないことがあります。自動化プログラムも管理者権限で実行してください。

ウィンドウが最小化されている場合や非表示の場合、要素が見つかりにくくなることがあります。必要に応じて対象アプリを前面に表示します。

ウィンドウ取得に失敗したときは、いきなりクリック処理を疑うのではなく、まずトップレベルウィンドウを正しく取得できているか確認することが大切です。

6. サンプルコードで学ぶ:UI要素を取得する方法

ウィンドウを取得できたら、次はウィンドウ内のUI要素を取得します。

UI要素の取得では、AutomationId、Name、ControlType、ClassNameなどを条件に使います。安定性を重視するなら、まずAutomationIdを確認し、AutomationIdがない場合にNameやControlTypeを使います。

6-1. AutomationIdでボタンやテキストボックスを取得する

AutomationIdは、UI要素を特定するうえで最も安定しやすいプロパティです。

対象アプリのボタンにbtnLoginというAutomationIdが設定されている場合、次のように取得できます。

C#
var loginButton = window.FindFirst(
TreeScope.Descendants,
new PropertyCondition(AutomationElement.AutomationIdProperty, "btnLogin"));

テキストボックスを取得する場合も同じです。

C#
var userNameTextBox = window.FindFirst(
TreeScope.Descendants,
new PropertyCondition(AutomationElement.AutomationIdProperty, "txtUserName"));

AutomationIdは画面に表示される文字ではないため、表示言語やラベル変更の影響を受けにくいという利点があります。

ただし、対象アプリによってはAutomationIdが空の場合があります。その場合は、Inspect.exeで他のプロパティを確認し、NameやControlTypeを使って検索します。

6-2. Nameでラベルやメニュー項目を取得する

Nameは、ボタンの表示文字列、ラベルのテキスト、メニュー項目名などに使われることが多いプロパティです。

たとえば、「保存」ボタンを取得する場合は次のように書きます。

C#
var saveButton = window.FindFirst(
TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "保存"));

メニュー項目を取得する場合も、Nameで検索できます。

C#
var fileMenu = window.FindFirst(
TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "ファイル"));

Nameは人間にとって分かりやすい反面、表示文字列が変わると検索できなくなります。

たとえば、日本語版では「保存」、英語版では「Save」になる可能性があります。多言語対応が必要なアプリでは、Nameだけに依存しない設計が必要です。

6-3. ControlTypeでButton・Edit・Textを取得する

ControlTypeは、UI要素の種類を表します。

ボタンならControlType.Button、テキストボックスならControlType.Edit、ラベルや表示テキストならControlType.Textです。

画面内のボタンをすべて取得する場合は、次のように書きます。

C#
var buttons = window.FindAll(
TreeScope.Descendants,
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button));

foreach (AutomationElement button in buttons)
{
Console.WriteLine(button.Current.Name);
}

テキストボックスを取得する場合は、ControlType.Editを使います。

C#
var edits = window.FindAll(
TreeScope.Descendants,
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));

foreach (AutomationElement edit in edits)
{
Console.WriteLine($"Name: {edit.Current.Name}, AutomationId: {edit.Current.AutomationId}");
}

ControlTypeだけでは候補が複数見つかることが多いため、最終的にはAutomationIdやNameと組み合わせるのがおすすめです。

6-4. AndConditionで複数条件を組み合わせる

複数の条件を組み合わせたい場合は、AndConditionを使います。

たとえば、Nameが「OK」でControlTypeがButtonの要素を取得する場合は、次のように書きます。

C#
var condition = new AndCondition(
new PropertyCondition(AutomationElement.NameProperty, "OK"),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button));

var okButton = window.FindFirst(TreeScope.Descendants, condition);

AutomationIdとControlTypeを組み合わせることもできます。

C#
var condition = new AndCondition(
new PropertyCondition(AutomationElement.AutomationIdProperty, "btnSubmit"),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button));

var submitButton = window.FindFirst(TreeScope.Descendants, condition);

複数条件を使うと、同じNameの要素が複数ある場合でも目的の要素を特定しやすくなります。

6-5. 取得した要素一覧をコンソールに出力する

要素がうまく取得できない場合は、画面内の要素一覧を出力して確認すると原因を見つけやすくなります。

次のコードは、対象ウィンドウ配下の要素を一覧表示するサンプルです。

C#
static void PrintElements(AutomationElement parent)
{
var elements = parent.FindAll(TreeScope.Descendants, Condition.TrueCondition);

foreach (AutomationElement element in elements)
{
try
{
Console.WriteLine(
$"Name=[{element.Current.Name}], " +
$"AutomationId=[{element.Current.AutomationId}], " +
$"ClassName=[{element.Current.ClassName}], " +
$"ControlType=[{element.Current.ControlType.ProgrammaticName}]");
}
catch (ElementNotAvailableException)
{
Console.WriteLine("要素が利用できません。");
}
}
}

使用例は次のとおりです。

C#
PrintElements(window);

一覧を出力すると、Inspect.exeで確認した情報と照らし合わせながら検索条件を決められます。

ただし、Condition.TrueConditionDescendantsを検索すると、要素数が多いアプリでは処理が重くなることがあります。デバッグ時だけ使うようにしましょう。

7. サンプルコードで学ぶ:クリック・入力・値取得を実装する

UI要素を取得できたら、次は実際に操作します。

C# UIAutomationでは、対象要素が対応しているControl Patternを使って操作します。ボタンならInvokePattern、テキストボックスならValuePattern、チェックボックスならTogglePattern、リストやコンボボックスならSelectionPatternSelectionItemPatternを使います。

7-1. InvokePatternでボタンをクリックする

ボタンをクリックするには、InvokePatternを使います。

C#
static void ClickButton(AutomationElement button)
{
if (button == null)
{
throw new ArgumentNullException(nameof(button));
}

if (button.TryGetCurrentPattern(InvokePattern.Pattern, out object patternObj))
{
var invokePattern = patternObj as InvokePattern;
invokePattern.Invoke();
}
else
{
throw new InvalidOperationException("InvokePatternに対応していません。");
}
}

使用例は次のとおりです。

C#
var okButton = window.FindFirst(
TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "OK"));

ClickButton(okButton);

InvokePatternは、クリック可能なボタンやメニュー項目で使えることが多いです。

ただし、見た目はボタンでも独自コントロールの場合はInvokePatternに対応していないことがあります。その場合は、フォーカスを当ててEnterキーを送る、または座標クリックで代替する方法を検討します。

7-2. ValuePatternでテキストボックスに文字を入力する

テキストボックスに文字を入力するには、ValuePatternを使います。

C#
static void SetText(AutomationElement textBox, string value)
{
if (textBox == null)
{
throw new ArgumentNullException(nameof(textBox));
}

if (textBox.TryGetCurrentPattern(ValuePattern.Pattern, out object patternObj))
{
var valuePattern = patternObj as ValuePattern;
valuePattern.SetValue(value);
}
else
{
throw new InvalidOperationException("ValuePatternに対応していません。");
}
}

使用例は次のとおりです。

C#
var textBox = window.FindFirst(
TreeScope.Descendants,
new PropertyCondition(AutomationElement.AutomationIdProperty, "txtUserName"));

SetText(textBox, "山田太郎");

ValuePattern.SetValue()は、対象テキストボックスが編集可能な場合に有効です。

読み取り専用のテキストボックスや、独自実装の入力欄では使えない場合があります。

7-3. ValuePatternで入力値を取得する

テキストボックスの値を取得する場合も、ValuePatternを使います。

C#
static string GetText(AutomationElement textBox)
{
if (textBox == null)
{
throw new ArgumentNullException(nameof(textBox));
}

if (textBox.TryGetCurrentPattern(ValuePattern.Pattern, out object patternObj))
{
var valuePattern = patternObj as ValuePattern;
return valuePattern.Current.Value;
}

throw new InvalidOperationException("ValuePatternに対応していません。");
}

使用例は次のとおりです。

C#
string text = GetText(textBox);
Console.WriteLine($"入力値: {text}");

画面上の入力結果や処理結果を取得できるため、UIテストや業務自動化の確認処理に利用できます。

7-4. TogglePatternでチェックボックスを操作する

チェックボックスのオン・オフを切り替えるには、TogglePatternを使います。

C#
static void ToggleCheckBox(AutomationElement checkBox)
{
if (checkBox == null)
{
throw new ArgumentNullException(nameof(checkBox));
}

if (checkBox.TryGetCurrentPattern(TogglePattern.Pattern, out object patternObj))
{
var togglePattern = patternObj as TogglePattern;
togglePattern.Toggle();
}
else
{
throw new InvalidOperationException("TogglePatternに対応していません。");
}
}

現在の状態を確認することもできます。

C#
static ToggleState GetToggleState(AutomationElement checkBox)
{
if (checkBox.TryGetCurrentPattern(TogglePattern.Pattern, out object patternObj))
{
var togglePattern = patternObj as TogglePattern;
return togglePattern.Current.ToggleState;
}

throw new InvalidOperationException("TogglePatternに対応していません。");
}

チェック状態を確実にオンにしたい場合は、現在の状態を確認してから必要なときだけToggle()します。

C#
if (GetToggleState(checkBox) != ToggleState.On)
{
ToggleCheckBox(checkBox);
}

7-5. SelectionPatternでコンボボックスやリストを操作する

リストやコンボボックスの選択操作では、SelectionItemPatternを使うことが多いです。

たとえば、リスト内の項目をNameで取得し、選択するコードは次のとおりです。

C#
static void SelectItem(AutomationElement parent, string itemName)
{
var item = parent.FindFirst(
TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, itemName));

if (item == null)
{
throw new InvalidOperationException("選択項目が見つかりません。");
}

if (item.TryGetCurrentPattern(SelectionItemPattern.Pattern, out object patternObj))
{
var selectionItemPattern = patternObj as SelectionItemPattern;
selectionItemPattern.Select();
}
else
{
throw new InvalidOperationException("SelectionItemPatternに対応していません。");
}
}

使用例は次のとおりです。

C#
SelectItem(window, "東京都");

コンボボックスの場合、最初に展開操作が必要なことがあります。ExpandCollapsePatternに対応している場合は、次のように展開できます。

C#
if (comboBox.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out object patternObj))
{
var expandPattern = patternObj as ExpandCollapsePattern;
expandPattern.Expand();
}

その後、候補項目を検索してSelectionItemPattern.Select()で選択します。

7-6. パターン非対応時にSendKeysや座標クリックで代替する方法

対象要素がControl Patternに対応していない場合は、代替手段としてSendKeysや座標クリックを使うことがあります。

SendKeysを使う場合は、対象要素にフォーカスを当ててからキー入力します。

C#
using System.Windows.Forms;

textBox.SetFocus();
SendKeys.SendWait("入力テキスト");

ただし、System.Windows.Forms.SendKeysを使うには、Windows Formsへの参照が必要です。

座標クリックを使う場合は、要素のBoundingRectangleから中心座標を計算し、マウス操作を実行します。

C#
var rect = button.Current.BoundingRectangle;
double x = rect.Left + rect.Width / 2;
double y = rect.Top + rect.Height / 2;

座標クリックは画面解像度、DPI、ウィンドウ位置の影響を受けやすいため、最終手段として考えるべきです。

基本方針は、まずAutomationIdやNameで要素を取得し、対応するControl Patternで操作することです。それが難しい場合のみ、SendKeysや座標クリックを検討します。

8. 実践例:メモ帳をC# UIAutomationで自動操作する

ここでは、Windows標準アプリのメモ帳を例に、C# UIAutomationで自動操作するサンプルを紹介します。

実装する流れは、メモ帳を起動し、編集エリアを取得し、テキストを入力し、入力内容を取得して確認する、というものです。

メモ帳はWindowsのバージョンによってUI構造や要素情報が異なる場合があります。実際に試すときはInspect.exeでAutomationIdやControlTypeを確認してください。

8-1. メモ帳を起動する

まず、Process.Startでメモ帳を起動します。

C#
Process process = Process.Start("notepad.exe");
process.WaitForInputIdle();

起動直後はウィンドウがまだ準備できていない場合があるため、プロセスIDを条件にしてウィンドウを待機取得します。

C#
var window = WaitForElement(
AutomationElement.RootElement,
TreeScope.Children,
new PropertyCondition(AutomationElement.ProcessIdProperty, process.Id),
10000);

プロセスIDを使うことで、自分が起動したメモ帳を特定できます。

8-2. 編集エリアを取得する

メモ帳の編集エリアは、環境によってAutomationIdやControlTypeが異なることがあります。

まずはControlType.Editで検索します。

C#
var edit = WaitForElement(
window,
TreeScope.Descendants,
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit),
10000);

もし取得できない場合は、Inspect.exeで編集エリアのControlTypeやAutomationIdを確認してください。

Windowsのバージョンによっては、メモ帳の内部構造が変わっている可能性があります。その場合は、該当する要素のName、AutomationId、ClassNameを確認して検索条件を変更します。

8-3. テキストを自動入力する

編集エリアがValuePatternに対応している場合は、SetValue()でテキストを設定できます。

C#
SetText(edit, "C# UIAutomationでメモ帳に自動入力しています。");

ValuePatternに対応していない場合は、フォーカスを当ててSendKeysを使います。

C#
edit.SetFocus();
System.Windows.Forms.SendKeys.SendWait("C# UIAutomationでメモ帳に自動入力しています。");

SendKeysを使う場合、日本語入力やIMEの状態に影響を受けることがあります。安定性を重視するなら、可能な限りValuePatternを使うのがおすすめです。

8-4. メニューやボタンを操作する

メモ帳のメニューを操作する場合は、Nameでメニュー項目を取得します。

C#
var fileMenu = window.FindFirst(
TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "ファイル"));

取得できたメニュー項目がExpandCollapsePatternに対応していれば、展開できます。

C#
if (fileMenu.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out object patternObj))
{
var expandPattern = patternObj as ExpandCollapsePattern;
expandPattern.Expand();
}

メニュー項目によってはInvokePatternで実行できる場合もあります。

C#
if (fileMenu.TryGetCurrentPattern(InvokePattern.Pattern, out object invokeObj))
{
var invokePattern = invokeObj as InvokePattern;
invokePattern.Invoke();
}

メニュー操作はWindowsバージョンやアプリのUI構造によって差が出やすいため、実務ではショートカットキーを使う方が安定する場合もあります。

8-5. 入力内容を取得して確認する

入力後、ValuePatternを使ってメモ帳の編集内容を取得します。

C#
string text = GetText(edit);
Console.WriteLine(text);

取得した値が期待通りであれば、自動入力が成功していると判断できます。

UIテストでは、このように操作後の値や表示内容を取得し、期待値と比較します。

C#
if (text.Contains("C# UIAutomation"))
{
Console.WriteLine("入力確認に成功しました。");
}
else
{
Console.WriteLine("入力確認に失敗しました。");
}

8-6. 完成版サンプルコード

以下は、メモ帳を起動し、編集エリアに文字を入力し、入力内容を取得する完成版サンプルコードです。

C#
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Automation;

class Program
{
static void Main()
{
Process process = Process.Start("notepad.exe");
process.WaitForInputIdle();

AutomationElement window = WaitForElement(
AutomationElement.RootElement,
TreeScope.Children,
new PropertyCondition(AutomationElement.ProcessIdProperty, process.Id),
10000);

if (window == null)
{
Console.WriteLine("メモ帳のウィンドウを取得できませんでした。");
return;
}

Console.WriteLine($"ウィンドウ取得: {window.Current.Name}");

AutomationElement edit = WaitForElement(
window,
TreeScope.Descendants,
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit),
10000);

if (edit == null)
{
Console.WriteLine("編集エリアを取得できませんでした。");
PrintElements(window);
return;
}

string inputText = "C# UIAutomationでメモ帳を自動操作しています。";

if (edit.TryGetCurrentPattern(ValuePattern.Pattern, out object valueObj))
{
var valuePattern = valueObj as ValuePattern;
valuePattern.SetValue(inputText);

string currentValue = valuePattern.Current.Value;
Console.WriteLine($"入力内容: {currentValue}");
}
else
{
Console.WriteLine("ValuePatternに対応していません。");
edit.SetFocus();
Console.WriteLine("必要に応じてSendKeysでの入力を検討してください。");
}
}

static AutomationElement WaitForElement(
AutomationElement parent,
TreeScope scope,
Condition condition,
int timeoutMilliseconds)
{
var stopwatch = Stopwatch.StartNew();

while (stopwatch.ElapsedMilliseconds < timeoutMilliseconds)
{
var element = parent.FindFirst(scope, condition);

if (element != null)
{
return element;
}

Thread.Sleep(200);
}

return null;
}

static void PrintElements(AutomationElement parent)
{
var elements = parent.FindAll(TreeScope.Descendants, Condition.TrueCondition);

foreach (AutomationElement element in elements)
{
try
{
Console.WriteLine(
$"Name=[{element.Current.Name}], " +
$"AutomationId=[{element.Current.AutomationId}], " +
$"ClassName=[{element.Current.ClassName}], " +
$"ControlType=[{element.Current.ControlType.ProgrammaticName}]");
}
catch (ElementNotAvailableException)
{
Console.WriteLine("要素が利用できません。");
}
}
}
}

このコードは、UIAutomationの基本である「ウィンドウ取得」「要素取得」「入力」「値取得」をまとめたサンプルです。

対象アプリをメモ帳以外に変更する場合は、ウィンドウ取得条件と編集エリアの検索条件を変更すれば応用できます。

9. C# UIAutomationでよくあるエラーと対処法

C# UIAutomationでは、要素が取得できない、クリックできない、入力できないといった問題がよく発生します。

多くの場合、原因は検索条件、タイミング、権限、Control Pattern非対応のいずれかです。

ここでは、よくあるエラーと対処法を整理します。

9-1. 要素がnullになる原因

FindFirstの戻り値がnullになる場合、条件に一致する要素が見つかっていません。

原因として多いのは、検索条件が間違っているケースです。NameやAutomationIdが想定と異なる場合、要素は取得できません。

また、検索範囲が間違っていることもあります。直下の子要素だけを探しているのに、対象要素がさらに深い階層にある場合、TreeScope.Childrenでは見つかりません。この場合はTreeScope.Descendantsを使います。

タイミングの問題もあります。画面が表示される前に検索している場合、要素はまだ存在しません。条件付き待機を入れて、要素が表示されるまで待ちましょう。

対処法としては、まず要素一覧を出力し、実際に取得できるName、AutomationId、ControlTypeを確認します。

C#
PrintElements(window);

そのうえで、検索条件を見直します。

9-2. AutomationIdが取得できない場合の探し方

対象アプリによっては、AutomationIdが空の要素があります。

その場合は、Inspect.exeで次の情報を確認します。

Name
ClassName
ControlType
BoundingRectangle
Supported Patterns

AutomationIdがない場合は、NameとControlTypeを組み合わせるのが基本です。

C#
var condition = new AndCondition(
new PropertyCondition(AutomationElement.NameProperty, "検索"),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button));

Nameも空の場合は、同じControlTypeの要素一覧を取得し、順番や親要素の情報から絞り込む必要があります。

C#
var buttons = window.FindAll(
TreeScope.Descendants,
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button));

ただし、順番に依存した実装はUI変更に弱くなります。可能であれば、対象アプリ側でAutomationIdを設定してもらうのが最も安定します。

9-3. InvokePatternが使えない場合の対処法

ボタンを取得できても、InvokePatternが使えない場合があります。

原因は、対象要素がクリック操作をUIAutomationのPatternとして公開していないことです。特に独自コントロールやカスタム描画のUIで発生しやすいです。

まず、対象要素が本当にボタンなのかを確認します。

C#
Console.WriteLine(button.Current.ControlType.ProgrammaticName);

次に、対応しているPatternをInspect.exeで確認します。

InvokePatternに対応していない場合は、次の代替方法を検討します。

フォーカスを当ててEnterキーを送る方法があります。

C#
button.SetFocus();
System.Windows.Forms.SendKeys.SendWait("{ENTER}");

また、対象要素の座標を取得してクリックする方法もあります。ただし、座標クリックは環境依存が強いため、できるだけ避けるべきです。

可能であれば、対象アプリ側にUIAutomation対応を改善してもらうのが理想です。

9-4. ValuePattern.SetValueが効かない場合の対処法

テキストボックスに対してValuePattern.SetValue()を呼び出しても入力できない場合があります。

原因として、対象要素がValuePatternに対応していない、読み取り専用である、フォーカスが必要である、独自入力コントロールである、などが考えられます。

まず、TryGetCurrentPatternValuePatternに対応しているか確認します。

C#
if (!textBox.TryGetCurrentPattern(ValuePattern.Pattern, out object valueObj))
{
Console.WriteLine("ValuePatternに対応していません。");
}

読み取り専用かどうかは、ValuePattern.Current.IsReadOnlyで確認できます。

C#
var valuePattern = valueObj as ValuePattern;

if (valuePattern.Current.IsReadOnly)
{
Console.WriteLine("読み取り専用です。");
}
else
{
valuePattern.SetValue("入力値");
}

ValuePatternが使えない場合は、フォーカスを当ててからSendKeysで入力します。

C#
textBox.SetFocus();
System.Windows.Forms.SendKeys.SendWait("入力値");

ただし、SendKeysはIMEやキーボードレイアウトの影響を受けるため、安定性には注意が必要です。

9-5. タイミング問題をWait処理で解決する

UIAutomationの失敗原因として非常に多いのがタイミング問題です。

画面遷移後すぐに要素を検索すると、まだUI要素が生成されていないことがあります。固定待機で対応することもできますが、環境によって必要な待機時間が変わるため不安定です。

おすすめは、条件付き待機を共通メソッド化することです。

C#
static AutomationElement WaitForElement(
AutomationElement parent,
TreeScope scope,
Condition condition,
int timeoutMilliseconds = 10000)
{
var stopwatch = Stopwatch.StartNew();

while (stopwatch.ElapsedMilliseconds < timeoutMilliseconds)
{
var element = parent.FindFirst(scope, condition);

if (element != null)
{
return element;
}

Thread.Sleep(200);
}

return null;
}

クリック後に画面が変わる場合は、次の画面の特徴的な要素が表示されるまで待ちます。

C#
ClickButton(nextButton);

var resultLabel = WaitForElement(
window,
TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "完了"),
10000);

このように、処理の完了をUI要素の表示で判断すると安定しやすくなります。

9-6. 32bit・64bitや権限差による取得失敗

UIAutomationでは、32bit・64bitの違いや権限差が問題になることがあります。

特に注意すべきなのは権限差です。対象アプリが管理者権限で実行されている場合、自動化プログラムも管理者権限で実行しないと要素取得や操作に失敗することがあります。

また、古いアプリや特殊なランタイムで作られたアプリでは、UIAutomation情報が十分に公開されていないことがあります。

対処法としては、まず対象アプリと自動化プログラムを同じ権限レベルで実行します。次に、Inspect.exeで要素情報が取得できるか確認します。

Inspect.exeでも要素情報が取得できない場合、C#コードでも安定して取得するのは難しい可能性があります。

その場合は、ショートカットキー、ファイル連携、コマンドライン引数、API連携など、UI操作以外の方法を検討することも重要です。

10. C# UIAutomationを安定運用するための実装ポイント

C# UIAutomationは便利ですが、画面構造に依存するため、雑に実装するとすぐに動かなくなります。

安定運用するには、検索条件、待機処理、例外処理、ログ出力、共通化を意識することが大切です。

10-1. 固定の待機時間ではなく条件付き待機を使う

Thread.Sleep(3000)のような固定待機は簡単ですが、環境差に弱いです。

処理が速い環境では無駄な待ち時間が発生し、遅い環境では待機時間が足りずに失敗します。

そのため、特定の要素が表示されるまで待つ条件付き待機を使いましょう。

C#
var button = WaitForElement(
window,
TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "実行"),
10000);

条件付き待機を使うと、要素が見つかった時点ですぐに次へ進めます。自動化処理全体の速度と安定性を両立できます。

10-2. AutomationIdを優先して要素を特定する

要素検索では、AutomationIdを優先するのがおすすめです。

Nameは表示文字列のため、文言変更や多言語対応の影響を受けやすいです。一方、AutomationIdはUI要素の識別子として設定されていることが多く、比較的安定しています。

C#
var button = window.FindFirst(
TreeScope.Descendants,
new PropertyCondition(AutomationElement.AutomationIdProperty, "btnExecute"));

AutomationIdがない場合は、Name、ControlType、ClassNameを組み合わせます。

C#
var condition = new AndCondition(
new PropertyCondition(AutomationElement.NameProperty, "実行"),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button));

対象アプリを自社で開発している場合は、主要なボタンや入力欄にAutomationIdを設定しておくと、自動化やUIテストが非常に楽になります。

10-3. 例外処理とログ出力を入れる

UIAutomationでは、対象アプリの状態によって要素が利用できなくなることがあります。

たとえば、画面遷移中に取得済みの要素へアクセスすると、ElementNotAvailableExceptionが発生することがあります。

安定運用するには、例外処理とログ出力を入れて、どの要素取得や操作で失敗したのか分かるようにします。

C#
try
{
ClickButton(button);
}
catch (ElementNotAvailableException ex)
{
Console.WriteLine($"要素が利用できません: {ex.Message}");
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"操作に失敗しました: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"予期しないエラー: {ex.Message}");
}

ログには、対象要素のName、AutomationId、ControlTypeも出力しておくと便利です。

10-4. UI変更に強い検索条件を設計する

UIAutomationはUI構造に依存するため、対象アプリの画面変更に弱い面があります。

UI変更に強くするには、できるだけ安定した検索条件を使います。

優先順位は次のように考えるとよいです。

1. AutomationId
2. AutomationId + ControlType
3. Name + ControlType
4. ClassName + ControlType
5. 要素の順番
6. 座標

要素の順番や座標に依存した実装は、レイアウト変更で壊れやすくなります。

また、親要素を先に取得してから、その配下で目的の要素を探すと、検索範囲を限定できて安定しやすくなります。

C#
var group = window.FindFirst(
TreeScope.Descendants,
new PropertyCondition(AutomationElement.AutomationIdProperty, "grpSearch"));

var searchButton = group.FindFirst(
TreeScope.Descendants,
new PropertyCondition(AutomationElement.AutomationIdProperty, "btnSearch"));

10-5. 共通メソッド化して再利用しやすくする

UIAutomationの処理は、要素取得、クリック、入力、値取得、待機など、似た処理が多くなります。

そのため、共通メソッド化しておくと保守しやすくなります。

C#
static AutomationElement FindByAutomationId(
AutomationElement parent,
string automationId,
int timeoutMilliseconds = 10000)
{
return WaitForElement(
parent,
TreeScope.Descendants,
new PropertyCondition(AutomationElement.AutomationIdProperty, automationId),
timeoutMilliseconds);
}

クリック処理も共通化できます。

C#
static void InvokeElement(AutomationElement element)
{
if (element == null)
{
throw new ArgumentNullException(nameof(element));
}

if (!element.TryGetCurrentPattern(InvokePattern.Pattern, out object patternObj))
{
throw new InvalidOperationException("InvokePatternに対応していません。");
}

var invokePattern = patternObj as InvokePattern;
invokePattern.Invoke();
}

共通化しておけば、対象アプリが変わっても検索条件を差し替えるだけで再利用できます。

11. C# UIAutomationの応用:業務自動化・UIテストへの活用

C# UIAutomationの基本を理解すると、業務自動化やUIテストに応用できます。

単発のクリックや入力だけでなく、CSVやExcelからデータを読み込み、複数件の入力作業を自動化することもできます。

11-1. 定型業務の入力作業を自動化する

業務アプリに毎日同じようなデータを入力している場合、C# UIAutomationで自動化できます。

たとえば、顧客番号を入力し、検索ボタンをクリックし、結果を確認して次の顧客番号へ進む、といった処理です。

C#
SetText(customerCodeTextBox, "C0001");
ClickButton(searchButton);

var result = WaitForElement(
window,
TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "検索完了"),
10000);

人が行っている画面操作をそのまま自動化できるため、APIやデータベース連携が用意されていない古い業務アプリにも対応しやすいです。

ただし、業務データを扱う場合は、誤入力や二重登録を防ぐための確認処理を必ず入れましょう。

11-2. Windowsアプリの回帰テストに活用する

C# UIAutomationは、Windowsアプリの回帰テストにも活用できます。

アプリを起動し、ログインし、画面を操作し、期待するラベルやメッセージが表示されるかを確認します。

C#
ClickButton(loginButton);

var homeLabel = WaitForElement(
window,
TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "ホーム"),
10000);

if (homeLabel == null)
{
throw new Exception("ログイン後のホーム画面が表示されませんでした。");
}

単体テストでは確認しにくい画面操作の流れを自動化できるため、リリース前の確認作業を効率化できます。

ただし、UIテストは実行環境に依存しやすいため、テスト専用のPCや仮想環境で実行するのがおすすめです。

11-3. CSVやExcelデータと組み合わせて連続処理する

CSVやExcelのデータを読み込み、1行ずつ業務アプリに入力する処理も実装できます。

CSVを使う簡単な例は次のとおりです。

C#
var lines = System.IO.File.ReadAllLines("data.csv");

foreach (var line in lines)
{
var columns = line.Split(',');

string customerCode = columns[0];
string customerName = columns[1];

SetText(customerCodeTextBox, customerCode);
SetText(customerNameTextBox, customerName);

ClickButton(registerButton);

var complete = WaitForElement(
window,
TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "登録完了"),
10000);

if (complete == null)
{
Console.WriteLine($"登録失敗: {customerCode}");
}
}

実務では、入力前の確認、エラー時のリトライ、処理済みデータの記録なども必要です。

Excelと組み合わせる場合は、ClosedXMLやEPPlusなどのライブラリを使ってデータを読み込む方法があります。

11-4. エラー発生時にスクリーンショットを保存する

自動化処理でエラーが発生した場合、ログだけでは状況を把握しにくいことがあります。

そのため、エラー発生時にスクリーンショットを保存しておくと便利です。

C#
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;

static void SaveScreenshot(string filePath)
{
var bounds = Screen.PrimaryScreen.Bounds;

using (var bitmap = new Bitmap(bounds.Width, bounds.Height))
using (var graphics = Graphics.FromImage(bitmap))
{
graphics.CopyFromScreen(bounds.Left, bounds.Top, 0, 0, bounds.Size);
bitmap.Save(filePath, ImageFormat.Png);
}
}

使用例は次のとおりです。

C#
try
{
ClickButton(registerButton);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
SaveScreenshot("error.png");
}

スクリーンショットを保存しておくと、どの画面で止まったのか、エラーダイアログが表示されていたのかを後から確認できます。

11-5. UIAutomationで簡易RPAを作る際の注意点

C# UIAutomationで簡易RPAを作る場合は、いくつか注意点があります。

まず、UI操作だけに依存しすぎないことです。API、CSV入出力、データベース、コマンドライン引数など、より安定した連携方法があるなら、そちらを優先すべきです。

次に、例外処理とリカバリ処理を入れることです。業務アプリでは、想定外の確認ダイアログやエラーメッセージが表示されることがあります。その場合に処理が止まらないよう、エラー画面を検出してログ出力する仕組みが必要です。

また、処理結果を必ず記録することも重要です。どのデータが成功し、どのデータが失敗したのかをCSVやログファイルに残しましょう。

最後に、UI変更への備えが必要です。対象アプリのバージョンアップでAutomationIdや画面構造が変わると、自動化プログラムが動かなくなる可能性があります。定期的に動作確認を行い、検索条件を保守しやすい形にしておくことが大切です。

まとめ

C# UIAutomationを使うと、WindowsアプリのUI要素を取得し、クリック、入力、値取得などの操作を自動化できます。

基本の流れは、対象アプリのウィンドウを取得し、その配下からボタンやテキストボックスなどのAutomationElementを検索し、InvokePatternValuePatternなどのControl Patternを使って操作する、というものです。

要素検索では、AutomationIdを優先し、必要に応じてName、ControlType、ClassNameを組み合わせると安定しやすくなります。また、固定待機ではなく条件付き待機を使うことで、画面表示のタイミング差にも対応できます。

C# UIAutomationは、業務自動化、WindowsアプリのUIテスト、簡易RPAに活用できる便利な技術です。一方で、画面構造や権限、対象アプリのUIAutomation対応状況に影響を受けるため、例外処理、ログ出力、スクリーンショット保存、検索条件の共通化を取り入れて、保守しやすい実装にすることが重要です。

まずはメモ帳のようなシンプルなアプリで、ウィンドウ取得、要素取得、クリック、入力、値取得を試してみましょう。基本パターンを理解できれば、社内業務アプリやテスト自動化にも応用できます。