将VM绑定到不同的视图,具体取决于VM的属性

时间:2017-07-17 04:59:09

标签: c# wpf xaml mvvm

现在我有以下代码:

视图模型(S)

public class VMBase
{
   public string TabID{get;set;}
   public string TabHeader {get;set;}
}

public class VM1:VMBase //implements the properties in base class
{
}

public class VM2:VMBase //implements the properties in base class
{
}

在我的DataTemplate.xaml中,我有ViewModel绑定的不同本地控件,具体取决于ViewModel的类型,即:

<DataTemplate DataType="{x:Type VM:VM1}">
  <local: Control1 />
</DataTemplate>
<DataTemplate DataType="{x:Type VM:VM2}">
  <local: Control2 />
</DataTemplate>

Control1Control2是不同类型的UserControl

public class Control1:UserControl
{
}

public class Control2:UserControl
{
}

当我只有VMBase的两个派生类时,事情仍然可以管理,但如果我有10个怎么办?或者更多?它会变得丑陋。

是否可以将单个VM绑定到不同的视图(用户控件),这样我就不必为VMBase手动创建这么多派生类了?我只需要指定VM属性,例如TabIDTabHeader,正确的视图将作为结果绑定。

编辑:

以下是更多详细信息:我的VM绑定到ContentControl(即:contentcontrol.Content=VM)。每个VM都有两个属性TabIDHeader。是否应调用DataTemplateSelector取决于它是否具有特定的TabID(如果它具有其他TabID,则不应调用此DataTemplateSelector),以及哪个DataTemplate(其中的逻辑)要调用的DataTemplateSelector还取决于Header。如何实现这个?

2 个答案:

答案 0 :(得分:2)

您要找的是DataTemplateSelector。您可以根据不同的标准选择DataTemplate

public class TaskListDataTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        var element = container as FrameworkElement;
        var vmBase = item as VMBase;

        if (element != null && vmBase != null)
        {
            switch(vmBase.TabID)
            {
                case "Tab1": return element.FindResource("Tab1Template") as DataTemplate;
                case "Tab2": return element.FindResource("Tab2Template") as DataTemplate;
                default: return null;
            }
        }
    }
}

您可以在docs中详细了解这些内容,或查看this教程。

答案 1 :(得分:2)

更新的答案 - v2(根据问题编辑)

我认为当null不匹配时,只需在DataTemplateSelector中返回TabID就可以了,因为WPF会尝试选择下一个最佳匹配(即模板)与DataType匹配的。如果TabID匹配,您可以根据TabHeader值返回模板。

因此,您的自定义DataTemplateSelector将如下所示:

public class TabHeaderDataTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        var element = container as FrameworkElement;
        if (element == null)
            return null;

        var viewModel = item as VMBase;
        if (viewModel == null || viewModel.TabID != "02") 
            return null; //continue only if TabID is a match

        if (viewModel != null)
        {
            switch(viewModel.TabHeader)
            {
                case "two":
                    return element.FindResource($"Template2") as DataTemplate;
                case "three":
                    return element.FindResource($"Template3") as DataTemplate;
            }
        }

        return null;
    }
}

示例XAML

<Window.Resources>
    <!-- data template for VM1 -->
    <DataTemplate DataType="{x:Type local:VM1}">
        <Grid>
            <Rectangle Stroke="Black" />
            <TextBlock Margin="5" Text="{Binding TabHeader}" FontSize="18"/>
        </Grid>
    </DataTemplate>

    <!-- data template for VM2 -->
    <DataTemplate DataType="{x:Type local:VM2}">
        <Grid>
            <Rectangle Stroke="Red" />
            <TextBlock Margin="5" Text="{Binding TabHeader}" FontSize="18"/>
        </Grid>
    </DataTemplate>

    <DataTemplate x:Key="Template2">
        <Grid>
            <Ellipse Stroke="Green" StrokeThickness="4"/>
            <TextBlock Margin="10" Text="{Binding TabHeader}" FontSize="24" 
                Foreground="Red" FontWeight="Bold"
                HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Grid>
    </DataTemplate>

    <DataTemplate x:Key="Template3">
        <Grid>
            <TextBlock Margin="10" Text="{Binding TabHeader}" FontSize="24" 
                Foreground="White" Background="Black" FontWeight="Bold"
                HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Grid>
    </DataTemplate>

    <Style TargetType="ContentControl">
        <Setter Property="ContentTemplateSelector">
            <Setter.Value>
                <local:TabHeaderDataTemplateSelector />
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>
<StackPanel Margin="25">
    <ContentControl Content="{Binding VmObj_1}" />
    <ContentControl Content="{Binding VmObj_2}" />
    <ContentControl Content="{Binding VmObj_3}" />
    <ContentControl Content="{Binding VmObj_4}" />
</StackPanel>

和代码隐藏

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        this.DataContext = new
        {
            VmObj_1 = new VM1 { TabID = "01", TabHeader = "one" },
            VmObj_2 = new VM1 { TabID = "02", TabHeader = "two" },
            VmObj_3 = new VM2 { TabID = "02", TabHeader = "three" },
            VmObj_4 = new VM2 { TabID = "03", TabHeader = "four" },
        };
    }
}

public class VMBase
{
    public string TabID { get; set; }
    public string TabHeader { get; set; }
}

public class VM1 : VMBase { }

public class VM2 : VMBase { }

更新了答案 - v1

您可以通过两种不同的方式解决此问题。每个选项都有自己的优点和缺点;但我最推荐的方法是(正如@ jon-stødle建议的那样)是使用DataTemplateSelector

选项1 - 使用DataTemplateSelector

当您使用基于Type的数据模板时 - 我假设您最有可能使用ContentControl(或变体)来显示动态视图模型驱动的UI。 ContentControl和其他模板化控件(例如LabelUserControlItemsControlListBox等通常具有依赖属性,如ContentTemplateSelector或{{1您可以将模板选择器绑定到。

您可以参考this link以获取ItemTemplateSelectorLabel一起使用的示例;或以下示例用于DataTemplateSelector

XAML:

TabControl

DataTemplateSelector:

<Window.Resources>
    <DataTemplate x:Key="Tab1Template">
        <Grid>
            <Rectangle Stroke="Black" />
            <TextBlock Margin="5" Text="{Binding}" FontSize="18"/>
        </Grid>
    </DataTemplate>

    <DataTemplate x:Key="Tab2Template">
        <Grid>
            <Ellipse Stroke="Green" StrokeThickness="4"/>
            <TextBlock Margin="10" Text="{Binding}" FontSize="24" 
                        Foreground="Red" FontWeight="Bold"
                        HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Grid>
    </DataTemplate>

    <DataTemplate x:Key="Tab3Template">
        <Grid>
            <TextBlock Margin="10" Text="{Binding}" FontSize="24" 
                        Foreground="White" Background="Black" FontWeight="Bold"
                        HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Grid>
    </DataTemplate>

    <Style TargetType="{x:Type TabControl}">
        <Setter Property="ContentTemplateSelector">
            <Setter.Value>
                <local:TabIdDataTemplateSelector />
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>
<TabControl>
    <TabControl.ItemsSource>
        <col:ArrayList>
            <sys:String>1</sys:String>
            <sys:String>2</sys:String>
            <sys:String>3</sys:String>
        </col:ArrayList>
    </TabControl.ItemsSource>
</TabControl>

选项2 - 使用基于public class TabIdDataTemplateSelector : DataTemplateSelector { public override DataTemplate SelectTemplate(object item, DependencyObject container) { var element = container as FrameworkElement; if (element == null) return null; //var vm = item as VMBase; //var id = vm.TabId; string id = item as string; if (id != null) { return element.FindResource($"Tab{id}Template") as DataTemplate; } return null; } } 的数据触发器

另一个选项是在ViewModel的属性(即TabId)上使用数据触发器来更新容器视图的Style(即ContentTemplate)。

TabControl