There is a known bug in the standard WPF DataGrid
when the user clicks on the last row to add a new row.
An exception is thrown because a ConvertBack method (on the default converter) fails when dealing with the MS.Internal.NamedObject
that represents the 'NewItemPlaceholder'. This instance is used to represent the blank “new row” if CanUserAddRows is set to True (and the collection supports it). In fact it appears as if the FormatException is actually being thrown within an exception handler whilst attempting to Trace the binding failure. See Nigel Spencer's Blog for more in formation.
Basically the work-around is to add a converter on the SelectedItem
binding:
public class IgnoreNewItemPlaceholderConverter : IValueConverter
{
private const string newItemPlaceholderName = "{NewItemPlaceholder}";
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null && value.ToString() == newItemPlaceholderName)
value = DependencyProperty.UnsetValue;
return value;
}
}
where an example of its use in XAML would be:
<Window.Resources>
<converters:IgnoreNewItemPlaceHolderConverter x:Key="ignoreNewItemPlaceHolderConverter"/>
</Window.Resources>
<toolkit:DataGrid ItemsSource="{Binding Persons}"
AutoGenerateColumns="False"
SelectedItem="{Binding SelectedPerson, Converter={StaticResource ignoreNewItemPlaceHolderConverter}}"
IsSynchronizedWithCurrentItem="True">...</toolkit:DataGrid>
My Problem is that I have attempted to implement this 'fix'/'hack' to my own DataGrid
without success. I have a custom DataGrid
in which I have overridden the standard DataGrid
control via:
/// <summary>
/// Class that overrides the standard DataGrid and facilitates the
/// the loading and binding of multiple cultures.
/// </summary>
public class ResourceDataGrid : DataGrid
{
private IResourceStrategy strategy;
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == DataContextProperty)
HandleDataContextChanged(e.OldValue, e.NewValue);
if (e.Property == ItemsSourceProperty)
HandleItemsSourceChanged(e.OldValue, e.NewValue);
}
private void HandleDataContextChanged(object oldValue, object newValue)
{
if (strategy != null)
strategy.ResourceCulturesChanged -= Strategy_ResourceAdded;
// Pull in the required data from the strategy.
var resourceDataViewModel = newValue as ResourceDataViewModel;
if (resourceDataViewModel == null)
return;
strategy = resourceDataViewModel.Strategy;
strategy.ResourceCulturesChanged += Strategy_ResourceAdded;
}
private void Strategy_ResourceAdded(object sender, ResourceCollectionChangedEventArgs args)
{
UpdateGrid();
}
private void HandleItemsSourceChanged(object oldValue, object newValue)
{
if (Equals(newValue, oldValue))
return;
UpdateGrid();
}
private void UpdateGrid()
{
if (strategy == null) return;
// Update the bound data set.
foreach (CollectionTextColumn item in Columns.OfType<CollectionTextColumn>().ToList())
{
// Remove dynamic columns of the current CollectionTextColumn.
foreach (var dynamicColumn in Columns.OfType<DynamicTextColumn>().ToList())
Columns.Remove(dynamicColumn);
int itemColumnIndex = Columns.IndexOf(item) + 1;
string collectionName = item.Collection;
List<string> headers = strategy.ResourceData.FileCultureDictionary.Select(c => c.Value).ToList();
// Check if ItemsSource is IEnumerable<object>.
var data = ItemsSource as IEnumerable<object>;
if (data == null)
return;
// Copy to list to allow for multiple iterations.
List<object> dataList = data.ToList();
var collections = dataList.Select(d => GetCollection(collectionName, d));
int maxItems = collections.Max(c => c.Count());
for (int i = 0; i < maxItems; i++)
{
// Header binding.
string header = GetHeader(headers, i);
Binding columnBinding = new Binding(String.Format("{0}[{1}]", item.Collection, i));
Columns.Insert(itemColumnIndex + i,
new DynamicTextColumn(item) { Binding = columnBinding, Header = header });
}
}
}
private IEnumerable<object> GetCollection(string collectionName, object collectionHolder)
{
// Reflect the property which holds the collection.
PropertyInfo propertyInfo = collectionHolder.GetType().GetProperty(collectionName);
object propertyValue = propertyInfo.GetValue(collectionHolder, null);
var collection = propertyValue as IEnumerable<object>;
return collection;
}
private static string GetHeader(IList<string> headerList, int index)
{
int listIndex = index % headerList.Count;
return headerList[listIndex];
}
}
To show the bindings I use the ResourceDataGrid
in XAML as follows:
<Window.Resources>
<converters:IgnoreNewItemPlaceHolderConverter x:Key="ignoreNewItemPlaceHolderConverter"/>
</Window.Resources>
<Controls:ResourceDataGrid x:Name="resourceDataGrid"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding SelectedResource, Converter={StaticResource ignoreNewItemPlaceholderConverter}, Mode=TwoWay}"
ItemsSource="{Binding Path=Resources, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, IsAsync=True}">
<Controls:ResourceDataGrid.Columns>
<DataGridTemplateColumn Header="KeyIndex" SortMemberPath="KeyIndex" CellStyle="{StaticResource MetroDataGridCell}"
CellTemplate="{StaticResource readOnlyCellUpdatedStyle}" IsReadOnly="True"/>
<DataGridTextColumn Header="FileName" CellStyle="{StaticResource MetroDataGridCell}"
Binding="{Binding FileName}" IsReadOnly="True"/>
<DataGridTextColumn Header="ResourceName" Binding="{Binding ResourceName}"
CellStyle="{StaticResource MetroDataGridCell}" IsReadOnly="False"/>
<Controls:CollectionTextColumn Collection="ResourceStringList" Visibility="Collapsed"
CellStyle="{StaticResource MetroDataGridCell}"/>
</Controls:ResourceDataGrid.Columns>
</Controls:ResourceDataGrid>
Now, I implement the IgnoreNewItemPlaceHolderConverter
converter and this is invoked and sets DependencyProperty.UnsetValue
; it does it's job. However, the overridden OnPropertyChanged
event is invoked and the DependencyPropertyChangedEventArgs
e
contains a validation error:
ErrorContent = "Value '{NewItemPlaceholder}' could not be converted."
I have implemented a basic example with two columns and this works. Is this due to my more complex custom DataGrid
and how can I stop this validation error from occurring?
Thanks for your time.