How to focus on the next row textbox in a WPF DataGrid

by Administrator 25. May 2011 07:22

I have the need when editing a WPF 4 DataGrid to focus on a named Textbox in a templated column grid when the user presses the down key- this is the same functionality as Excel provides and allows users to quickly mass edit a number of rows.

Working out how to do this was a little bit trickier than I imagined so I’ve decided to blog the answer in case anyone else has the same issue.

In our example, we have a simple datagrid with 1 templated column containing a vanilla Textbox as shown below:

 

 <Window x:Class="HighLightNextTextBox.MainWindow"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         Title="MainWindow" Height="350" Width="525">
     <Grid>
         <DataGrid AutoGenerateColumns="False" x:Name="SomeStringsDataGrid" PreviewKeyDown="SomeStringsDataGrid_PreviewKeyDown" IsSynchronizedWithCurrentItem="True">
             <DataGrid.Columns>
                 <DataGridTemplateColumn>
                     <DataGridTemplateColumn.CellTemplate>
                         <DataTemplate>
                             <TextBox x:Name="someTextBox" Text="{Binding SomeStuff}"></TextBox>
                         </DataTemplate>
                     </DataGridTemplateColumn.CellTemplate>
                 </DataGridTemplateColumn>
             </DataGrid.Columns>
         </DataGrid>
     </Grid>
 </Window>
 
 

To make life simple, we will bind the grid to a very simple class:

     public class SomeData
     {
         public string SomeStuff { get; set; }
     }
 

The code behind looks like this:

  public MainWindow()
         {
             InitializeComponent();
             SomeStringsDataGrid.ItemsSource = new List<SomeData>() { new SomeData() { SomeStuff = "Forever"}, new SomeData() { SomeStuff = "Young"}};
         }
 
         private void SomeStringsDataGrid_PreviewKeyDown(object sender, KeyEventArgs e)
         {
             var collectionView = CollectionViewSource.GetDefaultView(SomeStringsDataGrid.ItemsSource);
             
             if(e.Key == Key.Down)
             {
                 if (collectionView.MoveCurrentToNext())
                 {
                     object selectedItem = SomeStringsDataGrid.SelectedItem;
                     var root = SomeStringsDataGrid.ItemContainerGenerator.ContainerFromItem(selectedItem) as FrameworkElement;
                     var desiredNamedControl = FindByName("someTextBox", root);
 
                     desiredNamedControl.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;
         }
     }
 

 

The clever bit is the FindbyName method, which I found on StackOverflow in this answer. When the down key is pressed, the associated collection with the bound data is found, the collection view is moved to the next item, and because the datagrid has its IsSynchronizedWithCurrentItem property set to true, the next row in the grid is highlighted.

It is then *simply* a case of getting the selected item in the grid, getting the container from the item and then using the FindByName method, which interestingly uses an explicitly stack, as opposed to using the stack provided by writing a recursive function.

Once the named control is found it is simply a case of calling focus on the control. The FindByName bit really is the non obvious part. Hopefully this post will help someone else out, especially if like me you initially try using FrameworkElement.FindNameand find it does not work!

Tags:

Comments