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

后端 未结 4 1075
旧时难觅i
旧时难觅i 2020-12-03 07:23

In wpf I setup a tab control that binds to a collection of objects each object has a data template with a data grid presenting the data. If I select a particular cell and p

相关标签:
4条回答
  • 2020-12-03 07:45

    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.

    0 讨论(0)
  • 2020-12-03 07:47

    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;
        }
    }
    
    0 讨论(0)
  • 2020-12-03 07:51

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

    0 讨论(0)
  • 2020-12-03 08:09

    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;
            }            
        }
    
    0 讨论(0)
提交回复
热议问题