VB中动态创建控件
作者:admin 日期:2007-10-22
创建数据驱动窗体
所谓数据驱动窗体就是根据所请求的数据的不同生成相应的窗体。举例来讲,假设你现在有一个数据库,其中有些字段必须根据登录者的身份加以显示,授权级别高的用户可以浏览并修改这些字段的内容;授权级别一般的用户只能浏览这些字段中的数据;授权级别低的用户则不能浏览这些字段中的内容。要做到这一点就得利用VB动态添加控件的功能。
动态创建控件
无论你想要创建何种类型的数据驱动窗体,你必须知道如何在运行时动态地创建控件。你可以通过控件数组做到这一点,但VB6的Controls集合所提供的Add方法,功能更强大,灵活性更高。使用该方法,你不需要在设计时将控件的实例放在窗体上。实际上,用Add方法你甚至可以创建程序在编译时根本不存在的控件。这种方法的用法也很简单:
' 声明一个窗体级的变量
Dim WithEvents txtTotal As TextBox
Sub CreateTextbox()
' 创建新的Textbox控件
Set txtTotal = Controls.Add("VB.TextBox", "txtTotal")
' 将控件移动到你所需要的地方
txtTotal.Move 1000, 800, 1200, 300
' 创建时,所有的控件都是不可见的
txtTotal.Visible = True
End Sub
请注意Add方法的第二个参数:分配给控件的名称。从代码可读性出发,这个名称一般都与变量名相同。你可以用这个名称从Controls集合中获取相应的控件或移除该控件。例如:
Controls.Remove "txtTotal"
在变量声明时加上WithEvents关键字,即使在设计时窗体不存在该控件,你也可以为该控件的事件编写代码。
上面所讲的方法只适合解决VB内置的控件。例如,当你要添加一个TreeView控件时,VB会要求你证明你已经得到了合法的授权来创建该控件的实例。换句话说,VB要证明这个控件是买来的,而不是从其它附有该控件的程序中借来的。
要证明你经过了合法的授权有下面几种方法:
在窗体上放置一个控件。这也是最简单的方法。你完全没有必要将这个窗体显示出来。
将相关的控件添加到工具箱中,然后在“工程属性”对话框的“生成”标签页中取消选择“删除有关未使用的ActiveX控件的信息”这一项。
向Licenses集合添加一个元素。例如:Debug.Print Licenses.Add("MSMask.MaskedEdBox")
仅仅创建了控件并不足够
要创建一个数据驱动窗体,仅仅知道动态创建控件还不够。例如:现在你要创建一个能根据数据库中表的不同字段自动生成控件的窗体。该窗体可能会创建单行文本框,其长度随字段长度不同而不同;也可能会创建单选按钮或复选按钮以显示布尔型字段;甚至可能创建一个多行文本框显示备注型字段。
你需要解决的第一个问题是:文本框控件的Multiline属性在运行时是只读的,只在设计时可用。幸好,微软的Microsoft Windowless Controls 6.0可以解决这个问题。这组控件集包括了轻量级的TextBox,ComboBox,ListBox,CheckBox,OptionButton,CommandButton和两个scrollbar控件。这些控件与VB内置的相应的控件最大的区别在于:这些控件的所有属性在运行时是可读写的。在VB的安装光盘中的Common\Tools\VB\WinLess文件夹中可以找到这个控件组。用下面的代码可以创建一个多行文本框:
Dim WithEvents txtEditor As MSWLess.WLText
Private Sub CreateEditor()
Set txtEditor = Controls.Add( "MSWLess.WLText", "txtEditor")
txtEditor.Move 0, 0, 4000, 4000
txtEditor.MultiLine = True
txtEditor.ScrollBars = wlBoth
txtEditor.Visible = True
End Sub
另外一个问题比较复杂:在事先不知道要创建多少个控件的情况下,如何给每个对新创建的控件的引用分配唯一的带WithEvents关键字的变量。换句话说就是要对新创建的控件的事件进行编程,前提是你在设计时不知道程序会创建多少个控件。使用对象数组显然不行,因为不能用WithEvents关键字声明一个对象数组;更坏的情况是,资一个变量定义为As Control或As Object也不行,因为还是不能用WithEvents。
问题源自于我们无法在运行时捕获一个对象数组事件。所以我们只能采取曲线救国的办法。所要的编写的代码可能比你想象的多,不过这个解决方法很有趣,值得我们这样去做。
我们需要两个辅助类模块来捕获事件,分别取名为ControlItems和ControlItem。ControlItems是一个集合类,其中保存了ControlItem对象及其数量。该数量等于你所要对之编程的控件的数量。ControlItem类的每一份实例捕获控件产生的事件,然后调用在其所属的ControlItems集合类中的过程,最后由ControlItems在窗体中触发事件并执行事件中的代码。整个过程如下图所示:
捕获多个控件的事件
为简单起见,假设你要捕获来自所有的动态添加到窗体上去的控件的Validate事件。为完成这个工作,ControlItems集合类必须向父窗体展示该事件,并随时准备接收来自其子ControlItem类的通知以触发事件。代码如下:
Event Validate(CtrlItem As ControlItem, Cancel As Boolean)
Private m_ControlItems As New Collection
' 向集合中添加一个新的ControlItem项目
Function Add(ctrl As Control) As ControlItem
Dim newItem As New ControlItem
newItem.Init ctrl, Me
' 添加到私有类
m_ControlItems.Add newItem
' 返回新项目给调用者
Set Add = newItem
End Function
Friend Sub Notify_Validate(Item As ControlItem, Cancel As Boolean)
RaiseEvent Validate(Item, Cancel)
End Sub
ControlItem类必须捕获来自动态添加到窗体中的控件的事件,并通知其所属的ControlItems集合类。很显然,ControlItem类必须有一个用WithEvents关键字定义的变量来引用真正的控件。这意味着你不能将变量声明为As Control或As Object。如果你决定在窗体中所动态添加的控件不使用VB内置的控件的话,这个问题的解决办法相当的简单。你只需要将变量声明为VBControlExtender类型就行了。对于创建数据驱动窗体来讲,不使用VB内置的控件并不是一件大不了的事。
将变量声明为VBControlExtender,并加上WithEvents关键字,你就能直接捕获Validate,GotFocus,LostFocus,DragDrop和DragOver这几个事件了。如果要捕获其它更多的事件,你可以使用ObjectEvent。下面是ControlItem类模块中的代码:
Public WithEvents Ctrl As VBControlExtender
' 所属的ControlItems对象
Dim m_Parent As ControlItems
Sub Init(ctl As Object, parnt As ControlItems)
Set Ctrl = ctl
Set m_Parent = parnt
End Sub
Private Sub Ctrl_Validate(Cancel As Boolean)
' 通知所属的ControlItems类
m_Parent.Notify_Validate Me, Cancel
End Sub
将下面的代码放入窗体中,就可以捕获动态添加的控件所产生的事件了:
Dim WithEvents CtrlItems As New ControlItems
Private Sub cmdCreateControls_Click()
Dim ctrl As Control
' 创建两个文本框并将它们添加到ControlItems集合? Set ctrl = Controls.Add("MSWLess.WLText", "One")
ctrl.Move 100, 200, 1000, 300
ctrl.Visible = True
CtrlItems.Add ctrl
' 注意你可以使用同一个变量
Set ctrl = Controls.Add("MSWLess.WLText", "Two")
ctrl.Move 100, 800, 1000, 300
ctrl.Visible = True
CtrlItems.Add ctrl
End Sub
Private Sub CtrlItems_Validate( CtrlItem As ControlItem, Cancel As Boolean)
' 拒绝空字符串 - 注意如何引用控件的属? If CtrlItem.Ctrl.Text = ""
Then Cancel=True
End Sub
现在解决了最困难的部分,要创建一个数据驱动窗体就变得简单了
**************************************************************
****************************************************************
动态添加控件
VB6有一个新功能,可以动态添加控件,不用控件数组:
object.Add (ProgID, name, container)
参数说明
Object 必需的。一个对象表达式,其值是“应用于”列表中的一个对象。
ProgID 必需的。一个标识控件的字符串。大多数控件的 ProgID 都可通过查看对象浏览器来决定。控件的 ProgID 是由控件的库和类组成的。
例如,CommandButton 控件的 ProgID 是 VB.CommandButton。在ProgID 与对象浏览器中所显示的不一样的情况下,Visual Basic
将显示一个包括正确 ProgId 的错误信息。
name 必要的。一个字符串,用来标识集合的成员。
container 可选的。一个对象引用,它指定控件的容器。如果没有指定或为NULL,缺省值为 Controls 集合所属的容器。通过指定该参数,可以把一个控件放置在任何现存的容器控件(如 Frame 控件)中。用户控件或 ActiveX 文档也可以作为一个容器。
举例: //在picture1上面添加一个commandbutton
Private Sub Form_Load()
Form1.Controls.Add "VB.CommandButton", "cmdOk", Picture1
With Form1!cmdOk
.Visible = True
.Width = 500
.Caption = "确认(&Y)"
End With
End Sub
重点:当您添加一个未引用的需要许可证的控件到一个现存的(已部署好的)应用程序时,在使用 Add 方法之前您必须也添加这个控件的许可证关键字。
在运行时添加未引用的控件:
您也可以利用 Add 方法来动态添加一个在工程中没有被引用的控件。(“未引用的”控件是不出现在 Toolbox 中的控件)。为此,您必须也把控件的License 关键字添加到 Licenses 集合中。下面的示例中在添加控件本身之前添加了控件的许可证关键字:
Option Explicit
Private WithEvents extCtl As VBControlExtender
Private Sub Form_Load()
Licenses.Add "prjWeeks.WeeksCtl", "xydsfasfjewfe"
Set extCtl = Form1.Controls.Add("prjWeeks.WeeksCtl", "ctl1")
extCtl.Visible = True ' The control is invisible by default.
End Sub
但是,为了编程这样一个未引用控件的事件,您必须使用 WithEvents 关键字声明一个对象变量为VBControlExtender 对象(如上),并且设置该对象变量到Add 方法返回的引用上。然后,利用VBControlExtender 对象的 ObjectEvent事件来编程该控件的事件。下面是一个简单的例子。
Option Explicit
Dim WithEvents objExt As VBControlExtender '声明 Extender 变量
Private Sub LoadControl()
Licenses.Add "Project1.Control1", "xydsfasfjewfe"
Set objExt = Controls.Add("Project1.Control1", "myCtl")
objExt.Visible = True
End Sub
Private Sub extObj_ObjectEvent(Info As EventInfo)
'使用 Select Case 编程控件的事件。
Select Case Info.Name
Case "Click"
'这里处理 Click 事件。
'现在显示其他的 case
Case Else '未知事件
'这里处理未知事件。
End Select
End Sub
Note: 不能把一个固有的控件指定给这个 VBControlExtender 变量; 任何这种试图将引起类型不匹配错误。
但是,您也可以通过使用 WithEvents 关键字声明一个对象变量,并且设置该方法返回的引用为该变量,从而编程一个动态添加控件的事件,如下所示。
Option Explicit
'声明对象变量为 CommandButton 。
Private WithEvents cmdObject As CommandButton
Private Sub Form_Load()
Set cmdObject = Form1.Controls.Add("VB.CommandButton", "cmdOne")
cmdObject.Visible = True
cmdObject.Caption = "Dynamic CommandButton"
End Sub
Private Sub cmdObject_Click()
Print "This is a dynamically added control"
End Sub
如果希望添加一个用户控件或任何 ActiveX 控件到您的窗体,必须或者把这个控件添加到“工具箱”,或者把控件的 License 关键字添加到 Licenses集合中。有关详细信息请参阅“增加方法 (Licenses 集合)”。
注意:如果您添加一个 ActiveX 或用户控件到您的工程,但是没有在窗体中使用它,您也必须不要选定“工程属性”对话框的“生成” 选项卡上的“删除有关未使用的 ActiveX 控件”选项。如果您的应用程序试图添加该控件,那么该 Add 方法将失败,因为必需的信息已经被丢弃。
所谓数据驱动窗体就是根据所请求的数据的不同生成相应的窗体。举例来讲,假设你现在有一个数据库,其中有些字段必须根据登录者的身份加以显示,授权级别高的用户可以浏览并修改这些字段的内容;授权级别一般的用户只能浏览这些字段中的数据;授权级别低的用户则不能浏览这些字段中的内容。要做到这一点就得利用VB动态添加控件的功能。
动态创建控件
无论你想要创建何种类型的数据驱动窗体,你必须知道如何在运行时动态地创建控件。你可以通过控件数组做到这一点,但VB6的Controls集合所提供的Add方法,功能更强大,灵活性更高。使用该方法,你不需要在设计时将控件的实例放在窗体上。实际上,用Add方法你甚至可以创建程序在编译时根本不存在的控件。这种方法的用法也很简单:
' 声明一个窗体级的变量
Dim WithEvents txtTotal As TextBox
Sub CreateTextbox()
' 创建新的Textbox控件
Set txtTotal = Controls.Add("VB.TextBox", "txtTotal")
' 将控件移动到你所需要的地方
txtTotal.Move 1000, 800, 1200, 300
' 创建时,所有的控件都是不可见的
txtTotal.Visible = True
End Sub
请注意Add方法的第二个参数:分配给控件的名称。从代码可读性出发,这个名称一般都与变量名相同。你可以用这个名称从Controls集合中获取相应的控件或移除该控件。例如:
Controls.Remove "txtTotal"
在变量声明时加上WithEvents关键字,即使在设计时窗体不存在该控件,你也可以为该控件的事件编写代码。
上面所讲的方法只适合解决VB内置的控件。例如,当你要添加一个TreeView控件时,VB会要求你证明你已经得到了合法的授权来创建该控件的实例。换句话说,VB要证明这个控件是买来的,而不是从其它附有该控件的程序中借来的。
要证明你经过了合法的授权有下面几种方法:
在窗体上放置一个控件。这也是最简单的方法。你完全没有必要将这个窗体显示出来。
将相关的控件添加到工具箱中,然后在“工程属性”对话框的“生成”标签页中取消选择“删除有关未使用的ActiveX控件的信息”这一项。
向Licenses集合添加一个元素。例如:Debug.Print Licenses.Add("MSMask.MaskedEdBox")
仅仅创建了控件并不足够
要创建一个数据驱动窗体,仅仅知道动态创建控件还不够。例如:现在你要创建一个能根据数据库中表的不同字段自动生成控件的窗体。该窗体可能会创建单行文本框,其长度随字段长度不同而不同;也可能会创建单选按钮或复选按钮以显示布尔型字段;甚至可能创建一个多行文本框显示备注型字段。
你需要解决的第一个问题是:文本框控件的Multiline属性在运行时是只读的,只在设计时可用。幸好,微软的Microsoft Windowless Controls 6.0可以解决这个问题。这组控件集包括了轻量级的TextBox,ComboBox,ListBox,CheckBox,OptionButton,CommandButton和两个scrollbar控件。这些控件与VB内置的相应的控件最大的区别在于:这些控件的所有属性在运行时是可读写的。在VB的安装光盘中的Common\Tools\VB\WinLess文件夹中可以找到这个控件组。用下面的代码可以创建一个多行文本框:
Dim WithEvents txtEditor As MSWLess.WLText
Private Sub CreateEditor()
Set txtEditor = Controls.Add( "MSWLess.WLText", "txtEditor")
txtEditor.Move 0, 0, 4000, 4000
txtEditor.MultiLine = True
txtEditor.ScrollBars = wlBoth
txtEditor.Visible = True
End Sub
另外一个问题比较复杂:在事先不知道要创建多少个控件的情况下,如何给每个对新创建的控件的引用分配唯一的带WithEvents关键字的变量。换句话说就是要对新创建的控件的事件进行编程,前提是你在设计时不知道程序会创建多少个控件。使用对象数组显然不行,因为不能用WithEvents关键字声明一个对象数组;更坏的情况是,资一个变量定义为As Control或As Object也不行,因为还是不能用WithEvents。
问题源自于我们无法在运行时捕获一个对象数组事件。所以我们只能采取曲线救国的办法。所要的编写的代码可能比你想象的多,不过这个解决方法很有趣,值得我们这样去做。
我们需要两个辅助类模块来捕获事件,分别取名为ControlItems和ControlItem。ControlItems是一个集合类,其中保存了ControlItem对象及其数量。该数量等于你所要对之编程的控件的数量。ControlItem类的每一份实例捕获控件产生的事件,然后调用在其所属的ControlItems集合类中的过程,最后由ControlItems在窗体中触发事件并执行事件中的代码。整个过程如下图所示:
捕获多个控件的事件
为简单起见,假设你要捕获来自所有的动态添加到窗体上去的控件的Validate事件。为完成这个工作,ControlItems集合类必须向父窗体展示该事件,并随时准备接收来自其子ControlItem类的通知以触发事件。代码如下:
Event Validate(CtrlItem As ControlItem, Cancel As Boolean)
Private m_ControlItems As New Collection
' 向集合中添加一个新的ControlItem项目
Function Add(ctrl As Control) As ControlItem
Dim newItem As New ControlItem
newItem.Init ctrl, Me
' 添加到私有类
m_ControlItems.Add newItem
' 返回新项目给调用者
Set Add = newItem
End Function
Friend Sub Notify_Validate(Item As ControlItem, Cancel As Boolean)
RaiseEvent Validate(Item, Cancel)
End Sub
ControlItem类必须捕获来自动态添加到窗体中的控件的事件,并通知其所属的ControlItems集合类。很显然,ControlItem类必须有一个用WithEvents关键字定义的变量来引用真正的控件。这意味着你不能将变量声明为As Control或As Object。如果你决定在窗体中所动态添加的控件不使用VB内置的控件的话,这个问题的解决办法相当的简单。你只需要将变量声明为VBControlExtender类型就行了。对于创建数据驱动窗体来讲,不使用VB内置的控件并不是一件大不了的事。
将变量声明为VBControlExtender,并加上WithEvents关键字,你就能直接捕获Validate,GotFocus,LostFocus,DragDrop和DragOver这几个事件了。如果要捕获其它更多的事件,你可以使用ObjectEvent。下面是ControlItem类模块中的代码:
Public WithEvents Ctrl As VBControlExtender
' 所属的ControlItems对象
Dim m_Parent As ControlItems
Sub Init(ctl As Object, parnt As ControlItems)
Set Ctrl = ctl
Set m_Parent = parnt
End Sub
Private Sub Ctrl_Validate(Cancel As Boolean)
' 通知所属的ControlItems类
m_Parent.Notify_Validate Me, Cancel
End Sub
将下面的代码放入窗体中,就可以捕获动态添加的控件所产生的事件了:
Dim WithEvents CtrlItems As New ControlItems
Private Sub cmdCreateControls_Click()
Dim ctrl As Control
' 创建两个文本框并将它们添加到ControlItems集合? Set ctrl = Controls.Add("MSWLess.WLText", "One")
ctrl.Move 100, 200, 1000, 300
ctrl.Visible = True
CtrlItems.Add ctrl
' 注意你可以使用同一个变量
Set ctrl = Controls.Add("MSWLess.WLText", "Two")
ctrl.Move 100, 800, 1000, 300
ctrl.Visible = True
CtrlItems.Add ctrl
End Sub
Private Sub CtrlItems_Validate( CtrlItem As ControlItem, Cancel As Boolean)
' 拒绝空字符串 - 注意如何引用控件的属? If CtrlItem.Ctrl.Text = ""
Then Cancel=True
End Sub
现在解决了最困难的部分,要创建一个数据驱动窗体就变得简单了
**************************************************************
****************************************************************
动态添加控件
VB6有一个新功能,可以动态添加控件,不用控件数组:
object.Add (ProgID, name, container)
参数说明
Object 必需的。一个对象表达式,其值是“应用于”列表中的一个对象。
ProgID 必需的。一个标识控件的字符串。大多数控件的 ProgID 都可通过查看对象浏览器来决定。控件的 ProgID 是由控件的库和类组成的。
例如,CommandButton 控件的 ProgID 是 VB.CommandButton。在ProgID 与对象浏览器中所显示的不一样的情况下,Visual Basic
将显示一个包括正确 ProgId 的错误信息。
name 必要的。一个字符串,用来标识集合的成员。
container 可选的。一个对象引用,它指定控件的容器。如果没有指定或为NULL,缺省值为 Controls 集合所属的容器。通过指定该参数,可以把一个控件放置在任何现存的容器控件(如 Frame 控件)中。用户控件或 ActiveX 文档也可以作为一个容器。
举例: //在picture1上面添加一个commandbutton
Private Sub Form_Load()
Form1.Controls.Add "VB.CommandButton", "cmdOk", Picture1
With Form1!cmdOk
.Visible = True
.Width = 500
.Caption = "确认(&Y)"
End With
End Sub
重点:当您添加一个未引用的需要许可证的控件到一个现存的(已部署好的)应用程序时,在使用 Add 方法之前您必须也添加这个控件的许可证关键字。
在运行时添加未引用的控件:
您也可以利用 Add 方法来动态添加一个在工程中没有被引用的控件。(“未引用的”控件是不出现在 Toolbox 中的控件)。为此,您必须也把控件的License 关键字添加到 Licenses 集合中。下面的示例中在添加控件本身之前添加了控件的许可证关键字:
Option Explicit
Private WithEvents extCtl As VBControlExtender
Private Sub Form_Load()
Licenses.Add "prjWeeks.WeeksCtl", "xydsfasfjewfe"
Set extCtl = Form1.Controls.Add("prjWeeks.WeeksCtl", "ctl1")
extCtl.Visible = True ' The control is invisible by default.
End Sub
但是,为了编程这样一个未引用控件的事件,您必须使用 WithEvents 关键字声明一个对象变量为VBControlExtender 对象(如上),并且设置该对象变量到Add 方法返回的引用上。然后,利用VBControlExtender 对象的 ObjectEvent事件来编程该控件的事件。下面是一个简单的例子。
Option Explicit
Dim WithEvents objExt As VBControlExtender '声明 Extender 变量
Private Sub LoadControl()
Licenses.Add "Project1.Control1", "xydsfasfjewfe"
Set objExt = Controls.Add("Project1.Control1", "myCtl")
objExt.Visible = True
End Sub
Private Sub extObj_ObjectEvent(Info As EventInfo)
'使用 Select Case 编程控件的事件。
Select Case Info.Name
Case "Click"
'这里处理 Click 事件。
'现在显示其他的 case
Case Else '未知事件
'这里处理未知事件。
End Select
End Sub
Note: 不能把一个固有的控件指定给这个 VBControlExtender 变量; 任何这种试图将引起类型不匹配错误。
但是,您也可以通过使用 WithEvents 关键字声明一个对象变量,并且设置该方法返回的引用为该变量,从而编程一个动态添加控件的事件,如下所示。
Option Explicit
'声明对象变量为 CommandButton 。
Private WithEvents cmdObject As CommandButton
Private Sub Form_Load()
Set cmdObject = Form1.Controls.Add("VB.CommandButton", "cmdOne")
cmdObject.Visible = True
cmdObject.Caption = "Dynamic CommandButton"
End Sub
Private Sub cmdObject_Click()
Print "This is a dynamically added control"
End Sub
如果希望添加一个用户控件或任何 ActiveX 控件到您的窗体,必须或者把这个控件添加到“工具箱”,或者把控件的 License 关键字添加到 Licenses集合中。有关详细信息请参阅“增加方法 (Licenses 集合)”。
注意:如果您添加一个 ActiveX 或用户控件到您的工程,但是没有在窗体中使用它,您也必须不要选定“工程属性”对话框的“生成” 选项卡上的“删除有关未使用的 ActiveX 控件”选项。如果您的应用程序试图添加该控件,那么该 Add 方法将失败,因为必需的信息已经被丢弃。
评论: 0 | 引用: 0 | 查看次数: 1910
发表评论
你没有权限发表留言!