Garmin Connect IQ-コマンドラインでのビルド・シミュレーター起動でお勉強する環境が整ったので、いろいろと調べながらサンプルプログラムを作っている。
まずは、メニュー操作を習得するため、シミュレーターのメニュー一覧を出すプログラムを作成した。
動きとしては、以下のよう感じ。
- メイン画面にあるメニューバーを取り出す
- メニューバーに登録されているメニュー項目を取り出す
- メニュー項目が展開可能だったら展開し、展開されたメニューの中のメニュー項目を取り出す
- 3を全メニュー項目に対して再帰的に実行する。
メニューかどうかについては、AutomationElementのControlTypeがMenuBar、MenuItem、Menuかで判断している。
展開可能かどうかは、(bool)obj.GetCurrentPropertyValue(AutomationElement.IsExpandCollapsePatternAvailableProperty)
で判断している。
見つけた項目は、メニュー名等をツリー状で管理し、それを最後出力している。
シミュレーターは、UpdateTargetProcessで起動済みのものをタイトル名で検索している。
以下、ソースコード。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Windows.Automation;
namespace UiAutomation1
{
/// <summary>
/// メニューツリーを構築するためのツリー管理クラス
/// </summary>
class TreeNode<T>
{
public List<TreeNode<T>> Children = new List<TreeNode<T>>();
public T Item { get; set; }
public TreeNode(T item)
{
Item = item;
}
public TreeNode<T> AddChild(T item)
{
TreeNode<T> nodeItem = new TreeNode<T>(item);
Children.Add(nodeItem);
return nodeItem;
}
/// <summary>
/// ツリーの内容表示
/// </summary>
public void Print(string header)
{
string output;
if (header.Length == 0) {
output = Item.ToString();
}
else {
output = header + "-" + Item.ToString();
}
Console.WriteLine(output);
foreach (var item in Children) {
item.Print(output);
}
}
}
enum MenuType
{
MenuBar, // 一番トップのやつ
ExpandMenu, // メニューが展開するやつ
MenuItem, // 単独のメニュー
}
struct MenuData
{
public MenuType Type;
public string Name;
public string Id;
public AutomationElement Element;
override public string ToString()
{
return Name;
}
}
class Program
{
[System.Runtime.InteropServices.DllImport("Kernel32.dll")]
public static extern bool AllocConsole();
private const int MaxRetryNum = 1000;
//指定したタイトルの文字列が含まれているプロセスを取得
//一個目を戻すだけなので、複数対応はしていません。
// UWPの場合、起動直後タイトルが未決定の場合があるので、リトライを入れてみる
static public Process UpdateTargetProcess(string title)
{
for (int i = 0; i < MaxRetryNum; i++) {
foreach (Process p in Process.GetProcesses()) {
if (p.MainWindowTitle.Contains(title)) {
return p;
}
}
Thread.Sleep(10);
}
return null;
}
static void Main(string[] args)
{
AllocConsole();
TreeNode<MenuData> menu = new TreeNode<MenuData>(new MenuData() { Type = MenuType.MenuBar });
var proc = UpdateTargetProcess("Connect IQ Device Simulator");
// トップのメニューを探す
var root = AutomationElement.FromHandle(proc.MainWindowHandle);
var children = root.FindAll(TreeScope.Children, Condition.TrueCondition).Cast<AutomationElement>();
AutomationElement menuTop = null;
foreach (var obj in children) {
if (obj.Current.ControlType == ControlType.MenuBar) {
var item = new MenuData();
item.Name = obj.Current.Name;
item.Type = MenuType.MenuBar;
item.Id = obj.Current.AutomationId;
item.Element = obj;
menuTop = obj;
menu.Item = item;
}
}
if (menu.Item.Type == MenuType.MenuBar && menuTop is not null) {
FindChildren(menu, menuTop, root);
}
menu.Print("");
var line = Console.ReadLine();
}
/// <summary>
/// メニューの子供を見つけ、expand可能なアイテムの場合その子供を探す<br/>
/// ポップされたメニューはトップの子供として展開されるため、検索するためトップ要素も渡すようにしている
/// </summary>
/// <param name="appTop">ポップアップしたメニューを探すためのトップ要素</param>
static void FindChildren(TreeNode<MenuData> menu, AutomationElement menuTop, AutomationElement appTop) {
// メニュー項目の子要素をまず検索する
var children = menuTop.FindAll(TreeScope.Children, Condition.TrueCondition).Cast<AutomationElement>();
foreach (var obj in children) {
if (obj.Current.ControlType == ControlType.MenuItem || obj.Current.ControlType == ControlType.Menu) {
var item = new MenuData();
item.Name = obj.Current.Name;
if ((bool)obj.GetCurrentPropertyValue(AutomationElement.IsExpandCollapsePatternAvailableProperty)) {
item.Type = MenuType.ExpandMenu;
}
else {
item.Type = MenuType.MenuItem;
}
item.Id = obj.Current.AutomationId;
item.Element = obj;
menu.AddChild(item);
}
}
// 展開可能なメニューを展開していく
foreach (var obj in menu.Children) {
if (obj.Item.Type == MenuType.ExpandMenu) {
if (obj.Item.Element.Current.IsEnabled) {
var expand = obj.Item.Element.GetCurrentPattern(ExpandCollapsePattern.Pattern) as ExpandCollapsePattern;
expand.Expand();
// 以下Childrenで探せるかと思ったのだがだめだったので、Descendantsに変更してある
children = appTop.FindAll(TreeScope.Descendants, new PropertyCondition(AutomationElement.NameProperty, obj.Item.Name)).Cast<AutomationElement>();
foreach (var obj1 in children) {
if (obj1.Current.ControlType == ControlType.Menu) {
FindChildren(obj, obj1, appTop);
}
}
}
}
}
}
}
}
コメント