Skip to content

Adding accessible context menus to TabView pages - #1664

Closed
Luke Longley-Preston (llongley) wants to merge 10 commits into
mainfrom
user/llongley/TabViewAccessibility
Closed

Luke Longley-Preston (llongley) wants to merge 10 commits into
mainfrom
user/llongley/TabViewAccessibility

Conversation

@llongley

Copy link
Copy Markdown
Member

In order for TabView pages to be sufficiently accessible, there needs to be a way for users to rearrange tabs using a single pointer input without the need for precise manipulation. To achieve this, I've added context menus for each TabView that allows users to move tabs left and right, as well as moving tabs to other windows in the case of the tab tear-out example.

In working on this change, I found that there's a bug in WinUI 3 where moving a TabViewItem from one TabView in one window to another in another window retains the previous XamlRoot value, which throws an exception when we try to open a context menu on that TabViewItem, since it doesn't know what its correct XamlRoot is. I've filed a bug on that issue, but for now, to work around that, I've switched from using explicitly defined TabViewItems to using a data item source, which causes each TabView to generate its own TabViewItems.

Finally, I also found a bug in Win32WindowHelper - newWndProc and oldWndProc should not be static, because the class itself is instantiated on a per-window basis. Having these be static can result in an EngineExecutionException as a result of these delegates being garbage collected while there's still a native reference to them. Changing them to be instance fields fixes this issue.

@niels9001

Copy link
Copy Markdown
Collaborator

Luke Longley-Preston (@llongley) FYI, looks like a conflict popped up

@llongley
Luke Longley-Preston (llongley) force-pushed the user/llongley/TabViewAccessibility branch from c340efc to d3dd80d Compare January 17, 2025 23:03
@niels9001

Copy link
Copy Markdown
Collaborator

/azp run

@llongley

Copy link
Copy Markdown
Member Author

/azp run

1 similar comment
@niels9001

Copy link
Copy Markdown
Collaborator

/azp run

@niels9001

Copy link
Copy Markdown
Collaborator

Luke Longley-Preston (@llongley) A few conflicts popped up because of the indention changed caused by #1700. Once those are resolved we can merge this in :)

@niels9001

Copy link
Copy Markdown
Collaborator

Luke Longley-Preston (@llongley) FYI I fixed the conflicts and enabled Mica on the tabs demo

@niels9001

Copy link
Copy Markdown
Collaborator

/azp run

Comment thread WinUIGallery/TabViewPages/TabViewWindowingSamplePage.xaml.cs Outdated
@marcelwgn

Copy link
Copy Markdown
Contributor

Closing this PR for now since there is quite a lot of work needed to get this mergeable but this PR can of course be reopened if you wish to continue working on this.

@sabareesanms

Copy link
Copy Markdown
Contributor

Hi Marcel W. (@marcelwgn) . Currently we are working on this PR and fixing the issues addressed.

@niels9001

Copy link
Copy Markdown
Collaborator

Hi Marcel W. (@marcelwgn) . Currently we are working on this PR and fixing the issues addressed.

sabareesanms FYI - since the opening of this PR there were many changes in terms of project/solution structure and where files now live. That's why there are so many conflicts.

We are in a much better state now and we are not anticipating any other big changes like that at this point!

@niels9001

Copy link
Copy Markdown
Collaborator

/azp run

@marcelwgn Marcel W. (marcelwgn) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that you can crash the app when dragging the last tab from a window 🤔

@sabareesanms

Copy link
Copy Markdown
Contributor

Marcel W. (@marcelwgn) we have already added checks for not allowing drag when there is only one tab exists. Can you give more details if you still see the issue?

@akanpatel2206

Copy link
Copy Markdown
Contributor

Can we have screenshots/videos of before and after fix for visualization?

Comment thread WinUIGallery/Helpers/TabViewHelper.cs
{
if (tabView.TabItemsSource is IList itemsSourceList)
{
var item = itemsSourceList[index];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will index get copied over to lambda as it will be cleaned up from this method before the Click event actually happen?


while (current != null)
{
if (current is T parent)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 T and it has a parent of type T?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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

Comment thread WinUIGallery/Helpers/TabViewHelper.cs Outdated

public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register("Header", typeof(string), typeof(TabItemData), new PropertyMetadata(""));
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(string), typeof(TabItemData), new PropertyMetadata(""));
public static readonly DependencyProperty IsInProgressProperty = DependencyProperty.Register("Content", typeof(bool), typeof(TabItemData), new PropertyMetadata(false));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be IsInProgress?

<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}" />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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.


currentWindow.Closed += (s, args) =>
{
windowList.Remove(currentWindow);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have to unregister the Closed event as well from currentWindow?

return tabView;
}
source.Remove((TabItemData)tabItemData);
destination.Insert(index, (TabItemData)tabItemData);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sabareesanms This is where the app is crashing for me, trying to insert at index 1 while destination is empty. The repro is to move the last tab of a window out and you should be able to this the crash.

TabTearOutRequested="Tabs_TabTearOutRequested"
TabTearOutWindowRequested="Tabs_TabTearOutWindowRequested">
<TabView
x:Name="Tabs"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 TabItemsSource)?

private void TabViewContextMenu_Opening(object sender, object e)
{
// The contents of the context menu depends on the state of the application, so we'll build it dynamically.
MenuFlyout contextMenu = (MenuFlyout)sender;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it safe to assume the sender is always a MenuFlyout?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if it is unlikely not to be, I would rather use as and return if it is in fact not a MenuFlyout instead of having the potential to crash here.

Comment thread WinUIGallery/Helpers/TabViewHelper.cs Outdated
{
contextMenu.Items.Clear();

var item = (TabViewItem)contextMenu.Target;

Copy link
Copy Markdown

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?

</_NoneWithTargetPath>
</ItemGroup>
</Target>
<!-- Needed in order for running unpackaged to work properly. -->

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this change now and not beforehand?

@Zakariathr22

Copy link
Copy Markdown
Contributor

I was thinking if it is possible to place the TabView items inside the left header of a TitleBar control?

@niels9001

Copy link
Copy Markdown
Collaborator

Closing this PR as it seems to become stale. Feel free to reopen when work continues.

@niels9001
Niels Laute (niels9001) deleted the user/llongley/TabViewAccessibility branch September 4, 2026 07:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

10 participants