Search code examples
c#winformsuser-controls

Passing an object between two UserControls and a main Form


So I have one main Form that works as the navigation bar and two UserControls that display some controls.
In UserControlsA I have some fields that require to be filled. With that data I create an Object that contains some information. I require to pass that object to UserControlsB so I can display some data there.

My idea was to make three instances of the object, one in the UserControlsA to get the information required for the object, one in the main form to get a "copy" of the object from UserControlsA, and one in UserControlsB that can get the information from the main Form.

However, this seems redundant and doesn't even work. Here's some code:

Main Form:

public partial class main : Form
{
    public Object object { get; set; }
    public UCA uca;
    public UCB ucb;

    public Form1()
    {
        InitializeComponent();

        uca = new UCA();
        ucb = new UCB();

        panel2.Controls.Add(uca);
        panel2.Controls.Add(ucb);

        ucb.Visible = false;
        uca.Visible = true;

    }

    private void button1_Click(object sender, EventArgs e)
    {
        ucb.Visible = false;
        uca.Visible = true;
    }

    private void button2_Click(object sender, EventArgs e)
    {
        ucb.Visible = true;
        uca.Visible = false;
    }
}

UserControlsA:

public partial class UCA : UserControl
{
    public Object object { get; set; }
    
    public UCA()
    {
        InitializeComponent();
    }

    private void bUsage_Click(object sender, EventArgs e)
    {
        //Data is provided
        object = new Object(data);

        //I use var parent to try and access the object from the main form.
        var parent = Parent as Form1;
        object = parent.object;
    }
 }

        

UsercontrolB:

public partial class UCB : UserControl
{
    public Object object { get; set; }

    public UCB()
    {
        InitializeComponent();
    }

    public void updateData()
    {
        //I try to assign the object from the main form to this form's object.
        var parent = Parent as Form1;
        object = parent.object;
    }
}

Using var Parent doesn't work. What can I do?


Solution

  • A couple of examples using the INotifyPropertyChanged Interface and an implementation that makes use of standard public events.

    Related Documentation:
    Windows Forms Data Binding
    Change Notification in Windows Forms Data Binding
    Interfaces Related to Data Binding

    Using INotifyPropertyChanged:
    The UserControl exposes a public Property (here, named CustomDataObject, simple string Type in the first example, object in the second. It can be another Type of course).
    The Property is decorated with the Bindable attribute. The BindingDirection here is more a description of the intent, there's no Template attached to it.
    Two other standard Attributes are added:

    • DefaultValue defines the default value of a Property (the value assigned to the Property when the Control is created). It's used by the Code Generator to determine whether the current value should be serialized: it's not serialized if it matches the value set by the Attribute.
      It's also used by the PropertyGrid to show, in bold, a non-default value selection or assignment.
    • DesignerSerializationVisibility specifies the how the Property should be serialized at design-time. Here, is set to DesignerSerializationVisibility.Visible, to signify that the Property should be serialized.

    The INotifyPropertyChanged Interface can be seen as a simplified way to add Property bindings to more than one property, using the same event handler, to notify a change in value.
    The default implementation of the Interface simply requires that a public Event of type PropertyChangedEventHandler is added to the class.
    When a Property value is changed, the setter just invokes the Event. There are slightly different ways to perform this action; here I'm using a OnPropertyChanged() method that uses the CallerMemberName Attribute to acquire the name of the Property that calls it. It's fairly common in both WinForms and WPF.


    UCA UserControl:
    The UserControl (see the visual example), has two Buttons that change the bound CustomDataObject Property value. Their Click action is handled by ButtonsAction_Click.

    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Windows.Forms;
    
    public partial class UCA : UserControl, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private string m_DataObject = string.Empty;
    
        public UCA() => InitializeComponent();
    
        [Bindable(true, BindingDirection.TwoWay), DefaultValue("")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public string CustomDataObject {
            get => m_DataObject;
            set {
                if (m_DataObject != value){
                    m_DataObject = value;
                    OnPropertyChanged();
                }
            }
        }
    
        private void OnPropertyChanged([CallerMemberName] string propertyName = "") =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    
        private void ButtonsAction_Click(object sender, EventArgs e)
        {
            var btn = sender as Button;
            CustomDataObject = (btn == SomeButton) ? txtInput1.Text : txtInput2.Text;
        }
    }
    

    UCB UserControl:
    This other UserControl is the receiver. It just exposes a public Property (ReceiverDataObject) that will be bound to the CustomDataObject Property of UCA.

    The ReceiverDataObject property is also defined as [Bindable], with the intention of making it one-way only. The property doesn't raise any event. It receive a value, stores it in a private Field and sets an internal UI element.

    public partial class UCB : UserControl
    {
        private string m_RecvDataObject = string.Empty;
    
        public UCB() => InitializeComponent();
    
        [Bindable(true, BindingDirection.OneWay)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public string ReceiverDataObject {
            get => m_RecvDataObject;
            set {
                m_RecvDataObject = value;
                txtPresenter.Text = m_RecvDataObject;
            }
        }
    }
    

    Using Standard Events notifications:
    You can also generate Property change notifications using standard Events.
    The difference is that you need an Event for each Property that should notify changes.
    If you already have Event delegates used for this, then it's probably a good choice, since there's very few to add: just call the protected method that raises the Event in the Property setter.

    Here, I'm, using the common .NET Event handling, using the EventHandlerList defined by the underlying Component class and exposed by its Events property, to add remove event subscriptions.
    The Events are usually raised calling a protected method that has the same name of the Event, except the On prefix.
    Here, CustomDataObjectChanged Event => OnCustomDataObjectChanged() method.
    You can see this pattern in all standard Controls.

    ▶ The CustomDataObjectChanged name assigned to the Event is not a choice: this event must have the same name of the Property and the Changed suffix.
    This is the pattern, it's enough to just follow it.

    UCA UserControl:

    public partial class UCA : UserControl
    {
        private static readonly object Event_CustomDataObjectChanged = new object();
        private object m_DataObject = null;
    
        public UCButtonActions() => InitializeComponent();
    
        [Bindable(BindableSupport.Yes, BindingDirection.TwoWay), DefaultValue(null)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public object CustomDataObject {
            get => m_DataObject;
            set {
                if (m_DataObject != value){
                    m_DataObject = value;
                    OnCustomDataObjectChanged(EventArgs.Empty);
                }
            }
        }
    
        public event EventHandler CustomDataObjectChanged {
            add {
                Events.AddHandler(Event_CustomDataObjectChanged, value);
            }
            remove {
                Events.RemoveHandler(Event_CustomDataObjectChanged, value);
            }
        }
    
        protected virtual void OnCustomDataObjectChanged(EventArgs e)
        {
            if (Events[Event_CustomDataObjectChanged] is EventHandler evth) evth(this, e);
        }
    }  
    

    UCB UserControl:
    The second UserControl doesn't change. It's just the receiver.


    The Form class (or another class used as Handler):

    In the Form Constructor, or any other method called after the Form initialization, use the DataBindings property of UCB to link the Properties of the two UserControls:

    public SomeForm()
    {
        InitializeComponent();
        ucb1.DataBindings.Add("ReceiverDataObject", uca1, "CustomDataObject", 
            false, DataSourceUpdateMode.OnPropertyChanged);
    }
    

    You can also use a BindingSource to mediate:

    BindingSource ucsSource = null;
    
    public SomeForm()
    {
        InitializeComponent();
        ucsSource = new BindingSource(uca1, null);
        ucb1.DataBindings.Add("ReceiverDataObject", ucsSource, "CustomDataObject", 
            false, DataSourceUpdateMode.OnPropertyChanged);
    }
    

    Sample functionality:

    UserControls TwoWay DataBinding