我的最新App有点困扰。

这是一个使用MVVM Light和Fluent.Validation的Master-Detail WPF MVVM应用程序。

View的DataContext是MainViewModel : ViewModelBase,左边是ListView的ObservableCollection<ProviderDto>,右边是属性ProviderDto SelectedProvider的详细属性。

还有几个RelayCommands可以添加,编辑和删除单个ProviderDto
ViewModel使用ProviderService来执行这些操作,然后将其与mvvmlight的SimpleIoC注入(inject)其构造函数中,并在单独的ViewModelLocator中。

到目前为止,一切工作正常,我还设法拥有了设计时数据。

我现在尝试将Fluent.Validation添加到Mix中,并按照this post中所述进行实现(我的ProviderDto现在继承自ValidationBase而不是ObservableObject。基础现在继承自ObservableObject。我也将ProviderDtoValidator注册到ViewModelLocator中。)

这使我可以自动验证我的ObservableObjects并在它们上调用.IsValid

到目前为止,我确信我将能够使其适应View,并使这些错误框变成红色:)。

现在我真正的问题是:

我想在 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!
    }
}

这样做的好处是,当我更改每个属性时,验证即开即用地工作到 View 级别。缺点是我必须将ProviderController注入(inject)DTO的构造函数中,以便可以在私有(private)Save-Method中调用它。我不认为DTO应该知道如何保存自己。它只能知道.IsValid和Saving逻辑是否应该属于ViewModel。

希望您能看到我的困境:
  • 如果我将SaveCommand放在ViewModel中,那么我将不得不做
    我不知道在View中验证我的SelectedProvider的方法。验证将如何工作?我研究了DataTemplating控件,但似乎无法使其与Fluent.Validation ..
  • 一起使用
  • 如果我将SaveCommand放在DTO本身中,则Validation可以很好地工作,但我认为向如此愚蠢的事物中注入(inject)这么多功能是不正确的。

  • 当然,这是一个简单的例子,但我认为足以说明问题。希望对模式和实践获得一些好的建议。

    最佳答案

    我找到了解决该问题的合适方法,也许会对其他人有所帮助。

    由于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();
    }
    

    在 View 中,我可以根据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中拥有Event-Subscription并不是很美。

    关于c# - 具有mvvmlight和Fluent.Validation的WPF MVVM,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/31695804/

    10-13 03:16