What is the proper way to handle multiple datagrids in a tab control so that cells leave edit mode when the tabs are changed?

99封情书 提交于 2019-11-27 13:58:53

I have managed to work around this issue by detecting when the user clicks on a TabItem and then committing edits on visible DataGrid in the TabControl. I'm assuming the user will expect their changes to still be there when they click back.

Code snippet:

// PreviewMouseDown event handler on the TabControl
private void TabControl_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
    if (IsUnderTabHeader(e.OriginalSource as DependencyObject))
        CommitTables(yourTabControl);
}

private bool IsUnderTabHeader(DependencyObject control)
{
    if (control is TabItem)
        return true;
    DependencyObject parent = VisualTreeHelper.GetParent(control);
    if (parent == null)
        return false;
    return IsUnderTabHeader(parent);
}

private void CommitTables(DependencyObject control)
{
    if (control is DataGrid)
    {
        DataGrid grid = control as DataGrid;
        grid.CommitEdit(DataGridEditingUnit.Row, true);
        return;
    }
    int childrenCount = VisualTreeHelper.GetChildrenCount(control);
    for (int childIndex = 0; childIndex < childrenCount; childIndex++)
        CommitTables(VisualTreeHelper.GetChild(control, childIndex));
}

This is in the code behind.

Robert Hahn

I implemented a behavior for the DataGrid based on code I found in this thread.

Usage:<DataGrid local:DataGridCommitEditBehavior.CommitOnLostFocus="True" />

Code:

using System.Collections.Generic;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;

/// <summary>
///   Provides an ugly hack to prevent a bug in the data grid.
///   https://connect.microsoft.com/VisualStudio/feedback/details/532494/wpf-datagrid-and-tabcontrol-deferrefresh-exception
/// </summary>
public class DataGridCommitEditBehavior
{
    public static readonly DependencyProperty CommitOnLostFocusProperty =
        DependencyProperty.RegisterAttached(
            "CommitOnLostFocus", 
            typeof(bool), 
            typeof(DataGridCommitEditBehavior), 
            new UIPropertyMetadata(false, OnCommitOnLostFocusChanged));

    /// <summary>
    ///   A hack to find the data grid in the event handler of the tab control.
    /// </summary>
    private static readonly Dictionary<TabPanel, DataGrid> ControlMap = new Dictionary<TabPanel, DataGrid>();

    public static bool GetCommitOnLostFocus(DataGrid datagrid)
    {
        return (bool)datagrid.GetValue(CommitOnLostFocusProperty);
    }

    public static void SetCommitOnLostFocus(DataGrid datagrid, bool value)
    {
        datagrid.SetValue(CommitOnLostFocusProperty, value);
    }

    private static void CommitEdit(DataGrid dataGrid)
    {
        dataGrid.CommitEdit(DataGridEditingUnit.Cell, true);
        dataGrid.CommitEdit(DataGridEditingUnit.Row, true);
    }

    private static DataGrid GetParentDatagrid(UIElement element)
    {
        UIElement childElement; // element from which to start the tree navigation, looking for a Datagrid parent

        if (element is ComboBoxItem)
        {
            // Since ComboBoxItem.Parent is null, we must pass through ItemsPresenter in order to get the parent ComboBox
            var parentItemsPresenter = VisualTreeFinder.FindParentControl<ItemsPresenter>(element as ComboBoxItem);
            var combobox = parentItemsPresenter.TemplatedParent as ComboBox;
            childElement = combobox;
        }
        else
        {
            childElement = element;
        }

        var parentDatagrid = VisualTreeFinder.FindParentControl<DataGrid>(childElement);
        return parentDatagrid;
    }

    private static TabPanel GetTabPanel(TabControl tabControl)
    {
        return
            (TabPanel)
                tabControl.GetType().InvokeMember(
                    "ItemsHost", 
                    BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Instance, 
                    null, 
                    tabControl, 
                    null);
    }

    private static void OnCommitOnLostFocusChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        var dataGrid = depObj as DataGrid;
        if (dataGrid == null)
        {
            return;
        }

        if (e.NewValue is bool == false)
        {
            return;
        }

        var parentTabControl = VisualTreeFinder.FindParentControl<TabControl>(dataGrid);
        var tabPanel = GetTabPanel(parentTabControl);
        if (tabPanel != null)
        {
            ControlMap[tabPanel] = dataGrid;
        }

        if ((bool)e.NewValue)
        {
            // Attach event handlers
            if (parentTabControl != null)
            {
                tabPanel.PreviewMouseLeftButtonDown += OnParentTabControlPreviewMouseLeftButtonDown;
            }

            dataGrid.LostKeyboardFocus += OnDataGridLostFocus;
            dataGrid.DataContextChanged += OnDataGridDataContextChanged;
            dataGrid.IsVisibleChanged += OnDataGridIsVisibleChanged;
        }
        else
        {
            // Detach event handlers
            if (parentTabControl != null)
            {
                tabPanel.PreviewMouseLeftButtonDown -= OnParentTabControlPreviewMouseLeftButtonDown;
            }

            dataGrid.LostKeyboardFocus -= OnDataGridLostFocus;
            dataGrid.DataContextChanged -= OnDataGridDataContextChanged;
            dataGrid.IsVisibleChanged -= OnDataGridIsVisibleChanged;
        }
    }

    private static void OnDataGridDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var dataGrid = (DataGrid)sender;
        CommitEdit(dataGrid);
    }

    private static void OnDataGridIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var senderDatagrid = (DataGrid)sender;

        if ((bool)e.NewValue == false)
        {
            CommitEdit(senderDatagrid);
        }
    }

    private static void OnDataGridLostFocus(object sender, KeyboardFocusChangedEventArgs e)
    {
        var dataGrid = (DataGrid)sender;

        var focusedElement = Keyboard.FocusedElement as UIElement;
        if (focusedElement == null)
        {
            return;
        }

        var focusedDatagrid = GetParentDatagrid(focusedElement);

        // Let's see if the new focused element is inside a datagrid
        if (focusedDatagrid == dataGrid)
        {
            // If the new focused element is inside the same datagrid, then we don't need to do anything;
            // this happens, for instance, when we enter in edit-mode: the DataGrid element loses keyboard-focus, 
            // which passes to the selected DataGridCell child
            return;
        }

        CommitEdit(dataGrid);
    }

    private static void OnParentTabControlPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        var dataGrid = ControlMap[(TabPanel)sender];
        CommitEdit(dataGrid);
    }
}

public static class VisualTreeFinder
{
    /// <summary>
    ///   Find a specific parent object type in the visual tree
    /// </summary>
    public static T FindParentControl<T>(DependencyObject outerDepObj) where T : DependencyObject
    {
        var dObj = VisualTreeHelper.GetParent(outerDepObj);
        if (dObj == null)
        {
            return null;
        }

        if (dObj is T)
        {
            return dObj as T;
        }

        while ((dObj = VisualTreeHelper.GetParent(dObj)) != null)
        {
            if (dObj is T)
            {
                return dObj as T;
            }
        }

        return null;
    }
}
Algem Mojedo-MCPD-EA

I agreed what Phil Gan answered that, to work around this issue should be by detecting when the user clicks on a TabItem and then committing edits on visible DataGrid in the TabControl.

You could see this link for similarity problem...

Gridview not visible even after binding….

Hope it could help...

This bug is solved in the .NET Framework 4.5. You can download it at this link.

Oakcool

What I think you should do is pretty close to what @myermian said. There is an event called CellEditEnding end this event would allow you to intercept and make the decision to drop the unwanted row.

private void dataGrid1_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
    {
        DataGrid grid = (DataGrid)sender;
        TextBox cell = (TextBox)e.EditingElement;
        if(String.IsNullOrEmpty(cell.Text) && e.EditAction == DataGridEditAction.Commit)
        {
            grid.CancelEdit(DataGridEditingUnit.Row);
            e.Cancel = true;
        }            
    }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!