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

时间:2017-12-18 06:55:40

标签: c# wpf mvvm user-controls catel

I got a generic base-class for ViewModel of my user-control:

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

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

Whitch has many inheritable classes. That is two of them, for example:

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

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

That is view implementation:

<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>

Now, in MainWindow creating two ContentControls:

<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>

Due to violation of the Naming Convention, manually resolving a ViewModel in App.xaml.cs:

    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));

But now I have two views with identical ViewModels. How can I solve this problem without creation of identical Views with repetition of the code in each of them?

Thank you in advance!

2 个答案:

答案 0 :(得分:0)

One possibility is to create a dependencyProperty in your UserControl codebehind, for example

bc.addNewBestBlockListener((StoredBlock block) -> {
    if (!blocksFoundMap.contains(block)) {
        System.out.println("addNewBestBlockListener");
        System.out.println(block);
    }
});

Then in your xaml you can bind this property as :

   #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

Then in your MainWindow you can write:

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

So you will be able to bind from the property SomeTextPropertyFromMainWindowVM in your windowVM to some property in your userControl.

If you have in your main window several viewModels, you can write like:

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

答案 1 :(得分:0)

您有几个选择:

  1. 重复自己,如果将来需要自定义,您可以自定义1而不需要太多开销。缺点是,如果您需要检查通用行为,则需要对其进行全部更改。

  2. 创建一个表示VM所代表状态的枚举。在这种情况下,您只需创建一个 vm,即可捕获您需要处理的所有案例。您可以使用视图上的依赖项属性并使用ViewToViewModelMapping自动将其映射到vm来解决此问题。这与您希望通过视图实现的代码重用最接近。它确实有点反对&#34;关注点分离&#34;但由于它代表相同类型的数据我相信它仍然是一个好方法。

    < / LI>

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

    1使用SuggestEntityTypePlaceOfBirth等创建枚举Citizenship

    2在vm上创建一个属性(此示例代码假设您使用的是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/