-
Notifications
You must be signed in to change notification settings - Fork 663
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding accessible context menus to TabView pages #1664
base: main
Are you sure you want to change the base?
Changes from 1 commit
89305ac
12b88ce
37f1d85
877a3da
41a2ee9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
using System.Collections; | ||
using Microsoft.UI.Xaml; | ||
using Microsoft.UI.Xaml.Controls; | ||
using Microsoft.UI.Xaml.Media; | ||
|
||
namespace WinUIGallery.Helpers; | ||
|
||
public static class TabViewHelper | ||
{ | ||
public static void PopulateTabViewContextMenu(MenuFlyout contextMenu) | ||
{ | ||
contextMenu.Items.Clear(); | ||
|
||
var item = (TabViewItem)contextMenu.Target; | ||
ListView tabViewListView = null; | ||
TabView tabView = null; | ||
|
||
DependencyObject current = item; | ||
|
||
while (current != null) | ||
{ | ||
DependencyObject parent = VisualTreeHelper.GetParent(current); | ||
sabareesanms marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (parent is ListView parentTabViewListView) | ||
{ | ||
tabViewListView = parentTabViewListView; | ||
} | ||
else if (parent is TabView parentTabView) | ||
{ | ||
tabView = parentTabView; | ||
} | ||
|
||
if (tabViewListView != null && tabView != null) | ||
{ | ||
break; | ||
} | ||
|
||
current = parent; | ||
} | ||
|
||
if (tabViewListView == null || tabView == null) | ||
{ | ||
return; | ||
} | ||
|
||
// First, if there are tabs to the left or to the right of the tab on which this context menu is opening, | ||
// then we'll include menu items to move this tab to the left or to the right. | ||
// | ||
// There are two possible cases for tab views: either they have explicitly set tab items, or they have a data item source set. | ||
// To move a tab left or right with explicitly set tab items, we'll remove and replace the tab item itself. | ||
// To move a tab left or right with a data item source set, we'll instead remove and replace the data item in the source list. | ||
int index = tabViewListView.IndexFromContainer(item); | ||
|
||
if (index > 0) | ||
{ | ||
MenuFlyoutItem moveLeftItem = new() { Text = "Move tab left" }; | ||
moveLeftItem.Click += (s, args) => | ||
{ | ||
if (tabView.TabItemsSource is IList itemsSourceList) | ||
{ | ||
var item = itemsSourceList[index]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will |
||
itemsSourceList.RemoveAt(index); | ||
itemsSourceList.Insert(index - 1, item); | ||
} | ||
else | ||
{ | ||
var item = tabView.TabItems[index]; | ||
tabView.TabItems.RemoveAt(index); | ||
tabView.TabItems.Insert(index - 1, item); | ||
} | ||
}; | ||
contextMenu.Items.Add(moveLeftItem); | ||
} | ||
|
||
if (index < tabViewListView.Items.Count - 1) | ||
{ | ||
MenuFlyoutItem moveRightItem = new() { Text = "Move tab right" }; | ||
moveRightItem.Click += (s, args) => | ||
{ | ||
if (tabView.TabItemsSource is IList itemsSourceList) | ||
{ | ||
var item = itemsSourceList[index]; | ||
itemsSourceList.RemoveAt(index); | ||
itemsSourceList.Insert(index + 1, item); | ||
} | ||
else | ||
{ | ||
var item = tabView.TabItems[index]; | ||
tabView.TabItems.RemoveAt(index); | ||
tabView.TabItems.Insert(index + 1, item); | ||
} | ||
}; | ||
contextMenu.Items.Add(moveRightItem); | ||
} | ||
|
||
// If the context menu ended up with no items at all, then we'll prevent it from being shown. | ||
if (contextMenu.Items.Count == 0) | ||
{ | ||
contextMenu.Hide(); | ||
sabareesanms marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,4 +63,21 @@ static public void AnnounceActionForAccessibility(UIElement ue, string annouceme | |
peer.RaiseNotificationEvent(AutomationNotificationKind.ActionCompleted, | ||
AutomationNotificationProcessing.ImportantMostRecent, annoucement, activityID); | ||
} | ||
|
||
public static T GetParent<T>(DependencyObject child) where T : DependencyObject | ||
{ | ||
DependencyObject current = child; | ||
|
||
while (current != null) | ||
{ | ||
if (current is T parent) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's behavior of this function that we want (not current behavior) when current DO is of type There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is reasonable to return the first result, otherwise you would need to scan up to the root to find the latest T |
||
{ | ||
return parent; | ||
} | ||
|
||
current = VisualTreeHelper.GetParent(current); | ||
} | ||
|
||
return null; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,7 @@ | |
<TextBlock Text="{Binding}" Style="{ThemeResource TitleTextBlockStyle}" /> | ||
<TextBlock Text="Drag the Tab outside of the window to spawn a new window." Style="{ThemeResource SubtitleTextBlockStyle}" /> | ||
<TextBlock Text="Notice that the state of the Tab is maintained in the new window. For example, if you toggle the ToggleSwitch ON, it will remain ON in the new window." Style="{ThemeResource BodyTextBlockStyle}" /> | ||
<ToggleSwitch x:Name="ControlToggle" Header="Turn on ProgressRing" Margin="0,8" /> | ||
<ProgressRing IsActive="{x:Bind ControlToggle.IsOn, Mode=OneWay}" HorizontalAlignment="Left" /> | ||
<ToggleSwitch x:Name="ControlToggle" Header="Turn on ProgressRing" Margin="0,8" IsOn="{x:Bind IsInProgress, Mode=TwoWay}" /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am unclear on why this change is being made & net impact on UX. Can you explain or add screenshot in PR Description. |
||
<ProgressRing IsActive="{x:Bind IsInProgress, Mode=OneWay}" HorizontalAlignment="Left" /> | ||
</StackPanel> | ||
</UserControl> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,22 +9,36 @@ | |
mc:Ignorable="d"> | ||
|
||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> | ||
<TabView | ||
x:Name="Tabs" | ||
VerticalAlignment="Stretch" | ||
AddTabButtonClick="Tabs_AddTabButtonClick" | ||
CanTearOutTabs="True" | ||
ExternalTornOutTabsDropped="Tabs_ExternalTornOutTabsDropped" | ||
ExternalTornOutTabsDropping="Tabs_ExternalTornOutTabsDropping" | ||
TabCloseRequested="Tabs_TabCloseRequested" | ||
TabTearOutRequested="Tabs_TabTearOutRequested" | ||
TabTearOutWindowRequested="Tabs_TabTearOutWindowRequested"> | ||
<TabView | ||
x:Name="Tabs" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you fix indentation and why order of event subscription being changed for all the items (only diff seems addition of |
||
VerticalAlignment="Stretch" | ||
AddTabButtonClick="Tabs_AddTabButtonClick" | ||
TabCloseRequested="Tabs_TabCloseRequested" | ||
CanTearOutTabs="True" | ||
TabTearOutWindowRequested="Tabs_TabTearOutWindowRequested" | ||
TabTearOutRequested="Tabs_TabTearOutRequested" | ||
ExternalTornOutTabsDropping="Tabs_ExternalTornOutTabsDropping" | ||
ExternalTornOutTabsDropped="Tabs_ExternalTornOutTabsDropped" | ||
TabItemsSource="{x:Bind TabItemDataList}"> | ||
<TabView.TabStripHeader> | ||
<Grid x:Name="ShellTitleBarInset" Background="Transparent" /> | ||
</TabView.TabStripHeader> | ||
<TabView.TabStripFooter> | ||
<Grid x:Name="CustomDragRegion" Background="Transparent" /> | ||
</TabView.TabStripFooter> | ||
<TabView.TabItemTemplate> | ||
<DataTemplate x:DataType="local:TabItemData"> | ||
<TabViewItem Header="{x:Bind Header}"> | ||
<TabViewItem.ContextFlyout> | ||
<MenuFlyout Opening="TabViewContextMenu_Opening" /> | ||
</TabViewItem.ContextFlyout> | ||
<TabViewItem.IconSource> | ||
<SymbolIconSource Symbol="Placeholder" /> | ||
</TabViewItem.IconSource> | ||
<local:TabContentSampleControl DataContext="{x:Bind Content}" IsInProgress="{x:Bind IsInProgress, Mode=TwoWay}" /> | ||
</TabViewItem> | ||
</DataTemplate> | ||
</TabView.TabItemTemplate> | ||
</TabView> | ||
</Grid> | ||
</Page> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we check type first before typecasting?