uwpradio-buttonuwp-xamlkeyboard-navigation

How to enable arrow keys keyboard navigation and disable tab nagivation for a group of radio buttons?


I was trying to implement a dialog box with layout as something like this
enter image description here

I wanted keyboard behavior as

  1. Control at radio button 'a' OR 'b' OR 'c' -> Tab press -> Cancel button -> Tab press -> Save button -> Tab press -> Last selected radio button among 'a' OR 'b' or 'c' (even first radio button 'a' will do).
  2. Arrow key navigation between 'a', 'b' and 'c' radio buttons.

A very easy way is to use <RadioButtons> to group individual <Radiobutton> but that is not affordable with the overall project as using the dependency library winui 2+ is messing up with other parts of the code and affecting functionality of some feature.

After researching on internet and reading through this documentation, I tried <ListView> which was causing two issues

  1. Pressing spacebar button no longer selects any option.
  2. Narrator always says "Selected" for each radio button even if the radio button is not selected.

Upon further search, I landed upon this .

Code:

<ContentDialog>
   <StackPanel>
        <StackPanel XYFocusKeyboardNavigation="Enabled" TabFocusNavigation="Once">
            <RadioButton Content="a" XYFocusKeyboardNavigation="Enabled" />
            <RadioButton Content="b" XYFocusKeyboardNavigation="Enabled" />
            <RadioButton Content="c" XYFocusKeyboardNavigation="Enabled" />
        </StackPanel>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
            <Button x:Uid="Cancel" Name="CancelButton" Click="Cancel" Margin="5" IsEnabled="False"/>
            <Button x:Uid="Save" Name="SaveButton" Click="Save" Margin="5" IsEnabled="False"/>
        </StackPanel>
   </StackPanel>
</ContentDialog>

Upon implementation, most of the issues with narrator and navigation were gone, except for 1 issue:
The control never lands on 'b' radio button.
Being on 'a' radio button, pressing down arrow key doesn't works. Pressing up arrow key moves the control to 'c'.
Being on 'c' radio button, pressing up arrow key doesn't works. Pressing down arrow key moves the control to 'a'.

I even tried XYFocusDownNavigationStrategy="Projection" on 'c' radio button and XYFocusUpNavigationStrategy="Projection" on 'a' radio button, but it didn't work.

How to fix this issue?


Solution

  • Update2

    I'm tried to implement what you want a simple demo by handling the PreviewKeyDown Event. We need to find the RadioButtons first and then set focus state to different buttons based on which key the user has pressed.

    Xaml:

      <ContentDialog x:Name="MyDialog">
            <StackPanel>
                <StackPanel XYFocusKeyboardNavigation="Enabled" PreviewKeyDown="StackPanel_PreviewKeyDown" TabFocusNavigation="Once">
                    <RadioButton Content="a" XYFocusKeyboardNavigation="Enabled" />
                    <RadioButton Content="b" XYFocusKeyboardNavigation="Enabled" />
                    <RadioButton Content="c" XYFocusKeyboardNavigation="Enabled" />
                </StackPanel>
                <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
                    <Button x:Uid="Cancel" Content="Cancel" Name="CancelButton"  Margin="5" IsEnabled="True"/>
                    <Button x:Uid="Save" Content="Save" Name="SaveButton"  Margin="5" IsEnabled="True"/>
                </StackPanel>
            </StackPanel>
        </ContentDialog>
    

    Code-behind:

      //radiobuttons
        public List<RadioButton> radioButtons { get; set; }
        // focus index
        public int focusindex = -1;
        public MainPage()
        {
            this.InitializeComponent();
            radioButtons = new List<RadioButton>();
            this.Loaded += MainPage_Loaded;
        }
    
        private void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            //get all the radiobuttons inside the contentdialog
            DependencyObject contentObject = MyDialog.Content as DependencyObject;
            FindRadioButtons(contentObject, radioButtons);
        }
    
        private void StackPanel_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
        {
            // handle the arrow key navigation
            if (e.Key == Windows.System.VirtualKey.Up)
            {
                e.Handled = true;
                //check the focus index and make the correct radio button focus
                focusindex = focusindex < 1 ? 2 : focusindex - 1;
                RadioButton radioButton = radioButtons[focusindex];
                radioButton.Focus(FocusState.Keyboard);
    
                Debug.WriteLine("Up pressed");
    
            }
            else if (e.Key == Windows.System.VirtualKey.Down)
            {
                e.Handled = true;
                //check the focus index and make the correct radio button focus
                focusindex = focusindex > 1 ? 0 : focusindex +1;
                RadioButton radioButton = radioButtons[focusindex];
                radioButton.Focus(FocusState.Keyboard);
    
                Debug.WriteLine("Down pressed");
            }
            else if (e.Key == Windows.System.VirtualKey.Left)
            {
                e.Handled = true;
                //check the focus index and make the correct radio button focus
                focusindex = focusindex < 1 ? 2 : focusindex - 1;
                RadioButton radioButton = radioButtons[focusindex];
                radioButton.Focus(FocusState.Keyboard);
    
                Debug.WriteLine("Left pressed");
            }
            else if (e.Key == Windows.System.VirtualKey.Right)
            {
                e.Handled = true;
                //check the focus index and make the correct radio button focus
                focusindex = focusindex > 1 ? 0 : focusindex + 1;
                RadioButton radioButton = radioButtons[focusindex];
                radioButton.Focus(FocusState.Keyboard);
    
                Debug.WriteLine("Right pressed");
            }
            else if (e.Key == Windows.System.VirtualKey.Tab)
            {
                //When the tab pressed, the first item will always be focused
                focusindex = 0;
                Debug.WriteLine("Tab pressed");
            }
        }
    
        public void FindRadioButtons(DependencyObject parant, List<RadioButton> list)
        {
            int count = VisualTreeHelper.GetChildrenCount(parant);
    
            for (int i = 0; i < count; i++)
            {
                var MyChild = VisualTreeHelper.GetChild(parant, i);
                if (MyChild is FrameworkElement && MyChild.GetType() == typeof(RadioButton)) 
                {
                    list.Add((RadioButton)MyChild);
                }
                else 
                {
                    FindRadioButtons(MyChild,list);
                }
            
            }
        }
    

    Update:

    I've checked the source code of the winui RadioButtons to see how it implements the function. I found that it's handling the keydown event of the root panel so it could manually control the arrow key navigation logic. I think you might need to do the same in your UWP app by handling the keydown event of the StackPanel or contentdialog

    Old reply:

    I'd suggest that you could try the RadioButtons control in the WinUi 2.8 for UWP. It could meet all your requirement.

    Here is the link about how to get started with WinUI2 in UWP: Getting started with the Windows UI 2 Library.