How to tab and select first element in ListView?

Viewed 121

I am trying to select the first item in the ListView when I tab into it.

If I type text in the TextBox and then I tab to the ListView it selects the first item with a dotted border only and the select item is null and selected index is 0. If I press down on the keyboard I start getting selected item. How can I get it to work on item 0 directly from tabbing from the TextBox?

XAML:

<Grid>
    <StackPanel>
        <TextBox> </TextBox>
        <ListView Margin="10" Name="lvDataBinding">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <WrapPanel>
                        <TextBlock Text="Name: " />
                        <TextBlock Text="{Binding Name}" FontWeight="Bold" />
                        <TextBlock Text=", " />
                        <TextBlock Text="Age: " />
                        <TextBlock Text="{Binding Age}" FontWeight="Bold" />
                        <TextBlock Text=" (" />
                        <TextBlock Text="{Binding Mail}" TextDecorations="Underline" Foreground="Blue" Cursor="Hand" />
                        <TextBlock Text=")" />
                    </WrapPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackPanel>
</Grid>

C#:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        List<User> items = new List<User>();
        items.Add(new User() { Name = "John Doe", Age = 42 });
        items.Add(new User() { Name = "Jane Doe", Age = 39 });
        items.Add(new User() { Name = "Sammy Doe", Age = 13 });
        lvDataBinding.ItemsSource = items;
    }
}
public class User
{
    public string Name { get; set; }

    public int Age { get; set; }

    public override string ToString()
    {
        return this.Name + ", " + this.Age + " years old";
    }
}
2 Answers

I am trying to select the first item in the listview when I tab on to it.

From what it seems there is not a method on ListView that selects a particular item (through index for example, although there's a SelectAll method). What you have though is the list of the users where there indices maps to the elements of the ListView (User in items at position 0 is rendered by the element of the lvDataBinding at the same position). You can use an indirect way, through data-binding, to select an item. In the example that I'll give the first item will be selected.

Create User.IsSelected

That will be the data-binding source.

public class User
  {
    public string Name { get; set; }

    public int Age { get; set; }

    public bool IsSelected { get; set; }

    public override string ToString()
    {
      return this.Name + ", " + this.Age + " years old";
    }
  }

Bind to element's IsSelected

The target is the ListView element's IsSelected property.

ListView Margin="10" Name="lvDataBinding">
        
       <ListView.ItemContainerStyle>
          <Style TargetType="ListViewItem">
            <Setter Property="IsSelected" Value="{Binding IsSelected}"/>
          </Style>
        </ListView.ItemContainerStyle>

        <ListView.ItemTemplate>
          <DataTemplate>
            <WrapPanel>
              <TextBlock Text="Name: " />
              <TextBlock Text="{Binding Name}" FontWeight="Bold" />
              <TextBlock Text=", " />
              <TextBlock Text="Age: " />
              <TextBlock Text="{Binding Age}" FontWeight="Bold" />
              <TextBlock Text=" (" />
              <TextBlock Text="{Binding Mail}" TextDecorations="Underline" Foreground="Blue" Cursor="Hand" />
              <TextBlock Text=")" />
            </WrapPanel>
          </DataTemplate>
        </ListView.ItemTemplate>

</ListView>

Trigger the selection

Now where the data-binding is applied you need to trigger a user selection. Assumingly that you want this when the TextBox loses focus then you could subscribe to LostFocus event.

<TextBox
        LostFocus="TextBox_LostFocus"/>

and select your user (first in this example)

    private void TextBox_LostFocus(object sender, RoutedEventArgs e)
    {
      items[0].IsSelected = true;
    }

That will result in the selection of the first element.

In your specific case, you want the behavior only to apply if the TextBox was focused first. You can achieve it by naming the TextBox and add a handler in code-behind for the GotKeyboardFocus event.

<Grid>
   <StackPanel>
      <TextBox x:Name="MyTextBox"/>
      <ListView Margin="10" Name="lvDataBinding" GotKeyboardFocus="lvDataBinding_OnGotKeyboardFocus">
         <!-- ...your other markup. -->
      </ListView>
   </StackPanel>
</Grid>

In there you check if the TextBox was focused before using e.OldFocus and set SelectedIndex to zero.

private void lvDataBinding_OnGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
   if (!MyTextBox.Equals(e.OldFocus))
      return;

   ((ListView)sender).SelectedIndex = 0;
}

A more elegant approach is using a custom behavior. For this you need to install the Microsoft.Xaml.Behaviors.Wpf NuGet package and derive a class from Behavior<T>, where T is the control it will be attached to, here the ListView. We do the same as in code-behind, but this time we expose a dependency property to bind the previously focused element. If nothing is bound, the behavior will work regardless of the element that was focused before.

public class SelectFirstItemOnKeyboardFocusBehavior : Behavior<ListView>
{
   public UIElement PreviouslyFocusedElement
   {
      get => (UIElement)GetValue(PreviouslyFocusedElementProperty);
      set => SetValue(PreviouslyFocusedElementProperty, value);
   }

   public static readonly DependencyProperty PreviouslyFocusedElementProperty = DependencyProperty.Register(
      nameof(PreviouslyFocusedElement), typeof(UIElement), typeof(SelectFirstItemOnKeyboardFocusBehavior), new PropertyMetadata(null));

   protected override void OnAttached()
   {
      base.OnAttached();
      AssociatedObject.GotKeyboardFocus += OnGotKeyboardFocus;
   }

   protected override void OnDetaching()
   {
      base.OnDetaching();
      AssociatedObject.GotKeyboardFocus -= OnGotKeyboardFocus;
   }

   private void OnGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
   {
      if (PreviouslyFocusedElement is null || PreviouslyFocusedElement.Equals(e.OldFocus))
         ((ListView)sender).SelectedIndex = 0;
   }
}

Attach the behavior to the ListView and bind the TextBox as previously focused element.

<Grid>
   <StackPanel>
      <TextBox Name="MyTextBox"/>
      <ListView Margin="10" Name="lvDataBinding">
         <b:Interaction.Behaviors>
            <local:SelectFirstItemOnKeyboardFocusBehavior PreviouslyFocusedElement="{Binding ElementName=MyTextBox}"/>
         </b:Interaction.Behaviors>
         <!-- ...your other markup. -->
      </ListView>
   </StackPanel>
</Grid>

You need to import the following XML namespace for behaviors.

xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
Related