C# 4.0
In una view con all’interno una DataGrid mi capitava a volte, aggiungendo o modificando una riga,
la seguente exception:
"’DeferRefresh’ is not
allowed during an AddNew or EditItem transaction"
Girando quindi per la
rete ho trovato un behaviour che ho poi adattato alle mie esigenze, in pratica
al lost focus della grid viene forzato un CommitNew o un CommitEdit a seconda
del caso.
A seguire il behaviour e
la relativa modifica allo XAML.
using
System.ComponentModel;
using
System.Windows.Input;
using
System.Windows;
using
System.Windows.Controls;
namespace
Admin.Console.Behaviour
{
class DataGridCommitOnUnfocusedBehaviour
{
#region
DataGridCommitOnUnfocusedBehaviour
public static bool
GetDataGridCommitOnUnfocused(DataGrid
datagrid)
{
return
(bool)datagrid.GetValue(DataGridCommitOnUnfocusedProperty);
}
public static void
SetDataGridCommitOnUnfocused(
DataGrid
datagrid, bool value)
{
datagrid.SetValue(DataGridCommitOnUnfocusedProperty, value);
}
public static readonly DependencyProperty DataGridCommitOnUnfocusedProperty
=
DependencyProperty.RegisterAttached(
"DataGridCommitOnUnfocused",
typeof(bool),
typeof(DataGridCommitOnUnfocusedBehaviour),
new
UIPropertyMetadata(false,
OnDataGridCommitOnUnfocusedChanged));
static void OnDataGridCommitOnUnfocusedChanged(
DependencyObject
depObj, DependencyPropertyChangedEventArgs
e)
{
DataGrid
datagrid = depObj as DataGrid;
if
(datagrid == null)
return;
if
(e.NewValue is bool
== false)
return;
if
((bool)e.NewValue)
{
datagrid.LostKeyboardFocus +=
CommitDataGridOnLostFocus;
datagrid.DataContextChanged +=
CommitDataGridOnDataContextChanged;
}
else
{
datagrid.LostKeyboardFocus -=
CommitDataGridOnLostFocus;
datagrid.DataContextChanged -=
CommitDataGridOnDataContextChanged;
}
}
static void CommitDataGridOnLostFocus(object sender, KeyboardFocusChangedEventArgs
e)
{
DataGrid
senderDatagrid = sender as DataGrid;
if
(senderDatagrid == null)
return;
UIElement
focusedElement = Keyboard.FocusedElement as UIElement;
if
(focusedElement == null)
return;
DataGrid
focusedDatagrid = GetParentDatagrid(focusedElement); //let's
see if the new focused element is inside a datagrid
if
(focusedDatagrid == senderDatagrid)
{
return;
//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
}
//otherwise,
the focus went outside the datagrid; in order to avoid exceptions like
("DeferRefresh' is not allowed during an AddNew or EditItem
transaction")
//or
("CommitNew is not allowed for this view"), we undo the possible
pending changes, if any
IEditableCollectionView
collection = senderDatagrid.Items;
if
(collection.IsEditingItem)
{
collection.CommitEdit();
}
else
if (collection.IsAddingNew)
{
collection.CommitNew();
}
}
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
{
ItemsPresenter
parentItemsPresenter = VisualTreeFinder.FindParentControl<ItemsPresenter>((element as ComboBoxItem));
ComboBox
combobox = parentItemsPresenter.TemplatedParent as
ComboBox;
childElement = combobox;
}
else
{
childElement = element;
}
DataGrid
parentDatagrid = VisualTreeFinder.FindParentControl<DataGrid>(childElement); //let's see if the new focused element is inside a datagrid
return
parentDatagrid;
}
static void CommitDataGridOnDataContextChanged(object sender, DependencyPropertyChangedEventArgs
e)
{
DataGrid
senderDatagrid = sender as DataGrid;
if
(senderDatagrid == null)
return;
IEditableCollectionView
collection = senderDatagrid.Items;
if
(collection.IsEditingItem)
{
collection.CommitEdit();
}
else
if (collection.IsAddingNew)
{
collection.CommitNew();
}
}
#endregion
DataGridCommitOnUnfocusedBehaviour
}
}
class
VisualTreeFinder
{
/// <summary>
/// Find a specific parent object type in the visual tree
/// </summary>
public static T FindParentControl<T>(DependencyObject outerDepObj) where T : DependencyObject
{
DependencyObject
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;
}
}
Nello XAML ho introdotto
il seguente attributo evidenziato in giallo:
<DataGrid AutoGenerateColumns="False" Height="200" HorizontalAlignment="Left" Margin="132,43,0,0" VerticalAlignment="Top" Width="400"
ItemsSource="{Binding Threshold.Hierarchies, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
behaviour:DataGridCommitOnUnfocusedBehaviour.DataGridCommitOnUnfocused="True">
<DataGrid.RowValidationRules>
<Common:RowDataInfoValidationRule ValidationStep="UpdatedValue"
/>
</DataGrid.RowValidationRules>
<DataGrid.Columns>
<DataGridTextColumn Width="190" Binding="{Binding
Path=Code,
UpdateSourceTrigger=PropertyChanged}"
EditingElementStyle="{StaticResource CellEditStyle}"
Header="{Binding Source={StaticResource
CustomResources}, Path=LocalizedStrings.grdThresholdHiearchyCode}"/>
<DataGridCheckBoxColumn
Width="190"
Binding="{Binding Path=Included, UpdateSourceTrigger=PropertyChanged}"
Header="{Binding Source={StaticResource
CustomResources}, Path=LocalizedStrings.grdThresholdHiearchyIncluded}"/>
</DataGrid.Columns>
</DataGrid>