代码之家  ›  专栏  ›  技术社区  ›  Jon

在选项卡控件中处理多个数据报,以便在更改选项卡时单元格离开编辑模式的正确方法是什么?

  •  20
  • Jon  · 技术社区  · 16 年前

    在WPF中,我设置了一个选项卡控件,它绑定到一组对象,每个对象都有一个数据模板,其中的数据网格表示数据。如果我选择一个特定的单元格并将其置于编辑模式,转到另一个选项卡离开网格,这将导致返回数据报时引发以下异常:

    在AddNew或EditItem事务期间不允许使用“DeferRefresh”。

    单元格似乎从未离开编辑模式。是否有一种简单的方法可以将单元格从编辑模式中删除,或者这里是否正在发生其他事情?

    更新: 如果我不将选项卡控件绑定到数据源,而是显式定义每个选项卡,然后将数据源中的每个项绑定到内容控件,那么这个问题就消失了。这不是一个很好的解决方案,所以我仍然想知道如何将集合直接绑定到选项卡控件。

    更新: 因此,我为自己的解决方案所做的实际工作是使用ListView和内容控件代替选项卡控件。我使用样式使列表视图看起来像选项卡。视图模型公开一组子视图模型,并允许用户通过列表视图选择一个。然后,内容控件显示选定的视图模型,每个视图模型都有一个包含数据网格的关联数据模板。使用此设置,在网格上处于编辑模式时在视图模型之间切换将正确结束编辑模式并保存数据。

    以下是设置此项的XAML:

    <ListView ItemTemplate="{StaticResource MakeItemsLookLikeTabs}" 
              ItemsSource="{Binding ViewModels}"  
              SelectedItem="{Binding Selected}" 
              Style="{StaticResource MakeItLookLikeATabControl}"/>
    
    <ContentControl Content="{Binding Selected}">
    

    我会接受Phil的回答,因为这也应该有效,但对于我来说,上面的解决方案似乎在项目之间更易于移植。

    5 回复  |  直到 10 年前
        1
  •  7
  •   Phil Gan    15 年前

    我通过检测用户单击 TabItem 然后提交对可见内容的编辑 DataGrid 在 TabControl . 我假设当用户单击“上一步”时,他们希望更改仍然存在。

    代码片段:

    // 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));
    }
    

    这在代码后面。

        2
  •  10
  •   akjoshi HCP    13 年前

    我根据在中找到的代码为数据报实现了一种行为。 this 线程。

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

    代码:

    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;
        }
    }
    
        3
  •  0
  •   Community Mohan Dere    9 年前

    我同意PhilGan的回答,要解决这个问题,应该通过检测用户何时单击TabItem,然后提交对TabControl中可见数据报的编辑。

    你可以看到这个相似性问题的链接…

    Gridview not visible even after binding….

    希望能有帮助…

        4
  •  0
  •   Morgan M.    13 年前

    这个bug在.NET框架4.5中得到了解决。您可以在下载它 this link .

        5
  •  0
  •   not 0x12    10 年前

    我认为你应该做的和@myermian说的非常接近。 有一个名为celleditending end的事件。此事件允许您拦截并决定删除不需要的行。

    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;
            }            
        }