回 帖 发 新 帖 刷新版面

主题:跟我学做电子数码时钟控件

今天发个贴,通过制作电子数码时钟,了解VFP可视化类的应用及制作过程,希望对老狐狸的初学者和爱好者们有帮助。
注意:Ilikefox用英文版VFP9,考虑到可能有用汉化版本的朋友,因此描述时用了英汉对照(呵呵,可能英译汉有不太准确的地方)。另外本贴适合9版本,以前版本大部分适合。帖子长一些,如果边看边动手并坚持到底,那可能对你有帮助。

1.设计目标
生成一个电子数码时钟控件,能够像其它控件一样放置到表单上,运行时自动显示时钟。

2.设计思路
思路一:
用标签控件显示 HH:MM:SS ,这种方式实现起来很方便、编程最简单,核心语句如:
 Label.Caption=Time() 即可。(注:描述思路时所用的语句不一定严格遵循语法)
缺点是外观不太像(太不像)电子数码管显示,因为系统的字体没有一个能表现出电子数码管的样子。可能的解决方法是在网上下载一个电子数码字形库(数码管字体),当然这需要在开发机和其它机器上安装该字体文件。这个思路供大家参考,本例不采用。

思路二:
用图片控件Image实现,其中的 HH:MM:SS 用图片文件方式展示在Image控件中。这需要分别制作显示0-9的十个图片文件以及一个显示冒号(:)的图片,用8个Image分别显示。可以从网上找图片,也可以自己制作。我不知道网上好不好找(呵呵,我压根儿就没去找),自己制作的话可以想想Windows的扫雷游戏,上面不是正好有我们需要的数码管显示吗?!截图下来修改修改,就得到了。这个思路比起上一个实现起来稍复杂一点,首先是图片的制作,其次编程量要多一点。从Time()函数得到的时间字符串拆成一个个字符(主要是时分秒),根据它们决定某个Image显示哪个图片。有个缺点就是颜色固定,如果想灵活使用不同的颜色,得制作几组图片(比如红色的0-9:、绿色的0-9:等等)。这个思路同样供大家参考,本例不采用。

思路三:
用线条控件Line拼出0-9的数码管显示的样子,这听起来很麻烦(我们需要很多Line,横的、竖的),不过没有必要一一拼出0-9十个数码,只要拼出一个8就行了。之后用亮、暗两色设置这些Line的BorderColor就能显示全部数码(呵呵,这和实际的数码管显示原理一样)。用一个容器类装入这些拼成8的Line,换句话叫封装起来,一个显示单个数码的类就有了。然后再把数个显示单个数码的类用一个容器类封装起来,就可以了。好了,说了大致思路。本例采用这个思路,一些具体考虑在下面的实现步骤中现场解释。

回复列表 (共10个回复)

沙发

3.实现步骤

(1)建一个文件夹"DigitalClock"(意为数字时钟,这个命名以及下面的一些文件名啊、对象名啊等等命名你都可以按自己的意愿来起,不过暂且照着做),并设置为工作路径。

(2)新建项目"DigitalClock.pjx",选择项目管理器的“类”(Classes)页面,单击“新建”(New)。在弹出的“新类”(New Class)对话框中的"类名"(Class Name)后面填入"DigitalTube"(意为数码管)。接下来在"基于类"(Based On)后选一个容器类。哪个容器类?Container(容器)???很多情况下当我们需要一个东西来装一些控件时,往往选择Container,不过这回我们选择Control(控件)。Control类也是个容器,不过它包含的控件是私有的,即外界无法访问。如果你曾经在Container中放置过其它控件,设计时属性窗口能看到它包含的那些控件,它们是公有的,无论设计时还是运行时都能访问它们。而Control包含的控件,只能由Control自身访问,外界无法接触它们,换句话说,封装得更好。(后面还有关于这一点的描述,那时你能体会到好处)。言归正传,接下来在"存储于"(Store In)后面填入"DigitalClock.vcx"。单击"确定"(OK)。

(3)现在出现了类设计器,我们即将向Control类中添加多个线条来拼出8这个数码。多少个横线?多少个竖线?每个多长?位置怎么摆放?呵呵,一堆问题来了(想立刻开始加线条的请直接看帖子下一段)。我们需要个数码管显示的样板,OK,工作暂停,我们来玩扫雷游戏,多少挖几个雷,只要有"8"出现在数码显示里,不玩了,截取当前扫雷窗口(Alt+PrintScreen),关闭扫雷打开画图,粘贴出来,放大显示那个"8"(你能放大到800%)。现在你有样板了,这个"8"字共3横4竖,横和竖都是3个像素宽,Line控件的默认线条宽度是1个像素,那需要(3+4)*3=21个线条,有人可能想,我把Line的BorderWidth设为3,不就只要7个嘛。No,那很难看。不过可以少一点,用13个(有粗有细)。另外如果你沿着图片中"8"的边缘把它剪切下来,重新粘贴到一个新建画图里,可以更好的定位线条的位置以及这个"8"图案的大小。经过一番分析,下面开始。

添加一个横向的Line,命名为linUH1(意为上水平线1 Upper Horizontal),Left:2,Top:1,Width:9。
添加一个横向的Line,命名为linUH2(意为上水平线2),BorderWidth:3,Left:4,Top:2,Width:4。
添加一个横向的Line,命名为linMH(意为中间水平线 Middle Horizontal),BorderWidth:3,Left:3,Top:11,Width:6。
添加一个横向的Line,命名为linLH1(意为下水平线1 Lower Horizontal),Left:2,Top:21,Width:9。
添加一个横向的Line,命名为linLH2(意为下水平线2),BorderWidth:3,Left:4,Top:20,Width:4。

添加一个竖向的Line,命名为linULV1(意为上左竖线1 Upper Left Vertical),Height:9,Left:1,Top:2。
添加一个竖向的Line,命名为linULV2(意为上左竖线2),BorderWidth:3,Height:4,Left:2,Top:4。
添加一个竖向的Line,命名为linURV1(意为上右竖线1 Upper Right Vertical),Height:9,Left:11,Top:2。
添加一个竖向的Line,命名为linURV2(意为上右竖线2),BorderWidth:3,Height:4,Left:10,Top:4。
添加一个竖向的Line,命名为linLLV1(意为下左竖线1 Lower Left Vertical),Height:9,Left:1,Top:12。
添加一个竖向的Line,命名为linLLV2(意为下左竖线2),BorderWidth:3,Height:4,Left:2,Top:14。
添加一个竖向的Line,命名为linLRV1(意为下右竖线1 Upper Right Vertical),Height:9,Left:11,Top:12。
添加一个竖向的Line,命名为linLRV2(意为下右竖线2),BorderWidth:3,Height:4,Left:10,Top:14。

妈妈呀!最累的事情结束了。你能看到一个黑色的"8"了,你可以用鼠标划个框把这些Line全选中,设置BorderColor为红色(当然这并不重要,呵呵,但是请照做)。现在设置DigitalTube的Height为23,Width为13,BorderWidth为0,BackColor为0。OK,数码管的样子有了。

板凳

(4)接下来重要的事情来了,我们希望DigitalTube类有个Value属性,给它个数码,就能立刻显示。就像我们给文本框的Value一个值,它就能显示出来一样。在主菜单的"类"(Class)菜单中选择"新属性"(New Property),弹出新属性对话框,命名属性为"Value",接着注意要选中Assign Method,然后单击"加入"(Add),关闭该对话框。

--相关的题外话:
Assign方法是对对象某个属性附加的方法,当我们用语句给该属性赋值时,该方法的代码就要执行。你可能已经猜到,只要我们在这个方法中加入代码对那些横线、竖线设置亮、暗色,就能实现给个数码值立刻显示该数码。

现在在属性窗口中你能看到新加入的"value"属性和"value_assign"方法。在哪里啊?一般位于属性窗口"全部"(All)页面的最底下。双击"value_assign",打开代码窗口,接下来写如下代码:(代码稍长,你不用一个一个敲,复制下面,粘贴到代码窗口,覆盖掉老狐狸自动写的几条。

3 楼

LPARAMETERS vNewVal
LOCAL nBrightColor, nDarkColor, nValue
This.Value = vNewVal
nBrightColor=this.ForeColor  &&利用 DigitalTube类的前景色作为亮色
nDarkColor=BITAND(BITRSHIFT(nBrightColor, 1), 0x4F4F4F)  &&计算出暗色

*!*下一句来点灵活性扩展,使Value既可以是0-9数值也可以是单个数码字符
nValue=IIF(VARTYPE(vNewVal)="C", ASC(vNewVal)-48, vNewVal)

DO CASE
CASE nValue=9
     this.linUH1.BorderColor = nBrightColor
     this.LinUH2.BorderColor = nBrightColor
     this.LinMH.BorderColor = nBrightColor
     this.LinLH1.BorderColor = nBrightColor
     this.LinLH2.BorderColor = nBrightColor
     this.linULV1.BorderColor = nBrightColor
     this.linULV2.BorderColor = nBrightColor
     this.linURV1.BorderColor = nBrightColor
     this.linURV2.BorderColor = nBrightColor
     this.linLLV1.BorderColor = nDarkColor
     this.linLLV2.BorderColor = nDarkColor
     this.linLRV1.BorderColor = nBrightColor 
     this.linLRV2.BorderColor = nBrightColor
CASE nValue=8
     this.LinUH1.BorderColor = nBrightColor
     this.LinUH2.BorderColor = nBrightColor 
     this.LinMH.BorderColor = nBrightColor
     this.LinLH1.BorderColor = nBrightColor
     this.LinLH2.BorderColor = nBrightColor
     this.linULV1.BorderColor = nBrightColor
     this.linULV2.BorderColor = nBrightColor
     this.linURV1.BorderColor = nBrightColor
     this.linURV2.BorderColor = nBrightColor
     this.linLLV1.BorderColor = nBrightColor
     this.linLLV2.BorderColor = nBrightColor
     this.linLRV1.BorderColor = nBrightColor
     this.linLRV2.BorderColor = nBrightColor
CASE nValue=7
     this.LinUH1.BorderColor = nBrightColor
     this.LinUH2.BorderColor = nBrightColor
     this.LinMH.BorderColor = nDarkColor
     this.LinLH1.BorderColor = nDarkColor
     this.LinLH2.BorderColor = nDarkColor
     this.linULV1.BorderColor = nDarkColor
     this.linULV2.BorderColor = nDarkColor
     this.linURV1.BorderColor = nBrightColor 
     this.linURV2.BorderColor = nBrightColor
     this.linLLV1.BorderColor = nDarkColor
     this.linLLV2.BorderColor = nDarkColor
     this.linLRV1.BorderColor = nBrightColor 
     this.linLRV2.BorderColor = nBrightColor

4 楼

CASE nValue=6
     this.LinUH1.BorderColor = nBrightColor
     this.LinUH2.BorderColor = nBrightColor 
     this.LinMH.BorderColor = nBrightColor
     this.LinLH1.BorderColor = nBrightColor
     this.LinLH2.BorderColor = nBrightColor
     this.linULV1.BorderColor = nBrightColor
     this.linULV2.BorderColor = nBrightColor
     this.linURV1.BorderColor = nDarkColor
     this.linURV2.BorderColor = nDarkColor
     this.linLLV1.BorderColor = nBrightColor
     this.linLLV2.BorderColor = nBrightColor
     this.linLRV1.BorderColor = nBrightColor
     this.linLRV2.BorderColor = nBrightColor
CASE nValue=5
     this.LinUH1.BorderColor = nBrightColor
     this.LinUH2.BorderColor = nBrightColor
     this.LinMH.BorderColor = nBrightColor
     this.LinLH1.BorderColor = nBrightColor
     this.LinLH2.BorderColor = nBrightColor
     this.linULV1.BorderColor = nBrightColor
     this.linULV2.BorderColor = nBrightColor
     this.linURV1.BorderColor = nDarkColor
     this.linURV2.BorderColor = nDarkColor
     this.linLLV1.BorderColor = nDarkColor
     this.linLLV2.BorderColor = nDarkColor
     this.linLRV1.BorderColor = nBrightColor
     this.linLRV2.BorderColor = nBrightColor
CASE nValue=4
     this.LinUH1.BorderColor = nDarkColor
     this.LinUH2.BorderColor = nDarkColor
     this.LinMH.BorderColor = nBrightColor 
     this.LinLH1.BorderColor = nDarkColor
     this.LinLH2.BorderColor = nDarkColor
     this.linULV1.BorderColor = nBrightColor
     this.linULV2.BorderColor = nBrightColor
     this.linURV1.BorderColor = nBrightColor
     this.linURV2.BorderColor = nBrightColor
     this.linLLV1.BorderColor = nDarkColor
     this.linLLV2.BorderColor = nDarkColor
     this.linLRV1.BorderColor = nBrightColor 
     this.linLRV2.BorderColor = nBrightColor
CASE nValue=3
     this.LinUH1.BorderColor = nBrightColor
     this.LinUH2.BorderColor = nBrightColor 
     this.LinMH.BorderColor = nBrightColor
     this.LinLH1.BorderColor = nBrightColor
     this.LinLH2.BorderColor = nBrightColor
     this.linULV1.BorderColor = nDarkColor
     this.linULV2.BorderColor = nDarkColor
     this.linURV1.BorderColor = nBrightColor 
     this.linURV2.BorderColor = nBrightColor
     this.linLLV1.BorderColor = nDarkColor
     this.linLLV2.BorderColor = nDarkColor
     this.linLRV1.BorderColor = nBrightColor 
     this.linLRV2.BorderColor = nBrightColor
CASE nValue=2
     this.LinUH1.BorderColor = nBrightColor
     this.LinUH2.BorderColor = nBrightColor 
     this.LinMH.BorderColor = nBrightColor
     this.LinLH1.BorderColor = nBrightColor
     this.LinLH2.BorderColor = nBrightColor
     this.linULV1.BorderColor = nDarkColor
     this.linULV2.BorderColor = nDarkColor
     this.linURV1.BorderColor = nBrightColor 
     this.linURV2.BorderColor = nBrightColor
     this.linLLV1.BorderColor = nBrightColor 
     this.linLLV2.BorderColor = nBrightColor 
     this.linLRV1.BorderColor = nDarkColor
     this.linLRV2.BorderColor = nDarkColor
CASE nValue=1
     this.LinUH1.BorderColor = nDarkColor
     this.LinUH2.BorderColor = nDarkColor 
     this.LinMH.BorderColor = nDarkColor 
     this.LinLH1.BorderColor = nDarkColor
     this.LinLH2.BorderColor = nDarkColor 
     this.linULV1.BorderColor = nDarkColor
     this.linULV2.BorderColor = nDarkColor
     this.linURV1.BorderColor = nBrightColor
     this.linURV2.BorderColor = nBrightColor
     this.linLLV1.BorderColor = nDarkColor
     this.linLLV2.BorderColor = nDarkColor
     this.linLRV1.BorderColor = nBrightColor
     this.linLRV2.BorderColor = nBrightColor
CASE nValue=0
     this.LinUH1.BorderColor = nBrightColor
     this.LinUH2.BorderColor = nBrightColor
     this.LinMH.BorderColor = nDarkColor
     this.LinLH1.BorderColor = nBrightColor
     this.LinLH2.BorderColor = nBrightColor
     this.linULV1.BorderColor = nBrightColor
     this.linULV2.BorderColor = nBrightColor
     this.linURV1.BorderColor = nBrightColor
     this.linURV2.BorderColor = nBrightColor
     this.linLLV1.BorderColor = nBrightColor
     this.linLLV2.BorderColor = nBrightColor
     this.linLRV1.BorderColor = nBrightColor
     this.linLRV2.BorderColor = nBrightColor
ENDCASE

5 楼

好了,关闭代码窗口,DigitalTube类差不多建好了,关闭类设计器,回到项目管理器。

(5)测试一下这个DigitalTube类:
新建一个表单form1,添加一个计时器Timer1。在控件工具栏上单击"书本"图案的按钮,单击"添加"(Add),在打开对话框中选择DigitalClock.vcx打开,控件工具栏出现digitaltube按钮(图标怪怪的),单击它,然后在表单上单击,哈哈,出现数码管digitaltube1对象。设置Timer1的interval属性为1000,双击Timer1,打开Timer事件代码窗口,输入如下代码:
ThisForm.digitaltube1.value=right(time(),1)

运行表单,数码管开始显示。关闭表单,设置digitaltube1对象的ForeColor为其它颜色,再次运行看效果。

--相关的题外话:
正因为使用Assign方法,我们只需要给Value一个数码字符(数值0-9也行),它就自动执行写在Assign方法中的代码去设置各个Line的颜色,从而呈现出对应数码的样子。另外,在表单设计器属性窗口,digitaltube1对象看不到它里面包含的那些Line控件,如果刚才建DigitalTube类时选用Container,那就可以看到里面的13个Line控件,并且可以直接访问它们。问题是需要直接访问那些Line吗?NO,不需要。封装的目的就是为了隐藏细节,对外提供尽可能简单的接口,一旦封装好了,就没有必要关心它内部是怎样做了,我们的DigitalTube类就像一个单独的控件。

(6)做一点专业的改进
刚才你如果细心,你应该能看到在表单设计器属性窗口,digitaltube1对象的Value属性显示为小写的"value",我们希望它能显示为"Value"才看着专业,就像文本框或其它控件的Value一样。同时在表单设计器属性窗口还能看到digitaltube1对象的"value_assign"方法,该方法后面写着谈蓝色的类似"[Inherited DIGITALTUBE...."字样的文字,意思是指该方法的代码继承于DIGITALTUBE类。有点糟糕的是如果你双击"value_assign"方法打开代码窗口,胡乱写点东西,将会替代我们在DIGITALTUBE类中编写的代码。这可能破坏我们辛辛苦苦编写的成果。我们希望它不被看到(隐藏起来),这样就不会被用户乱改,术语叫做:让value_assign方法对用户"透明"。我们来做一点改进。

关闭表单设计器(如果你没有关闭的话),重新在项目管理器中找到digitaltube类,单击"修改"(Modify),打开类设计器,从主菜单的"类"(Class)菜单中选择"成员数据编辑器"(MenberData Editor...),现在弹出成员数据编辑器窗口,在左边的成员列表框中找到Value选中,在右边"设置"(Settings)页面选中"拥有成员数据"(Has MemberData),注意到下面的一些东东开始有效,不用管太多,只要"显示为"(Display As)后面是"Value"就行了,单击"确定"(OK),关闭该窗口。哈哈,立刻注意到旁边属性窗口中原先的value变成Value了。你还会注意到多了一个属性"_memberdata",正是该属性的建立及设置实现了我们想要的效果(该属性请自行参考Help了解)。

接下来我们来隐藏value_assign方法,从主菜单的"类"(Class)菜单中选择"编辑属性/方法"(Edit Property/Method...),现在弹出编辑属性/方法窗口,列表框中找到value_assign选中,在右边的""(Visibility)后选择"隐藏"(Hidden),然后单击"应用"(Apply)。使用同样方法顺便把_memberdata也隐藏。最后关闭这个窗口。

关闭类设计器窗口,重新打开表单form1的设计器,妙极了,属性窗口再也看不到value_assign,那个Value看着也很专业。

6 楼

(7)不错不错,万里长征走完一大半。接下来我们要再次封装DigitalTube,构成一个显示时分秒的类。
仍旧选择项目管理器的“类”(Classes)页面,单击“新建”(New)。在弹出的“新类”(New Class)对话框中的"类名"(Class Name)后面填入"DigitalPanel"(意为数字面板)。接下来在"基于类"(Based On)后选Control,然后确定。现在弹出了类设计器,把DigitalPanel适当拉大一些,从控件工具栏上加入6个digitaltube到DigitalPanel中,分别命名为dgtH1、dgtH2、dgtM1、dgtM2、dgtS1、dgtS2。全选中它们,设置BackStyle为0(透明),Top为0,接着分别设置:
dgtH1--Left:0
dgtH2--Left:13
dgtM1--Left:39
dgtM2--Left:52
dgtS1--Left:78
dgtS2--Left:91
嗯,样子差不多了,少了冒号。放入4个形状控件(shape),分别命名为shpC11、shpC12、shpC21、shpC22。全选中它们,设置BackColor为255(红色),BoarderWidth为0,Height为4,Width为4。接着分别设置:
shpC11--Left:30,Top:6
shpC12--Left:30,Top:14
shpC21--Left:70,Top:6
shpC22--Left:70,Top:14

设置DigitalPanel的Height为23,Width为104,BorderWidth为0,BackColor为0。OK,数字面板的样子有了。

(8)同样在主菜单的"类"(Class)菜单中选择"新属性"(New Property),弹出新属性对话框,命名属性为"Value",接着选中Assign Method,单击"加入"(Add),关闭该对话框。

属性窗口中双击"value_assign",打开代码窗口,接下来写如下代码:

lparameters vNewVal
This.Value = vNewVal
this.dgtH1.Value=LEFT(vNewVal,1)
this.dgtH2.Value=SUBSTR(vNewVal,2,1)
this.dgtM1.Value=SUBSTR(vNewVal,4,1)
this.dgtM2.Value=SUBSTR(vNewVal,5,1)
this.dgtS1.Value=SUBSTR(vNewVal,7,1)
this.dgtS2.Value=RIGHT(vNewVal,1)

我们希望通过DigitalPanel的ForeColor属性来控制数码管的颜色,因此编写DigitalPanel的Init事件代码:

this.dgtH1.ForeColor=this.ForeColor 
this.dgtH2.ForeColor=this.ForeColor 
this.dgtM1.ForeColor=this.ForeColor 
this.dgtM2.ForeColor=this.ForeColor 
this.dgtS1.ForeColor=this.ForeColor 
this.dgtS2.ForeColor=this.ForeColor 
this.shpC11.BackColor =this.ForeColor 
this.shpC12.BackColor =this.ForeColor
this.shpC21.BackColor =this.ForeColor
this.shpC22.BackColor =this.ForeColor

好了,关闭代码窗口。想想步骤(6)中我们曾经做过的内容,这里也照猫画虎,隐藏DigitalPanel类的value_assign,改变Value的显示(忘了怎么做?晕,返回步骤(6)重看)。完成后,关闭类设计器,回到项目管理器。

7 楼

(9)再次测试一下,打开刚才的form1,从控件工具栏(注意有两个怪怪的图标,要分清)添加一个DigitalPanel到表单,修改Timer1的Timer事件代码,加一句:
ThisForm.digitalPanel1.value=time()

运行,哈哈,电子钟开始工作了。关闭,修改DigitalPanel1的ForeColor为其它颜色(比如黄色),再次运行看看。
等等,有点小瑕疵,一开始冒号是黄色,数码还是红色,开始计时后才变黄色。

(10)好,关闭表单设计器。重新从“类”(Classes)页面找到digitaltube修改,打开类设计器。从主菜单的"类"(Class)菜单中选择"编辑属性/方法"(Edit Property/Method...),现在弹出编辑属性/方法窗口,列表框中找到ForeColor选中,在右边上部选中Assign方法,然后单击"应用"(Apply),再在左边列表框中找到forecolor_assign,同样隐藏它,单击"应用"(Apply),单击"关闭"(Close)。
在属性窗口找到forecolor_assign方法,写入如下代码:

LPARAMETERS vNewVal
THIS.ForeColor = m.vNewVal
FOR EACH lin as Line IN this.Controls
    lin.BorderColor =m.vNewVal
ENDFOR

好了,关闭,关闭,回到项目管理器。运行form1看看。

(11)最后的封装,呵呵,Ilikefox不再写了(已经做过两遍封装,步骤应该了解),留给朋友们独立完成。要做的事情是建一个DigitalClock类封装DigitalPanel和Timer,最后实现“放置到表单上,运行时自动显示时钟”的设计目标。

8 楼

学习学习

9 楼

好复杂呀。学习了... ...
顺便请赵老师如果有时间,给我们讲解一下api的使用。我现在用的api都是别人用过的总结。但是自己去查,还有很多不明白。比如:如何设置参数,常量值到什么地方去查,使用的思路是什么等待。
希望赵老师有时间给我们系统的讲解一下。

10 楼

从实践到理论,升华后再用理论来指导实践,这就是老毛的实践论的精华所在。
望博导(同时抬高双方身份,也可能单方)能发个表单,让俺先来看一下结果,如何,谢谢!

我来回复

您尚未登录,请登录后再回复。点此登录或注册