Каков надлежащий способ обработки нескольких datagrids в элементе управления вкладкой, чтобы ячейки покидали режим редактирования при изменении вкладок?
В wpf я настраиваю элемент управления табуляции, который связывается с коллекцией объектов. Каждый объект имеет шаблон данных с сеткой данных, представляющей данные. Если я выберу конкретную ячейку и перевежу ее в режим редактирования, оставив сетку, перейдя на другую вкладку, это приведет к тому, что исключение ниже будет передано при возврате datagrid:
"DeferRefresh" не разрешено во время транзакции AddNew или EditItem.
Похоже, что ячейка никогда не покидала режим редактирования. Есть ли простой способ вывести камеру из режима редактирования или что-то еще происходит здесь?
Обновление: Похоже, если я не привяжу элемент управления вкладкой к источнику данных, а вместо этого явным образом определяю каждую вкладку, а затем привязываю каждый элемент в источнике данных к элементу управления содержимым, эта проблема возникает далеко. Это не очень хорошее решение, поэтому мне все равно хотелось бы знать, как привязать коллекцию непосредственно к элементу управления вкладками.
Обновление:. То, что я действительно сделал для своего собственного решения, - это использовать ListView и элемент управления содержимым вместо элемента управления вкладкой. Я использую стиль, чтобы сделать вкладку вида списка похожим. Модель просмотра предоставляет набор моделей детского вида и позволяет пользователю выбирать один из них в виде списка. Затем элемент управления содержимым представляет выбранную модель представления, и каждая модель представления имеет связанный шаблон данных, который содержит сетку данных. При этой настройке переключение между режимами просмотра в режиме редактирования в сетке будет правильно закончить режим редактирования и сохранить данные.
Вот xaml для настройки этого:
<ListView ItemTemplate="{StaticResource MakeItemsLookLikeTabs}"
ItemsSource="{Binding ViewModels}"
SelectedItem="{Binding Selected}"
Style="{StaticResource MakeItLookLikeATabControl}"/>
<ContentControl Content="{Binding Selected}">
Я соглашусь с ответом Фила, так как это тоже должно работать, но для меня решение выше похоже на то, что он будет более переносимым между проектами.
Ответы
Ответ 1
Мне удалось обойти эту проблему, обнаружив, когда пользователь нажимает на 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
Я применил поведение 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 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
То, что я думаю, вам нужно сделать, очень близко к тому, что сказал @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;
}
}
Ответ 4
Я согласился с тем, что ответил Фил Ган, чтобы обойти эту проблему, чтобы определить, когда пользователь нажимает на TabItem, а затем совершает изменения в видимом DataGrid в TabControl.
Вы можете увидеть эту ссылку для проблемы сходства...
Gridview не видно даже после привязки....
Надеюсь, это поможет...
Ответ 5
Эта ошибка решена в .NET Framework 4.5. Вы можете скачать его на этой ссылке.