灵活的 ComboBox 和 EditingControl
在 ComboBox 或 DataGridView 的 EditingControl 中使用任何控件。
引言
相似库
- Xceed Combo and Grid
- DXperience
此库的不同之处
Xceed | DXperience | 此库 | |
直接将任何控件分配给下拉列表框 | 是 | 否 | 是 |
直接将任何控件分配给列 | 是 | 选择预先存储的内联编辑器 | 是 |
支持的 Grid | Xceed Grid | DXperience Grid | DataGridView |
可以选择不在窗体中的控件 | 否 | 否 | 是 |
免费 | 否 | 否 | 是 |
轻量级简单库 | 否 | 否 | 是 |
还有许多其他有用的组件 | 是 | 是 | 否 |
我为什么创建这个应用程序?
- 我需要扩展
DataGridViewColumn
而无需每次都重写它。 - 我需要一个灵活的
combobox
,它可以接受任何控件作为下拉列表。
这个应用程序做什么?为什么有用?
- 它可以用来以简单的方式构建您自己的网格列或您自己的组合框。
- 使用包含的控件转换器在运行时和设计时动态复制和创建控件。
- 使用包含的控件编辑器在设计时更改您创建的控件类型。
- 控件转换器和控件编辑器都可以用作
TypeConverter
和Editor
属性来开发新控件。
解决了什么问题
- 我们的组件有一个控件属性,可以在设计时创建不同类型的控件,并将创建的控件存储为简单的文本值。
- 下拉控件或编辑控件的值属性名称可以在设计时根据需要指定,例如
Control 值属性名称 NumericUpDown
文本
CheckedListBox
CheckedItems
TimeEdit
值
Value
属性名可以是list
或collection
,值将被转换为包含集合中所有项的string
。
Using the Code
- 在 Windows 应用程序中创建新窗体。
- 添加对
AnyControlComboColumn
的引用。 - 添加一个新的 Data Grid View,添加新列并将其类型设置为
GridColumn
。 - 在属性窗口中,转到
EditingControl(Create New)
属性,然后从下拉列表中选择控件类型。 - 从工具箱,将
DropDownCombo
添加到窗体。 - 在属性窗口中,转到
DropDownButton
属性并展开它。 - 在
DropDownButton
下,转到DropDownControl(Create New)
属性,然后从下拉列表中选择控件类型。
演示应用程序
演示应用程序包含 DropDownCombo
,我们可以选择它的下拉控件为:CheckedListBox
、DirList
或 FileList
,并且它包含一个 DataGridView
,其中有两列 - 第一列使用 NumericUpDown
作为编辑控件,第二列使用 Mask Text Box 作为编辑控件。
关于此库
此库包含以下类
DropDownButton
:此类提供一个下拉按钮,它可以接受任何控件作为下拉控件,并接受任何其他控件作为显示区域。DropDownCombo
是一个包含上述下拉按钮的文本框,因此它提供了一个可以随时设置或更改的文本框,其中包含下拉控件。ControlConverter
和ControlEditor
提供了一种将任何控件序列化为简单文本的技术,以便存储在窗体设计器中。EditingControl
、GridCell
和GridColumn
提供了将任何控件用作DataGridView
中编辑控件的能力。
关于本文中的 C# 代码
此库是使用 VB 开发的。本文中的 C# 代码是通过代码转换器获得的。有关代码转换器的更多信息,请参见
关于 DropDownButton
- 下拉按钮是创建下拉组合框的主要类。
- 当它被添加到任何控件时,它将被停靠在右侧。
- 它可以分配两个控件:第一个是下拉控件,第二个是显示区域。
- 控件中的显示区域将显示创建的组合框的值,它可以是文本框、富文本框或任何控件
- 下拉控件是在单击下拉按钮时会下拉的控件。
- 要设置下拉控件,我们可以选择窗体中的一个控件,或者从一个包含
system.windows.forms
程序集以及应用程序引用的程序集中所有控件类型的列表中选择。 - 单击下拉按钮时,下拉值将更改为与显示区域的值匹配
- 关闭下拉弹出窗口时,显示区域的值将更改为与下拉控件的值匹配
DropDownCombo
是一个包含下拉按钮的文本框。
关于 GridColumn
GridColumn
是用于扩展DataGridView
以接受任何控件作为编辑控件的类。- 要使用此类,我们需要使用
DataGridView
并从DataGridView
设计器窗口中添加GridColumn
。 - 要设置编辑控件,我们可以选择窗体中的一个控件,或者从包含
system.windows.forms
程序集以及应用程序引用的程序集中所有控件类型的列表中选择。
关于 ControlConverter 和 Control Properties Serialization
在构建下拉控件的控件属性之前,我们需要将其序列化为 string
以存储在窗体设计器中,但不要使用 XmlSerializer
来序列化控件属性,因为
XmlSerializer
不支持的类型ISite
IDictionary
- 任何实现不支持接口的类
- 任何具有返回不支持接口的属性的类
Control
PropertyDescriptorCollection
SortedList
Collection
TreeNodeCollection
TreeNode
- 任何继承不支持类的类
- 任何具有返回不支持的类的属性的类
- 此外,
XmlSerializer
可以序列化以下类型,但无法反序列化它们ArrayList
- 对象列表
ListBox.ObjectCollection
CheckedListBox.ObjectCollection
XmlSerializer
返回多行值,最好将控件属性存储为纯文本- 要测试
XmlSerializer
,我们可以使用以下代码
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 "";
}
}
}
}
我们不使用默认的 ComponentConverter
因为
- 它只允许从窗体中预先创建的控件中选择。
- 它不允许选择不在窗体中的控件。
- 它只能在设计时使用,不能在运行时使用。
ComponentConverter
的作用
- 从选定的类型创建控件的实例。
- 将控件转换为
string
以存储为ControlText
。 - 将控件文本转换为控件对象。
关注点
要将下拉控件分配给下拉按钮,我们使用以下属性
'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 {
//................
}
- 属性窗口中的
DropDownControl
属性命名为DropDownControl
(在窗体中创建),它使用默认的ComponentConverter
从父窗体中已创建的控件中选择控件。 - 属性窗口中的
NewDropDownControl
属性命名为DropDownControl
(创建新),它使用我们的控件转换器和控件编辑器从包含应用程序所有支持的控件的列表中选择控件,或者我们可以直接输入控件名称,例如:TextBox
、Button
等。 ControlText
属性是一个简单的文本属性,它接受并返回一个文本,该文本包括控件类型名称、程序集名称以及控件的非默认属性值。
控件转换器使用以下代码将文本转换为控件
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;
}
}
Text
格式为TypeName@AssmblyName@ControlPropertyValues
。- 首先,转换器获取类型名称和程序集名称的属性值。
- 它创建一个属性值字典,该字典由属性名称及其值组成,使用指定的属性类型转换器以及程序集和类型名称将它们转换为
string
。 - 使用
CreateInstance
方法创建控件的实例。 - 要从类型对象创建对象实例,我们使用以下代码
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; } }
- 要从类型名称获取类型对象,我们使用以下代码
Dim vAssembly = System.Reflection.Assembly.Load(AssemblyName) Dim vType = vAssembly.GetType(TypeName)
var vAssembly = System.Reflection.Assembly.Load(AssemblyName); var vType = vAssembly.GetType(TypeName);
为了查看下拉控件,我使用了 ToolStripDropDown
和 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
历史
- 初始版本