How can a property be bound to a type
I have two HeaderedContentControl
s like those below that each have their content property bound to one of two view model properties of the same base type (one control is on the left side of the window and one on the right, thus the view model property names).
However, either view model property can be one of four different derived types. So the left could be an Airplane
and the right can be a Car
. Then later, the left could be a Boat
and right could be an Airplane
. I would like the Style
property of the header controls to be dynamic based on the derived type. What's the best way to do this declaratively?
<Window...>
<StackPanel
Grid.Row="2"
Orientation="Horizontal" VerticalAlignment="Top">
<Border
Height="380"
Width="330"
Margin="0,0,4,0"
Style="{StaticResource MainBorderStyle}">
<HeaderedContentControl
Content="{Binding Path=LeftChild}"
Header="{Binding LeftChild.DisplayName}"
Style="{StaticResource StandardHeaderStyle}"
/>
</Border>
<Border
Height="380"
Width="330"
Style="{StaticResource开发者_运维百科 MainBorderStyle}">
<HeaderedContentControl
Content="{Binding Path=RightChild}"
Header="{Binding RightChild.DisplayName}"
Style="{StaticResource StandardHeaderStyle}"
/>
</Border>
</StackPanel>
</Window>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:myViewModelNamespace;assembly=myViewModelAssembly"
xmlns:vw="clr-namespace:myViewNamespace" >
<!--***** Item Data Templates ****-->
<DataTemplate DataType="{x:Type vm:CarViewModel}">
<vw:CarView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:BoatViewModel}">
<vw:BoatView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:AirplaneViewModel}">
<vw:AirplaneView />
</DataTemplate>
<!--*****
Other stuff including the StandardHeaderStyle and the MainBorderStyle
****-->
</ResourceDictionary>
Are you sure you need to vary HeaderedContentControl's Style, not the ContentTemplate basing on Content's dynamic type? In other words: do you need to vary the control's style or you just need to vary the item's data-template?
Because there is very handy property ContentTemplateSelector and if you'll write very simple class you'll be able to select the DataTemplate basing on content's dynamic type.
If that's not the case and you are sure you need to vary the Style, then could you please elaborate a little which parts of the style you'd like to vary - maybe there a workaround through the same ContentTemplateSelector is available.
In case you insist on varying the styles, think a little about using data trigger inside your style - using a very simple converter you'll be able to vary certain properties (or all of them if you prefer) of your style.
I'll be glad to provide you further assistance as soon as you'll elaborate the specifics of your problem.
UPD: OK, author insists that he need to vary the Style. Here are two possible ways of how you can do that.
First and simple solution, but severely limited one: since your Header
content can be specified through Content
content you can do this:
<DataTemplate x:Key="DefaultTemplate">
<HeaderedContentControl Content="{Binding}"
Header="{Binding DisplayName}"
Style="{StaticResource DefaultStyle}" />
</DataTemplate>
<DataTemplate x:Key="CarTemplate"
DataType="dm:Car">
<HeaderedContentControl Content="{Binding}"
Header="{Binding DisplayName}"
Style="{StaticResource CarStyle}" />
</DataTemplate>
<DataTemplate x:Key="BoatTemplate"
DataType="dm:Boat">
<HeaderedContentControl Content="{Binding}"
Header="{Binding DisplayName}"
Style="{StaticResource BoatStyle}" />
</DataTemplate>
<u:TypeBasedDataTemplateSelector x:Key="MySelector"
DefaultTemplate="{StaticResource DefaultTemplate}"
NullTemplate="{StaticResource DefaultTemplate}">
<u:TypeMapping Type="dm:Car" Template="{StaticResource CarTemplate}" />
<u:TypeMapping Type="dm:Boat" Template="{StaticResource BoatTemplate}" />
</u:TypeBasedDataTemplateSelector>
<ContentPresenter Content="{Binding LeftChild}"
ContentTemplateSelector="{StaticResource MySelector}" />
The only code you'll need to back this purely declarative solution is a very simple template selector implementation. Here it goes:
public class TypeMapping
{
public Type Type { get; set; }
public DataTemplate Template { get; set; }
}
public class TypeBasedDataTemplateSelector : DataTemplateSelector, IAddChild
{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate NullTemplate { get; set; }
private readonly Dictionary<Type, DataTemplate> Mapping = new Dictionary<Type, DataTemplate>();
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item == null)
return NullTemplate;
DataTemplate template;
if (!Mapping.TryGetValue(item.GetType(), out template))
template = DefaultTemplate;
return template;
}
#region IAddChild Members
public void AddChild(object value)
{
if (!(value is TypeMapping))
throw new Exception("...");
var tm = (TypeMapping)value;
Mapping.Add(tm.Type, tm.Template);
}
public void AddText(string text)
{
throw new NotImplementedException();
}
#endregion
}
The second solution is more generic and can be applied to the cases where Header
content has nothing to do with Content
content. It bases on the Binding's converter capabilities.
<Style x:Key="StandardHeaderedStyle">
<!--...-->
</Style>
<Style x:Key="CarHeaderedStyle"
BasedOn="{StaticResource StandardHeaderedStyle}">
<!--...-->
</Style>
<Style x:Key="BoatHeaderedStyle"
BasedOn="{StaticResource StandardHeaderedStyle}">
<!--...-->
</Style>
<Style x:Key="UnknownHeaderedStyle"
BasedOn="{StaticResource StandardHeaderedStyle}">
<!--...-->
</Style>
<u:StylesMap x:Key="MyStylesMap"
FallbackStyle="{StaticResource UnknownHeaderedStyle}">
<u:StyleMapping Type="Car" Style="{StaticResource CarHeaderedStyle}" />
<u:StyleMapping Type="Boat" Style="{StaticResource BoatHeaderedStyle}" />
</u:StylesMap>
<u:StyleSelectorConverter x:Key="StyleSelectorConverter" />
<HeaderedContentControl Content="{Binding LeftChild}"
Header="{Binding LeftChild.DisplayName}">
<HeaderedContentControl.Style>
<Binding Path="LeftChild"
Converter="{StaticResource StyleSelectorConverter}"
ConverterParameter="{StaticResource MyStylesMap}" />
</HeaderedContentControl.Style>
</HeaderedContentControl>
It also requires some of backing code:
public class StyleMapping
{
public Type Type { get; set; }
public Style Style { get; set; }
}
public class StylesMap : Dictionary<Type, Style>, IAddChild
{
public Style FallbackStyle { get; set; }
#region IAddChild Members
public void AddChild(object value)
{
if (!(value is StyleMapping))
throw new InvalidOperationException("...");
var m = (StyleMapping)value;
this.Add(m.Type, m.Style);
}
public void AddText(string text)
{
throw new NotImplementedException();
}
#endregion
}
public class StyleSelectorConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var m = (StylesMap)parameter;
if (value == null)
return m.FallbackStyle;
Style style;
if (!m.TryGetValue(value.GetType(), out style))
style = m.FallbackStyle;
return style;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
HTH
My answer is an elaboration on Archimed's. Don't hesitate to ask further!
<Window x:Class="Datatemplate_selector.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
xmlns:local="clr-namespace:Datatemplate_selector">
<Window.Resources>
<DataTemplate DataType="{x:Type local:CarDetail}">
<Border BorderBrush="Yellow" BorderThickness="2">
<HeaderedContentControl Margin="4" Foreground="Red">
<HeaderedContentControl.Header>
<Border BorderBrush="Aquamarine" BorderThickness="3">
<TextBlock Text="{Binding Name}"/>
</Border>
</HeaderedContentControl.Header>
<HeaderedContentControl.Content>
<Border BorderBrush="CadetBlue" BorderThickness="1">
<TextBlock TextWrapping="Wrap" Text="{Binding Description}"/>
</Border>
</HeaderedContentControl.Content>
</HeaderedContentControl>
</Border>
</DataTemplate>
<DataTemplate DataType="{x:Type local:HouseDetail}">
<HeaderedContentControl Margin="4" Foreground="Yellow" FontSize="20"
Header="{Binding Name}">
<HeaderedContentControl.Content>
<TextBlock Foreground="BurlyWood" TextWrapping="Wrap"
Text="{Binding Description}"/>
</HeaderedContentControl.Content>
</HeaderedContentControl>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ItemDetail}">
<HeaderedContentControl Margin="4" Foreground="Green" FontStyle="Italic"
Content="{Binding Description}"
Header="{Binding Name}">
</HeaderedContentControl>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ItemsControl ItemsSource="{Binding ItemDetails}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="2"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
using System.Collections.ObjectModel;
using System.Windows;
namespace Datatemplate_selector
{
public partial class Window1 : Window
{
public ObservableCollection<ItemDetail> ItemDetails { get; set; }
public Window1()
{
ItemDetails = new ObservableCollection<ItemDetail>
{
new CarDetail{Name="Trabant"},
new HouseDetail{Name="Taj Mahal"}
};
DataContext = this;
InitializeComponent();
}
}
public class ItemDetail:DependencyObject
{
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name",
typeof(string),
typeof(ItemDetail),
new UIPropertyMetadata(string.Empty));
public virtual string Description
{
get { return Name + " has a lot of details"; }
}
}
public class CarDetail:ItemDetail
{
public override string Description
{
get { return string.Format("The car {0} has two doors and a max speed of 90 kms/hr", Name); }
}
}
public class HouseDetail:ItemDetail
{
public override string Description
{
get { return string.Format("The house {0} has two doors and a backyard", Name); }
}
}
}
PS: I thought that this use of inheritance in a generic collection was not supported in .Net 3. I am pleasurably surprised that this code works!
try using the Style Selector class:
http://msdn.microsoft.com/en-us/library/system.windows.controls.styleselector.aspx
I haven't used it myself specifically, so i don't have any sample code for you to check out, but the MSDN link has some.
精彩评论