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 1098
旧时难觅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:47

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

    Usage:

    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;
    
    /// 
    ///   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
    /// 
    public class DataGridCommitEditBehavior
    {
        public static readonly DependencyProperty CommitOnLostFocusProperty =
            DependencyProperty.RegisterAttached(
                "CommitOnLostFocus", 
                typeof(bool), 
                typeof(DataGridCommitEditBehavior), 
                new UIPropertyMetadata(false, OnCommitOnLostFocusChanged));
    
        /// 
        ///   A hack to find the data grid in the event handler of the tab control.
        /// 
        private static readonly Dictionary ControlMap = new Dictionary();
    
        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(element as ComboBoxItem);
                var combobox = parentItemsPresenter.TemplatedParent as ComboBox;
                childElement = combobox;
            }
            else
            {
                childElement = element;
            }
    
            var parentDatagrid = VisualTreeFinder.FindParentControl(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(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
    {
        /// 
        ///   Find a specific parent object type in the visual tree
        /// 
        public static T FindParentControl(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;
        }
    }
    

提交回复
热议问题