Brush类型的绑定属性在异步调用时抛出异常

时间:2015-02-11 10:42:09

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

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

在向Data Grid渲染数据时,应用程序会抛出这样的异常"必须在与DependencyObject相同的线程上创建DependencySource。"

调试问题时发现的事情

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

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

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

  4. 以下是示例应用程序代码

    模型

    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>
    

2 个答案:

答案 0 :(得分:2)

System.Media.BrushDependencyObject,因此需要在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 };
    }

答案 1 :(得分: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; }

或者你没有绑定到Model并遵循标准的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);
}