2

UIにSilverlightを使用し、バックエンドにWCF Webサービスを使用して、ビジネスアプリケーションを開発しています。データベースには、いくつかのルックアップテーブルがあります。WCFサービスがビジネスオブジェクトを返す場合、プロパティの1つに、外部キーだけでなくルックアップテーブルの行全体が含まれるため、UIで、ルックアップテーブルの説明などを別の呼び出しを行わずに表示できます。サービス。現在私がやろうとしているのは、ルックアップ値のリスト全体にバインドされたコンボボックスを提供し、それを適切に更新することです。この例で扱っているビジネスオブジェクトはSessionと呼ばれ、ルックアップはSessionTypeと呼ばれます。

以下は、コンボボックスの定義です。DataContextはSessionのインスタンスに設定されます。コンボボックスには文字列のリスト以上のものが表示されているため、ItemTemplateを設定しています。

<ComboBox 
x:Name="SessionTypesComboBox"
ItemTemplate="{StaticResource SessionTypeDataTemplate}"
ItemsSource="{Binding Source={StaticResource AllSessionTypes}}"
SelectedItem="{Binding Path=SessionType, Mode=TwoWay}"
/>

ビジネスオブジェクトとルックアップテーブルの両方が、Webサービスを介して非同期にロードされています。他に何もしなければ、コンボボックスリストにはSessionTypesが入力されますが、Sessionからの初期SessionType値は表示されません。ただし、コンボボックスの選択が変更された場合、Sessionは正しいSessionTypeで更新されます。

起こっているように見えるのは、SelectedItemバインディングがSessionのSessionTypeをSessionTypeリストの同等のものと一致させることができないということです。オブジェクトの値は同じですが、参照は異なります。

私が見つけた回避策は、SessionとSessionTypesリストをロードしてから、Sessionの現在のSessionTypeをSesstionTypesリストの対応するもので更新することです。そうすると、コンボボックスが正しく表示されます。しかし、私にはこれは悪いコードの臭いがあります。すべてが非同期でロードされるため、すべてがいつ使用可能になるかを判断する必要があります。これが私がそれをしている方法です:

私のSilverlightユーザーコントロールのコードビハインド:

// incremented every time we get data back during initial form load.
private volatile int m_LoadSequence = 0;

...

// Loaded event, called when the form is er... loaded.
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
    // load session types
    var sessionTypes = this.Resources["AllSessionTypes"] as Lookups.AllSessionTypes;
    if (sessionTypes != null)
    {
        sessionTypes.DataLoadCompleted += (s, ea) =>
        {
            IncrementLoadSequence();
        };
        sessionTypes.LoadAsync();
    }

    // start loading another lookup table, same as above
    // omitted for clarity

    // set our DataContect to our business object (passed in when form was created)
    this.LayoutRoot.DataContext = this.m_Session;
    IncrementLoadSequence();
}

// This is the smelly part. This gets called by OnBlahCompleted events as web service calls return.
private void IncrementLoadSequence()
{
    // check to see if we're expecting any more service calls to complete.
    if (++m_LoadSequence < 3)
        return;

    // set lookup values on m_Session to the correct one in SessionType list.

    // Get SessionType list from page resources
    var sessionTypes = this.Resources["AllSessionTypes"] as Lookups.AllSessionTypes;

    // Find the matching SessionType based on ID
    this.m_Session.SessionType = sessionTypes.Where((st) => { return st.SessionTypeID == this.m_Session.SessionType.SessionTypeID; }).First();

    // (other lookup table omitted for clarity)
}

つまり、基本的に、Webサービスからデータを取得するたびにインクリメントされるカウンターがあります。3つのもの(コアビジネスオブジェクト+ 2つのルックアップテーブル)を期待しているので、そのカウンターが3になると、参照を一致させます。

私には、これは非常にハッキーに思えます。コンボボックスでValueMemberPathとSelectedValueを指定して、選択したアイテムをリスト内のアイテムと一致させたいと思います。

誰かがこれを行うためのよりクリーンな方法を見ることができますか?この状況はビジネスアプリでは非常に一般的であるため、それを行うための優れた方法があるはずです。

4

5 に答える 5

2

ジェフ、

私があなたの問題を理解していることを確認するために:データバインディングインフラストラクチャは、あなたが「等しい」と考える2つのオブジェクト実際に等しいことを認識していないようですSelectedItem.StaticResourceコレクションに合わせてSession.SessionType。これは、参照を「フラット化」することで回避できます (つまり、コード内で参照が等しいことを強制します。Session.SessionTypeWhere((st)...First()

同様の問題がありました。

2 つのオブジェクトが同じデータを表していることわかっているという理由だけで、Silverlight が異なる「ソース」から 2 つのオブジェクトを自動的に「同一視」しないこと、ある程度理にかなっています。あなたが言ったように、「オブジェクトの値は同じですが、参照は異なります」。しかし、どのようにしてデータバインディングをそれらと同等にすることができますか?

私たちが考えたこと/試したこと:

  • クラスに . Equals() を実装する(SessionTypeあなたの場合)

  • クラスに operator == を実装する(SessionTypeあなたの場合)

  • IEquatableクラスに実装する(SessionTypeあなたの場合)

  • コレクションのみを作成Stringし、文字列プロパティにバインドする

しかし、最終的にはあきらめて、あなたと同じアプローチを使用しました-「コレクション」から正しい参照と等しいオブジェクトを「抽出」し(すべてがロードされた後)、それをSelectedItem-bound プロパティに突っ込みます。

コードのにおいについてはあなたに同意します。より良い解決策があるに違いないと思います。これまでのところ、プロパティ アクセサーと no-op IValueConverters でのすべてのデバッグで解決策が見つかりませんでした。解決策が見つかった場合は、ここにも投稿します。

于 2009-01-26T04:25:40.537 に答える
1

問題を完全に理解しているのかわかりません(早いです:))しかし、必要なすべてのアイテムを1回の呼び出しで転送することはできませんか?(3を新しいDTOクラスでラップする必要がある場合でも)、完全なイベントを使用して現在のセッションタイプを更新できます。まだ完璧ではありませんが、少なくともカウンターを保持する必要はありません。

また、そのすべてのロジックをViewModelに移動して、それにバインドするだけですが、それは私だけです:)

于 2009-01-24T06:51:17.633 に答える
1

ObservableCollection にバインドしてから、他のコード (MVVM の View Model 部分は悪い選択ではありません) を使用してバックグラウンドで更新することをお勧めします。そうすれば、UI から分離され、UI がバインドされているだけなので、更新の処理がはるかに簡単になります。

于 2009-01-24T07:38:26.117 に答える
0

回答ありがとうございます。上記のすべてが役に立ちました。私はMVVMの方法に移行しており、複数のサービス呼び出しを1つにまとめています(往復のオーバーヘッドも削減しています)。当分の間、ルックアップの再参照に固執するようです-より良い方法を見つけたら、それも投稿します.

于 2009-01-26T20:36:55.333 に答える
0

geofftnz、これに対する良い解決策を見つけましたか?

CraigD、 Equals などをオーバーライドすることが良い解決策であるとは思えません。まず、これは生成されたプロキシ クラス SessionType 内で行われるため、これらの変更はサービス参照が更新されるたびに失われます。次に、SessionType セッター (ここでは、SessionType は、生成されたクライアント プロキシ クラスと同じです) での通知は、ReferenceEquals 呼び出しを使用するため、生成されたコードに触れる場所がもう 1 つあります。OK、最初のことは手作りの部分クラス SessionType を介して実行できます (したがって、更新後に失われることはありません) が、2 番目のことは確かに同じ方法では実行できません。

于 2010-03-18T16:01:27.647 に答える