WPF dynamic binding to property

10,197

Ok, thanks to @GazTheDestroyer comment:

@GazTheDestroyer wrote: I cannot think of any way to dynamically iterate and bind to an arbitrary object's properties in XAML only. You need to write a VM or behaviour to do this so you can watch for change notifications, but do it in a generic way using reflection you can just reuse it throughout your project

I found a solution: editing the ViewModel class InstanceProperty like this

  1. added a PropertyValue property
  2. listen to PropertyChanged event on Instance and when the PropertyName value changed is fired, raise PropertyChanged on PropertyValue
  3. When Instance or PropertyName changes, save a reference to Reflection's PropertyInfo that will be used by PropertyValue to read the value

here is the new, complete, ViewModel class:

public class InstanceProperty : INotifyPropertyChanged
{

    #region Properties and events

    public event PropertyChangedEventHandler PropertyChanged;

    private INotifyPropertyChanged FInstance = null;
    public INotifyPropertyChanged Instance
    {
        get { return this.FInstance; }
        set
        {
            if (this.FInstance != null) this.FInstance.PropertyChanged -= Instance_PropertyChanged;
            this.FInstance = value;
            if (this.FInstance != null) this.FInstance.PropertyChanged += Instance_PropertyChanged;
            this.CheckProperty();
        }
    }

    private string FPropertyName = null;
    public string PropertyName
    {
        get { return this.FPropertyName; }
        set
        {
            this.FPropertyName = value;
            this.CheckProperty();
        }
    }

    private System.Reflection.PropertyInfo Property = null;

    public object PropertyValue
    {
        get { return this.Property?.GetValue(this.Instance, null); }
    }

    #endregion

    #region Private methods

    private void CheckProperty()
    {
        if (this.Instance == null || string.IsNullOrEmpty(this.PropertyName))
        {
            this.Property = null;
        }
        else
        {
            this.Property = this.Instance.GetType().GetProperty(this.PropertyName);
        }
        this.RaisePropertyChanged(nameof(PropertyValue));
    }

    private void Instance_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == this.PropertyName)
        {
            this.RaisePropertyChanged(nameof(PropertyValue));
        }
    }

    private void RaisePropertyChanged(string propertyname)
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
    }

    #endregion

}

and here is the XAML:

<ItemsControl ItemsSource={Binding Path=InstanceProperties}>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Path=PropertyName, Mode=OneWay}"/>
                <TextBlock Text=": "/>
                <TextBlock Text="{Binding Path=PropertyValue, Mode=OneWay}"/>
            </StackPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
Share:
10,197
Formentz
Author by

Formentz

Updated on June 04, 2022

Comments

  • Formentz
    Formentz almost 2 years

    I have an ItemsControl that should display the values of some properties of an object.

    The ItemsSource of the ItemsControl is an object with two properties: Instance and PropertyName.

    What I am trying to do is displaying all the property values of the Instance object, but I do not find a way to set the Path of the binding to the PropertyName value:

    <ItemsControl ItemsSource={Binding Path=InstanceProperties}>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Path=PropertyName, Mode=OneWay}"/>
                    <TextBlock Text=": "/>
                    <TextBlock Text="{Binding Source=??{Binding Path=Instance}??, Path=??PropertyName??, Mode=OneWay}"/>
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    the question marks are the points where I don't know how to create the binding.

    I initially tried with a MultiValueConverter:

    <TextBlock Grid.Column="1" Text="{Binding}">
        <TextBlock.DataContext>
            <MultiBinding Converter="{StaticResource getPropertyValue}">
                <Binding Path="Instance" Mode="OneWay"/>
                <Binding Path="PropertyName" Mode="OneWay"/>
            </MultiBinding>
        </TextBlock.DataContext>
    </TextBlock>
    

    The MultiValueConverter uses Reflection to look through the Instance and returns the value of the property.

    But if the property value changes, this change is not notified and the displayed value remains unchanged.

    I am looking for a way to do it with XAML only, if possible, if not I will have to write a wrapper class to for the items of the ItemsSource collection, and I know how to do it, but, since it will be a recurring task in my project, it will be quite expensive.

    Edit:

    For those who asked, InstanceProperties is a property on the ViewModel which exposes a collection of objects like this:

    public class InstanceProperty : INotifyPropertyChanged
    {
        //[.... INotifyPropertyChanged implementation ....]
    
        public INotifyPropertyChanged Instance { get; set; }
    
        public string PropertyName { get; set; }
    
    }
    

    Obviously the two properties notify theirs value is changing through INotifyPropertyChanged, I don't include the OnPropertyChanged event handling for simplicity.

    The collection is populated with a limited set of properties which I must present to the user, and I can't use a PropertyGrid because I need to filter the properties that I have to show, and these properties must be presented in a graphically richer way.

    Thanks