我有一个传统的表单布局,顶部有一个菜单栏,底部有一个状态栏。当用户选择一个菜单项时,中间的空间(表单的整个剩余客户区)将被用户控件替换 - 想象一个可以托管多种类型文档的 SDI 应用程序。
如果您知道解决此问题的更好方法,请插话。目前,我正在尝试让它在带有 ContentControl 的非常简化的版本中工作,但我无法让它在以下情况下更新屏幕它的 DataContext 已设置。
下面是 ViewModelA 的非常简单的代码。 ViewModelB 除了 B 之外是相同的。
namespace Dynamic_ContentControl
{
public class ViewModelA: ViewModelBase
{
public ViewModelA()
{
DisplayName = "This is A";
}
}
}
主窗口非常简单。它基本上声明了一个属性来保存托管控件的 View 模型,并公开了两个命令来分配 View 模型 A 或 B。
namespace Dynamic_ContentControl
{
public class MainViewModel: ViewModelBase
{
private ViewModelBase clientContent = null;
public ICommand ShowA { get; private set; }
public ICommand ShowB { get; private set; }
public ViewModelBase ClientContent {
get
{
return clientContent;
}
private set
{
clientContent = value;
OnPropertyChanged("ClientContent");
}
}
public MainViewModel()
{
ShowA = new RelayCommand((obj) =>
{
ClientContent = new ViewModelA();
});
ShowB = new RelayCommand((obj) =>
{
ClientContent = new ViewModelB();
});
}
}
}
最后,XAML 声明一个 ContentControl 并将其 ContentTemplate 设置为一个名为 ClientAreaTemplate 的 DataTemplate,其 ContentPresenter 指向另一个名为 TextBlockLayout 的 DataTemplate:
<Window x:Class="Dynamic_ContentControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Dynamic_ContentControl"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="TextBlockLayout">
<TextBlock Text="{Binding Path=DisplayName}" />
</DataTemplate>
<DataTemplate x:Key="ButtonLayout">
<Button Content="{Binding Path=DisplayName}" />
</DataTemplate>
<DataTemplate x:Key="CheckBoxLayout">
<CheckBox Content="{Binding Path=DisplayName}" />
</DataTemplate>
<DataTemplate x:Key="ClientAreaTemplate">
<ContentPresenter x:Name="ContentArea" ContentTemplate="{StaticResource ResourceKey=TextBlockLayout}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=DataContext}"
Value="{x:Type vm:ViewModelB}">
<Setter TargetName="ContentArea"
Property="ContentTemplate"
Value="{StaticResource ResourceKey=ButtonLayout}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=DataContext}"
Value="{x:Type vm:ViewModelB}">
<Setter TargetName="ContentArea"
Property="ContentTemplate"
Value="{StaticResource ResourceKey=CheckBoxLayout}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<Grid>
<Button Content="Show A"
Command="{Binding Path=ShowA}"
HorizontalAlignment="Left"
Margin="10,10,0,0"
VerticalAlignment="Top"
Width="75" />
<Button Content="Show B"
Command="{Binding ShowB}"
HorizontalAlignment="Left"
Margin="90,10,0,0"
VerticalAlignment="Top"
Width="75" />
<Label Content="{Binding Path=ClientContent.DisplayName}"
HorizontalAlignment="Left"
Margin="170,8,0,0"
VerticalAlignment="Top" />
<ContentControl DataContext="{Binding Path=ClientContent}"
Content="{Binding}"
ContentTemplate="{StaticResource ResourceKey=ClientAreaTemplate}"
HorizontalAlignment="Left"
Margin="10,37,0,0"
VerticalAlignment="Top"
Height="198"
Width="211" />
</Grid>
</Window>
预期行为
当屏幕打开时,我希望显示 TextBoxLayout。如果用户随后单击两个按钮之一,它应该加载 ButtonLayout 或 CheckBoxLayout,具体取决于分配的 View 模型的实际运行时类型。
实际行为
屏幕打开时加载了 TextBoxLayout,但当我单击按钮时它永远不会更改为另一种类型。
我认为问题在于 DataTrigger 尝试与类型进行比较的方式,但在所有输出窗口中都没有绑定(bind)消息。
最佳答案
在这种情况下,你需要使用DataTemplateSelector
:
Provides a way to choose a
DataTemplate
based on the data object and the data-bound element.
Here
是动态 DataTemplateSelector
的一个版本,它根据类型返回所需的 DataTemplate
:
/// <summary>
/// Provides a means to specify DataTemplates to be selected from within WPF code
/// </summary>
public class DynamicTemplateSelector : DataTemplateSelector
{
/// <summary>
/// Generic attached property specifying <see cref="Template"/>s
/// used by the <see cref="DynamicTemplateSelector"/>
/// </summary>
/// <remarks>
/// This attached property will allow you to set the templates you wish to be available whenever
/// a control's TemplateSelector is set to an instance of <see cref="DynamicTemplateSelector"/>
/// </remarks>
public static readonly DependencyProperty TemplatesProperty =
DependencyProperty.RegisterAttached("Templates", typeof(TemplateCollection), typeof(DataTemplateSelector),
new FrameworkPropertyMetadata(new TemplateCollection(), FrameworkPropertyMetadataOptions.Inherits));
/// <summary>
/// Gets the value of the <paramref name="element"/>'s attached <see cref="TemplatesProperty"/>
/// </summary>
/// <param name="element">The <see cref="UIElement"/> who's attached template's property you wish to retrieve</param>
/// <returns>The templates used by the givem <paramref name="element"/>
/// when using the <see cref="DynamicTemplateSelector"/></returns>
public static TemplateCollection GetTemplates(UIElement element)
{
return (TemplateCollection)element.GetValue(TemplatesProperty);
}
/// <summary>
/// Sets the value of the <paramref name="element"/>'s attached <see cref="TemplatesProperty"/>
/// </summary>
/// <param name="element">The element to set the property on</param>
/// <param name="collection">The collection of <see cref="Template"/>s to apply to this element</param>
public static void SetTemplates(UIElement element, TemplateCollection collection)
{
element.SetValue(TemplatesProperty, collection);
}
/// <summary>
/// Overriden base method to allow the selection of the correct DataTemplate
/// </summary>
/// <param name="item">The item for which the template should be retrieved</param>
/// <param name="container">The object containing the current item</param>
/// <returns>The <see cref="DataTemplate"/> to use when rendering the <paramref name="item"/></returns>
public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
//This should ensure that the item we are getting is in fact capable of holding our property
//before we attempt to retrieve it.
if (!(container is UIElement))
return base.SelectTemplate(item, container);
//First, we gather all the templates associated with the current control through our dependency property
TemplateCollection templates = GetTemplates(container as UIElement);
if (templates == null || templates.Count == 0)
base.SelectTemplate(item, container);
//Then we go through them checking if any of them match our criteria
foreach (var template in templates)
//In this case, we are checking whether the type of the item
//is the same as the type supported by our DataTemplate
if (template.Value.IsInstanceOfType(item))
//And if it is, then we return that DataTemplate
return template.DataTemplate;
//If all else fails, then we go back to using the default DataTemplate
return base.SelectTemplate(item, container);
}
}
/// <summary>
/// Holds a collection of <see cref="Template"/> items
/// for application as a control's DataTemplate.
/// </summary>
public class TemplateCollection : List<Template>
{
}
/// <summary>
/// Provides a link between a value and a <see cref="DataTemplate"/>
/// for the <see cref="DynamicTemplateSelector"/>
/// </summary>
/// <remarks>
/// In this case, our value is a <see cref="System.Type"/> which we are attempting to match
/// to a <see cref="DataTemplate"/>
/// </remarks>
public class Template : DependencyObject
{
/// <summary>
/// Provides the value used to match this <see cref="DataTemplate"/> to an item
/// </summary>
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(Type), typeof(Template));
/// <summary>
/// Provides the <see cref="DataTemplate"/> used to render items matching the <see cref="Value"/>
/// </summary>
public static readonly DependencyProperty DataTemplateProperty =
DependencyProperty.Register("DataTemplate", typeof(DataTemplate), typeof(Template));
/// <summary>
/// Gets or Sets the value used to match this <see cref="DataTemplate"/> to an item
/// </summary>
public Type Value
{ get { return (Type)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } }
/// <summary>
/// Gets or Sets the <see cref="DataTemplate"/> used to render items matching the <see cref="Value"/>
/// </summary>
public DataTemplate DataTemplate
{ get { return (DataTemplate)GetValue(DataTemplateProperty); } set { SetValue(DataTemplateProperty, value); } }
}
使用示例
<local:DynamicTemplateSelector x:Key="MyTemplateSelector" />
<DataTemplate x:Key="StringTemplate">
<TextBlock>
<Run Text="String: " />
<Run Text="{Binding}" />
</TextBlock>
</DataTemplate>
<DataTemplate x:Key="Int32Template">
<TextBlock>
<Run Text="Int32: " />
<Run Text="{Binding}" />
</TextBlock>
</DataTemplate>
<Style x:Key="MyListStyle" TargetType="ListView">
<Setter Property="ItemTemplateSelector" Value="{StaticResource MyTemplateSelector}"/>
<Setter Property="local:DynamicTemplateSelector.Templates">
<Setter.Value>
<local:Templates>
<local:Template Value={x:Type String} DataTemplate={StaticResource StringTemplate}/>
<local:Template Value={x:Type Int32} DataTemplate={StaticResource Int32Template}/>
</local:Templates>
</Setter.Value>
</Setter>
</Style>
关于c# - 在 WPF 的 ContentControl 中托管 ViewModel,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22318576/