Search code examples
accessibilitykeyboard-shortcutswinui-3winuiwindows-app-sdk

How do you underline AccessKey characters (mnemonics) in applications built using WinAppSDK/WinUI?


Microsoft's accessibility guidance for Win32 application development is to assign access keys to menu items:

Assign access keys to all menu items. No exceptions.

In Win32 applications, this is done by placing an ampersand in a menu item's assigned text string, which represents the character that is to be underlined. For example, "&File" would render the string File, but the F character would be underlined:

File menu

I'm trying to understand how to accomplish this same thing in a Windows App SDK application that's using WinUI.

I'm aware of the AccessKey property that can be set on a MenuBarItem control. This mimics Microsoft Office's implementation of access keys in the Ribbon control. It causes the specified character to appear in a tooltip after the Alt key is pressed.

For example, this markup:

<MenuBar>
    <MenuBarItem Title="File" AccessKey="F" />
</MenuBar>

Will render this after the user presses the Alt key:

AccessKey example

But that markup doesn't result in the F character being underlined so that the user knows about the access key's existence before having to press the Alt key (as is the case for Win32 applications, assuming that "Always underline access keys" is enabled in Settings).

According to this document, you have to do this manually using the <Underline/> element:

The underline text decoration for an access key is not provided automatically. You must explicitly underline the text for the specific key in your mnemonic as inline Underline formatting if you wish to show underlined text in the UI.

But the AccessKey property is of type string, so it's unclear to me how to set this to a text block that includes an <Underline/> element.

So using the example markup above, my question is how to make the F character underlined in the "File" menu item.


Solution

  • There's a discussion about this but for the moment, AFAIK, there's no built-in way to do this.

    What you can do is to create a custom MenuBarItem that overrides the Title property from string to object:

    MenuBarItemEx.cs

    using Microsoft.UI.Xaml;
    using Microsoft.UI.Xaml.Controls;
    
    namespace MenuBarExample;
    
    public sealed class MenuBarItemEx : MenuBarItem
    {
        public static new readonly DependencyProperty TitleProperty =
            DependencyProperty.Register(
                nameof(Title),
                typeof(object),
                typeof(MenuBarItemEx),
                new PropertyMetadata(default, OnTitlePropertyChanged));
    
        public MenuBarItemEx()
        {
            this.DefaultStyleKey = typeof(MenuBarItem);
        }
    
        public new object Title
        {
            get => GetValue(TitleProperty);
            set => SetValue(TitleProperty, value);
        }
    
        private Button? ContentButton { get; set; }
    
        protected override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
    
            if (GetTemplateChild("ContentButton") is Button contentButton)
            {
                ContentButton = contentButton;
                ContentButton.Content = Title;
            }
        }
    
        private static void OnTitlePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is MenuBarItemEx menuBarItemEx &&
                menuBarItemEx.ContentButton is Button contentButton)
            {
                contentButton.Content = e.NewValue;
            }
        }
    }
    

    Now you can do this:

    <MenuBar>
        <local:MenuBarItemEx AccessKey="F">
            <local:MenuBarItemEx.Title>
                <RichTextBlock>
                    <Paragraph CharacterSpacing="0">
                        <!-- This have to be in a single line
                        unless you'll get a space between 'F' and 'ile' -->
                        <Underline>F</Underline><Run>ile</Run>
                    </Paragraph>
                </RichTextBlock>
            </local:MenuBarItemEx.Title>
        </local:MenuBarItemEx>
    </MenuBar>
    

    How to get rid of whitespace between Runs in TextBlock?