65.9K
CodeProject 正在变化。 阅读更多。
Home

灵活的 ComboBox 和 EditingControl

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (10投票s)

2011 年 9 月 19 日

CPOL

6分钟阅读

viewsIcon

56486

downloadIcon

5802

在 ComboBox 或 DataGridView 的 EditingControl 中使用任何控件。

引言

相似库

  1. Xceed Combo and Grid
  2. DXperience

此库的不同之处

  Xceed DXperience 此库
直接将任何控件分配给下拉列表框
直接将任何控件分配给列 选择预先存储的内联编辑器
支持的 Grid Xceed Grid DXperience Grid DataGridView
可以选择不在窗体中的控件
免费
轻量级简单库
还有许多其他有用的组件

我为什么创建这个应用程序?

  1. 我需要扩展 DataGridViewColumn 而无需每次都重写它。
  2. 我需要一个灵活的 combobox,它可以接受任何控件作为下拉列表。

这个应用程序做什么?为什么有用?

  1. 它可以用来以简单的方式构建您自己的网格列或您自己的组合框。
  2. 使用包含的控件转换器在运行时和设计时动态复制和创建控件。
  3. 使用包含的控件编辑器在设计时更改您创建的控件类型。
  4. 控件转换器和控件编辑器都可以用作 TypeConverterEditor 属性来开发新控件。

解决了什么问题

  1. 我们的组件有一个控件属性,可以在设计时创建不同类型的控件,并将创建的控件存储为简单的文本值。
  2. 下拉控件或编辑控件的值属性名称可以在设计时根据需要指定,例如
    Control 值属性名称
    NumericUpDown 文本
    CheckedListBox CheckedItems
    TimeEdit
  3. Value 属性名可以是 listcollection,值将被转换为包含集合中所有项的 string

Using the Code

  1. 在 Windows 应用程序中创建新窗体。
  2. 添加对 AnyControlComboColumn 的引用。
  3. 添加一个新的 Data Grid View,添加新列并将其类型设置为 GridColumn
  4. 在属性窗口中,转到 EditingControl(Create New) 属性,然后从下拉列表中选择控件类型。
  5. 从工具箱,将 DropDownCombo 添加到窗体。
  6. 在属性窗口中,转到 DropDownButton 属性并展开它。
  7. DropDownButton 下,转到 DropDownControl(Create New) 属性,然后从下拉列表中选择控件类型。

演示应用程序

演示应用程序包含 DropDownCombo,我们可以选择它的下拉控件为:CheckedListBoxDirListFileList,并且它包含一个 DataGridView,其中有两列 - 第一列使用 NumericUpDown 作为编辑控件,第二列使用 Mask Text Box 作为编辑控件。

关于此库

此库包含以下类

  1. DropDownButton:此类提供一个下拉按钮,它可以接受任何控件作为下拉控件,并接受任何其他控件作为显示区域。
  2. DropDownCombo 是一个包含上述下拉按钮的文本框,因此它提供了一个可以随时设置或更改的文本框,其中包含下拉控件。
  3. ControlConverterControlEditor 提供了一种将任何控件序列化为简单文本的技术,以便存储在窗体设计器中。
  4. EditingControlGridCellGridColumn 提供了将任何控件用作 DataGridView 中编辑控件的能力。

关于本文中的 C# 代码

此库是使用 VB 开发的。本文中的 C# 代码是通过代码转换器获得的。有关代码转换器的更多信息,请参见

关于 DropDownButton

  1. 下拉按钮是创建下拉组合框的主要类。
  2. 当它被添加到任何控件时,它将被停靠在右侧。
  3. 它可以分配两个控件:第一个是下拉控件,第二个是显示区域。
  4. 控件中的显示区域将显示创建的组合框的值,它可以是文本框、富文本框或任何控件
  5. 下拉控件是在单击下拉按钮时会下拉的控件。
  6. 要设置下拉控件,我们可以选择窗体中的一个控件,或者从一个包含 system.windows.forms 程序集以及应用程序引用的程序集中所有控件类型的列表中选择。
  7. 单击下拉按钮时,下拉值将更改为与显示区域的值匹配
  8. 关闭下拉弹出窗口时,显示区域的值将更改为与下拉控件的值匹配
  9. DropDownCombo 是一个包含下拉按钮的文本框。

关于 GridColumn

  1. GridColumn 是用于扩展 DataGridView 以接受任何控件作为编辑控件的类。
  2. 要使用此类,我们需要使用 DataGridView 并从 DataGridView 设计器窗口中添加 GridColumn
  3. 要设置编辑控件,我们可以选择窗体中的一个控件,或者从包含 system.windows.forms 程序集以及应用程序引用的程序集中所有控件类型的列表中选择。

关于 ControlConverter 和 Control Properties Serialization

在构建下拉控件的控件属性之前,我们需要将其序列化为 string 以存储在窗体设计器中,但不要使用 XmlSerializer 来序列化控件属性,因为

  1. XmlSerializer 不支持的类型
    • ISite
    • IDictionary
    • 任何实现不支持接口的类
    • 任何具有返回不支持接口的属性的类
    • Control
    • PropertyDescriptorCollection
    • SortedList
    • Collection
    • TreeNodeCollection
    • TreeNode
    • 任何继承不支持类的类
    • 任何具有返回不支持的类的属性的类
  2. 此外,XmlSerializer 可以序列化以下类型,但无法反序列化它们
    • ArrayList
    • 对象列表
    • ListBox.ObjectCollection
    • CheckedListBox.ObjectCollection
  3. XmlSerializer 返回多行值,最好将控件属性存储为纯文本
  4. 要测试 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 因为

  1. 它只允许从窗体中预先创建的控件中选择。
  2. 它不允许选择不在窗体中的控件。
  3. 它只能在设计时使用,不能在运行时使用。

ComponentConverter 的作用

  1. 从选定的类型创建控件的实例。
  2. 将控件转换为 string 以存储为 ControlText
  3. 将控件文本转换为控件对象。

关注点

要将下拉控件分配给下拉按钮,我们使用以下属性

'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 {
//................
}
  1. 属性窗口中的 DropDownControl 属性命名为 DropDownControl(在窗体中创建),它使用默认的 ComponentConverter 从父窗体中已创建的控件中选择控件。
  2. 属性窗口中的 NewDropDownControl 属性命名为 DropDownControl(创建新),它使用我们的控件转换器和控件编辑器从包含应用程序所有支持的控件的列表中选择控件,或者我们可以直接输入控件名称,例如:TextBoxButton 等。
  3. 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;
    }
}
  1. Text 格式为 TypeName@AssmblyName@ControlPropertyValues
  2. 首先,转换器获取类型名称和程序集名称的属性值。
  3. 它创建一个属性值字典,该字典由属性名称及其值组成,使用指定的属性类型转换器以及程序集和类型名称将它们转换为 string
  4. 使用 CreateInstance 方法创建控件的实例。
  5. 要从类型对象创建对象实例,我们使用以下代码
    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;
        }
    }
  6. 要从类型名称获取类型对象,我们使用以下代码
    Dim vAssembly = System.Reflection.Assembly.Load(AssemblyName)
    Dim vType = vAssembly.GetType(TypeName)
    var vAssembly = System.Reflection.Assembly.Load(AssemblyName);
    var vType = vAssembly.GetType(TypeName);

为了查看下拉控件,我使用了 ToolStripDropDownToolStripControlHost

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

历史

  • 初始版本
© . All rights reserved.