POCO(Plain Old CLR Objects)视图模型简化并加快了开发过程。
POCO 视图模型允许您:
- 将可绑定属性定义为简单的自动实现的属性。
- 创建在运行时用作命令的方法。
- 使属性和方法实现特定于 MVVM 的接口。
这允许您创建干净、简单、可维护和可测试的 MVVM 代码,POCO 视图模型与任何 WPF 控件完全兼容。
您可以使用在编译时生成的视图模型在编译时为您的视图模型生成样板代码。
Services
DevExpress MVVM框架包括Services机制,下面的代码示例演示了如何访问 Message Box 服务。
C#
using DevExpress.Mvvm.POCO; ... public class LoginViewModel { public IMessageBoxService MessageBoxService { get { return this.GetService<IMessageBoxService>(); } } }
依赖注入
要将视图绑定到视图模型,请创建解析正确 ViewModel 类型的 MarkupExtension:
C#
public class DISource : MarkupExtension { public static Func<Type, object, string, object> Resolver { get; set; } public Type Type { get; set; } public object Key { get; set; } public string Name { get; set; } public override object ProvideValue(IServiceProvider serviceProvider) => Resolver?.Invoke(Type, Key, Name); }
在应用程序启动时注册解析器:
C#
protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); DISource.Resolver = Resolve; } object Resolve(Type type, object key, string name) { if(type == null) return null; if(key != null) return Container.ResolveKeyed(key, type); if(name != null) return Container.ResolveNamed(name, type); return Container.Resolve(type); }
通过以下方式在 XAML 中指定 DataContext:
XAML
DataContext="{common:DISource Type=common:MainViewModel}"
要在依赖注入容器中使用 POCO 视图模型,请利用 ViewModelSource.GetPOCOType 方法注册在运行时生成的 POCO 类型:
C#
container.RegisterType(typeof(IMainViewModel), ViewModelSource.GetPOCOType(typeof(MainViewModel)));
查看模型父子关系
POCO 视图模型可以通过父子关系相互关联。 这是通过 ISupportParentViewModel 接口实现的,该接口在您使用 ViewModelSource 类创建 POCO 对象时自动实现。 通过这个接口,子视图模型可以访问在主视图模型中注册的服务。
自动 IDataErrorInfo 实现
IDataErrorInfo 接口是 WPF 中数据验证的标准机制,您可以使用此接口为每个单独的属性或整个对象定义验证规则。 POCO 机制允许您基于定义的属性或 Fluent API 自动实现IDataErrorInfo 接口。
要启用此功能,请为您的视图模型应用 POCOViewModel 属性并将 POCOViewModel.ImplementIDataErrorInfo 参数设置为 True。
C#
//Attribute-based approach [POCOViewModel(ImplementIDataErrorInfo = true)] public class LoginViewModel { [Required(ErrorMessage = "Please enter the user name.")] public virtual string UserName { get; set; } } //Fluent API [POCOViewModel(ImplementIDataErrorInfo = true)] [MetadataType(typeof(LoginViewModel.Metadata))] public class LoginViewModel { public class Metadata : IMetadataProvider<LoginViewModel> { void IMetadataProvider<LoginViewModel>.BuildMetadata(MetadataBuilder<LoginViewModel> builder) { builder.Property(x => x.UserName). Required(() => "Please enter the user name."); } } public virtual string UserName { get; set; } }
当 ViewModelSource 生成 View Model 的后代时,它会实现 IDataErrorInfo 接口,如下所示:
C#
public class LoginViewModel : IDataErrorInfo { ... string IDataErrorInfo.Error { get { return string.Empty; } } string IDataErrorInfo.this[string columnName] { get { return IDataErrorInfoHelper.GetErrorText(this, columnName); } } }
IDataErrorInfoHelper类允许您根据指定的DataAnnotation 属性或 Fluent API 获取错误。
下面的代码示例演示了如何使用 POCO 机制来实现 IDataErrorInfo 接口。
MainView.xaml
<UserControl x:Class="Example.View.MainView" 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:dx="http://schemas.devexpress.com/winfx/2008/xaml/core" xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors" xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm" xmlns:ViewModel="clr-namespace:Example.ViewModel" mc:Ignorable="d" d:DesignHeight="400" d:DesignWidth="400" DataContext="{dxmvvm:ViewModelSource Type=ViewModel:MainViewModel}"> <UserControl.Resources> <dxmvvm:BooleanNegationConverter x:Key="BooleanNegationConverter"/> </UserControl.Resources> <Grid> <StackPanel Orientation="Vertical" Margin="10" dxe:ValidationService.IsValidationContainer="True" x:Name="validationContainer"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <StackPanel Orientation="Vertical" Margin="0,0,4,6"> <TextBlock Text="Name" Margin="6,2,0,2"/> <dxe:TextEdit NullText="First" EditValue="{Binding FirstName, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/> </StackPanel> <StackPanel Orientation="Vertical" Margin="4,0,0,6" Grid.Column="1"> <TextBlock Text=" " Margin="6,2,0,2"/> <dxe:TextEdit NullText="Last" EditValue="{Binding LastName, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/> </StackPanel> </Grid> <StackPanel Orientation="Vertical" Margin="0,0,0,6"> <TextBlock Text="Email" Margin="6,2,0,2"/> <dxe:TextEdit EditValue="{Binding Email, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/> </StackPanel> <StackPanel Orientation="Vertical" Margin="0,0,0,6"> <TextBlock Text="Password" Margin="6,2,0,2"/> <dxe:PasswordBoxEdit EditValue="{Binding Password, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/> </StackPanel> <StackPanel Orientation="Vertical" Margin="0,0,0,6"> <TextBlock Text="Confirm Password" Margin="6,2,0,2"/> <dxe:PasswordBoxEdit EditValue="{Binding ConfirmPassword, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/> </StackPanel> <Button VerticalAlignment="Top" Content="Sign Up" Width="150" HorizontalAlignment="Right" Margin="0,10" IsEnabled="{Binding Path=(dxe:ValidationService.HasValidationError), ElementName=validationContainer, Converter={StaticResource BooleanNegationConverter}}"/> </StackPanel> </Grid> </UserControl>
MainViewModel.cs
using DevExpress.Mvvm; using DevExpress.Mvvm.DataAnnotations; using System.Windows.Media; namespace Example.ViewModel { [POCOViewModel(ImplementIDataErrorInfo = true)] public class MainViewModel : ViewModelBase { static PropertyMetadataBuilder<MainViewModel, string> AddPasswordCheck(PropertyMetadataBuilder<MainViewModel, string> builder) { return builder.MatchesInstanceRule((name, vm) => vm.Password == vm.ConfirmPassword, () => "The passwords don't match.") .MinLength(8, () => "The password must be at least 8 characters long.") .MaxLength(20, () => "The password must not exceed the length of 20."); } public static void BuildMetadata(MetadataBuilder<MainViewModel> builder) { builder.Property(x => x.FirstName) .Required(() => "Please enter the first name."); builder.Property(x => x.LastName) .Required(() => "Please enter the last name."); builder.Property(x => x.Email) .EmailAddressDataType(() => "Please enter a correct email address."); AddPasswordCheck(builder.Property(x => x.Password)) .Required(() => "Please enter the password."); AddPasswordCheck(builder.Property(x => x.ConfirmPassword)) .Required(() => "Please confirm the password."); } public virtual string FirstName { get; set; } public virtual string LastName { get; set; } public virtual string Email { get; set; } public virtual string Password { get; set; } public virtual string ConfirmPassword { get; set; } public void OnPasswordChanged() { this.RaisePropertyChanged(() => ConfirmPassword); } public void OnConfirmPasswordChanged() { this.RaisePropertyChanged(() => Password); } } }
MainViewModel.vb
Imports DevExpress.Mvvm Imports DevExpress.Mvvm.DataAnnotations Imports System.Windows.Media Namespace Example.ViewModel <POCOViewModel(ImplementIDataErrorInfo := True)> _ Public Class MainViewModel Inherits ViewModelBase Private Shared Function AddPasswordCheck(ByVal builder As PropertyMetadataBuilder(Of MainViewModel, String)) As PropertyMetadataBuilder(Of MainViewModel, String) Return builder.MatchesInstanceRule(Function(name, vm) vm.Password = vm.ConfirmPassword, Function() "The passwords don't match.").MinLength(8, Function() "The password must be at least 8 characters long.").MaxLength(20, Function() "The password must not exceed the length of 20.") End Function Public Shared Sub BuildMetadata(ByVal builder As MetadataBuilder(Of MainViewModel)) builder.Property(Function(x) x.FirstName).Required(Function() "Please enter the first name.") builder.Property(Function(x) x.LastName).Required(Function() "Please enter the last name.") builder.Property(Function(x) x.Email).EmailAddressDataType(Function() "Please enter a correct email address.") AddPasswordCheck(builder.Property(Function(x) x.Password)).Required(Function() "Please enter the password.") AddPasswordCheck(builder.Property(Function(x) x.ConfirmPassword)).Required(Function() "Please confirm the password.") End Sub Public Overridable Property FirstName() As String Public Overridable Property LastName() As String Public Overridable Property Email() As String Public Overridable Property Password() As String Public Overridable Property ConfirmPassword() As String Public Sub OnPasswordChanged() Me.RaisePropertyChanged(Function() ConfirmPassword) End Sub Public Sub OnConfirmPasswordChanged() Me.RaisePropertyChanged(Function() Password) End Sub End Class End Namespace
DevExpress WPF拥有120+个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序,这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件的衍伸产品,还是以数据为中心的商业智能产品,都能通过DevExpress WPF控件来实现。
DevExpress技术交流群6:600715373 欢迎一起进群讨论