带有 mvvmlight 和 Fluent.Validation 的 WPF MVVM
WPF MVVM with mvvmlight and Fluent.Validation
我对我的最新应用程序有点困惑。
这是一个使用 MVVM Light 和 Fluent.Validation 的主从 WPF MVVM 应用程序。
View 的 DataContext 是 MainViewModel : ViewModelBase
,左边是 ListView 的 ObservableCollection<ProviderDto>
,右边是 属性 ProviderDto SelectedProvider
的详细 Properties。
还有几个RelayCommands
可以添加、编辑和删除单个ProviderDto
的
ViewModel 使用 ProviderService
来执行这些操作,这些操作是通过 mvvmlight 的 SimpleIoC
在单独的 ViewModelLocator
.
中注入到它的构造函数中的
到目前为止一切正常,我还设法获得了设计时数据。
我现在尝试将 Fluent.Validation 添加到 Mix 并按照 this post 中描述的方式实现(我的 ProviderDto
现在继承自 ValidationBase
而不是 ObservableObject
。Base 现在继承自 ObservableObject
。另外我在 ViewModelLocator
中注册了 ProviderDtoValidator
。)
这让我可以自动验证我的 ObservableObjects 并对其调用 .IsValid
。
到目前为止一切顺利,我相信我能够让它适应视图并使那些错误框变红:)。
现在回答我真正的问题:
我想在视图上有一个按钮来保存 SelectedProvider
上的更改。这个自然要绑定这个:
Relaycommand SaveProviderCommand = new RelayCommand(SaveProvider, CanSaveProvider)
private bool CanSaveProvider()
{
return SelectedProvider.IsValid;
}
private void SaveProvider()
{
if (SelectedProvider.IsValid)
_providerController.SaveProvider(SelectedProvider);
}
SaveProviderCommand SaveCommand 放在哪里?
如果我把它放在 ViewModel 中,那么我只能从 SelectedProvider 中调用它-属性:
public ProviderDto SelectedProvider
{
get { return _selectedProvider; }
set
{
Set(() => SelectedProvider, ref prV_selectedProvider, value);
SaveProviderCommand.RaiseCanExecuteChanged(); // Here!
}
}
当 SelectedProvider
中只有一个 属性 被更改时,这显然不起作用。
另一种可能性是将命令放在 DTO 本身上,并在每次 属性 发生更改时调用它。例如,当电子邮件-属性 更改时:
//A Property from Provider
public string Email
{
get { return _email; }
set
{
Set(() => Email, ref _email, value.TrimSafe());
SaveProviderCommand.RaiseCanExecuteChanged(); // Here!
}
}
这里的优点是,当我每 属性 更改一次时,验证工作开箱即用,直至视图级别。缺点是我必须在 DTO 的构造函数中注入 ProviderController
,以便它可以在私有 Save-Method 中调用。我认为 DTO 不应该知道如何自救。它只能判断它是否 .IsValid
并且保存逻辑应该属于 ViewModel。
希望你能看到我的困境:
如果我将 SaveCommand 放在 ViewModel 中,那么我将不得不做
我不知道如何在视图中验证我的 SelectedProvider。验证将如何工作?我查看了控件的 DataTemplating,但我似乎无法使其与 Fluent.Validation..
一起工作
如果我将 SaveCommand 放在 DTO 本身中,那么验证会很好地工作,但我认为在应该保持愚蠢的东西中注入这么多功能是不正确的。
当然这是一个浓缩的例子,但我认为足以说明问题。希望得到一些关于模式和实践的好建议。
我找到了解决此问题的合适方法,也许它会对其他人有所帮助。
由于 Viewmodel 中的 SelectedProvider 及其单个属性都实现了 INotifyPropertyChanged(通过 ViewModelBase 或 ObservableObject),我可以简单地订阅 ViewModel 中的 SelectedProvider.PropertyChanged。
public MainViewModel()
{
// Constructor
if (SelectedProvider != null)
SelectedProvider.PropertyChanged += SelectedProvider_PropertyChanged;
}
private void SelectedProvider_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
SaveProviderCommand.RaiseCanExecuteChanged();
}
在视图中我可以根据this post
实现控件
<Window.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel>
<Grid DockPanel.Dock="Right" Width="16" Height="16"
VerticalAlignment="Center" Margin="3 0 0 0">
<Ellipse Width="16" Height="16" Fill="Red"/>
<Ellipse Width="3" Height="8"
VerticalAlignment="Top" HorizontalAlignment="Center"
Margin="0 2 0 0" Fill="White"/>
<Ellipse Width="2" Height="2" VerticalAlignment="Bottom"
HorizontalAlignment="Center" Margin="0 0 0 2"
Fill="White"/>
</Grid>
<Border BorderBrush="Red" BorderThickness="2" CornerRadius="2">
<AdornedElementPlaceholder/>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource=
{x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<TextBox Name="TxtEmail" Margin="5" Text="{Binding Path=SelectedProvider.Email,
UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, Mode=TwoWay}" />
这种方法为我提供了我想要的关注点分离和开箱即用的验证。唯一的小缺点:我觉得在 VM 中有一个事件订阅不是很美观...
我对我的最新应用程序有点困惑。
这是一个使用 MVVM Light 和 Fluent.Validation 的主从 WPF MVVM 应用程序。
View 的 DataContext 是 MainViewModel : ViewModelBase
,左边是 ListView 的 ObservableCollection<ProviderDto>
,右边是 属性 ProviderDto SelectedProvider
的详细 Properties。
还有几个RelayCommands
可以添加、编辑和删除单个ProviderDto
的
ViewModel 使用 ProviderService
来执行这些操作,这些操作是通过 mvvmlight 的 SimpleIoC
在单独的 ViewModelLocator
.
到目前为止一切正常,我还设法获得了设计时数据。
我现在尝试将 Fluent.Validation 添加到 Mix 并按照 this post 中描述的方式实现(我的 ProviderDto
现在继承自 ValidationBase
而不是 ObservableObject
。Base 现在继承自 ObservableObject
。另外我在 ViewModelLocator
中注册了 ProviderDtoValidator
。)
这让我可以自动验证我的 ObservableObjects 并对其调用 .IsValid
。
到目前为止一切顺利,我相信我能够让它适应视图并使那些错误框变红:)。
现在回答我真正的问题:
我想在视图上有一个按钮来保存 SelectedProvider
上的更改。这个自然要绑定这个:
Relaycommand SaveProviderCommand = new RelayCommand(SaveProvider, CanSaveProvider)
private bool CanSaveProvider()
{
return SelectedProvider.IsValid;
}
private void SaveProvider()
{
if (SelectedProvider.IsValid)
_providerController.SaveProvider(SelectedProvider);
}
SaveProviderCommand SaveCommand 放在哪里?
如果我把它放在 ViewModel 中,那么我只能从 SelectedProvider 中调用它-属性:
public ProviderDto SelectedProvider
{
get { return _selectedProvider; }
set
{
Set(() => SelectedProvider, ref prV_selectedProvider, value);
SaveProviderCommand.RaiseCanExecuteChanged(); // Here!
}
}
当 SelectedProvider
中只有一个 属性 被更改时,这显然不起作用。
另一种可能性是将命令放在 DTO 本身上,并在每次 属性 发生更改时调用它。例如,当电子邮件-属性 更改时:
//A Property from Provider
public string Email
{
get { return _email; }
set
{
Set(() => Email, ref _email, value.TrimSafe());
SaveProviderCommand.RaiseCanExecuteChanged(); // Here!
}
}
这里的优点是,当我每 属性 更改一次时,验证工作开箱即用,直至视图级别。缺点是我必须在 DTO 的构造函数中注入 ProviderController
,以便它可以在私有 Save-Method 中调用。我认为 DTO 不应该知道如何自救。它只能判断它是否 .IsValid
并且保存逻辑应该属于 ViewModel。
希望你能看到我的困境:
如果我将 SaveCommand 放在 ViewModel 中,那么我将不得不做 我不知道如何在视图中验证我的 SelectedProvider。验证将如何工作?我查看了控件的 DataTemplating,但我似乎无法使其与 Fluent.Validation..
一起工作
如果我将 SaveCommand 放在 DTO 本身中,那么验证会很好地工作,但我认为在应该保持愚蠢的东西中注入这么多功能是不正确的。
当然这是一个浓缩的例子,但我认为足以说明问题。希望得到一些关于模式和实践的好建议。
我找到了解决此问题的合适方法,也许它会对其他人有所帮助。
由于 Viewmodel 中的 SelectedProvider 及其单个属性都实现了 INotifyPropertyChanged(通过 ViewModelBase 或 ObservableObject),我可以简单地订阅 ViewModel 中的 SelectedProvider.PropertyChanged。
public MainViewModel()
{
// Constructor
if (SelectedProvider != null)
SelectedProvider.PropertyChanged += SelectedProvider_PropertyChanged;
}
private void SelectedProvider_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
SaveProviderCommand.RaiseCanExecuteChanged();
}
在视图中我可以根据this post
实现控件<Window.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel>
<Grid DockPanel.Dock="Right" Width="16" Height="16"
VerticalAlignment="Center" Margin="3 0 0 0">
<Ellipse Width="16" Height="16" Fill="Red"/>
<Ellipse Width="3" Height="8"
VerticalAlignment="Top" HorizontalAlignment="Center"
Margin="0 2 0 0" Fill="White"/>
<Ellipse Width="2" Height="2" VerticalAlignment="Bottom"
HorizontalAlignment="Center" Margin="0 0 0 2"
Fill="White"/>
</Grid>
<Border BorderBrush="Red" BorderThickness="2" CornerRadius="2">
<AdornedElementPlaceholder/>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource=
{x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<TextBox Name="TxtEmail" Margin="5" Text="{Binding Path=SelectedProvider.Email,
UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, Mode=TwoWay}" />
这种方法为我提供了我想要的关注点分离和开箱即用的验证。唯一的小缺点:我觉得在 VM 中有一个事件订阅不是很美观...