web-dev-qa-db-ja.com

MVVMでのWPF UserControlの再利用

私は、WPFでMVVMパラダイムを使用して次のシナリオをコーディングするエレガントで慣用的な方法を見つけるのに苦労しており、他の人がそれにどのように取り組むのか疑問に思っていました。

UserControlアプリケーションにWPFがあり、さまざまな場所で再利用したい。このコントロールはフィルターされたComboBoxセットアップであり、ユーザーは選択を絞り込むことができます。私の例は、Department> Team> Personです。

Filtered ComboBoxes being used in three different windows

各シナリオで、少し異なる方法でコントロールを構成する場合があります。例えばWindow 1には、すべての部門、チーム、および人が含まれる場合があります。 Window 2は、すべての部門のサブセットのみを表示する場合があります。 Window 3はユーザーの部門とチームにロックされている可能性があります。

最初の(そしておそらく最悪の)解決策:UserControlに独自のViewModelを与える

これは、各ウィンドウにコントロールをドロップできる限り機能し、すぐに追加の作業は必要ないようです。フィルタリングロジックは、すべてのルックアップ値の読み込みと同様に、コントロールのViewModelです。問題が発生するのは、次に値を取得するとき、および主にDataContext継承チェーンを壊したためです。結局、コントロールのViewModelに構成設定のメッセージをサブスクライブさせ、値の選択を報告するメッセージを送信させることになり、MVVM/WPFを操作するのではなく戦っているように感じます。

2番目の解決策:ViewModelに対してUserControlを使用せず、ウィンドウのViewModelに依存するこれには、 WindowのUserControlを介してViewModelとやり取りするのは簡単ですが、ルックアップ値のロジックとフィルタリングロジックのロードの多くを複製しているように感じます。

分離コードとMVVMのエレガントなソリューションがあるように感じますが、それを見つけることができないようです!この要件をどのように解決しますか?

3
CptCoathanger

そもそも、 この答え は理論的なサポートが大幅に不足しています(つまり、理由の説明)。私は非常に2番目に この答え を代わりに使用します。投稿されたため、投票数はかなり少なくなっています 少し後で

あなたのアプローチはまた、少しの哲学によっても導かれるべきです。

MVVM(またはビジネスロジックをプレゼンテーションロジックから分離する方法

あなたが思いついたものを見てみましょう。 UserControlがあり、DependencyProperty- iesにDepartmentsPeopleTeamsなどの名前が付けられています。これはどうですかUserControl crowded with Domain-specific nouns?プログラミングはあなたが思っているよりもはるかに 名前について です。コントラスト:TextBox.TextおよびTextBox.Addressプロパティの名前として。 2番目のケースは、突然素因を導入します。このプロパティの値が非常に特定の目的に役立つことを期待しています。メンバーの名前と関係する概念的なコミュニケーションの重みを過小評価しないでください。

UserControlとは何ですか?

あなたのリンクされた答えに基づいて:

UserControlは、コンポジションを使用してコントロールを作成する簡単な方法です。 UserControlsは引き続きControlsであるため、UIの問題のみを考慮する必要があります。

しかし、そうですか?この説明に基づくと、 proper PersonPickerコントロールは次のようになります。

<PersonPicker
    FirstItemsSource="{Binding PersonPickerModel.Departments}"
    SecondItemsSource="{Binding PersonPickerModel.Teams}"
    ThirdItemsSource="{Binding PersonPickerModel.People}"
    FirstSelectedItem="{Binding PersonPickerModel.SelectedDepartment}"
    SecondSelectedItem="{Binding PersonPickerModel.SelectedTeam}"
    ThirdSelectedItem="{Binding PersonPickerModel.SelectedPerson}"
    IsFirstItemSelectionEnabled="{Binding PersonPickerModel.IsDepartmentSelectionEnabled}"
    IsSecondItemSelectionEnabled="{Binding PersonPickerModel.IsDepartmentSelectionEnabled}"
</PersonPicker>

ああ、ところで、コントロールもThreeComboBoxControlThreeHierarchiesControlのような名前になっているはずです。 PersonPickerという名前を付けたのは、それが /にしたいためです。完全に再利用可能ですが、コントロールはUserControlではありません... facility です。 非常に独自のニーズのための機能が必要な場合は、次のことを認めなければならないことがあります。本当に再利用可能なUserControl、または再利用可能なのどちらが必要ですか?ビジネスツール?それがビジネスツールであると判断した場合、完全な意味がありますそれのための特定のビューモデルがあります。

ビューモデルは解釈が過剰です

WPFのバインドでは、クラスの種類は考慮されず、プロパティ名のみが考慮されます。例では、タイプPersonPickerModelのオブジェクトがバインド済みDataContextに存在し、バインドされた名前のプロパティを保持している限り(もちろん、プロパティタイプは互換性があります)、すべてPersonPickerModelにそのような名前が付けられているかどうかに関係なく、または他の方法で正しく動作します。オブジェクトとして定義することもできます。

つまり、あなたが思いついたのは、UserControlとビジネスツールの中間的なものです。それが正しいことだけを目的としても、2つの間で何が必要かを再考することをお勧めします。ビジネスツールが必要な場合は、名前を保持して、オーダーメイドのビューモデルを作成します。これについて考えてみてください。あなたはWindowまたは何か他のものを持っていて、そして:

<PersonPicker DataContext="{Binding PersonPickerViewModel}"/>

バインディングはコントロールのxamlコードの内側に配置できます。これは、ユーザーを選択するという非常に具体的な理由でのみ使用するためです。コントロールを再利用するすべての場所で、なぜ verbose になるのですか? ViewModelを渡して、残りはそのままにします。

しかし、これがあなたが既に持っているものよりも優れているという本当に明確な理由があります。

あなたの解決策は、再利用性の偽の感覚を与えています。

真に抽象的な「適切な」UIコントロールの設計は hard であり、過小評価してはなりません。隣接する3つのコンボボックスを考えてみてください。彼らは何を表していますか?部門?チーム?いいえ、 collections です。なぜ 3 ?なぜ four 、またはさらに良いのは variable number にならないのですか。特定のニーズをカバーするふりをする適切な制御は、可変数のコレクション処理を提供する必要があります。これらのコレクションは何ですか?単純な文字列?より複雑なビューモデル?

これまでのところ、MultiComboBoxコントロールはほぼ完成しています。考えてみてください。可変数のComboBox、それらのItemsSourceは別のコレクションにバインドされています。必要に応じて、ViewModelを使用できます。あなたはComboBoxの使い方を知っており、その概念的表現を明確に理解しているので、これらの複数をうまく処理するのに苦労することはありません。その後、インデックスによってアクセス可能なItemsSourceおよびSelectedItemプロパティのコレクションを使用して、コードビハインドでそれらを公開できます。 UserControl abstract であるため、何十もの可能性(そしてもちろん responsibilities の負荷)があります。

UserControlを設計することは、DepartmentsTeamsまたはを持っていることを誰も気にしないことを意味します人。 「一般的ではないが部分的に再利用可能な」UserControlsを作成することは、悪い習慣ですが、良い回避策なので、 MVVMを使用しているときのメリットです。

編集:


確かではありません...しかし、あるComboBoxでの選択が別のComboBoxのアイテムに影響を与えることは、純粋なUIの問題であり、選択ロジックに応じて分離コードに属するものと考えることができるように思えます。

明日、あなたの要件は突然変わります! ComboBoxesは必要ありません。ListBoxesが必要です。もっと考えてみてください。最初のリストで部署を選択すると、2番目のリストにすぐにチームが表示され、追加のクリックを回避できます(UIスペースが増えることになります)。したがって、別のコントロールを作成することにしました。1つは3つのリストボックス(またはその問題についてはListViewsなど)を備えています。フィルタリングロジックを再度記述しますか?多分それをコピーして貼り付けますか?

「フィルタリングロジック」と呼んでいるものは、 UIの問題ではありません。最も基本的なUserControlsは、正当な理由でその基本的なものです。つまり、何をしたいかについての想定をできるだけ避けようとします。彼らは単にプログラマーであるあなたにユーザーの反応を伝えようとします。追加の「イニシアチブ」は、柔軟性を失うだけです。

Buttonは、「クリックを生成する」の抽象概念です。 ComboBoxは、コレクションから選択を行うための抽象概念です。 ListBoxも。 ListBoxは、抽象化の点ではComboBox以外のものを提供せず、表示の点で fanciness のみが異なります。それがあなたのキーワードです! Presentation (Windows Presentation Foundationと同様)。

これらの基本的なユーザーコントロールからUserControlを作成するときはいつでも、このエレガントな意味のない抽象化を実際に breaking 危険にさらしています。 UserControlsは、抽象化を「チェーン」するだけです。その意味で、作成しようとしているUserControlは、 3つの選択を一度に行うという抽象化を提供します。ここでも、フィルタリングロジックは UIの懸念事項ではありません。これについて議論する簡単な方法は、単に attached するのではなく、UIを変更する必要があり、フィルタリングロジックを書き直す必要があるということでした。それはあなたの他のキーワードです。 Attached 、ViewModelsがコントロールにアタッチするように。

コントロールの唯一の仕事は、ユーザーインタラクションをモデル化して抽象化することである必要があります。ユーザーインタラクションには、テレビのリモコンに2桁の値を生成するためのロジックが含まれているのと同じように、フィルタリングリストは含まれません(2つの数値を順番にクリックする場合など)。 。番号1を3〜4秒以内に続けて2回押すと、チャネル11に移動します。これは、3〜4秒以内に2番目の入力を待つため、番号1ではなく番号11が決定されると思いますか。 、はリモコンに「コード化」されているため、3番目の間隔で2度移動する1番の信号ではなく、11番の信号が3秒後にTVに送信され、TVは毎回何をするかを決定します。 ?

さて、あなたのUserControlはリモコンであり、テレビはあなたのモデルです。ボタン 1 の押下に関する2つの通知を受け取ります。1つは3秒に1つです。 UserControlはコレクションを要求し、コレクションのアイテムから潜在的な選択をプログラマーに伝えなければなりません。これは、ほとんどの場合、その責任が終了します。残りは...哲学です;)

3
Vector Zita

あなたが探している3番目の解決策はDependencyPropertyかもしれません

https://docs.Microsoft.com/en-us/dotnet/framework/wpf/advanced/dependency-properties-overview

これらを使用してあらゆる種類の賢さを実行できますが、それらがどれほどエレガントであるかは別の問題です。

私は正しい解決策だと思いますが、非常に一般的なコントロールを作成しているのでなければ、ビューモデルを用意することです。なぜ問題が発生するのかはわかりません。静的な共有ビューモデルではないため、ウィンドウごとにインスタンスを作成し、それに応じてイベントをバインドできます

public class Window1VM
{
    public UserControlVM  ucvm {get;set;}
}
4
Ewan

ロジックが非常に複雑でない限り、3つすべてを統合した共通のデザインを探して見つけるよりも、画面ごとに3回機能を再実装する方が簡単かもしれません。

将来の危険性は、このコントロールを必要とするが独自の特別な要件がある4番目の画面がある場合、共通コントロールのロジックをもう一度作り直す必要があるかもしれませんが、4つの画面が(再)になっていることです。 1つではなくテストを行うと、単純に各画面に特化するのではなく、4つの画面すべてと互換性のある方法でロジックを作り直す必要があります。

この動作パターンの名前はわかりませんが、コードはメンテナンスのために再利用可能または管理しやすいように見えますが、その繰り返しが排除されているか、含まれている概念要素が少ないため、実際に生成されるのは非常に複雑で専門性の高い要素であり、考案が困難であり、推論が困難であり、さらに調整することなく、その後の目的で再利用することはほとんどできません。

3
Steve

あなたの最初で最悪のアイデアは、実際にはMVVMに似た最も優れたアプローチです。

Caliburn microのような適切なMVVMフレームワークを使用すると、親モデルのサブビューモデルを指摘するだけで、そのビューモデルに対して正しいビューがレンダリングされます。

すべてのケースをサポートするには、viewmodelとviewが乱雑にならないようにする必要があります。この場合のように、各アグリゲートごとに3つのモジュールのように、より小さなモジュールを使用することをお勧めします。そして、これらを好きなように組み合わせて、より構図のような方法にします。

1
Anders

私が取ったアプローチは、SO質問: https://stackoverflow.com/a/28815689/2571982 へのこの回答によって大部分が通知されました。user1228は... 。情熱的...私は彼らの議論が説得力があると思いました。

SOの答えは、私が作成しているようなUserControlは、他のフレームワークコントロール(DatePickerComboBoxListBoxなど)、つまり、それとの相互作用は依存関係プロパティを通じて行う必要があります。UIロジックは、コントロールの分離コードファイルに含める必要があります。また、 UserControlの作成は、モデルのレンダリングに関するものでなければなりません。私の場合、単純なPOCOです。

<PersonPicker
    Departments="{Binding PersonPickerModel.Departments}"
    Teams="{Binding PersonPickerModel.Teams}"
    People="{Binding PersonPickerModel.People}"
    SelectedDepartment="{Binding PersonPickerModel.SelectedDepartment}"
    SelectedTeam="{Binding PersonPickerModel.SelectedTeam}"
    SelectedPerson="{Binding PersonPickerModel.SelectedPerson}"
    IsDepartmentSelectionEnabled="{Binding PersonPickerModel.IsDepartmentSelectionEnabled}"
    IsTeamSelectionEnabled="{Binding PersonPickerModel.IsDepartmentSelectionEnabled}">
</PersonPicker>
public class PersonPickerModel
{
   public IEnumerable<Department> Departments { get; }
   public IEnumerable<Team> Teams { get; }
   ...
   public bool IsTeamSelectionEnabled { get; }
}
public class Window1ViewModel
{
   public PersonPickerModel PersonPickerModel { get; set; }

   public WindowViewModel(IDepartmentsQuery departmentsQuery)
   {
      PersonPickerViewModel = new PersonPickerViewModel
      {
         Departments = departmentsQuery.Execute(),
         ...
         IsTeamSelectionEnabled = true;
      }
   }
}

各ViewModelにPersonPickerModelを作成し、それに応じて値を設定します。フィルタリングロジックは、コントロールのコードビハインドにあります。

このアプローチにより、任意のページにコントロールをドロップでき、追加の作業なしでフィルタリングロジックを取得できます。コントロールの特定のインスタンスを構成するためにバインドするモデルを作成するだけです。

1
CptCoathanger