Here are the summary of your questions:
- Basic concepts of a custom property editor and Example
- If it's possible to make a property that takes 1 or more required parameters
- Whether it's possible to tweak the property-page type that handles "collection" properties
Basic concepts of a custom property editor and Example
Assuming you want to have a custom property editor for a property, then you need to do the following steps:
- Create a custom property editor by inheriting from UITypeEditor
- Override GetEditStyle and return UITypeEditorEditStyle.Modal or UITypeEditorEditStyle.DropDown. An example of a modal editor is the modal that you see while editing a Font. An example of a dropdown editor is the dropdown that you see while editing Anchor or Dock.
- Override EditValue and show the dropdown or show the modal to edit the value.
- Decorate your property with the EditorAttribute
To learn more, take look at:
Example - Modal property editor for a complex property
Here is a step by step example which shows you how to create a modal property editor for a complex property. In this example we create a control which has a Boder property which has two properties, BorderWidth and BorderColor. So when editing the property, you see a [...] and if you click on it, you see the following modal:
Here are the steps:
- Create a Windows Forms project
- Add a reference to System.Design
- Add a class called Border.vb with the following content: Public Class Border
Private m_borderWidth As Integer
Public Property BorderWidth() As Integer
Get
Return m_borderWidth
End Get
Set(ByVal value As Integer)
' Valiate the value and throw new Exception if it's not valie
m_borderWidth = value
End Set
End Property
Private m_borderColor As Color
Public Property BorderColor() As Color
Get
Return m_borderColor
End Get
Set(ByVal value As Color)
' Valiate the value and throw new Exception if it's not valie
m_borderColor = value
End Set
End Property
End Class - Add a class called BorderEditorForm.vb with the following content:
End ClassPublic Class BorderEditorForm Inherits Form Friend WithEvents borderColorComboBox As ComboBox Private borderColorLabel As Label Private borderWidthNumericUpDown As NumericUpDown Private borderWidthLabel As Label Private saveButton As Button Private rejectButton As Button Friend WithEvents tableLayoutPanel1 As TableLayoutPanel Friend WithEvents flowLayoutPanel1 As FlowLayoutPanel Private m_border As Border Public Property Border() As Border Get Return New Border() With { .BorderColor = CType(borderColorComboBox.SelectedItem, Color), .BorderWidth = borderWidthNumericUpDown.Value} End Get Set(ByVal value As Border) m_border = value borderWidthNumericUpDown.Value = value.BorderWidth borderColorComboBox.SelectedItem = value.BorderColor End Set End Property Private Sub InitializeComponent() Me.borderColorComboBox = New System.Windows.Forms.ComboBox() Me.borderColorLabel = New System.Windows.Forms.Label() Me.borderWidthNumericUpDown = New System.Windows.Forms.NumericUpDown() Me.borderWidthLabel = New System.Windows.Forms.Label() Me.saveButton = New System.Windows.Forms.Button() Me.rejectButton = New System.Windows.Forms.Button() Me.tableLayoutPanel1 = New System.Windows.Forms.TableLayoutPanel() Me.flowLayoutPanel1 = New System.Windows.Forms.FlowLayoutPanel() CType(Me.borderWidthNumericUpDown, System.ComponentModel.ISupportInitialize).BeginInit() Me.tableLayoutPanel1.SuspendLayout() Me.flowLayoutPanel1.SuspendLayout() Me.SuspendLayout() ' 'borderColorComboBox ' Me.borderColorComboBox.Anchor = CType(((System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Left) _ Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles) Me.borderColorComboBox.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed Me.borderColorComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList Me.borderColorComboBox.Location = New System.Drawing.Point(220, 20) Me.borderColorComboBox.Margin = New System.Windows.Forms.Padding(10) Me.borderColorComboBox.Name = "borderColorComboBox" Me.borderColorComboBox.Size = New System.Drawing.Size(552, 39) Me.borderColorComboBox.TabIndex = 1 ' 'borderColorLabel ' Me.borderColorLabel.AutoSize = True Me.borderColorLabel.Location = New System.Drawing.Point(20, 20) Me.borderColorLabel.Margin = New System.Windows.Forms.Padding(10) Me.borderColorLabel.Name = "borderColorLabel" Me.borderColorLabel.Size = New System.Drawing.Size(175, 32) Me.borderColorLabel.TabIndex = 0 Me.borderColorLabel.Text = "Border Color" ' 'borderWidthNumericUpDown ' Me.borderWidthNumericUpDown.Anchor = CType(((System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Left) _ Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles) Me.borderWidthNumericUpDown.Location = New System.Drawing.Point(220, 79) Me.borderWidthNumericUpDown.Margin = New System.Windows.Forms.Padding(10) Me.borderWidthNumericUpDown.Maximum = New Decimal(New Integer() {50, 0, 0, 0}) Me.borderWidthNumericUpDown.Name = "borderWidthNumericUpDown" Me.borderWidthNumericUpDown.Size = New System.Drawing.Size(552, 38) Me.borderWidthNumericUpDown.TabIndex = 3 ' 'borderWidthLabel ' Me.borderWidthLabel.AutoSize = True Me.borderWidthLabel.Location = New System.Drawing.Point(20, 79) Me.borderWidthLabel.Margin = New System.Windows.Forms.Padding(10) Me.borderWidthLabel.Name = "borderWidthLabel" Me.borderWidthLabel.Size = New System.Drawing.Size(180, 32) Me.borderWidthLabel.TabIndex = 2 Me.borderWidthLabel.Text = "Border Width" ' 'saveButton ' Me.saveButton.DialogResult = System.Windows.Forms.DialogResult.OK Me.saveButton.Location = New System.Drawing.Point(609, 13) Me.saveButton.Name = "saveButton" Me.saveButton.Size = New System.Drawing.Size(160, 70) Me.saveButton.TabIndex = 0 Me.saveButton.Text = "Save" ' 'rejectButton ' Me.rejectButton.DialogResult = System.Windows.Forms.DialogResult.Cancel Me.rejectButton.Location = New System.Drawing.Point(443, 13) Me.rejectButton.Name = "rejectButton" Me.rejectButton.Size = New System.Drawing.Size(160, 70) Me.rejectButton.TabIndex = 1 Me.rejectButton.Text = "Cancel" ' 'tableLayoutPanel1 ' Me.tableLayoutPanel1.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle()) Me.tableLayoutPanel1.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) Me.tableLayoutPanel1.Controls.Add(Me.borderColorLabel, 0, 0) Me.tableLayoutPanel1.Controls.Add(Me.borderColorComboBox, 1, 0) Me.tableLayoutPanel1.Controls.Add(Me.borderWidthLabel, 0, 1) Me.tableLayoutPanel1.Controls.Add(Me.borderWidthNumericUpDown, 1, 1) Me.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill Me.tableLayoutPanel1.Location = New System.Drawing.Point(0, 0) Me.tableLayoutPanel1.Name = "tableLayoutPanel1" Me.tableLayoutPanel1.Padding = New System.Windows.Forms.Padding(10) Me.tableLayoutPanel1.RowStyles.Add(New System.Windows.Forms.RowStyle()) Me.tableLayoutPanel1.RowStyles.Add(New System.Windows.Forms.RowStyle()) Me.tableLayoutPanel1.Size = New System.Drawing.Size(792, 170) Me.tableLayoutPanel1.TabIndex = 0 ' 'flowLayoutPanel1 ' Me.flowLayoutPanel1.AutoSize = True Me.flowLayoutPanel1.Controls.Add(Me.saveButton) Me.flowLayoutPanel1.Controls.Add(Me.rejectButton) Me.flowLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Bottom Me.flowLayoutPanel1.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft Me.flowLayoutPanel1.Location = New System.Drawing.Point(0, 170) Me.flowLayoutPanel1.Name = "flowLayoutPanel1" Me.flowLayoutPanel1.Padding = New System.Windows.Forms.Padding(10) Me.flowLayoutPanel1.Size = New System.Drawing.Size(792, 96) Me.flowLayoutPanel1.TabIndex = 1 ' 'BorderEditorForm ' Me.AcceptButton = Me.saveButton Me.ClientSize = New System.Drawing.Size(792, 266) Me.Controls.Add(Me.tableLayoutPanel1) Me.Controls.Add(Me.flowLayoutPanel1) Me.MaximizeBox = False Me.MinimizeBox = False Me.Name = "BorderEditorForm" Me.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent Me.Text = "Edit Border" CType(Me.borderWidthNumericUpDown, System.ComponentModel.ISupportInitialize).EndInit() Me.tableLayoutPanel1.ResumeLayout(False) Me.tableLayoutPanel1.PerformLayout() Me.flowLayoutPanel1.ResumeLayout(False) Me.ResumeLayout(False) Me.PerformLayout() End Sub Public Sub New() MyBase.New() InitializeComponent() borderColorComboBox.DataSource = GetType(Color).GetProperties() _ .Where(Function(x) x.PropertyType = GetType(Color)) _ .Select(Function(x) x.GetValue(Nothing)).ToList() End Sub Private Sub borderColorComboBox_DrawItem(sender As Object, e As DrawItemEventArgs) Handles borderColorComboBox.DrawItem e.DrawBackground() If e.Index >= 0 Then Dim txt = borderColorComboBox.GetItemText(borderColorComboBox.Items(e.Index)) Dim Color = CType(borderColorComboBox.Items(e.Index), Color) Dim r1 = New Rectangle(e.Bounds.Left + 1, e.Bounds.Top + 1, 2 * (e.Bounds.Height - 2), e.Bounds.Height - 2) Dim r2 = Rectangle.FromLTRB(r1.Right + 2, e.Bounds.Top, e.Bounds.Right, e.Bounds.Bottom) Using b As New SolidBrush(Color) e.Graphics.FillRectangle(b, r1) End Using e.Graphics.DrawRectangle(Pens.Black, r1) TextRenderer.DrawText(e.Graphics, txt, borderColorComboBox.Font, r2, borderColorComboBox.ForeColor, TextFormatFlags.Left + TextFormatFlags.VerticalCenter) End If End Sub
- Add a class called BorderEditor.vb with the content: Imports System.ComponentModel
Imports System.Drawing.Design
Imports System.Windows.Forms.Design
Public Class BorderEditor
Inherits UITypeEditor
Public Overrides Function GetEditStyle(context As ITypeDescriptorContext) As UITypeEditorEditStyle
Return UITypeEditorEditStyle.DropDown
End Function
Public Overrides Function EditValue(context As ITypeDescriptorContext, provider As IServiceProvider, value As Object) As Object
If (value Is Nothing OrElse value.GetType() IsNot GetType(Border)) Then
Throw New InvalidOperationException()
End If
Dim editorService = CType(provider.GetService(GetType(IWindowsFormsEditorService)), IWindowsFormsEditorService)
If (editorService IsNot Nothing) Then
Dim f = New BorderEditorForm
f.Border = CType(value, Border)
If (editorService.ShowDialog(f) = DialogResult.OK) Then
Return f.Border
End If
End If
Return value
End Function
End Class - Add a class called MyControl.vb with the following content: Imports System.ComponentModel
Imports System.Drawing.Design
Public Class MyControl
Inherits Control
End ClassPrivate m_border As Border = New Border With {.BorderColor = Color.Blue, .BorderWidth = 5} <Editor(GetType(BorderEditor), GetType(UITypeEditor))> Public Property Border() As Border Get Return m_border End Get Set(ByVal value As Border) m_border = value Me.Invalidate() End Set End Property Protected Overrides Sub OnPaint(e As PaintEventArgs) MyBase.OnPaint(e) If (m_border IsNot Nothing) Then Using p As New Pen(Border.BorderColor, Border.BorderWidth) p.Alignment = Drawing2D.PenAlignment.Inset e.Graphics.DrawRectangle(p, Me.ClientRectangle) End Using End If End Sub
- Rebuild the project.
- Add an instance of MyControl to your form
- In the property editor, find Border property and click on [...] in front of it and it shows the modal. You can then save the property and see the appearance of your control changes.
If it's possible to make a property that takes 1 or more required parameters
Yes, it is possible to add any kind of validation in the property setter and if the property value if not valid, then throw an exception, for example:
Public Property FirstName() As String
Get
Return m_firstName
End Get
Set(ByVal value As String)
If (String.IsNullOrEmpty(value)) Then
Throw New Exception("FirstName is mandatory.")
End If
m_firstName = value
End Set
End Property
Whether it's possible to tweak the property-page type that handles "collection" properties
Yes it's possible. In above example you learned how to edit any kind of object. A collection is also a type, so you can easily create a custom UITypeEditor for that and show a DataGridView or any other UI. A good base class for this kind of properties is CollectionEditor. You can create a custom editor form, from scratch or by deriving from CollectionEditorForm.