繁体   English   中英

带有 mvvmlight 和 Fluent.Validation 的 WPF MVVM

[英]WPF MVVM with mvvmlight and Fluent.Validation

我的最新应用程序遇到了一些难题。

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

视图的 DataContext 是一个MainViewModel : ViewModelBase ,左侧有一个用于 ListView 的ObservableCollection<ProviderDto> ,右侧有一个用于详细属性的属性ProviderDto SelectedProvider

还有几个RelayCommands可以添加、编辑和删除单个ProviderDto

ViewModel 使用ProviderService来执行这些操作,这些操作被注入到它的构造函数中, SimpleIoC在单独的ViewModelLocator使用 mvvmlight 的SimpleIoC

到目前为止一切正常,我还设法拥有 Design-Time-Data。

我现在尝试将 Fluent.Validation 添加到 Mix 并按照本文中描述的方式实现(我的ProviderDto现在继承自ValidationBase而不是ObservableObject现在继承自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-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 中,那么我将不得不做我不知道的事情来验证我的 SelectedProvider 在视图中。 验证将如何工作? 我查看了 DataTemplating the Controls,但似乎无法使其与 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();
}

在视图中,我可以根据这篇文章实现控件

<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 不是很美观……

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM