3

この [フレームワーク 4.0 with async/await] のように、バックグラウンド スレッドで wpf ウィンドウのデータをフェッチします。

async void refresh()
{
    // returns object of type Instances
    DataContext = await Task.Factory.StartNew(() => serviceagent.GetInstances());
    var instances = DataContext as Instances;
    await Task.Factory.StartNew(() => serviceagent.GetGroups(instances));
    // * problem here * instances.Groups is filled but UI not updated
}

GetInstances に GetGroups のアクションを含めると、UI にグループが表示されます。
別のアクションで更新すると、DataContext には正しいグループが含まれますが、UI には表示されません。

私がグループに含めたGetGroups()方法NotifyCollectionChangedAction.ResetではObservableCollection、これは役に立ちません。
さらに奇妙なのはNotifyCollectionChangedAction.Reset、リストに 10 個のアイテムがあるのに、リストを 1 回だけ呼び出すのに 3 回実行されるということです!

次のように書くことで問題を解決できます。

DataContext = await Task.Factory.StartNew(() => serviceagent.GetGroups(instances));

しかし、これはバックグラウンド プロセスを介して DataContxt と UI を更新する通常の方法ですか?
実際には、既存の DataContext を再度設定せずに更新したいだけですか?

編集:serviceagent.GetGroups(instances)より詳細に:

public void GetGroups(Instances instances)
{
    // web call
    instances.Admin = service.GetAdmin();

    // set groups for binding in UI
    instances.Groups = new ViewModelCollection<Groep>(instances.Admin.Groups);

    // this code has no effect
    instances.Groups.RaiseCollectionChanged();
}

ここViewModelCollection<T>から継承しObservableCollection<T>、メソッドを追加しました:

public void RaiseCollectionChanged()
{
    var handler = CollectionChanged;
    if (handler != null)
    {
        Trace.WriteLine("collection changed");
        var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
        handler(this, e);
    }
}
4

3 に答える 3

4

DataContext とは何かについて少し混乱しているようです。DataContext は、更新する必要がある特別なオブジェクトではありません。これは、UI にバインドする 1 つまたは複数のオブジェクトへの参照です。これらのオブジェクトに変更を加えるたびに、UI に通知されます (適切なインターフェイスを実装している場合)。

したがって、DataContext を明示的に変更しない限り、UI は、別のオブジェクト セットを表示する必要があることを推測できません。

実際、あなたのコードでは、DataContext を 2 回設定する理由はありません。表示したいオブジェクトの最終セットを設定するだけです。実際、同じデータで作業するため、次の 2 つのタスクを使用する理由はありません。

async Task refresh()
{
    // returns object of type Instances
    DataContext=await Task.Factory.StartNew(() => {
             var instances = serviceagent.GetInstances();
             return serviceagent.GetGroups(instances);
    });
}

ノート:

署名を使用しないでくださいasync void。これは、成功するか失敗するかを気にしない、ファイア アンド フォーゲット イベント ハンドラーにのみ使用されます。その理由は、async voidメソッドを待機できないため、メソッドが成功したかどうかを誰も知ることができないためです。

于 2013-11-11T10:02:49.230 に答える
4

asyncコードの一部で際立っている点がいくつかあります。

これらに基づいて、ブログ投稿の紹介もお勧めします。async

実際の問題に...

バックグラウンド スレッドからデータ バインドされたコードを更新するのは常に注意が必要です。ViewModel データを UI の一部であるかのように扱うことをお勧めします (いわば「論理 UI」です)。そのため、バックグラウンド スレッドでデータを取得しても問題ありませんが、実際の VM 値の更新は UI スレッドで行う必要があります。

これらの変更により、コードは次のようになります。

async Task RefreshAsync()
{
  var instances = await TaskEx.Run(() => serviceagent.GetInstances());
  DataContext = instances;
  var groupResults = await TaskEx.Run(() => serviceagent.GetGroups(instances));
  instances.Admin = groupResults.Admin;
  instances.Groups = new ObservableCollection<Group>(groupResults.Groups);
}

public GroupsResult GetGroups(Instances instances)
{
  return new GroupsResult
  {
    Admin = service.GetAdmin(),
    Groups = Admin.Groups.ToArray(),
  };
}

次に確認する必要があるのは、Instances実装しているかどうかINotifyPropertyChangedです。Reset設定時にコレクション変更イベントを発生させる必要はありませんGroups。はGroupsのプロパティであるため、を発生させるInstancesのは の責任です。InstancesINotifyPropertyChanged.PropertyChanged

DataContextまたは、最後に次のように設定することもできます。

async Task RefreshAsync()
{
  var instances = await TaskEx.Run(() => serviceagent.GetInstances());
  var groupResults = await TaskEx.Run(() => serviceagent.GetGroups(instances));
  instances.Admin = groupResults.Admin;
  instances.Groups = new ObservableCollection<Group>(groupResults.Admin.Groups);
  DataContext = instances;
}
于 2013-11-11T12:56:30.563 に答える
1

がバインドされているプロパティにRaiseCollectionChanged影響を与えないことを発見しました。私は単に通知する必要があります: .GroupsDataContextinstances.RaisePropertyChanged("Groups");

于 2013-11-11T13:20:13.630 に答える