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

时间:2010-07-28 16:10:03

标签: wpf datagrid tabcontrol

在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 个答案:

答案 0 :(得分:10)

我基于this线程中找到的代码实现了DataGrid的行为。

用法:<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;
    }
}

答案 1 :(得分:7)

通过检测用户何时点击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 :(得分:0)

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

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

答案 3 :(得分:0)

我同意Phil Gan的回答,解决这个问题的方法应该是检测用户何时点击TabItem,然后在TabControl中对可见的DataGrid进行编辑。

您可以看到此链接的相似性问题...

Gridview not visible even after binding….

希望它可以帮助......

答案 4 :(得分:0)

此错误在.NET Framework 4.5中得到解决。您可以在this link下载。