Specifying DataTemplate.DataType with a custom type extension
Asked Answered
D

4

9

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 serviceProvider ) {
        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?

Demirep answered 13/12, 2009 at 20:11 Comment(0)
M
11

I really got into your question, and here is what I found.

Q: Why only those three (String, TypeExtension, and StaticExtension) 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 alt text ). In any case, hope I could help :).

Cheers

Mehalek answered 15/12, 2009 at 17:22 Comment(3)
Thanks. I realize it could be dangerous but I would think it would be possible to validate the type of value assigned to DataType was, in fact, a System.Type. I'm not trying to assign any arbitrary value to DataType, just a Type. Interesting about the BAML parser. As for the boxing issue, that's a good point, although in the case I've got I will be explicitly providing a Nullable<T> rather than a boxed value and have other scaffolding in place to make that "do the right thing" for my needs. Looks like I'll have to continue with a workaround. Thanks for the help.Demirep
Ugh, I always forget that comments don't keep extra newlines here. Sorry the above is formatting so icky.Demirep
Things have obviously changed since this was written, and while it looks like DataType="{x:Type TypeName=System:Nullable`1[[System.Int32]]}" should work, in August 2013, under .NET 4.0, it doesn't. The only way I've found to work around this is to use some kind of MarkupExtension.Actor
C
2

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.

Carrycarryall answered 2/3, 2010 at 0:5 Comment(0)
D
1

this should work...

<DataTemplate DataType="{x:Type System:Nullable`1[System.Int32]}"> 
</DataTemplate> 
Deflation answered 28/6, 2012 at 13:9 Comment(1)
To write a better answer, you should provide some context with the solution (why does it work? what sources did you use?)Toothlike
P
1

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>
Peyote answered 10/11, 2015 at 22:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.