複数のユーザー コントロールで構成されたウィンドウがあり、各ユーザー コントロールに独自のビュー モデルがあるのか、それともウィンドウ全体に 1 つのビュー モデルしかないのか疑問に思っていました。
6 に答える
絶対、積極的に
いいえ
UserControlsには、特別に設計された ViewModels を含めないでください。実際、これはコードの匂いです。アプリケーションがすぐに壊れるわけではありませんが、作業中に痛みが生じます。
UserControl は、コンポジションを使用してコントロールを作成する簡単な方法です。UserControls は引き続き Controls であるため、UI の問題のみに関係する必要があります。
UserControl の ViewModel を作成すると、そこにビジネス ロジックまたは UI ロジックが配置されます。ViewModel を使用して UI ロジックを含めるのは正しくないため、それが目標である場合は、VM を捨てて、そのコントロールの分離コードにコードを配置します。ビジネス ロジックを UserControl に配置する場合、ほとんどの場合、コントロールの作成を簡素化するためではなく、アプリケーションの一部を分離するために使用しています。コントロールは単純で、設計された目的が 1 つである必要があります。
UserControl の ViewModel を作成すると、DataContext を介したデータの自然な流れも中断されます。ここが最も痛みを感じる場所です。実証するために、次の簡単な例を考えてみましょう。
People を含む ViewModel があり、それぞれが Person 型のインスタンスです。
public class ViewModel
{
public IEnumerable<Person> People { get; private set; }
public ViewModel()
{
People = PeopleService.StaticDependenciesSuckToo.GetPeople();
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
ウィンドウに人のリストを表示するのは簡単です。
<Window x:Class="YoureDoingItWrong.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:YoureDoingItWrong"
Title="Derp">
<Window.DataContext>
<l:ViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type l:Person}">
<l:PersonView />
</DataTemplate>
</Window.Resources>
<ListView ItemsSource="{Binding People}" />
</Window>
リストは、Person の正しい項目テンプレートを自動的に選択し、PersonView を使用してユーザーの情報をユーザーに表示します。
PersonView とは何ですか? 個人の情報を表示するように設計された UserControl です。TextBlock がテキストの表示コントロールであるのと同様に、これは人間の表示コントロールです。Person に対してバインドするように設計されているため、スムーズに動作します。上のウィンドウで、ListView が各 Person インスタンスを PersonView に転送し、ビジュアルのこのサブツリーの DataContext になる方法に注意してください。
<UserControl x:Class="YoureDoingItWrong.PersonView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<Label>Name</Label>
<TextBlock Text="{Binding Name}" />
<Label>Age</Label>
<TextBlock Text="{Binding Age}" />
</StackPanel>
</UserControl>
これがスムーズに機能するためには、UserControl の ViewModel が、設計対象の Type のインスタンスである必要があります。次のような愚かなことをしてこれを破るとき
public PersonView()
{
InitializeComponent();
this.DataContext = this; // omfg
}
また
public PersonView()
{
InitializeComponent();
this.DataContext = new PersonViewViewModel();
}
モデルの単純さを壊してしまいました。通常、これらのインスタンスでは、最終的に忌まわしい回避策を講じることになります。最も一般的な回避策は、DataContext が実際にどうあるべきかについて、疑似 DataContext プロパティを作成することです。そして今、あなたは一方を他方にバインドすることができないので、次のようなひどいハックになってしまいます
public partial class PersonView : UserControl
{
public PersonView()
{
InitializeComponent();
var vm = PersonViewViewModel();
// JUST KILL ME NOW, GET IT OVER WITH
vm.PropertyChanged = (o, e) =>
{
if(e.Name == "Age" && MyRealDataContext != null)
MyRealDataContext.Age = vm.PersonAge;
};
this.DataContext = vm;
}
public static readonly DependencyProperty MyRealDataContextProperty =
DependencyProperty.Register(
"MyRealDataContext",
typeof(Person),
typeof(PersonView),
new UIPropertyMetadata());
public Person MyRealDataContext
{
get { return (Person)GetValue(MyRealDataContextProperty); }
set { SetValue(MyRealDataContextProperty, value); }
}
}
UserControl は、より複雑なコントロールにすぎないと考える必要があります。TextBox には独自の ViewModel がありますか? いいえ。VM のプロパティをコントロールの Text プロパティにバインドすると、コントロールの UI にテキストが表示されます。
MVVM は「No Codebehind」の略ではありません。ユーザー コントロールの UI ロジックをコード ビハインドに配置します。ユーザー コントロール内にビジネス ロジックが必要なほど複雑な場合は、範囲が広すぎることを示唆しています。簡素化する!
MVVM の UserControl は次のように考えてください。モデルごとに UserControl があり、そのモデルのデータをユーザーに表示するように設計されています。ユーザーにそのモデルを表示したい場所ならどこでも使用できます。ボタンは必要ですか?UserControl で ICommand プロパティを公開し、ビジネス ロジックをそれにバインドさせます。ビジネス ロジックは、内部で行われていることを知る必要がありますか? ルーティング イベントを追加します。
通常、WPF では、何かを実行するのがなぜ苦痛なのかを尋ねる場合、それは実行すべきではないからです。
これはイエスかノーかの質問ではありません。追加のビュー モデルを使用することで、保守性やテスト容易性が向上するかどうかによって異なります。何も得られなければ、ビュー モデルを追加しても意味がありません。オーバーヘッドが特定のユース ケースに見合うかどうかを判断する必要があります。
各ユーザー コントロールには独自の ViewModel が必要であると言えます。これにより、ViewModel/UserControl ペアを将来新しいコンスタレーションで再利用できるようになるからです。
私が理解しているように、あなたのウィンドウはユーザーコントロールの複合体であるため、ユーザーコントロールごとに個別のViewModelをすべて構成するViewModelをいつでも作成できます。これにより、両方の長所が得られます。
[should] 各ユーザー コントロールには独自の ViewModel が必要ですか、それともウィンドウ全体に ViewModel が 1 つだけ必要ですか?
残念ながら、この質問に対する最も投票数の多い回答は誤解を招くものであり、他の質問で交換したコメントに基づいており、WPF を学ぼうとしている人々に貧弱なガイダンスを提供しています。その答えは次のように答えます。
UserControlsには、特別に設計された ViewModels を含めないでください。
問題は、それが尋ねられた質問ではないということです。
を記述する場合UserControl
、コントロールのパブリック API には、そのコントロール用に特別に設計されたビュー モデル タイプの作成も含まれるべきではないという一般的な意見に同意します。クライアント コードは、必要なビュー モデルを使用できる必要があります。
しかし、それは「各ユーザー コントロールが独自の ViewMomdel を持っている可能性がある」という考えを排除するものではありません。それに対する答えが「はい、各ユーザーコントロールのビューモデル」となる場合、私が考えることができる少なくとも2つの明白なシナリオがあります。
ユーザー コントロールは、アイテム プレゼンターのデータ テンプレートの一部です (例:
ItemsControl
)。この場合、ビュー モデルは個々のデータ要素に対応し、ビュー モデル オブジェクトとそのビュー モデル オブジェクトを表示するユーザー コントロールの間には 1 対 1 の対応があります。
このシナリオでは、ビュー モデル オブジェクトは「それらのために特別に設計された」ものではありません (したがって、疑わしい回答と矛盾することはありません) 。はい、各ユーザー コントロールには独自のビュー モデルがあります」)。ユーザー コントロールの実装は、ユーザー コントロール用に特別に設計されたビュー モデル データ構造の恩恵を受けるか、または必要とさえします。このビュー モデルのデータ構造は、クライアント コードには公開されません。これは実装の詳細であり、ユーザー コントロールを使用してクライアント コードから隠されます。しかし、それは確かに、そのユーザー コントロール用に「特別に設計された」ビュー モデルのデータ構造です。
このシナリオは明らかにまったく問題ではありません。これは、 「UserControlsには、特別に設計された ViewModel を持たせるべきではない」という主張と直接矛盾しています。
さて、これらのシナリオのいずれかを除外することが、その回答の作成者の意図ではなかったと思います。しかし問題は、WPF を学ぼうとしている人は、違いを認識するのに十分なコンテキストを持っていない可能性があることです。そのため、この強調された、非常に支持された、誤解を招く回答に基づいて、ユーザー コントロールとビュー モデルに関して誤って一般化する可能性があります。
この別の視点を明確化のポイントとして提示し、元の質問に狭い視野で答えることによって、WPF についてさらに学びながらこの質問を見つけた人が、より良いコンテキストとより良いアイデアとビュー モデルがユーザー コントロールに固有に実装される可能性がある場合とそうでない場合。
あなたのアプリケーションはある種のビュー構成を行っていると思います。そのため、ユーザー コントロールを独自のビュー モデルにすると、ウィンドウのグローバル ビュー モデルを変更せずに他のホスト ウィンドウに自由に埋め込むことができます。
追加のボーナスとして、アプリケーションの要件が発生した場合、アプリケーションは、Prism または Caliburn フレームワークによって提供されるような、よりアーキテクチャ的に健全な構成モデルに進化するのにより適しています。