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

发布于 2024-12-11 20:08:23 字数 2258 浏览 0 评论 0原文

我有一个用 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,fe

[0] => NameOfItem = "FirstOne", Properties = {99, #FF00FF00}
[1] => NameOfItem = "SecondOne", Properties = {50, #FFFF0000}
etc.

As您可以看到,当此方法 DownloadItems 完成其工作时,会引发 OnDownloadCompleted 事件。在主线程中,

void listOfItems_OnDownloadCompleted(object sender, EventArgs args)
{
    dataGrid.Dispatcher.Invoke(new Action(() => { 
                dataGrid.ItemsSource = ListOfItemsInstance.ListToBind;
            }));
}

MainWindow.xaml 上的以下代码 DataGrid 填充了值,因为以下 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>

它工作得很好。但有这个问题。尝试取消注释已注释的 xaml 代码段,您将收到 Must create DependencySource on same Thread as the DependencyObject. 错误。

最后,我的问题是,如何避免这个错误?

编辑:

最终应该看起来像这样。这张图片取自 MS Excel,并在 Adob​​e Photoshop 中着色。

示例

I have an application written in wpf, which downloads some webpages, parses html code and saves some values.

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

This is the object model I'm using. It's simplified version and I don't want you to reorganize it, there is a reason I wrote it this way. In ListOfItems class is method which does all the job (there are some other methods used inside to make code readable) - downloads source, parses and fills ListToBind with data, f.e.

[0] => NameOfItem = "FirstOne", Properties = {99, #FF00FF00}
[1] => NameOfItem = "SecondOne", Properties = {50, #FFFF0000}
etc.

As you can see, when this method DownloadItems completes its job, OnDownloadCompleted event is raised. In the main thread is following code

void listOfItems_OnDownloadCompleted(object sender, EventArgs args)
{
    dataGrid.Dispatcher.Invoke(new Action(() => { 
                dataGrid.ItemsSource = ListOfItemsInstance.ListToBind;
            }));
}

DataGrid on the MainWindow.xaml is filled with values, because of following xaml code snippet.

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

It works just fine. But there is this problem. Try to uncomment commented xaml snippet and you will get Must create DependencySource on same Thread as the DependencyObject. error.

Finally, my question is, how to avoid this error?

EDIT:

It should look like this in the end. This picture is taken from MS Excel and coloured in Adobe Photoshop.

example

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

貪欢 2024-12-18 20:08:23

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

The SolidColorBrush is a Freezable which is a derived DispatcherObject. DispatcherObjects have thread affinity - i.e it can only be used/interacted with on the thread on which it was created. Freezables however do offer the ability to freeze an instance. This will prevent any further changes to the object but it will also release the thread affinity. So you can either change it so that your property is not storing a DependencyObject like SolidColorBrush and instead just store the Color. Or you can freeze the SolidColorBrush that you are creating using the Freeze method.

不顾 2024-12-18 20:08:23

我认为标准方法是从 Freezable 派生数据对象,并在将其传递给另一个线程之前将其冻结。一旦对象被冻结,您就无法再更改它,因此不存在线程错误的危险。

另一种选择可能是使数据对象成为普通 C# 对象(不是从 DispatcherObject 派生)并自行实现 INotifyPropertyChanged

I think the standard way is to derive the data object from Freezable and Freeze it before passing it to another thread. Once the object is frozen, you can't change it any more, so there's no danger of threading bugs.

Another option might be to make the data object a plain C# object (not derived from DispatcherObject) and implement INotifyPropertyChanged yourself.

鱼窥荷 2024-12-18 20:08:23

在主线程上设置 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;

Its not enough to set your dataGrid.ItemsSource on the main thread. You must create each item on the main-thread.
Something like:

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;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文