必须在与DependencyObject相同的Thread上创建DependencySource

时间:2011-10-23 10:11:44

标签: c# wpf multithreading datagrid dependency-properties

我有一个用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中着色。

example

3 个答案:

答案 0 :(得分:22)

SolidColorBrushFreezable,它是派生的DispatcherObject。 DispatcherObjects具有线程关联性 - 即它只能在创建它的线程上使用/交互。然而,Freezables提供了冻结实例的能力。这将阻止对对象的任何进一步更改,但它也将释放线程关联。因此,您可以更改它,以便您的属性不存储像SolidColorBrush这样的DependencyObject,而只是存储Color。或者,您可以使用Freeze方法冻结正在创建的SolidColorBrush。

答案 1 :(得分:1)

我认为标准方法是在将数据对象传递给另一个线程之前从FreezableFreeze派生数据对象。一旦对象被冻结,你就不能再改变它了,所以没有线程错误的危险。

另一种选择可能是使数据对象成为普通的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;