XAML markup binding to dictionary with key of type Type
Asked Answered
D

2

7

I'm trying to bind to a Dictionary<Type,string> through xaml.

The problem is, the indexer [] in the Binding markup extension interprets it's content as string. Is there some kind of 'unescape-sequence' for that case?

<TextBox Text="{Binding theDictionary[{x:Type ns:OnePrettyType}]}" />

(Binding doesn't work because {x:Type ns:OnePrettyType} is being sent as string)

Dulciedulcify answered 21/2, 2012 at 2:52 Comment(1)
Instead of binding to the dictionary and some other value, expose a property on the viewmodel that does the work for you.Dubai
T
12

If the indexer has a specific type the conversion should be done automatically so this should work:

{Binding theDictionary[ns:OnePrettyType]}

If you need an explicit interpretation you can try a "cast" like this:

{Binding theDictionary[(sys:Type)ns:OnePrettyType]}

(Where sys is mapped to the System namespace of course)

That would be the theory but all of that won't work. First of all if you use the Binding constructor that takes a path the cast will be ignored as it uses a certain constructor of PropertyPath in a certain way. Also you will get a binding error:

System.Windows.Data Error: 40 : BindingExpression path error: '[]' property not found on 'object' ''Dictionary`2'

You would need to make it construct the PropertyPath via the type converter by avoiding the Binding constructor:

{Binding Path=theDictionary[(sys:Type)ns:OnePrettyType]}

Now this will most likely just throw an exception:

{"Path indexer parameter has value that cannot be resolved to specified type: 'sys:Type'"}

So there unfortunately is no default type conversion going on. You could then construct a PropertyPath in XAML and make sure a type is passed in, but the class is not meant to be used in XAML and will throw an exception if you try, also very unfortunate.

One workaround would be to create a markup extension which does the construction, e.g.

[ContentProperty("Parameters")]
public class PathConstructor : MarkupExtension
{
    public string Path { get; set; }
    public IList Parameters { get; set; }

    public PathConstructor()
    {
        Parameters = new List<object>();
    }
    public PathConstructor(string path, object p0)
    {
        Path = path;
        Parameters = new[] { p0 };
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return new PropertyPath(Path, Parameters.Cast<object>().ToArray());
    }
}

Which then can be used like this:

<Binding>
    <Binding.Path>
        <me:PathConstructor Path="theDictionary[(0)]">
            <x:Type TypeName="ns:OnePrettyType" />
        </me:PathConstructor>
    </Binding.Path>
</Binding>

or like this

{Binding Path={me:PathConstructor theDictionary[(0)], {x:Type ns:OnePrettyType}}}
Toenail answered 21/2, 2012 at 2:55 Comment(7)
thanks, H.B. - you amaze me. it seems like it's only been a few months since you passed 10k and now you're approaching 40k. Thanks for all these quality answers you provide. this looks good, I'll probably mark it answered once I try it out.Responsibility
holy cow, look at what i've done. I always thought 'Binding is not extendable'. This feels close to it.Responsibility
@MarkusHütter: I spend way too much time on this site. I didn't know you you could have multiple constructor parameters in a markup extension, thanks for that, that is interesting way to inline it. I tried using params earlier but unfortunately the compliner does not get it, would have been perfect. Don't know about passing in the binding though, i think it makes it a bit harder to read, i would just stick to usng it in the Path like {Binding Path={me:ParameterPath [(0)], ...}}Toenail
I think you're right about readablity, I'll probably go with your soloution with the added constructor with parametersResponsibility
Wouldn't have made more sense to just expose a property on the view-model? i.e. public OnePrettyType TheType { get { return TheDictionary[0]; } } //Error check it as wellDubai
@m-y: I don't know, if you were to create such a property for every binding to the dictionary you will soon clutter up the view model with all those junk properties.Toenail
@m-y H.B. is right. As you might guess from my own answer I've written a SettingsService that gathers usersettings of different modules to be accessable through types with MEF. A particular View might access quite some of these properties and so exposing them all through the viewmodel would definately clutter things upResponsibility
D
3

update: I leave this up for reference for extending Bindings

<Grid Width="{my:ParameterBinding {Binding [(0)],Source={x:Static my:SettingsService.Current}, Mode=TwoWay},{x:Type my:LeftPanelWidthSetting}}"/>

and that's the code behind

[ContentProperty( "Parameters" )]
public class ParameterBinding : MarkupExtension
{
    public Binding Binding { get; set; }
    public IList Parameters { get; set; }

    public ParameterBinding()
    {
        Parameters = new List<object>(); 
    }

    public ParameterBinding( Binding b, object p0 )
    {
        Binding = b;
        Parameters = new []{p0};
    }

    public override object ProvideValue( IServiceProvider serviceProvider )
    {
        Binding.Path = new PropertyPath( Binding.Path.Path, Parameters.Cast<object>().ToArray() );
        return Binding.ProvideValue(serviceProvider);
    }
}

this would be extendable to support more parameters in inline syntax with additional constructors. I still left in the ability to add many parameters with the extended element syntax.

thx to H.B. for inspiring this

Dulciedulcify answered 21/2, 2012 at 12:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.