[英]Bug in WPF DataGrid
当用户单击最后一行以添加新行时,标准WPF DataGrid
存在一个已知错误。
抛出异常是因为在处理代表'NewItemPlaceholder'的MS.Internal.NamedObject
,ConvertBack方法(在默认转换器上)失败。 如果CanUserAddRows设置为True(并且集合支持它),则此实例用于表示空白的“新行”。 实际上,在尝试跟踪绑定失败时,似乎实际上在异常处理程序中抛出了FormatException。 有关更多信息,请参阅Nigel Spencer的博客 。
基本上解决方法是在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
以及如何阻止此验证错误发生?
谢谢你的时间。
如果你做mvvm,你可以采取另一种方式,如果它可行的话。 我在我的项目中设置了CanUserAddRows =“False”,但在这些命令中添加了一个带有ICommand“AddNewItemCommand”的按钮,我只需在我的ItemsSource集合中添加一个新项目 - 然后我就完成了:)
如果这对你不行 - 简单地忽略我的答案:)
我设法通过从拖动列表中删除元素来获得解决方法,请参阅下面的代码段:
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" />
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.