Catel:多个 ViewModels 与一个 View。可能吗?

Catel: multiple ViewModels with one View. Is it possible?

我为我的用户控件的 ViewModel 获得了一个通用基础-class:

public class SuggestModule<TEntity> : ViewModelBase 
        where TEntity : class, ISuggestable, new()
    {
        public SuggestModule(ISomeService someService)
        {
            // Some logic
        }

        // Some private fields, public properties, commands, etc...
    }
}

Whitch 有许多可继承的 classes。也就是其中的两个,例如:

public class CitizenshipSuggestViewModel : SuggestModule<Citizenship>
{
    public CitizenshipSuggestViewModel(ISomeService someService) 
        : base(someService) { }       
}

public class PlaceOfBirthSuggestViewModel : SuggestModule<PlaceOfBirth>
{
    public PlaceOfBirthSuggestViewModel(ISomeService someService) 
        : base(someService) { }       
}

即视图实现:

<catel:UserControl
    x:Class="WPF.PRC.PBF.Views.UserControls.SuggestUserControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:catel="http://schemas.catelproject.com"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:pbf="clr-namespace:WPF.PRC.PBF">

    <Grid>
        <TextBox Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"> />
        <ListBox ItemsSource="{Binding ItemsCollection}" />
        // Other elements, behaviors, other extensive logic...
    </Grid>

</catel:UserControl>

现在,在 MainWindow 中创建两个 ContentControl:

<catel:Window
    x:Class="WPF.PRC.PBF.Views.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:catel="http://schemas.catelproject.com">

    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <ContentControl Grid.Row="0" Content="{Binding CitizenshipSuggestViewModel, Converter={catel:ViewModelToViewConverter}}" />
        <ContentControl Grid.Row="1" Content="{Binding PlaceOfBirthSuggestViewModel, Converter={catel:ViewModelToViewConverter}}" />

    </Grid>

</catel:Window>

由于违反命名约定,手动解析App.xaml.cs中的ViewModel:

    var viewModelLocator = ServiceLocator.Default.ResolveType<IViewModelLocator>();
    viewModelLocator.Register(typeof(SuggestUserControl), typeof(CitizenshipSuggestViewModel));
    viewModelLocator.Register(typeof(SuggestUserControl), typeof(PlaceOfBirthSuggestViewModel));

    var viewLocator = ServiceLocator.Default.ResolveType<IViewLocator>();
    viewLocator.Register(typeof(CitizenshipSuggestViewModel), typeof(SuggestUserControl));
    viewLocator.Register(typeof(PlaceOfBirthSuggestViewModel), typeof(SuggestUserControl));

但现在我有两个具有相同 ViewModel 的视图。 我如何在不创建相同的视图并在每个视图中重复代码的情况下解决这个问题?

提前致谢!

一种可能是在您的 UserControl 代码隐藏中创建一个 dependencyProperty,例如

   #region Properties       

    public string Test
    {
        get { return (string)GetValue(TestProperty); }
        set { SetValue(TestProperty, value); }
    }

    #endregion Properties

    #region Dependency Properties

    public static readonly System.Windows.DependencyProperty TestProperty =
       System.Windows.DependencyProperty.Register("Test", typeof(string), typeof(YourUserControl), new System.Windows.FrameworkPropertyMetadata() { BindsTwoWayByDefault = true });


    #endregion Dependency Properties

然后在您的 xaml 中,您可以将此 属性 绑定为 :

<TextBlock Text="{Binding Test, RelativeSource={RelativeSource AncestorType={x:Type catel:UserControl}, Mode=FindAncestor}}">

然后在你的主窗口中你可以写:

  <views:YourUserControlName Test="{Binding SomeTextPropertyFromMainWindowVM}"/>

因此,您将能够从 windowVM 中的 属性 SomeTextPropertyFromMainWindowVM 绑定到 userControl 中的某些 属性。

如果你的主要 window 有几个视图模型,你可以这样写:

<views:YourUserControlName Test="{Binding SomeViewModel.SomeTextProperty}"/>
<views:YourUserControlName Test="{Binding SomeOtherViewModel.SomeTextProperty}"/>

您有几个选择:

  1. 重复一遍,如果以后需要自定义,您可以只自定义 1 个,而不会产生太多开销。缺点是,如果您需要检查通用行为,则需要将它们全部更改。

  2. 创建一个枚举来表示 VM 所代表的状态。在这种情况下,您可以简单地创建一个 single 虚拟机来捕获您需要处理的所有情况。您可以使用视图上的依赖项 属性 并使用 ViewToViewModelMapping 自动将其映射到虚拟机来解决此问题。这最接近您希望通过视图实现的代码重用。它确实有点违背 "separation of concerns",但由于它代表了 相同类型的数据,我相信它仍然是一个很好的方法。

对于 2,您需要执行以下操作:

1 使用 PlaceOfBirthCitizenship

创建一个枚举 SuggestEntityType

2 在虚拟机上创建一个 属性(此示例代码假设您使用的是 Catel.Fody):

public SuggestedEntityType EntityType { get; set; }

3 在视图上创建依赖关系 属性:

[ViewToViewModel(MappingType = ViewToViewModelMappingType.ViewToViewModel)]
public SuggestedEntityType EntityType
{
    get { return (SuggestedEntityType) GetValue(EntityTypeProperty); }
    set { SetValue(EntityTypeProperty, value); }
}

public static readonly DependencyProperty EntityTypeProperty = DependencyProperty.Register("EntityType", typeof (SuggestedEntityType),
    typeof (MyControl), new PropertyMetadata(null));

4 您现在可以像这样使用用户控件:

<controls:MyView EntityType="Citizenship" />

有关详细信息,请参阅 http://docs.catelproject.com/vnext/catel-mvvm/view-models/mapping-properties-from-view-to-view-model/