2

I have some problems with my data grid. My project is transforming a Delphi project to .Net. The product owner want the same behaviour for the datagrids.

When positioned on the last cell and tab or enter is hit, the following should happen:

  1. A new row is added
  2. The first cell in the new row is selected

Other demands for the datagrid is:

  • The focus should remain inside the datagrid once it has the focus (ALT + key combinations is the way to leave the datagrid again).
  • The datagrid is databound
  • The datagrid is used in MVVM
  • We use the .net4.0 full profile
Mafii
  • 7,227
  • 1
  • 35
  • 55
Rokke
  • 63
  • 1
  • 4
  • I have tried fidling with KeyboardNavigation.TabNavigation="Contained" and other values for KeyboardNavigation.TabNavigation. I cannot seem to find a combination. All my attempts either tabs out of the data grid or does not create a new line. It seems to me that I need some other technique to tweak the datagrid, that I have not found yet. – Rokke Nov 30 '12 at 13:07

2 Answers2

2

This article had the best solution I could find.

I preferred to use an attached property rather than a behavior, since this enabled me to set it easily in the default style for DataGrid. Here's the code:

namespace SampleDataGridApp
{
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;

    /// <summary>
    /// An attached behavior that modifies the tab behavior for a <see cref="DataGrid"/>.
    /// </summary>
    public static class DataGridBehavior
    {
        /// <summary>
        /// Identifies the <c>NewLineOnTab</c> attached property.
        /// </summary>
        public static readonly DependencyProperty NewLineOnTabProperty = DependencyProperty.RegisterAttached(
            "NewLineOnTab",
            typeof(bool),
            typeof(DataGridBehavior),
            new PropertyMetadata(default(bool), OnNewLineOnTabChanged));

        /// <summary>
        /// Sets the value of the <c>NewLineOnTab</c> attached property.
        /// </summary>
        /// <param name="element">The <see cref="DataGrid"/>.</param>
        /// <param name="value">A value indicating whether to apply the behavior.</param>
        public static void SetNewLineOnTab(DataGrid element, bool value)
        {
            element.SetValue(NewLineOnTabProperty, value);
        }

        /// <summary>
        /// Gets the value of the <c>NewLineOnTab</c> attached property.
        /// </summary>
        /// <param name="element">The <see cref="DataGrid"/>.</param>
        /// <returns>A value indicating whether to apply the behavior.</returns>
        public static bool GetNewLineOnTab(DataGrid element)
        {
            return (bool)element.GetValue(NewLineOnTabProperty);
        }

        /// <summary>
        /// Called when the value of the <c>NewLineOnTab</c> property changes.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private static void OnNewLineOnTabChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            DataGrid d = sender as DataGrid;

            if (d == null)
            {
                return;
            }

            bool newValue = (bool)e.NewValue;
            bool oldValue = (bool)e.OldValue;

            if (oldValue == newValue)
            {
                return;
            }

            if (oldValue)
            {
                d.PreviewKeyDown -= AssociatedObjectKeyDown;
            }
            else
            {
                d.PreviewKeyDown += AssociatedObjectKeyDown;
                KeyboardNavigation.SetTabNavigation(d, KeyboardNavigationMode.Contained);
            }
        }

        /// <summary>
        /// Handles the <see cref="UIElement.KeyDown"/> event for a <see cref="DataGridCell"/>.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private static void AssociatedObjectKeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key != Key.Tab)
            {
                return;
            }

            DataGrid dg = e.Source as DataGrid;

            if (dg == null)
            {
                return;
            }

            if (dg.CurrentColumn.DisplayIndex == dg.Columns.Count - 1)
            {
                var icg = dg.ItemContainerGenerator;

                if (dg.SelectedIndex == icg.Items.Count - 2)
                {
                    dg.CommitEdit(DataGridEditingUnit.Row, false);
                }
            }
        }
    }
}

My default style looks like this:

<Style TargetType="DataGrid">
    <Setter Property="GridLinesVisibility" Value="None" />
    <Setter Property="KeyboardNavigation.TabNavigation" Value="Contained" />
    <Setter Property="sampleDataGridApp:DataGridBehavior.NewLineOnTab" Value="True" />
    <Setter Property="IsSynchronizedWithCurrentItem" Value="True" />
</Style>

If the last column's DataGridCell has it's IsTabStop set to false like in this example the above will not work.

Here is a buggy workaround:

private static void AssociatedObjectKeyDown(object sender, KeyEventArgs e)
{               
    if (e.Key != Key.Tab)
    {
        return;
    }

    DataGrid dg = e.Source as DataGrid;

    if (dg == null)
    {
        return;
    }

    int offSet = 1;
    var columnsReversed = dg.Columns.Reverse();
    foreach (var dataGridColumn in columnsReversed)
    {
        // Bug: This makes the grand assumption that a readonly column's "DataGridCell" has IsTabStop == false;
        if (dataGridColumn.IsReadOnly)
        {
            offSet++;
        }
        else
        {
            break;
        }
    }

    if (dg.CurrentColumn.DisplayIndex == (dg.Columns.Count - offSet))
    {
        var icg = dg.ItemContainerGenerator;

        if (dg.SelectedIndex == icg.Items.Count - 2)
        {
            dg.CommitEdit(DataGridEditingUnit.Row, false);
        }
    }
}
Community
  • 1
  • 1
Olly
  • 5,966
  • 31
  • 60
0

Ok, I have been fighting this problem for many hours now. I have tried nearly every proposed solution out there and here is what I found that works for me...

    private void grid_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Tab)
        {
            if (grid.SelectedIndex == grid.Items.Count - 2 && grid.CurrentColumn.DisplayIndex == grid.Columns.Count - 1)
            {
                grid.CommitEdit(DataGridEditingUnit.Row, false);
                e.Handled = true;
            }
        }
    }

    private void DataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
    {
        if (grid.SelectedIndex == grid.Items.Count - 2)
        {
            grid.SelectedIndex = grid.Items.Count - 1;
            grid.CurrentCell = new DataGridCellInfo(grid.Items[grid.Items.Count - 1], grid.Columns[0]);
        }
    }

What this code does is when you tab to the last cell of the last row and press tab it will move focus to the first cell of the new row. Which is what you would expect but this is not default behavior. The default behavior is to move focus to the next control and not commit the current row edit. This is clearly a bug in the DataGrid I believe which is why all the proposed solutions have a whiff of kluge. My solution doesn't smell that good I admit, but if you agree that this is bug, I prefer this to the ridiculous default behavior.

This solution works even if the grid is sorted. The newly entered row will sort to the proper place but the focus will be put on the first column of the new row.

The only unsolved issue is that when tabbing down from the top to the last cell before the new row, tab must be entered twice before focus is moved to the new row. I looked into this quirk for a bit and finally gave up on it.

AQuirky
  • 4,691
  • 2
  • 32
  • 51