Pressing Tab Key in last column of last row in DataGrid should set focus to first column of new row












0














I have a DataGrid that edits an ObservableCollection of IEditableObject objects. The DataGrid is set to CanUserAddRows="True" so that the blank row for adding a new record is present. Everything works perfectly, with one notable exception.



The default tab behavior for all rows that have data in them is to move to the first column in the next row when tabbing out of the last column in the current row, which is exactly the behavior I want. However, this is not the behavior I get if the next row is a new row, the row that will contain the next new record. Instead of moving to the first column in the new row, the tab moves focus to the first column of the first row in the DataGrid.



My current attempt to change the behavior to what I want looks like this:



private void ItemsDataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
if (ItemsDataGrid.SelectedIndex == ItemsDataGrid.Items.Count - 2)
{
DataGridRow row = ItemsDataGrid
.ItemContainerGenerator.ContainerFromItem(CollectionView.NewItemPlaceholder) as DataGridRow;

if (row.Focusable)
row.Focus();

DataGridCell cell = ItemsDataGrid.GetCell(row, 0);
if (cell != null)
{
DataGridCellInfo dataGridCellInfo = new DataGridCellInfo(cell);
if (cell.Focusable)
cell.Focus();
}
}
}


Which doesn't set the focus to where I want, even though cell.SetFocus() actually gets called.



My current working theory is this: row.Focusable returns false, probably because the row doesn't "quite" exist yet (I already know that it doesn't yet contain data at this point), so the desired cell can't get the focus because the row can't get the focus.



Any thoughts?





The closest thing to an MCVE that I could muster is below. WPF is rather verbose. Note that I'm using Fody.PropertyChanged as my INotifyPropertyChanged implementation.



MainWindow.XAML



<Window
x:Class="WpfApp2.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:local="clr-namespace:WpfApp2"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">

<Grid>
<TabControl>
<TabItem Header="List">
<DataGrid
Name="ItemsDataGrid"
AutoGenerateColumns="False"
CanUserAddRows="True"
ItemsSource="{Binding EditableFilterableItems}"
KeyboardNavigation.TabNavigation="Cycle"
RowEditEnding="ItemsDataGrid_RowEditEnding"
RowHeaderWidth="20"
SelectedItem="{Binding SelectedItem}"
SelectionUnit="FullRow">

<DataGrid.Resources>
<!-- http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/ -->
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>

<DataGrid.Columns>
<DataGridTextColumn
x:Name="QuantityColumn"
Width="1*"
Binding="{Binding Quantity}"
Header="Quantity" />
<DataGridComboBoxColumn
x:Name="AssetColumn"
Width="3*"
DisplayMemberPath="Description"
Header="Item"
ItemsSource="{Binding Data.ItemDescriptions, Source={StaticResource proxy}}"
SelectedValueBinding="{Binding ItemDescriptionID}"
SelectedValuePath="ItemDescriptionID" />
<DataGridTextColumn
x:Name="NotesColumn"
Width="7*"
Binding="{Binding Notes}"
Header="Notes" />
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>
</Grid>

</Window>


MainWindow.xaml.CS



using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApp2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
MainWindowViewModel _viewModel;
public MainWindow()
{
_viewModel = new MainWindowViewModel();
DataContext = _viewModel;
InitializeComponent();
}


private void ItemsDataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
if (ItemsDataGrid.SelectedIndex == ItemsDataGrid.Items.Count - 2)
{
DataGridRow row = ItemsDataGrid
.ItemContainerGenerator.ContainerFromItem(CollectionView.NewItemPlaceholder) as DataGridRow;

var rowIndex = row.GetIndex();

if (row.Focusable)
row.Focus();

DataGridCell cell = ItemsDataGrid.GetCell(row, 0);
if (cell != null)
{
DataGridCellInfo dataGridCellInfo = new DataGridCellInfo(cell);
if (cell.Focusable)
cell.Focus();
}
}
}
}
}


MainWindowViewModel.CS



using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
using PropertyChanged;

namespace WpfApp2
{
[AddINotifyPropertyChangedInterface]
public class MainWindowViewModel
{
public MainWindowViewModel()
{
Items = new ObservableCollection<Item>(
new List<Item>
{
new Item {ItemDescriptionID=1, Quantity=1, Notes="Little Red Wagon"},
new Item {ItemDescriptionID=2, Quantity=1, Notes="I Want a Pony"},
}
);
FilterableItems = CollectionViewSource.GetDefaultView(Items);
EditableFilterableItems = FilterableItems as IEditableCollectionView;
}
public ObservableCollection<Item> Items { get; set; }
public ICollectionView FilterableItems { get; set; }
public IEditableCollectionView EditableFilterableItems { get; set; }

public Item SelectedItem { get; set; }

public List<ItemDescription> ItemDescriptions => new List<ItemDescription>
{
new ItemDescription { ItemDescriptionID = 1, Description="Wagon" },
new ItemDescription { ItemDescriptionID = 2, Description="Pony" },
new ItemDescription { ItemDescriptionID = 3, Description="Train" },
new ItemDescription { ItemDescriptionID = 4, Description="Dump Truck" },
};
}
}


Item.CS, ItemDescription.CS



public class Item : EditableObject<Item>
{
public int Quantity { get; set; }
public int ItemDescriptionID { get; set; }
public string Notes { get; set; }
}

public class ItemDescription
{
public int ItemDescriptionID { get; set; }
public string Description { get; set; }
}


BindingProxy.CS



using System.Windows;

namespace WpfApp2
{
/// <summary>
/// http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
/// </summary>
public class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}

public object Data
{
get { return GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}

// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
}


DataGridHelper.CS



using System;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace WpfApp2
{
public static class DataGridHelper
{
public static T GetVisualChild<T>(Visual parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
public static DataGridCell GetCell(this DataGrid grid, DataGridRow row, int column)
{
if (row != null)
{
DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(row);

if (presenter == null)
{
grid.ScrollIntoView(row, grid.Columns[column]);
presenter = GetVisualChild<DataGridCellsPresenter>(row);
}

DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
return cell;
}
return null;
}
public static DataGridCell GetCell(this DataGrid grid, int row, int column)
{
DataGridRow rowContainer = grid.GetRow(row);
return grid.GetCell(rowContainer, column);
}
}
}


EditableObject.CS



using System;
using System.ComponentModel;

namespace WpfApp2
{
public abstract class EditableObject<T> : IEditableObject
{
private T Cache { get; set; }

private object CurrentModel
{
get { return this; }
}

public RelayCommand CancelEditCommand
{
get { return new RelayCommand(CancelEdit); }
}

#region IEditableObject Members
public void BeginEdit()
{
Cache = Activator.CreateInstance<T>();

//Set Properties of Cache
foreach (var info in CurrentModel.GetType().GetProperties())
{
if (!info.CanRead || !info.CanWrite) continue;
var oldValue = info.GetValue(CurrentModel, null);
Cache.GetType().GetProperty(info.Name).SetValue(Cache, oldValue, null);
}
}

public virtual void EndEdit()
{
Cache = default(T);
}


public void CancelEdit()
{
foreach (var info in CurrentModel.GetType().GetProperties())
{
if (!info.CanRead || !info.CanWrite) continue;
var oldValue = info.GetValue(Cache, null);
CurrentModel.GetType().GetProperty(info.Name).SetValue(CurrentModel, oldValue, null);
}
}
#endregion
}
}


RelayCommand.CS



using System;
using System.Windows.Input;

namespace WpfApp2
{
/// <summary>
/// A command whose sole purpose is to relay its functionality to other objects by invoking delegates.
/// The default return value for the CanExecute method is 'true'.
/// <see cref="RaiseCanExecuteChanged"/> needs to be called whenever
/// <see cref="CanExecute"/> is expected to return a different value.
/// </summary>
public class RelayCommand : ICommand
{
#region Private members
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
private readonly Action execute;

/// <summary>
/// True if command is executing, false otherwise
/// </summary>
private readonly Func<bool> canExecute;
#endregion

/// <summary>
/// Initializes a new instance of <see cref="RelayCommand"/> that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action execute) : this(execute, canExecute: null) { }

/// <summary>
/// Initializes a new instance of <see cref="RelayCommand"/>.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action execute, Func<bool> canExecute)
{
this.execute = execute ?? throw new ArgumentNullException("execute");
this.canExecute = canExecute;
}

/// <summary>
/// Raised when RaiseCanExecuteChanged is called.
/// </summary>
public event EventHandler CanExecuteChanged;

/// <summary>
/// Determines whether this <see cref="RelayCommand"/> can execute in its current state.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed, this object can be set to null.
/// </param>
/// <returns>True if this command can be executed; otherwise, false.</returns>
public bool CanExecute(object parameter) => canExecute == null ? true : canExecute();

/// <summary>
/// Executes the <see cref="RelayCommand"/> on the current command target.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed, this object can be set to null.
/// </param>
public void Execute(object parameter)
{
execute();
}

/// <summary>
/// Method used to raise the <see cref="CanExecuteChanged"/> event
/// to indicate that the return value of the <see cref="CanExecute"/>
/// method has changed.
/// </summary>
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}









share|improve this question
























  • Not what you are asking. BUT. It's rarely a good idea to let users edit directly in a datagrid. It's only suitable for very simple scenarios where you don't need validation. I would usually make datagrids read only and edit in a separate panel where I can ensure the data is valid before they can possibly commit it.
    – Andy
    Nov 21 at 9:46










  • @Andy: A fair point. But this particular use case requires putting in a significant amount of data in a very short time, and validation is not critical (it's essentially a list of simple items). It really does need to work like I described.
    – Robert Harvey
    Nov 21 at 15:48


















0














I have a DataGrid that edits an ObservableCollection of IEditableObject objects. The DataGrid is set to CanUserAddRows="True" so that the blank row for adding a new record is present. Everything works perfectly, with one notable exception.



The default tab behavior for all rows that have data in them is to move to the first column in the next row when tabbing out of the last column in the current row, which is exactly the behavior I want. However, this is not the behavior I get if the next row is a new row, the row that will contain the next new record. Instead of moving to the first column in the new row, the tab moves focus to the first column of the first row in the DataGrid.



My current attempt to change the behavior to what I want looks like this:



private void ItemsDataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
if (ItemsDataGrid.SelectedIndex == ItemsDataGrid.Items.Count - 2)
{
DataGridRow row = ItemsDataGrid
.ItemContainerGenerator.ContainerFromItem(CollectionView.NewItemPlaceholder) as DataGridRow;

if (row.Focusable)
row.Focus();

DataGridCell cell = ItemsDataGrid.GetCell(row, 0);
if (cell != null)
{
DataGridCellInfo dataGridCellInfo = new DataGridCellInfo(cell);
if (cell.Focusable)
cell.Focus();
}
}
}


Which doesn't set the focus to where I want, even though cell.SetFocus() actually gets called.



My current working theory is this: row.Focusable returns false, probably because the row doesn't "quite" exist yet (I already know that it doesn't yet contain data at this point), so the desired cell can't get the focus because the row can't get the focus.



Any thoughts?





The closest thing to an MCVE that I could muster is below. WPF is rather verbose. Note that I'm using Fody.PropertyChanged as my INotifyPropertyChanged implementation.



MainWindow.XAML



<Window
x:Class="WpfApp2.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:local="clr-namespace:WpfApp2"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">

<Grid>
<TabControl>
<TabItem Header="List">
<DataGrid
Name="ItemsDataGrid"
AutoGenerateColumns="False"
CanUserAddRows="True"
ItemsSource="{Binding EditableFilterableItems}"
KeyboardNavigation.TabNavigation="Cycle"
RowEditEnding="ItemsDataGrid_RowEditEnding"
RowHeaderWidth="20"
SelectedItem="{Binding SelectedItem}"
SelectionUnit="FullRow">

<DataGrid.Resources>
<!-- http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/ -->
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>

<DataGrid.Columns>
<DataGridTextColumn
x:Name="QuantityColumn"
Width="1*"
Binding="{Binding Quantity}"
Header="Quantity" />
<DataGridComboBoxColumn
x:Name="AssetColumn"
Width="3*"
DisplayMemberPath="Description"
Header="Item"
ItemsSource="{Binding Data.ItemDescriptions, Source={StaticResource proxy}}"
SelectedValueBinding="{Binding ItemDescriptionID}"
SelectedValuePath="ItemDescriptionID" />
<DataGridTextColumn
x:Name="NotesColumn"
Width="7*"
Binding="{Binding Notes}"
Header="Notes" />
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>
</Grid>

</Window>


MainWindow.xaml.CS



using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApp2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
MainWindowViewModel _viewModel;
public MainWindow()
{
_viewModel = new MainWindowViewModel();
DataContext = _viewModel;
InitializeComponent();
}


private void ItemsDataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
if (ItemsDataGrid.SelectedIndex == ItemsDataGrid.Items.Count - 2)
{
DataGridRow row = ItemsDataGrid
.ItemContainerGenerator.ContainerFromItem(CollectionView.NewItemPlaceholder) as DataGridRow;

var rowIndex = row.GetIndex();

if (row.Focusable)
row.Focus();

DataGridCell cell = ItemsDataGrid.GetCell(row, 0);
if (cell != null)
{
DataGridCellInfo dataGridCellInfo = new DataGridCellInfo(cell);
if (cell.Focusable)
cell.Focus();
}
}
}
}
}


MainWindowViewModel.CS



using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
using PropertyChanged;

namespace WpfApp2
{
[AddINotifyPropertyChangedInterface]
public class MainWindowViewModel
{
public MainWindowViewModel()
{
Items = new ObservableCollection<Item>(
new List<Item>
{
new Item {ItemDescriptionID=1, Quantity=1, Notes="Little Red Wagon"},
new Item {ItemDescriptionID=2, Quantity=1, Notes="I Want a Pony"},
}
);
FilterableItems = CollectionViewSource.GetDefaultView(Items);
EditableFilterableItems = FilterableItems as IEditableCollectionView;
}
public ObservableCollection<Item> Items { get; set; }
public ICollectionView FilterableItems { get; set; }
public IEditableCollectionView EditableFilterableItems { get; set; }

public Item SelectedItem { get; set; }

public List<ItemDescription> ItemDescriptions => new List<ItemDescription>
{
new ItemDescription { ItemDescriptionID = 1, Description="Wagon" },
new ItemDescription { ItemDescriptionID = 2, Description="Pony" },
new ItemDescription { ItemDescriptionID = 3, Description="Train" },
new ItemDescription { ItemDescriptionID = 4, Description="Dump Truck" },
};
}
}


Item.CS, ItemDescription.CS



public class Item : EditableObject<Item>
{
public int Quantity { get; set; }
public int ItemDescriptionID { get; set; }
public string Notes { get; set; }
}

public class ItemDescription
{
public int ItemDescriptionID { get; set; }
public string Description { get; set; }
}


BindingProxy.CS



using System.Windows;

namespace WpfApp2
{
/// <summary>
/// http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
/// </summary>
public class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}

public object Data
{
get { return GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}

// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
}


DataGridHelper.CS



using System;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace WpfApp2
{
public static class DataGridHelper
{
public static T GetVisualChild<T>(Visual parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
public static DataGridCell GetCell(this DataGrid grid, DataGridRow row, int column)
{
if (row != null)
{
DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(row);

if (presenter == null)
{
grid.ScrollIntoView(row, grid.Columns[column]);
presenter = GetVisualChild<DataGridCellsPresenter>(row);
}

DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
return cell;
}
return null;
}
public static DataGridCell GetCell(this DataGrid grid, int row, int column)
{
DataGridRow rowContainer = grid.GetRow(row);
return grid.GetCell(rowContainer, column);
}
}
}


EditableObject.CS



using System;
using System.ComponentModel;

namespace WpfApp2
{
public abstract class EditableObject<T> : IEditableObject
{
private T Cache { get; set; }

private object CurrentModel
{
get { return this; }
}

public RelayCommand CancelEditCommand
{
get { return new RelayCommand(CancelEdit); }
}

#region IEditableObject Members
public void BeginEdit()
{
Cache = Activator.CreateInstance<T>();

//Set Properties of Cache
foreach (var info in CurrentModel.GetType().GetProperties())
{
if (!info.CanRead || !info.CanWrite) continue;
var oldValue = info.GetValue(CurrentModel, null);
Cache.GetType().GetProperty(info.Name).SetValue(Cache, oldValue, null);
}
}

public virtual void EndEdit()
{
Cache = default(T);
}


public void CancelEdit()
{
foreach (var info in CurrentModel.GetType().GetProperties())
{
if (!info.CanRead || !info.CanWrite) continue;
var oldValue = info.GetValue(Cache, null);
CurrentModel.GetType().GetProperty(info.Name).SetValue(CurrentModel, oldValue, null);
}
}
#endregion
}
}


RelayCommand.CS



using System;
using System.Windows.Input;

namespace WpfApp2
{
/// <summary>
/// A command whose sole purpose is to relay its functionality to other objects by invoking delegates.
/// The default return value for the CanExecute method is 'true'.
/// <see cref="RaiseCanExecuteChanged"/> needs to be called whenever
/// <see cref="CanExecute"/> is expected to return a different value.
/// </summary>
public class RelayCommand : ICommand
{
#region Private members
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
private readonly Action execute;

/// <summary>
/// True if command is executing, false otherwise
/// </summary>
private readonly Func<bool> canExecute;
#endregion

/// <summary>
/// Initializes a new instance of <see cref="RelayCommand"/> that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action execute) : this(execute, canExecute: null) { }

/// <summary>
/// Initializes a new instance of <see cref="RelayCommand"/>.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action execute, Func<bool> canExecute)
{
this.execute = execute ?? throw new ArgumentNullException("execute");
this.canExecute = canExecute;
}

/// <summary>
/// Raised when RaiseCanExecuteChanged is called.
/// </summary>
public event EventHandler CanExecuteChanged;

/// <summary>
/// Determines whether this <see cref="RelayCommand"/> can execute in its current state.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed, this object can be set to null.
/// </param>
/// <returns>True if this command can be executed; otherwise, false.</returns>
public bool CanExecute(object parameter) => canExecute == null ? true : canExecute();

/// <summary>
/// Executes the <see cref="RelayCommand"/> on the current command target.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed, this object can be set to null.
/// </param>
public void Execute(object parameter)
{
execute();
}

/// <summary>
/// Method used to raise the <see cref="CanExecuteChanged"/> event
/// to indicate that the return value of the <see cref="CanExecute"/>
/// method has changed.
/// </summary>
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}









share|improve this question
























  • Not what you are asking. BUT. It's rarely a good idea to let users edit directly in a datagrid. It's only suitable for very simple scenarios where you don't need validation. I would usually make datagrids read only and edit in a separate panel where I can ensure the data is valid before they can possibly commit it.
    – Andy
    Nov 21 at 9:46










  • @Andy: A fair point. But this particular use case requires putting in a significant amount of data in a very short time, and validation is not critical (it's essentially a list of simple items). It really does need to work like I described.
    – Robert Harvey
    Nov 21 at 15:48
















0












0








0







I have a DataGrid that edits an ObservableCollection of IEditableObject objects. The DataGrid is set to CanUserAddRows="True" so that the blank row for adding a new record is present. Everything works perfectly, with one notable exception.



The default tab behavior for all rows that have data in them is to move to the first column in the next row when tabbing out of the last column in the current row, which is exactly the behavior I want. However, this is not the behavior I get if the next row is a new row, the row that will contain the next new record. Instead of moving to the first column in the new row, the tab moves focus to the first column of the first row in the DataGrid.



My current attempt to change the behavior to what I want looks like this:



private void ItemsDataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
if (ItemsDataGrid.SelectedIndex == ItemsDataGrid.Items.Count - 2)
{
DataGridRow row = ItemsDataGrid
.ItemContainerGenerator.ContainerFromItem(CollectionView.NewItemPlaceholder) as DataGridRow;

if (row.Focusable)
row.Focus();

DataGridCell cell = ItemsDataGrid.GetCell(row, 0);
if (cell != null)
{
DataGridCellInfo dataGridCellInfo = new DataGridCellInfo(cell);
if (cell.Focusable)
cell.Focus();
}
}
}


Which doesn't set the focus to where I want, even though cell.SetFocus() actually gets called.



My current working theory is this: row.Focusable returns false, probably because the row doesn't "quite" exist yet (I already know that it doesn't yet contain data at this point), so the desired cell can't get the focus because the row can't get the focus.



Any thoughts?





The closest thing to an MCVE that I could muster is below. WPF is rather verbose. Note that I'm using Fody.PropertyChanged as my INotifyPropertyChanged implementation.



MainWindow.XAML



<Window
x:Class="WpfApp2.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:local="clr-namespace:WpfApp2"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">

<Grid>
<TabControl>
<TabItem Header="List">
<DataGrid
Name="ItemsDataGrid"
AutoGenerateColumns="False"
CanUserAddRows="True"
ItemsSource="{Binding EditableFilterableItems}"
KeyboardNavigation.TabNavigation="Cycle"
RowEditEnding="ItemsDataGrid_RowEditEnding"
RowHeaderWidth="20"
SelectedItem="{Binding SelectedItem}"
SelectionUnit="FullRow">

<DataGrid.Resources>
<!-- http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/ -->
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>

<DataGrid.Columns>
<DataGridTextColumn
x:Name="QuantityColumn"
Width="1*"
Binding="{Binding Quantity}"
Header="Quantity" />
<DataGridComboBoxColumn
x:Name="AssetColumn"
Width="3*"
DisplayMemberPath="Description"
Header="Item"
ItemsSource="{Binding Data.ItemDescriptions, Source={StaticResource proxy}}"
SelectedValueBinding="{Binding ItemDescriptionID}"
SelectedValuePath="ItemDescriptionID" />
<DataGridTextColumn
x:Name="NotesColumn"
Width="7*"
Binding="{Binding Notes}"
Header="Notes" />
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>
</Grid>

</Window>


MainWindow.xaml.CS



using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApp2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
MainWindowViewModel _viewModel;
public MainWindow()
{
_viewModel = new MainWindowViewModel();
DataContext = _viewModel;
InitializeComponent();
}


private void ItemsDataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
if (ItemsDataGrid.SelectedIndex == ItemsDataGrid.Items.Count - 2)
{
DataGridRow row = ItemsDataGrid
.ItemContainerGenerator.ContainerFromItem(CollectionView.NewItemPlaceholder) as DataGridRow;

var rowIndex = row.GetIndex();

if (row.Focusable)
row.Focus();

DataGridCell cell = ItemsDataGrid.GetCell(row, 0);
if (cell != null)
{
DataGridCellInfo dataGridCellInfo = new DataGridCellInfo(cell);
if (cell.Focusable)
cell.Focus();
}
}
}
}
}


MainWindowViewModel.CS



using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
using PropertyChanged;

namespace WpfApp2
{
[AddINotifyPropertyChangedInterface]
public class MainWindowViewModel
{
public MainWindowViewModel()
{
Items = new ObservableCollection<Item>(
new List<Item>
{
new Item {ItemDescriptionID=1, Quantity=1, Notes="Little Red Wagon"},
new Item {ItemDescriptionID=2, Quantity=1, Notes="I Want a Pony"},
}
);
FilterableItems = CollectionViewSource.GetDefaultView(Items);
EditableFilterableItems = FilterableItems as IEditableCollectionView;
}
public ObservableCollection<Item> Items { get; set; }
public ICollectionView FilterableItems { get; set; }
public IEditableCollectionView EditableFilterableItems { get; set; }

public Item SelectedItem { get; set; }

public List<ItemDescription> ItemDescriptions => new List<ItemDescription>
{
new ItemDescription { ItemDescriptionID = 1, Description="Wagon" },
new ItemDescription { ItemDescriptionID = 2, Description="Pony" },
new ItemDescription { ItemDescriptionID = 3, Description="Train" },
new ItemDescription { ItemDescriptionID = 4, Description="Dump Truck" },
};
}
}


Item.CS, ItemDescription.CS



public class Item : EditableObject<Item>
{
public int Quantity { get; set; }
public int ItemDescriptionID { get; set; }
public string Notes { get; set; }
}

public class ItemDescription
{
public int ItemDescriptionID { get; set; }
public string Description { get; set; }
}


BindingProxy.CS



using System.Windows;

namespace WpfApp2
{
/// <summary>
/// http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
/// </summary>
public class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}

public object Data
{
get { return GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}

// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
}


DataGridHelper.CS



using System;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace WpfApp2
{
public static class DataGridHelper
{
public static T GetVisualChild<T>(Visual parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
public static DataGridCell GetCell(this DataGrid grid, DataGridRow row, int column)
{
if (row != null)
{
DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(row);

if (presenter == null)
{
grid.ScrollIntoView(row, grid.Columns[column]);
presenter = GetVisualChild<DataGridCellsPresenter>(row);
}

DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
return cell;
}
return null;
}
public static DataGridCell GetCell(this DataGrid grid, int row, int column)
{
DataGridRow rowContainer = grid.GetRow(row);
return grid.GetCell(rowContainer, column);
}
}
}


EditableObject.CS



using System;
using System.ComponentModel;

namespace WpfApp2
{
public abstract class EditableObject<T> : IEditableObject
{
private T Cache { get; set; }

private object CurrentModel
{
get { return this; }
}

public RelayCommand CancelEditCommand
{
get { return new RelayCommand(CancelEdit); }
}

#region IEditableObject Members
public void BeginEdit()
{
Cache = Activator.CreateInstance<T>();

//Set Properties of Cache
foreach (var info in CurrentModel.GetType().GetProperties())
{
if (!info.CanRead || !info.CanWrite) continue;
var oldValue = info.GetValue(CurrentModel, null);
Cache.GetType().GetProperty(info.Name).SetValue(Cache, oldValue, null);
}
}

public virtual void EndEdit()
{
Cache = default(T);
}


public void CancelEdit()
{
foreach (var info in CurrentModel.GetType().GetProperties())
{
if (!info.CanRead || !info.CanWrite) continue;
var oldValue = info.GetValue(Cache, null);
CurrentModel.GetType().GetProperty(info.Name).SetValue(CurrentModel, oldValue, null);
}
}
#endregion
}
}


RelayCommand.CS



using System;
using System.Windows.Input;

namespace WpfApp2
{
/// <summary>
/// A command whose sole purpose is to relay its functionality to other objects by invoking delegates.
/// The default return value for the CanExecute method is 'true'.
/// <see cref="RaiseCanExecuteChanged"/> needs to be called whenever
/// <see cref="CanExecute"/> is expected to return a different value.
/// </summary>
public class RelayCommand : ICommand
{
#region Private members
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
private readonly Action execute;

/// <summary>
/// True if command is executing, false otherwise
/// </summary>
private readonly Func<bool> canExecute;
#endregion

/// <summary>
/// Initializes a new instance of <see cref="RelayCommand"/> that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action execute) : this(execute, canExecute: null) { }

/// <summary>
/// Initializes a new instance of <see cref="RelayCommand"/>.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action execute, Func<bool> canExecute)
{
this.execute = execute ?? throw new ArgumentNullException("execute");
this.canExecute = canExecute;
}

/// <summary>
/// Raised when RaiseCanExecuteChanged is called.
/// </summary>
public event EventHandler CanExecuteChanged;

/// <summary>
/// Determines whether this <see cref="RelayCommand"/> can execute in its current state.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed, this object can be set to null.
/// </param>
/// <returns>True if this command can be executed; otherwise, false.</returns>
public bool CanExecute(object parameter) => canExecute == null ? true : canExecute();

/// <summary>
/// Executes the <see cref="RelayCommand"/> on the current command target.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed, this object can be set to null.
/// </param>
public void Execute(object parameter)
{
execute();
}

/// <summary>
/// Method used to raise the <see cref="CanExecuteChanged"/> event
/// to indicate that the return value of the <see cref="CanExecute"/>
/// method has changed.
/// </summary>
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}









share|improve this question















I have a DataGrid that edits an ObservableCollection of IEditableObject objects. The DataGrid is set to CanUserAddRows="True" so that the blank row for adding a new record is present. Everything works perfectly, with one notable exception.



The default tab behavior for all rows that have data in them is to move to the first column in the next row when tabbing out of the last column in the current row, which is exactly the behavior I want. However, this is not the behavior I get if the next row is a new row, the row that will contain the next new record. Instead of moving to the first column in the new row, the tab moves focus to the first column of the first row in the DataGrid.



My current attempt to change the behavior to what I want looks like this:



private void ItemsDataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
if (ItemsDataGrid.SelectedIndex == ItemsDataGrid.Items.Count - 2)
{
DataGridRow row = ItemsDataGrid
.ItemContainerGenerator.ContainerFromItem(CollectionView.NewItemPlaceholder) as DataGridRow;

if (row.Focusable)
row.Focus();

DataGridCell cell = ItemsDataGrid.GetCell(row, 0);
if (cell != null)
{
DataGridCellInfo dataGridCellInfo = new DataGridCellInfo(cell);
if (cell.Focusable)
cell.Focus();
}
}
}


Which doesn't set the focus to where I want, even though cell.SetFocus() actually gets called.



My current working theory is this: row.Focusable returns false, probably because the row doesn't "quite" exist yet (I already know that it doesn't yet contain data at this point), so the desired cell can't get the focus because the row can't get the focus.



Any thoughts?





The closest thing to an MCVE that I could muster is below. WPF is rather verbose. Note that I'm using Fody.PropertyChanged as my INotifyPropertyChanged implementation.



MainWindow.XAML



<Window
x:Class="WpfApp2.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:local="clr-namespace:WpfApp2"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">

<Grid>
<TabControl>
<TabItem Header="List">
<DataGrid
Name="ItemsDataGrid"
AutoGenerateColumns="False"
CanUserAddRows="True"
ItemsSource="{Binding EditableFilterableItems}"
KeyboardNavigation.TabNavigation="Cycle"
RowEditEnding="ItemsDataGrid_RowEditEnding"
RowHeaderWidth="20"
SelectedItem="{Binding SelectedItem}"
SelectionUnit="FullRow">

<DataGrid.Resources>
<!-- http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/ -->
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>

<DataGrid.Columns>
<DataGridTextColumn
x:Name="QuantityColumn"
Width="1*"
Binding="{Binding Quantity}"
Header="Quantity" />
<DataGridComboBoxColumn
x:Name="AssetColumn"
Width="3*"
DisplayMemberPath="Description"
Header="Item"
ItemsSource="{Binding Data.ItemDescriptions, Source={StaticResource proxy}}"
SelectedValueBinding="{Binding ItemDescriptionID}"
SelectedValuePath="ItemDescriptionID" />
<DataGridTextColumn
x:Name="NotesColumn"
Width="7*"
Binding="{Binding Notes}"
Header="Notes" />
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>
</Grid>

</Window>


MainWindow.xaml.CS



using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApp2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
MainWindowViewModel _viewModel;
public MainWindow()
{
_viewModel = new MainWindowViewModel();
DataContext = _viewModel;
InitializeComponent();
}


private void ItemsDataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
if (ItemsDataGrid.SelectedIndex == ItemsDataGrid.Items.Count - 2)
{
DataGridRow row = ItemsDataGrid
.ItemContainerGenerator.ContainerFromItem(CollectionView.NewItemPlaceholder) as DataGridRow;

var rowIndex = row.GetIndex();

if (row.Focusable)
row.Focus();

DataGridCell cell = ItemsDataGrid.GetCell(row, 0);
if (cell != null)
{
DataGridCellInfo dataGridCellInfo = new DataGridCellInfo(cell);
if (cell.Focusable)
cell.Focus();
}
}
}
}
}


MainWindowViewModel.CS



using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
using PropertyChanged;

namespace WpfApp2
{
[AddINotifyPropertyChangedInterface]
public class MainWindowViewModel
{
public MainWindowViewModel()
{
Items = new ObservableCollection<Item>(
new List<Item>
{
new Item {ItemDescriptionID=1, Quantity=1, Notes="Little Red Wagon"},
new Item {ItemDescriptionID=2, Quantity=1, Notes="I Want a Pony"},
}
);
FilterableItems = CollectionViewSource.GetDefaultView(Items);
EditableFilterableItems = FilterableItems as IEditableCollectionView;
}
public ObservableCollection<Item> Items { get; set; }
public ICollectionView FilterableItems { get; set; }
public IEditableCollectionView EditableFilterableItems { get; set; }

public Item SelectedItem { get; set; }

public List<ItemDescription> ItemDescriptions => new List<ItemDescription>
{
new ItemDescription { ItemDescriptionID = 1, Description="Wagon" },
new ItemDescription { ItemDescriptionID = 2, Description="Pony" },
new ItemDescription { ItemDescriptionID = 3, Description="Train" },
new ItemDescription { ItemDescriptionID = 4, Description="Dump Truck" },
};
}
}


Item.CS, ItemDescription.CS



public class Item : EditableObject<Item>
{
public int Quantity { get; set; }
public int ItemDescriptionID { get; set; }
public string Notes { get; set; }
}

public class ItemDescription
{
public int ItemDescriptionID { get; set; }
public string Description { get; set; }
}


BindingProxy.CS



using System.Windows;

namespace WpfApp2
{
/// <summary>
/// http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
/// </summary>
public class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}

public object Data
{
get { return GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}

// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
}


DataGridHelper.CS



using System;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace WpfApp2
{
public static class DataGridHelper
{
public static T GetVisualChild<T>(Visual parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
public static DataGridCell GetCell(this DataGrid grid, DataGridRow row, int column)
{
if (row != null)
{
DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(row);

if (presenter == null)
{
grid.ScrollIntoView(row, grid.Columns[column]);
presenter = GetVisualChild<DataGridCellsPresenter>(row);
}

DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
return cell;
}
return null;
}
public static DataGridCell GetCell(this DataGrid grid, int row, int column)
{
DataGridRow rowContainer = grid.GetRow(row);
return grid.GetCell(rowContainer, column);
}
}
}


EditableObject.CS



using System;
using System.ComponentModel;

namespace WpfApp2
{
public abstract class EditableObject<T> : IEditableObject
{
private T Cache { get; set; }

private object CurrentModel
{
get { return this; }
}

public RelayCommand CancelEditCommand
{
get { return new RelayCommand(CancelEdit); }
}

#region IEditableObject Members
public void BeginEdit()
{
Cache = Activator.CreateInstance<T>();

//Set Properties of Cache
foreach (var info in CurrentModel.GetType().GetProperties())
{
if (!info.CanRead || !info.CanWrite) continue;
var oldValue = info.GetValue(CurrentModel, null);
Cache.GetType().GetProperty(info.Name).SetValue(Cache, oldValue, null);
}
}

public virtual void EndEdit()
{
Cache = default(T);
}


public void CancelEdit()
{
foreach (var info in CurrentModel.GetType().GetProperties())
{
if (!info.CanRead || !info.CanWrite) continue;
var oldValue = info.GetValue(Cache, null);
CurrentModel.GetType().GetProperty(info.Name).SetValue(CurrentModel, oldValue, null);
}
}
#endregion
}
}


RelayCommand.CS



using System;
using System.Windows.Input;

namespace WpfApp2
{
/// <summary>
/// A command whose sole purpose is to relay its functionality to other objects by invoking delegates.
/// The default return value for the CanExecute method is 'true'.
/// <see cref="RaiseCanExecuteChanged"/> needs to be called whenever
/// <see cref="CanExecute"/> is expected to return a different value.
/// </summary>
public class RelayCommand : ICommand
{
#region Private members
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
private readonly Action execute;

/// <summary>
/// True if command is executing, false otherwise
/// </summary>
private readonly Func<bool> canExecute;
#endregion

/// <summary>
/// Initializes a new instance of <see cref="RelayCommand"/> that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action execute) : this(execute, canExecute: null) { }

/// <summary>
/// Initializes a new instance of <see cref="RelayCommand"/>.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action execute, Func<bool> canExecute)
{
this.execute = execute ?? throw new ArgumentNullException("execute");
this.canExecute = canExecute;
}

/// <summary>
/// Raised when RaiseCanExecuteChanged is called.
/// </summary>
public event EventHandler CanExecuteChanged;

/// <summary>
/// Determines whether this <see cref="RelayCommand"/> can execute in its current state.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed, this object can be set to null.
/// </param>
/// <returns>True if this command can be executed; otherwise, false.</returns>
public bool CanExecute(object parameter) => canExecute == null ? true : canExecute();

/// <summary>
/// Executes the <see cref="RelayCommand"/> on the current command target.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed, this object can be set to null.
/// </param>
public void Execute(object parameter)
{
execute();
}

/// <summary>
/// Method used to raise the <see cref="CanExecuteChanged"/> event
/// to indicate that the return value of the <see cref="CanExecute"/>
/// method has changed.
/// </summary>
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}






c# wpf wpfdatagrid






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 21 at 0:13

























asked Nov 20 at 23:40









Robert Harvey

147k33271415




147k33271415












  • Not what you are asking. BUT. It's rarely a good idea to let users edit directly in a datagrid. It's only suitable for very simple scenarios where you don't need validation. I would usually make datagrids read only and edit in a separate panel where I can ensure the data is valid before they can possibly commit it.
    – Andy
    Nov 21 at 9:46










  • @Andy: A fair point. But this particular use case requires putting in a significant amount of data in a very short time, and validation is not critical (it's essentially a list of simple items). It really does need to work like I described.
    – Robert Harvey
    Nov 21 at 15:48




















  • Not what you are asking. BUT. It's rarely a good idea to let users edit directly in a datagrid. It's only suitable for very simple scenarios where you don't need validation. I would usually make datagrids read only and edit in a separate panel where I can ensure the data is valid before they can possibly commit it.
    – Andy
    Nov 21 at 9:46










  • @Andy: A fair point. But this particular use case requires putting in a significant amount of data in a very short time, and validation is not critical (it's essentially a list of simple items). It really does need to work like I described.
    – Robert Harvey
    Nov 21 at 15:48


















Not what you are asking. BUT. It's rarely a good idea to let users edit directly in a datagrid. It's only suitable for very simple scenarios where you don't need validation. I would usually make datagrids read only and edit in a separate panel where I can ensure the data is valid before they can possibly commit it.
– Andy
Nov 21 at 9:46




Not what you are asking. BUT. It's rarely a good idea to let users edit directly in a datagrid. It's only suitable for very simple scenarios where you don't need validation. I would usually make datagrids read only and edit in a separate panel where I can ensure the data is valid before they can possibly commit it.
– Andy
Nov 21 at 9:46












@Andy: A fair point. But this particular use case requires putting in a significant amount of data in a very short time, and validation is not critical (it's essentially a list of simple items). It really does need to work like I described.
– Robert Harvey
Nov 21 at 15:48






@Andy: A fair point. But this particular use case requires putting in a significant amount of data in a very short time, and validation is not critical (it's essentially a list of simple items). It really does need to work like I described.
– Robert Harvey
Nov 21 at 15:48














2 Answers
2






active

oldest

votes


















1














Have you seen the approach described here:
https://peplowdown.wordpress.com/2012/07/19/wpf-datagrid-moves-input-focus-and-selection-to-the-wrong-cell-when-pressing-tab/



In my experience, once you start changing the behaviour of roweditending and tabbing etc you can find edge case after edge case.
Good luck.






share|improve this answer





















  • How did you find this? I have very good Google-fu, but I googled for ages and never found this.
    – Robert Harvey
    Nov 21 at 15:48










  • I think I just searched on wpf tab to new row. But I already knew this was likely to be a challenge.
    – Andy
    Nov 21 at 16:14










  • Fantastic, it works! Do you want me to edit the full solution into your answer, or post a new answer?
    – Robert Harvey
    Nov 21 at 17:16



















0














Here's the full solution.



using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;

namespace MyNamespace
{
/// <summary>
/// Creates the correct behavior when tabbing out of a new row in a DataGrid.
/// https://peplowdown.wordpress.com/2012/07/19/wpf-datagrid-moves-input-focus-and-selection-to-the-wrong-cell-when-pressing-tab/
/// </summary><remarks>
/// You’d expect that when you hit tab in the last cell the WPF data grid it would create a new row and put your focus in the first cell of that row.
/// It doesn’t; depending on how you have KeboardNavigation.TabNavigation set it’ll jump off somewhere you don’t expect, like the next control
/// or back to the first item in the grid. This behavior class solves that problem.
/// </remarks>
public class NewLineOnTabBehavior : Behavior<DataGrid>
{
private bool _monitorForTab;

protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.BeginningEdit += _EditStarting;
AssociatedObject.CellEditEnding += _CellEnitEnding;
AssociatedObject.PreviewKeyDown += _KeyDown;
}

private void _EditStarting(object sender, DataGridBeginningEditEventArgs e)
{
if (e.Column.DisplayIndex == AssociatedObject.Columns.Count - 1)
_monitorForTab = true;
}

private void _CellEnitEnding(object sender, DataGridCellEditEndingEventArgs e)
{
_monitorForTab = false;
}

private void _KeyDown(object sender, KeyEventArgs e)
{
if (_monitorForTab && e.Key == Key.Tab)
{
AssociatedObject.CommitEdit(DataGridEditingUnit.Row, false);
}
}

protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.BeginningEdit -= _EditStarting;
AssociatedObject.CellEditEnding -= _CellEnitEnding;
AssociatedObject.PreviewKeyDown -= _KeyDown;
_monitorForTab = false;
}
}
}


And in the XAML for the DataGrid:



<i:Interaction.Behaviors>
<local:NewLineOnTabBehavior />
</i:Interaction.Behaviors>


Add the following namespaces to the top-level XAML attributes:



xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:MyNamespace"


This solution doesn't play nice with the usual validation techniques, so I used a RowValidator to validate each row.



using System.Windows.Controls;
using System.Windows.Data;
using System.Globalization;

namespace MyNamespace
{

public class RowValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
T_Asset item = (value as BindingGroup).Items[0] as T_Asset;
item.ValidateModel();

if (!item.HasErrors) return ValidationResult.ValidResult;

return new ValidationResult(false, item.ErrorString);
}
}
}


T_Asset implements the INotifyDataErrorInfo interface.



And then in the XAML for the DataGrid:



<DataGrid.RowValidationRules>
<local:RowValidationRule ValidationStep="CommittedValue" />
</DataGrid.RowValidationRules>





share|improve this answer





















    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53403251%2fpressing-tab-key-in-last-column-of-last-row-in-datagrid-should-set-focus-to-firs%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    1














    Have you seen the approach described here:
    https://peplowdown.wordpress.com/2012/07/19/wpf-datagrid-moves-input-focus-and-selection-to-the-wrong-cell-when-pressing-tab/



    In my experience, once you start changing the behaviour of roweditending and tabbing etc you can find edge case after edge case.
    Good luck.






    share|improve this answer





















    • How did you find this? I have very good Google-fu, but I googled for ages and never found this.
      – Robert Harvey
      Nov 21 at 15:48










    • I think I just searched on wpf tab to new row. But I already knew this was likely to be a challenge.
      – Andy
      Nov 21 at 16:14










    • Fantastic, it works! Do you want me to edit the full solution into your answer, or post a new answer?
      – Robert Harvey
      Nov 21 at 17:16
















    1














    Have you seen the approach described here:
    https://peplowdown.wordpress.com/2012/07/19/wpf-datagrid-moves-input-focus-and-selection-to-the-wrong-cell-when-pressing-tab/



    In my experience, once you start changing the behaviour of roweditending and tabbing etc you can find edge case after edge case.
    Good luck.






    share|improve this answer





















    • How did you find this? I have very good Google-fu, but I googled for ages and never found this.
      – Robert Harvey
      Nov 21 at 15:48










    • I think I just searched on wpf tab to new row. But I already knew this was likely to be a challenge.
      – Andy
      Nov 21 at 16:14










    • Fantastic, it works! Do you want me to edit the full solution into your answer, or post a new answer?
      – Robert Harvey
      Nov 21 at 17:16














    1












    1








    1






    Have you seen the approach described here:
    https://peplowdown.wordpress.com/2012/07/19/wpf-datagrid-moves-input-focus-and-selection-to-the-wrong-cell-when-pressing-tab/



    In my experience, once you start changing the behaviour of roweditending and tabbing etc you can find edge case after edge case.
    Good luck.






    share|improve this answer












    Have you seen the approach described here:
    https://peplowdown.wordpress.com/2012/07/19/wpf-datagrid-moves-input-focus-and-selection-to-the-wrong-cell-when-pressing-tab/



    In my experience, once you start changing the behaviour of roweditending and tabbing etc you can find edge case after edge case.
    Good luck.







    share|improve this answer












    share|improve this answer



    share|improve this answer










    answered Nov 21 at 9:48









    Andy

    2,9051106




    2,9051106












    • How did you find this? I have very good Google-fu, but I googled for ages and never found this.
      – Robert Harvey
      Nov 21 at 15:48










    • I think I just searched on wpf tab to new row. But I already knew this was likely to be a challenge.
      – Andy
      Nov 21 at 16:14










    • Fantastic, it works! Do you want me to edit the full solution into your answer, or post a new answer?
      – Robert Harvey
      Nov 21 at 17:16


















    • How did you find this? I have very good Google-fu, but I googled for ages and never found this.
      – Robert Harvey
      Nov 21 at 15:48










    • I think I just searched on wpf tab to new row. But I already knew this was likely to be a challenge.
      – Andy
      Nov 21 at 16:14










    • Fantastic, it works! Do you want me to edit the full solution into your answer, or post a new answer?
      – Robert Harvey
      Nov 21 at 17:16
















    How did you find this? I have very good Google-fu, but I googled for ages and never found this.
    – Robert Harvey
    Nov 21 at 15:48




    How did you find this? I have very good Google-fu, but I googled for ages and never found this.
    – Robert Harvey
    Nov 21 at 15:48












    I think I just searched on wpf tab to new row. But I already knew this was likely to be a challenge.
    – Andy
    Nov 21 at 16:14




    I think I just searched on wpf tab to new row. But I already knew this was likely to be a challenge.
    – Andy
    Nov 21 at 16:14












    Fantastic, it works! Do you want me to edit the full solution into your answer, or post a new answer?
    – Robert Harvey
    Nov 21 at 17:16




    Fantastic, it works! Do you want me to edit the full solution into your answer, or post a new answer?
    – Robert Harvey
    Nov 21 at 17:16













    0














    Here's the full solution.



    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Interactivity;

    namespace MyNamespace
    {
    /// <summary>
    /// Creates the correct behavior when tabbing out of a new row in a DataGrid.
    /// https://peplowdown.wordpress.com/2012/07/19/wpf-datagrid-moves-input-focus-and-selection-to-the-wrong-cell-when-pressing-tab/
    /// </summary><remarks>
    /// You’d expect that when you hit tab in the last cell the WPF data grid it would create a new row and put your focus in the first cell of that row.
    /// It doesn’t; depending on how you have KeboardNavigation.TabNavigation set it’ll jump off somewhere you don’t expect, like the next control
    /// or back to the first item in the grid. This behavior class solves that problem.
    /// </remarks>
    public class NewLineOnTabBehavior : Behavior<DataGrid>
    {
    private bool _monitorForTab;

    protected override void OnAttached()
    {
    base.OnAttached();
    AssociatedObject.BeginningEdit += _EditStarting;
    AssociatedObject.CellEditEnding += _CellEnitEnding;
    AssociatedObject.PreviewKeyDown += _KeyDown;
    }

    private void _EditStarting(object sender, DataGridBeginningEditEventArgs e)
    {
    if (e.Column.DisplayIndex == AssociatedObject.Columns.Count - 1)
    _monitorForTab = true;
    }

    private void _CellEnitEnding(object sender, DataGridCellEditEndingEventArgs e)
    {
    _monitorForTab = false;
    }

    private void _KeyDown(object sender, KeyEventArgs e)
    {
    if (_monitorForTab && e.Key == Key.Tab)
    {
    AssociatedObject.CommitEdit(DataGridEditingUnit.Row, false);
    }
    }

    protected override void OnDetaching()
    {
    base.OnDetaching();
    AssociatedObject.BeginningEdit -= _EditStarting;
    AssociatedObject.CellEditEnding -= _CellEnitEnding;
    AssociatedObject.PreviewKeyDown -= _KeyDown;
    _monitorForTab = false;
    }
    }
    }


    And in the XAML for the DataGrid:



    <i:Interaction.Behaviors>
    <local:NewLineOnTabBehavior />
    </i:Interaction.Behaviors>


    Add the following namespaces to the top-level XAML attributes:



    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:local="clr-namespace:MyNamespace"


    This solution doesn't play nice with the usual validation techniques, so I used a RowValidator to validate each row.



    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Globalization;

    namespace MyNamespace
    {

    public class RowValidationRule : ValidationRule
    {
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
    T_Asset item = (value as BindingGroup).Items[0] as T_Asset;
    item.ValidateModel();

    if (!item.HasErrors) return ValidationResult.ValidResult;

    return new ValidationResult(false, item.ErrorString);
    }
    }
    }


    T_Asset implements the INotifyDataErrorInfo interface.



    And then in the XAML for the DataGrid:



    <DataGrid.RowValidationRules>
    <local:RowValidationRule ValidationStep="CommittedValue" />
    </DataGrid.RowValidationRules>





    share|improve this answer


























      0














      Here's the full solution.



      using System.Windows.Controls;
      using System.Windows.Input;
      using System.Windows.Interactivity;

      namespace MyNamespace
      {
      /// <summary>
      /// Creates the correct behavior when tabbing out of a new row in a DataGrid.
      /// https://peplowdown.wordpress.com/2012/07/19/wpf-datagrid-moves-input-focus-and-selection-to-the-wrong-cell-when-pressing-tab/
      /// </summary><remarks>
      /// You’d expect that when you hit tab in the last cell the WPF data grid it would create a new row and put your focus in the first cell of that row.
      /// It doesn’t; depending on how you have KeboardNavigation.TabNavigation set it’ll jump off somewhere you don’t expect, like the next control
      /// or back to the first item in the grid. This behavior class solves that problem.
      /// </remarks>
      public class NewLineOnTabBehavior : Behavior<DataGrid>
      {
      private bool _monitorForTab;

      protected override void OnAttached()
      {
      base.OnAttached();
      AssociatedObject.BeginningEdit += _EditStarting;
      AssociatedObject.CellEditEnding += _CellEnitEnding;
      AssociatedObject.PreviewKeyDown += _KeyDown;
      }

      private void _EditStarting(object sender, DataGridBeginningEditEventArgs e)
      {
      if (e.Column.DisplayIndex == AssociatedObject.Columns.Count - 1)
      _monitorForTab = true;
      }

      private void _CellEnitEnding(object sender, DataGridCellEditEndingEventArgs e)
      {
      _monitorForTab = false;
      }

      private void _KeyDown(object sender, KeyEventArgs e)
      {
      if (_monitorForTab && e.Key == Key.Tab)
      {
      AssociatedObject.CommitEdit(DataGridEditingUnit.Row, false);
      }
      }

      protected override void OnDetaching()
      {
      base.OnDetaching();
      AssociatedObject.BeginningEdit -= _EditStarting;
      AssociatedObject.CellEditEnding -= _CellEnitEnding;
      AssociatedObject.PreviewKeyDown -= _KeyDown;
      _monitorForTab = false;
      }
      }
      }


      And in the XAML for the DataGrid:



      <i:Interaction.Behaviors>
      <local:NewLineOnTabBehavior />
      </i:Interaction.Behaviors>


      Add the following namespaces to the top-level XAML attributes:



      xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
      xmlns:local="clr-namespace:MyNamespace"


      This solution doesn't play nice with the usual validation techniques, so I used a RowValidator to validate each row.



      using System.Windows.Controls;
      using System.Windows.Data;
      using System.Globalization;

      namespace MyNamespace
      {

      public class RowValidationRule : ValidationRule
      {
      public override ValidationResult Validate(object value, CultureInfo cultureInfo)
      {
      T_Asset item = (value as BindingGroup).Items[0] as T_Asset;
      item.ValidateModel();

      if (!item.HasErrors) return ValidationResult.ValidResult;

      return new ValidationResult(false, item.ErrorString);
      }
      }
      }


      T_Asset implements the INotifyDataErrorInfo interface.



      And then in the XAML for the DataGrid:



      <DataGrid.RowValidationRules>
      <local:RowValidationRule ValidationStep="CommittedValue" />
      </DataGrid.RowValidationRules>





      share|improve this answer
























        0












        0








        0






        Here's the full solution.



        using System.Windows.Controls;
        using System.Windows.Input;
        using System.Windows.Interactivity;

        namespace MyNamespace
        {
        /// <summary>
        /// Creates the correct behavior when tabbing out of a new row in a DataGrid.
        /// https://peplowdown.wordpress.com/2012/07/19/wpf-datagrid-moves-input-focus-and-selection-to-the-wrong-cell-when-pressing-tab/
        /// </summary><remarks>
        /// You’d expect that when you hit tab in the last cell the WPF data grid it would create a new row and put your focus in the first cell of that row.
        /// It doesn’t; depending on how you have KeboardNavigation.TabNavigation set it’ll jump off somewhere you don’t expect, like the next control
        /// or back to the first item in the grid. This behavior class solves that problem.
        /// </remarks>
        public class NewLineOnTabBehavior : Behavior<DataGrid>
        {
        private bool _monitorForTab;

        protected override void OnAttached()
        {
        base.OnAttached();
        AssociatedObject.BeginningEdit += _EditStarting;
        AssociatedObject.CellEditEnding += _CellEnitEnding;
        AssociatedObject.PreviewKeyDown += _KeyDown;
        }

        private void _EditStarting(object sender, DataGridBeginningEditEventArgs e)
        {
        if (e.Column.DisplayIndex == AssociatedObject.Columns.Count - 1)
        _monitorForTab = true;
        }

        private void _CellEnitEnding(object sender, DataGridCellEditEndingEventArgs e)
        {
        _monitorForTab = false;
        }

        private void _KeyDown(object sender, KeyEventArgs e)
        {
        if (_monitorForTab && e.Key == Key.Tab)
        {
        AssociatedObject.CommitEdit(DataGridEditingUnit.Row, false);
        }
        }

        protected override void OnDetaching()
        {
        base.OnDetaching();
        AssociatedObject.BeginningEdit -= _EditStarting;
        AssociatedObject.CellEditEnding -= _CellEnitEnding;
        AssociatedObject.PreviewKeyDown -= _KeyDown;
        _monitorForTab = false;
        }
        }
        }


        And in the XAML for the DataGrid:



        <i:Interaction.Behaviors>
        <local:NewLineOnTabBehavior />
        </i:Interaction.Behaviors>


        Add the following namespaces to the top-level XAML attributes:



        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:local="clr-namespace:MyNamespace"


        This solution doesn't play nice with the usual validation techniques, so I used a RowValidator to validate each row.



        using System.Windows.Controls;
        using System.Windows.Data;
        using System.Globalization;

        namespace MyNamespace
        {

        public class RowValidationRule : ValidationRule
        {
        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
        T_Asset item = (value as BindingGroup).Items[0] as T_Asset;
        item.ValidateModel();

        if (!item.HasErrors) return ValidationResult.ValidResult;

        return new ValidationResult(false, item.ErrorString);
        }
        }
        }


        T_Asset implements the INotifyDataErrorInfo interface.



        And then in the XAML for the DataGrid:



        <DataGrid.RowValidationRules>
        <local:RowValidationRule ValidationStep="CommittedValue" />
        </DataGrid.RowValidationRules>





        share|improve this answer












        Here's the full solution.



        using System.Windows.Controls;
        using System.Windows.Input;
        using System.Windows.Interactivity;

        namespace MyNamespace
        {
        /// <summary>
        /// Creates the correct behavior when tabbing out of a new row in a DataGrid.
        /// https://peplowdown.wordpress.com/2012/07/19/wpf-datagrid-moves-input-focus-and-selection-to-the-wrong-cell-when-pressing-tab/
        /// </summary><remarks>
        /// You’d expect that when you hit tab in the last cell the WPF data grid it would create a new row and put your focus in the first cell of that row.
        /// It doesn’t; depending on how you have KeboardNavigation.TabNavigation set it’ll jump off somewhere you don’t expect, like the next control
        /// or back to the first item in the grid. This behavior class solves that problem.
        /// </remarks>
        public class NewLineOnTabBehavior : Behavior<DataGrid>
        {
        private bool _monitorForTab;

        protected override void OnAttached()
        {
        base.OnAttached();
        AssociatedObject.BeginningEdit += _EditStarting;
        AssociatedObject.CellEditEnding += _CellEnitEnding;
        AssociatedObject.PreviewKeyDown += _KeyDown;
        }

        private void _EditStarting(object sender, DataGridBeginningEditEventArgs e)
        {
        if (e.Column.DisplayIndex == AssociatedObject.Columns.Count - 1)
        _monitorForTab = true;
        }

        private void _CellEnitEnding(object sender, DataGridCellEditEndingEventArgs e)
        {
        _monitorForTab = false;
        }

        private void _KeyDown(object sender, KeyEventArgs e)
        {
        if (_monitorForTab && e.Key == Key.Tab)
        {
        AssociatedObject.CommitEdit(DataGridEditingUnit.Row, false);
        }
        }

        protected override void OnDetaching()
        {
        base.OnDetaching();
        AssociatedObject.BeginningEdit -= _EditStarting;
        AssociatedObject.CellEditEnding -= _CellEnitEnding;
        AssociatedObject.PreviewKeyDown -= _KeyDown;
        _monitorForTab = false;
        }
        }
        }


        And in the XAML for the DataGrid:



        <i:Interaction.Behaviors>
        <local:NewLineOnTabBehavior />
        </i:Interaction.Behaviors>


        Add the following namespaces to the top-level XAML attributes:



        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:local="clr-namespace:MyNamespace"


        This solution doesn't play nice with the usual validation techniques, so I used a RowValidator to validate each row.



        using System.Windows.Controls;
        using System.Windows.Data;
        using System.Globalization;

        namespace MyNamespace
        {

        public class RowValidationRule : ValidationRule
        {
        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
        T_Asset item = (value as BindingGroup).Items[0] as T_Asset;
        item.ValidateModel();

        if (!item.HasErrors) return ValidationResult.ValidResult;

        return new ValidationResult(false, item.ErrorString);
        }
        }
        }


        T_Asset implements the INotifyDataErrorInfo interface.



        And then in the XAML for the DataGrid:



        <DataGrid.RowValidationRules>
        <local:RowValidationRule ValidationStep="CommittedValue" />
        </DataGrid.RowValidationRules>






        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Nov 28 at 21:53









        Robert Harvey

        147k33271415




        147k33271415






























            draft saved

            draft discarded




















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.





            Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


            Please pay close attention to the following guidance:


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53403251%2fpressing-tab-key-in-last-column-of-last-row-in-datagrid-should-set-focus-to-firs%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Wiesbaden

            Marschland

            Dieringhausen