views:

8107

answers:

6

Within an event, I'd like to put the focus on a specific TextBox within the ListViewItem's template. The XAML looks like this:

<ListView x:Name="myList" ItemsSource="{Binding SomeList}">
    <ListView.View>
        <GridView>
            <GridViewColumn>
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <!-- Focus this! -->
                        <TextBox x:Name="myBox"/>

I've tried the following in the code behind:

(myList.FindName("myBox") as TextBox).Focus();

but I seem to have misunderstood the FindName() docs, because it returns null.

Also the ListView.Items doesn't help, because that (of course) contains my bound business objects and no ListViewItems.

Neither does myList.ItemContainerGenerator.ContainerFromItem(item), which also returns null.

A: 

I believe the issue is that the ListView doesn't technically host the TextBox. Its the child of the template. Since your DataTemplate is inline (its not a resource associated with a data type), you can try to apply the x:Name attribute to it and then access it from code, calling FindName on it...

<DataTemplate x:Name="foo">
  <!-- Focus this! -->
  <TextBox x:Name="myBox"/>

and then

var tb = foo.FindName("myBox");
Will
I can't see how this would work. I didn't think it mattered whether the data template was defined inline or not, it's a template. Referencing 'myBox' in this way would only return you the template's instance (a prototype of sorts) not the instance created for each data item. Please correct me if I'm mistaken.
Drew Noakes
+1  A: 

This might prove helpful:

http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/fbd03126-ab8e-45fa-8b3b-f2baae35af87/

TrevorD
Yeah, the GetFrameworkElementByName() function from there worked as advertised. While it didn't solve my problem, it shed light what the actual problem is. The ItemContainerGenerator runs async after the handler that adds a new item to the bound list.
David Schmitt
+2  A: 

As others have noted, The myBox TextBox can not be found by calling FindName on the ListView. However, you can get the ListViewItem that is currently selected, and use the VisualTreeHelper class to get the TextBox from the ListViewItem. To do so looks something like this:

private void myList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (myList.SelectedItem != null)
    {
     object o = myList.SelectedItem;
     ListViewItem lvi = (ListViewItem)myList.ItemContainerGenerator.ContainerFromItem(o);
     TextBox tb = FindByName("myBox", lvi) as TextBox;

     if (tb != null)
      tb.Dispatcher.BeginInvoke(new Func<bool>(tb.Focus));
    }
}

private FrameworkElement FindByName(string name, FrameworkElement root)
{
    Stack<FrameworkElement> tree = new Stack<FrameworkElement>();
    tree.Push(root);

    while (tree.Count > 0)
    {
     FrameworkElement current = tree.Pop();
     if (current.Name == name)
      return current;

     int count = VisualTreeHelper.GetChildrenCount(current);
     for (int i = 0; i < count; ++i)
     {
      DependencyObject child = VisualTreeHelper.GetChild(current, i);
      if (child is FrameworkElement)
       tree.Push((FrameworkElement)child);
     }
    }

    return null;
}
Abe Heidebrecht
A: 

We use a similar technique with WPF's new datagrid:

Private Sub SelectAllText(ByVal cell As DataGridCell)
    If cell IsNot Nothing Then
        Dim txtBox As TextBox= GetVisualChild(Of TextBox)(cell)
        If txtBox IsNot Nothing Then
            txtBox.Focus()
            txtBox.SelectAll()
        End If
    End If
End Sub

Public Shared Function GetVisualChild(Of T As {Visual, New})(ByVal parent As Visual) As T
    Dim child As T = Nothing
    Dim numVisuals As Integer = VisualTreeHelper.GetChildrenCount(parent)
    For i As Integer = 0 To numVisuals - 1
        Dim v As Visual = TryCast(VisualTreeHelper.GetChild(parent, i), Visual)
        If v IsNot Nothing Then
            child = TryCast(v, T)
            If child Is Nothing Then
                child = GetVisualChild(Of T)(v)
            Else
                Exit For
            End If
        End If
    Next
    Return child
End Function

The technique should be fairly applicable for you, just pass your listviewitem once it's generated.

Bob King
+2  A: 

To understand why ContainerFromItem didn't work for me, here some background. The event handler where I needed this functionality looks like this:

var item = new SomeListItem();
SomeList.Add(item);
ListViewItem = SomeList.ItemContainerGenerator.ContainerFromItem(item); // returns null

After the Add() the ItemContainerGenerator doesn't immediately create the container, because the CollectionChanged event could be handled on a non-UI-thread. Instead it starts an asynchronous call and waits for the UI thread to callback and execute the actual ListViewItem control generation.

To be notified when this happens, the ItemContainerGenerator exposes a StatusChanged event which is fired after all Containers are generated.

Now I have to listen to this event and decide whether the control currently want's to set focus or not.

David Schmitt
A: 

Or it can be simply done by

private void yourtextboxinWPFGrid_LostFocus(object sender, RoutedEventArgs e) {

       //textbox can be catched like this. 
       var textBox = ((TextBox)sender);


        EmailValidation(textBox.Text);
    }
miral shah