Silverlight: Get first TextBox in DataTemplate

93 views Asked by At

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
1

There are 1 answers

4
Martin On BEST ANSWER

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?