我将可观察的集合绑定到数据网格。通过异步调用从服务器获取集合。集合模型包含一个名为' BackgroundBrush'类型' System.Windows.Media.Brush'它绑定到数据网格中模板列的背景颜色。 Brush属性可以是SolidColorBrush或LinearGradientBrush取决于应用于该属性的业务逻辑。
在向Data Grid渲染数据时,应用程序会抛出这样的异常"必须在与DependencyObject相同的线程上创建DependencySource。"
调试问题时发现的事情
问题在于'背景'属性。注释掉这个属性绑定并使异步调用工作正常。
使服务调用同步工作正常,但我需要将其作为异步服务。
在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>
答案 0 :(得分: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 };
}
答案 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);
}