72241

Bug in WPF DataGrid

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> <hr>

<strong>My Problem is that</strong> 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. <strong>Is this due to my more complex custom DataGrid and how can I stop this validation error from occurring?</strong>

Thanks for your time.

Answer1:

if you do mvvm you could go another way if it feasible for you. i set CanUserAddRows="False"in my projects but add a button with ICommand "AddNewItemCommand" within these command i simply add a new item to my ItemsSource collection - and then i'm done :)

if this is no way for you - simply ignore my answer :)

Answer2:

I managed to get a workaround by removing the element from the dragged list see the snippet bellow:

private void preventDragEmpty(object sender, DragEventArgs e) { List<dynamic> h = new List<dynamic>(); try { //i'm using GongSolutions to handle drag and drop wich is highlly recommended //but if you dont use it just adapt to the correct type! h = e.Data.GetData("GongSolutions.Wpf.DragDrop") as List<dynamic>; if (h != null) { h.Remove(h.FirstOrDefault(x => x.ToString() == "{NewItemPlaceholder}")); e.Data.SetData(h); } } finally { e.Handled = true; } }

And to use it, you can attach to any type of list like:

<DataGrid ... PreviewDragOver="preventDragEmpty" />

Recommend

  • Java: equals and ==
  • QGlWidget fullscreen no menu
  • How does `super` interacts with a class's `__mro__` attribute in multiple inheritance?
  • Playing a monetized YouTube song inside of a Google Chrome Extension. Do I have any options?
  • Django and Heroku: Static files work with 'foreman start' but not './manage.py runser
  • How to Configure Log4Net Custom Object Renderer for Generic Objects?
  • Unable to gem install nokogiri
  • Is there an HTML code that can make my background picture transparent and my text non-transparent?
  • Creating a Multidimensional, Associative Array in VBScript
  • CSS bleed-through with cfinput type=“datefield”
  • Android changing fragment order inside FragmentPagerAdapter
  • IE11 throwing “SCRIPT1014: invalid character” where all other browsers work
  • nonblocking BIO_do_connect blocked when there is no internet connected
  • Insert new calendar with SyncAdapter- Calendar API Android
  • Django simple Captcha “No module named fields” error
  • Extracting HTML between tags
  • Get data from AJAX - How to
  • C++ Partial template specialization - design simplification
  • NHibernate Validation Localization with S#arp Architecture
  • Repeat a vertical line on every page in Report Builder / SSRS
  • QLineEdit password safety
  • Android screen density dpi vs ppi
  • Nant, Vault & Windows Integrated Authentication
  • Regex thinks I'm nesting, but I'm not
  • C# - Serializing and deserializing static member
  • TFS: Get latest causes slow project reloading
  • Incrementing object id automatically JS constructor (static method and variable)
  • Javascript Callbacks with Object constructor
  • Join two tables and save into third-sql
  • How to model a transition system with SPIN
  • Redux, normalised entities and lodash merge
  • How to make Safari send if-modified-since header?
  • ORA-29908: missing primary invocation for ancillary operator
  • jQuery tmpl and DataLink beta
  • How can I estimate amount of memory left with calling System.gc()?
  • Run Powershell script from inside other Powershell script with dynamic redirection to file
  • WPF Applying a trigger on binding failure
  • how does django model after text[] in postgresql [duplicate]
  • Converting MP3 duration time
  • java string with new operator and a literal