2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

UIAutomationを使ってスクロールバーをRangeValuePatternで動かそうとしたときにハマった

Last updated at Posted at 2020-04-10

結論

.NET Framework版のUIAutomationClientを使いましょう!
C++などから利用するCOM実装のUIAutomationや.NET Core版のUIAutomationではうまく動きません

経緯

個人で開発している ExpTabBarというエクスプローラーにタブを付けるシェル拡張を開発している最中、タブ切り替え時にスクロール位置を復元する機能を実装しようと思い試行錯誤していた

ご存じの通りWindows10になってからのエクスプローラーのファイルビューは、ListViewなどの標準コンポーネントが使用されておらず 独自実装のウィンドウとなっており スクロールバーなどのUI部品から ウィンドウハンドルを取得できなくなっている

ウィンドウハンドルが取得できないのならどうしようといろいろ調べた結果
Windowsに標準実装されているUIAutomationという機能を使えばいいことが分かり
それではさっそくとUI Spyを使ってスクロールバーのElementを調査し、スクロール位置の取得と、RangeValuePatternを使ってスクロール位置の変更が確認できたので、自分のアプリケーションに実装してみたのだが

HRESULT	hr;
CComPtr<IUIAutomation2>	spUIAutomation;
hr = CoCreateInstance(CLSID_CUIAutomation8, NULL, CLSCTX_INPROC_SERVER, __uuidof(IUIAutomation2), (void**)&spUIAutomation);
ATLASSERT(spUIAutomation);

CComPtr<IUIAutomationElement>	spListViewElement;
hr = spUIAutomation->ElementFromHandle(ListView.m_hWnd, &spListViewElement);
if (FAILED(hr))
	AtlThrow(hr);

CComVariant varProp;
varProp.vt = VT_I4;
varProp.intVal = UIA_ScrollBarControlTypeId;
CComPtr<IUIAutomationCondition>	spCondition2;
hr = spUIAutomation->CreatePropertyCondition(UIA_ControlTypePropertyId, varProp, &spCondition2);
if (FAILED(hr))
	AtlThrow(hr);

CComPtr<IUIAutomationElement>	spScrollBarElement;
hr = spListViewElement->FindFirst(TreeScope_Children, spCondition2, &spScrollBarElement);
if (spScrollBarElement == NULL)
	AtlThrow(hr);

CComPtr<IUIAutomationRangeValuePattern> spRangeValuePattern;
hr = spScrollBarElement->GetCurrentPatternAs(UIA_RangeValuePatternId, IID_PPV_ARGS(&spRangeValuePattern));
if (spRangeValuePattern == NULL)
	AtlThrow(hr);

double scrollPos = 0;
hr = spRangeValuePattern->get_CurrentValue(&scrollPos);

get_CurrentValueで取れる値が、UISpyと異なり
さらにSetValueでスクロール位置の変更が効かなかった(UISpyでは変更できた)

なんで動かねえんだよくそがあああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ

と、何十時間もの調査の結果
自分のコードでUIElementから取得したAutomationIdが"NonClientVerticalScrollBar"なのに対して、UISpyで表示されるAutomationIdが"Vertical ScrollBar"なことに気づく
googleでぐぐってみると以下のURLにたどり着いた
https://www.csharpcodi.com/csharp-examples/FlaUI.Core.Conditions.ConditionFactory.ByAutomationId(string)/

UIA2とUIA3では同じように違っていた、
この違いが動かない原因なのでは...と、FlaUIというライブラリを調べて調査した結果が、結論に記したことである

おわりに

ちょっとマイクロソフトさん同じUIAutomationなのに、使ってるライブラリの違いで動くとか動かないとかいうバグを作るのはやめてください

てっきり.NET版のUIAutomationCoreはCOM版のラッパーだと思ってたのに、こんな結果になるとは思わなかった

同じ.NETでも.NET Coreの
using UIAutomationClient;
と書いて利用するUIAutomationは正常に動作しません

おまけ

正常に動作するC#版のソース
UIAutomationClientとUIAutomationTypesを参照に追加してね

// using System.Windows.Automation;
var listViewElm = AutomationElement.FromHandle((IntPtr)0x00031186/*フォルダービューのウィンドウハンドル*/);
var condScrollbarCtrl = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ScrollBar);
var scrollbarElm = listViewElm.FindFirst(TreeScope.Children, condScrollbarCtrl);
var rangeValuePattern = (RangeValuePattern)scrollbarElm.GetCurrentPattern(RangeValuePattern.Pattern);
// 現在のスクロール位置
Console.WriteLine("Hello World! {0}", rangeValuePattern.Current.Value);
// スクロール位置の変更
rangeValuePattern.SetValue(50000);
2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?