我目前正在开发Windows Phone应用程序,我想使用反应式扩展来创建异步,以获得更好的UI体验。
我使用MVVM模式:我的视图在ViewModel中有一个ListBox绑定到ObservableCollection of Items。项具有传统属性,如Name或IsSelected。
<ListBox SelectionMode="Multiple" ItemsSource="{Binding Checklist, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected, Mode=TwoWay}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
我在App.xaml.cs:中实例化我的MainViewModel
public partial class App : Application
{
private static MainViewModel _viewModel = null;
public static MainViewModel ViewModel
{
get
{
if (_viewModel == null)
_viewModel = new MainViewModel();
return _viewModel;
}
}
[...]
}
我在MainPage_Loaded中加载我的数据。
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
this.DataContext = App.ViewModel;
this.Loaded += new System.Windows.RoutedEventHandler(MainPage_Loaded);
}
private void MainPage_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
App.ViewModel.LoadData();
}
}
我从ViewModel加载数据(稍后我将把代码移到模型中):
public class MainViewModel : ViewModelBase
{
public ObservableCollection<Item> _checklist;
public ObservableCollection<Item> Checklist
{
get
{
return this._checklist;
}
set
{
if (this._checklist != value)
{
this._checklist = value;
}
}
}
private const string _connectionString = @"isostore:/ItemDB.sdf";
public void LoadData()
{
using (ItemDataContext context = new ItemDataContext(_connectionString))
{
if (!context.DatabaseExists())
{
// Create database if it doesn't exist
context.CreateDatabase();
}
if (context.Items.Count() == 0)
{
[Read my data in a XML file for example]
// Save changes to the database
context.SubmitChanges();
}
var contextItems = from i in context.Items
select i;
foreach (Item it in contextItems)
{
this.Checklist.Add(it);
}
}
}
[...]
}
它运行良好,项目在视图中更新。
现在我想创建异步。在我从View而不是LoadData调用的新方法中使用传统的BeginInvoke,它可以很好地工作。
public partial class MainPage : PhoneApplicationPage
{
[...]
private void MainPage_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
App.ViewModel.GetData();
}
}
我使用了一个名为CurrentDispatcher的属性,它在App.xaml.cs中填充了App.Current.RootVisual.Dispatcher.
public class MainViewModel : ViewModelBase
{
[...]
public Dispatcher CurrentDispatcher { get; set; }
public void GetData()
{
this.CurrentDispatcher.BeginInvoke(new Action(LoadData));
}
[...]
}
但我想使用反应扩展。因此,我尝试了Rx的不同元素,例如ToAsync或ToObservable,但当我向清单中添加项目时,我遇到了一些"UnauthorizedAccessException未处理"one_answers"无效跨线程访问"。
我试着观察其他线程,因为错误可能来自UI和后台线程之间的混合,但它不起作用。也许我没有像在那种特殊情况下那样使用Rx?
任何帮助都将不胜感激。
回答后编辑:
这是一个非常有效的代码!
public void GetData()
{
Observable.Start(() => LoadData())
.ObserveOnDispatcher()
.Subscribe(list =>
{
foreach (Item it in list)
{
this.Checklist.Add(it);
}
});
}
public ObservableCollection<Item> LoadData()
{
var results = new ObservableCollection<Item>();
using (ItemDataContext context = new ItemDataContext(_connectionString))
{
//Loading
var contextItems = from i in context.Items
select i;
foreach (Item it in contextItems)
{
results.Add(it);
}
}
return results;
}
正如你所看到的,我以前没有正确使用。现在我可以使用ObservableCollection并在Sussubscribe中使用它。太完美了!非常感谢!
您的代码中似乎没有异步性(保存BeginInvoke,我不认为它在做您认为它正在做的事情)。
我假设你想使用Rx,因为你的代码目前都处于阻塞状态,并且在连接到数据库并加载数据时UI没有响应:-(
您要做的是在后台线程上执行"繁重的工作",然后在获得值后,将它们添加到Dispatcher上的ObservableCollection中。你可以用三种方法之一:
- 一次性归还全部藏品。然后循环列表,foreach循环添加到ObservableCollection中。如果列表太大,这可能会阻止UI(无响应的应用程序)
- 每次返回集合一个值,在对调度器的独立调用中将每个项添加到ObservableCollection。这将使UI保持响应,但可能需要更长的时间才能完成
- 以缓冲块的形式返回集合,并尝试两全其美
您想要的代码可能看起来像这个
public IList<Item> FetchData()
{
using (ItemDataContext context = new ItemDataContext(_connectionString))
{
//....
var results = new List<Item>();
foreach (Item it in contextItems)
{
results.Add(it);
}
return results;
}
}
public void LoadData()
{
Observable.Start(()=>FetchData())
.ObserveOnDispatcher()
.Subscribe(list=>
{
foreach (Item it in contextItems)
{
this.Checklist.Add(it);
}
});
}
该代码不适合单元测试,但您似乎对它不感兴趣(静态成员、具有DB连接的VM等),所以这可能只是工作?!
我自己刚刚遇到这个问题。有多种方法可以到达调度员,我不得不使用以下方法。我直接从ViewModels调用它(不从外部传入)。
Application.Current.Dispatcher.Invoke(action);