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