Question

I have a ListView that presents items horizontally. Each Item is a 1 column Grid. The appearance is of a grid where the number of columns is dynamic. All of this works and looks like I want it to, except tab navigation. I have KeyboardNavigation.TabNavigation="Continue" set on the ListView and have KeyboardNavigation.IsTabStop set to false on the ItemContainerStyle, which is allowing me to through each row in a item, then on to the next item, etc. However, I would like to tab from the first row in the first item to the first row in the second item, etc. then on to the next row.

Ex.

Item1Row1 -> Item2Row1 -> Item3Row1 -> ...

Item1Row2 -> Item2Row2 -> Item3Row2 -> ...

I have tab indexes set up for the controls in each cell (which I have tested are correct), but I can't figure out what settings I need to enable TabIndexes within a ListView/ListViewItems. Any help would be greatly appreciated. Here's the xaml...

<ListView VerticalAlignment="Top" Background="Transparent" BorderThickness="0" KeyboardNavigation.TabNavigation="Continue" ItemsSource="{Binding RawProductDataItemViewModels}">
    <ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ListView.ItemsPanel>
    <ListView.ItemContainerStyle>
        <Style TargetType="{x:Type ListViewItem}">
            <Setter Property="Focusable" Value="False"/>
            <Setter Property="KeyboardNavigation.IsTabStop" Value="False"/>
        </Style>
    </ListView.ItemContainerStyle>
    <ListView.ItemTemplate>
        <DataTemplate>
            <Grid VerticalAlignment="Top" Margin="2.5,0,2.5,0">
                <Grid.RowDefinitions>
                    <RowDefinition SharedSizeGroup="ButtonAndGridGroup"/>
                    <RowDefinition SharedSizeGroup="ButtonAndGridGroup"/>
                    <RowDefinition SharedSizeGroup="ButtonAndGridGroup"/>
                    <RowDefinition SharedSizeGroup="ButtonAndGridGroup"/>
                    <RowDefinition SharedSizeGroup="ButtonAndGridGroup"/>
                    <RowDefinition SharedSizeGroup="ButtonAndGridGroup"/>
                    <RowDefinition SharedSizeGroup="ButtonAndGridGroup"/>
                    <RowDefinition SharedSizeGroup="ButtonAndGridGroup"/>
                    <RowDefinition SharedSizeGroup="ButtonAndGridGroup"/>
                    <RowDefinition SharedSizeGroup="ButtonAndGridGroup"/>
                    <RowDefinition SharedSizeGroup="ButtonAndGridGroup"/>
                </Grid.RowDefinitions>

                <TextBlock Margin="5,4,0,0" Grid.Row="0">
                        <TextBlock.Text>
                            <MultiBinding StringFormat="Lane #{0}">
                                <Binding Path="Lane"/>
                            </MultiBinding>
                        </TextBlock.Text>
                </TextBlock>
                <TextBox Grid.Row="1" Margin="0,4,0,0" Width="75" Text="{Binding RollNumber, StringFormat='{}{0:#####-#}', TargetNullValue=''}"
                            TabIndex="{Binding Lane, Converter={StaticResource IntegersTo2DIndex}, ConverterParameter=1}"/>
                <TextBox Grid.Row="2" Margin="0,4,0,0" Width="75" Text="{Binding PCode}"
                            TabIndex="{Binding Lane, Converter={StaticResource IntegersTo2DIndex}, ConverterParameter=2}"/>
                <TextBox Grid.Row="3" Margin="0,4,0,0" Width="75" Text="{Binding RollWidth}"
                            TabIndex="{Binding Lane, Converter={StaticResource IntegersTo2DIndex}, ConverterParameter=3}"/>
                <TextBox Grid.Row="4" Margin="0,4,0,0" Width="75" Text="{Binding RollWeight}"
                            TabIndex="{Binding Lane, Converter={StaticResource IntegersTo2DIndex}, ConverterParameter=4}"/>
                <TextBox Grid.Row="5" Margin="0,4,0,0" Width="75" Text="{Binding GrossWeight}"
                            TabIndex="{Binding Lane, Converter={StaticResource IntegersTo2DIndex}, ConverterParameter=5}"/>
                <TextBox Grid.Row="6" Margin="0,4,0,0" Width="75" Text="{Binding BurnWeight}"
                            TabIndex="{Binding Lane, Converter={StaticResource IntegersTo2DIndex}, ConverterParameter=6}"/>
                <TextBox Grid.Row="7" Margin="0,4,0,0" Width="75" Text="{Binding SqFtWeight}"
                            TabIndex="{Binding Lane, Converter={StaticResource IntegersTo2DIndex}, ConverterParameter=7}"/>
                <TextBox Grid.Row="8" Margin="0,4,0,0" Width="75" Text="{Binding Cure}"
                            TabIndex="{Binding Lane, Converter={StaticResource IntegersTo2DIndex}, ConverterParameter=8}"/>
                <TextBox Grid.Row="9" Margin="0,4,0,0" Width="75" Text="{Binding Rigidity}"
                            TabIndex="{Binding Lane, Converter={StaticResource IntegersTo2DIndex}, ConverterParameter=9}"/>
                <TextBox Grid.Row="10" Margin="0,4,0,0" Width="75" Text="{Binding Telescope}"
                            TabIndex="{Binding Lane, Converter={StaticResource IntegersTo2DIndex}, ConverterParameter=10}"/>
            </Grid>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>
Was it helpful?

Solution

I decided to handle this in the code behind. I removed the tab indexes on the textboxes in xaml. The solution isn't pretty, but it is better, in my opinion, than removing the listbox and harcoding all the lanes so that I can use tab indexes.

Preview Key Down Handler on ListView:

private void LanesListView_PreviewKeyDown(object sender, KeyEventArgs e)
{
    UIElement uie = e.OriginalSource as UIElement;

    // 'Ctrl + Tab' or 'Shift + Enter' (Reverse Tab)
    if ((e.Key == Key.Tab || e.Key == Key.Return) &&
        (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift &&
         uie != null)
    {
        MoveFocusPrevious(uie, (UIElement)sender);

        e.Handled = true;
    }
    else if ((e.Key == Key.Tab || e.Key == Key.Return) && uie != null)
    {
        // Normal 'Enter' or 'Tab' key click
        MoveFocusNext(uie, (UIElement)sender);

        e.Handled = true;
    }
}

Move Focus Methods:

private void MoveFocusNext(UIElement uie, UIElement sender)
{
    if(!uie.MoveFocus(new TraversalRequest(FocusNavigationDirection.Right)))
    {
        uie.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next)); // Move Down
        uie = (UIElement)Keyboard.FocusedElement;
        MoveFocusToFirst(uie, sender); // Move to to first
    }
}

private void MoveFocusPrevious(UIElement uie, UIElement sender)
{
    if (!uie.MoveFocus(new TraversalRequest(FocusNavigationDirection.Left)))
    {
        uie.MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous)); // Move Up
        uie = (UIElement)Keyboard.FocusedElement;
        MoveFocusToLast(uie, sender); // Move focus to last
    }
}

private void MoveFocusToLast(UIElement uie, UIElement sender)
{
    bool isLast = false;

    // Move right until hitting last item
    while(!isLast)
    {
        // If Focus cannot be moved, it is last item.
        isLast = !uie.MoveFocus(new TraversalRequest(FocusNavigationDirection.Right));
        uie = (UIElement)Keyboard.FocusedElement;
    }
}

private void MoveFocusToFirst(UIElement uie, UIElement sender)
{
    bool isFirst = false;

    // Move left until hitting last item
    while (!isFirst)
    {
        // If Focus cannot be moved, it is last item.
        isFirst = !uie.MoveFocus(new TraversalRequest(FocusNavigationDirection.Left)); 
        uie = (UIElement)Keyboard.FocusedElement; 
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top