Let's start by creating a new WPF project in C# called Autoforward.
Add a new class called CustomTextbox to hold the custom control. This would normally be in a separate project, but I want to keep the sample simple. The code in the class looks like this. It adds a KeyDown event handler that crudely predicts when the text box will be full, lets the event handler complete, then executes a Next TraversalRequest. This has to be done asynchronously so the text box can be updated with the key before we move to the next control.
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace Autoforward
{
class CustomTextbox : TextBox
{
public CustomTextbox()
{
this.Loaded += Textbox_Loaded;
}
~CustomTextbox()
{
this.KeyDown -= Textbox_KeyDown;
}
private void
Textbox_Loaded(object sender,
EventArgs e)
{
this.KeyDown += Textbox_KeyDown;
}
private void
Textbox_KeyDown(object sender,
KeyEventArgs e)
{
TextBox tb = (TextBox)sender;
string c = KeyToChar(e.Key);
if (c == "")
e.Handled = true;
else
if ((tb.Text + c).Length == tb.MaxLength)
{
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.ApplicationIdle,
(Action)(() =>
{
TraversalRequest tr = new
TraversalRequest(FocusNavigationDirection.Next);
this.MoveFocus(tr);
}
));
}
}
private static string KeyToChar(Key key)
{
if (new Regex(@"D[0-9]").IsMatch(key.ToString()))
return key.ToString().Replace("D", "");
if (new Regex(@"NumPad[0-9]").IsMatch(key.ToString()))
return key.ToString().Replace("NumPad", "");
return "";
}
}
}
<Window x:Class="Autoforward.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Autoforward"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<local:CustomTextbox Width="100" MaxLength="4"/>
<local:CustomTextbox Width="100" MaxLength="4"/>
</StackPanel>
</StackPanel>
</Window>
Now let's wire up a DataGrid that uses the same custom control. Change the XAML to look like this.
<Window x:Class="Autoforward.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Autoforward"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="450"
Width="800">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<local:CustomTextbox Width="100"
MaxLength="4"/>
<local:CustomTextbox Width="100"
MaxLength="4"/>
</StackPanel>
<DataGrid ItemsSource="{Binding Data}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Text 1">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:CustomTextbox MaxLength="4" Text="{Binding text1}"></local:CustomTextbox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Text 2">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:CustomTextbox MaxLength="4" Text="{Binding text2}"></local:CustomTextbox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Window>
using System;
using System.Collections.ObjectModel;
using System.Windows;
namespace Autoforward
{
public partial class MainWindow : Window
{
public class Datum
{
public String text1;
public String text2;
}
public ObservableCollection<Datum> Data { get; set; }
public MainWindow()
{
Data = new ObservableCollection<Datum>()
{ new Datum() };
InitializeComponent();
}
}
}
The thing to do is to put the data grid into edit mode whenever any cell gets focus. This event handler is called every time a new cell gets focus. We can handle this in the custom control. Change the custom control to look like this. You probably have your own FindChildrenByType and GetControlAncestor library functions already.
using System;
using
System.Collections.Generic;
using
System.Text.RegularExpressions;
using
System.Windows;
using
System.Windows.Controls;
using
System.Windows.Input;
using
System.Windows.Media;
namespace
Autoforward
{
class CustomTextbox : TextBox
{
DataGrid dg = null;
public CustomTextbox()
{
this.Loaded += Textbox_Loaded;
}
~CustomTextbox()
{
this.KeyDown -= Textbox_KeyDown;
if (dg != null)
dg.GotFocus -= DataGrid_GotFocus;
}
private void Textbox_Loaded(object
sender, EventArgs e)
{
this.KeyDown += Textbox_KeyDown;
dg =
GetControlAncestor<DataGrid>(this);
if (dg != null)
dg.GotFocus += DataGrid_GotFocus;
}
private void Textbox_KeyDown(object
sender, KeyEventArgs e)
{
TextBox tb = (TextBox)sender;
string c = KeyToChar(e.Key);
if (c == "")
e.Handled = true;
else
if ((tb.Text + c).Length ==
tb.MaxLength)
{
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.ApplicationIdle,
(Action)(() =>
{
TraversalRequest tr = new
TraversalRequest(FocusNavigationDirection.Next);
this.MoveFocus(tr);
}
));
}
}
private void
DataGrid_GotFocus(object sender,
RoutedEventArgs e)
{
DataGridCell cell =
e.OriginalSource as DataGridCell;
if (!dg.IsReadOnly && cell != null)
{
foreach (TextBox child in FindChildrenByType<TextBox>(cell))
{
child.Focus();
}
}
}
private static string KeyToChar(Key
key)
{
if (new Regex(@"D[0-9]").IsMatch(key.ToString()))
return key.ToString().Replace("D", "");
if (new Regex(@"NumPad[0-9]").IsMatch(key.ToString()))
return key.ToString().Replace("NumPad", "");
return "";
}
private static
List<T> FindChildrenByType<T>(DependencyObject Parent) where T : DependencyObject
{
List<T> Children = new List<T>();
if (Parent != null)
{
for (int i = 0; i
< VisualTreeHelper.GetChildrenCount(Parent); i++)
{
DependencyObject child =
VisualTreeHelper.GetChild(Parent, i);
if (child != null && child is T)
Children.Add(child as T);
Children.AddRange(FindChildrenByType<T>(child));
}
}
return Children;
}
private static T
GetControlAncestor<T>(DependencyObject
Child) where T : DependencyObject
{
DependencyObject Ancestor;
DependencyObject Parent;
if (Child == null) return default(T);
Ancestor = Child;
while (Ancestor != null && !(Ancestor is T))
{
Parent = VisualTreeHelper.GetParent(Ancestor);
if (Parent == null) Parent
= LogicalTreeHelper.GetParent(Ancestor);
if (Parent == null
&& Child is Control)
Parent = (Ancestor as
Control).Parent;
Ancestor = Parent;
}
return Ancestor as T;
}
}
}