【问题标题】:Passing an object between two UserControls and a main Form [closed]在两个用户控件和主窗体之间传递一个对象[关闭]
【发布时间】:2021-07-14 20:57:06
【问题描述】:

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

我的想法是创建对象的三个实例,一个在UserControlsA 中获取对象所需的信息,一个在主窗体中从UserControlsA 获取对象的“副本”,以及一个在UserControlsB 中可以从主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;
    }
}

用户控件A

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

        

用户控件B

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 不起作用。我能做什么?

【问题讨论】:

    标签: c# winforms user-controls


    【解决方案1】:

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

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

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

    • DefaultValue 定义属性的默认值(创建控件时分配给属性的值)。代码生成器使用它来确定是否应该序列化当前值:如果它与 Attribute 设置的值匹配,则不序列化。
      PropertyGrid 还使用它以粗体显示非默认值选择或赋值。
    • DesignerSerializationVisibility 指定在设计时应如何序列化属性。这里,设置为DesignerSerializationVisibility.Visible,表示该属性应该被序列化。

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


    UCA 用户控件
    UserControl(参见视觉示例)有两个 Button,它们更改绑定的 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。它只是公开了一个公共属性 (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;
            }
        }
    }
    

    使用标准事件通知
    您还可以使用标准事件生成属性更改通知。
    不同之处在于,每个属性都需要一个事件来通知更改。
    如果您已经为此使用了 Event 委托,那么它可能是一个不错的选择,因为几乎不需要添加:只需调用在 Property setter 中引发 Event 的受保护方法。

    在这里,我使用通用 .Net 事件处理,使用由底层 Component 类定义并由其 Events 属性公开的 EventHandlerList 添加删除事件订阅。
    事件通常通过调用与事件同名的受保护方法引发,但 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 不会改变。它只是接收器。


    Form 类(或其他用作 Handler 的类):

    在Form Constructor中,或者在Form初始化后调用的任何其他方法中,使用UCB的DataBindings属性链接两个UserControl的属性:

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

    您也可以使用 BindingSource 进行调解:

        var ucsSource = new BindingSource(uca1, null);
        ucb1.DataBindings.Add("ReceiverDataObject", ucsSource, "CustomDataObject", 
            false, DataSourceUpdateMode.OnPropertyChanged);
    

    示例功能:

    【讨论】:

    • 哇,这是我在论坛中得到的最有用的回复,不仅是堆栈溢出,而且总的来说。太感谢了!我刚刚为我的特定问题找到了一个更简单的解决方案,但这看起来更加充实,我将尝试实现它。再次感谢!
    【解决方案2】:

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

    要提供从 A 到 B 的信息,一种方法是为这些控件创建公共 Get/Set 方法或属性。主要形式与那些公共成员一起工作,伪代码可以是:

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

    【讨论】:

      【解决方案3】:

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

      主要形式:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-25
        相关资源
        最近更新 更多