我对我最新的应用程序有一点难题。
它是使用MVVM Light和Fluent.Validation的Master-Detail WPF MVVM应用程序。
View的DataContext是MainViewModel : ViewModelBase
,左侧为ListView,ObservableCollection<ProviderDto>
,右侧为详细属性,属性为ProviderDto SelectedProvider
。
还有多个RelayCommands
添加,修改和删除单个ProviderDto
ViewModel使用ProviderService
执行这些操作,这些操作在mvvmlight SimpleIoC
的单独ViewModelLocator
中注入其构造函数。
到目前为止一切正常,我还设法获得了设计时数据。
我现在尝试将Fluent.Validation添加到Mix中,并按照this post中的描述进行实施(我的ProviderDto
现在继承自ValidationBase
而不是ObservableObject
。 Base现在继承自ObservableObject
。我还在ProviderDtoValidator
注册了ViewModelLocator
。)
这使我可以自动验证我的ObservableObjects并在它们上面调用.IsValid
。
到目前为止一切都那么好,我相信我能够使它适用于View并使这些错误框变红:)。
现在回答我真正的问题:
我希望在视图上有一个按钮以保存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-Property中调用它:
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中,那么我将不得不这样做 我不知道在View中验证我的SelectedProvider是什么。验证将如何运作?我查看了DataTemplating控件,但我似乎无法使它与Fluent.Validation一起工作..
如果我将SaveCommand放在DTO本身,那么验证效果很好,但我认为将这么多功能注入一些应该保持愚蠢的东西是正确的。
< / LI>当然这是一个很简单的例子,但我认为这足以说明问题。希望对模式和实践有一些好的建议。
答案 0 :(得分:1)
我为这个问题找到了合适的解决方案,也许它会帮助别人。
由于Viewmodel中的SelectedProvider和它的单个Properties都实现了INotifyPropertyChanged(trhough 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中有一个事件订阅非常美观...