简体   繁体   中英

Problem with binding child controls of a User Control to UC's public property

  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.

System.ArgumentNullException: 'Value cannot be null. Parameter name: 'dataSource'

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 :

The DataSource property can be set to a number of data sources, including types , objects, and lists of types. The resulting data source will be exposed as a list.

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)
The Obj folder is empty, so rebuild the Solution before opening the Form or the UserControl in the Form's Designer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM