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>
C'è qualcosa che mi sfugge. Ho provato sia la tua che la soluzione originale nell'msdn ma l'errore in compilazione è sempre
RispondiEliminaProperty 'DataGridCommitOnUnfocused' is not attachable to elements of type 'DataGrid'
Puoi allegare lo xaml completo?
F.
Ciao Francesco,forse nella view ti manca il riferimento al behaviour, che nel mio caso è:
RispondiEliminaxmlns:behaviour="clr-namespace:Admin.Console.Behaviour"
Vorrei inviarti lo XAML completo della view ma c'è un limite ai caratteri, quindi se vuoi mandami una mail a: diego_parolin "chiocciola" hotmail.com
Diego.
Ti ringrazio per la risposta, il behaviour viene riconosciuto ma non mi consente di inserirlo nel DataGrid. Inizialmente pensavo anch'io ad un problema di namespaces.
RispondiEliminaHo risolto ugualmente attaccandomi all'evento LostFocus del DataGrid, e chiamando il metodo _datagrid.CancelEdit() nel code behind.
Il mio caso è il seguente, ho 2 Page: A e B.
Premendo un bottone all'interno della cella del datagrid in A, passo a B. A volte se da B torno in A (bottone "indietro") ricevevo la famosa eccezione; questo nonostante che il datagrid di A non sia abilitato ad aggiungere/rimuovere/editare le proprie righe.
Comunque il baco è segnalato come risolto nella versione 4.5 del WPF.
la mia mail è francesco.umani gmail com