4

In XAML (specifically on Universal Windows Platform) I can data bind to an indexed property using the property path notation for indexers.

e.g. Given a data source of type Dictionary<string,string> I can bind to an indexed property as follows:

<TextBlock Text="{Binding Dictionary[Property]}"/>

In the above, the TextBlock's DataContext is set to an object with a dictionary property, say the following:

public class DataObject
{
    public Dictionary<string,string> Dictionary { get; } = new Dictionary<string,string> {["Property"]="Value"};
}

My question is, is it possible to bind to the indexed property without using the indexer notation, but instead using the syntax for standard property binding?

i.e.

<TextBlock Text="{Binding Dictionary.Property}"/>

From my initial tests this doesn't seem to work. Is there an easy way to make it work? I want to use a data source object with an indexed property (like the Dictionary in this case but could just be a simple object). I don't want to use dynamic objects.

stuart
  • 133
  • 5
  • More code would help. How is the `Dictionary` property defined, what is `Property`, how is the `DataContext` set up? – Petter Hesselberg Dec 11 '16 at 13:39
  • Sorry the names above were intended to be placeholders. So in the binding above, the DataContext will be an object containing the property of type Dictionary named "Dictionary". "Property" is any index key into that dictionary. – stuart Dec 11 '16 at 23:15
  • Posting actual and complete code will greatly improve your chances for help. :-) – Petter Hesselberg Dec 11 '16 at 23:18
  • Can you edit your post into a complete example of your problem? (I'm in Europe and off to bed, but a) others might run across this question, and b) tomorrow is another day ) – Petter Hesselberg Dec 11 '16 at 23:26

3 Answers3

1

There is no syntax that does what you want, I'm afraid. The syntax for standard property binding works for standard properties, e.g.,

<TextBlock Text="{Binding Dictionary.Count}" />

...will bind to the Count property of the dictionary (or whatever object). You need them brackets...

EDIT

If you really hate the brackets, the closest thing I can find would be to use a converter with a parameter. For example:

public class MyConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        IDictionary<string, string> dictionary = value as IDictionary<string, string>;
        string dictionaryValue;
        if (dictionary != null && dictionary.TryGetValue(parameter as string, out dictionaryValue))
        {
            return dictionaryValue;
        }

        return value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotSupportedException();
    }
}

XAML:

<Page
    x:Class="UWP.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:uwp="using:UWP">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.Resources>
            <uwp:MyConverter x:Key="MyConverter" />
        </Grid.Resources>
        <Grid.DataContext>
            <uwp:DataObject />
        </Grid.DataContext>
        <TextBlock
            Text="{Binding Dictionary, ConverterParameter=Property1, Converter={StaticResource MyConverter}}" />
    </Grid>
</Page>

...which is a roundabout way of ending up with something harder to read than the brackets.

Petter Hesselberg
  • 5,062
  • 2
  • 24
  • 42
  • OK thanks, I thought that would be the case. I guess it wouldn't make sense to be able to bind to indexer properties and standard properties with the same syntax since there is the possibility of having the same property name for both. It would have be nice though to be able to mark a dictionary in some way so that it could be used like an object with dynamic properties. The syntax for standard properties is simpler. I'm working on a developer platform, so this became important. I guess I could hack the data binding system implemented in Xamarin.Forms and see if I could make this work. – stuart Dec 13 '16 at 10:39
  • Um, yes. I have an idea I'll check out; I'll get back to you. – Petter Hesselberg Dec 13 '16 at 10:58
  • 1
    I updated my answer with an alternative approach -- I don't think it's any improvement on the brackets, mind you. But please accept the answer if this was in any way useful to you. – Petter Hesselberg Dec 13 '16 at 11:08
  • Thanks for your suggestion. It doesn't really help my particular case though because the I'm trying to expose a simple/elegant binding syntax for developers. My platform defines a set of objects that can be referenced as a source of databinding, As far as the developer is concerned they can assume these are standard objects with standard properties. However the underlying implementation relies on indexer properties. Ideally I would be able to hide this from the user. I'll just go with the square brackets for now. – stuart Dec 13 '16 at 20:43
  • Pity this isn't WPF -- a `MarkupExtension` would've done the trick nicely. Unfortunately, UWP doesn't allow custom markup extensions... – Petter Hesselberg Dec 14 '16 at 10:37
  • Do you know how to specify the dictionary approach `` in the .cs codebehind? – Zach Smith Jul 24 '17 at 09:55
0

A long time ago there was this ObservableDictionary class in Windows 8 app templates that does what you want. It's not a Dictionary<string, string>, but you can copy values from your Dictionary into the ObservableDictionary

Usage

Declare an ObservableDictionary property in your ViewModel, then bind to its indexer like usual properties:

using System;
using System.Collections.Generic;
using System.Linq;
using Windows.Foundation.Collections;

namespace MyApp
{
    public class ObservableDictionary : IObservableMap<string, object>
    {
        private class ObservableDictionaryChangedEventArgs : IMapChangedEventArgs<string>
        {
            public ObservableDictionaryChangedEventArgs(CollectionChange change, string key)
            {
                this.CollectionChange = change;
                this.Key = key;
            }

            public CollectionChange CollectionChange { get; private set; }
            public string Key { get; private set; }
        }

        private Dictionary<string, object> _dictionary = new Dictionary<string, object>();
        public event MapChangedEventHandler<string, object> MapChanged;

        private void InvokeMapChanged(CollectionChange change, string key)
        {
            var eventHandler = MapChanged;
            if (eventHandler != null)
            {
                eventHandler(this, new ObservableDictionaryChangedEventArgs(change, key));
            }
        }

        public void Add(string key, object value)
        {
            this._dictionary.Add(key, value);
            this.InvokeMapChanged(CollectionChange.ItemInserted, key);
        }

        public void Add(KeyValuePair<string, object> item)
        {
            this.Add(item.Key, item.Value);
        }

        public bool Remove(string key)
        {
            if (this._dictionary.Remove(key))
            {
                this.InvokeMapChanged(CollectionChange.ItemRemoved, key);
                return true;
            }
            return false;
        }

        public bool Remove(KeyValuePair<string, object> item)
        {
            object currentValue;
            if (this._dictionary.TryGetValue(item.Key, out currentValue) &&
                Object.Equals(item.Value, currentValue) && this._dictionary.Remove(item.Key))
            {
                this.InvokeMapChanged(CollectionChange.ItemRemoved, item.Key);
                return true;
            }
            return false;
        }

        public virtual object this[string key]
        {
            get
            {
                return this._dictionary[key];
            }
            set
            {
                this._dictionary[key] = value;
                this.InvokeMapChanged(CollectionChange.ItemChanged, key);
            }
        }

        public void Clear()
        {
            var priorKeys = this._dictionary.Keys.ToArray();
            this._dictionary.Clear();
            foreach (var key in priorKeys)
            {
                this.InvokeMapChanged(CollectionChange.ItemRemoved, key);
            }
        }

        public ICollection<string> Keys
        {
            get { return this._dictionary.Keys; }
        }

        public bool ContainsKey(string key)
        {
            return this._dictionary.ContainsKey(key);
        }

        public bool TryGetValue(string key, out object value)
        {
            return this._dictionary.TryGetValue(key, out value);
        }

        public ICollection<object> Values
        {
            get { return this._dictionary.Values; }
        }

        public bool Contains(KeyValuePair<string, object> item)
        {
            return this._dictionary.Contains(item);
        }

        public int Count
        {
            get { return this._dictionary.Count; }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
        {
            return this._dictionary.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this._dictionary.GetEnumerator();
        }

        public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
        {
            int arraySize = array.Length;
            foreach (var pair in this._dictionary)
            {
                if (arrayIndex >= arraySize) break;
                array[arrayIndex++] = pair;
            }
        }
    }
}
Artemious
  • 1,980
  • 1
  • 20
  • 31
0
class DictionaryXamlWrapper : DynamicObject, INotifyPropertyChanged
{
    #region PropertyChangedFunctional
    public void OnPropertyChanged([CallerMemberName] string aProp = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(aProp));
    }

    public event PropertyChangedEventHandler PropertyChanged;
    #endregion

    Dictionary<string, object> members = new Dictionary<string, object>();

    // установка свойства
    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        members[binder.Name] = value;
        OnPropertyChanged(binder.Name);
        return true;
    }
    // получение свойства
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = null;
        if (members.ContainsKey(binder.Name))
        {
            result = members[binder.Name];
            return true;
        }

        return false;
    }
}

Of course, you can delete INotifyPropertyChanged functional if you do not need it.

And usage as you wanted

<TextBlock Text="{Binding DictionaryXamlWrapper.TestField}"/>