2024年终活动

DevExpress控件使用交流,DevExpress中国社区Dev联系电话 联系电话:023-68661681

界面控件DevExpress WPF入门级指南:运行时生成的POCO视图模型(三)

来源:   发布时间:2022-05-07   浏览:1251次

POCO(Plain Old CLR Objects)视图模型简化并加快了开发过程。

POCO 视图模型允许您:

  • 将可绑定属性定义为简单的自动实现的属性。
  • 创建在运行时用作命令的方法。
  • 使属性和方法实现特定于 MVVM 的接口。

这允许您创建干净、简单、可维护和可测试的 MVVM 代码,POCO 视图模型与任何 WPF 控件完全兼容。

您可以使用在编译时生成的视图模型在编译时为您的视图模型生成样板代码。

DevExpress WPF v21.2正式版下载

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 | 下载试用

DevExpress WPF拥有120+个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序,这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件的衍伸产品,还是以数据为中心的商业智能产品,都能通过DevExpress WPF控件来实现。


DevExpress技术交流群6:600715373      欢迎一起进群讨论

更多DevExpress线上公开课、中文教程资讯请上中文网获取

慧都图表线上公开课火热报名中
本站文章除注明转载外,均为本站原创或翻译
欢迎任何形式的转载,但请务必注明出处,尊重他人劳动成果
转载请注明:文章转载自:DevExpress控件中文网 [https://www.devexpresscn.com/]
本文地址:https://www.devexpresscn.com/post/3041.html

相关产品: DevExpress Universal Subscription,

扫码咨询
电话咨询
023-68661681
返回
顶部