本文介绍了将用户控件的子控件绑定到UC的公共属性时出现问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述


  1. 我如下所述创建了一个具有自定义类公共属性的UserControl,目的是编辑我的自定义类的实例

  2. 我试图在下面绑定一个文本框

  3. 我在主窗体上放置了该UC的实例

  4. 在主窗体上,我将UC的公共属性设置为按钮单击事件

启动后立即引发异常,因为(由于尚未在主窗体上按下Button)引发 Load 事件时,UC的 Job 类型属性为 null

我想做的甚至是可能吗?

 公共类JobEditor'我的用户控件

公共属性Job as JobDefinition

Private Sub JobEditor_Load( sender作为对象,e作为EventArgs)处理MyBase.Load
TextJobName.DataBindings.Add(New Binding( Text,Me.Job, JobName,True,DataSourceUpdateMode.OnPropertyChanged,-1))
End Sub

End Class

我可以解决这个问题(很差)通过给 Job 属性设置一个设置器,然后在设置器中进行null-check-then-bind。

这似乎很糟糕,因为绑定被烧掉了并反复创建。似乎应该为这种情况提供一劳永逸的解决方案,但我没有看到它。



编辑1:
换句话说,为什么 New Binding()(貌似)坚持存在一个实际实例( ArgumentNullException )?

在我的naïveté中,我认为绑定可以反映我变量的 type 来验证属性名称,而不关心实例是否存在(特别是考虑到存在第六个参数 nullValue)。

我是误解了 New Binding()的用法还是我做错了?

解决方案

下面是一个示例UserControl,其中的某些控件绑定到公共类对象的属性( JobDefinition )。公共类是Project类的一部分。

该类的属性是枚举数- JobStatusEnum -用于进一步测试当用 TypeConverter 属性修饰类对象时,公共属性的工作方式。



公共 JobDefinition 类使用



在运行时,所有属性当然可以将其设置为不同的值:UI和UserControl的属性将反映新的值。



JobDefinition 类型的属性也可以设置为新的不同对象。 BindingSource将处理绑定控件的 DataBindings ,并在更改其DataSource时更新属性。



  Private Sub btnJobChangeValue_Click(sender as Object,e as EventArgs)处理btnJobChangeValue.Click 
MyUserControl1.Job.JobName = txtNewJobName.Text
End Sub

Private Sub btnNewJob_Click(sender作为对象,作为EventArgs)处理btnNewJob.Click
Dim newJob = New JobDefinition()与{
.JobID = 5,
.JobName = New Job,
。 JobStatus = JobStatusEnum.Starting
}
MyUserControl1.Job = newJob
End Sub

示例UserControl

 导入System.ComponentModel 
导入系统。 Windows.Forms

部分公共类MyUserControl
继承UserControl

Private m_Job As JobDefinition = Nothing
私有m_Source作为BindingSource =什么都没有

Public Sub New()
InitializeComponent()
m_Source = New BindingSource()
Me.Job = New JobDefinition()With {
.JobID = 1,
.JobName = Initial Job,
.JobStatus = JobStatusEnum.Starting
}
End Sub

公共财产工作作为JobDefinition
获取
返回m_Job
End Get
Set(ByVal值作为JobDefinition)
m_Job =值
m_Source.DataSource = m_Job
结束集
结束属性

受保护的重写Sub OnLoad(如EventArgs)
MyBase.OnLoad(e)
BindControls()
End Sub

Friend Sub BindControls()
txtJobID.DataBindings.Add(New Binding( Text,m_Source, JobID,False,DataSourceUpdateMode.OnPropertyChanged))
txtJobName。 DataBindings.Add(新Binding( Text,m_Source, JobName,False,DataSourceUpdateMode.OnPropertyChanged))
txtJobStatus.DataBindings.Add(New Binding( Text,m_Source, JobStatus,False,DataSourceUpdateMode.OnPropertyChanged))
最终子级
最终级








JobDefinition 类对象

 导入System.ComponentModel 

< TypeConverter(GetType(ExpandableObjectConverter))>
公共类JobDefinition
实现INotifyPropertyChanged

公共事件PropertyChanged作为PropertyChangedEventHandler实现INotifyPropertyChanged.PropertyChanged

私有m_JobID作为整数
私有m_JobName作为字符串
私有m_JobStatus作为JobStatusEnum

公共属性JobID作为整数
获取
返回m_JobID
End获取
Set(ByVal值作为整数)
m_JobID =值
OnPropertyChanged(NameOf(Me.JobID))
结束集
结束属性

公共属性JobName作为字符串
获取
返回m_JobName
End Get
Set(ByVal值作为字符串)
m_JobName =值
OnPropertyChanged(NameOf(Me.JobName))
End Set
最终财产

公共财产JobStatus作为JobStatusEnum
获得
R eturn m_JobStatus
End Get
Set(ByVal值作为JobStatusEnum)
m_JobStatus = value
OnPropertyChanged(NameOf(Me.JobStatus))
End Set
End属性

朋友子OnPropertyChanged(以字符串形式命名的PropertyName)
RaiseEvent PropertyChanged(Me,New PropertyChangedEventArgs(PropertyName))
结束子项
结束类

JobStatusEnum 枚举器

 公共枚举JobStatusEnum作为整数
起始
起始
待定
停止
已完成
结束枚举

(Google云端硬盘)


  1. I created a UserControl with a public property of my custom class as described below, with the purpose of editing instances of my custom class
  2. I attempted to bind a TextBox per below
  3. I placed an instance of this UC on my main Form
  4. On the main Form, I set the UC's public property in a Button click event

This throws an exception immediately after launch, because (since the Button has not yet been pressed on the main Form) the UC's Job type property is null when the Load event is raised.

Is what I'm trying to do even possible?

Public Class JobEditor 'my user control

    Public Property Job As JobDefinition

    Private Sub JobEditor_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        TextJobName.DataBindings.Add(  New Binding("Text", Me.Job, "JobName", True, DataSourceUpdateMode.OnPropertyChanged, -1) )
    End Sub

End Class

I can work around this (poorly) by giving the Job property a setter, then doing a null-check-then-bind in the setter.
That seems terrible because the bindings get burned down and recreated repeatedly. It seems like there ought to be a "bind once and for all" solution for this situation, but I'm not seeing it.

EDIT 1:To put it another way, why does New Binding() (seemingly) insist on there being an actual instance present (ArgumentNullException)?
In my naïveté I would assume the binding could reflect on my variable's type to verify property names, and not care whether an instance was present (especially given the presence of the sixth argument "nullValue").
Am I misunderstanding the usage of New Binding() or am I just doing it wrong?

解决方案

Here's a sample UserControl with some Controls bound to the properties of a public class object (JobDefinition). The public class is part of the Project classes.
A property of this class is an Enumerator - JobStatusEnum - used to further test how the public property works when the class object is decorated with a TypeConverter attribute.

The public JobDefinition class uses a TypeConverter of type ExpandableObjectConverter, to allow the editing of the UserControl Property of type JobDefinition.

This class also implements the INotifyPropertyChanged interface, commonly used to notify binding clients that a Property value has changed, raising a PropertyChanged event.

A BindingSource is then used to bind some of the UC's Controls to a Property of the JobDefinition class, setting the DataSource of the BindingSource to the UserControl's public Job property.

All the Controls' Bindings are added in the UserControl's OnLoad method override, calling the BindControls() method.

Edit:
If you don't want/need to have active bindings when a new instance of the UserControl is created, you can set the BindingSource's DataSource property to the object type that will generate the source of the data, without specifying an instance of that object.

BindingSource.DataSource:

In this case, the initialization procedure of the UserControl can be changed in:

Public Sub New()
    InitializeComponent()
    m_Source = New BindingSource() With {.DataSource = GetType(JobDefinition)}
End Sub

The UserControl's child controls will show empty at Design-Time and the Job property, though listed in the Designer's PropertyGrid, will also be empty.
It can set to a new object at any time.


At Design-Time, the UC's JobDefinition property type can be set to any value. These setting will be preserved when the Form is loaded, since the class is serialized by the Designer.
All bound Controls will react to the Property changes and the new values will be reflected in the UI at Design-Time.

At Run-Time, all properties can of course be set to different values: the UI and the UserControl's Properties will reflect the new values.

The Property of type JobDefinition can also be set to a new, different object. The BindingSource will take care of the bound Control's DataBindings, updating the properties when its DataSource is changed.

Private Sub btnJobChangeValue_Click(sender As Object, e As EventArgs) Handles btnJobChangeValue.Click
    MyUserControl1.Job.JobName = txtNewJobName.Text
End Sub

Private Sub btnNewJob_Click(sender As Object, e As EventArgs) Handles btnNewJob.Click
    Dim newJob = New JobDefinition() With {
        .JobID = 5,
        .JobName = "New Job",
        .JobStatus = JobStatusEnum.Starting
    }
    MyUserControl1.Job = newJob
End Sub

The sample UserControl:

Imports System.ComponentModel
Imports System.Windows.Forms

Partial Public Class MyUserControl
    Inherits UserControl

    Private m_Job As JobDefinition = Nothing
    Private m_Source As BindingSource = Nothing

    Public Sub New()
        InitializeComponent()
        m_Source = New BindingSource()
        Me.Job = New JobDefinition() With {
            .JobID = 1,
            .JobName = "Initial Job",
            .JobStatus = JobStatusEnum.Starting
        }
    End Sub

    Public Property Job As JobDefinition
        Get
            Return m_Job
        End Get
        Set(ByVal value As JobDefinition)
            m_Job = value
            m_Source.DataSource = m_Job
        End Set
    End Property

    Protected Overrides Sub OnLoad(e As EventArgs)
        MyBase.OnLoad(e)
        BindControls()
    End Sub

    Friend Sub BindControls()
        txtJobID.DataBindings.Add(New Binding("Text", m_Source, "JobID", False, DataSourceUpdateMode.OnPropertyChanged))
        txtJobName.DataBindings.Add(New Binding("Text", m_Source, "JobName", False, DataSourceUpdateMode.OnPropertyChanged))
        txtJobStatus.DataBindings.Add(New Binding("Text", m_Source, "JobStatus", False, DataSourceUpdateMode.OnPropertyChanged))
    End Sub
End Class


JobDefinition class object:

Imports System.ComponentModel

<TypeConverter(GetType(ExpandableObjectConverter))>
Public Class JobDefinition
    Implements INotifyPropertyChanged

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Private m_JobID As Integer
    Private m_JobName As String
    Private m_JobStatus As JobStatusEnum

    Public Property JobID As Integer
        Get
            Return m_JobID
        End Get
        Set(ByVal value As Integer)
            m_JobID = value
            OnPropertyChanged(NameOf(Me.JobID))
        End Set
    End Property

    Public Property JobName As String
        Get
            Return m_JobName
        End Get
        Set(ByVal value As String)
            m_JobName = value
            OnPropertyChanged(NameOf(Me.JobName))
        End Set
    End Property

    Public Property JobStatus As JobStatusEnum
        Get
            Return m_JobStatus
        End Get
        Set(ByVal value As JobStatusEnum)
            m_JobStatus = value
            OnPropertyChanged(NameOf(Me.JobStatus))
        End Set
    End Property

    Friend Sub OnPropertyChanged(PropertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName))
    End Sub
End Class

The JobStatusEnum enumerator:

Public Enum JobStatusEnum As Integer
    Starting
    Started
    Pending
    Stopped
    Completed
End Enum

Downloadable Project (Google Drive)

这篇关于将用户控件的子控件绑定到UC的公共属性时出现问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

05-26 22:31
查看更多