我有一个DataGrid
,其中包含为一系列日期动态生成的一组列,并使用自定义依赖项属性绑定到网格,
public static readonly DependencyProperty BindableColumnsProperty =
DependencyProperty.RegisterAttached("BindableColumns",
typeof(ObservableCollection<DataGridColumn>),
typeof(DataGridAttachedProperties),
new UIPropertyMetadata(null, BindableColumnsPropertyChanged));
BindableColumnsPropertyChanged
包含以下出现问题的代码:
else if (ne.Action == NotifyCollectionChangedAction.Add)
{
foreach (DataGridColumn column in ne.NewItems)
{
dataGrid.Columns.Add(column);
}
}
else if (ne.Action == NotifyCollectionChangedAction.Remove)
{
foreach (DataGridColumn column in ne.OldItems)
{
dataGrid.Columns.Remove(column);
}
}
当我从InitColumns
代码调用我的RefreshCommand
方法时,在dataGrid.Columns.Remove(column)
我收到错误:
调用线程无法访问此对象,因为它不同 线程拥有它。 我通过将
来解决这个问题Remove
代码更改为:
Application.Current.Dispatcher.BeginInvoke((Action)(() =>
{
dataGrid.Columns.Remove(column);
}));
然后我再试一次,Remove
代码可以正常工作,但我显然在dataGrid.Columns.Add(column)
行上得到了相同的错误。我也改变了:
Application.Current.Dispatcher.BeginInvoke((Action)(() =>
{
dataGrid.Columns.Add(column);
}));
我再次尝试运行刷新命令,新发送的Remove
仍然有效,但现在我收到同样的错误,但BeginInvoke
代表dataGrid.Columns.Add(column)
调用:(at [A-Z](?:\w+)?|in [A-Z](?:\w+)?|of [A-Z](?:\w+)?)
这个我不明白。它肯定是成功删除列的相同线程,但现在看起来像一些新的幻像线程正在尝试添加列。无论是什么原因造成的?
答案 0 :(得分:3)
据我所知,您正在从非UI线程中操纵UI绑定ObservableCollection<DataGridColumn>
。因此,在处理Dispatcher
(以及CollectionChanged
)时使用PropertyChanged
并将更改应用于DataGrid
是正确的方法。
但为什么Remove
有效且Add
没有?
帖子中没有显示,但我怀疑你不只是将DataGridColumn
个后代添加到你的可观察集合中,而且你还在
这是因为DataGridColumn
类继承了(DependencyObject
)DispatcherObject
。 DispatcherObject
类(因此派生类)将当前线程Dispatcher
存储在其构造函数中,然后使用该调度程序验证每个依赖项属性访问是从创建对象的线程完成的。
很快,整个DataGridColumn
创建和操作应该在UI线程上进行。找到创建列的代码,并确保它使用Application.Current.Dispatcher
和一些Dispatcher.Invoke
重载在UI线程上执行。
P.S。虽然以上是我能想到的最合乎逻辑的解释,但您可以通过修改发布的代码来验证假设是否正确,如下所示:
Application.Current.Dispatcher.BeginInvoke((Action)(() =>
{
if (!column.CheckAccess())
{
// Houston, we've got a problem!
}
dataGrid.Columns.Add(column);
}));
并在if
语句中放置一个断点。
答案 1 :(得分:1)
Application.Current.Dispatcher
将为您提供当前应用的Dispatcher
。因此,调用BeginInvoke
并不一定意味着它将起作用:如果不是该线程不是控件的所有者,该怎么办?毕竟,任何线程都可以创建控件。因此,您应该尝试获取控件的Dispatcher
,然后在其上调用BeginInvoke
:
YourControl.Dispatcher.Invoke();
现在这并不意味着你可以简单地这样做:
if (column.CheckAccess())
{
// Great this thread has access so I will remove this column.
dataGrid.Columns.Add(column);
}
该检查仅告诉您当前线程拥有column
但不一定是dataGrid
和Columns
集合(这我不太确定,但您有代码,所以你可以试试吧)。因此,您需要这样做:
if (column.CheckAccess())
{
if (dataGrid.CheckAccess())
{
// Code here. You may need to check `Columns` as well but like I said I am not sure about this one.
}
else
{
dataGrid.Dispatcher.BeginInvoke(/* code here */);
}
}
else
{
column.Dispatcher.BeginInvoke(/* code here */);
}
现在你看到它会让你进入圈内因此,在同一个线程上创建父ui控件及其子项是个好主意。至少这将帮助您找出问题,并且为了将来您不知道不使用Application.Current.Dispatcher
。
答案 2 :(得分:1)
我认为可能存在时间问题。您正在使用异步BeginInvoke
。也许两个Dispatched Actions将同时运行并且您得到此异常。
您可以尝试使用Dipatcher.Invoke
,或在Lock
来电时添加一些DataGrid.Columns
。
答案 3 :(得分:0)
试试此代码
dataGrid.Dispatcher.BeginInvoke((Action)(() =>
{
dataGrid.Columns.Add(column);
}));
而不是你的代码。
我认为您正在使用多个UI线程,因此您会收到此错误。
答案 4 :(得分:0)
您收到此错误是因为您在非ui线程上创建了DataGridColumn。
执行dataGrid.Columns.Add(column);
时,内部DataGrid正在尝试获取新项“DataGridColumn.DisplayIndex
依赖项属性值,并在执行此DataGridColumn调用CheckAccess
时验证当前线程是否是一个线程创建时,此测试失败并导致异常抛出。
因此引发异常的是DataGridColumn。
我已经创建了一个小测试项目来检查这个(see github),特别是两个方法只有线程DataGridColumns的区别在于创建:
这导致异常(在线程池线程上创建列):
await Task.Delay(1).ConfigureAwait(false); //detaching to thread pool thread
ViewModel.Columns = new ObservableCollection<DataGridColumn>(); //initializing colleciton in thread pool, but this gets marshalled by WPF
Dispatcher.Invoke(() => { }, DispatcherPriority.ApplicationIdle); //wait for dispatched PropertyChanged to take effect
//creating cols in thread pool thread
var cols = new List<DataGridColumn>
{
new DataGridTextColumn {Header = "test col 1", Binding = new Binding()},
new DataGridTextColumn {Header = "test col 2", Binding = new Binding()}
};
//adding previously created columns - should throw
foreach (var dataGridColumn in cols)
ViewModel.Columns.Add(dataGridColumn);
这个工作正常(在UI线程上创建列):
//creating cols in UI thread
var cols = new List<DataGridColumn>
{
new DataGridTextColumn {Header = "test col 1", Binding = new Binding()},
new DataGridTextColumn {Header = "test col 2", Binding = new Binding()}
};
await Task.Delay(1).ConfigureAwait(false); //detaching to thread pool thread
ViewModel.Columns = new ObservableCollection<DataGridColumn>(); //initializing colleciton in thread pool
Dispatcher.Invoke(() => { },DispatcherPriority.ApplicationIdle); //wait for dispatched PropertyChanged to take effect
//adding previously created columns - should succeed
foreach (var dataGridColumn in cols)
ViewModel.Columns.Add(dataGridColumn);
我建议你使用更多的MVVM方式,例如创建一个viewView模型,如具有视图无关属性的ColumnViewModel,绑定ObservableCollection,然后在附加的INotifyCollectionChanged实现中创建实际的DataGridColumns,例如:而不是
Application.Current.Dispatcher.BeginInvoke((Action)(() =>
{
dataGrid.Columns.Add(column);
}));
你应该做点什么
Application.Current.Dispatcher.BeginInvoke((Action)(() =>
{
dataGrid.Columns.Add(CreateColumnFromViewModel(columnVm));
}));
反过来,你会得到
如果你有兴趣,我可以详细说明这个
答案 5 :(得分:0)
简单来说,在UI线程上创建DataGridColumn
个实例。
如果您想从async / await中受益,我会采用更加MVVM友好的方法:
我不会使用ObservableCollection<DataGridColumn>
而是使用“描述”所需列的模型列表,例如ObservableCollection<MyColumnDefinition>
其中MyColumnDefinition
包含用于创建DataGridColumn
的属性,例如:映射到列类型,绑定路径,宽度等的值。
然后在MyColumnDefinition
处理程序
DataGridColumn
到PropertyChanged
的转换
在DataGridColumn
的{{1}}属性
DataGrid
个实例时,您仍应使用调度程序
这样,使用以下内容创建列应该没有问题:
Columns