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

VB.NET 运行时控件设计器用于 Windows 窗体

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (20投票s)

2015年6月28日

CPOL

8分钟阅读

viewsIcon

43941

downloadIcon

2361

只需添加一个代码模块,即可为应用程序的所有 Windows 窗体添加即时设计功能!

引言

有时,有必要允许您的应用程序用户更改一个或多个窗体的设计。解决这些功能的需要很复杂,并且每个窗体都需要大量的时间和代码来添加此功能。在网上搜索解决方案,您只会找到一些基础示例或现成的组件,其中包含所有期望的功能 - 但没有一个是免费的。

背景

演示项目包含一个单一且可重用的模块,其中包含 2 个主要类,可以在任何使用 Windows 窗体的应用程序中使用。要激活任何窗体的设计器,您只需创建一个控件设计器类的实例,传递所需的窗体和一些更简单的参数(其中大部分是可选的),然后您的窗体就处于设计模式。释放该类将结束设计模式 - 就像您期望的那样简单,不是吗?

当然,如果我们无法保存和恢复用户所做的更改,那么这一切都没有真正的必要。因此,我们将找到第二个类(控件管理器),它提供了这些方法。在窗体的 Load 事件中添加对 Load 方法的调用,在窗体的 Closing 事件中添加对 Save 方法的调用,这样您就完成了!

该设计器允许使用鼠标或箭头键进行单个或多个控件选择、移动和调整大小,将多个控件对齐到左、右、上或下,吸附到网格/吸附到容器边框功能,并在与 Visual Studio 中设计时使用的相同属性网格中编辑窗体和控件最常见的显示属性。

ControlDesignerControlManagerSave 方法都可以传递一个简单的控件 List,您希望将其从设计中排除。

使用代码

1) 将 ControlDesigner.vb 文件添加到您的 VB.NET 项目中。

2) 对于您希望增强设计器功能的每个窗体:
    a) 将以下代码添加到窗体的 Load 事件中

Dim objCM As New ControlManager

objCM.RestoreProperties(Me)

objCM.Dispose()

     b) 将以下代码添加到窗体的 Closing 事件中(不包括保存的按钮)

Dim objCM As New ControlManager

objCM.SaveProperties(Me, New List(Of Control)({btnDesigner}))

objCM.Dispose()

       c) 在窗体级别添加一个布尔标志,以反映设计模式

Private bolDesignMode As Boolean = False

       c) 将以下代码添加到用户开始/停止设计模式的按钮(或菜单项)事件中

Select Case btnDesigner.Text
    Case "Designer"
         btnDesigner.Text = "Done"
         bolDesignMode = True
         objCD = New ControlDesigner(Me, New List(Of Control)({btnDesigner}), Color.LightYellow)
    Case Else
         objCD.Dispose()
         bolDesignMode = False
         btnDesigner.Text = "Designer"
End Select

        (您可以为可选参数指定自定义值,以使设计器适应您的应用程序需求)
    
    d) 为防止控件在设计模式下执行其单击(或其他)事件的标准操作,
       将所有这些事件过程扩展为仅在不在设计模式下执行。例如

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    If Not bolDesignMode Then
       'Do something...
    End If
End Sub

3) 运行您的应用程序并测试设计功能。按住 Shift 键右键单击 2 个或更多控件以对齐多个控件

           
            
...或右键单击窗体或任何控件以编辑它们的属性

           

您无需在您的应用程序中提供上下文菜单和属性编辑对话框 - 两者都是从模块中“即时”创建的。

深入细节

除了用于拖放控件的常规事件挂钩外,还有 2 个主要的包装器类,它们允许在属性网格中编辑任何控件最常见的显示属性,并将这些属性从 XML 文件中进行序列化/反序列化。

1) ControlForm 包装器(类 PropertyWrapperFormPropertiesWrapper
Propertygrid 的主要优点是能够简单地将任何对象分配给它的 SelectedObject 属性,然后 Propertygrid 将为我们完成所有工作,例如分析对象的​​方法、属性和事件,将它们组织到类别中,并为每种数据类型创建所有编辑器,同时显示与之相关的帮助文本。不幸的是,没有简单的方法可以将显示的网格条目限制为完全允许用户编辑的内容。包装器类将为您完成此操作:它们仅公开我们希望显示/编辑的属性。为了支持 PropertyGrid 的完整功能,我们必须在每个属性过程前加上帮助文本和类别信息。例如,Backcolor 的属性将如下所示:

<DescriptionAttribute("Defines the control's backcolor."),
CategoryAttribute("Display")> _
Public Property BackColor() As Color
    Get
        Return _lstControls.Item(0).BackColor
    End Get
    Set(ByVal Value As Color)
        For Each ctl As Control In _lstControls
            ctl.BackColor = Value
        Next
    End Set
End Property

其中 _lstControls 是一个 List(Of Control),包含所有当前选定的控件。如您所见,Get 方法始终返回 List 中第一个选定控件的值,而 Set 方法会将新的属性值分发给所有当前选定的控件。
方法前缀 DescriptionAttribute 包含我们希望与此属性一起显示的帮助文本,而 CategoryAttribute 包含我们希望此属性所属的类别的显示名称。您可以减少或扩展您希望用户编辑的属性列表,但请记住,控件包装器用于所有控件,您必须处理并非所有控件都提供所有属性的问题。您还必须以相同的方式减少或扩展以下序列化包装器类,以确保控件管理器在保存和恢复时支持相同的属性。

2) 序列化包装器(类 ControlInfo
当我们想将控件的受支持属性保存到 XML 文件时,我们将面临的下一个问题。好吧:.NET 框架提供了易于使用的方法和对象来简单地序列化和反序列化对象,但是……如果您尝试传递 - 例如 - 上述控件包装器的 List 对象给这些方法,您将收到一个错误,提示序列化器无法反射某些属性。具体来说,这些是使用复杂数据类型的所有属性,如 LocationSizeMarginPaddingFont,它们是具有 2 个或更多值的结构。LocationSize 结构包含 2 个值:LeftTopWidthHeightMarginPadding 属性包含 4 个值,而 Font 属性甚至更多!像 ForeBackcolor 这样的属性使用无法自动翻译的 Color 数据类型,并且像 AnchorDock 这样的枚举也可能导致问题。为了解决这个问题,您可以将序列化包装器视为一个具有双向接口的转换器:
- 一种是所有我们习惯在代码中处理的属性数据类型
- 另一种是将这些属性分解为可以处理反序列化的数据类型。
唯一的技巧是告诉反序列化器在执行工作时使用哪个以及忽略哪个。让我们看 2 个例子:

a) 转换 Color 属性
标准接口将如下所示:

<Xml.Serialization.XmlIgnore> _
Public Property BackColor() As Color
    Get
        Return _BackColor
    End Get
    Set(ByVal Value As Color)
        _BackColor = Value
    End Set
End Property

方法前缀 XmlIgnore 将告诉反序列化器忽略此属性,同时它仍然可以使用 Color 数据类型进行直接与控件交换此值。对于反序列化器,我们提供 HTML 格式的相同值:

<Xml.Serialization.XmlElement("BackColor")> _
Public Property BackColorXML() As String
    Get
        Return ColorTranslator.ToHtml(_BackColor)
    End Get
    Set(ByVal Value As String)
        _BackColor = ColorTranslator.FromHtml(Value)
    End Set
End Property

方法前缀 XmlElement 告诉反序列化器将此属性名称处理为 'BackColor'(而不是 'BackColorXML'!)。使用 ColorTranslator,我们可以轻松地将颜色值从 HTML 格式转换到 HTML 格式。类内部变量 _BackColor 始终以 Color 数据类型存储颜色值。

b) 转换具有多个子值的复杂数据类型(结构)
LocationSizeMargingPaddingFont 等数据类型由几个子值组成。保存这些属性的最简单方法是将它们分解为多个单个属性,这些属性将在请求复杂数据类型时重新组合。例如,标准的 Location 属性将如下所示:

<Xml.Serialization.XmlIgnore> _
Public Property Location() As System.Drawing.Point
    Get
        Return New Point(_Left, _Top)
    End Get
    Set(ByVal Value As System.Drawing.Point)
        _Left = Value.X
        _Top = Value.Y
    End Set
End Property

同样,我们使用方法前缀 XmlIgnore 来隐藏此属性以进行反序列化。Get 方法将内部的 _Left_Top 值组合成一个新的 Point 结构并返回,而 Set 方法会将 Point 结构分解为两个单独的值 _Left 和 _Top。因此,XML 反序列化器将找到 2 个属性:

Public Property Left() As Integer
    Get
        Return _Left
    End Get
    Set(ByVal Value As Integer)
        _Left = Value
    End Set
End Property

Public Property Top() As Integer
    Get
        Return _Top
    End Get
    Set(ByVal Value As Integer)
        _Top = Value
    End Set
End Property

我们不需要在这里使用 XmlElement 前缀,因为标准接口没有同名属性。

以同样的方式,我们将分解所有复杂的数据类型,如 SizeMarginPaddingFont

Enumerations 通常代表一个 Integer 值,因此您只需将这些属性声明为 Integer 而不是它们的枚举名称 - VB.NET 的隐式转换功能将为您处理一切。

...并且不要忘记在整个包装器类前加上 <System.Serializable> 前缀,否则反序列化将无法工作!

您将在代码模块的标题、类和过程中找到所有功能和限制的更详细描述。如果您想与他人分享您的扩展,请随时扩展模块,我很乐意将其添加到本文和演示项目中(当然,您的姓名将在您的所有更改和扩展中显示!)。

历史

2015-06-28
首次发布。

2015-07-01
SelectControl 的两个过程都以这种方式扩展,用户在处理多个选定的控件时无需一直按住 Shift 键。已更改行

If Not My.Computer.Keyboard.ShiftKeyDown Then

to

If (Not My.Computer.Keyboard.ShiftKeyDown) AndAlso (Not lstSelectedControls.Contains(sender)) Then

(如果单击的控件已经是当前选择的一部分,则选择不会被清除)。

© . All rights reserved.