【问题标题】:Bug in WPF DataGridWPF 数据网格中的错误
【发布时间】:2013-12-12 06:40:16
【问题描述】:

当用户单击最后一行以添加新行时,标准 WPF DataGrid 中存在一个已知错误。

由于 ConvertBack 方法(在默认转换器上)在处理表示“NewItemPlaceholder”的MS.Internal.NamedObject 时失败,因此引发异常。如果 CanUserAddRows 设置为 True(并且集合支持它),则此实例用于表示空白的“新行”。实际上,在尝试跟踪绑定失败时,似乎 FormatException 实际上是在异常处理程序中抛出的。有关更多信息,请参阅Nigel Spencer's Blog

基本上解决方法是在SelectedItem 绑定上添加一个转换器:

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;
    }
}

它在 XAML 中的使用示例如下:

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

我的问题是我试图对我自己的DataGrid 实施这个“修复”/“破解”但没有成功。我有一个自定义DataGrid,其中我通过以下方式覆盖了标准DataGrid 控件:

/// <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];
    }
}

为了显示绑定,我在 XAML 中使用了ResourceDataGrid,如下所示:

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

现在,我实现了IgnoreNewItemPlaceHolderConverter 转换器,它被调用并设置DependencyProperty.UnsetValue;它完成了它的工作。然而,被覆盖的OnPropertyChanged 事件被调用并且DependencyPropertyChangedEventArgs e 包含一个验证错误:

ErrorContent = "无法转换值'{NewItemPlaceholder}'。"

我已经实现了一个包含两列的基本示例,这很有效。 这是由于我更复杂的自定义 DataGrid 造成的吗?如何阻止此验证错误的发生?

感谢您的宝贵时间。

【问题讨论】:

  • 您是否尝试过禁用SelectedItem 上的验证错误,例如SelectedItem="{Binding SelectedResource, Converter={StaticResource ignoreNewItemPlaceholderConverter}, Mode=TwoWay, NotifyOnValidationError=False}"
  • 是的,我有。它仍然在我的DataGrid 周围显示一个红色框,DependencyPropertyChangedEventArgs 包含此错误消息。但是请注意,它不会抛出异常 - 我似乎可以让它正常运行,但我不知道为什么。就像我上面说的,我在一个带有基本自定义 DataGrid 的示例应用程序中工作得很好......
  • 我尽可能多地复制了你的代码,但我没有收到验证错误,你使用的是什么 .NET 版本
  • 我正在使用 .NET4.5 (V4.5.50709)。这很奇怪。也许是我添加到自定义DataGrid 的数据导致了问题?你愿意看看解决方案吗?如果不是,我明白(问我有点厚脸皮!)。如果没有,我想我将按照以下答案删除此功能的尝试...

标签: c# wpf mvvm binding datagrid


【解决方案1】:

如果您使用 mvvm,如果可行,您可以采用另一种方式。 我在我的项目中设置了 CanUserAddRows="False" 但添加了一个带有 ICommand "AddNewItemCommand" 的按钮 在这些命令中,我只需将一个新项目添加到我的 ItemsSource 集合中 - 然后我就完成了:)

如果这不适合你 - 只需忽略我的回答 :)

【讨论】:

  • 感谢您的回答。我已经实现了这个。我只是认为按照 VS2012 实现“键入时”添加会“很好”。可能是我不得不忘记这个想法,只需要按钮......再次感谢。
【解决方案2】:

我设法通过从拖动的列表中删除元素来获得解决方法,请参见下面的 sn-p:

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;
    }
}

要使用它,您可以附加到任何类型的列表,例如:

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

【讨论】:

    猜你喜欢
    • 2014-02-08
    • 2013-04-02
    • 1970-01-01
    • 1970-01-01
    • 2010-12-19
    • 1970-01-01
    • 2019-03-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多