现在我有以下代码:
视图模型(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>
Control1
和Control2
是不同类型的UserControl
:
public class Control1:UserControl
{
}
public class Control2:UserControl
{
}
当我只有VMBase
的两个派生类时,事情仍然可以管理,但如果我有10个怎么办?或者更多?它会变得丑陋。
是否可以将单个VM绑定到不同的视图(用户控件),这样我就不必为VMBase
手动创建这么多派生类了?我只需要指定VM属性,例如TabID
和TabHeader
,正确的视图将作为结果绑定。
编辑:
以下是更多详细信息:我的VM绑定到ContentControl(即:contentcontrol.Content=VM
)。每个VM都有两个属性TabID
和Header
。是否应调用DataTemplateSelector
取决于它是否具有特定的TabID
(如果它具有其他TabID
,则不应调用此DataTemplateSelector),以及哪个DataTemplate(其中的逻辑)要调用的DataTemplateSelector还取决于Header
。如何实现这个?
答案 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;
}
}
}
}
答案 1 :(得分:2)
我认为当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 { }
您可以通过两种不同的方式解决此问题。每个选项都有自己的优点和缺点;但我最推荐的方法是(正如@ jon-stødle建议的那样)是使用DataTemplateSelector
选项1 - 使用DataTemplateSelector
当您使用基于Type
的数据模板时 - 我假设您最有可能使用ContentControl
(或变体)来显示动态视图模型驱动的UI。 ContentControl
和其他模板化控件(例如Label
,UserControl
,ItemsControl
,ListBox
等通常具有依赖属性,如ContentTemplateSelector
或{{1您可以将模板选择器绑定到。
您可以参考this link以获取ItemTemplateSelector
与Label
一起使用的示例;或以下示例用于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