谢谢

解决方案 »

  1.   

    学了很多的 VB 知识,我们的编程技术渐渐进入一个更高的层次,这个时候,往往会自己设计一些方法来解决具体问题或为程序增添一些锦上添花的功能,但是我们经常碰到当一个方法可行的时候,应用这个方法到多个对象上要花费很大的重复工作量。比如你打算给你的标签制作一种不断闪动的效果,你想到了用一个 Timer 控件来改变标签的前景色,但是你的界面上有20几个标签都想要有这个效果的时候(当然只是打个比方,如果这么做的话,我认为界面反而不好看),你必须针对每一个标签处理一次,这个时候,即使已经学会了使用自己的过程的你,也觉得这功夫派不上什么大用场了。事实不只这种简单的情况,但总之如果我们希望有一种方法可以针对一个对象来解决一个问题,之后就可以很方便的使有同样问题的其它同类型对象也得到解决,以前的知识就不足够了,所以这里将告诉你如何运用 VB 的一种方便的编程方法:自定义控件。
      一般情况下,你不需要做什么特别的准备,如果你的程序需要一个闪动的标签,那么你可以在你的工程里加入一个用户控件:这时 VB 弹出如图2的对话框:选择 UserControl 后按确定,然后就可以开始设计你的控件并为其加入适当的代码。你可以在工程管理器看到你的工程多出了一个类别:User Controls,你的窗体在 Forms 列表中,而用户控件都在 User Controls 列表中:现在可以使用窗体设计器在这个用户控件上面随便放些其它东西,就像一个窗体一样:但是注意这个“窗体”实际上它将成为你的控件在设计时的界面:在代码编辑器中你也可以发现,设计一个用户控件时跟设计窗体是很相似的,只是对象变成用户控件,Form 对象现在变成 UserControl 对象,可触发的事件不一样而已。
      其实说到闪动的标签,我只是随口说说用来举例子,因为原本我打算用一个稍微复杂的例子:模拟时钟,但是我犹豫了好一段时间,因为里面涉及的知识已经有相当一部分不属于基础的范畴。当闪动的标签在我脑海中一闪而过的时候,我马上改变了主意,因为你将可以在闪动的标签:FlickerLabel 这个例子中最大限度地学到的属于基础的内容,而难度属于—低。
      先看看完成后的 FlickerLabel:图中是闪动时的样子,因为是静止的图片,所以看不到动态的效果。现在新建一个工程,然后添加一个用户控件,把控件命名为 FlickerLabel,把它的尺寸改小一点,这个大小将是控件被拖放到窗体上的默认大小,太大了影响美观。接着在控件的窗体设计器中为控件添加一个标签,把标签命名为 lblFlicker。然后可以开始为控件加入属性,这些属性将会和其它控件一样,当控件放在窗体上时可以通过属性窗口设置。通常我们可以通过自己写代码来加入属性,也可以通过 Tools 菜单的 Add Procedure 来添加然后修改。如果通过菜单添加,VB 其实是自动为你写几行固定格式的代码,免去你手工输入的麻烦。选择 Add Procedure 后,会出现这样一个对话框:选择 Property,然后为属性起一个名字,如 Caption,VB 就在你的代码编辑器里自动生成几行这样的代码:Public Property Let Caption(ByVal vNewvalue As Variant)
    End Property
    Public Property Get Caption() As Variant
    End Property
    Property Get 和 Property Let 是两个相对的属性,Get 过程是当取该属性的值的时候被调用的,你可以在里面写一些适当的运算然后把结果返回,即赋给该属性名,其实就像函数一样;而 Let 过程是当赋值给该属性时被调用的,vNewvalue 是被赋的值,你可以把得到的 vNewvalue 的值按自己的需要作任何用途。如果你手动输入上面的代码,你要确保 Get 和 Let 后面的属性名应该相同,类型也要相同,如果你是通过自动生成得到代码的,你在修改时也要注意类型。如果你希望建一个只读的属性,只要把 Let 过程删除就行了。不要被自动生成的名字 vNewvalue 所束缚,你应该改一个更好的参数名,比如 sNewCaption 或 sCaption。因为我们的 Caption 将要用作标签的标题,就像 Label 控件的 Caption 属性,所以我们把类型定为 String。那么 FlickerLabel 的 Caption 属性就应该是这样的:Public Property Let Caption(ByVal sCaption As String)
    End Property
       
    Public Property Get Caption() As String
    End Property
    好了,加了用户控件之后,你的工具箱会出现一个代表你的控件的图标,你可以试试把控件放在 Form1 上面(你必须先关掉控件的窗体设计器才行)。你可以在属性窗口看到 Caption 了吗?不过现在设置这个属性没什么用,因为我们没有写下任何有用的代码,我们在改变 Caption 属性时,要改变的是 lblFlicker 的 Caption,这样我们的控件才能显示文字,所以 Caption 属性应该这样完成:Public Property Get Caption() As String
      Caption = lblFlicker.Caption
    End Property
       
    Public Property Let Caption(ByVal sCaption As String)
      lblFlicker.Caption = sCaption
      PropertyChanged "Caption"
    End Property
    这样就把为 Caption 赋的值赋给了 lblFlicker,而取 Caption 的时候,返回的是 lblFlicker 的 Caption 值。你会发现有一句 PropertyChanged 写在 Let 里面,这个方法是用户控件特有的,用来通知属性编辑器有一个属性被改变了,系统会根据需要记下被改变的属性以便作适当处理(通常是保存属性值),但是我们不需要理会,只要我们做好这个环节即可。后面带的一个参数是属性名,通常我们只写在 Let 过程里,因为一般在 Let 过程里才有去改变一个属性的值(虽然改不改变是由我们自己控制的,但是如果不这么做的话显然不合理,以后你还会接触到 Set 过程,同样是赋值)。这样处理之后,控件就能改变属性了,但是还不能保存属性的设置。要使每一次程序启动的时候,我们所使用的控件在设计时改过的属性值都能保留下来并应用到控件中,这一步是需要我们做一点工作的:Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
      lblFlicker.Caption = PropBag.ReadProperty("Caption", Ambient.DisplayName)
    End Sub
       
    Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
      PropBag.WriteProperty "Caption", lblFlicker.Caption
    End Sub
    用户控件的这两个事件,分别是设计时的属性值在被读取和被保存时,由系统调用的。这个时候的 PropBag 参数,保留了控件在属性窗口中改过的属性值,在运行时一个控件被创建之后,系统会调用 ReadProperties 过程,这个时候我们将 PropBag 中保存的属性值赋给我们的各个属性,而 WriteProperties 是在窗体文件保存时被调用的,VB 要在这个时候把各个控件的属性值保存起来,所以在这里我们把我们的属性的值写到 PropBag 中去。要注意 PropBag.ReadProperty 的第二个参数是可选的,但是我们应该写上,因为当 VB 没有在 PropBag 中找到指定属性的时候,会使用默认值代替,如果省略了默认值,在找不到的时候就出错了(按正常顺序建立的控件不会出现这种情况,但是我们的控件经常反复修改,对于我们来说就不是正常建立的顺序了)。
      Ambient 对象提供的是给用户控件的一些信息,比如是不是运行时等,DisplayName 取得的是控件的默认名字。你应该还有印象,一个控件在刚放上窗体的时候,自动被名为 Label1, Label2……我们这里这么做是为了让这个控件在刚被放上去时标题显示自己的名字,不会空白一片。
    做完这几步,我们就完成一个控件的一个属性的设计了,现在你已经可以为 Form1 上的 FlickerLabel 控件改变 Caption 属性,而且它们都能被保存下来,下次启动的时候,它们也能自己用设计时改变的属性值代替默认值了。如果你窗体上的用户控件变成这个样子:
    你可以在窗体上点击鼠标右键,选 UpdateUserControls 就行了:
    一个控件的属性基本上就是经过这几个步骤来完成的,它们的差别只在于类型和处理的不同而已。接下来我们还要给这个控件加入一些新属性:前景色:ForeColor
    背景色:BackColor
    闪动前景色:FlickerForeColor
    闪动背景色:FlickerBackColor这几个属性是用来记录反复变化的颜色的,由于 Label 控件只有一个 ForeColor 和一个 BackColor 属性,如果颜色变了自然就变不回来,所以我们需要自己定义变量来记录这些颜色,为用户控件加入下面这四个模块级的变量:Private m_rgbForeColor As OLE_COLOR
    Private m_rgbFlickerForeColor As OLE_COLOR
    Private m_rgbBackColor As OLE_COLOR
    Private m_rgbFlickerBackColor As OLE_COLOR
    OLE_COLOR 类型是颜色,其实也相当于 Long,但是,VB 的属性编辑器会识别属性的类型,根据类型出现不同的设置选项。如果是 Long 型,在属性编辑器中将需要自己输入一个数值,如果是 OLE_COLOR,由于这个类型只用于颜色,所以属性编辑器会使用标准的设置颜色选项:
      

  2.   

    这样在选择颜色的时候就比较方便了。
      有了前面讲过的增加属性的方法,现在我们能用同样的手段加入这四个属性,只不过类型要用 OLE_COLOR 而已,所以我就不一一讲解,下面是这四个属性的代码,如果有不清楚的地方,参考一下你就能明白了:Public Property Get ForeColor() As OLE_COLOR
      ForeColor = m_rgbForeColor
    End Property
       
    Public Property Let ForeColor(ByVal rgbColor As OLE_COLOR)
      m_rgbForeColor = rgbColor
      PropertyChanged "ForeColor"
    End Property
       
    Public Property Get BackColor() As OLE_COLOR
      BackColor = m_rgbBackColor
    End Property
       
    Public Property Let BackColor(ByVal rgbColor As OLE_COLOR)
      m_rgbBackColor = rgbColor
      PropertyChanged "BackColor"
    End Property
       
    Public Property Get FlickerForeColor() As OLE_COLOR
      FlickerForeColor = m_rgbFlickerForeColor
    End Property
    Public Property Let FlickerForeColor(ByVal rgbColor As OLE_COLOR)
      m_rgbFlickerForeColor = rgbColor
      PropertyChanged "FlickerForeColor"
    End Property
       
    Public Property Get FlickerBackColor() As OLE_COLOR
      FlickerBackColor = m_rgbFlickerBackColor
    End Property
       
    Public Property Let FlickerBackColor(ByVal rgbColor As OLE_COLOR)
      m_rgbFlickerBackColor = rgbColor
      PropertyChanged "FlickerBackColor"
    End Property
    能够设置属性之后,同样需要在 ReadProperties 和 WriteProperties 那里也加入读取和记录属性值的处理,现在这两个事件就变成这样了:Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
      lblFlicker.Caption = PropBag.ReadProperty("Caption", Ambient.DisplayName)
       
      m_rgbForeColor = PropBag.ReadProperty("ForeColor", lblFlicker.ForeColor)
      m_rgbBackColor = PropBag.ReadProperty("BackColor", lblFlicker.BackColor)
      m_rgbFlickerForeColor = PropBag.ReadProperty("FlickerForeColor", lblFlicker.ForeColor)
      m_rgbFlickerBackColor = PropBag.ReadProperty("FlickerBackColor", lblFlicker.BackColor)
    End Sub
       
    Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
      PropBag.WriteProperty "Caption", lblFlicker.Caption
       
      PropBag.WriteProperty "ForeColor", m_rgbForeColor
      PropBag.WriteProperty "BackColor", m_rgbBackColor
      PropBag.WriteProperty "FlickerForeColor", m_rgbFlickerForeColor
      PropBag.WriteProperty "FlickerBackColor", m_rgbFlickerBackColor
    End Sub
    对于默认值,我全部使用了 lblFlicker 的颜色,其实如果你有意思将默认颜色改成别的,你可以使用 RGB() 函数,比如:RGB( 255, 128, 90 )
    这样的结果得到的是 R 值为 255,G 值为 128,B 值为 90 的颜色值,相信这样的函数不用再啰嗦对你已经是小菜一碟。完成了这四个属性你就可以改变标签的颜色,但是当你第一次把 FlickerLabel 放到窗体上时,你会发现颜色值一开始都是纯黑色:
    这是因为所有的属性在设计时建立的时候(即是你拖放到窗体上时),不会经历 ReadProperties 事件(可是在运行时或当你执行 Update UserControls 菜单功能时却会),所以一开始的颜色不会经过初始化,对于这个问题,在设计时可以使用用户控件的 InitProperties 事件,这个事件就是在这种情况下触发的:Private Sub UserControl_InitProperties()
      lblFlicker.Caption = Ambient.DisplayName
      m_rgbForeColor = lblFlicker.ForeColor
      m_rgbBackColor = lblFlicker.BackColor
      m_rgbFlickerForeColor = RGB(255, 0, 0)
      m_rgbFlickerBackColor = RGB(255, 255, 0)
    End Sub
    现在行了吗?再拖一个新的 FlickerLabel 试试看,就连标签也能在刚建立的时候就能变成默认控件名了(因为这里也做了处理)。
      现在我们来让标签能反复改变颜色,使用一个 Timer 控件,这个你已非常熟悉了,另外还需要一个模块级的变量,用来表示每次闪动时显示哪一组颜色:Private m_bFlicker As Boolean
    为 Timer 控件起一个名字:tmrFlicker,然后在 Timer 事件中这样处理:Private Sub tmrFlicker_Timer()
      If m_bFlicker Then
        lblFlicker.ForeColor = m_rgbForeColor
        lblFlicker.BackColor = m_rgbBackColor
      Else
        lblFlicker.ForeColor = m_rgbFlickerForeColor
        lblFlicker.BackColor = m_rgbFlickerBackColor
      End If
      m_bFlicker = Not m_bFlicker
    End Sub
    每一次触发的时候根据 m_bFlicker 的值,使 lblFlicker 的颜色改变,然后把 m_bFlicker 的值反过来,下一次就能取另外一组颜色了。现在给 tmrFlicker 设置 Inerval 值为 500,然后再试试控件,你已经能看到控件在一次次的闪动了:
    可是就连设计时它也一闪一闪的,这个时候的 tmrFlicker 中的代码是一直在执行的,虽然看上去挺有意思,可是设计时总是有一段代码不断被执行,对于系统来说工作就比较繁重,如果你的机子不够快,你还可以看到代码编辑器的标题也在闪动,因为它正在一行行的执行呢。显然这样子叫做“设计时”不太合理了,所以我们还是把 tmrFlicker 的 Enabled 属性设置为 False,停止 tmrFlicker 在设计时的运行。如果希望它在运行时能自动变 Enabled 为 True,可以在用户控件的 Show 事件中这样改变:Private Sub UserControl_Show()
      tmrFlicker.Enabled = Ambient.UserMode
    End Sub
    Show 事件是在控件被显示的时候触发的,无论是在设计时还是运行时,这个时候我们利用 Ambient.UserMode 来得知是不是运行时,运行时的 UserMode 值是 True,设计时是 False。其实你也可以在 ReadProperties 等其它适当的事件中设置 tmrFlicker 的 Enabled 属性,这完全取决于该事件中做这件事能不能解决问题(自己动手试啦)。
      嗯……运行了你的程序了没有?是不是很不错了?但是有点美中不足,就是标签的大小没有改变。那么现在把这个问题也解决掉,而且我们还要保留标签的 AutoSize 属性,让 FlickerLabel 也能自动匹配大小:Public Property Let AutoSize(ByVal bAuto As Boolean)
      lblFlicker.AutoSize = bAuto
      UserControl_Resize
      PropertyChanged "AutoSize"
    End Property
       
    Public Property Get AutoSize() As Boolean
      AutoSize = lblFlicker.AutoSize
    End Property
       
    Private Sub UserControl_Resize()
      On Error Resume Next
      If lblFlicker.AutoSize Then
        lblFlicker.Move 0, 0
        UserControl.Width = lblFlicker.Width + 20
        UserControl.Height = lblFlicker.Height + 20
      Else
        lblFlicker.Move 0, 0, UserControl.Width, UserControl.Height
      End If
    End Sub
    上面多了一个 AutoSize 属性和 Resize 事件,AutoSize 属性通过改变 lblFlicker 的 AutoSize 属性使标签能自动匹配大小,而用户控件的 Resize 事件和窗体的 Resize 事件类似,在里面我根据情况处理了控件的大小:如果标签已经被设置为 AutoSize,那么把控件本身的大小设置成和 lblFlicker 的大小相同(尝试后觉得加大一点点宽度看起来比较美观,所以加了20),如果标签没有设置为 AutoSize,则改变 lblFlicker 的大小以匹配控件的大小。在 AutoSize 的 Let 过程中,我还调用了控件的 Resize 过程,是为了在该属性被改变的时候,能马上看到改变的效果,你可以去掉这一行看看在改变 FlickerLable 的 AutoSize 属性时出现了什么问题。
      最后,别忘了在 ReadProperties 和 WriteProperties 加入对 AutoSize 属性值的读取和记录,现在我给出这两个事件完整的代码,注意有几句原本没有的程序,使控件的一些细节解决的更好:Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
      lblFlicker.AutoSize = PropBag.ReadProperty("AutoSize", False)
      lblFlicker.Caption = PropBag.ReadProperty("Caption", Ambient.DisplayName)
       
      m_rgbForeColor = PropBag.ReadProperty("ForeColor", lblFlicker.ForeColor)
      m_rgbBackColor = PropBag.ReadProperty("BackColor", lblFlicker.BackColor)
      m_rgbFlickerForeColor = PropBag.ReadProperty("FlickerForeColor", lblFlicker.ForeColor)
      m_rgbFlickerBackColor = PropBag.ReadProperty("FlickerBackColor", lblFlicker.BackColor)
       
      '读取属性后,改变标签的颜色使设置能即时显现
      lblFlicker.ForeColor = m_rgbForeColor
      lblFlicker.BackColor = m_rgbBackColor
    End Sub
       
    Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
      PropBag.WriteProperty "AutoSize", lblFlicker.AutoSize
      PropBag.WriteProperty "Caption", lblFlicker.Caption
       
      PropBag.WriteProperty "ForeColor", m_rgbForeColor
      PropBag.WriteProperty "BackColor", m_rgbBackColor
      PropBag.WriteProperty "FlickerForeColor", m_rgbFlickerForeColor
      PropBag.WriteProperty "FlickerBackColor", m_rgbFlickerBackColor
    End Sub
      整个控件到这里就已经完成了,不过我还想进行改进,为它加入设置 Interval 值和是否闪动的功能,虽然不是必要,但也算锦上添花吧,可是这一期的内容已经结束了,所以这个任务就交给读者你去完成吧!
      

  3.   

    http://www.hebeif.com/kjxy/studycomputer/program/vb/vb15/fcbgbe.htm
    http://163.13.112.100/_files/_OS/VB60/deve.60/17.htm
    这里也有点资料,太长了,去看看嘛~~