私は現在、Windows Phone アプリケーションに取り組んでおり、Reactive Extensions を使用して非同期を作成し、UI エクスペリエンスを向上させたいと考えています。
私は MVVM パターンを使用します。私のビューには、ViewModel でアイテムの ObservableCollection にバインドされた ListBox があります。Item には、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);
}
}
}
[...]
}
そしてそれは正常に動作し、アイテムはビューで更新されます。
今、私は非同期性を作りたいと思っています。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));
}
[...]
}
しかし、Reactive Extentions を使用したいと思います。たとえば、ToAsync や ToObservable などの Rx のさまざまな要素を試してみましたが、チェックリストに項目を追加すると、「無効なクロススレッド アクセス」で「UnauthorizedAccessException が処理されませんでした」というメッセージが表示されました。
他のスレッドを ObserveOn しようとしましたが、エラーは 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 を使用して、Subscribe で使用できるようになりました。パーフェクトだ!どうもありがとう!