はじめに (対象読者・この記事でわかること)

この記事は、Windowsデスクトップアプリケーションの開発に携わっている方、特にWin32 APIを使って他のアプリケーションのUI要素から情報を取得したい方を対象としています。

この記事を読むことで、テキストボックス以外のウィンドウコントロール(ボタン、リストビュー、ツリービュー、リストボックスなど)からテキストを抽出する方法がわかります。GetWindowText APIだけでは取得できない、様々なコントロールのテキスト取得テクニックを習得できます。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 - C++の基本的な文法 - Win32 APIの基礎知識(ウィンドウハンドルの概念) - Visual StudioでのWindowsアプリケーション開発経験

Win32 APIにおけるテキスト取得の課題

Windowsアプリケーションのオートメーションやテストを行う際、他のアプリケーションのウィンドウからテキストを取得する必要がよくあります。多くの開発者はGetWindowText APIを使えば簡単に取得できると思われがちですが、実際には多くの制限があります。

GetWindowTextは、エディットコントロール(テキストボックス)や静的テキスト(ラベル)からのテキスト取得には有効ですが、ボタン、リストビュー、ツリービュー、リストボックスなどの複雑なコントロールからは、期待通りにテキストを取得できないことが多々あります。

これらのコントロールは、内部的に独自の描画処理を行い、ウィンドウテキストとしては空または意味のない値しか保持していないことが原因です。

実践:様々なコントロールからテキストを取得する方法

ここでは、実際に異なるタイプのコントロールからテキストを取得する具体的な方法を説明します。C++を使用しますが、概念は他の言語でも応用可能です。

ステップ1:基本のアプローチとWM_GETTEXTの使用

まず、最も基本的なアプローチから始めましょう。GetWindowText APIの内部では、WM_GETTEXTメッセージが送信されています。これを直接使うことで、より多くのコントロールに対応できます。

Cpp
#include <windows.h> #include <iostream> // ウィンドウハンドルからテキストを取得する汎用関数 std::wstring GetWindowTextUniversal(HWND hwnd) { if (!hwnd) return L""; // まずWM_GETTEXTで試みる const int bufferSize = 1024; wchar_t buffer[bufferSize]; // WM_GETTEXTを送信 LRESULT result = SendMessage(hwnd, WM_GETTEXT, bufferSize, (LPARAM)buffer); if (result > 0) { return std::wstring(buffer); } // 失敗した場合はGetWindowText APIを試す int length = GetWindowTextLength(hwnd); if (length > 0) { std::wstring text(length + 1, L'\0'); GetWindowText(hwnd, &text[0], length + 1); text.resize(length); return text; } return L""; }

ステップ2:リストビュー(ListView)からアイテムテキストを取得

リストビューコントロールは、LVM_GETITEMTEXTメッセージを使用して各アイテムのテキストを取得できます。

Cpp
#include <commctrl.h> #pragma comment(lib, "comctl32.lib") // リストビューから全アイテムのテキストを取得 std::vector<std::wstring> GetListViewItems(HWND listViewHwnd) { std::vector<std::wstring> items; if (!listViewHwnd) return items; // アイテム数を取得 int itemCount = ListView_GetItemCount(listViewHwnd); for (int i = 0; i < itemCount; i++) { wchar_t buffer[256]; LVITEM lvi = {0}; lvi.iItem = i; lvi.iSubItem = 0; // 最初の列 lvi.pszText = buffer; lvi.cchTextMax = sizeof(buffer) / sizeof(wchar_t); // アイテムテキストを取得 if (SendMessage(listViewHwnd, LVM_GETITEMTEXT, i, (LPARAM)&lvi) > 0) { items.push_back(buffer); } } return items; }

ステップ3:ツリービュー(TreeView)からノードテキストを取得

ツリービューは階層構造を持つため、少し複雑ですがTVM_GETITEMメッセージを使用します。

Cpp
#include <commctrl.h> // ツリービューから全ノードのテキストを取得 std::vector<std::wstring> GetTreeViewItems(HWND treeViewHwnd) { std::vector<std::wstring> items; if (!treeViewHwnd) return items; // ルートアイテムを取得 HTREEITEM rootItem = TreeView_GetRoot(treeViewHwnd); // 再帰的にアイテムを処理 ProcessTreeItem(treeViewHwnd, rootItem, items); return items; } void ProcessTreeItem(HWND treeViewHwnd, HTREEITEM item, std::vector<std::wstring>& items) { while (item) { wchar_t buffer[256]; TVITEM tvi = {0}; tvi.hItem = item; tvi.mask = TVIF_TEXT; tvi.pszText = buffer; tvi.cchTextMax = sizeof(buffer) / sizeof(wchar_t); if (TreeView_GetItem(treeViewHwnd, &tvi)) { items.push_back(buffer); } // 子アイテムを処理 HTREEITEM childItem = TreeView_GetChild(treeViewHwnd, item); if (childItem) { ProcessTreeItem(treeViewHwnd, childItem, items); } // 次の兄弟アイテム item = TreeView_GetNextSibling(treeViewHwnd, item); } }

ステップ4:UIオートメーションAPIを使用した高度な方法

上記の方法でも取得できない場合は、Microsoft UIオートメーションAPIを使用します。これはより高度な方法で、ほぼすべてのUI要素から情報を取得できます。

Cpp
#include <uiautomation.h> #pragma comment(lib, "uiautomationcore.lib") // UIオートメーションを使用してテキストを取得 std::wstring GetTextUsingUIAutomation(HWND hwnd) { std::wstring result; CoInitialize(NULL); IUIAutomation* pAutomation = NULL; HRESULT hr = CoCreateInstance(__uuidof(CUIAutomation), NULL, CLSCTX_INPROC_SERVER, __uuidof(IUIAutomation), (void**)&pAutomation); if (SUCCEEDED(hr)) { IUIAutomationElement* pElement = NULL; hr = pAutomation->ElementFromHandle(hwnd, &pElement); if (SUCCEEDED(hr)) { // コントロールパターンを取得 IUIAutomationLegacyIAccessiblePattern* pPattern = NULL; hr = pElement->GetCurrentPatternAs(UIA_LegacyIAccessiblePatternId, __uuidof(IUIAutomationLegacyIAccessiblePattern), (void**)&pPattern); if (SUCCEEDED(hr)) { BSTR name; pPattern->get_CurrentName(&name); result = name; SysFreeString(name); pPattern->Release(); } // 名前プロパティも試す if (result.empty()) { BSTR name; pElement->get_CurrentName(&name); result = name; SysFreeString(name); } pElement->Release(); } pAutomation->Release(); } CoUninitialize(); return result; }

ハマった点やエラー解決

実装中に遭遇した主な問題をいくつか紹介します。

1. メッセージの送信が失敗する 一部のアプリケーション(特に管理者権限で動作しているもの)からは、権限の問題でメッセージを送信できません。この場合、プロセスを管理者権限で実行するか、UIAccess属性を設定する必要があります。

2. 64ビットプロセスと32ビットプロセス間の通信 異なるアーキテクチャのプロセス間でメッセージを送受信する場合、LPARAMWPARAMのサイズ違いにより問題が発生します。UIオートメーションAPIを使用すると、この問題を回避できます。

3. カスタムコントロールへの対応 独自に実装されたカスタムコントロールは、標準的なメッセージに対応していないことが多いです。この場合、UIオートメーションAPIの使用が最も確実な方法です。

解決策

上記の問題に対する総合的な解決策として、以下のような統合的なアプローチを推奨します:

Cpp
// 万能のテキスト取得関数 std::wstring ExtractWindowText(HWND hwnd) { if (!IsWindow(hwnd)) return L""; std::wstring result; // 1. まずWM_GETTEXTを試す result = GetWindowTextUniversal(hwnd); if (!result.empty()) return result; // 2. コントロールのクラス名を確認 wchar_t className[256]; GetClassName(hwnd, className, 256); std::wstring classStr(className); // リストビューかチェック if (classStr == L"SysListView32") { auto items = GetListViewItems(hwnd); if (!items.empty()) { return items[0]; // 最初のアイテムを返す } } // ツリービューかチェック if (classStr == L"SysTreeView32") { auto items = GetTreeViewItems(hwnd); if (!items.empty()) { return items[0]; // 最初のノードを返す } } // 3. UIオートメーションを試す result = GetTextUsingUIAutomation(hwnd); if (!result.empty()) return result; // 4. 最後の手段:子ウィンドウを探索 HWND child = FindWindowEx(hwnd, NULL, NULL, NULL); while (child) { result = ExtractWindowText(child); if (!result.empty()) return result; child = FindWindowEx(hwnd, child, NULL, NULL); } return L""; }

まとめ

本記事では、Win32 APIを使用してテキストボックス以外のウィンドウコントロールからテキストを取得する方法を解説しました。

  • WM_GETTEXTメッセージの直接利用:基本的なテキスト取得の方法
  • コントロール固有のメッセージ活用:ListView、TreeViewなど専用のメッセージを使用
  • UIオートメーションAPIの活用:最も強力で汎用的な方法
  • 統合的なアプローチ:様々な状況に対応する包括的な解決策

この記事を通して、WindowsアプリケーションのUIオートメーションやテストにおいて、より高度なテキスト抽出が可能になります。特に、レガシーなアプリケーションや独自実装されたUIから情報を取得する必要がある場合、これらの技術は非常に有効です。

今後は、.NET環境での同等の実装や、クロスプラットフォームなUIオートメーションツールについても記事にする予定です。

参考資料