如何最好地应用WPF MVVM? [英] How best to apply WPF MVVM?
问题描述
我想建立一个MVVM风格的应用程序,我想我得到自己变成了几个疙瘩这些项目的互动,希望有人可以提供帮助。我做得真的错了吗?
我想我的主要2个问题是
- 我应该如何从我的模型去我的看法。我目前正试图通过转换器来做到这一点。
- 如果使用一个转换器是正确的,我怎么得到这个工作正常?我相信,在节点构造设置的datacontext由包括转换器中的XAML取代
我的班(简化了一下):
IFieldDescription
//接口这应该是模型
公共接口IFieldDescription
{
字符串名称{; }
布尔残疾人{搞定; }
}
节点模型
//这应该是视图模型
公共类节点模型类:NotifyPropertyChanged
{
内部节点模型(){ }
公共节点模型(IFieldDescription fieldDescription)
{
this.FieldDescription = fieldDescription;
}
保护IFieldDescription FieldDescription
{
{返回this.fieldDescription; }
集合{
this.fieldDescription =价值;
this.OnPropertyChanged(姓名);
this.OnPropertyChanged(已禁用);
this.OnPropertyChanged(PrimaryKey的); }
}
私人IFieldDescription fieldDescription;
公共字符串名称{{返回this.FieldDescription.Name; }}
公布尔残疾人{{返回this.FieldDescription.Disabled; }}
}
节点
码背后
公共节点(节点模型模型)
{
this.DataContext =模型;
this.InitializeComponent();
}
XAML
<用户控件的xmlns =http://schemas.microsoft.com/winfx/2006/xaml/presentation的xmlns:X =http://schemas.microsoft.com/winfx/ 2006 / XAML的xmlns:D =http://schemas.microsoft.com/expression/blend/2008的xmlns:MC =http://schemas.openxmlformats.org/markup-compatibility/2006
XMLNS:地方=CLR的命名空间:GOX:类=GO.Node背景=白
>
<电网X:NAME =LayoutRoot>
< Grid.ColumnDefinitions>
< ColumnDefinition WIDTH =自动/> <! - 主键图标 - >
< ColumnDefinition WIDTH =自动/> <! - 类型图标 - >
< ColumnDefinition /> <! - 节点文本 - >
< ColumnDefinition WIDTH =自动/> <! - 选项齿轮 - >
< ColumnDefinition WIDTH =自动/> <! - 比赛图标 - >
< ColumnDefinition WIDTH =自动/>
< /Grid.ColumnDefinitions>
< Grid.Resources>
<局部:AttributeDataTypeConverter X:键=DateTypeConverter/>
< /Grid.Resources>
<图像Grid.Column =0来源=C:\Users\ian.wright\Documents\Expression\Blend 4\Projects\GO\GO\Resources\\ \\Images\PrimaryKey.png拉伸=无能见度={绑定路径= IsPrimaryKey}/>
<图像Grid.Column =1来源={绑定路径=类型转换器= {StaticResource的DateTypeConverter}}拉伸=无/>
< TextBlock的Grid.Column =2文本={绑定路径=名称}VerticalAlignment =底保证金=0,0,0,2/>
<图像Grid.Column =3来源=C:\Users\ian.wright\Documents\Expression\Blend 4\Projects\GO\GO\Resources\\ \\Images\Cog.png拉伸=无能见度={绑定路径= HasOptions}/>
<图像Grid.Column =4来源={绑定路径= CastType}拉伸=无/>
< /网格和GT;
< /用户控件>
主窗口
<窗口
的xmlns =http://schemas.microsoft.com/winfx/2006/xaml/presentation
的xmlns:X =HTTP: //schemas.microsoft.com/winfx/2006/xaml
的xmlns:D =http://schemas.microsoft.com/expression/blend/2008的xmlns:MC =HTTP://模式。 openxmlformats.org/markup-compatibility/2006MC:可忽略=D
X:类=GO.MainWindow
的xmlns:地方=CLR的命名空间:GO
X:名称=窗口
标题=主窗口
WIDTH =640HEIGHT =480>
<电网WIDTH =200HEIGHT =500>
< Grid.Resources>
<局部:NodeConverter X:键=NodeConverter/>
<局部:ModelToViewConverter X:键=ModelConverter/>
< /Grid.Resources>
<! - < ListView控件Grid.Column =1的ItemsSource ={绑定路径= FieldDescriptions,转换器= {StaticResource的ModelConverter}}> - >
< ListView控件Grid.Column =1的ItemsSource ={绑定路径= FieldDescriptions}>
< ListView.ItemTemplate>
<&DataTemplate的GT;
<局部:节点的DataContext ={结合转换器= {StaticResource的ModelConverter}}/>
< / DataTemplate中>
< /ListView.ItemTemplate>
< /&的ListView GT;
< /网格和GT;
在主窗口的数据上下文被设置为一个新的RD(),其定义如下:
RD
公开RD类
{
私人的IEnumerable< IFieldDescription> GetTestData()
{
收益回报新FieldDescription(字符串,真正的);
收益回报新FieldDescription(整数,FALSE);
收益回报新FieldDescription(双,FALSE);
收益回报新FieldDescription(日期,FALSE);
收益回报新FieldDescription(枚举,FALSE);
}
公共虚拟的ObservableCollection< IFieldDescription> FieldDescriptions
{
{返回新的ObservableCollection< IFieldDescription>(GetTestData()); }
}
}
我的任何转换器目前被定义为:
公共类ModelToViewConverter:的IValueConverter
{
公共对象转换(对象的值,类型TARGETTYPE,对象参数,系统.Globalization.CultureInfo文化)
{
如果(价值== NULL)
返回NULL;
如果(价值IFieldDescription)
{
节点模型模型=新的节点模型((IFieldDescription)值);
返回新节点(模型);
}
返回NULL;
}
公共对象ConvertBack(对象的值,类型TARGETTYPE,对象参数,System.Globalization.CultureInfo文化)
{
抛出新NotImplementedException();
}
}
通常我使用的DataTemplates
搭售一个查看
与模式
或视图模型
唯一的地方我查看的隐藏代码以往任何时候都引用模型或视图模型是在启动时设置启动视图模型作为启动视图的的DataContext
。一切是迷上了的DataTemplates
(或 DataTemplateSelectors
Silverlight的)。
(其实是公平的,有时我需要做一些特别的东西,我会投对象的的DataContext
作为视图模型或模型的代码隐藏,但这些情况是罕见的,我认为他们为黑客)
例如,设置启动浏览/视图模型:
公共部分类应用:应用
{
保护覆盖无效OnStartup(StartupEventArgs E)
{
base.OnStartup(E);
VAR应用=新ShellView();
VAR背景=新ShellViewModel();
app.DataContext =背景;
app.Show();
}
}
下面是一些的DataTemplates的例子:
< Window.Resources>
<数据类型的DataTemplate ={X:类型本地:SomeViewModel}>
<局部:SomeViewForViewModel />
< / DataTemplate中>
<数据类型的DataTemplate ={X:类型本地:SomeModel}>
<局部:SomeViewForModel />
< / DataTemplate中>
< /Window.Resources>
最后,我将使用 ContentControls
在我的XAML,我想显示自己的看法
< ContentControl中CONTENT ={结合SomeViewModelProperty}/>
或
< ContentControl中CONTENT ={结合SomeModelProperty}/>
有时,甚至没有必要ContentControls。例如,如果你绑定一个的ListView
到的ObservableCollection<节点模型>
,然后在<$ C $每个项目C>的ListView 将类型的对象节点模型
和WPF将自动拿起的DataTemplate了点。
< ListView控件的ItemsSource ={绑定路径= CollectionOfNodeModel}>
< ListView.Resources> <! - 也可以把这个Window.Resources - >
<数据类型的DataTemplate ={X:类型本地:节点模型}>
<局部:节点/> <! - 的DataContext会隐是节点模型对象 - >
< / DataTemplate中>
< /ListView.Resources>
< /&的ListView GT;
背后MVVM的想法是,在你的ViewModels整个应用程序的功能,视图仅仅是一个漂亮的它坐落在的ViewModels的顶部UI,使其更加人性化。在一个完美的世界里,查看很容易被其他UI替换
如果你有兴趣,我有一个的简单的例子MVVM在我的博客里面包含使用MVVM设计模式
的一些范例
I'm trying to setup an MVVM style application, and I think I'm getting myself into a few knots with the interactions of these items and hoping someone can help. Am I doing something really wrong here?
I suppose my main 2 questions are
- How should I be going from my model to my view. Currently I'm trying to do this via a converter.
- If using a converter is correct, how do I get this working correctly? I believe the datacontext set on the Node constructor is replaced by the XAML that includes the converter.
My classes (simplified a bit):
IFieldDescription
// Interface that is supposed to be the Model
public interface IFieldDescription
{
String Name { get; }
bool Disabled { get; }
}
NodeModel
// Class that is supposed to be the ViewModel
public class NodeModel : NotifyPropertyChanged
{
internal NodeModel() { }
public NodeModel(IFieldDescription fieldDescription)
{
this.FieldDescription = fieldDescription;
}
protected IFieldDescription FieldDescription
{
get { return this.fieldDescription; }
set {
this.fieldDescription = value;
this.OnPropertyChanged("Name");
this.OnPropertyChanged("Disabled");
this.OnPropertyChanged("PrimaryKey"); }
}
private IFieldDescription fieldDescription;
public String Name { get { return this.FieldDescription.Name; } }
public Boolean Disabled { get { return this.FieldDescription.Disabled; } }
}
Node Code behind
public Node(NodeModel model)
{
this.DataContext = model;
this.InitializeComponent();
}
XAML
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:GO" x:Class="GO.Node" Background="White"
>
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/> <!-- Primary Key Icon -->
<ColumnDefinition Width="Auto"/> <!-- Type Icon -->
<ColumnDefinition/> <!-- Node Text -->
<ColumnDefinition Width="Auto"/> <!-- Option Cog -->
<ColumnDefinition Width="Auto"/> <!-- Match Icon -->
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.Resources>
<local:AttributeDataTypeConverter x:Key="DateTypeConverter"/>
</Grid.Resources>
<Image Grid.Column="0" Source="C:\Users\ian.wright\Documents\Expression\Blend 4\Projects\GO\GO\Resources\Images\PrimaryKey.png" Stretch="None" Visibility="{Binding Path=IsPrimaryKey}"/>
<Image Grid.Column="1" Source="{Binding Path=Type, Converter={StaticResource DateTypeConverter}}" Stretch="None"/>
<TextBlock Grid.Column="2" Text="{Binding Path=Name}" VerticalAlignment="Bottom" Margin="0,0,0,2"/>
<Image Grid.Column="3" Source="C:\Users\ian.wright\Documents\Expression\Blend 4\Projects\GO\GO\Resources\Images\Cog.png" Stretch="None" Visibility="{Binding Path=HasOptions}"/>
<Image Grid.Column="4" Source="{Binding Path=CastType}" Stretch="None"/>
</Grid>
</UserControl>
MainWindow
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
x:Class="GO.MainWindow"
xmlns:local="clr-namespace:GO"
x:Name="Window"
Title="MainWindow"
Width="640" Height="480">
<Grid Width="200" Height="500">
<Grid.Resources>
<local:NodeConverter x:Key="NodeConverter"/>
<local:ModelToViewConverter x:Key="ModelConverter"/>
</Grid.Resources>
<!--<ListView Grid.Column="1" ItemsSource="{Binding Path=FieldDescriptions, Converter={StaticResource ModelConverter}}">-->
<ListView Grid.Column="1" ItemsSource="{Binding Path=FieldDescriptions}">
<ListView.ItemTemplate>
<DataTemplate>
<local:Node DataContext="{Binding Converter={StaticResource ModelConverter}}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
The data context for the mainwindow is set to a new RD() which is defined below: RD
public class RD
{
private IEnumerable<IFieldDescription> GetTestData()
{
yield return new FieldDescription("String", true);
yield return new FieldDescription("Integer", false);
yield return new FieldDescription("Double", false);
yield return new FieldDescription("Date", false);
yield return new FieldDescription("Enum", false);
}
public virtual ObservableCollection<IFieldDescription> FieldDescriptions
{
get { return new ObservableCollection<IFieldDescription>(GetTestData()); }
}
}
Any my converter is currently defined as :
public class ModelToViewConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return null;
if (value is IFieldDescription)
{
NodeModel model = new NodeModel((IFieldDescription)value);
return new Node(model);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Usually I use DataTemplates
for tying a View
with a Model
or ViewModel
The only place my View's code-behind ever references a Model or ViewModel is on startup when it sets the startup ViewModel as the startup View's DataContext
. Everything else is hooked up with DataTemplates
(or DataTemplateSelectors
for Silverlight).
(Actually to be fair sometimes I do need to do something special and I will cast an object's DataContext
as a ViewModel or Model in the code-behind, but those cases are rare and I view them as hacks)
For example, setting the startup View/ViewModel:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var app = new ShellView();
var context = new ShellViewModel();
app.DataContext = context;
app.Show();
}
}
Here's an example of some DataTemplates:
<Window.Resources>
<DataTemplate DataType="{x:Type local:SomeViewModel}">
<local:SomeViewForViewModel />
</DataTemplate>
<DataTemplate DataType="{x:Type local:SomeModel}">
<local:SomeViewForModel />
</DataTemplate>
</Window.Resources>
And finally, I'll use ContentControls
in my XAML where I want to display my Views
<ContentControl Content="{Binding SomeViewModelProperty}" />
or
<ContentControl Content="{Binding SomeModelProperty}" />
Sometimes ContentControls are not even needed. For example, if you bound a ListView
to an ObservableCollection<NodeModel>
, then each item in the ListView
would be an object of type NodeModel
and WPF will automatically pick up the DataTemplate for that.
<ListView ItemsSource="{Binding Path=CollectionOfNodeModel}">
<ListView.Resources> <!-- Could also put this in Window.Resources -->
<DataTemplate DataType="{x:Type local:NodeModel}">
<local:Node /> <!-- DataContext will implicitly be the NodeModel object -->
</DataTemplate>
</ListView.Resources>
</ListView>
The idea behind MVVM is that your entire application functions in your ViewModels, and the Views are simply a pretty UI which sits on top of the ViewModels to make them more user-friendly. In a perfect world, the View could easily be replaced by any other UI
If you're interested, I have a simple MVVM example on my blog which contains some examples of using the MVVM design pattern
这篇关于如何最好地应用WPF MVVM?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!