Home > OS >  How to tab and select first element in ListView?
How to tab and select first element in ListView?

Time:03-10

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";
    }
}

CodePudding user response:

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.

CodePudding user response:

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