Skip to content

Commit 2bd0571

Browse files
authored
Refactor UIA (#337)
* Refactor UIA * Update UIA when visual ordering changes * Fix keyboard input and support UIA removal of all children --------- Co-authored-by: adpa-ms
1 parent b5c4dbb commit 2bd0571

File tree

7 files changed

+690
-658
lines changed

7 files changed

+690
-658
lines changed

Samples/Islands/DrawingIsland/DrawingCppTestApp/WinMain.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
4040
siteBridge.Show();
4141
siteBridge.Connect(drawing.Island());
4242

43+
// Move initial focus to the island.
44+
auto focusNavigationHost = winrt::InputFocusNavigationHost::GetForSiteBridge(siteBridge);
45+
focusNavigationHost.NavigateFocus(winrt::FocusNavigationRequest::Create(
46+
winrt::FocusNavigationReason::Programmatic));
47+
4348
queue.RunEventLoop();
4449

4550
controller.ShutdownQueue();

Samples/Islands/DrawingIsland/DrawingIslandComponents/DrawingIsland.cpp

Lines changed: 151 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ namespace winrt::DrawingIslandComponents::implementation
5656

5757
DrawingIsland::~DrawingIsland()
5858
{
59-
m_uia.FragmentRoot = nullptr;
60-
m_uia.FragmentFactory = nullptr;
6159
m_output.Compositor = nullptr;
6260
}
6361

@@ -104,9 +102,18 @@ namespace winrt::DrawingIslandComponents::implementation
104102
}
105103
#endif
106104

107-
m_uia.FragmentFactory = nullptr;
108-
m_uia.FragmentRoot = nullptr;
109-
m_uia.VisualToFragmentMap.clear();
105+
// Make sure automation is destroyed.
106+
for (auto& automationPeer : m_uia.AutomationPeers)
107+
{
108+
automationPeer.GetAutomationProvider()->SetCallbackHandler(nullptr);
109+
}
110+
m_uia.AutomationPeers.clear();
111+
112+
if (nullptr != m_uia.FragmentRoot)
113+
{
114+
m_uia.FragmentRoot->SetCallbackHandler(nullptr);
115+
m_uia.FragmentRoot = nullptr;
116+
}
110117

111118

112119
// Destroy Content:
@@ -173,9 +180,105 @@ namespace winrt::DrawingIslandComponents::implementation
173180
}
174181

175182

183+
winrt::Windows::Graphics::RectInt32
184+
DrawingIsland::GetScreenBoundsForAutomationFragment(
185+
_In_::IUnknown const* const sender) const
186+
{
187+
// Check if the automation provider that has sent this callback is the fragment root.
188+
if (m_uia.FragmentRoot.as<::IUnknown>().get() == sender)
189+
{
190+
winrt::Rect logicalRect;
191+
logicalRect.X = 0;
192+
logicalRect.Y = 0;
193+
logicalRect.Width = m_island.ActualSize().x;
194+
logicalRect.Height = m_island.ActualSize().y;
195+
196+
// This will convert from the logical coordinate space of the ContentIsland to
197+
// Win32 screen coordinates that are needed by Accessibility.
198+
return m_island.CoordinateConverter().ConvertLocalToScreen(logicalRect);
199+
}
200+
201+
// Else find the matching automation peer entry for the sending fragment.
202+
auto iterator = std::find_if(
203+
m_uia.AutomationPeers.begin(), m_uia.AutomationPeers.end(), [&sender](auto const& automationPeer)
204+
{
205+
return automationPeer.Match(sender);
206+
});
207+
208+
if (m_uia.AutomationPeers.end() == iterator)
209+
{
210+
// Did not find any match for this automation provider.
211+
return { 0, 0, 0, 0 };
212+
}
213+
214+
auto const& visualPeer = iterator->GetVisual();
215+
winrt::Rect logicalRect;
216+
logicalRect.X = visualPeer.Offset().x;
217+
logicalRect.Y = visualPeer.Offset().y;
218+
logicalRect.Width = visualPeer.Size().x;
219+
logicalRect.Height = visualPeer.Size().y;
220+
221+
// This will convert from the logical coordinate space of the ContentIsland to
222+
// Win32 screen coordinates that are needed by Accessibility.
223+
return m_island.CoordinateConverter().ConvertLocalToScreen(logicalRect);
224+
}
225+
226+
227+
winrt::com_ptr<IRawElementProviderFragment>
228+
DrawingIsland::GetFragmentFromPoint(
229+
_In_ double x,
230+
_In_ double y) const
231+
{
232+
// UIA provides hit test points in screen space.
233+
// Convert the point into a dummy empty rectangle to use with the coordinate converter.
234+
winrt::Windows::Graphics::RectInt32 screenRect{ static_cast<int>(x + 0.5), static_cast<int>(y + 0.5), 0, 0 };
235+
auto logicalRect = m_island.CoordinateConverter().ConvertScreenToLocal(screenRect);
236+
float2 localPoint{ logicalRect.X, logicalRect.Y };
237+
auto hitTestVisual = HitTestVisual(localPoint);
238+
239+
// Find the automation peer for the hit test visual if any.
240+
if (nullptr != hitTestVisual)
241+
{
242+
auto iterator = std::find_if(
243+
m_uia.AutomationPeers.begin(), m_uia.AutomationPeers.end(), [&hitTestVisual](auto const& automationPeer)
244+
{
245+
return automationPeer.Match(hitTestVisual);
246+
});
247+
248+
if (m_uia.AutomationPeers.end() != iterator)
249+
{
250+
// Return the automation provider if we found an automation peer for the hit test visual.
251+
return iterator->GetAutomationProvider().as<IRawElementProviderFragment>();
252+
}
253+
}
254+
255+
return nullptr;
256+
}
257+
258+
259+
winrt::com_ptr<IRawElementProviderFragment>
260+
DrawingIsland::GetFragmentInFocus() const
261+
{
262+
// Find the currently selected visual's automation peer.
263+
auto iterator = std::find_if(
264+
m_uia.AutomationPeers.begin(), m_uia.AutomationPeers.end(), [visual = m_items.SelectedVisual](auto const& automationPeer)
265+
{
266+
return automationPeer.Match(visual);
267+
});
268+
269+
if (m_uia.AutomationPeers.end() != iterator)
270+
{
271+
// Return the automation provider if we found an automation peer for the selected visual.
272+
return iterator->GetAutomationProvider().as<IRawElementProviderFragment>();
273+
}
274+
275+
return nullptr;
276+
}
277+
278+
176279
winrt::Visual
177280
DrawingIsland::HitTestVisual(
178-
float2 const point)
281+
float2 const& point) const
179282
{
180283
winrt::Visual selectedVisual{ nullptr };
181284
for (winrt::Visual visual : m_items.Visuals)
@@ -199,53 +302,51 @@ namespace winrt::DrawingIslandComponents::implementation
199302
void
200303
DrawingIsland::Accessibility_Initialize()
201304
{
202-
m_uia.FragmentRoot = winrt::make_self<IslandFragmentRoot>(m_island);
305+
// Create an UI automation fragment root for our island's content.
306+
m_uia.FragmentRoot = winrt::make_self<IslandFragmentRoot>();
203307
m_uia.FragmentRoot->SetName(L"Drawing Squares");
204-
205-
m_uia.FragmentFactory = winrt::make_self<NodeSimpleFragmentFactory>();
308+
m_uia.FragmentRoot->SetProviderOptions(
309+
ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading | ProviderOptions_RefuseNonClientSupport);
310+
m_uia.FragmentRoot->SetUiaControlTypeId(UIA_WindowControlTypeId);
311+
m_uia.FragmentRoot->SetCallbackHandler(this);
206312

207313
(void)m_island.AutomationProviderRequested(
208314
{ this, &DrawingIsland::Accessibility_OnAutomationProviderRequested });
209315
}
210316

211317

212318
void
213-
DrawingIsland::Accessibility_CreateItemFragment()
319+
DrawingIsland::Accessibility_CreateItemFragment(
320+
const winrt::Visual& itemVisual)
214321
{
215-
// Create a new fragment for the new item.
216-
winrt::com_ptr<NodeSimpleFragment> fragment = m_uia.FragmentFactory->Create(
217-
s_colorNames[m_output.CurrentColorIndex].c_str(), m_uia.FragmentRoot);
322+
// Create a new automation fragment.
323+
auto fragment = winrt::make_self<NodeSimpleFragment>();
324+
fragment->SetName(s_colorNames[m_output.CurrentColorIndex].c_str());
325+
fragment->SetCallbackHandler(this);
218326

219-
// Store the mapping between the Visual and the Fragment:
327+
// Add an entry to our automation peers which is a mapping between the Visual and the Fragment:
220328
// - This is keeping the UIA objects alive.
221329
// - Although not used yet, the lookup would be used to get back to the item Fragment for
222330
// specific operations, such as hit-testing or tree walking. This avoids adding to more
223331
// expensive data storage, such as the Visual.CustomProperties map.
332+
m_uia.AutomationPeers.push_back(AutomationPeer{ itemVisual, fragment });
224333

225-
m_uia.VisualToFragmentMap[m_items.SelectedVisual] = fragment;
226-
227-
// Give the fragment a backpointer to the Visual to get real-time information.
228-
fragment->SetVisual(m_items.SelectedVisual);
229-
230-
// Add the new fragment into the UIA tree.
231-
m_uia.FragmentRoot->AddChild(fragment);
232-
233-
// Finally set up parent chain
234-
fragment->SetParent(m_uia.FragmentRoot);
334+
// Connect the automation fragment to our fragment root.
335+
m_uia.FragmentRoot->AddChildToEnd(fragment);
235336
}
236337

237338

238339
void
239340
DrawingIsland::Accessibility_OnAutomationProviderRequested(
240-
const winrt::ContentIsland& /*island*/,
341+
const winrt::ContentIsland& island,
241342
const winrt::ContentIslandAutomationProviderRequestedEventArgs& args)
242343
{
243-
IInspectable providerAsIInspectable;
244-
m_uia.FragmentRoot->QueryInterface(
245-
winrt::guid_of<IInspectable>(),
246-
winrt::put_abi(providerAsIInspectable));
344+
// Make sure the host provider is up to date.
345+
m_uia.FragmentRoot->SetHostProvider(
346+
island.GetAutomationHostProvider().as<IRawElementProviderSimple>());
247347

248-
args.AutomationProvider(std::move(providerAsIInspectable));
348+
// Return the fragment root as an IInspectable.
349+
args.AutomationProvider(m_uia.FragmentRoot.as<IInspectable>());
249350

250351
args.Handled(true);
251352
}
@@ -286,8 +387,7 @@ namespace winrt::DrawingIslandComponents::implementation
286387
Input_OnPointerReleased();
287388
});
288389

289-
// Set up the keyboard source. We store this in a member variable so we can easily call
290-
// TrySetFocus() in response to left clicks in the content later on.
390+
// Set up the keyboard source.
291391
m_input.KeyboardSource = winrt::InputKeyboardSource::GetForIsland(m_island);
292392

293393
m_input.KeyboardSource.KeyDown(
@@ -407,6 +507,11 @@ namespace winrt::DrawingIslandComponents::implementation
407507
case winrt::Windows::System::VirtualKey::Escape:
408508
{
409509
m_items.Visuals.RemoveAll();
510+
511+
// Update accessibility.
512+
m_uia.FragmentRoot->RemoveAllChildren();
513+
m_uia.AutomationPeers.clear();
514+
410515
handled = true;
411516
break;
412517
}
@@ -515,7 +620,19 @@ namespace winrt::DrawingIslandComponents::implementation
515620
m_items.Visuals.Remove(m_items.SelectedVisual);
516621
m_items.Visuals.InsertAtTop(m_items.SelectedVisual);
517622

518-
// TODO: The m_uia.FragmentRoots child should be removed and added to the end as well.
623+
// Update automation.
624+
// First find the existing automation peer.
625+
auto iterator = std::find_if(
626+
m_uia.AutomationPeers.begin(), m_uia.AutomationPeers.end(), [visual = m_items.SelectedVisual](auto const& automationPeer)
627+
{
628+
return automationPeer.Match(visual);
629+
});
630+
if (m_uia.AutomationPeers.end() != iterator)
631+
{
632+
// Mirror the change to the visuals above.
633+
m_uia.FragmentRoot->RemoveChild(iterator->GetAutomationProvider());
634+
m_uia.FragmentRoot->AddChildToEnd(iterator->GetAutomationProvider());
635+
}
519636
}
520637
else
521638
{
@@ -644,7 +761,7 @@ namespace winrt::DrawingIslandComponents::implementation
644761
m_items.Offset.x = -BlockSize / 2.0f;
645762
m_items.Offset.y = -BlockSize / 2.0f;
646763

647-
Accessibility_CreateItemFragment();
764+
Accessibility_CreateItemFragment(visual);
648765
}
649766

650767

Samples/Islands/DrawingIsland/DrawingIslandComponents/DrawingIsland.h

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44
#pragma once
55

66
#include "DrawingIsland.g.h"
7-
#include "NodeSimpleFragment.h"
7+
#include "IslandFragmentRoot.h"
88

99
namespace winrt::DrawingIslandComponents::implementation
1010
{
1111
struct DrawingIsland :
1212
public DrawingIslandT<
1313
DrawingIsland,
14-
Microsoft::UI::Input::IInputPreTranslateKeyboardSourceHandler>
14+
Microsoft::UI::Input::IInputPreTranslateKeyboardSourceHandler>,
15+
IAutomationCallbackHandler
1516
{
1617
public:
1718
DrawingIsland(
@@ -78,13 +79,24 @@ namespace winrt::DrawingIslandComponents::implementation
7879
UINT keyboardModifiers,
7980
_Inout_ bool* handled);
8081

82+
// IAutomationCallbackHandler overrides.
83+
winrt::Windows::Graphics::RectInt32 GetScreenBoundsForAutomationFragment(
84+
_In_::IUnknown const* const sender) const override;
85+
86+
winrt::com_ptr<IRawElementProviderFragment> GetFragmentFromPoint(
87+
_In_ double x,
88+
_In_ double y) const override;
89+
90+
winrt::com_ptr<IRawElementProviderFragment> GetFragmentInFocus() const override;
91+
8192
private:
8293
winrt::Visual HitTestVisual(
83-
float2 const point);
94+
float2 const& point) const;
8495

8596
void Accessibility_Initialize();
8697

87-
void Accessibility_CreateItemFragment();
98+
void Accessibility_CreateItemFragment(
99+
const winrt::Visual& itemVisual);
88100

89101
void Accessibility_OnAutomationProviderRequested(
90102
const winrt::ContentIsland& island,
@@ -244,17 +256,50 @@ namespace winrt::DrawingIslandComponents::implementation
244256
winrt::ContentLayoutDirection LayoutDirection =
245257
winrt::ContentLayoutDirection::LeftToRight;
246258
} m_prevState;
259+
260+
struct AutomationPeer
261+
{
262+
explicit AutomationPeer(
263+
winrt::Visual const& visual,
264+
winrt::com_ptr<NodeSimpleFragment> const& automationProvider) :
265+
_visual{ visual },
266+
_automationProvider{ automationProvider }
267+
{
268+
269+
}
270+
271+
winrt::Visual const& GetVisual() const
272+
{
273+
return _visual;
274+
}
275+
276+
winrt::com_ptr<NodeSimpleFragment> const& GetAutomationProvider() const
277+
{
278+
return _automationProvider;
279+
}
280+
281+
bool Match(winrt::Visual const& visual) const noexcept
282+
{
283+
return visual == _visual;
284+
}
285+
286+
bool Match(::IUnknown const* const automationProviderAsIUnknown) const noexcept
287+
{
288+
return automationProviderAsIUnknown == _automationProvider.try_as<::IUnknown>().get();
289+
}
290+
291+
private:
292+
winrt::Visual _visual{ nullptr };
293+
winrt::com_ptr<NodeSimpleFragment> _automationProvider{ nullptr };
294+
};
247295

248296
struct
249297
{
250-
// Our UIA object to create fragments
251-
winrt::com_ptr<NodeSimpleFragmentFactory> FragmentFactory{ nullptr };
252-
253298
// The UIA parent Root for this Island that contains the fragment children.
254299
winrt::com_ptr<IslandFragmentRoot> FragmentRoot{ nullptr };
255300

256-
// Map a given square (Visual) to its UIA fragment object
257-
std::map<winrt::Visual, winrt::com_ptr<NodeSimpleFragment>> VisualToFragmentMap;
301+
// Map a given square (Visual) to its UIA fragment object.
302+
std::vector<AutomationPeer> AutomationPeers{};
258303
} m_uia;
259304
};
260305
}

0 commit comments

Comments
 (0)