Skip to content

Commit

Permalink
Add TabView SelectionPattern (#2856)
Browse files Browse the repository at this point in the history
* Add TabView SelectionPattern

* Add logic for accessing parent tabview

* Add test

* Implement SelectionItem pattern and provider

* Remove parent tabview getter

* Fix missing SelectionContainer

* Fix compile time error and add nullcheck for SelectionContainer
  • Loading branch information
marcelwgn authored Jul 20, 2020
1 parent 347c4bd commit 542e6f9
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 5 deletions.
85 changes: 84 additions & 1 deletion dev/TabView/APITests/TabViewTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using MUXControlsTestApp.Utilities;
Expand All @@ -10,6 +10,9 @@
using Common;
using Microsoft.UI.Xaml.Controls;
using System.Collections.Generic;
using Windows.UI.Xaml.Automation.Peers;
using Windows.UI.Xaml.Automation;
using Windows.UI.Xaml.Automation.Provider;

#if USING_TAEF
using WEX.TestExecution;
Expand Down Expand Up @@ -84,6 +87,86 @@ public void VerifyCompactTabWidthVisualStates()
});
}

[TestMethod]
public void VerifyTabViewUIABehavior()
{
RunOnUIThread.Execute(() =>
{
TabView tabView = new TabView();
Content = tabView;

tabView.TabItems.Add(CreateTabViewItem("Item 0", Symbol.Add));
tabView.TabItems.Add(CreateTabViewItem("Item 1", Symbol.AddFriend));
tabView.TabItems.Add(CreateTabViewItem("Item 2"));

Content.UpdateLayout();

var tabViewPeer = FrameworkElementAutomationPeer.CreatePeerForElement(tabView);
Verify.IsNotNull(tabViewPeer);
var tabViewSelectionPattern = tabViewPeer.GetPattern(PatternInterface.Selection);
Verify.IsNotNull(tabViewSelectionPattern);
var selectionProvider = tabViewSelectionPattern as ISelectionProvider;
// Tab controls must require selection
Verify.IsTrue(selectionProvider.IsSelectionRequired);
});
}

[TestMethod]
public void VerifyTabViewItemUIABehavior()
{
TabView tabView = null;

TabViewItem tvi0 = null;
TabViewItem tvi1 = null;
TabViewItem tvi2 = null;
RunOnUIThread.Execute(() =>
{
tabView = new TabView();
Content = tabView;

tvi0 = CreateTabViewItem("Item 0", Symbol.Add);
tvi1 = CreateTabViewItem("Item 1", Symbol.AddFriend);
tvi2 = CreateTabViewItem("Item 2");

tabView.TabItems.Add(tvi0);
tabView.TabItems.Add(tvi1);
tabView.TabItems.Add(tvi2);

tabView.SelectedIndex = 0;
tabView.SelectedItem = tvi0;
Content.UpdateLayout();
});

IdleSynchronizer.Wait();

RunOnUIThread.Execute(() =>
{
var selectionItemProvider = GetProviderFromTVI(tvi0);
Verify.IsTrue(selectionItemProvider.IsSelected,"Item should be selected");

selectionItemProvider = GetProviderFromTVI(tvi1);
Verify.IsFalse(selectionItemProvider.IsSelected, "Item should not be selected");

Log.Comment("Change selection through automationpeer");
selectionItemProvider.Select();
Verify.IsTrue(selectionItemProvider.IsSelected, "Item should have been selected");

selectionItemProvider = GetProviderFromTVI(tvi0);
Verify.IsFalse(selectionItemProvider.IsSelected, "Item should not be selected anymore");

Verify.IsNotNull(selectionItemProvider.SelectionContainer);
});

static ISelectionItemProvider GetProviderFromTVI(TabViewItem item)
{
var peer = FrameworkElementAutomationPeer.CreatePeerForElement(item);
var provider = peer.GetPattern(PatternInterface.SelectionItem)
as ISelectionItemProvider;
Verify.IsNotNull(provider);
return provider;
}
}

private static void VerifyTabWidthVisualStates(IList<object> items, bool isCompact)
{
foreach (var item in items)
Expand Down
3 changes: 2 additions & 1 deletion dev/TabView/TabView.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

#include "pch.h"
Expand Down Expand Up @@ -638,6 +638,7 @@ void TabView::OnItemsChanged(winrt::IInspectable const& item)
if (const auto newItem = TabItems().GetAt(args.Index()).try_as<TabViewItem>())
{
newItem->OnTabViewWidthModeChanged(TabWidthMode());
newItem->SetParentTabView(*this);
}
UpdateTabWidths();
}
Expand Down
28 changes: 28 additions & 0 deletions dev/TabView/TabViewAutomationPeer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ TabViewAutomationPeer::TabViewAutomationPeer(winrt::TabView const& owner)
// IAutomationPeerOverrides
winrt::IInspectable TabViewAutomationPeer::GetPatternCore(winrt::PatternInterface const& patternInterface)
{
if (patternInterface == winrt::PatternInterface::Selection)
{
return *this;
}
return __super::GetPatternCore(patternInterface);
}

Expand All @@ -31,3 +35,27 @@ winrt::AutomationControlType TabViewAutomationPeer::GetAutomationControlTypeCore
return winrt::AutomationControlType::Tab;
}

bool TabViewAutomationPeer::CanSelectMultiple()
{
return false;
}

bool TabViewAutomationPeer::IsSelectionRequired()
{
return true;
}

winrt::com_array<winrt::Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple> TabViewAutomationPeer::GetSelection()
{
if (auto tabView = Owner().try_as<TabView>())
{
if (auto tabViewItem = tabView->ContainerFromIndex(tabView->SelectedIndex()).try_as<winrt::TabViewItem>())
{
if (auto peer = winrt::FrameworkElementAutomationPeer::CreatePeerForElement(tabViewItem))
{
return { ProviderFromPeer(peer) };
}
}
}
return {};
}
9 changes: 8 additions & 1 deletion dev/TabView/TabViewAutomationPeer.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
#include "TabViewAutomationPeer.g.h"

class TabViewAutomationPeer :
public ReferenceTracker<TabViewAutomationPeer, winrt::implementation::TabViewAutomationPeerT>
public ReferenceTracker<TabViewAutomationPeer,
winrt::implementation::TabViewAutomationPeerT,
winrt::ISelectionProvider>
{

public:
Expand All @@ -17,4 +19,9 @@ class TabViewAutomationPeer :
winrt::IInspectable GetPatternCore(winrt::PatternInterface const& patternInterface);
hstring GetClassNameCore();
winrt::AutomationControlType GetAutomationControlTypeCore();

// ISelectionProvider
bool CanSelectMultiple();
bool IsSelectionRequired();
winrt::com_array<winrt::Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple> GetSelection();
};
15 changes: 15 additions & 0 deletions dev/TabView/TabViewItem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ void TabViewItem::OnApplyTemplate()

void TabViewItem::OnIsSelectedPropertyChanged(const winrt::DependencyObject& sender, const winrt::DependencyProperty& args)
{
if (const auto peer = winrt::FrameworkElementAutomationPeer::CreatePeerForElement(*this))
{
peer.RaiseAutomationEvent(winrt::AutomationEvents::SelectionItemPatternOnElementSelected);
}

if (IsSelected())
{
SetValue(winrt::Canvas::ZIndexProperty(),box_value(20));
Expand Down Expand Up @@ -142,6 +147,16 @@ void TabViewItem::OnCloseButtonOverlayModeChanged(winrt::TabViewCloseButtonOverl
UpdateCloseButton();
}

winrt::TabView TabViewItem::GetParentTabView()
{
return m_parentTabView.get();
}

void TabViewItem::SetParentTabView(winrt::TabView const& tabView)
{
m_parentTabView = winrt::make_weak(tabView);
}

void TabViewItem::OnTabViewWidthModeChanged(winrt::TabViewWidthMode const& mode)
{
m_tabViewWidthMode = mode;
Expand Down
5 changes: 5 additions & 0 deletions dev/TabView/TabViewItem.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ class TabViewItem :
void OnTabViewWidthModeChanged(winrt::TabViewWidthMode const& mode);
void OnCloseButtonOverlayModeChanged(winrt::TabViewCloseButtonOverlayMode const& mode);

winrt::TabView GetParentTabView();
void SetParentTabView(winrt::TabView const& tabView);

private:
tracker_ref<winrt::Button> m_closeButton{ this };
tracker_ref<winrt::ToolTip> m_toolTip{ this };
Expand Down Expand Up @@ -72,4 +75,6 @@ class TabViewItem :

void UpdateShadow();
winrt::IInspectable m_shadow{ nullptr };

winrt::weak_ref<winrt::TabView> m_parentTabView{ nullptr };
};
62 changes: 61 additions & 1 deletion dev/TabView/TabViewItemAutomationPeer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ TabViewItemAutomationPeer::TabViewItemAutomationPeer(winrt::TabViewItem const& o
}

// IAutomationPeerOverrides
winrt::IInspectable TabViewItemAutomationPeer::GetPatternCore(winrt::PatternInterface const& patternInterface)
{
if (patternInterface == winrt::PatternInterface::SelectionItem)
{
return *this;
}
return __super::GetPatternCore(patternInterface);
}

hstring TabViewItemAutomationPeer::GetClassNameCore()
{
return winrt::hstring_name_of<winrt::TabViewItem>();
Expand All @@ -27,7 +36,6 @@ winrt::AutomationControlType TabViewItemAutomationPeer::GetAutomationControlType
return winrt::AutomationControlType::TabItem;
}


winrt::hstring TabViewItemAutomationPeer::GetNameCore()
{
winrt::hstring returnHString = __super::GetNameCore();
Expand All @@ -43,3 +51,55 @@ winrt::hstring TabViewItemAutomationPeer::GetNameCore()

return returnHString;
}


bool TabViewItemAutomationPeer::IsSelected()
{
if (auto tvi = Owner().try_as<TabViewItem>())
{
return tvi->IsSelected();
}
return false;
}

winrt::IRawElementProviderSimple TabViewItemAutomationPeer::SelectionContainer()
{
if (const auto parent = GetParentTabView())
{
if (const auto peer = winrt::FrameworkElementAutomationPeer::CreatePeerForElement(parent))
{
return ProviderFromPeer(peer);
}
}
return nullptr;
}

void TabViewItemAutomationPeer::AddToSelection()
{
Select();
}

void TabViewItemAutomationPeer::RemoveFromSelection()
{
// Can't unselect in a TabView without knowing next selection
}

void TabViewItemAutomationPeer::Select()
{
if (auto owner = Owner().try_as<TabViewItem>().get())
{
owner->IsSelected(true);
}
}

winrt::TabView TabViewItemAutomationPeer::GetParentTabView()
{
winrt::TabView parentTabView{ nullptr };

winrt::TabViewItem tabViewItem = Owner().try_as<winrt::TabViewItem>();
if (tabViewItem)
{
parentTabView = winrt::get_self<TabViewItem>(tabViewItem)->GetParentTabView();
}
return parentTabView;
}
15 changes: 14 additions & 1 deletion dev/TabView/TabViewItemAutomationPeer.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,27 @@
#include "TabViewItemAutomationPeer.g.h"

class TabViewItemAutomationPeer :
public ReferenceTracker<TabViewItemAutomationPeer, winrt::implementation::TabViewItemAutomationPeerT>
public ReferenceTracker < TabViewItemAutomationPeer,
winrt::implementation::TabViewItemAutomationPeerT,
winrt::ISelectionItemProvider >
{

public:
TabViewItemAutomationPeer(winrt::TabViewItem const& owner);

// IAutomationPeerOverrides
winrt::IInspectable GetPatternCore(winrt::PatternInterface const& patternInterface);
winrt::hstring GetNameCore();
hstring GetClassNameCore();
winrt::AutomationControlType GetAutomationControlTypeCore();

// ISelectionItemProvider
bool IsSelected();
winrt::IRawElementProviderSimple SelectionContainer();
void AddToSelection();
void RemoveFromSelection();
void Select();

private:
winrt::TabView GetParentTabView();
};

0 comments on commit 542e6f9

Please sign in to comment.