Как правильно обрабатывать несколько таблиц данных в элементе управления вкладками, чтобы ячейки выходили из режима редактирования при изменении вкладок? - PullRequest
20 голосов
/ 28 июля 2010

В wpf я устанавливаю элемент управления с вкладками, который привязывает к коллекции объектов, каждый объект имеет шаблон данных с сеткой данных, представляющей данные.Если я выберу определенную ячейку и переведу ее в режим редактирования, оставив сетку, перейдя на другую вкладку, это вызовет исключение, приведенное ниже, при возврате сетки данных:

«DeferRefresh» не допускается во время добавления AddNewили транзакция EditItem.

Похоже, что ячейка никогда не выходила из режима редактирования.Есть ли простой способ вывести ячейку из режима редактирования, или здесь происходит что-то еще?

Обновление: Похоже, я не привязываю элемент управления вкладкой к даннымисточник, но вместо этого явно определите каждую вкладку, а затем привяжите каждый элемент в источнике данных к элементу управления контентом, эта проблема исчезнет.Это не очень хорошее решение, поэтому я все же хотел бы знать, как привязать коллекцию непосредственно к элементу управления вкладками.

Обновление: Итак, что я на самом деле сделал для своего собственного решенияэто использовать ListView и элемент управления контентом вместо элемента управления вкладкой.Я использую стиль, чтобы сделать вид списка похожим на вкладку.Модель представления предоставляет набор моделей дочерних представлений и позволяет пользователю выбрать одну из них через представление списка.Затем элемент управления контентом представляет выбранную модель представления, и каждая модель представления имеет связанный шаблон данных, который содержит сетку данных.При такой настройке переключение между моделями представлений в режиме редактирования на сетке приведет к правильному завершению режима редактирования и сохранению данных.

Вот xaml для настройки этого:

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

<ContentControl Content="{Binding Selected}">

I 'Я приму ответ Фила, так как это также должно сработать, но для меня приведенное выше решение выглядит более переносимым между проектами.

Ответы [ 5 ]

10 голосов
/ 24 июня 2011

Я реализовал поведение для 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;
    }
}
7 голосов
/ 04 мая 2011

Мне удалось обойти эту проблему, обнаружив, когда пользователь нажимает на 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));
}

Это в коде позади.

0 голосов
/ 03 сентября 2013

Эта ошибка решена в .NET Framework 4.5.Вы можете скачать его по этой ссылке .

0 голосов
/ 25 мая 2011

Я согласился с тем, что ответил Фил Ган, чтобы обойти эту проблему, нужно определить, когда пользователь нажимает на TabItem, а затем вносить изменения в видимую DataGrid в TabControl.

Вы можете увидеть эту ссылку для проблемы схожести ...

Gridview не виден даже после связывания ...

0 голосов
/ 26 апреля 2011

То, что вы должны сделать, довольно близко к тому, что сказал @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;
        }            
    }
...