私は、WPFでMVVMパラダイムを使用して次のシナリオをコーディングするエレガントで慣用的な方法を見つけるのに苦労しており、他の人がそれにどのように取り組むのか疑問に思っていました。
UserControl
アプリケーションにWPF
があり、さまざまな場所で再利用したい。このコントロールはフィルターされたComboBox
セットアップであり、ユーザーは選択を絞り込むことができます。私の例は、Department> Team> Personです。
各シナリオで、少し異なる方法でコントロールを構成する場合があります。例えばWindow 1
には、すべての部門、チーム、および人が含まれる場合があります。 Window 2
は、すべての部門のサブセットのみを表示する場合があります。 Window 3
はユーザーの部門とチームにロックされている可能性があります。
最初の(そしておそらく最悪の)解決策:UserControl
に独自のViewModelを与える
これは、各ウィンドウにコントロールをドロップできる限り機能し、すぐに追加の作業は必要ないようです。フィルタリングロジックは、すべてのルックアップ値の読み込みと同様に、コントロールのViewModel
です。問題が発生するのは、次に値を取得するとき、および主にDataContext継承チェーンを壊したためです。結局、コントロールのViewModel
に構成設定のメッセージをサブスクライブさせ、値の選択を報告するメッセージを送信させることになり、MVVM/WPFを操作するのではなく戦っているように感じます。
2番目の解決策:ViewModel
に対してUserControl
を使用せず、ウィンドウのViewModel
に依存するこれには、 WindowのUserControl
を介してViewModel
とやり取りするのは簡単ですが、ルックアップ値のロジックとフィルタリングロジックのロードの多くを複製しているように感じます。
分離コードとMVVMのエレガントなソリューションがあるように感じますが、それを見つけることができないようです!この要件をどのように解決しますか?
そもそも、 この答え は理論的なサポートが大幅に不足しています(つまり、理由の説明)。私は非常に2番目に この答え を代わりに使用します。投稿されたため、投票数はかなり少なくなっています 少し後で 。
あなたのアプローチはまた、少しの哲学によっても導かれるべきです。
あなたが思いついたものを見てみましょう。 UserControl
があり、DependencyProperty
- iesにDepartments
、People
、Teams
などの名前が付けられています。これはどうですかUserControl
crowded with Domain-specific nouns?プログラミングはあなたが思っているよりもはるかに 名前について です。コントラスト:TextBox.Text
およびTextBox.Address
プロパティの名前として。 2番目のケースは、突然素因を導入します。このプロパティの値が非常に特定の目的に役立つことを期待しています。メンバーの名前と関係する概念的なコミュニケーションの重みを過小評価しないでください。
あなたのリンクされた答えに基づいて:
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>
ああ、ところで、コントロールもThreeComboBoxControl
やThreeHierarchiesControl
のような名前になっているはずです。 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
を設計することは、Departments、Teamsまたはを持っていることを誰も気にしないことを意味します人。 「一般的ではないが部分的に再利用可能な」UserControl
sを作成することは、悪い習慣ですが、良い回避策なので、 MVVMを使用しているときのメリットです。
編集:
確かではありません...しかし、あるComboBoxでの選択が別のComboBoxのアイテムに影響を与えることは、純粋なUIの問題であり、選択ロジックに応じて分離コードに属するものと考えることができるように思えます。
明日、あなたの要件は突然変わります! ComboBox
esは必要ありません。ListBox
esが必要です。もっと考えてみてください。最初のリストで部署を選択すると、2番目のリストにすぐにチームが表示され、追加のクリックを回避できます(UIスペースが増えることになります)。したがって、別のコントロールを作成することにしました。1つは3つのリストボックス(またはその問題についてはListView
sなど)を備えています。フィルタリングロジックを再度記述しますか?多分それをコピーして貼り付けますか?
「フィルタリングロジック」と呼んでいるものは、 UIの問題ではありません。最も基本的なUserControl
sは、正当な理由でその基本的なものです。つまり、何をしたいかについての想定をできるだけ避けようとします。彼らは単にプログラマーであるあなたにユーザーの反応を伝えようとします。追加の「イニシアチブ」は、柔軟性を失うだけです。
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番目の解決策はDependencyProperty
かもしれません
https://docs.Microsoft.com/en-us/dotnet/framework/wpf/advanced/dependency-properties-overview
これらを使用してあらゆる種類の賢さを実行できますが、それらがどれほどエレガントであるかは別の問題です。
私は正しい解決策だと思いますが、非常に一般的なコントロールを作成しているのでなければ、ビューモデルを用意することです。なぜ問題が発生するのかはわかりません。静的な共有ビューモデルではないため、ウィンドウごとにインスタンスを作成し、それに応じてイベントをバインドできます
public class Window1VM
{
public UserControlVM ucvm {get;set;}
}
ロジックが非常に複雑でない限り、3つすべてを統合した共通のデザインを探して見つけるよりも、画面ごとに3回機能を再実装する方が簡単かもしれません。
将来の危険性は、このコントロールを必要とするが独自の特別な要件がある4番目の画面がある場合、共通コントロールのロジックをもう一度作り直す必要があるかもしれませんが、4つの画面が(再)になっていることです。 1つではなくテストを行うと、単純に各画面に特化するのではなく、4つの画面すべてと互換性のある方法でロジックを作り直す必要があります。
この動作パターンの名前はわかりませんが、コードはメンテナンスのために再利用可能または管理しやすいように見えますが、その繰り返しが排除されているか、含まれている概念要素が少ないため、実際に生成されるのは非常に複雑で専門性の高い要素であり、考案が困難であり、推論が困難であり、さらに調整することなく、その後の目的で再利用することはほとんどできません。
あなたの最初で最悪のアイデアは、実際にはMVVMに似た最も優れたアプローチです。
Caliburn microのような適切なMVVMフレームワークを使用すると、親モデルのサブビューモデルを指摘するだけで、そのビューモデルに対して正しいビューがレンダリングされます。
すべてのケースをサポートするには、viewmodelとviewが乱雑にならないようにする必要があります。この場合のように、各アグリゲートごとに3つのモジュールのように、より小さなモジュールを使用することをお勧めします。そして、これらを好きなように組み合わせて、より構図のような方法にします。
私が取ったアプローチは、SO質問: https://stackoverflow.com/a/28815689/2571982 へのこの回答によって大部分が通知されました。user1228は... 。情熱的...私は彼らの議論が説得力があると思いました。
SOの答えは、私が作成しているようなUserControl
は、他のフレームワークコントロール(DatePicker
、 ComboBox
、ListBox
など)、つまり、それとの相互作用は依存関係プロパティを通じて行う必要があります。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
を作成し、それに応じて値を設定します。フィルタリングロジックは、コントロールのコードビハインドにあります。
このアプローチにより、任意のページにコントロールをドロップでき、追加の作業なしでフィルタリングロジックを取得できます。コントロールの特定のインスタンスを構成するためにバインドするモデルを作成するだけです。