1

I have a Button on a UserControl that adds an item to a ListBox on that UserControl. Let's call that control Parent. The ListBoxItems contain another UserControl. Let's call that Child. The button adds an item to the ItemSource of the listbox (MVVM style).

I can scroll that into view without a problem. I can set the focus to the ListBoxItem, but what I want is the focus to be set on the first TextBox of the child UserControlof the content of the ListBoxItem. I can't seem to figure that out. The code below sets the focus to the ListBoxItem, not the UserControl child of it or any control on it.

Private Sub bnAdd(sender As Object, e As RoutedEventArgs)
   VM.AddDetail()
   MyList.ScrollIntoView(MyList.Items(MyList.Items.Count - 1))
   Dim ListBoxItem As ListBoxItem = MyList.ItemContainerGenerator.ContainerFromItem(MyList.SelectedItem)
   ListBoxItem.Focus()
End Sub

On my child UserControl I used this in XAML:

FocusManager.FocusedElement="{Binding ElementName=txtMyBox}"
thatguy
  • 21,059
  • 6
  • 30
  • 40
  • Your usercontrol should expose a dependency property or public method responsible for setting focus on a textbox in it. Aside from encapsulation, that way someone changing the usercontrol at least has a reasonable chance of noticing they're about to break functionality when they move that textbox or whatever. How is the usercontrol instantiated? What does VM.AddDetail do? Does it have a reference to this usercontrol? – Andy Jul 28 '20 at 08:18

1 Answers1

0

There is a related question here and most of the approaches use hooking into focus events to achieve the focus change. I want to propose another solution that is based on traversing the visual tree. Unfortunately, I can only provide you C# code, but you can use the concept to apply it in your Visual Basic code.

As far as I can see, you are using code-behind to scroll your items into view. I will build on that. Your list box has list box items and I guess you use a data template to display UserControls as child items. Within these user controls there is a TextBox that you have assigned a name in XAML via x:Name or in code-behind via the Name property. Now, you need a helper method to traverse the visual tree and search for text boxes.

private IEnumerable<TextBox> FindTextBox(DependencyObject dependencyObject)
{
   // No other child controls, break
   if (dependencyObject == null)
      yield break;

   // Search children of the current control
   for (var i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObject); i++)
   {
      var child = VisualTreeHelper.GetChild(dependencyObject, i);

      // Check if the current item is a text box
      if (child is TextBox textBox)
         yield return textBox;

      // If we did not find a text box, search the children of this child recursively
      foreach (var childOfChild in FindTextBox(child))
         yield return childOfChild;
   }
}

Then we add a method that filters the enumerable of text boxes for a given name using Linq.

private TextBox FindTextBox(DependencyObject dependencyObject, string name)
{
   // Filter the list of text boxes for the right one with the specified name
   return FindTextBox(dependencyObject).SingleOrDefault(child => child.Name.Equals(name));
}

In your bnAdd handler you can take your ListBoxItem, search for the text box child and focus it.

var textBox = FindTextBox(listBoxItem, "MyTextBox");
textBox.Focus();
thatguy
  • 21,059
  • 6
  • 30
  • 40