Using any Control In ComboBox or in EditingControl for DataGridView simply, in Windows Forms Controls (C#, VB)
Why Do I Create a Flexible Combo Box and Editing Control?
- I need to extend the
DataGridViewColumn
without rewriting it each time. - I need the flexibility
combobox
to accept any control as a dropdown.
What Does The Flexible Combo Box and Editing Control Do and Why is it Useful?
- Build your own grid column or your own combo simply.
- Use any control in the data grid column dynamically during run and design time.
- It comes with a control converter and a control editor to change the control type that you create at design time.
- Both control converter and the control editor use as
TypeConverter
andEditor
attributes in developing new controls.
What Problem is Solved
- Our component has a control property that creates controls of different types at design time and stores the created control as a simple text value.
- Use the Value property name of the drop-down control or editing control to specify the witch property to use to get the control value, for example:
Control Value Property Name NumericUpDown
Text
CheckedListBox
CheckedItems
TimeEdit
Value
- When using a
list
or acollection
for the Value property name; the library will convert the value tostring
that contains all items in the collection.
Using the Code
- Create a new form in the Windows application.
- Add reference to
AnyControlComboColumn
. - Add a new DataGridView and add a new column and set its type to
GridColumn
. - In the property window, go to
EditingControl(Create New)
a property and choose the control type from the dropdown list. - From the toolbox, add to the form
DropDownCombo
. - In the property window, go to
DropDownButton
property and expand it. - Under
DropDownButton
, go toDropDownControl(Create New)
property and choose the control type from the dropdown list.
Demo Application
The demo application contains DropDownCombo
; from the dropdown control choose: CheckedListBox
or DirList
or FileList
, and it contains a DataGridView
with two columns – the first with NumericUpDown
as an editing control and the second column with Mask Text Box as an edit control.
About this Library
The library contains the following classes:
DropDownButton
: This class provides a dropdown button that accepts any control as a dropdown control and any other control to be the display area.DropDownCombo
is a text box that contains the above dropdown button and so it provides a text box with dropdown control; you can set or change then control at run time or at design time.ControlConverter
andControlEditor
provide a technique to serialize any control to simple text, and we store text in the form designer.EditingControl
,GridCell
andGridColumn
provide the ability to use any control as an editing control inDataGridView
.
About C# Code in This Article
This library is developed using VB. I get the C# code using code converters. For more information about code converters, please see:
About the DropDown Button
- The dropdown button is the main class for creating dropdown combos.
- If we add it to any control, it will be docked to the right side.
- It assignes two controls: the first is the dropdown control and the second is the display area.
- The display area in the control will show the value of the created combo and could be a text box or rich text box or any control
- The dropdown control is the control that will be dropped when the dropdown button is clicked.
- To set up the dropdown control, you can choose control in the form or choose from a list that will contain all control types in
system.windows.forms
Assembly and assemblies that the application includes as a reference to them. - When clicking the dropdown button, the dropdown value will be changed to meet the value of the display area
- When the dropdown popup is closed, the display area value will be changed to meet the value of the dropdown control
DropDownCombo
is a Text box that contains a dropdown button.
About the GridColumn
GridColumn
is the class which is used to extend theDataGridView
to accept any control as editing control.- To use this class, I need to use
DataGridView,
and addGridColum
to it fromDataGridView
the Designer window. - To set up the editing control, you can choose control in the form or choose from a list that will contain all control types in
system.windows.forms
Assembly and assemblies that the application includes a reference to them.
About Control Converter and Control Properties Serialization
Before building the control property for the dropdown control, you need to serialize it to string to be stored in the form designer, but
don’t use XmlSerializer
to serialize control properties because:
XmlSerializer
not supported types:ISite
IDictionary
- Any class that Implements unsupported interface
- Any class has the property that returns an unsupported interface
Control
PropertyDescriptorCollection
SortedList
Collection
TreeNodeCollection
TreeNode
- Any class that Inherits not supported class
- Any class has the property that returns unsupported class
- Also
XmlSerializer
can serialize the following types, but it can’t de-serialize them:ArrayList
- List of Objects
ListBox.ObjectCollection
CheckedListBox.ObjectCollection
XmlSerializer
returns a multiline value, and it is better to store the control property as simple text- To test
XmlSerializer
, you can use the following code:
Imports System.Xml.Serialization Imports System.Reflection Imports System.ComponentModel Imports AnyControlComboColumn Imports System.Windows.Forms Public Module Module1 Public Function Serialize(ByVal Value As Object) As String Try If Value Is Nothing Then Return "" Exit Function End If Dim Serializer As New XmlSerializer(Value.GetType) Dim vMemoryStream As New IO.MemoryStream Serializer.Serialize(vMemoryStream, Value) Dim s = System.Text.Encoding.UTF8.GetString(vMemoryStream.ToArray) s = s.Replace("<?xml version=""1.0""?>", _ "<?xml version=""1.0"" encoding=""utf-8""?>") vMemoryStream = New IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes(s)) s = System.Text.Encoding.UTF8.GetString(vMemoryStream.ToArray) 'Try to Deserialize Try Dim f = Serializer.Deserialize(vMemoryStream) Catch ex As Exception Return "" End Try Return s Catch ex As Exception Return "" End Try End Function End Module
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Xml.Serialization; using System.Reflection; using System.ComponentModel; using AnyControlComboColumn; using System.Windows.Forms; namespace AnyControlComboColumn { public static class MSerialize { public static string Serialize(object Value) { try { if (Value == null) { return ""; return null; } XmlSerializer Serializer = new XmlSerializer(Value.GetType()); System.IO.MemoryStream vMemoryStream = new System.IO.MemoryStream(); Serializer.Serialize(vMemoryStream, Value); var s = System.Text.Encoding.UTF8.GetString(vMemoryStream.ToArray()); s = s.Replace("<?xml version=\"1.0\"?>", "<?xml version=\"1.0\" encoding=\"utf-8\"?>"); vMemoryStream = new System.IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes(s)); s = System.Text.Encoding.UTF8.GetString(vMemoryStream.ToArray()); //>Try to Deserialize try { var f = Serializer.Deserialize(vMemoryStream); } catch (Exception ex) { return ""; } return s; } catch (Exception ex) { return ""; } } } }
We don’t use the default ComponentConverter
because:
- It only allows selecting from pre-created controls in the form.
- It does not allow you to choose a control that is not present in the form.
- It can be used at design time only and can’t be used at run time.
What ComponentConverter
does:
- Creates an instance of the control from the selected type.
- Convert the control to
string
be stored asControlText
. - Converts the control text to the control object.
Points of Interest
To assign a dropdown control to the dropdown button, we use the following properties:
'Don't add TypeConverter attribute so this property use the default ComponentConverter <DisplayName("DropDownControl(Created in the form)")> _ Public Property DropDownControl() As Control <TypeConverter(GetType(ControlConverter)), _ Editor(GetType(ControlEditor), GetType(UITypeEditor)), _ DisplayName("DropDownControl(Create New)")> _ Public Property NewDropDownControl() As Control <Description("a text that will be used to create new DropDownControl")> _ Public Overridable Property ControlText As String
//dont put DesignerSerializationVisibility(DesignerSerializationVisibility.Content //this is only good for readonly property //ControlConverter does not work automatically if property type inherits form componenet or control //so we need a ControlText property [TypeConverter(typeof(ControlConverter)), Editor(typeof(ControlEditor), typeof(UITypeEditor)), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Description("Create new control displayed when OpenDropDown."), DefaultValue(typeof(Control), "Nothing"), Category("DropDown"), DisplayName("DropDownControl(Create New)"), EditorBrowsable(EditorBrowsableState.Advanced)] public Control NewDropDownControl { //........... } [DefaultValue(""), Browsable(false), Description("a text that will be used to create new DropDownControl")] public virtual string ControlText { //................. } //Don't add TypeConverter attribute so this property use the default ComponentConverter [Description("The control displayed when OpenDropDown."), DefaultValue(typeof(Control), "Nothing"), Category("DropDown"), DisplayName("DropDownControl(Select form the form controls)")] public Control DropDownControl { //................ }
- The
DropDownControl
property is named in the property windows asDropDownControl
(created in the form) uses the defaultComponentConverter
to choose the control from already created control that is present in the parent form NewDropDownControl
property is named in the property window asDropDownControl
(create new) uses our control converter and control editor to select a control from a list containing all supported controls for applications or we can type the control name directly such as:TextBox
,Button
and so on.ControlText
property is a simple text property that accepts and returns a text that includes the control type name followed by the assembly name and the control none default properties values.
The control converter uses the following code to convert the text to control:
Public Overrides Function ConvertFrom(ByVal context As ITypeDescriptorContext, _ ByVal culture As System.Globalization.CultureInfo, ByVal value As Object) As Object Try If value Is Nothing OrElse (Not TypeOf value Is String) _ OrElse String.IsNullOrEmpty(CType(value, String)) Then Return Nothing Exit Function End If Dim vPropertyValues As New Dictionary(Of String, String) Dim TypeParts = Split(CType(value, String), "@") If UBound(TypeParts) <> 2 Then ReDim Preserve TypeParts(2) End If If String.IsNullOrEmpty(TypeParts(1)) Then TypeParts(1) = WinFormsAssemblyName End If vPropertyValues.Add(c_TypeName, TypeParts(0)) vPropertyValues.Add(c_AssemblyName, TypeParts(1)) Dim aPropertyValues() As String = Split(TypeParts(2), ",") For i As Integer = 0 To UBound(aPropertyValues) If (Not String.IsNullOrEmpty(aPropertyValues(i))) AndAlso _ aPropertyValues(i).Contains(":"c) Then Dim KeyAndValue = Split(aPropertyValues(i), ":") vPropertyValues.Add(KeyAndValue(0).Trim, KeyAndValue(1).Trim) End If Next Return Me.CreateInstance(Nothing, vPropertyValues) Catch ex As Exception ErrMsg(ex) Return Nothing End Try End Function
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { try { if (value == null || (! (value is string)) || string.IsNullOrEmpty(Convert.ToString(value))) { return null; return null; } Dictionary<string,> vPropertyValues = new Dictionary<string,>(); string[] TypeParts = Microsoft.VisualBasic.Strings.Split(Convert.ToString(value), "@", -1, Microsoft.VisualBasic.CompareMethod.Binary); if (TypeParts.GetUpperBound(0) != 2) Array.Resize(ref TypeParts, 3); if (string.IsNullOrEmpty(TypeParts[1])) TypeParts[1] = WinFormsAssemblyName; vPropertyValues.Add(c_TypeName, TypeParts[0]); vPropertyValues.Add(c_AssemblyName, TypeParts[1]); string[] aPropertyValues = Microsoft.VisualBasic.Strings.Split(TypeParts[2], ",", -1, Microsoft.VisualBasic.CompareMethod.Binary); for (int i = 0; i <= aPropertyValues.GetUpperBound(0); i++) { if ((! (string.IsNullOrEmpty(aPropertyValues[i]))) && aPropertyValues[i].Contains(":")) { string[] KeyAndValue = Microsoft.VisualBasic.Strings.Split(aPropertyValues[i], ":", -1, Microsoft.VisualBasic.CompareMethod.Binary); vPropertyValues.Add(KeyAndValue[0].Trim(), KeyAndValue[1].Trim()); } } return CreateInstance(null, vPropertyValues); } catch (Exception ex) { _ErrMsg.ErrMsg(ex); return null; } }
- The
Text
format is[email protected]@ControlPropertyValues
. - First of all, the converter gets the type name and the assembly name properties values.
- It creates a properties values dictionary that consists of a pair of properties names and their values converted to
string
using the property assigned type converter and the assembly and type names. - Create an instance of the control using
CreateInstance
method. - To create an instance of the object from the type object, we use the following code:
Public Overloads Shared Function CreateInstance(ByVal vType As Type) As Control Try '####### Dim c As New vType is not correct If vType Is Nothing Then Return Nothing Exit Function End If Dim ConstructorInfo = vType.GetConstructor({}) If ConstructorInfo Is Nothing Then Return Nothing Exit Function End If Return CType(ConstructorInfo.Invoke({}), Control) Catch ex As Exception ErrMsg(ex) Return Nothing End Try End Function
public static Control CreateInstance(Type vType) { try { //> Dim c As New vType is not correct if (vType == null) { return null; return null; } var ConstructorInfo = vType.GetConstructor(new System.Type[0]); if (ConstructorInfo == null) { return null; return null; } return (Control)(ConstructorInfo.Invoke(new System.Type[0])); } catch (Exception ex) { _ErrMsg.ErrMsg(ex); return null; } }
- To get the type object from the type name, we use the following code:
Dim vAssembly = System.Reflection.Assembly.Load(AssemblyName) Dim vType = vAssembly.GetType(TypeName)
var vAssembly = System.Reflection.Assembly.Load(AssemblyName); var vType = vAssembly.GetType(TypeName);
To view the dropdown control, I use ToolStripDropDown
and ToolStripControlHost
:
Private WithEvents Popup As ToolStripDropDown Private Host As ToolStripControlHost Public Property DropDownControl() As Control Get Return _DropDownControl End Get Set(ByVal value As Control) Try '....some code for testing and preparing value the cloning it to NewControl = ControlConverter.Clone(value) Popup.Items.Clear() Host = Nothing _DropDownControl = NewControl _DropDownControl.Margin = Padding.Empty _DropDownControl.Padding = Padding.Empty Host = New ToolStripControlHost(_DropDownControl) Host.Margin = Padding.Empty Host.Padding = Padding.Empty Host.AutoSize = False Host.Size = value.Size Popup.Items.Add(Host) Popup.Size = Host.Size Popup.AutoSize = True _DropDownControl.Location = New Point(0, 0) If _DisplayArea IsNot Nothing Then DisplayAreaValue = DropDownControlValue End If OnClosed(Nothing, Nothing) Catch ex As Exception ErrMsg(ex) End Try End Set End Property Protected Overrides Sub OnClick(ByVal e As EventArgs) Try MyBase.OnClick(e) If Me.DroppedDown Then Me.CloseDropDown() Else Me.OpenDropDown() 'Using Popup.Show method End If Catch ex As Exception ErrMsg(ex) End Try End Sub Public Overridable Sub OpenDropDown() Try If _DropDownControl Is Nothing OrElse Host Is Nothing OrElse _ _DisplayArea Is Nothing Then Exit Sub Dim frm = _DisplayArea.FindForm Dim Pos As Point 'additional code to adjust the position according to Drop Down Direction Pos = New Point(_DisplayArea.Left, _DisplayArea.Bottom + 1) Popup.Show(frm, Pos, ToolStripDropDownDirection.BelowRight) Catch ex As Exception ErrMsg(ex) End Try End Sub
History
- Initial version