0

So far I have looked at several questions and answers for how to get a TextBox within a DataTemplate, but none of it is working for me.

I have xaml like so (minimal example). The data template is in my section for static resources, and the ItemsControl is in the content section:

<DataTemplate x:Key="GridTemplate">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="140" />
        </Grid.ColumnDefinitions>
        <sdk:IntegerTextBox DataField="Model.DataField" Width="90" SelectAllOnFocus="True" />
    </Grid>
</DataTemplate>

<ItemsControl x:Name="MyControl" ItemsSource="{Binding MyList}" ItemTemplate="{StaticResource GridTemplate}" />

I need to be able set focus to the first IntegerTextBox in the Grid in the code behind. IntegerTextBox inherits the TextBox class.

I first tried writing my own method to recursively search through all UIElements for the first TextBox, but I found that the content within the DataTemplate isn't searchable in this way. The ItemsControl's children always return Nothing:

Private Function FirstTextBox(ByVal uiElement As UIElement) As TextBox
    Dim textBox As TextBox = TryCast(uiElement, TextBox)
    If textBox IsNot Nothing Then Return textBox
    Dim panel As Panel = TryCast(uiElement, Panel)
    If panel IsNot Nothing Then
        For Each child As UIElement In panel.Children
            textBox = FirstTextBox(child)
            If textBox IsNot Nothing Then Return textBox
        Next
    End If
    Dim itemsControl As ItemsControl = TryCast(uiElement, ItemsControl)
    If itemsControl IsNot Nothing Then
        For i As Integer = 0 To itemsControl.Items.Count
            textBox = FirstTextBox(CType(itemsControl.ItemContainerGenerator.ContainerFromIndex(i), UIElement))
            If textBox IsNot Nothing Then Return textBox
        Next
    End If
    Return textBox
End Function

I have tried this, similar to here and here, but the ContentPresenter is Nothing:

Dim contentPresenter = CType(MyControl.ItemContainerGenerator.ContainerFromIndex(0), ContentPresenter)
Dim textbox As TextBox = CType(CType(contentPresenter.ContentTemplate.LoadContent(), Panel).Children.First(Function(c) TypeOf c Is TextBox), TextBox)

I tried getting the DataTemplate as shown here, then loading the content and searching the children for a TextBox as shown here, but it never finds a TextBox.

I have worked a couple days on this, but I cannot see what I am doing wrong. Is it some obvious mistake, or am I approaching the problem incorrectly? Thanks.

EDIT - This is how I got it to work by adding 100 ms delay:

Private Function FindDescendant(Of TDescendant As DependencyObject)(ByVal obj As DependencyObject) As TDescendant
    Dim all = VisualTreeExtensions.GetVisualDescendants(obj)
    Dim first = all.OfType(Of TDescendant)().FirstOrDefault()
    Return first
End Function

Private Sub bw_DoWork(ByVal sender As Object, ByVal e As ComponentModel.DoWorkEventArgs)
    System.Threading.Thread.Sleep(100)
End Sub

Private Sub bw_RunWorkerCompleted(ByVal sender As Object, ByVal e As ComponentModel.RunWorkerCompletedEventArgs)
    Dim firstTextBox = FindDescendant(Of IntegerTextBox)(MyControl)
    If firstTextBox IsNot Nothing Then firstTextBox.Focus()
End Sub

Private Sub SetFocus()
    Dim bw As New ComponentModel.BackgroundWorker
    bw.WorkerReportsProgress = True
    bw.WorkerSupportsCancellation = True
    AddHandler bw.DoWork, AddressOf bw_DoWork
    AddHandler bw.RunWorkerCompleted, AddressOf bw_RunWorkerCompleted
    bw.RunWorkerAsync()
End Sub
Community
  • 1
  • 1
mbomb007
  • 3,788
  • 3
  • 39
  • 68
  • A delay may actually work, but is never advised. If you ever want to correct that you should rather use the [Loaded](https://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.loaded%28v=vs.95%29.aspx) event of `FrameworkElement`. – Martin Jul 07 '15 at 12:17
  • @Martin *In Silverlight, the Loaded event is not guaranteed to occur after the template is applied.* But that link did contain other helpful information on potential solutions to the problem. Thanks. – mbomb007 Jul 22 '15 at 15:04

1 Answers1

1

You are approaching the problem incorrectly. Accessing the DataTemplate and searching for a TextBox will get you nowhere. The template is only a blueprint, only when it is used somewhere (like for each item in your ItemsControl) its content is instantiated (once for each item). One of several possible solutions to set focus to the first item's textbox: In your code-behind add an eventhandler to the itemsControl:

MyControl.GotFocus += (sender, args) =>
{
    var firstItemTextBox = MyControl.FindDescendant<IntegerTextBox>();
    if ( firstItemTextBox != null ) firstItemTextBox.Focus();
};

some helper code:

//you need System.Windows.Controls.Toolkit.dll from the SilverlightToolkit for the class VisualTreeExtensions
using System.Windows.Controls.Primitives;
public static class ControlExtensions
{
    public static TDescendant FindDescendant<TDescendant>(this DependencyObject element)
    {
        return element.GetVisualDescendants().OfType<TDescendant>().FirstOrDefault();
    }
}

And btw: why do you wrap the IntergerTextBox in a separate Grid for each item?

Martin
  • 5,714
  • 2
  • 21
  • 41
  • No, I don't wrap the `IntegerTextBox`. Also, could you maybe rewrite your answer in vb.net so that it fits with the question? If you don't know how, I'll probably suggest an edit since I already converted it to vb.net to write it. On further testing, after load, the elements haven't been displayed on the screen yet, so it doesn't focus initially. Also, when I click on *anything* in the grid now, it focuses the first one... – mbomb007 Jun 19 '15 at 14:16
  • I'm not sure I understood what you are trying to do. You want to set focus to the first textbox? but when should it happen? what point in time? which event shall trigger the focus change? – Martin Jun 19 '15 at 14:39
  • I edited in what I'm trying to use at the end of my question. If I handle MyControl.GotFocus, then any time it gets focus it selects the first box, but this includes when I click a different box in the grid, meaning that I cannot ever select the other boxes. If I try to make the method *not* be a handler, then `first` is `Nothing` for some reason. As a side note, when focus happens after load, I think it happens too soon, and the content hasn't actually been rendered yet, so I'll need to find a way to delay that call by 100 ms asynchronously or something. – mbomb007 Jun 19 '15 at 15:30
  • I added a background worker to wait 100 ms, then after it's done I get the first textbox and it works. Silverlight takes a while to render controls on the screen, and I had to let it finish first. This was probably the main issue. – mbomb007 Jun 22 '15 at 20:13