Search code examples
c#winformsdata-bindingpropertychanged

In Winform why ALL bound properties are updated when I call PropertyChanged on ONE data source?


I have two buttons and bind their property to two properties of a data object. But every property is updated when I call PropertyChanged of the data object.

public partial class Form1 : Form
{
    private DataClass data = new DataClass();

    public Form1()
    {
        InitializeComponent();
        ButtonA.DataBindings.Add("Text", data, "DataA");
        ButtonB.DataBindings.Add("Text", data, "DataB");
        ButtonB.Click += new EventHandler(OnButtonBClicked);
    }

    private void OnButtonBClicked(object sender, EventArgs e)
    {
        data.DataA += "1";
        data.DataB += "1";
        data.Notify("DataB");
    }
}

public class DataClass : INotifyPropertyChanged
{
    public string DataA { get; set; }
    public string DataB { get; set; }
    public event PropertyChangedEventHandler PropertyChanged;

    public DataClass() {}

    public void Notify(string property_name)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(property_name));
    }
}
  • When I press ButtonB (which means I call PropertyChanged(this, new PropertyChangedEventArgs("DataB"))), both ButtonA and ButtonB show new text.

  • If I call PropertyChanged(this, new PropertyChangedEventArgs("DataA")), both buttons are updated.

  • If I don't change value of DataA / DataB and just call PropertyChanged(this, new PropertyChangedEventArgs("DataB")), still both buttons are updated (can be noticed by breakpoint debugging).

  • If I call PropertyChanged(this, new PropertyChangedEventArgs("QQQ")), then no button is updated.

PropertyChangedEventArgs has a property named propertyName, I thought it's used to specify one property to notify but it doesn't.

In my real code, DataB changes much more frequently than DataA. I don't want to update ButtonA each time DataB is changed, it takes too much time.

Question: why would this happen? When a data source property is changed, how can I only update properties really connected to it?

(All code is .Net Framework 4.7.1 on Windows.)


Solution

  • @Jimi's method works.Simple and effective.I put each property in a shell class and bind data to the shell:

        public class MyProperty<T>: INotifyPropertyChanged
        {
            public T Content { get; set; }
            public event PropertyChangedEventHandler PropertyChanged;
            public MyProperty(T _content)
            {
                Content = _content;
            }
            public void Notify()
            {
                PropertyChanged(this, new PropertyChangedEventArgs("Content"));
            }
        }
        public class DataClass
        {
            public MyProperty<string> DataA = new MyProperty<string>("");
            public MyProperty<string> DataB = new MyProperty<string>("");
            public DataClass() {}
        }
    

    But in this way I must use DataA.Content+="1" instead of DataA+="1" every where.

    I decide to use a base class to create all shells.But my real DataClass must inherit from other class and C# don't support multi-inherit.So I have to use a extension class.

    public class BindHandle<T> : INotifyPropertyChanged
    {
        public T Content { get { return (T)parent.GetType().GetProperty(prop_name).GetValue(parent); } }
        private object parent;
        private string prop_name;
        public event PropertyChangedEventHandler PropertyChanged;
        public BindHandle(object _parent, string _prop_name)
        {
            parent = _parent;
            prop_name = _prop_name;
        }
        public void NotifyChange()
        {
            PropertyChanged(this, new PropertyChangedEventArgs("Content"));
        }
    }
    public interface IBindHandleProvider
    {
        BindHandleProvider provider { get; set; }
    }
    public class BindHandleProvider
    {
        private Dictionary<string, object> handle_map = new Dictionary<string, object>();
        public BindHandle<T> GetBindHandle<T>(object obj,string property_name)
        {
            if (!handle_map.ContainsKey(property_name))
                handle_map.Add(property_name, new BindHandle<T>(obj, property_name));
            return (BindHandle<T>)handle_map[property_name];
        }
        public void NotifyChange<T>(string property_name)
        {
            if (handle_map.ContainsKey(property_name))
                ((BindHandle<T>)handle_map[property_name]).NotifyChange();
        }
    }
    public static class BindHandleProviderExtension
    {
        public static void NotifyChange<T>(this IBindHandleProvider obj, string property_name)
        {
            obj.provider.NotifyChange<T>(property_name);
        }
        public static BindHandle<T> GetBindHandle<T>(this IBindHandleProvider obj, string property_name)
        {
            return obj.provider.GetBindHandle<T>(obj,property_name);
        }
    }
    public class DataClass:IBindHandleProvider
    {
        public BindHandleProvider provider { get; set; } = new BindHandleProvider();
        public string DataA { get; set; } = "";
        public string DataB { get; set; } = "";
        public DataClass(){ }
    }
    

    Then bind it like

    ButtonA.DataBindings.Add("Text", data.GetBindHandle<string>("DataA"), "Content");
    

    And notify like

    data.NotifyChange<string>("DataB");
    

    It's kinda complex but works well.