【问题标题】:Binding property of type Brush throws exception on asynchronous callBrush类型的绑定属性在异步调用时抛出异常
【发布时间】:2015-04-11 16:46:26
【问题描述】:

我正在将一个可观察的集合绑定到一个数据网格。通过异步调用从服务器获取的集合。集合模型包含一个名为“System.Windows.Media.Brush”类型的“BackgroundBrush”属性,该属性绑定到数据网格中模板列的背景颜色。 Brush 属性可以是 SolidColorBrush 或 LinearGradientBrush,具体取决于应用于该属性的业务逻辑。

在向数据网格呈现数据时,应用程序会抛出类似“必须在与 DependencyObject 相同的线程上创建 DependencySource”这样的异常。

调试问题时注意的事项

  1. 问题在于“背景”属性。注释掉这个属性绑定并使异步调用工作正常。

  2. 使服务调用同步可以正常工作,但我需要它作为异步调用。

  3. 在 Application.Current.Dispatcher.Invoke 中进行服务调用没有任何区别

以下是示例应用程序代码

型号

public class Model
{
    public string Name { get; set; }

    public string Email { get; set; }

    public string Address { get; set; }

    public Brush BackgroundBrush { get; set; }
}

查看模型

private ObservableCollection<Model> _dataCollection;

public ObservableCollection<Model> DataCollection
{
    get { return _dataCollection; }
    set
    {
        _dataCollection = value;
        RaisePropertyChanged(() => DataCollection);
    }
}

public RelayCommand LoadCommand { get; private set; }

private async Task LoadData()
{
    var list = await Task.Run(() => GetData());
    DataCollection = new ObservableCollection<Model>(list);

}

private ObservableCollection<Model> GetData()
{
    return new ObservableCollection<Model>()
    {
        new Model()
        {
            Address = "a",
            Email = "2",
            Name = "3",
            BackgroundBrush = new SolidColorBrush(Colors.SaddleBrown)
        }
    };
}

查看

<Grid x:Name="LayoutGrid">
    <DataGrid ItemsSource="{Binding DataCollection}" AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTemplateColumn>
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                    <Border Background="{Binding BackgroundBrush}">
                        <TextBlock Text="{Binding Name}"></TextBlock>
                        </Border>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

【问题讨论】:

  • 不要将您的代码发布为图像。请改用正确的代码块。

标签: wpf xaml data-binding async-await mvvm-light


【解决方案1】:

这是因为 await Task.Run 内部的所有内容都在不同的线程上运行。因此您无法更改其中的绑定源DataCollection。由于ObservableCollection 触发事件以更新绑定目标,因此它不允许来自不同线程的更改。

所以你的GetData 函数应该返回一个正常的集合:

private IEnumerable<Model> GetData()
{
    return new List<Model>()
    {
         new Model(){...}
    };
}

另一个问题是Brush。你的模型(线程安全)应该使用Color,你的viewModel(可绑定)应该使用Brush。因此,您应该在模型中添加一个 Color 属性,该属性可以在 GetData 的异步调用中设置。像这样的:

private Color _color;
public Color Color 
{ 
    get{ return _color; } 
    set
    { 
        _color = value; 
        Dispatcher.Invoke(()=>Brush = new SolidColorBrush(Color)); 
    } 
}
public Brush Brush { get; set; }

或者您不绑定到模型并遵循标准 MVVM 模式。

对于涉及依赖属性或您需要从调度程序调用的任何绑定源的部分。例如

Dispatcher.Invoke(() => aThreadSafeFunction());

但是在这种情况下,您可以先阅读整个列表,然后将其转换为 ObservableCollection:

private async Task LoadData()
{
    //load thread-safe data asynchronously
    var list = await Task.Run(() => GetData());
    //set binding source in the same thread
    DataCollection = new ObservableCollection<Model>(list);
}

【讨论】:

  • 不走运。抛出相同的异常“必须在与 DependencyObject 相同的线程上创建 DependencySource。”
  • 谢谢。但是画笔可以是 SolidColorBrush 或 LinearGradientBrush,它不能用 Color.Right? 代替。实际上,画笔是在模型数据的帮助下从业务逻辑生成的。所以我必须将逻辑操作从业务转移到视图模型并在主线程中执行。对吗?
  • 有很多方法可以使它成为线程安全的。在 GetData 的稍微不同的实现中,您可以直接在模型中使用画笔并加载其值。但是您必须确保特定的设置画笔值操作是在同一个线程中完成的。即来自主线程的调度程序应该调用该操作。
  • 看来toadflakz已经说出了我想说的!
【解决方案2】:

System.Media.Brush 是一个DependencyObject,因此需要在Dispatcher 线程上创建。

ObservableCollection 通过INotifyCollectionChanged 使用UI 通知来启用它的Observer 模式实现,这意味着它还需要在Dispatcher 线程上构建。如果不编写自定义实现以在正确的线程上引发通知,则无法从另一个线程(即异步)加载 ObservableCollection 的内容。

编辑:

为了解决您的问题 - 在构造函数中创建 ObservableCollection 并且永远不要覆盖来自其他线程的属性引用。

LoadData()GetData()如下:

    private async Task LoadData()
    {
        var list = await Task.Run(() => GetData());
        list.ForEach(item => Dispatcher.Invoke(() => 
        {
           DataCollection.Add(item);
        }));

    }

    private List<Model> GetData()
    {
        var modelObject =
            new Model()
            {
                Address = "a",
                Email = "2",
                Name = "3",
            };
        Dispatcher.Invoke(() => 
        { 
            modelObject.BackgroundBrush = new SolidColorBrush(Colors.SaddleBrown);
        });

       return new List<Model>(){ modelObject };
    }

【讨论】:

  • 它的工作..感谢 toadflakz..在现实世界场景中需要考虑的另一个案例。在我原来的解决方案中,GetData() 方法是一个业务层调用,它是一个不同的项目(WPF 项目除外)。在这种情况下,调度程序不起作用。正确的?。所以唯一的解决方法是将逻辑操作从业务移动到视图模型并在主线程中执行。对吗?
  • 在为项目调用Add 之前,您可以轻松地将BackgroundBrush set 调用移动到LoadData 方法。小心将 BackgroundBrush 之类的演示项目嵌入到您的 Model 中 - 这样会带来很多痛苦和痛苦...... ;-)
  • 是的。我知道。实际上,该模型是我的业务对象,我想对用于生成背景画笔的业务逻辑进行单元测试。否则我会在转换器中进行。
猜你喜欢
  • 2016-07-10
  • 1970-01-01
  • 2016-07-10
  • 2019-06-02
  • 1970-01-01
  • 2021-02-07
  • 2012-02-03
  • 2013-05-24
  • 1970-01-01
相关资源
最近更新 更多