WPF 从异步任务更新 ObservableList 抛出 XamlParseException
WPF Updating ObservableList from async task throws XamlParseException
当我尝试从一个单独的线程然后 ui 线程更新我在 XAML 中使用的 ObservableCollection 时,我得到一个 XamlParseException,它说 DependencySource 必须在同一个线程上创建线程作为 DependencyObject。我正在使用 Caliurn Micro 将 ViewModel 绑定到视图。
我尝试了几种方法来实现我的目标,下面的方法对我来说似乎是最合理的方法。我将 SyncronizationContext 从 UI 传递给任务,以便它可以在完成繁重的工作后更新 ui。
我做错了什么?
查看
<Window x:Class="QuickScope.Views.NavigatorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:QuickScope.Views"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:quickScope="clr-namespace:QuickScope"
mc:Ignorable="d">
<Grid>
<TextBox Grid.Row="0"
Name="TextBox"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Center"
Margin="5,0,5,5"
Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</TextBox>
<Popup PlacementTarget="{Binding ElementName=TextBox}">
<ListView x:Name="ItemList" ItemsSource="{Binding Items}" SelectedItem ="{Binding SelectedItem}">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Image Source="{Binding IconSource}" Width="20" Height="20"></Image>
<Label Content="{Binding SearchName}"></Label>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Popup>
</Grid>
ViewModel
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Threading;
using Caliburn.Micro;
using QuickScope.Views;
namespace QuickScope.ViewModels
{
public class NavigatorViewModel : Screen
{
private readonly ItemService _itemService;
public NavigatorViewModel()
{
_itemService = new ItemService();
Items = new ObservableCollection<ItemViewModel>();
}
public ObservableCollection<ItemViewModel> Items { get; }
public ItemViewModel SelectedItem { get; set; }
private string _searchText;
public string SearchText
{
get => _searchText;
set
{
_searchText = value;
NotifyOfPropertyChange(() => SearchText);
UpdateItemList(_searchText);
}
}
private void UpdateItemList(string searchText)
{
Items.Clear();
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
//the long running workload
var items = _itemService.GetByFilter(searchText);
//update the ui
Task.Factory.StartNew(() =>
{
foreach (var item in items)
Items.Add(item);
if (items.Any())
{
SelectedItem = items.First();
NotifyOfPropertyChange(() => SelectedItem);
}
}, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
});
}
}
}
编辑
我尝试了 Shadrix 的方法,但不幸的是它也没有用。我也尝试了 Peter Dunihos 的答案 marked duplicate 但我也没有成功(我得到了与上面描述的相同的 XamlParseException)。
我最后尝试的是 OnUIThread 方法中的 CM 的 build,它基本上包装了 Dispatcher.CurrentDispatcher。显然(考虑到我最近两次失败的尝试),它也失败了。当前的实现类似于以下内容(我删除了其他不相关的属性和方法):
public class NavigatorViewModel : Screen
{
public NavigatorViewModel()
{
Items = new ObservableCollection<ItemViewModel>();
}
public ObservableCollection<ItemViewModel> Items { get; set; }
public ItemViewModel SelectedItem { get; set; }
private string _searchText;
public string SearchText
{
get => _searchText;
set
{
_searchText = value;
NotifyOfPropertyChange(() => SearchText);
UpdateItemList(_searchText);
}
}
private void UpdateItemList(string searchText)
{
Items.Clear();
var updater = new ItemUpdater();
updater.ItemsUpdated += (s, e) => {
OnUIThread(() =>
{
var items = ((ItemsUpdatedEventArgs) e).Items;
foreach (var item in items)
{
Items.Add(item);
}
NotifyOfPropertyChange(() => Items);
});
};
var updateThread = new Thread(updater.GetItems);
updateThread.Start(searchText);
}
}
public class ItemUpdater
{
public event EventHandler ItemsUpdated;
private readonly ItemService _itemService;
public ItemUpdater()
{
_itemService = new ItemService();
}
public void GetItems(object searchText)
{
var items = _itemService.GetByFilter((string)searchText);
ItemsUpdated?.Invoke(this, new ItemsUpdatedEventArgs(items));
}
}
public class ItemsUpdatedEventArgs : EventArgs
{
public ItemsUpdatedEventArgs(IList<ItemViewModel> items)
{
Items = items;
}
public IList<ItemViewModel> Items { get; }
}
我无法解决这个问题,这让我抓狂,所以如果有人愿意帮助一个年轻的小辈,我将不胜感激。 :)
您可以找到完整的源代码here。
谢谢大家!
使用当前UI线程的Dispatcher
:
//update the ui
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
foreach (var item in items)
{
Items.Add(item);
}
if (items.Any())
{
SelectedItem = items.First();
NotifyOfPropertyChange(() => SelectedItem);
}
}));
当我尝试从一个单独的线程然后 ui 线程更新我在 XAML 中使用的 ObservableCollection 时,我得到一个 XamlParseException,它说 DependencySource 必须在同一个线程上创建线程作为 DependencyObject。我正在使用 Caliurn Micro 将 ViewModel 绑定到视图。
我尝试了几种方法来实现我的目标,下面的方法对我来说似乎是最合理的方法。我将 SyncronizationContext 从 UI 传递给任务,以便它可以在完成繁重的工作后更新 ui。
我做错了什么?
查看
<Window x:Class="QuickScope.Views.NavigatorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:QuickScope.Views"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:quickScope="clr-namespace:QuickScope"
mc:Ignorable="d">
<Grid>
<TextBox Grid.Row="0"
Name="TextBox"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Center"
Margin="5,0,5,5"
Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</TextBox>
<Popup PlacementTarget="{Binding ElementName=TextBox}">
<ListView x:Name="ItemList" ItemsSource="{Binding Items}" SelectedItem ="{Binding SelectedItem}">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Image Source="{Binding IconSource}" Width="20" Height="20"></Image>
<Label Content="{Binding SearchName}"></Label>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Popup>
</Grid>
ViewModel
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Threading;
using Caliburn.Micro;
using QuickScope.Views;
namespace QuickScope.ViewModels
{
public class NavigatorViewModel : Screen
{
private readonly ItemService _itemService;
public NavigatorViewModel()
{
_itemService = new ItemService();
Items = new ObservableCollection<ItemViewModel>();
}
public ObservableCollection<ItemViewModel> Items { get; }
public ItemViewModel SelectedItem { get; set; }
private string _searchText;
public string SearchText
{
get => _searchText;
set
{
_searchText = value;
NotifyOfPropertyChange(() => SearchText);
UpdateItemList(_searchText);
}
}
private void UpdateItemList(string searchText)
{
Items.Clear();
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
//the long running workload
var items = _itemService.GetByFilter(searchText);
//update the ui
Task.Factory.StartNew(() =>
{
foreach (var item in items)
Items.Add(item);
if (items.Any())
{
SelectedItem = items.First();
NotifyOfPropertyChange(() => SelectedItem);
}
}, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
});
}
}
}
编辑
我尝试了 Shadrix 的方法,但不幸的是它也没有用。我也尝试了 Peter Dunihos 的答案 marked duplicate 但我也没有成功(我得到了与上面描述的相同的 XamlParseException)。
我最后尝试的是 OnUIThread 方法中的 CM 的 build,它基本上包装了 Dispatcher.CurrentDispatcher。显然(考虑到我最近两次失败的尝试),它也失败了。当前的实现类似于以下内容(我删除了其他不相关的属性和方法):
public class NavigatorViewModel : Screen
{
public NavigatorViewModel()
{
Items = new ObservableCollection<ItemViewModel>();
}
public ObservableCollection<ItemViewModel> Items { get; set; }
public ItemViewModel SelectedItem { get; set; }
private string _searchText;
public string SearchText
{
get => _searchText;
set
{
_searchText = value;
NotifyOfPropertyChange(() => SearchText);
UpdateItemList(_searchText);
}
}
private void UpdateItemList(string searchText)
{
Items.Clear();
var updater = new ItemUpdater();
updater.ItemsUpdated += (s, e) => {
OnUIThread(() =>
{
var items = ((ItemsUpdatedEventArgs) e).Items;
foreach (var item in items)
{
Items.Add(item);
}
NotifyOfPropertyChange(() => Items);
});
};
var updateThread = new Thread(updater.GetItems);
updateThread.Start(searchText);
}
}
public class ItemUpdater
{
public event EventHandler ItemsUpdated;
private readonly ItemService _itemService;
public ItemUpdater()
{
_itemService = new ItemService();
}
public void GetItems(object searchText)
{
var items = _itemService.GetByFilter((string)searchText);
ItemsUpdated?.Invoke(this, new ItemsUpdatedEventArgs(items));
}
}
public class ItemsUpdatedEventArgs : EventArgs
{
public ItemsUpdatedEventArgs(IList<ItemViewModel> items)
{
Items = items;
}
public IList<ItemViewModel> Items { get; }
}
我无法解决这个问题,这让我抓狂,所以如果有人愿意帮助一个年轻的小辈,我将不胜感激。 :)
您可以找到完整的源代码here。
谢谢大家!
使用当前UI线程的Dispatcher
:
//update the ui
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
foreach (var item in items)
{
Items.Add(item);
}
if (items.Any())
{
SelectedItem = items.First();
NotifyOfPropertyChange(() => SelectedItem);
}
}));