2

私は現在、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 で使用できるようになりました。パーフェクトだ!どうもありがとう!

4

2 に答える 2

3

あなたのコードには非同期性がないようです(BeginInvokeを保存してください。これは、あなたが思っていることをしているとは思いません)。

現時点ではコードがすべてブロックされており、データベースへの接続が確立されてデータが読み込まれている間は UI が応答しないため、Rx を使用する必要があると思います :-(

あなたがしたいことは、バックグラウンド スレッドで「重労働」を実行し、値を取得したら、それらを Dispatcher の ObservableCollection に追加することです。これは、次の 3 つの方法のいずれかで実行できます。

  1. コレクション全体を一度に返します。次に、ObservableCollection に foreach ループを追加してリストをループします。これには、リストが大きすぎる場合に UI をブロックする (応答しないアプリ) という潜在的な欠点があります。
  2. ディスパッチャーへの独立した呼び出しで ObservableCollection に各項目を追加して、一度に 1 つの値のコレクションを返します。これにより、UI の応答性が維持されますが、完了するまでに時間がかかる場合があります。
  3. バッファリングされたチャンクでコレクションを返し、両方の世界を最大限に活用しようとします

必要なコードは次のようになります

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 など)。

于 2012-07-25T09:21:46.487 に答える
0

私はちょうどこれに遭遇しました。ディスパッチャにアクセスするには複数の方法があります。次の方法を使用する必要がありました。これを ViewModel から直接呼び出します (外部からは渡されません)。

Application.Current.Dispatcher.Invoke(action);
于 2012-07-24T23:03:30.923 に答える