I have this markup extension
public class NullableExtension : TypeExtension
{
public NullableExtension() {
}
public NullableExtension( string type )
: base(type) {
}
public NullableExtension( Type type )
: base(type) {
}
public override object ProvideValue( IServiceProvider servicePro开发者_StackOverflow中文版vider ) {
Type basis = (Type)base.ProvideValue( serviceProvider );
return typeof(Nullable<>).MakeGenericType( basis );
}
}
which is designed to provide a nullable version of some other type. It works as expected when used in "normal" XAML. For example, as in
<SomeControl DataContext="{My:Nullable System:Int32}"/>
(assuming that My is the XML namespace defined for the C# namespace holding the extension, and similarly for System). The data context for the control gets set to a System.Type
for Nullable<int>
as I expect.
However, when I use this extension to try and set the DataType
property of a DataTemplate
like
<DataTemplate DataType="{My:Nullable System:Int32}">
<TextBlock ... />
</DataTemplate>
I am told, by the compiler, that
A key for a dictionary cannot be of type 'System.Windows.Controls.Primitives.TextBlock'. Only String, TypeExtension, and StaticExtension are supported."
and
"No constructor for type 'NullableExtension' has 1 parameters.
Does anybody know why only those three methods (and not even subclasses of TypeExtension
, like mine is) are allowed? What is special about the processing of the XAML at that point? And is there another way to accomplish this (data template selection based on types that may be nullable) without resorting to a DataTemplateSelector
?
I really got into your question, and here is what I found.
Q: Why only those three (
String
,TypeExtension
, andStaticExtension
) are allowed?
A: By design. If you could write any custom markup extension to be used as a key in a dictionary, what side effects would this introduce? Consider you have Binding as a value of DataType... I'm pretty sure you can add dozen issues related to dictionary keys dynamic nature.
Q: What is special about the processing of the XAML at that point?
A. At that point you have BAML creation. The problem comes from internal class BamlRecordWriter
, but the message doesn't describe actual problem. When you specify custom markup extension as DataType, it takes a DataTemplate's child, and checks it if it's assignable from string, TypeExtension or StaticExtension (see BamlRecordWriter.WriteElementStart() function). Indeed. Not your extension (which is assignable to TypeExtension), but first child (which is not assignable). Now you have this strange "cannot be of type" thing. Although it looks like a BamlRecordWriter's bug, I think they left it intentionally. Until it doesn't let you use custom markup extension as a DataType value, who cares about error message?
Q: Is there another way to accomplish this (data template selection based on types that may be nullable) without resorting to a DataTemplateSelector?
A: Yes, kind of. First of all you can have standard TypeExtension do all the dirty job for you:
<DataTemplate DataType="{x:Type TypeName=System:Nullable`1[[System.Int32]]}">
</DataTemplate>
But in most of the cases (if not all the time) you will not see the templating results. Why? Now it comes to boxing rules for nullable types. Boxing a non-null nullable value type boxes the value type itself, not the System.Nullable that wraps the value type. Thus default template selector will look for DataTemplate with DataType of T
not of Nullable<T>
.
I may not understand exact problem you are trying to solve with nullable extension, but you may want to wrap nullables into your own ref type, write one DataTemplate for the wrapper and use DataTemplate.Triggers
, to choose content appearance. Well, this looks like reinvented data template selector :)...
NB: I'm not a MS guy, and my findings are based on Reflector and my own experience (which is not as big as I would like it to be
). In any case, hope I could help :).Cheers
The syntax
DataType="{x:Type TypeName=System:Nullable`1[[System.Int32]]}">
doesn't seem to work for user defined types :(
Actually one other way is to create a base non-generic type. Set first data template to that type and bind ContentPresenter.Content
to the property which holds object of T
. Then create other data templates for whatever T
.
this should work...
<DataTemplate DataType="{x:Type System:Nullable`1[System.Int32]}">
</DataTemplate>
I found a nasty workaround for this. For whatever reason, @Anvaka is right: the DataType property won't allow you to use a custom MarkupExtension. But it WILL allow you to use a StaticResource of the custom MarkupExtension.
Take your MarkupExtension, add a public default constructor to it. Then create an instance of your extension in the resources, setting the properties directly. Boom, it takes it. The below is similar to what you would need to do
<My:Nullable x:Key="Foo" Type="{x:Type System:Int32}"/>
<DataTemplate DataType="{StaticResource Foo}">
<TextBlock ... />
</DataTemplate>
精彩评论