我有一个用wpf编写的应用程序,可以下载一些网页,解析HTML代码并保存一些值。
class ListOfItems
{
public List<SomeObject> ListToBind;
public void DownloadItems()
{
Task.Factory.StartNew(() =>
{
...
...
if (OnDownloadCompleted != null)
OnDownloadCompleted(this, EventArgs.Empty);
}
}
}
class SomeObject
{
public string NameOfItem;
public MyClass Properties;
}
class MyClass
{
public int Percentage;
public SolidColorBrush Color;
}
这是我正在使用的对象模型。这是简化版本,我不希望你重新组织它,我有这样写道的原因。在ListOfItems
类中是执行所有工作的方法(内部使用其他一些方法使代码可读) - 下载源代码,使用数据分析和填充ListToBind
,等等。
[0] => NameOfItem = "FirstOne", Properties = {99, #FF00FF00}
[1] => NameOfItem = "SecondOne", Properties = {50, #FFFF0000}
etc.
如您所见,当此方法DownloadItems
完成其工作时,会引发OnDownloadCompleted
事件。在主线程中是以下代码
void listOfItems_OnDownloadCompleted(object sender, EventArgs args)
{
dataGrid.Dispatcher.Invoke(new Action(() => {
dataGrid.ItemsSource = ListOfItemsInstance.ListToBind;
}));
}
由于以下xaml代码段,<{1}}上的DataGrid会填充值。
MainWindow.xaml
它运作得很好。但是存在这个问题。尝试取消注释已注释的xaml代码段,您将收到<DataGrid Name="dataGrid" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Tag" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Color" Binding="{Binding MyClass.Percentage}">
<!--<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Background" Value="{Binding MyClass.Color}" />
</Style>
</DataGridTextColumn.CellStyle>-->
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
错误。
最后,我的问题是,如何避免这个错误?
修改
最终看起来应该是这样的。此图片来自MS Excel并在Adobe Photoshop中着色。
答案 0 :(得分:22)
SolidColorBrush是Freezable,它是派生的DispatcherObject。 DispatcherObjects具有线程关联性 - 即它只能在创建它的线程上使用/交互。然而,Freezables提供了冻结实例的能力。这将阻止对对象的任何进一步更改,但它也将释放线程关联。因此,您可以更改它,以便您的属性不存储像SolidColorBrush这样的DependencyObject,而只是存储Color。或者,您可以使用Freeze方法冻结正在创建的SolidColorBrush。
答案 1 :(得分:1)
我认为标准方法是在将数据对象传递给另一个线程之前从Freezable
和Freeze
派生数据对象。一旦对象被冻结,你就不能再改变它了,所以没有线程错误的危险。
另一种选择可能是使数据对象成为普通的C#对象(不是从DispatcherObject
派生的)并自己实现INotifyPropertyChanged
。
答案 2 :(得分:0)
仅在主线程上设置dataGrid.ItemsSource
是不够的。您必须在主线程上创建每个项目。
类似的东西:
List<SomeObject> l = new List<SomeObject>();
foreach(var item in ListOfItemsInstance.ListToBind)
{
l.Add(new SomeObject(){NameOfItem = item.NameOfItem, Properties = item.Properties });
}
dataGrid.ItemsSource = l;