在两个 UserControl 和一个主窗体之间传递一个对象

Passing an object between two UserControls and a main Form

所以我有一个用作导航栏的主窗体和两个显示一些控件的用户控件。
UserControlsA 中,我有一些字段需要填写。使用该数据,我创建了一个包含一些信息的对象。我需要将该对象传递给 UserControlsB,以便我可以在那里显示一些数据。

我的想法是创建对象的三个实例,一个在 UserControlsA 中以获取对象所需的信息,一个在主窗体中以从 [= 中获取对象的“副本” 13=],UserControlsB中的一个可以从主窗体中获取信息。

然而,这似乎是多余的,甚至不起作用。这是一些代码:

主要形式:

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;
    }
}

使用 var Parent 无效。我能做什么?

所以我刚刚学会了如何正确使用我猜的事件。下面是代码现在的样子:

主要形式:

public partial class main : Form
{
    public UCA uca;
    public UCB ucb;

    public delegate void passObject(object source, someObject u);

    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 Form1_Load(object sender, EventArgs e)
    {
        uca.objectRequired += ucb.ucb_objectRequired;
    }

    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;
    }
    
}

用户控件 A:

public partial class UCA : UserControl
{
    public someObject o { get; set; }
    public event passObject objectRequired;
    
    public UCA()
    {
        InitializeComponent();
    }

    private void bUsage_Click(object sender, EventArgs e)
    {
        //Data is provided
        o = new someObject(data);
        usageRequired?.Invoke(this, o);
    }
 }

用户控件 B:

public partial class UCB : UserControl
{
    public SomeObject o { get; set; }

    public UCDetails()
    {
        InitializeComponent();
        
    }

    public void ucn_objectRequired(object sender, sObject u)
    {
        o = u;
        //Use the data from the object.
    }
}

几个使用 INotifyPropertyChanged 接口的示例和一个使用标准 public 事件的实现。

相关文档:
Windows Forms Data Binding
Change Notification in Windows Forms Data Binding
Interfaces Related to Data Binding

使用INotifyPropertyChanged:
UserControl 公开了一个 public 属性(这里,命名为 CustomDataObject,第一个示例中简单的 string 类型,第二个示例中的 object。它可以是另一种类型当然)。
属性 用 Bindable 属性修饰。 BindingDirection 这里更多的是 意图描述 ,没有附加模板。
添加了另外两个标准属性:

  • DefaultValue定义了一个属性的默认值(创建Control时赋给属性的值)。代码生成器使用它来确定当前值是否应该被序列化:如果它与属性设置的值匹配,则不会被序列化。
    属性网格也使用它以粗体显示非默认值选择或分配。
  • DesignerSerializationVisibility specifies the how the Property should be serialized at design-time. Here, is set to DesignerSerializationVisibility.Visible,表示 属性 应该被序列化。

INotifyPropertyChanged 接口可以看作是一种简化的方式,可以使用相同的事件处理程序将 属性 绑定添加到多个 属性,以通知值的更改。
接口的默认实现只需要将类型为 PropertyChangedEventHandler 的 public 事件添加到 class.
当 属性 值更改时,setter 仅调用事件。执行此操作的方法略有不同;在这里,我使用 OnPropertyChanged() 方法,该方法使用 CallerMemberName 属性来 获取 调用它的 属性 的名称。这在 WinForms 和 WPF 中都很常见。


UCA 用户控件:
UserControl(参见可视示例)有两个按钮,用于更改绑定的 CustomDataObject 属性 值。他们的 Click 操作由 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 是 receiver。它只是公开了一个 public 属性 (ReceiverDataObject),它将绑定到 UCA 的 CustomDataObject 属性。

ReceiverDataObject 属性 也被定义为 [Bindable]意图 使其仅是单向的。 属性 不会引发任何事件。它接收一个值,将其存储在私有字段中并设置一个内部 UI 元素。

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;
        }
    }
}

使用标准事件通知:
您还可以使用标准事件生成 属性 更改通知。
区别在于您需要为每个 属性 通知更改的事件。
如果您已经为此使用了事件委托,那么它可能是一个不错的选择,因为要添加的东西很少:只需调用引发事件的受保护方法 属性 setter.

在这里,我使用常见的 .Net 事件处理,使用 EventHandlerList defined by the underlying Component class and exposed by its Events 属性 添加删除事件订阅。
通常引发事件调用具有相同名称的事件的受保护方法,除了 On 前缀。
这里,CustomDataObjectChanged 事件 => OnCustomDataObjectChanged() 方法。
您可以在所有标准控件中看到此模式。

▶ 分配给事件的 CustomDataObjectChanged 名称不可选择:此事件必须与 属性 和 Changed后缀.
就是这个规律,照着做就行了

UCA 用户控件:

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 没有改变。只是接收器。


表单class(或用作处理程序的另一个class):

在Form Constructor中,或者在Form初始化之后调用的任何其他方法,使用UCB的DataBindings 属性来link两个UserControls的Properties:

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

你也可以使用 BindingSource 来调解:

BindingSource ucsSource = null;

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

示例功能:

也许您应该重新设计数据流。 UserControl 通常不应该假设它的父级是什么,这就是为什么它是一个“自定义控件”。它可以是 Form1 但不是必需的。所以你不应该像你的例子那样进行转换。

要将信息从 A 提供给 B,一种方法是为这些控件创建 public Get/Set 方法或属性。主窗体与那些 public 成员一起工作,伪代码可以是:

class main{
    UCA uca;
    UCB ucb;

    public void RefreshData(){
        object data = uca.GetData();
        ucb.UpdateData(data);
    }
}