WPF MVVM Prevent keyboard tabbing into a collapsed control

465 Views Asked by At

I have various stackpanels/grids that are hidden/shown based on a VM property called "TabIndex". Using a converter and a parameter, the if the TabIndex matches the parameter the visibilty is set to visible else it's collapsed.

My issue is that when using the keyboard to tab through the controls, the tab order goes into the collapsed panel controls before it returns to the to visible panel controls.

Question: Is there anyway to prevent the keyboard from tabbing into the collapsed panels; something like setting up tab groups that can be enabled/disabled?

<StackPanel Visibility="{Binding TabIndex, Converter={local:IntegerToCollapsedVisibilityValueConverter}, ConverterParameter=0}">
    Controls in here...
</StackPanel>

<StackPanel Visibility="{Binding TabIndex, Converter={local:IntegerToCollapsedVisibilityValueConverter}, ConverterParameter=1}">
    Controls in here...
</StackPanel>

<StackPanel Visibility="{Binding TabIndex, Converter={local:IntegerToCollapsedVisibilityValueConverter}, ConverterParameter=2}">
    Controls in here...
</StackPanel>

EDIT - Adding converter code.

BaseConverter

/// <summary>
/// A base value converter that allows direct XAML usage
/// </summary>
/// <typeparam name="T">The type of this value converter</typeparam>
public abstract class BaseValueConverter<T> : MarkupExtension, IValueConverter
    where T : class, new()
{

    #region Private Variables

    /// <summary>
    /// A single static instance of this value converter
    /// </summary>
    private static T Coverter = null;

    #endregion

    #region Markup Extension Methods
    /// <summary>
    /// Provides a static instance of the value converter
    /// </summary>
    /// <param name="serviceProvider">The service provider</param>
    /// <returns></returns>
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return Coverter ?? (Coverter = new T());
    }

    #endregion

    #region Value Converter Methods

    /// <summary>
    /// The method to convert on type to another
    /// </summary>
    /// <param name="value"></param>
    /// <param name="targetType"></param>
    /// <param name="parameter"></param>
    /// <param name="culture"></param>
    /// <returns></returns>
    public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture);

    /// <summary>
    /// The method to convert a value back to it's source type
    /// </summary>
    /// <param name="value"></param>
    /// <param name="targetType"></param>
    /// <param name="parameter"></param>
    /// <param name="culture"></param>
    /// <returns></returns>
    public abstract object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);


    #endregion
}

ValueConverter

/// <summary>
/// A converter that takes in an integer and returns a <see cref="Visibility"/>
/// </summary>
public class IntegerToCollapsedVisibilityValueConverter : BaseValueConverter<IntegerToCollapsedVisibilityValueConverter>
{
    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return int.Parse((string)parameter) == (int)value ? Visibility.Visible : Visibility.Collapsed;
    }

    public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
2

There are 2 best solutions below

2
PeonProgrammer On

I think what you're looking for is IsTabStop. By setting this property to false, you can remove it from the tab list and effectively skip over it. You can look at the Microsoft documentation here: https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.control.istabstop?view=netframework-4.7.2

If you already have some code that is going through and disabling/hiding elements, you could just add a {element}.IsTabStop = true/false in the same place. Here is an example:

void button1_click(object sender, RoutedEventArgs e)
{
    if (true)//some flag for checking if you want to hide/disable it
    {
        panel1.IsTabStop = false;
    }
}
0
Richardissimo On

This isn't really an answer, because I'm not answering the question, other than saying this isn't repeatable. I'm adding this as an answer because it's too big to be a comment, and requires formatting.

Repeating my comment earlier, if something is Collapsed (or even Hidden) then it should not be possible for anything in it to receive the focus. For example, create a new Wpf project, and put this into the Window that it gives you:

<StackPanel>
    <StackPanel>
        <TextBox Text="Example1"/>
    </StackPanel>
    <StackPanel Visibility="Hidden">
        <TextBox Text="Example2"/>
    </StackPanel>
    <StackPanel Visibility="Collapsed">
        <TextBox Text="Example3"/>
    </StackPanel>
    <StackPanel>
        <TextBox Text="Example4"/>
    </StackPanel>
</StackPanel>

Now run the application and put the focus in Example1 and try Tabbing. The focus will go from Example1 to Example4 and back again. It does not go to Example2 or Example3.

Taking this further, if I use the example code for the converter:

<StackPanel>
    <StackPanel Visibility="{Binding TabIndex, Converter={local:IntegerToCollapsedVisibilityValueConverter}, ConverterParameter=0}">
        <TextBox Text="Example1"/>
    </StackPanel>
    <StackPanel Visibility="{Binding TabIndex, Converter={local:IntegerToCollapsedVisibilityValueConverter}, ConverterParameter=1}">
        <TextBox Text="Example2"/>
    </StackPanel>
    <StackPanel Visibility="{Binding TabIndex, Converter={local:IntegerToCollapsedVisibilityValueConverter}, ConverterParameter=2}">
        <TextBox Text="Example3"/>
    </StackPanel>
    <StackPanel Visibility="{Binding TabIndex, Converter={local:IntegerToCollapsedVisibilityValueConverter}, ConverterParameter=3}">
        <TextBox Text="Example4"/>
    </StackPanel>
</StackPanel>

and if I put this into the constructor of the Window:

DataContext = new { TabIndex = 2 };

then when I run it, it only shows the item with ConverterParameter=2, and tabbing doesn't let the focus go anywhere else. So there's definitely something else going on in your environment that I'm not seeing - you'll need to give a Minimal, Complete and Verifiable Example to allow people to investigate further.

Apropos of nothing, I'm going to mention that TabIndex is the name of a property on WPF Control, in case you used that property name on your view model without realising. For example, maybe you are accidentally binding to that instead of the one on your view model.