Pregunta

Say I have a BindingList<Person> where Person has a public string property called Name. Is there a way to effectively (if not directly) bind to the Current property (which is a Person object) and then index into the Name property?

I'm imagining a binding set up like

nameLabel.DataBindings.Add(
    new Binding("Text", this.myBindingListSource, "Current.Name", true));

or

nameLabel.DataBindings.Add(
    new Binding("Text", this.myBindingListSource.Current, "Name", true));

Both of these approaches produce runtime errors.

Currently, I am simply subscribing to the BindingList's CurrentChanged event and handling updates in there. This works but I would prefer the DataBinding approach if possible.

¿Fue útil?

Solución

Using the NestedBindingProxy class provided below, you can do

nameLabel.DataBindings.Add(
new Binding("Text", new NestedBindingProxy(this.myBindingListSource, "Current.Name"), true));

Below is the c# code for NestedBindingProxy. The problem with WinForms data binding is it doesn't detect value changes when you use a navigation path that contains multiple properties. WPF does this though. So I created the class NestedBindingProxy that does the change detection and it exposes a property called "Value" that the windows binding can bind too. Whenever any property changes in the navigation path, the notify property changed event will fire for the "Value" property.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;

namespace WindowsFormsApplication4
{
public sealed class NestedBindingProxy : INotifyPropertyChanged
{
    class PropertyChangeListener
    {
        private readonly PropertyDescriptor _prop;
        private readonly WeakReference _prevOb = new WeakReference(null);

        public event EventHandler ValueChanged;

        public PropertyChangeListener(PropertyDescriptor property)
        {
            _prop = property;
        }

        public object GetValue(object obj)
        {
            return _prop.GetValue(obj);
        }

        public void SubscribeToValueChange(object obj)
        {
            if (_prop.SupportsChangeEvents)
            {
                _prop.AddValueChanged(obj, ValueChanged);
                _prevOb.Target = obj;
            }
        }

        public void UnsubsctribeToValueChange()
        {
            var prevObj = _prevOb.Target;
            if (prevObj != null)
            {
                _prop.RemoveValueChanged(prevObj, ValueChanged);
                _prevOb.Target = null;
            }
        }
    }

    private readonly object _source;
    private PropertyChangedEventHandler _subscribers;
    private readonly List<PropertyChangeListener> _properties = new List<PropertyChangeListener>();
    private readonly SynchronizationContext _synchContext;

    public event PropertyChangedEventHandler PropertyChanged
    {
        add
        {
            bool hadSubscribers = _subscribers != null;
            _subscribers += value;
            bool hasSubscribers = _subscribers != null;
            if (!hadSubscribers && hasSubscribers)
            {
                ListenToPropertyChanges(true);
            }
        }
        remove
        {
            bool hadSubscribers = _subscribers != null;
            _subscribers -= value;
            bool hasSubscribers = _subscribers != null;
            if (hadSubscribers && !hasSubscribers)
            {
                ListenToPropertyChanges(false);
            }
        }
    }

    public NestedBindingProxy(object source, string nestedPropertyPath)
    {
        _synchContext = SynchronizationContext.Current;
        _source = source;
        var propNames = nestedPropertyPath.Split('.');
        Type type = source.GetType();
        foreach (var propName in propNames)
        {
            var prop = TypeDescriptor.GetProperties(type)[propName];
            var propChangeListener = new PropertyChangeListener(prop);
            _properties.Add(propChangeListener);
            propChangeListener.ValueChanged += (sender, e) => OnNestedPropertyChanged(propChangeListener);
            type = prop.PropertyType;
        }
    }

    public object Value
    {
        get
        {
            object value = _source;
            foreach (var prop in _properties)
            {
                value = prop.GetValue(value);
                if (value == null)
                {
                    return null;
                }
            }
            return value;
        }
    }

    private void ListenToPropertyChanges(bool subscribe)
    {
        if (subscribe)
        {
            object value = _source;
            foreach (var prop in _properties)
            {
                prop.SubscribeToValueChange(value);
                value = prop.GetValue(value);
                if (value == null)
                {
                    return;
                }
            }
        }
        else
        {
            foreach (var prop in _properties)
            {
                prop.UnsubsctribeToValueChange();
            }
        }
    }

    private void OnNestedPropertyChanged(PropertyChangeListener changedProperty)
    {
        ListenToPropertyChanges(false);
        ListenToPropertyChanges(true);
        var subscribers = _subscribers;
        if (subscribers != null)
        {
            if (_synchContext != SynchronizationContext.Current)
            {
                _synchContext.Post(delegate { subscribers(this, new PropertyChangedEventArgs("Value")); }, null);
            }
            else
            {
                subscribers(this, new PropertyChangedEventArgs("Value"));
            }
        }
    }
}

}

Otros consejos

Try this:

Binding bind = new Binding("Text", myBindingListSource, "Current");
bind.Format += (s,e) => {
    e.Value = e.Value == null ? "" : ((Person)e.Value).Name;
};
nameLabel.DataBindings.Add(bind);

I haven't tested it but it should work and I've been waiting for the feedback from you.

I stumbled across this by accident (four years after the original post), and after a quick read, I find that the accepted answer appears to be really over-engineered in respect to the OP's question.

I use this approach, and have posted an answer here to (hopefully) help anyone else who has the same problem.

The following should work (if infact this.myBindingListSource implements IBindingList)

I would just create a binding source to wrap my binding list (in my Form/View) BindingSource bindingSource = new BindingSource(this.myBindingListSource, string.Empty);

And then just bind to the binding source like this:
nameLabel.DataBindings.Add(new Binding("Text", bindingSource, "Name"));

I think the OP's original code didn't work because there is no member called 'Current' on a BindingList (unless the OP has some sort of specialised type not mentioned).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top