Visual Basic 6编程技术大全 第5章

前端之家收集整理的这篇文章主要介绍了Visual Basic 6编程技术大全 第5章前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

注意 * 提示 *
建议 ** 新功能 **
警告 ****
p160 等于 书里 144页. 16页差距. ' p160 = 0160.jpg = 0160.jpg.txt
代码注释部分错误请无视....

' p156
第5章VB应用程序与VB库
一般说来,Microsoft VB可以看做是VB应用程序库的总和,加上由VB库函数和VB运行时显现的一组对象.在本章中,我集中概述VBA语言和其函数命令以及一些可以应用其上的先进的或鲜为人知的技巧.可以用Object Browser工具和选择VBA库来浏览本章中涉及的大多数对象.在本章的最后部分,我介绍一些首要的系统对象,例如位丁VB库中的App和Clipboard.
5.1控制流程
所有的编程语言必须提供一种或几种不按程序清单出现的顺序执行一些语句的方法.除了调用Sub过程和Function函数之外,还可以引用所有的控制流程语句,它们分为两类:分支语句和循环语句.
5.1.1分支语句
主要的分支语句是If Else Else If End If程序块.VB支持该语句的几种不同风格,有单行和多行形式:
' Single line version,withOUt Eise clause
If x > 0 Then y = x
' Single line varsion,with Else clause
If x > 0 Then y = x Else y = O
' Single Iine,but with multlple statements separated by colons
If x > 0 Then y = x: x -O Else y = 0
' Multllline version of the above code (more readable)
If x > 0 Then
y = x
Else
y = O
End If
' An example of if Elself Else block
If x > O Then
y = x
ElseIf x < O Then
y = x * x
Else ' X is surely 0,no need to actually test it
x = -1
End If


' Single line version,without ELse clause
If x > 0 Then y = x
' Single line varsion,no need to actually test it
x = -1
End If

应该意识到If关键字之后的任何非零值都认为是True,它引发Then执行程序块.
' The Fallowing lines are equivalent.
If Value <> 0 Then Print ' Non Zero'
If Value Then Print ' Non Zero'

即使后一种表示法节省了一些键入字,但并不能提高程序运行的速度.至少是不一定的.基准测试表明:如果被测变量为Boolean.Integer或Long型,这种缩简的表示法并不能使程序运行加快.但是对于其他的数字类型,就可以期望速度得到一些适度增快,大约不超过20%.如果对于这种方法感觉很舒服,就使用它吧.但是要注意在大多数情况下,速度的提高与可读性降低是不相等的.
当利用 AND和OR 运算符将多个条件组合起来时,可以使用一些改进的优化方法.下面的例子说明了如何通过改写 Boolean 表达式来编写简洁.高效的程序.
' if two numbers are both zero. you can apply the OR operator
' to thelT bits and you still have zero.
If x = 0 And y = 0 Then
If (x Or y) = 0 Then
' If either value is < >0,you can apply the OR operator
' to their bits and you surely have a nonzero value.
If x <> 0 Or y <> 0 Then
If (x Or y) Then
' If two integer numbers have opposite signs,applying the X OR
' operator to them yields a result that has the sign
' bit set. (In other words,it is a negative vaIve.)
If (x < 0 And y >= 0) Or (x >= O And y < 0) Then
If (x Xor y) < 0 Then

当使用布尔运算符,并且由于疏忽将细微的错误引入程序中时,很容易失去控制.例如,可能相信下面两行程序是等价的,但它们不等价.(要知道原因,只要想一想数字是如何用二进制表示的.)
' Not equivalant: just try with x:3 and y-4,whose binary
' representations are 0011 and 0100 respectively.
If x <> 0 And y <> 0 Then....
If ( x And y ) Then....
' AnyWay. you can partially optimize the first line as follows
If ( x <> 0 ) And y Then....
另一个常见的模糊点是 NOT 运算符,它对数字的所有位求反.在VB中,该运算特只有在其参数为True( -1 )时才返回False,所以应该除了将之与某一个比较关系的布尔结果或者一个Boolean变量一起使用之外不要与其他任何东西一起使用.
IF Not ( x = y ) Then....' The aame as x<>y
If Not x Than….' The same as X<>-1. don't use instead of x = 0
要获得更多的信息,请参考本章后面的5.2.3"布尔和位运算符".
有一个细节会使许多从其他语言转入VB的编程者吃惊,就是If语句不支持短回环计算,换句话说,VB总是计算If语句中的整个表达式的值,即使已有足够的信息能确定表达式为False还是True,例如下面的程序段:
' lf x <= 0,lt makes no sense to evaluate Sqr ( y ) >x
' bacause the entire expresslon IS guaranteed tO be False.
If x > 0 And Sqr(y) < z Then z = 0 'p158
' if x = 0,it makes no sense to evaluate X * y > 100
' because tha entire exprassion is guarantead to be True
If x = 0 Or x * y > 100 Then z = 0
尽管VB在自动优化表达式方面还不够灵活,但并不意味着不能人工优化.可以将上面的第一个lf语句段写如下:
if x > 0 Then if Sqr ( y ) < z Then z = 0
可以将上面的第二个If语句改写如下:
if x = 0 Than
z = 0
Elself x+y > 100 Then
z = 0
EndIf
Select Case语句的通用性比If控制块略逊一筹,它只能检铡一个表达式的对应的一列表值:
Select Case Mid$( Text,i,1 )
Casa "0" To "9"
' lt's a digit
Case "A" To "Z","a" To "z"
' it's a lener
CaSe ",",".",";",":","'","?"
' it's a punctuation symboi or a space
Case Else
' it's something elsa.
End Stieci
对Select Case控制块最有效的优化方法是将最频繁发生的情况移至程序块的最上面,例如,在上例中,可能决定要在测试字符是否为一个数字之前测试它是否为一个字母.如果正在扫描的文本中,预计单词多于数字,该变动将使程序稍稍加快一些.
出人意料的是,Select Case控制块具有一个有趣的特点,这个特点是灵活的 If 语句所不具备的,即一种所谓的实现短路求值的能力.事实上,Case子表达式只有到其返回True值之前才被检测,之后在同一行上所有剩余的表达武将被跳过.例如,在上面的程序段中测试标点符号的 Case 子句是,如果字符为点号,那么所有运行上的其他检测就将永不执行.可利用这个有趣的特性来改写(及优化)一些复杂的包含有多个Boolean子表达式的If语句:
' This series of subexpressions connected by the AND operator:
lf x > 0 And Sqr( y ) > x And Log( x ) < z Then z = 0
' can be rewritten as:
Select Case False
Case x > 0,Sqr( y ) > x,Log( x ) < z
' Do nothing ?? any of the above meets the conditlon,
' that is,is False
Case Else
' This is executed only if all tha above are True
z = 0
End Select

' This series of subexpresslons connectetd by the OR operator:
lf x = 0 Or y < x ^ 2 Or x * y = 100 Then z = 0' p159
' can be rewritten as:
Select Case True
Case x = 0,y < x ^ 2,x * y = 100
' This is execuled as soc}n as one the abova is found
' to be True
z=O
End Select
由于这还不是正式的优化方法,我的建议是要充分地对程序进行注释,解释正在做什么并且始终包含原有的 If 语句作为标注.这个方法对于加速程序段的运行非常有效,但是不应忘记,如果要遗忘所做过的或者程序对于要去维护它的同事们来说难以理解的话,优化就不是完全重要的.
下面说说 GOTO 语句,相信它是使许多操作变成不容易直观理解的程序的起因.但是我必须承认,我对这个字母的关键词的态度并不是如此否定的.事实上,对于跳高出一系列的嵌套循环,我仍然更偏爱单个的 GOTO 语句,而不是一系列的 Exit Do.或者 Exit For 语句.我建议这样:** 作为常规执行流程的一个例外情况使用 GOTO 语句,并且用遍布于整个程序的明显的标记名和有意义的标往来解释正在做什么.
GoSub…Return 关键字比 GOTO 略好一些,它比较有组织.在某些情况下,利用 GoSub 调用一段当前过程内部的程序比调用外部的 Sub 过程或Function 函数更好.可以既不传递参数也不接收返回值.但是在另一方面,被调用程序与当前的过程共有所有的参数和局部变量,因此在大多数情况下不需要传递任何东西.无论如何应该知道,当编译成本机代码时,GoSub 关键字大约比调用同一模块中的一个外部过程要慢6至7倍,所以,如果正在编写时间紧急的程序,要对这两种方法进行基准测试.
5.1.2循环语句
在VB中最常使用的循环结构无疑是 For .... Next循环:
For counter = stanvalue To endvalue [Step incremant]
' Statemems to be executed In the loop
Next
只有在增量不等于1时,才需要指定Step项.可以使用Exit For语句退出循环,但遗憾的是,VB不提供任何所谓的"Repeat"命令,从而能跳过当前循环体的余下部分,并重新开始循环.最好使用(嵌套的)If 语句,或者如果不想使逻辑变得过于复杂,就使用普通的 GOTO 关键字指向循环的结尾处.事实上,单个GOTO语句可以增强程序的可读性和可维护性:
For counler = 1 To 100
' Do you stuff hare ....
' if you want to skip ovar what follows,just GoTo NextLoop.
if Err Then Goto NextLoop
' more coda that you don' t want to enclose within nested IF blocks
' ....
NextLoop:
Next
提示 * 应该使用Integer 或 Long 型变量作为For...Next循环的控制变量,这是因为它们比 Single 或 Double 型控制变量快10倍以上,如果需要增高一个浮点数值,最有效的方法在下一个例子中说明. 'p160
警告 *** 避开浮点型变量作为 For....Next 循环中的控制变量的一个主要原因是舍入误差,当增量为分数值时不能完全确定浮点变量是否被正确地增加,并且可能比预期提早或
延迟结束循环.
Dim d As Single,count As Long
For d = 0 to 1 Step o.i
caunt= coum+’
Next
Print count ' Displays "10" but should be "11"
当要完全绝对循环执行给定的次数时,就使用整型控制变量,并且在循环体内明确地对浮点变量加1:
Dim d As Singig. count As Long
' Scale start and end values by a factor of 1O
' so that you can use integars to control the loop
For count = 0 To 10
' Do what you want with the D variable,and then incremant it
' to be ready for the next iteration of the loop
d = d + 0.1
Next
我在第4章已提到过 For Each....Next 循环,在这里我不再复述.我只想说明基于此种类型循环的一个简单诀窍及 Array 函数.这个技巧可以让你利用一个控制变量的不同数值来执行一组语句,它们不需要依次排序:
' Test if Number can be divided any of the first io prima numbers.
Dim var AS Variant. NOtPrime As Boolean
For Each var In Array(2.3.5.7.11.13.17.19.23.29)
If (Number Mod var) = O Then NotPrime = true: Exit For
Next
数值甚至不一定是数字:
' Test if SourceString contains the strings "one","two","three",etc.
Dim var2 As Variant,MatchFound As boolean
For Each var2 In Array("one","four","five")
if InStr(1,SourceString,var2. vbTextCompare)Than
MatchFound = True: Exit For
End if
Next
Do....Loop 结构比 For....Next 循环更加灵活,可以将终止测试放在循环体头或尾(后一种情况,循环体至少执行一次).可阻使用 While 子句(重复执行直至测试条件为True)或Until子句(重复执行直至测试条件为False).可以在任何时刻通过Exit Do语句来退出Do循环,但是,与 For....Next 循环一样.VBA没有提供一个跳过循环体中余下语句而立即重新开始循环的关键字.
' Exampiz of a Do loop with test condition at the top
' This loop is never executed if x <= 0
DO While X > 0
y = y + 1 'p161..145
x = x / 2
Loop
' Example of a Do loap with test conditian at the bottom.
' This loop is always executed at least once,even if x <= O
Do
y = y + 1
X = X / 2
Loop Until x <= O
' Endless loop: requires an Exii Do statement to get out
Do
....
Loop
While....Wend 循环存概念上与 Do While....Loo p类似.但是只能在循环体的开始测试条件,没有 Until 子句甚至没有 Exit While 命令.由于这些原因,大多数程序设计人员喜欢更为灵恬的 Do....Loop 结构,实际上在整个书中只会看到一个 While....Wend 循环.
5.1.3其他函数
一些VBA函数与控制流程紧密相关,即使它们自己并没有改变执行流程.例如 IIf 函数通常可以替代 If....Else....End If 程序块,正如下面的程序所示:
' These Lines are equivalent
if X > 0 Then y = 1O Else y = 20
y = IIf(x > O,10,20)
Choose函数用下选择一组值中的某一个,可用它来完成三种或以上情况的选择.如替代以下代码段:
' The classic three choices selection
If x > y Then
Print "x greater than y"
Elself x < y Then
Print "x less than y"
Else
Print "x equals y"
End If
可以使用这种较短的形式:
' Shortenad form. based on Sgn() and Choose() functions
' Note how you keep the result of Sgn() in the range 1-3
Print "x" Choose(Sgn(x-y)+2,"less than","equals",_
"greater than") & "y"
Switch 函数接收一系列(条件,数值)对,并返回第一个与某个条件的求值为True相对应的数值.举例说明如何用这个函数取代 Select Case 程序块:
Select Case X
Case IS <= 10:y = 1
Case 11 to 100: y = 2
Case 101 to 1000:y = 3
Case Else:y = 4
End Select 'p162..146
只用一行可取得同样的作用:
' The last "True" expression replaces the "Else" ciause.
y = Switch(x <= 10,1,x <=100,2,x <= 1000,3,True,4)
在使用这个函数时应记住两点:第一,如果没有一个表达式返回 True 值,Switch函数就返回Null.第二,一直求算所有的表达式,即便只返回一个值.由于这些原因,可能会出现意
想不到的错误或不希望的副作用(例如,如果一个表达式出现溢出或被 0 除).
警告 *** IIf . Choose 和 Switch 函数有时对于减少编写程序量是有用的,但也应知道它们总是比它们意欲取代的 If 或 Select Case 结构要慢.由于这个原因,在要求时间性的循环体中不要使用它们.
5.2数值的处理
VB提供了一批种类齐全的算术运算符和函数.大多数运算符适合多种数据类型,它们可以适用干任何类型的参数包括 Integer.Long.Single.Double.Date 和 Currency.对于特殊的运算符或函数,VB编译器可以决定将操作数转换成更适合的数据类型.无论如何这是语言的工作,每一件事都自动地为用户完成,因此不必担心.
5.2.1算术运算符
正如所知道的,VB支持所有四则算术运算符.当将两个类型的数值组合在一起时,VB自动地实施数据类型强制转换,将较为简单的类型转换成更复杂的类型(例如,Integer 转为
Long型,Single 转为 Double 型),如果用一个 Integer 或 Long 型数被另一个 Integer 或 Long 型数除,并且对商数的小数部分不感兴趣.就应该使用整数除法运算符( / ),它执行起来更快:
Dim a As Long,b AS Long,result As Long
resuit = a/b ' Floating point divislon
result = a/b ' This is about 4 timas faster
VB还支持求幂运算符( ^ ),求一个数的指数.这样,即使求一个整数的整数次幂,其结果也总是 Double 类型.一般来说,"^" 运算符相对较慢,所以对于数值较小的整指数,最
好使用连乘运算来取而代之.
Dim x As Double,result As Double
x = 1.2345
result = x ^ 3
result = x * x * x ' This is about 20 times faster
MOD运算符求两个整数相除所得的余数.它常用来检测一个数是否恰好是另一个数的整数倍.此运算符非常有用,但它有一个局限:它将其运算对象转换成 Long 型,因此不能操作任意大的数值.它还截去所有小数部分.这里演示了处理任意Double型数值的函数
Function FPMod(ByVal Number As Double,ByVal divisor As Double) AS Double
' Note:this differs from MOD when number is negative
FPMod = Number - int(Number / divisor) * divisor
End Function
当处理数字时还有一些其他常用的函数
* Abs返回参数的绝对值. 'p163..147
* 如果 Sgn 的参数分别为负,0 或正数,则sgn分别返回1.O 或 +1
* Sqr 返同数的平方根.
* Exp 求以 e (自然对数的底) 为底函数参数为指数的指数值.
* Log 返回参数的自然指数.可以利用下列函数求取小数的对数:
Function Log10(Number As Double) As Double
Logl0 = Log(Number) / 2.30258509299405
End Function
5.2.2关系运算符
VB支持六种关系运算符.它们可以适用十数值和字符串两种运算对象
= < <= > >= < >
这些运算符常用于 If 程序块中,但是应牢记它们在概念上与其他任何运算符没有区别,在这个意义上,它们都接收两个运算对象并提供一个结果.其结果可能是 False (0)或True(-1).我们可以利用这个事实来编写更简洁的程序,如下所示:
' The following lines are equivalent
if x > y Then x = x - 1
x = x + (x > y)
警告 *** 由于VB在处理浮点数时通常产生小的舍入误差,因此在对 Single 或 Double 型数值使用"="运算符时应该当,例如下面的程序:
Dim d As Double,i As Integer
For i = 1 to 10:d = d + 0.1:Next
Print d,(d = 1) ' Displays"1 Falsa" !!!
因为变量看来像是含有正确的数值,所以上面的结果看似不合理,但是测试条件 (d = 1) 返回 False .由于 Print 语句总将小数舍入成整数,所以不能依赖VB中的 Print 语句作任何判断.事实上,d 变量的实际数值稍小于 1,确切的差额是 1.11022302462516E-16(小数点之后有15个零的数),但是这已足够使求相等的测试失败.因此,我建设不要将 "=" 用在浮点数上.下面是一种较好的方法
' "equal" up to 10th decimal digil
Function AlmostEqual(x,y) As Boolean
AlmostEqual = (Abs(x - y) <= O.OOOOO00001)
End Function
5.2.3布尔和位运算符
VB应用程序支持一些 Boolean 运算符,在组合多个 Boolean 子表达式上特别有用.最常用的道算符有 AND.OR.XOR 和 NOT.例如,下面的程序利用 Boolean 运算符确定两个变量的正负
if(x > 0) And (y > 0) Then
' Both X and Y are positive
ElseIf (x = o) Or (y = 0) Then
' Either X or Y(or both) are zero.
Elself (x > 0) Xor (y > 0) Then
' Either X or Y(but nat both of them) are posltive. 'p164....148
ElseIf Not(x = 0) Then
' X is not positive
End If
记住,这些运算符实际上是位运算符,它们作用于其运算对象的每一个位上.在实际中,如果运算对象不是 Boolean 值,就可能引起一些不同(即它们的数值可能不是与-l和0).可以使用 AND 运算符测试一个数的一位或几位.
lf (number And 1) Then Print "bit 0 is set(number is an odd vafue)"
If (number And 6) = 6 Then Print "Both bits 1 and 2 are set"
lf (number And 6) Then Print "Eilher bits 1 and 2,or both,are set"
通常使用OR运算符将一位或几位置为"1":
number = number Or 4 ' Set bil 2.
number = number Or (8+1) ' Sel bits 3 and 0.
联合使用 AND 和 NOT 运算符可以将一位或几位置为 "0"
Number = number And Not 4 ' Reset bit 2.
最后,使用 XOR 运算符可将一位或几位的状态"翻转":
Number = number Xor 2 ' Flip the slate of bit 1.
如果不知道在编译时哪一位应被置"1".置 "0" 或翻转,可以使用指数运算符,如下面程序所示:
Number = Number Or(2^N) ' Set Nth bit(N in range 0-30)
这种方法有两个缺点:如果 N = 31,会产生溢出错误,还有由于它依赖于浮点操作,所以它的效率特别低.可以用下列函数解决这两个问题:
Function Power2(ByVal exponent As Long) As Long
Static result(0 T0 31) As Long,i As Integer
' Evaluate all powers of 2 only once
If result(0) = 0 Then
result(O) = 1
For i = 1 to 30
result(i) = result(i-1).2
Next
rasult(31) = &H80000000 ' This is a special value
End If
Power2 = result(exponent)
End Function
5.2.4舍入及截尾
Int函数将一个数截断成等于或小于其参数的整数值.这与刚剐所说的"截击一个数的小数部分"不同.如果参数为负数,这种区别就更加明显,
Print Int(1.2) ' Displays "1"
Print Int(-1.2) ' Dispiays "-1"
实际截击一个数的小数部分的函数是Fix函数
Print FiX(1.2) ' Displays "1"
Print Fix(-1.2) ' Displays "-1" 'p165....149
****VB 6引入了一个新的数学函数 -- Round,它可将一个小数舍入到一个想要的数位上.
(或者如果第二个参数省略,含八成最接近的整数):
Print Round(1.45) ' Displays "1"
Print Round(1.55) ' Displays "2"
Print Round{1.23456,4) ' Disglays "1.2346'
Round函数有一个来得到文字证明的遁辞:当小数部分正好是0.5时,如果整数部分为奇数则向上进一位,如果整数部分为偶数则向下舍一位:
Print Round(1.5),Round(2.5) ' Both display "2"
有必要强调这点,以防在做统计计算时引入错误.
当做含入时,有时需要限定最接近的整数大于或等于函数,但是VB没有这样的函数,可以用这个短程序弥补这个问蹿:
Function Ceiling(number As Double) As Long
Ceiling = -int(-number)
End Function
5.2.5在不同的数制间转换
VBA支持数值常数以十进制.十六进制和八进制表示:
value = &H1234 ' The value 4660 as a hexadecimal constant
value = &011064 ' The same value as ocial constant
可以利用Val函数将任一个十六进制或八进制的字串转换成十进制数值:
' If Text1 holds a hexadecimal value
value = Val("& H" & Text1.Text)
利用 Hex 和 Oct 函数执行相反的转换——从十进制转换成十六进制或八进制:
Text1.Text = Hex$(value)
奇怪的是,VB没有提供转换或转换到二进制的函数,然而二进制数远比八进制值使用更为普遍.可以利用一对函数完成这些转换,它们建立在5.2.3"布尔和位运算符"一节中提到
的Power2函数基础上:
' Convart from decimal to binary
Function Bin(ByVal value As Lonq) As String
Dim iesuli AS Stnng,exponent As intEger
' This is faster than creating the string by appending chars
result = String$(32,"0")
Do
if value And Power2(exponent) Then
' We found a bit that is set. clear it
Mid$(resull,32 - exponent,1)= "1"
valua = valua Xor Power2(exponent)
End If
exponent = exponent + 1
Loop While value
Bin = Mid$(result.33 - exponent) ' Drop leading zeras
End Function' p166....150
' Convert from binary to decimat.
Function BintoDec(value As String) AS Long
Dim rasult As Long,I As Integer,exponent As Integer
For i = Len(value) To 1 Step -1
Select Case Asc(Mid$(value,i. 1))
Case 48 ' "0",do nothing.
Case 49 ' "1",add the corresponding power of 2.
result = result + Power2<exponent)
Casa Else
Err.Raise 5 ' invalid procedure call or argument
End Select
expanenl = exponent + 1
Next
BinToDec = result
End Function
5.2.6数值的格式选项
VBA语言的所有版本包含有 Format 函数,它是一个可以满足大多数格式化要求的强有力的工具.其语法相当复杂:
result = Format(Expression,[Format]
[ FirstDayOfweek As VbDayOfWeek = vbSunday ]
[ FirstWeekOfYear As VbFirstWeekOfYear = vbFirstjan1 ]
幸运的是,前两个参数已足够应付所有的工作,除非要格式化日期,本章后面我将论述这个问题.下面将总结当格式化数值时,Format 函数的许多性能(查看VB文档可获取更多
细节).
在格式化数值时.Format 函数支持已命名格式和自定义格式.已命名格式包括下列字符串:General Number (无特殊格式,需要时采用科学记数法).Currency (货币符号,有千位分隔符和两个小数位).Fixed (有两个小数位).Standard (有千位分嗝符和两个小数位).Percent (百分比,附有%符号).Scientific (科学记数法).Yes/No.True/False.On/Off (False或Off为 O,True或On相反).Format 是一种确知区域的函数,能自动地使用与当前区域相应的货币符号.千位分隔符和小数分隔符.
如果已命名格式不能完成任务,还可以使用由特殊字符组成的格式字符串建立用户自己的自定义格式(要获得这种格式字符串的详细清单和意义,请参见VB文档).
' Decimal and thousand separators.(Fomat rounds its result)
Print Format(1234.567,"#,##0.00")' "1,234.57"
' PerCentage values
Print Format(0.234,"#.#%")' "23.4%"
' Scientffic notation
Print Format(12345.67,"#.###E+")' "1.235E+4"
Print Format(12345.67,"#.###E-") ' "1.235E4"
Format函数的一个显著特点就是如果数值为正.负.O或NULL.它能够使用不同的格式字符串.在白定义格式字符串中使用分号作为每段的分隔符(可以指定一个.两个.三个或四个不同的段).
' Two decimal digits for positive numbers,enclose negative numbers within'p167....151
' a pair of parentheses,use a blank for zero,and "N/A" for Null values
Print Format(number."##,###.00;(##,###.00);;N/A")
****VB 6 引入了三种对数值进行格式化的函数-即 FormatNumber.FormatPercent 和 FormatCurrency (从VBScript借用过来) . (另三种函数 FormatDate.MonthName 和WeekdayName 在本章5.4节中介绍.)这些新函数继承了 Format 函数的强大功能,但是语法更为直观,这点在下面的程序中可以看到.
result = FormatNumber(expr,[DecDigits],[InclLeadingDigit],[UseParens],[GroupDigits])
result = FormatPercent(expr,[GroupDigits])
result = FormatCurrency(expr,[GroupDigits])
DecDigits是想要的小数位数(2为缺省值);IncILeadingDigit 决定在 [-1,1] 范围的数值是否以 0 开头显示;UseParens 确定负数是否用括弧括起来;GroupDigits 决定是否使用千位分隔符.后三个选项参数都可以是下列数值之一: O — vbFalse,-1 — vbTrue 或 -2 — vbUseDefault (用户的位置为缺省设置值).如果省略数值.则默认为 vbUseDefauLt.
5.2.7随机
有时需要产生一个或更多的随机值.在需要产生随机值的各种软件中,一般会想到是游戏软件,但在需要有仿真的商业应用中该功能也很有用.VB只提供一种语句和一个函数产生
随机值.利用 Randomize 语句将内部随机值发生器初始化.可以传递给一个值使其是随机值的产生值,否则,VB自动使用由 Timer 函数返回的值产生随机值.
Randomize 10
每一次调用Rnd函数后都返回一个随机值.返回值总是小于1.大于或等于O,所以需要将结果换算成所需的数值范围:
' Simple computenzad dice
Randamize
For i = 1 To 10
Print int(Rnd * 6) + 1
Next
有时可能重复同一随机值序列,特别是在调试程序时.看似可以通过用同一个 Randomize 语句就可以完成这种功能,但事实并非如此.其实可以利用一个负参数调用 Rnd 函数,就可以重复同一随机值序列:
dummy = Rnd(-1)' Initialize the seed (No Randomize is needed!)
For = 1 To 10' This loop will always deliver the same
Print Int(Rnd * 6) + 1' sequence of random numbars
Next
还可以通过传递参数给 Rnd 函数来前读刚刚产生的随机值.
处理随机值时常见的工作是在一给定范国内产生一列任意排列的数值:例如,这个工作对游戏中的洗牌特别有用.这里有一个简单而有效的程序,返回一组随机排列的在 first 和 last 范围内的所有 Long 型数:
Function RandomArray(first As Long,last As Long) As Long()'p168....152
Dim i As Long,j As Long. temp As Long
ReDim result(first To last) As Long
' Inltiallze the array.
For i = first To last: result(i) = i: Next
' Now shuffle it.
For i = last To first Step -1
' Generate a random number in the proper range.
j = Rnd * (last - first + 1) + first
' Swap the two items.
temp = resuit(i): result(i) = result(j): result(j) = temp
Next
RandomArray = result
End Function
5.3字符串的处理
VB应用程序包含许多强有力的字符串函数,有时第一眼看到很难确定哪一个满足需求.在本节中,我简要介绍所有字符串函数以供使用,提供一些在典型情况下如何选择最适合的
函数的技巧,还提供一些可以在实际应用程序中利用的有用的字符串函数.
5.3.1基本的字符串运算符和函数
基本字符串运算符 "&" 执行字符串连接操作.结果字符串包含有第一个字符串的所有字符,后面跟有笫二个字符串的所有字符:
Print "ABCDE" & "1234"' Displays"ABCDE1234’
许多有 QuickBasic 基础的程序设计人员仍然使用 "+" 运算符来执行字符串连接操作.这种做法很危险,会影响到程序可读性,并且当操作数不是字符串时,可能产生意想不到的作用.
下面介绍一组常用的字符串函数 Left$.Right$ 和 Mid$,将分别从源字符串的开头.结尾和中间抽取一个子字符串.
Text= "123456789"
Print Left$(text,3)' Displays "123"
Print Right$(text,2)' Displays "89"
Print Mid$(text,4)' Displays "3456"
提示 ** VBA文档一贯省略所有字符串函数中后面的"$"字符,并要求使用新的无 "$" 的函数.不要这样做! 无 "$" 的函数返回一个包含字符串结果的 Variant(变量),这意味着
大多数情况下,该 Variant(变量) 在能够再用于表达式中前必须转换成字符串或者指定为 String 变量.这是一个不会带采任何好处的浪费时间的过程.非正式基准测试表明,例如
Left$ 函数比对应的无 "$" 的函数快两倍.对于其他存在有两种形式的函数同样适用以上规律,这些函数有 LCase,UCase.LTrim.RTrim.Trim.Chr.Fomat.Space和 String.
Mid$还可以被用来改变一个字符串中的一个或多个字符.
Text = "123456789"
Mids(Text,3.4) = "abcd"' Now Text = "12abcd789"
Len 函数返回一个字符串的当前长度.它常用来测试一个字符串是否含有字符:'p169....153
Print Len("12345")' Displays "5"
if Len(Text) = O Then....' Faster than comparison with an empty siring.
要删去不想要的尾部或开头的空格,可咀使用LTrim$.RTrim$和Trim$函数
Text = " abcde"
Print LTrim$(Text)' Displays "abcde"
Prlnt RTrim$(Text)' Displays " abcde"
Print Trim${Text)' Displays "abcde"
这些函数对于具有固定长度,但被多余的空格填充而被算进其预定的长度的字符串特别有用.可以利用RTrim$函数将那些多余的空格裁剪掉:
Dim Text As String * 10
Text = "abcde"' Text now contains "abcde "
Print Trim$(Text)' Displays "abcde"
警告 **** 当已说明而还末使用一个固定长度的字符串时,它只包含有Null字符,没有空格. 也就是说 RTrim$ 函数不能裁剪这样的字符串.
Dim Text As String * 10
Print Len(Trim${Text))' Displays "10". no trimming has occurred
可以在应用程序中在说明字符串之后不久和使用其之前简单地将一个空字符串指派给所有固定长度的字符串,从而解决这个问题.
Asc函数返回字符串中的第一个字母的字符代码.它的作用与利用Left$函数抽取第一个字相似,但Asc相当快:
¨Asc{Text) -32 Then lTest Whelher the fist charis a space
" LeftS(Texl,1}=- Then' Same eflact. bUt 210 3times slower
在使用Asc函数时,必须保证字符串不为零,这是因为这种情况下,该函数会出现错误. 在某种意义上,Chr$ 与 Asc 作用相反,它将一个数字代码转换成相应的字符:
Print Chr$(65)' Displays "A"
Space$ 和 String$ 函数非常相似.前者返回一个想要的长度的空字符串,后者返回一个字符串,其所含字符由第二个参数确定,字符重复次数由第一个参数指定:
Print Space${5)' Displays " " (five spaces)
Print String#(5,"")' Same effect
Prini String$(5,32)' Same effect,using the char cade
Print String$(50,"")' A raw of 50 dots
最后,StrComp函数咀忽略大小写的方式对字符串进行比较,如果第一个参数小于.等于或大于第二个参数,它将返回 -l.O 或 1.第三个参数确定比较操作是否以忽略大小写的方式进行.
Select CasastrComp(first,second,vbTextCompare)
Case O
' first = secana (e.g. "VISUALBASIC" vs "Visual Basic")
Case -1
' first < secand (e.g. "C++" vs "visuat Basic")
Case 1
' first > second (e.g. "Visual" vs "visuat Basicn)
End Select' p170....154
因为有时不需要两种分别的和测试来确定一个字符串是否小于.等于最大于另一个字符串.所以 StrComp 函数即使对于考虑大小写的比较操作也是很方便的.
5.3.2转换函数
最常用的字符串转换函数为UCase$和LCase$,它们分别将其参数转换成大写和小写.
Text = "New York,USA"
Print UCase$(Text) ' "NEW YORK,USA"
Print LCase$(Text) ' "new york,usa"
StrConv 函数包含有前两个函数功能并且还增加了更多的性能.可以用它将字符串转换成大写.小写和原格式(每个词的第一个字母是大写,所有其他的字母为小写):
Print StrConv(Text,vbUpperCase)' "NEW YORK,USA"
Print StrConv(Text,vbLowerCase)' "new york,usa"
Print SirConv(Text,vbProperCasa)' "New York,Usa"
(有效的单词分隔符为空格.Null 字符,回车符或换行符). 该函数还可以使用 vbUnicode 和 vbFromUnicode 符号常数完成 ANSI 到 Unicode 的双向转换. 在常用VB应用程序中用户将很少用刘这些函数.
Val函数将一个字符串转换成十进制表示法.VB还引入了可以将一个字符串转换成数值的函数,宅要有 CInt.CLng. CSng.CDbl.CCur 和 CDate. 它们与Val由数的主要区别是它们是确知区域的.例如,在有些国家中将逗号作为小数点,它们能正确地识别,并忽略任何千位分隔符.相反,Val函数只能识别小数点并且当其发现任何无效字符(包括货币符号或用来给千位分组的逗号)时就停止分析其参数.
Str$函数将一个数值转换成字符串表示.Str$与CStr的主要区别是如果参数为正数,前者将在字符串开头加一个空格,而后者不会.
5.3.3查找和替换子字符串
InStr函数队考虑大小写或忽略大小写的方式在一个字符串中查找一个子字符串,如果要传递未指定想要执行哪种查询方式的参数,就不能省略第一个参数:
Print InStr("abcde ABCOE","ABC") ' Displays"7"(case sensitive)
Print InStr(8,"abcde ABCDE","ABC") ' Drspiays"."(seatr index > 1)
Print InStr(1,"ABC",vbTextCompare)' Dispiays"1"(case insensitive)
IriStr函数对于建立一些VBA语句中没有提供的有效字符串函数非常方便.例如,这是一个能在一个查询表中查找指定子串第一次出现的位置的程序段.它对于抽取可以被许多不同
的分隔符号划界的单词非常有用:
Function Instr Tbl(source As String,searchTable As String,_
Optional statr As Long = 1,_
Optional Compare As vbCompareMethod = vbBinaryCompare) As Long
Dim i As Long
For i = start To Len(source)
If InStr(1,searchTabla,mid$(source,1),Compare) Then
InstrTbl = i'p171....155
EXit For
End If
Next
End Function
****VB 6允许利用新的InStrRev函数进行反向查找.它的语法格式与原先的InStr函数相似,但是其参数的顺序有所不同:
found = lnStrRev(Source,Search,[Stan],[CompareMethod])
这里有一些例子.注意如果省略了参数Start,查找就从字符串尾部开始进行:
Print InStrRev("abcde ABCDE","abc")' Displays "1" (case sensitiva)
Print InStrRev("abcde ABCDE","abc",vbTextCompare)' Displays "7" (case insensitiva)
Print InStrRev("abcde ABCDE",4,vbTextCompare)' Displays "1" (case insensitiva,start <> 0)
VB还引入了一个方便的字符串操作符-- Like 操作符,它常常在分析字符串并且执行复杂的查找操作时充当一个时间节省器.这个操作符的语法形式如下:
result = string Lika patterm
其中String为正被分析的字符串,而Pattern是特殊字符组成的指定查找条件的一个字符串.最常用的特殊字符有 "?(任一个单个字符)"."*(0以上字符)" 和 "#(任意单个数)".这里是几个例子:
' The Like operatar is affected by the current Option Compare setting.
Option Compare TeXt ' Enforce case-insensitive comparisons.
' Check that a string consists of "AB" followad by three digits
If value Like "AB###" Then ' e.g "AB123' or "ab987"
' Check that a string starts with "ABC" and ends with "XYZ"
If value Like "ABC*XYZ" Then... ' e.g "ABCDEFGHI-VWXYZ"
' Check that starts wish "1",ends with "X",and includes 5 Chars.
If value Like "1???X" Then.... ' e.g "1234X" or "1uvwx"
还可阻通过插入一未列以方括号包围起来的字符来指定那些想要在查找中包含的字符(或排除出去).
' One of the letters "A","B","C" followed by three digits
If value Like' "[A-C###]" Then ' e.g "A123" or "c456"
' Three lelters,the first one must be a vowel
If value Like "[AEIoU][A-Z][A-Z]" Then' e.g "IVB" or "OOP'
' At least three Characters,the first one can't be a digit
' Note: a leading "l" symbol excludes a range
If value Like[10-9)??*" Then....' e.g "K12BC" or "ABHIL"
****VB 6引入了新的 Replace 函数,可以快速查找和替换于字符串.由于该函数包含有几个选项参数,所以其语法格式不是很直截了当:
Text = Replace(Source,Find,Replace,[Start],[Count],[CompareMethod])
以考虑大小写的方式查找子字符串并替换所有出现的于字符串的最简单的形式:
Print Replace("abc ABC abc","ab","123")' "123c ABC 123c"
通过改变其他参数,可以从不同位置开始查找,限定替换次数以及执行忽略大小写的查找操作.注意Start的值若大于1,实际上相当于在开始查找前对Source参数进行了裁剪:
Print Replace("abc ABC abc","123",5,1)' "ABC 123c"
Print Replace("abc ABC abc",vbTextCompare)' "123C abc"
还有一种非正式的方式可使用 Replace 函数来计算一个子字符串在另一个字符串中出现的次数:'p172....156
Function instrCount(Source As $tring,Search As Stnng) Aa Long
' You get the number of substrings by subtracfing the length of the
' original string from the length of the string that you obtain by
' replacing the substring with another string that is one char longer.
InstrCount = Len(Replace(Source,Search & "*")) - Len(Source)
End Function
****新的 StrReverse 函数快速地将一个字符串中的字符次序颠倒.这个函数自己本身没有意义,但它可以给其他字符串处理函数带来好处:
' Replace only the LAST occurrence of a substring
Function ReplaceLast(Source As String. Search As String,_
ReplaceStr As String) As String
ReplaceLast = StrReverse(Replace(StrReverse(Source),_
StrReverse(Search),StrReverse(ReplaceStr),1))
End Function
可以使用新的 Split 函数在一个字符串中查找所有限定的项目.其语法格式如下:
str() = Split(Source,[Delimlte],[limit]. [CompareMethod])
其中 delimiter 是用来分隔各项的字符.如果不想要比给定值更多的项目,就可给limit参数传递一个正值,还可以给后一个参数传送 vbTextCompare 值以进行忽略大小写的查找操作.由于缺省的分隔符为空格,因此用这个程序可以很容易地取出一个句子中的所有单词:
Dim words() As String
words() = split("Microsoft visual Basic 6")
' words() is now a zero-based array with four elements.
****Jorn函数是对 Split 函数的补充,它接收一个字符串数组和一个分隔字符,并且能恢复原始的字符串:
' Continuing the preceding example
' The delimiter argument lS optlonai riare.because it defaulls to""
Print Jo/nONords.¨1 ' Displays"Microsoft visual Basid 5 1
注意在Split和Join函数中的分隔符参数可以是多个字符.
另一个受欢迎的对VBA语言的补充是Filter函数,它可以为子串快速扫描一个搜索数组,
并返回另一个只包含(或不包含)有被查找到的予串的数组.Filter函数的语法格式如下:
arr()=Flier(Source(),[Include],[CompareMefhod])
如果Include参数为True或者省略,结果数组只是Source 中的包含着 Search 子串的各项;如果它为False,结果数组只是那些不包含子串的各项.与往常一样,CompareMethod参数确定查找操作是否考虑大小写:
ReDim s(2) As String
s(O) = "Flrst"; s(1) = "Second": S(2) = "Third"
Dim res() As String' p173....157
res = Filter{s,"i",vbTextCompare)
' Print the result array ("First" and "Third")
For i = O To UBound(res); Print res(i); Next
如果在 Source 数组中没有可以满足要求的项,当被传递给 UBound 函数时,Filter 函数传递一个特殊数组,并返回l.
5.3.4字符串的格式化选项
还可以使用 Format 函数格式化字符串.在这种情况下,可以只确定一种自定义格式(对于字符串,没有命名格式可供使用),并且对特殊字符的选择有限制,但无论怎样可以获得很大的灵活性.可以指定两段,一段是非空字符串值,一段是空字符串值,如下面所示.
' By default,placeholders are filled from right to left
' "@" stands for a character or a space,"&" is a character or nothing
Print Format("abcde","@@@@@@@")' "abcde"
' You can exploit this feature to right allgn numbers in reports
Print Format(Format(1234.567,"Currency"),"@@@@@@@@@@@")'" $1,234.57"
' "!" forces left to right of placeholders
Print Format("abcde","@@@@@@@")' "abcde"
'">" forces to uppercase,"<" forcea to lowercase.
Print Format("abcde",">& & & & & ")' "A B C D E"
' This is a good way to farmat phone numbers or credit-card numbers.
Print Format("6152127865","&&&-&&&-&&&&")' "615-212-7865"
' Use a second section to format empty strings
' "/" iS the escape character
Print Format("","!@@@@@@@;/n/o/n/e")' "none"
5.4日期和时间的处理
VB不仅可以将日期和时间信息以Date数据类型存储,而且还提供了许多与日期.时间相关的函数,这些函数在所有的商业应用程序中非常重耍,值得做深层次探究.
5.4.1当前日期和时间的获取及设置
严格地讲,Date和Time不是函数,它们是属性.事实上,既可以使用它们获得当前的日期和时间(作为Date值),又可以给它们指定新数值以修改系统设置:
Print Date & "" & Time' Displays "8/14/98 8:35:48 P.M"
' Set a new system date using any valid date format
Date = "10/14/98"
Date = "October 14,1998"
注意 * 为了帮助你比较所有日期和时间函数的结果,本节中全部例子都假定在上面小段程序中表示的日期和时间执行:1998年8月14日,8:35:48PM.
已过时的Date$和Time$属性也可用来做同一工作.总之它们是String属性,因此分别只识别 mm/dd/yy 或 mm/dd/yyyy格式 和hh:mm:ss 或 hh:mm 格式,由于这个原因,通常最好使用新的无"$"的函数.
Now函数返回一个包含当前日期和时间的Date型数值:' p174....158 第一部分基础篇
Print Now’Displays"8/14/98 8:35:48 P.M"
由来已久的Timer函数返回从午夜开始逝去的秒数,由于Timer函数计算秒的小数部分,因此比Now函数精确(实际的精确度依赖于系统).这个函数常用来对一段程序进行基准测试:
StartTime = Timer
' Insert the code to be benchmarked here.
Print Timer - StartTime
上述程序还有一些不准确:当系统报时信号将要停止时,才可指定 StartTime 变量,所以程序可能比实际运行时间要长.这里是一种稍好的方法
StartTime = NextTimerTick
' Insert the code to be banchmarked here.
Print Timer - StartTime
' Wait for the current timer tick to elapse.
Functian NextTimerTick() As Single
Dim t As Single
t = Timer
Do: Loop While t = Timer
NextTimerTick = Timer
End Function
如果正在产品程序中使用Timer函数,那么应当如道它存午夜时被复位,因此总是承担着产品不想要的但是潜在的严重错误的危险.试着找出这个程序中的缺陷,在它的程序中添加一个与cpu无关的暂停时间:
' WARNING: this procedure has a bug.
Sub BuggedPause(seconds As Integer)
Dim start As Single
start = Timer
Do: Loop Until Timer - start >= seconds
End Sub
该缺陷很少显现出来例如,如果程序要求在11:09:59PM有一个2s暂停.即使这种可能性很小,但这个小缺陷的作用是破坏性很强的,不能不按动"CtrI+Alt+Del"键来取消编译操作.这里是解决此问题的一种方法
' The correct version of the procedure
Sub Pausa(seconds As integer)
Const SECS_INDAY = 241 * 60 * 60' Seconds per day
Dim start As Single
start = Timer
Do: Loop Until (Timer + SECS_INDAY - start) Mod SECS_INDAY >= seconds
End Sub
5.4.2Date相Time型数值的建立和提取
有许多装备Date型数值的方法.例如,可以使用Date型常数,例如下例所示:
StartDate = #8/15/1998 9:20:57 PM#
但是更多的还是利用 VBA 提供的许多函数中的某一个来建立一个Date型数值. DateSerial 函数从其年,月,日组件中建立一个Date型数值,类似地.TimeSerial 函数也从其小时,分钟,秒组件中建立一个Time型数值:' p175....159
Print DateSerial (1998,8,14)' Displays "8/14/98"
Print TimeSerial (12,20,30)' DiSplays "12:20:30 P.M"
' Note that they don't raise errors with invalid arguments.
Print DateSerial (1998,31)' Displays "5/1/98"
DateSerial 函数也可用来间接地确定某一特殊年是否为闰年:
Function IsLeapYear(year As Integer) As Boolean
' Are February 29 and March 1 different dates?
IsLeapYear = DateSeriat (year,29) <> DateSerial(yeat,1)
End Function
DateValue 和 TimeValue 函数返回其参数的日期或时间部分,可以是字符串或Date型表达式,
' The date a week from now
Print DateValue(Now+7)' Displays "8/21/98"
--组 VBA 函数可以实现从个 Date 型表达式或变量中提取日期和时间信息.Day,Month 和 Year函数返回日期值,而Hour.Minute和 Second 相数返回时间值:
' Get information about today's date
y = Year(Now):m = Month(Now):d = Day(Now)
' These functions also support any valid date format
Print Year("8/15/1998 89:10:26 PM")' Displays "1998"
Weekday 函数返回 1 - 7 范围内的一个数,对应于给定的 Date 型参数的星期几
Print WeeKday("8/14/98")' Dispiays"6"(= vbFriday)
当日期是一个星期的第一天时,Weekday函数返回t,该函数是确知区域的,即在 Microsoft Windows 的不同区域下,它可以确认星期中的第一天与 vbSunday 是不同的.在多数情况下,这个规则不会影响程序的结构.但是如果要确定 1 就是星期天,2 就是星瑚一等等,就可以在所有 Windows 系统下强制函数返回一个一数值,如下所示:
Print Weekday(Now. vbSunday)
尽管可以利用任选的第二个参数强制函数返回正确的数值,它也不会改变系统定位.如果下一步不带第二个参数调用 Weekday 函数,它将仍认定星期的第一天为其以前的值.
最后,还可以使用 DatePart 函数从一个 Date 型数值或表达式中提取日期和时间信息,它的语法格式为:
Result = DatePart(Interval,Data,[FiratDayOfWeek],[FirstWeekOfYear])
因为可以利用我已说明过的其他函数来做大多数计算,因此将很少需要采用这个函数.但在两种情况下达个函数确实有用:
The quarter we are in
Print DatePart("q",Now)' Diaplays "3"
' The week number we are in (# of weeks since Jan 1st)
Print DatePart("ww’,Now)' Displays "33"
第一个参数可以是表5-1中所列的字符串常量之一.要获得更多有关两个选项参数的信息,参见下-节对 DateAdd 函数的描述.
' p176....160 第一部分基础蔫
表 5 - 1 DatePart.DateAdd 和 DateDiff 函数中的 interval 参数的可取值
┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ 设定值 说 明 ┃ 设定值 说 明 ┃
┣━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━┫
┃ "yyyy" 年 ┃ "w5 星期几 ┃
┃ "q" 季度 ┃ "ww’ 星期 ┃
┃ "m" 月 ┃ "h" 小时 ┃
┃ "y" 一年中的第几天(与d相同) ┃ "D" 分钟 ┃
┃ "d" 日 ┃ "s" 秒 ┃
┗━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━┛
5.4.3日期的运算
在大多数情况下,不需要任何特殊的函数来进行日期的运算.所有需要知道的就是 Date 型变量中的整数部分包含日期信息,小数部分包含时间信息:
' 2 days and 12 hours from now
Print Now + 2 + #12:QO#' Disptays "8/17/98 8:35:48 A.M."
对于更复杂的日期运算,可以使用 DateAdd 函数,其语法格式如下:
NewDate = DateAdd(interval,number,dare)
Interval是-个指定日期或时间单位的字符串(参见表5 - 1),number 是要添加的单位数,Date 为起始日期.可以使用这个函数加上或减去日期和时间值:
The date three months from now
Print DateAdd("m",Now)' Dispiays "11/14/98 8:35:48 P.M"
' One year ago (automatically accounts for leap years)
Print DateAdd("yyyy",-1,Now)' Displays "8/14/97 8:35:49 PM"
' The number of months since Jan 30,1998
Print DateDiff("m",#1/30/1998#,Now)' Dispiays"7"
' The number of days since Jan 30,1998,you can use "d" or "y"
Print DateDiff("y",Now)' Dispiays "196"
' The number of entire weeks since Jan 30,1998
Print DateDiff("w",Now)' Dispiays "28"
' The number of weekends before 21St century - value <0 means
' future dates.
' Note: use "ww" to return the number of Sundays in the date interval.
Print DateDiff("ww",#1/1/2000#,Now)' Dispiays "-72"
当有两个日期并且想求它们之间的差值时 -- 即求出一个日期与下一个日期之间逝去的时间,应使用 DateDiff 函数,其语法格式为:
Result = DateDiff(intetval,startdate,enddate _
[,FirstDayOfWeek[,FirstWeekOfYear]])
其中 interval 的意义见表 5 - 1,FirstDayOfWeek 是一个任选参数,可用来确定星期几作为星期中的第一天(可以使用vbSunday.vbMonday等等常量),FirstWeekOfYear是另一个任选参数,可确定哪一个星期应被认为一年中的第一个星期(见表5-2).
表 5 - 2 DateDiff 函数中的 FirstWeekOfYear 参数的可取值
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
常量值描述
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
vbUseSystem0使用NLS API设置
?vbFirsUanl1第一个星期为包含1月1日的那个星期(缺省值)
vbFirstFourDays2第一个星期是在新年中至少有四天
vbFirstFullWeek3第一个星期是完全包含在新年中的
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
' p177....161
5.4.4日期和时间值的格式选项
格式化日期和时间值的重要而灵活的函数是 Format 函数.这个函数给日期和时间值提供了六种不同的命名格式.
* Gereral Date(日期和时间以普通格式表示;如果小数部分为0,则只有日期;如果整数部分为0,则只有时间).
* Long Date(例如,Fnday.August 14,1998.但是根据你所在的场昕,结果会有所变化).
* Medium Date(例如 14-Aug-98).
* Short Date(例如,8/14/98).
* Long Time(例如,8:35:48).
* Medium Time(例如,8:35AM).
* Short Time(例如,24小时格式8:35).
还可以用几十特殊字符建立自己的定制日期和时间格式字符串,包括一位和两位日期和月份数.完整的或简写的月份和星期名.AM/PM指示.星期和季度数等等:
' mmm/ddd = abbreviated month/weekday,
' mmmm/dddd = complete month/weekday,
Print Farmat(Now,"mmm dd,yyyy (dddd)")' "Aug 14,1998(Friday)"
' hh/mm/ss always use two digits,h/m/s use one or two digrts
Print Format(Now,"h:mm:ss")' "20:35:48"
Print Format(Now,"h:mm AMPM")' "8:35 PM"
' y = day in the year,ww = Week in the year,q = quaner in the year
' Note how a backslash can be used to specify literal characters.
Print Format(Now,"mm/dd/yy (/d/a/y = y /w/e/e/k = ww /q/u/a/r/t/e/r = q)")
' Displays "08/14/98(day = 226 week = 33 quarter = 3)"
****VB 6已引入了新的 FormatDateTime 函数.它远没有标准的 Format 函数灵活,只允许 Format 函数中几个命名格式.FormatDateTime 函数的惟一优点是它还被 VBScript 支持,因此可阻有助于简单地将程序段从 VB 和 VBA 转入到 VBScript,反这亦然,其语法格式为:
result = FormatDateTime(Expression,[NamedFormat])
其中 NamedFormat 可以是下列内部常量之一:O-vbGeneraIDate(缺省值).1-vbLongDate.2-vbShonDate.3-vbLongTime 或 4-vbShonTime.这里有一些例子:
Prinl FormatDateTFme(Now)' 8/14/98 8:35:48 P.M"
Prinl FormatDateTime(Now,vbLongDate)' "Saturday,August 15,1998"
Print FormatDateTime(Now,vbShortTime)' "20:35"
VB 6 还提供了两个新的与日期格式化有关的函数.MonthName 函数返回一个月份的完整名或简写名,而 WeekdayName 函数返回一个星期几的完整名或简写名.两个函数都是确知区域的,所以可以利用它们的操作系统置的语言表示月份和星期几的名字:
Print Monthname(2)' "February"
Print MonthName(2. True)' "Feb"
Print WeekdayName(1,True)' "Sun"
5.5文件的处理' p178....162
VB-直提供许多处理文本文件和二进制文件的强有力的指令.但VB 6还未扩展内部函数集,尽管如此,它还是可以很容易地处理文件和目录,通过添加一个新的有趣的 FileSystemObject 对象,间接地扩展了语言的潜力.在这一节中,找对所有的与文件有关的函数和语句作一个概述,并有许多有用的提示.
5.5.1文件的管理
通常,可以对一个文件做许多事而不用打开文件.VB可以删除一个文件(使用Kill命令).移动或重命名文件(使用Name....As命令)和将之复制到别处(使用 FileCopy 命令):
' All file operations should be protecled against errors.
' None Of these functions works on open files.
On Error Resume Next
' Rename a file - note that yau must specify the path in the target.
' otharwise the file will be moved to the current directory.
Name "c:/vb6/TempData.tmp" As "c:/vb6/TempData.$$$"
' Move the file to another directory,possibly on another drive.
Name "c:/vb6/TempData.$$$" As "d:/vs98/Temporary.Dat"
' Make a capy of a file - note thae you can change the name during the copy
' and that you can ???? the filename portion of the target file
FileCopy "d:/VS98/Temporary.Dat","d:/TemPorary.$$$"
' Delete one or more files-Kill also supports wildcafds
Kill "d:/temporary.*"
可以利用 GetAttr 函数和 SetAttr 命令分别读取和更改一个文件属性,GetAttr 函数返回一个位码值,所以需要利用VBA提供的内部常量测试它的各个位.下面是一个可复用的函数,它利用文件的所有属性建立了一个描述字符串:
' This routine also works with open files
' and raises an error if the file doesn't exist.
Function GetAttrDescr(filename As String)As String
Dim result AS String,attr As Long
attr = GelAttr(filenama)
' GelAttr also works with directories.
If attr And vbDirectory Then rasult = result & "Directory"
If attr And vbReadOnly Then result = result & "ReadOnly"
If attr And vbHidden Then result = result & "Hidden"
If attr And vbSystem Then result = result & "System"
If attr And vbArchive Than result = result & "Archive"
' Discard the first (extra) space.
GetAttrDescr = Mid$(result,2}
End Function
类似地,通过传送给 SetAttr 命令一组值可以更改文件或目录的属性,如下面的程序所示:
' Mark a file as Archive and Read - only
filename = "d:/VS98/Temporary.Dat"
SetAttr filename,vbArchive + VbReadOnly
' Change a file from hidden to visibie,and vice versa
SetAttr filename,GetAttr(filename) Xor vbHidden
不能将 SetAttr 函数用在打开的文件上,当然不能通过翻转 vbDirectory 位的值将一个文件变体成一个目录(或反之).可以不用打开文件确定两个以上有关文件的信息,vbDirectory 以字节表示的长度和建立的日期和时间,分别用 FileLen 和 FileDateTime 函数实现.' p179....163
Print FiIeLen("d:/VS98/Temporary.Dat")' "Returns a Long value"
Print FileDateTime("d:/VS98/Temporary.Dat")' "Returns a Date value"
也可以对打开的文件使用 FileLen 函数,但在这种情况下,将得到文件被打开前的长度
5.5.2目录的管理
使用 CurDir$ 函数(或其他无"$"的等价函数 CurDir )可以获得当前目录名.如果给该函数传送一个驱动器符号,它就会返回在指定路径下的当前目录.在下例中,假设 Microsoft Visual Studio安装在驱动器D下,Microsoft Windows NT 驻留在驱动器C下,但在你的系统上可能会有不同的结果:
' Always use On Error - the current dir might be on a remaved tloppy disk.
On ErrorResume Next
Print CurDir$' Displays "D:/VisStudio/VB98"
' The current directory on drive C:
Print = CurDir$("c")' Displays "C:/WinNT/System"
可以利用 ChDrive 和 ChDir 命令分别改变当前驱动器和日录.如果在一个非当前驱动器上执行ChDir命令,那么实际上只修改那个驱动器上的当前目录,因此必须两个命令一起使用以保证修改的是系统的当前目录:
' Make "C:/Windows" the current directory
On Error Resume Next
SaveCurDir = CurDir$
ChDriva "C:":ChDir "C:/Windows"
' Do Whatever you need tO do...
' and then restore the oniginai current directory
ChDrive SaveCurDir: ChDir SaveCurDir
还可以利用MkDLraiRmDir命夸分别建立和删除子目录:
' Creata a new folder in the current directory,and then make it current
On Error Resume Next
MkDir "TempDir"
ChDir CurDir$ & "/TempDir"' (Assumes current dir is not the root)
' Da whatever you need to do
' ....
' then restore the original directory and delete the temporary folder
' You can't remove directories wilh file in them.
Kill "*.*"' No need for absolute path.
ChDir "_"' Move to the parent directory.
RmDir CurDir$ & "/TempDir"' Remove the temporary directory.
使用 Name 命令可以重命名一个目录,但是不能将目录移至他处
' Assumes tnat "TempDir" is a subdirectory of the current directory
Name "TempDir" As "TempXXX"
5.5.3遍历目录中的文件' p180....164
VBA 的 Dir 函数提供了一种遍历目录中的文件的简单而有效的方法.开始时用 fileapec 参数(可以包含通配符)和一个指定你感兴趣的文件属性的任选参数.然后在每一次遍历中,可以不带任何参数调用Dir,直至它返回一个空字符串.下列程序返回一个给定目录中的一组文件名.并且还说明了连奇循环的正确方法
Function GetFiles(filespec AS String,Optional Attributes As _
vbFileAttribule) As String()
Dim result() As String
Dim filename As String,count As Long,path2 As String
Const ALLOC_CHUNK = 50
ReDim result(O To ALLOC_CHUNK) As String
filename = Dir$(filespec,Attributes)
Do While Len{filename)
caunt = count+1
if count > UBcund(result) Then
' Resize the result array if necessary.
ReDim Preserve resulf(0 To count + ALLOC_CHUNK) As String
End If
result(count) = filename
' Get ready for the next iteration
filename = Dir$
Loop
' Trim the resull array
ReDim Preserve result(0 To count) As String
GetFiles = resull
End Function
提示 ** 还可以使用 Dir$ 函数间接地测试一个文件或目录的存在,使用下列函数
Function FileExists(filename As String) As Boolean
On Error ReSume Next
FileExists = (Dir$(filename)<>")
End Function
Function DirExists(path As String) As Boolean
On Error Resume Next
DirExists = (Dir$(path&"/nul")<>"")
End Function
虽然 FileExists 的程序相当简单,但可能还是对 DirExists 感到困惑,不知 "/nul" 字符串起源于哪里?对它的解释要回溯到MS-DOS的年代和它的特球文件名"nul"."con" 等等.这些文件名实际上指的是一些出现在所查找的目录(假设目录存在)上的特殊设备(空设备.控制台设备等等).这个方法可以处理任何目录,而测试一个空目录是否存在时使用Dir$("*.*")就会失败.
GetFiles 程序可用来给 ComboBox 控件中装入一组文件名.如果将控件的Sorted属性设置为True,这个程序特别有效:
Dim Files() As String,i As Long
' All files in C:/WINDOWS/SYSTEM directory.including systerm/hidden ones
Files() = GetFiles("C:/windows/system/*.*",vbNormal + vbHiddan + vbSystem)
Print "Found & UBound(Files) & "files"
For i = 1 To UBound(Files)
Combol.AddItem Files(i)
Naxt' p181....165
如果将 vbDirecory 位隐含在 Attribute 参数中,Dir$ 函数还会在其结果中返回目录名.可以利用这个特性建立一个GetDirectories 函数,它返回给定路径中所有子目录名.
Function GetDiectories(path As String,0ptional Attributes As VbFileAttribute,Optional lncIudePath As Boolean) As String()
Dim resuit() As String
Dim dirname As String,path2 As String
Const ALLOC_CHUNK = 50
ReDim result(ALLOC_CHUNK) As String
' Build Ihe path name? backslash
path2 = path
If Rignt$(path2,1)<>"/"Then path2 = path2 & "/"
dirname = Dir$(path2 & "*.*",vbDirectory Or Attributes)
Do While Len(dirname)
If dirname = "." Or dirname = ".." Then
' Exclude the "" and ".." entries
ElseIf (GetAttr(patn2 & dirname) And vbDdirectory) = 0 Then
' This is a regular fila
Else
' This is a directory
count = count+1
If count > UBound(result) Then
' Resize the result array if necessary.
ReDim Preserve result(count + ALLOC_CHUNK) As String
End If
' Include the path if requested
If IncludePath Then dirname = path2 & dirnama
result(count) = dirname
End If
dirname = Dir$
Loop
' Trim the resuit array.
ReDim Preserve result(count) As String
GetDirectories = result
End Function
通常的编程任务就是处理一个目录树中的所有文件.我刚才所列的程序和建立递归程序的能力,使得这项工作(几乎)变成儿童游戏:
' Load the names of all executable files in a diiectory tree into a ListBox
' Note: this is a racursive routine
Sub ListExecutableFiles(Byval path As String,Lst As ListBox)
Dim names() As String,i As Long,j As Integer
' Ensure that there is a trailing backslash
If Right(path,1)<> "/" Then path = path & "/"
' Get the list of executable files
For j = 1 To 3
' At each iteration search for a different extension
name() = GefFiles(path & "*." & Choose(j."exe","bat","com"))
' Load partial results in the ListBox lst
For i = 1 TO UBound(names)
Ist.AddItem path & namas(i)
Next
Naxt
' Get the list at subdirectories,including hidden ones
' and call this routine recursively on all of them
names() = GetDirectories(path,vbHidden)
For i = 1 To UBound(names)
ListExecutableFiles path & names(i),lst
Next
End Sub
5.5.4文本文件的处理
文本文件是要处理的最简单类型的文件.利用 Open 语句和 For Input.For Output 或 For Appending 子句打开文件,然后就可以从这些文件中读取数据或给这些文件写入数据.要打开一个文件(无论是文本文件还是二进制文件)需要一个文件号,如下列程序所示:
' Error if file #1 is already open
Open "readme.txt" For Input As #1
在单独的应用中,通常能够将一个惟一的文件号指派给不同的处理文件程序.但是,这个方法严重地阻碍了程序的可复用性,所以我建议使用 FreeFile 函数询问 VB 第一个可用的文件名:
Dim fnum As Integer
fnum = FreeFile()
Open "readme.txt)For Input As #fnum
在打开一个文件准备进行输之后,通常使用 Line Input语句一次读一行文本直至 EOF(End - Of - File)函数返回 True.任何文件程序还必须考虑到错误,无论是在它打开文件时还是它读取内容时.但是如果使用 LOF 函数来指定文件的长度,并利用Input$函数读取一次操作中的所有字符,可能会更好一些.下面是利用了这种优化方法的可复用的程序:
Function ReadTexFileContents(filename As String)As String
Dim fnum As Integer,isOpen As Boolean
On Error GoTo Error_Handler
' Get the next free file number
fnum = FreeFile()
Open filename For input As #fnum
' If execution flow got here,the file has been open without error
isOpen = True
' Read the entire contents in one single operation
ReadTextFileContents = Input(LOF(fnum),fnum)
' Intentionally flow into the error handler to close the file.
Error_Randler.
' Raise the error(if any),but first close the file
If isOpen Then Close #fnum
If Err Then Err.Raise Err.Number,ErrDescription
End Function
' Load a text file into a TextBox cantral
Textl Text = ReadTextFileContents("C:/bootlog.txt")
当要给文件写入数据时,如果要替换当前内容,就利用 For Output 子句打开文件,或者如果只是简单地给文件附加新数据,就用 For Append 子句打开文件.通常使用一系列的 Print# 语句给这个输出文件发送输出,但是如果将输出集中在一个字符串中,再输出文件,将会运行更快.下面的可复用的函数已为你做好了一切:
Sub WritaTextFileContents(Text As Siring,filename As String,Optional AppendMode As Boolean)
Dirn fnum As Ineger,isOpen As Boolean
On Error GoTo Error_Handler
' Get the next free file number.
fnum = FreeFile()
If AppendMode Then
Open filename For Append As #fnum
Else
Open filename For Output As #fnum
End If
' If execution flow gets here.the file has been opened correclly"
isOpen = True
' Print to the file in one single operation
Print #fnum,Text
' Intentionally flow into the error handler to close the file
Error_Handler:
' Raise the error(if any),but firsr close the file
If isOpen Then Clase #fnum
If Err Then Err.Raise Err.Number,Err.Description
End Sub
****即使VB 6没有特别地为处理文本文件增加任何函数,但是事实证明其新的 Split 函数对于文本处理特别有用.我们假定文本文件包含要被装入 ListBox 或 ComboBox 控件中的项目.不能使用前面展示的 ReadTextFileContents 程序来将之直接装入控件,但是可以利用它使程序更简洁:
Sub TextFileToListBox(lst As ListBox,filename As String)
Dim items() As String,i As Long
' Read the file's contents,and split it into an array of strings
' (Exit here if any error occurs.)
items() = Split(ReadTextFileContents(filename),vbCrLf)
Load all non-empty items into the ListBox
For i = LBound(items) To UBound(items)
If Len(items(i)) > O Then lst.AddItem items(i)
Next
End Sub
5.5.5分隔文本文件的处理
分隔文本文件使得在文本的每一行都包含多个字段.即使严格的程序设计员从不将分隔文本文件作为主要存储应用数据的手段,但是尽管如此,这类文件仍由于其提供了在不同数据库格式之间交换数据的主要方式,而在实际中起着重要作用.例如,分隔文本文件通常是将数据导入或导出主机数据库惟一有效的方式.这里是一个简单的以分号分隔的文本文件的结构.(注意通常在文件的第一行包含字段名.)
Name;Departmant;Salary
John Smith;Marketing;800OO'p184....168
Anne Lipton;Sares;75000
Robert Douglas;Administration;70000
总的来说,Split 和 Join 函数对于导入和导出分隔文本文件特别有用.参见下例,看一下它是多么容易地将一个以分号分隔的数据文件内容导人到一组数组中去:
' The contents of a delirnited text file as an array of strings arrays
' NOTE: requires the GetTextFileLines routine
Function ImportDelimitedFile(filename As String,Optional delimiter As String = vbTab) As Variant()
Dim lines() As String,i As Long
' Get all lines in the file
lines() = Split(ReadTextFileContents(filename),vbCrLf)
' To quickly delete all empty lines,load them with a special char
For i = 0 To UBound(lines)
If Len(lines(i)) = 0 Then lines(i) = vbNullChar
Next
' Then use the Filter function to delete these lines
lines() = Filter(lines(),vdNullChar. False)
' Create a string array out of each line of text
' and store it in a Variant element
ReDim values(0 To UBound(lines)) As Variant
For i = O To UBound(lines)
values(i) = Split(lines(i),delimiter)
Next
ImportDelimitedFile = values()
End Function
' An example of using the ImportDetimitedFile routine
Dim values() As Variant,i As Long
values() = ImportDelimitedFi|e ("c:/datafile.txt",";")
' Values(O)(n) is the name of the Nth field
' Values(i)(n) is the value of the Nth field on the ith record
' For example,see how you can increment employees' salaries by 20%
For i = 1 to UBound(values)
values(i)(2) = values(i)(2)*1.2
Next
使用数组是一个很好的策略,因为在数组中加入一个新记录很容易:
' Add a new record
ReDim Preserve values(0 To UBound(values) + 1) As Variant
values(UBound(values) = Split("Roscoe Powell;Sales;80000".";")
删除一个记录也很容易:
' Delete the Nth recnrd
For i = n To UBound(values) - 1
values(i) = values(i + 1)
Next
ReDim Preserve values(0 To UBound(values) - 1) AS Variant
将一组字符串数组写回到分隔文件中也是一项简单的任务见下面这个建立在Join函数
的可复用的程序:' p185....169
' Write the contents of an array of string arrays to a delimlted
' text file
' NOTE: requires the WriteTextFileContents routine
Sub ExportDelimitedFile(values() As Variant,_
Optional delimiter As String = vbTab)
Dim i As Lang
' Rebuifd the individual lines of text of the file.
ReDim lines(0 To UBound(values)) As String
For i = O To UBound(values)
lines(i) = Join(values(i),delimiter)
Next
' Create CRLFs among records,and write them.
WriteTextFileConlents Join(lines,vbCrLf),filename
End Sub
' Write the modified data back to the de1imited file
ExportDelimitedFile values(),"C:/datafile.txt",";"
所有在本节中描述的程序都建立在分隔文本文件足够小能保存在存储器的假设之上.而这听起来似乎是一个很严格的限制,实际上,文本文件大多用来建立小型档案或在不同数据库格式之间移动数量较小的数据.如果发现由于数组的大小而出现问题,就要利用多个 Line Input# 和 Print# 语句将之分成块进行读写.在多数情况下,可以处理大小为l或2兆字节(或者更多,这要根据所拥有的RAM存储器的大小)的文件,而不会有任何问题.
5.5.6二进制文件的处理
要打开一个二进制文件,可以使用带有 Random 或 For Binary 选项的 Open 语句.首先解释一下后面一种方式,它是二者之中较为简单的一种方式.在 Binary 方式中,利用 Put 语句写文件.用 Get 语句读回数据.VB 通过查看作为最后一个参数被传送的变量的结构来决定写或读多少字节:
Dim numEls As Long,text As String
numEis = 12345: text = "A 16-char string"
' Binary files are automatically created if necessary
Open "data.bin" For Binary As #1
Put #1,numEis' Put writes 4 bytes
Put #1,text' Put writes 16 bytes (ANSI format)
当要读取数据时,必须重复同样的语句顺序并正确地选定各种长度的字符串.因为可以使用 Seek 语句将文件指针重定位到一个指定字节处,所以不需要关闭和再打开二进制文件.
Seek #1,1' Back to the beginning(first byte is byte 1)
Get #1,numEls' All Long values are 4 bytes
text = Space$(16)' Prepare to read 16 bytes
Get #1,text' Do it.
另一方面,还可以在写或读数据之前使用第二参数将文件指针右移,如下面这一代码所示:
Get #1,numEls' Same as Seek + Get
警告 **** 当打开一个二进制文件时,如果文件不存在,VB自动建立一个文件.因此,不能使用 On Error 语句来确定文件是否已存在.在这种情况下,使用 Dir$ 函数确定文件被打开之前它实际是否存在.'p186....170
可以将整个数组快速写到磁盘上,并且在一次操作中又读取这些数据,但是在多数情况下,因为在读取之前必须正确地决定数组大小,因此还必须预先指定数据的实际元素的个数:
' Store a zero-based array of Double
Put #1,CLng(UBound(arr))' First store the UBound value
Put #1,arr()' Then store all items in ona shot
' read it back
Dim LastItem As Long
Get #l,LastItem' Read the number of items
ReDim arr2(0 To LastItem) As Double
Get #1,arr2()' Read tha array in memory in one operation.
Close #1
警告 **** 如果在读取数据时使用的读语句序列与原有的写语句序列不同,将会把错误的数据读入变量.在有些情况下,这种错误可能会在要显示这些内容时引起 VB 环境的崩溃.因此,要反复检查写和读操作顺序.当有所怀疑时,就在运行程序前补救它.
当正在读一个二进制文件时,不能使用 EOF 函数查明何时处于数据尾部;而应当测试 LOF (文件的字节长度)函数返回值,并使用Seek 函数来确定何时已读取所有的数据:
Do While Seek(l) < LOF(1)
' Continue to read
....
Loop
警告 **** 当将字符串保存到磁盘中时(无论是到文本文件还是二进制文件)VB都要将它们从 Unicode 转换到 ANSI,这样节省了大量的磁盘空间,并且允许与16位VB应用程序变换数据.但是如果正在为国际市场编写 Unicode 程序,这个特性就会起反作用,由于正在从一个文件读回的字符串将不一定与以前存储的相匹配,从而引起数据丢失.要解决这个问题,就必须将字符串移入一个 Byte 数组,并且保存起来.
Dim v As Variant,s As String,b() As Byte
s = "This is a string that you want to save in Unicode format"
b() = s: v = b()' You need this double step
Put #1,v' Write that to disk.
' Read it back
Get #1,v:s = v' No need for intermediary Byte array here.
用For Random子句打开一个二进制文件在一些重要的方面写以前我所描述的有所不同:
* 数据被当作是一个有固定长度的记录写入文件或从文件读出.这种记录长度可在打开文件(利用 Open 语句中的 Len 子句)时被确定,或者在单独的Put和Get语句中计算出来.
如果传送到Put语句中的实际数据长度小于预期的记录长度,VB将以随机字符填充(更精确地说是内部文件缓冲器的当前内容).如果实际数据比记录长度长,就会出现错误.
* Seek 命令的参数与 Put 和 Get 语句的第二个参数一样,表示记录数,不是二进制文件中绝对字节位置.文件中的第一个记录为记录1.
* 不必担心可变长度的数据(包括字符串和数组)的存储和获取,因为 Put和Get 语句能正确地处理这些情况.但是我还是特别建议最好避开包含普通的(非固定长度)字符串和动态数组 UDT 数据,这样记录的长度不会随实际内容而变化.'
p187....171
存储到 For Random 子句打开的二进制文件中的字符串预先指定一个2字节的值,指明后跟字符串的个数.这表示不能写人包含有多于 32767 个字符的字符串,这也是最大的有效的记录规模.要写入一个更长的字符串,应使用 For Binary 子句.
最后注意一点:迄今为止所看到的全部示例程序都假设工作在单用户环境下.不存在这样的错误:如打开一个已由另一个用户打开的文件,或者使用Lock语句锁定了一个数据文件的全部或一部分(然后用 Unlock 语句将之解锁).要获得更多信息,参见VB文档.
提示 * 在对二进制进行读写数据时,如果不想涉及额外的计算求值,可以用一个中间的 Variant 变量使路径变短.如果将一个任意类型(对象徐外)的数值存入 Variant 变量,然后将这个变量写入一个二进制文件,VB就将写入变量的类型(即 VarType 返回值),然后写人数据.如果变量含有一个字符串或一个数组,VB还会存入足够的信息以准确地读出查到的字节数,使得可从额外的读语句中摆脱出来:
Dim v As Variant,s(100) As String,i As Long
' Fill the s() array with data.... (ornitted)
Open"C:/binary.dat" For Binary As #1
v = s()' Store the array in a Variant variabla.
Put #1,v' and write that to disk
V = Empty' Release memory.
' Read data back
Dim v2 As variant,s2() As String
Get #1,v2' Read data in the Variant variable.
s2() = v2' and then move it to the real array
v2 = Empty' Release memary
Close #1
这个方法同样可处理多维数组.
5.5.7 FileSystemObject 分层结构
****VB6 带来了一个新的文件命令库,可以使程序设计人员很容易地浏览驱动器和目录,执行基本的文件操作(包括复制.删除.移动等等),以及抽取通过一般的 VB 函数不能得到的信息.但是我认为新命令的最好性能是可以利用一种现代的.有争论的.面向对象的语法做所有的事,使程序更加可读.所有这些功效以扩展的 FileSystemObject 分层结构形式提供,它嵌在 Microsoft Scripting Library中,这个库还保留在 Dictionary 对象中(参考第4章有关安装和使用该库的指令). FileSystemObject 分层结构包括许多复杂的对象(见图5—1),每一个对象都有许多有趣的属性方法.
l. FileSystemObject根对象
在分层结构的根部是 FileSystemObject 对象自己.它有许多方法和惟一的属性 - Drives,它返回系统中所有驱动器的集合,FileSystemObject 对象(在下面文字和程序中缩写为FSO)是分层结构中惟一可建立的对象 - 即它是惟一可用 New 关键字说明的对象.所有其他对象都为非独立对象,都是从该对象衍生出来的并且以方法属性的形式出现.看一下如何轻易地将系统中所有现有驱动器的日录装入两个数组中以及它们的特性:' p188....172
Dim fso As New Scripting.FileSystemObiact,dr AS Scripting.Drive
On Error Resume Next' Needed for not-ready drives
For Each dr In fso.Drives
Print dr.DriveLertter "[" & dr.TotalSize & "]"
Next

┃.. 对象 ..
┃FileSystemObject
┗━━━━━━━
┃.. 集合 ..
┃Drives
┗━━━━━━━
┃.. 对象 ..
┃Drive
┗━━━━━━━
┃.. 集合 ..
┃Folders
┗━━━━━━━
┃.. 对象 ..
┃Folder
┗━━━━━━━
┃.. 集合 ..
┃Files
┗━━━━━━━
┃.. 对象 ..
┃file
┗━━━━━━━
┃.. 集合 ..
┃TextStream
┗━━━━━━━

图 5-1 FileSystemObjects 分层结构

表 5-3 总结了 FSO 对象的许多方法,它们中的一些还可以用作第二层 Folder和File 对象的方法(通常以不同的名字和语法形式出现),这些方法中的大多数给已存任于VB中的命令增加函数性质.例如,可以用一个命令删除非空文件(要特别小心).复制和重命名多个文件和目录,还可以不必编写特殊的程序而报容易地抽取一个文件名的一部分.

表 5-3 FileSystemObjact 对象的所有方法
语法格式说明描述
Build Path(patt.name)
* 返会一个完整文件名,通过把路径(相对或绝对路径)和名字连接在一起而获得
CapyFile Source.Destination,[Overwrite]
* 复制一个或多个文件:Sourec可以包含通配符,Destination如果以反斜杠结尾则认为是一个目录.它将重写现有文件,除非将Overwrite设置为False
CopyFolder Source,Destination,[Overwrite]
* 与CopyFile相似,但它是复制整个文件夹及其内容(子文件夹和文件).如果Destination 与现有目录不相对应,就建立一个Destination(但是如果Source包含通配符,就不建立)
Creat Folder(Path) As Folder
* 建立一个新的Folder并将之返回;如果文件夹已存在就会出现错误
Creat TextFile(File Name,[Overwrite],[Unicode]) As TextStream
* 建立一个新的 Text File 对象并将之返回;为避免重写已存在的文件,设置Overwrite = False; 为避免建立一个 Unicode Text File 对象,设置 Unicode = True'p189....173
DelcteFile
FileSpec,[force]
* 删除一个或多个文件.FileSpec可以包含通配符;为强制删除只读文件,设置Force = True
DeleteFolder
(FolderSpec.[Force])
* 删除一个或多个文件夹及其内容;为强制删除只读文件设置 Force = True
DriveExists(DriveName)
* 如果给定的逻辑驱动器存在,就返回 True
FileExists(FileName)
* 如果指定文件夹存在,就返回True(路径可以是相对于当前目录的)
FolderExists
(FolderName)
* 如果指定文件夹存在,就返回True(路径可以是相对于当前目录的)
GetAbsolutePathName(Path)
* 将相对于当前目录的路径名转换成绝对路径
GetBaseName(Filename)
* 提取基本文件名(不带路径和扩展名),它不检验文件与/或路径实际上是否存在
GetDrive (DriveName) As Drive
* 返回与作为传递参数字母或UNC路径响应的Drive对象(它检验驱动器是否存在)
GetDriveName(Path)
* 从路径中提取驱动器
GetExtensionName(FileName)
* 从一个文件名中提取扩展名字符串
GetFile(FileName)
* 返回与作为传递参数的文件相对应的File对象(可以相对或绝对于当前目录)
GetFileName
* 提取文件名(不带路径但是带有扩展名); 它不检验文件与/或路径实际是否存在
(Get Folder(FolderName) As Folder
* 返回与作为穿体参数的路径相对应的Folder对象(可以绝对或相对于当前目录)
GetParentFolderName(Path)
* 返回作为传递参数的目录的父目录名(或者如果父目录存在,返回一个空字符串)
GetSpecialFolder(SpecialFolder) As Folder
* 返回对应与特别的Windows目录之一的Folder对象.SpecialFolder可以是 0 - WindowsFolder .1 - SystemFolder . 2 - TemporaryFolder
GetTempName()
* 返回一个不存在的可以用作临时文件文件
MoveFile (Sourcc,Destination)
* 与CopyFile类似,但是它是删除文件.如果操作系统支持这个函数,它还可以在不用的驱动器之中移动
MoveFolder (Sourct,Destination)
* 与 MoveFile 相似,但是它处理的是目录
OpenTextFile (FileName,[ioMode],[Create],[format] As TextStream
* 打开一个文本文件并返回相应的TextStream对象. IOMode 可以是下列常量中的一个或一个组合(用OR运算符): 1- ForReading. 2- ForWaiting. 8- ForAppending; 如果要建立一个新文件,将Create设置为True;Format可以是0- TristateFalse(ANSI).-1- TristateTrue(Unicode) 或 -22 TristateUseUdfault(由系统确定)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2. Drive对象
这个对象只有属性(没有方法),所有属性在表 5-4 概述.全部属性除 VolumeName 属性以外都是只读的.这个代码段确定现有的至少有100MB空闲空间的本地驱动器
Dim fso As New Scripting.FileSystemObject,dr As Scripting.Drive
For Each dr ln fso.Drives
If dr.isReady Then
lf dr.DriveType = Fixed Or dr.DrivaType = Removable Then
' 2 ^ 20 equals one megabyte.
If dr.FreeSpace > 100 * 2 ^ 20 Then
Print dr.Path? "[" & dr.VolumaName & "]=" & dr.FreeSpace
End if
End lf
End if
表5-4 Drive对象的所有属性
语法格式
说 明
AvailableSpace
* 驱动器上空闲空间,以字节表示:通常它与 FreeSpace 属性的返回值相同,除非操作系统支持磁盘定额
DrivteLetter
* 与驱动罄相联系的字母或网络驱动器对应的一个空字符串,与字母不相关
DrivcType
* 一个指明驱动器类型的常量:O- Unknown. 1- Removable. 2- Fixed.3- Remote.4- CDRom.5- RamDisk
FileSysem
* 一个描述正在使用的文什系统的字符串:FAT. NFS.CDFS
FreeSpace
* 驱功器上的空闲空间(参见 AvailableSpace)
LsReady
* 如果驱动器准备好,为True.否则为False
Path
* 与驱动器相联系的路径,不带反斜杠(例如C:)
RootFolder
* 与根目录对应的 Folder 对象
SerialNumber
* 一个与磁盘序列号对应的 Long 型数
ShareName
* 驱动器的网络共享名或者如果不是网络驱动器,就是一个空字符串
TotalSize
* 驱动器的总容量,以字节表示
VolumeName
* 磁盘标号(可读和写)

3 Folder对象
Folder 对象代表一个单独的子目录.可以用不同方式调用这样的对象:通过使用 FileSystemObject 对象的 GetFolder 或GetSpecialFolder方法;通过 Drive 对象的 RootFolder属性;通过 File 对象或另一个 Folder 对象的 ParentFolder属性;或者通过遍历另一个 Folder 对象的 SubFolders 集合.Folder 对象有一些有趣的属性《见表5-5),但是只有 Attribute和Name 属性可被写入.最引人好奇的属性可能是 SubFolders和Files 集合,它们使得可以利用优雅而简洁的语法格式遍历子目录和文件
'Print the names of all first-ievel directories on all drives
' together with their Short 8.3 names
Dim fso As New Scripting.FileSystamObject
Dim dr As Scripting.Drive,fld As Scripting.Folder
On Error Resume Next
For Each dr in fso.Drives
If dr.isReady Then
Print dr.RootFolder.Path' The root folder
For Each fld in dr.RootFolderSubFolders
Print fld.Path & "[" & fld.ShortName & "]"
Next
End If
Next'p191....175
表 5-5 Folder和File对象的所有属性
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
语法格式& 应用
说 明
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
AttributesFolder和File
文件文件夹的属性,为下列常量的组合:0-Normal. 1- ReadOnly. 2- Hidden. 4- System. 8- Volume. 16- Directory. 32- Archive. 64- Alias. 2048- Compressed. Volume. Directory.
Alias和Compresed属性不能被修改
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
DateCreatedFolder和File
建立日期(为只读Date型数值)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
DateLastAccessedFolder和File
最后一次访问的日期
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
DateLastModifiedFolder和File
最后一次修改的日期
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
DriveFolder和File
文件文件夹所在的 Drive 对象
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
FilesFolder
所有包含的 File 对象的集合
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
LsRootFolderFolder
如果是其驱动器的根文件夹,就为True
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
NameFolder和File
文件文件夹名.指定一个新数值来重命名对象
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ParentFolderFolder和File
父Folder对象
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PathFolder和File
Folder或File的路径(却省属性)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ShortNameFolder和File
以8.3MS-DOS格式表示的对象名
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ShortPathFolder和File
以8.3MS-DOS格式表示的对象的路径
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
SizeFolder和File
一个File对象的字节大小;Folder对象的所有被包含的文件和子文件夹大小的总和
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
SubFolderFolder
包含在这个文件夹中的所有子文件夹的集合,包括系统和隐含的子文件
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
TypeFolder和File
对象的字符串描述. 例如:fso.GetFolder("C:/Recycled"). Type返回"Recycle Bin"; 对于 File 对象,该值取决于其扩展名(例如,"TextDocument" 对应TXT扩展名)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Folder 对象还有一些方法在表5-6中概述.注意通常还可以利用主要的 FSO 对象的恰当方法获得类似的结果.还可以利用 Add 方法应用于 SubFolders 集合,建立一个新的Folder,如下面递归程序所示,它将一个驱动器的目录结构复制到另一个驱动器上,而不需要复制所包含的文件
' Call this routine to initiate the copy proceas
' NOTE: the destination folder is created if necessary.
Sub DuplicateDirTree(SourcePath As String,DestPath As String)
Dim fso As New Scripting.FileSystemObject
Dim sourceFld As Scripting.Folder,destFld As Scripting.Folder
' The source folder must exist
Set sourceFld = fso.GetFolder(SourcePath)
' The dastination folder is created if necessary
If fso.FolderExists(DestPath) Then
Set destFld = fso.GetFolder(DestPath)
Else
Set destFld = fao.CreateFolder(DestPath)
End If
' Jump to the recursiva routine to do the real job
DuplicateDirTreeSub sourceFld.dastFld
End Sub
Private Sub DuplicateDirTreeSub(source As Folder,destination As Folder)
Dim sourceFld As Scripting.Folder,destFld As Scripting.Folder
For Each sourcaFld in sourca.SubFolders
' Copy this subfolder into destination folder
Set destFld = destination.SubFolders.Add(sourceFld.Name)
' Then repeat the process racursively for all
' the subfolders of the folder just considered.
DuplicateDirTreeSub sourceFld,destFld
Next
End Sub
表5-6 Folder和File对象的所有方法
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
语法格式&应用
描述
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Copy Destination,[OverWriteFilse]Folder和File
将当前File或Folder对象复制到另一个路径;这与FSO的CopyFolder和CopyFile方法相似,它也能在一次操作中复制多个对象
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CreatTextFile(FileName,[OverWrite],[Unicode]) As TextStreamFolder
在当前Folder中建立一个文本文件并返回相应的TextStream对象.参见对于相应的FSO方法各个参数的解释
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Delete[Force]Folder和File
删除这个File或这个Folder对象(及其所有的子文件夹和文件).与FSO的DeleteFile和DeleteFolder方法类似
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Move DestinationPathFolder和File
将这个File或Folder对象移到另一个路径;与FSO的MoveFile和MoveFolder方法相似
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
OpenAsTextStream([IOMode],[Format]) As TextStreamFile
将这个File对象作为文本文件打开并返回相应的TextStream对象
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

4.File对象
File对象代表磁盘上的一个文件.可以用两种方法调用这个对象:通过使用FSO对象的 GetFile 方法或者通过遍历其父 Folder 对象 Files 集合.不管其本质有什幺不同,File和Folder 对象共有许多属性方法,所以对于在表 5-5和表5-6 中给出的说明我将不再重复.
FSO分层结构的一个局限在于没有一个直接的方法通过使用通配符来筛选文件名,就像用 Dir$ 函数能做到的那样.所能做的就是遍历 Folder 对象的 Files 集合并检测文件的名字.扩展名或者其他属性以查看对它是否感兴趣,如下面所示:
' List all the DLL files in the C:/WINDOWS/SYSTEM directory
Dim fso As New Scripting.FileSystemObject,fil As Scripting.File
For Each fil in fso GefSpecialFolder(SystemFolder).Files
If UCasa$(fso.GetExtensionName(fil.Path)) = "DLL" Then
Print fil.Name
End If
Next
FileSystemObjec 分层结构不允许在文件上多次操作.更特别的是,可以列出它们的属性(包括许多超出了原来VBA文件函数本来性能属性),只能以文本方式打开文件,这在下一节将进行说明.
5. TextStream对象'p193....177
TextStream 对象代表一个以文本方式打开的文件.可以用下列方珐调用这个对象:通过使用FSO对象的CreateTextFile 或 OpenTextFile方法;通过使用 Folder 对象的 CreateTextFile 方法;或者通过使用File对象的 OpenAsTextStream 方法.TextStream 对象有一些方法和一些只读属性,其说明在表 5-7 中.TextStream 对象的确除了普通的VBA文件命令之外还提供了一些新的特性 -- 例如,在读或写文本文件时,跟踪当前行和列的能力.这个特点用在一个可复用的程序中,这个程序浏览一个目录中的所有TXT文件以查找一个字符串,井返回一组结果(实际上是一组数组),包括那些所有包含这个字符串的文件并指明字符串在文件的位置行号和列导:
' For each TXT file that Contams the search string,the function
' returns a Variant element that contains a 3-item array that holds
' the filename. the line number,and the column number
' NOTE: all searches are case insensitive
Function SearchTextFiles(path As String,search As String) As Variant()
Dim fso As New Scripting FileSyStemObject
Dim fil As Scriping.File,ts As Scriping.TextStream
Dim pus As Long,count As Long
ReDim result(50) As Variant
' Search for all the TXT files in the directory
For Each fil in fso.GetFolder(path).Files
If UCase$(fso.getExtensionName(fil.path)) = "TXT" Then
' Get the corresponding TextStream object.
Set ts = fil.OpenAsTextStream(ForReading)
' Read its contems,search the string,close it.
pos = InStr(1,ts.ReadAll,search,vbTextCampare)
ts.Close
If pos > O Then
' If the string has been found,feopen the file
' to determine string position in terms of (line,coiumn)
Set ts = fil.OpenAsTextStream(ForReading)
' Skip all preceding characters to get where
' the search string is
ts.Skip pos - 1
' Fill the result array,make room if necessary
count = count + 1
if count > UBound(result) Then
ReDim Preserve result(UBound(result)+ 50) As Variant
End If
' Each result item is a 3-element array.
result(caunt) = Array(fil.path,ts.Line,ts.Column)
' Now we can close the TextStream.
ts.close
End If
End If
Next
' Resize the result array to indicate number of matches.
ReDim Preserve result(0 TO count) As Variant
SearchTextFiles = result'p194....178
End Function
' An example that uses the above routine; search for a name in all
' the TXT filas In E:/DOCS directory,show the results in
' the IstResuits ListBox,in the format "filename[line,column]"
Dim v() As Variant,i As Long
v() = SearchTextFiles("E:/docs","Francesca Balena")
For i = 1 To UBound(v)
IstResults.AddItem v(i)(0) & "[" & v(i)(1) & "," & v(i)(2) & "]"
Next
表 5-7 TextStream对象的所有属性方法
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
属性方法&语法格式
说明
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
属性AtEndOfLine
如果文件指针位于当前行的结尾,则为True
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
属性AtEndOfFile
如果文件指针位于文件的结尾,则为True(与VBA的EOF函数相似)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
方法Close
关闭文件(与VBA的Close语句相似)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
属性Column
当前列号
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
属性Line
当前行号
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
方法Read(Characters)
读取指定数量的字符并返回一个字符串(与VBA的Input$函数相似)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
方法ReadAll()
将整个文件读到一个字符串中(VBA中的函数与LOF函数一起使用的Input$函数相似)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
方法ReadLine()
读取文本的下一行并返回一个字符串(与VBA的Input$函数相似)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
方法Skip Characters
跳到指定数量的字符
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
方法Skip Line
跳过一行文本
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
方法Write Text
写入一串字符,不用尾随New Line 字符(与以分号结尾的Print#命令相似)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
方法WriteBlankLines Lines
写入指定数量的空白行(与不带参数的一个或多个Print#命令相似)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
方法WriteLine[Text]
写入一串字符,以NewLine字符结尾(与不带分号结尾的Print#命令相似)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

5.6 5Windows交互
迄今为止,我们一直关注自我包含的应用程序,还没有接触到外面的世界.但是在许多情形下,将需要应用程序与其环境交互,包括与其他和应用程序并运行的应用程序之间的交互.本节引入这个话题,并介绍一些解决这种交互的技巧.
5.6.1 App对象
App 对象是由VB库提供的,代表正在执行的应用程序.App对象有许多属性方法,其中许多是先进的,这将在本书的后面说明.
EXEName和Path 属性返回可执行文件的名字和路径或者工程名(如果在环境内部运行).这些属性通常在一起使用 -- 例如,要定位一个保存在同一目录中并且具有相同基本名的INT可执行文件:'p195....179
IniFila = App.Path & IIf(Right$(App.Path,1) <> "/","/","") & App.EXEName & ".INI"
Open IniFile For Input As #1
' and so on.
另一种常见的对 App.Path 属性的使用是设置当前与应用程序匹配的目录,这样可以找到它的所有附属文件,而不需要确定它们的完整路径名
' Let the application's directary be the current directory.
On Error Resume Next
ChDrive App.Path: ChDir App.Path
警告 **** 上述程序段在某些条件下可能会失败,特别是当VB应用从远程网络服务器开始时.这是由于 App.Path 属性可能追回一个UNC路径(例如,server namc/dimame/…)并且 ChDrive 命令不能处理这样的路径.因此,应当保护程序以防不可预测的错误发生,并且应当一直给用户提供其他可能的方法,使应用程序指向它自己的目录(例如,在系统 Registry 中设置一个锁).
Prevlnstance 属性允许确定是否有另一个(已编译过)应用程序实例在系统上运行.如果要防止用户偶然同时运行程序两次,这非常有用:
Private Sub Form_Load()
If App.Pravlnstance Then
' Another instance of this application is running
Dim saveCaption As String
saveCaption = Caption
' Modify this form's caption so that it isn't traced by
' the AppActivate comrnand
Caption = Caption & Space$(5)
On Error Resume Next
AppActivate saveCaption
' Restore the Caption,in case AppActivate Failed
Caption = saveCaption
If Err = O Then Unload Me
End If
End Sub
有一对属性可以在运行时读取和修改.TaskVisibleBoolean 型属性确定应用程序在任务表中是否可见.Title 属性是在Windows任务表中识别应用程序的字符串.其初始值就是设计时在 Project ProperLies 对话框的 Make 选顶卡中输入的字符串.
App 对象的其他属性返回设计时在 Project Properties 对话框的 General和Make 选项卡中输入的数值(见图5-2).例如,HelpFile 属性是相关的帮助文件名,如果有的话. Unattended App和RetainedProject 属性报告相应的对话框中的 General 选项卡中的复选框的状态(它们的意义将在第16章和第20章论述).合起来看,Major.Minor和Revision 属性返同关于运行可执行的版本.Comments,CompanyName.FileDescription.LegalCopyright.LegalTrademarks和ProductName属性允许在运行时查询已在Project Properties 对话框中的 Make 选项卡中输入的其他数值.它们大多在建立提供资料的 About Box 对话框或闪动屏幕时有所用处.'p196....180


截图 5-2 Project Properties对话框的 General 和 Make 选项卡
5.6.2Clipboard对象
在Windows9x和Windows NT的32位世界中,通过系统剪贴板与其他应用程序交换信息好像有一点过时,但事实上剪贴板为最终用户保留了一种最简单,最有效的在各个应用中快速复制数据的方法.VB允许使用 Clipboard 全局对象控制系统剪贴板.与其他VB对象相比.这
是一个非常简单的对象,它有六种方法,没有属性.
1.文本的复制和粘贴
要将一段文本放在剪贴板中,使用SetText方法
Clipboard.SetText Text,[Format]
其中,format可以是l- vbCFText(纯文本,缺省值).&HBFO1 - vbCFRTF(RTF格式的文本)或者 &HBFOO - vbCFLink(DDE转换信息).这个参数是必需的,因为剪贴板可以存储多种格式的信息块.例如,如果有一个 RichTextBox 控件(Microsoft Active X控件,在第12章介绍),就可以将所选文本以 vbCFText或vbCFRTF 格式存储,并允许用户以任何适合目标控件的格式粘贴文本.
Clipboard.Clear
Clipboard.SetText RichTextBox1.SelText' vbCFText is the default.
Clipboard.SatText RichTextBox1.SelRTF,VbCFRTF
警告 **** 在某些情况和一些外部应用程序中,将文本放在剪贴板上不能正常工作,除非使用 Clear 方法重新设置 Clipboard 对象,如上述程序段所示.
可使用 GetText 方法获取当前在剪贴板中的文本,可以用下列语法指定要获取的数据格式:
' For a regular TextBox control
Text1.SelText = Clipboard.GetText()' You can omit vbCFText
' For a RichTextBox control
RichTextBox1.SelRTF = Clipboard.GetText(vbCFRTF)
一般来说,不知道剪贴板实际是否包含RTF格式的文本,所以应该用 GetFormat方法测试其当前内容,它将格式作为参数并返回一个指明剪贴板格式是否与格式参数匹配的Boolean型数值:
If Clipboard.GetFarmat(vbCFRTF) Then
' The Clipboard contains data in RTF format
End If
format 的值可以是1- vbCFText(纯文本). 2- vbCFBitmap(位图). 3- vbCFMetafile(图元文件). 8- vbCFDIB(设备独立位图). 9- vbCFPalette(调色板). &HBF01- vbCFRTF(RTF格式文本)或 &HBFOO- vbCFLink(DDE转换信息).下面是将文本粘贴到 RichTextBox 控件中的正确顺序:
If Clipboard.GetFormat(vbCFRTF) Then
RichTexrBoxl.SelRTF = Clipboard.GetText(vbCFRTF)
ElseIf Clipboard.GetFormat(vbCFText) Then
RichTextBoxl.SelText = Clipboard.GetText()
End If
2.图像的复制及粘贴
在使用 PictureBox 和 Image 控件时,可以使用 GetData 方法获取,保存在Clipboard中的图像,它也需要一个格式属性(vbCFBitmap. vbCFMetafife.vbCFDIB或vbCFPalette -- 对于Image控制只能使用vbCFBitmap).正确的顺序是:
Dim frmt As Variant
For Each frmt In Array(vbCFBitmap,vbCFMetafile,vbCFDIB,vbCFPalette)
If Clipboard.GetFormat(frmt) Then
Set Picturel.Picture = Clipboard.GetData(frmt)
Exit For
End if
Next
利用SetData方法可以将 PictureBox或Image 控件的内容复制到剪贴板上:
Clipboard.SetData Picturel.Picture
' You can also load an image from disk onto the Clipboard
Clipboard.SetData LoadPicture("c:/myimage.bmp")
3.-般的Edit菜单
在多数Windows应用程序中,所有的剪贴板命令一般集中在 Edit 菜单中,提供给用户的命令(以及代码如何处理它们)取决于哪个控件是激活控件.这里要解决两个问题对于一个真正的用户友好的界面,应该将所有不适用于激活的控件和剪贴板当前内容的某单项屏蔽,还有必须设计一个在任何情况下都正常工作的剪贴,复制,粘贴方案.
当在表单中有多个控件时,因为要处理几个潜在的问题,事情很快变得混乱起来.我已经准备了一种简单但完整的示例程序(见图5-3).为使你很容易地在应用程序中复用其代码,所有对控件的调用都通过表单的 ActiveControl 属性来实现.取代利用 TypeOf或TypeName 关键字测试控件类型的方法的是,代码利用 On Error Resume Next 语句间接地测试实际支持哪些属性(参见下列清单中的黑体代码).这个方法可以处理任何类型的控件,包括第三方的 ActiveX 控件,在给ToolBox添加新控件时不需要修改代码.
'p198....182
截图 5-3 Clipboard.vbp 示例工程,展示如何建立一个通用的使用TextBox. RTF TextBox和PicureBox控件的Edit菜单

' Items in Edit menu belong to a control array. Theae are their indices
Const MNU_EDITCUT = 2,MNU_EDITCOPY = 3
Const MNU_EDITPASTE = 4,MNU_EDITCLEAR = 6,MNU_EDITSELECTALL = 7
' Enable/disable items in the Edit menu
Private Sub mnuEdit_Click()
Dim supSelText As Boolean,supPicture As Boolean
' Check which pcoperties are supported by the active control.
On Error Reaume Next
' These expressions return False only it tha property isn't aupported.
supSelText = Len(ActiveControl.SelText) Or True
supPicture = (ActiveControl.Picture Is Nothing) Or True
If supSelText Than
mnuEditltem(MNU_EDITCUT).Enabled = Len(ActiveControl.SelText)
mnuEditltem(MNU_EDITPASTE).Enabled = Clipboard.getFormat(vbCFText)
mnuEditltem(MNU_EDITCLEAR).Enabled = Len(ActiveControl.SelText)
mnuEditltem(MNU_EDITSELECTALL).Enabled = Len(ActiveControl.Text)

ElseIf supPicture Than
mnuEditltem(MNU_EDITCUT).Enabled = Not ActivaControl.Picture is Nothing
mnuEditltem(MNU_EDITPASTE).Enabled = Clipboard.GetFormat(vbCFBitmap) Or _
Clipboard.GetFormat(vbCFMetafile)
mnuEditltem(MNU_EDITCLEAR).Enabled = Not (ActivaControl.Picture is Nothing)
Else
' Neither a texi- nor a ptcture-based control
mnuEditltem(MNU_EDITCUT).Enabled = False
mnuEditltem(MNU_EDITPASTE).Enabled = False
mnuEditltem(MNU_EDITCLEAR).Enabled = False
mnuEditltem(MNU_EDITSELECTALL).Enabled = False
End If
' The Copy menu command always has the sama state as the Cul command
mnuEdititem(MNU_EDITCOPY).Enabled = mnuEdititem(MNU_EDITCUT).Enabled
End Sub
' Actually perform copy-cut-paste commands.
Private Sub mnuEdititem_Click(Index As Integer)
Dim supSelText As Boolean,supSelRTF As Boolean,supPicture As Boolean
' Check Which propertles are supported by the activa control.
On Error Rosume Next
supSelText = Len(ActiveControl.SelText) >= O
supSelRTF = Len(ActiveControl.SelText) >= O
supPicture = (ActiveControl.Picture Is Nothing) Or True
Err.Clear
Salact Case Index
Case MNU_EDITCUT
If supSelRTF Then
Clipboard.Clear
Clipboard.SetText ActiveControl.SelRTF,vbCFRTF
ActiveControl.SelRTF = ""
ElseIf supSelText Then
Clipboard.Clear
Clipboard.SetText ActiveControl.SelText
ActiveControl.SelText = ""
Else
Clipboard.SetData ActiveControl.Picture
Set ActiveControl.Picture = Nothing
End If
Case MNU_EDITCOPY
' Similar to Cut,but the current selection isn't deleted.
if supSelRTF Then
Clipboard.Clear
Clipboard.SetText ActiveControl.SelRTF,vbCFRTF
ElseIf supSelText Then
Clipboard.Clear
Clipboard.SetTaxt ActivaCantrol.SelText
Else
Clipboard.SetData ActiveControl.Picture
End If
Case MNU_EDITPASTE
If supSelRTF And Clipboard.GetFormat(vbCRTF) Then
' Paste RTF text if posaible.
ActivaControl.SelRTF = Clipboard.GetText(vbCFText)
ElseIf supSelText Then
' Else,paste regular Text
ActiveControl.SelText = Clipboard.GetText(vbCFText)
Elself Clipboard.GetFormat(vbCFBitmap) Then
' First,try with bitmap data.
Set ActiveControl.Picture = Clipboard.GetData(vbCFBitmap)
Else
' Else.try with Metafile data.
Set ActiveControl.Picture = Clipboard.GetData(vbCFMetafile)
End If
Case MNU_EDITCLEAR
If supSelText Then
ActiveControl.SelText = ""
Else
Set ActiveControl.Picture = Nothing
End If

Case MNU_EDITSELECTALL
if supSeLText Then
ActiveControl.SelStart = 0
ActiveControl.SelLength = Len(ActiveControl.Text)
End If
End Select
End Sub
5.6.3 Printer对象
许多应用程序要将它们的结果提交到纸上.VB提供了一个Printer对象,它有一些属性方法可精确地控制打印机文档的出现.
VB库还有一个 Printers 集合,可以收集所有安装在系统上的打印机信息.这个集合的每一项都是一个Printer对象,其所有属性都是只读的.换句话说,你可以读取所有已安装的打印机特性,但是不能直接修改它们.如果要修改某一打印机的特性,就必须首先将集合中代表
所选打印机的那一项赋给 Printer 对象,然后改变其属性.
1.获取已安装打印机的信息
Printer 对象具有许多确定一台现有打印机特性及其驱动程序的属性.例如,DeviceName 属性返回出现在 Control Panel 中的打印机的名字,DriverName 返回被这个外部设备使用的驱动程序的名字.利用这个信息来填充 ListBox或ComboBox 控件是很简单的:
For i = 0 To Printers.Count - 1
cboPrinters AddItem Printers(i).DeviceName? "[" & Printers(i).DriverName & "]"
Next
Port属性返回打印机所连接的端口(例如,LPT1:).ColarMode 属性确定打印机是否可以彩色打印(它可以是1- vbPRCMMonochrome或2- vbPRCMColor). Orientation 属性反映页的当前方向(它可以是l- vbPRORPorlrait或2- vbPRORLandscape). PrinterQuality属性返回当前的分辨率(它可以是l- vbPRPQDraft.2- vbPRPQLow.3- vbPRPQMedium 或者4- vbPRPQHigh).
其他的属性有 PaperSize (纸张的大小),PaperBin(进纸箱),Duplex(打印双向纸张的功能).Copies(要打印的副本数)和Zoom(打印时实施的缩放系数).要获得更多有关这些属性的信息.参见VB文档.存所附CD盘上可以找列演示程序(如图5-4所示).它列举了所有已安装的打印机.可浏览到它们的属性,也可在任意一台上打印一页内容.

截图 5-4运行演示程序可看弱Primers肇台和f寿动韵Primer对象
2.当前打印机的使用
一个现代的应用程序应该给其用户提供可以使用任何一台已安装在系统上的打印机的能力.任VB中,通过将一个描述所选打印机的Printers集合中的元素赋给Printer对象来做到这一点.例如,如果给 ComboBox 控件填入所有已安装的打印机名,就可以使用户通过敲击 MakeCurrent 按钮选择其中任何一台打印机:
Private Sub cmdMakeCurrent_click()
Set Printer = Printers(cboPrinters.ListIndex)
End Sub
与能看到的保存 Printers 整合中的 Printer 对象的限定相反,这些属性是只读的,可以修改Printer对象的属性.在理论上,可以写入至今所见到的所有属性.只有 DeviceName.DriverName和Port除外,但在实际中,当给一个属性指定某一数值时会发生什么取决于打印机和驱动程序.例如,如果当前打印机是单色的,给ColorMode属性指定2- vbPRCMColor值没有任何意义.这种指定或者可被忽略,或者会产生错误.这取决于正在使用的驱动程序.一般情况.如果一个属性不受支持,它返回O.
有时,可能需要了解 Printers 集合中的哪一项与 Printer 对象相对应,想要暂时利用另一台打印机进行打印,然后再恢复原来的打印机时.可以将Printer对象的DeviceName属性与Printers集合中的每一项的返回值进行比较:
' Determine the Index of the Printer object in the Printers collectian
For i = 0 to Printers.Count - 1
If Printer.DaviceName = Printers(i).DaviceName Then
PrinterIndex = i:Exit For
End If
Next
' Prepare to output to the printer selected by the user
Set Printer = Printers(cboPrinters.ListIndex)
' Restore the original printer
Set Printer = Printers(PrinterIndex)
另一种让用户打印至其所选打印机的方法就是将 Printer的TrackDefault 属性设置为True.在这样做时,Printer 对象自动引用在Control Panel中选择的打印机.
3.将数据输出到 Printer 对象
因为Printer 对象支持所有 Form和PictureBox 对象具有的图形方法,包括Print.PSet.Line.Circle和PaintPicture,所以给Printer对象发送输出是很普通的.不可以使用一些标准属性如Font对象和单独的Fontxxxx属性.CurrentX和CurrentY 属性控制输出的外观.
Printer 对象有三个独特的方法.EndDoc 方法告诉 Printer 对象所有数据已被发送,实际的打印操作可以开始.KiIIDoc 方法在给打印机设备发送任何东西前中断当前的打印任务.最后一个 NewPage 方法给打印机(或打印机假脱机程序)发送当前页并进到下一页.它还重新设置打印页中可打印区域的左上角的打印位置,并增加页号.利用Page属性可以获取当前页号.这里是一个打印两页文档的例子:
Printer.Print "Page One"
Printer.NewPage
Prlnter.Print"Page Two"
Printer.EndDoc
Printer 对象还支持一些标准属性ScaleLeft.ScaleTop.ScaleWidth和ScaleHeight,它们用由 ScaleMode 属性指定的测量单位表示(通常以缇表示).ScaleLeft和ScaleTop 属性缺省返回值为O,并指向可打印区域的左上角.ScaleWidth和ScaleHeight 属性返回可打印区域的右下角的坐标值.
5.6.4运行其他应用程序
VB允许利用 Shell 命令运行其他Windows应用程序,Shell命令的语法格式为:
Taskld = Shell(PathName,[Windowstyle])
PathName可以包含一个命令行.WindowStyle是下列常量之一:O- vbHide(窗口被隐藏起来并将之聚焦).1- vbNormalFocus(窗口有聚焦点并被恢复至其原有的尺寸和位置).2- vbMinimizedFocus(窗口显示为带有聚焦点的图标 -- 这是缺省值).3-vbMaximizedFocus(窗口被最大化并带有聚焦点).4- vbNormalNoFocus(窗口被恢复,但不带聚焦点)或者6- vbMinimizedNoFocus(窗口被最小化,聚焦没有离开激活窗口).参看下例如何运行 Notepad并给它装入一个文件
'NO need to provide a path if Notepad.Exe is on the system path.
Shell "notepad c:/bootlog.txt",vbNormalFocus
Shell函数不可与外部程序同步执行.这意味着控件快速返回虱JVB应用程序,它因此可以继续执行其自己的代码.在大多数程序中,这个特性很好,因为它利用了Windows的多任务的本质,但有时可能需要等待一个外壳程序来完成(例如,如果需要处理其结果)或简单地检
查一下它是否还在运行.VB没有提供一个可以获取这个信息的函数,但是你可以使用一些 Windows API函数来进行这个工作.我已准备了一个多用途的函数检测外壳程序是否还在执行,等待所确定的超时任选项(省略这个参数就永远等待),然后如果程序仍在运行就返回True:
' API edcfarations
Private Declare Function WaitForSingleObject Llb"kerne132" _
(ByVal hHandle As Long,ByVal dwMilliseconds As Long) As Long
Private Declare Function OpenProcess Lib"kernel32"(ByVal dwAccess As _
Long,ByVal flnherit As integer,ByVal hObject As Long) As Long
Private Daclare Function CloseHandle Lib "kerne132" _
(ByVal hObject AS Long) AS Long
' Wait for a number of milliseconds,and return the running status of a
' process. if argument is omitted,wait until the process terminates
Function WaitForProcess(taskld As Long,Optional msecs As Long = -1) As Boolean
Dim procHandle As Long
Get the process handle
procHandle = OpenProcess(&H100000,taskld)
'Check for its signaled status; return to caller
WaitForProcess = WaitForSingleObject(procHandle,msecs)<> -1
' Clase the handle
CloseHandle procHandle
End Function
传递给该例程的参数是Shell函数的返回值.
' Run Notepad,and wait until it is closed
WaitForProcess Shell("notepad c:/bootlog.txt",vbNormalFocus)
有几种方法可与运行中的程序交互.在第16章,我将说明如何通过COM控制一个应用程序,但并不是所有外部应用程序可用这种方法控制.即使它们可以,有时结果并不值得额外付出努力.在无要求的情况下,可以利用一种简单的基于 AppActivate和SendKeys 命令的方法
完成这个工作.AppActivate 命令将输入焦点转移到与其第一个参数匹配的应用程序上:
AppActivate WindowTitle(.wait]
WindowTitle可以是一个字符串或者Shell函数的返值.在前一种情况下,如果不存在一个准确的匹配.VB就反复查找一个以这个字符串参数开头的窗口.如果传递的是由Shell函数返回的taskid值,就不会有第二个传递,因为taskid惟一标识一个运行过程.如果VB不能找到要求的窗口,就会在运行时产生错误.Wait是一个任选参数,它指明VB是应等待直至当前应用程序在将之发送给其他程序之前有输入焦点(Wait=True),或是还必须立即执行命令(Wait=FaLse,缺省状态).
SendkeVs语句给当前有输入焦点的应用程序发送一个或多个键.这个语句支持一种相当复杂的语法.可以指定控制键如Ctrl.Alt和Shift键.箭头键.功能键等等(参见VB文档以获取更多信息).这个程序运行Notepad,然后给它提供焦点,井将当前剪贴板的内容粘贴到它的窗口中:
Taskld = Shell("Notapad" vbMaximizedFocus)
AppActivate Taskld
SendKeys"^V"'ctrl - V

p204
现在已经具备所有外部程中运行的需求,与之交互,井查明它何时完成其操作.还可以试验几个不同的设置值(见图5—5).完整的源代码可在所附光盘CD上找到.
截图 5-5 说明怎样使用Sheil. AppAcrivaie和SendKeys语句的演示程序
5.6.5展示Halp
一个成功的Windows应用程序始终应当给初学用户提供一个指导,一般以帮助文件的形式出现.VB支持两种不同的展示此类用户信息的方法,都使用HELP文件页.
l.写一个帮助文件
在两种情况下都必须首先建立一个帮助文件.要做到这点,需要一个能够产生RTF格式文件的字处理器(例如Microsoft Word)和一个帮助编译器.在VB 6 CD-ROM中,你可以发现Microsoft Help WorkShop,如图5-6所示.可以让你集成所有已准备好的文档和位图并把它们编辑到一个HELP文件中.
写一个帮助文件是一件复杂的事情,已超出了本书的范围.可以从与Microsoft Help WorkShop 装在一起的文档中获得有关这个话题的信息.
但是在我看来,最有效的方法是依赖于第三方共享软件或商业软件,例如Blue Sky Software的RoboHelp 或者WoxTech的Doc-To-Help,可以使得建造帮助文件变成一种简单而直现的过程.
一旦已经产生了一个HELP文件,就可以在VB应用程序中引用它.可以在设计时通过在 Project Propenies 对话框中的 General选项卡中键入文件名或在运行时通过给App.HelpFile 属性指定一个值来引用它.后一种方法在不能确定帮助文件将被安装在何处时是必需的.例如,可以在应用程序的主文件夹下的一个目录中设定这个路径:
' if S lile refarence is incorrect,Visual Basic raises an error
' when you later try to access this file
App.HalpFile = App.Path & "/Help/MyApplication.Hlp"

截图5-6 Help Workshop工具在VB CD - ROM上,必须分开安装

2.标准的窗口帮助
第一种提供上下文有关的帮助的方珐是基于Fl键.这类帮助使用了HelpComtexID属性,它被所有VB的可见对象支持,包括窗体.内部控件和外部ActiveX控件.还可以在设计时,在 Project Properties 对话框中输入一个应用程序的帮助上下文ID(尽管App对象在运行时不具
有等价的属性).
用户按下F1键,VB捡查具有焦点的控件的 HelpComtextID 属性是否有一个非零值,如果有.就显示与这个ID相关的帮助页面;否则.VB就检查是否父窗体有一个非零的 HeIpContextID 属性,如果有就显示相应的帮助页面.如果控件和窗体的 HelpContextID 属性都为0.VB就显示与工程程序的帮助上下文ID相对应的页面.
3.Whars Thih帮助
VB还支持另外一种显示帮助的方法,即所谓 What's This(这是什么)的帮助.可以通过在窗体的右上角显示一个What's This按钮来支持这种帮助模式,如图5-7所示,当用户按击这个按钮时,鼠标光标变成一个箭头和个问号,用户然后可以单击任何窗体上的控件以获得关于这个控件是什么,做什么的快速解释.
要在程序中利用这个特性,必须将窗体的 WhatsThisButton 属性设置为True.才能使What's This 按钮出现在窗体标题上.

截图 5-7 一个窗体的右上角屏幕的缩放相片,窗体中的What's This按钮已被按击

这个属性在运行时是只读的,所以只能在设计时在 PropeFties 窗口中设置它.还有,要使 What's This按钮出现,必需将 BorderStyle 属性设为 l- Fixed Sigle 或为3- Fixed DiaLag,要不然就必须将属性 MaxButton和MinButton 设置为False.
如果不能满足这些要求.就不能显示 What's This按钮.但是可以一直给用户提供一个按钮或一个菜单命令,通过执行窗体的WhatsThisMode 方法输入这个模式:
Private SUb cmdWhatsThis_Click()
' Enter What's This mode and changemouse cursor shape.
WhatsThisMode
End Sub' p206....190
窗体上的每一个控件(不是窗体本身)都有 WhatsThisHelpID属性.你给这个属性指定页面的帮助上下文ID,当处于What's This模式时,这个页面用户接击控件后将显示出来.
最后窗体的WhatsThisHelp属性必须被设置为 True 以激活 What's This帮助.如果这个属性被设置为False,VB就回复到基于F1键和HelpContextID 属性的标准帮助机制.WhatsThisHelp 属性只能在设计时设置.在这一点上,有三种显示 What's This 帮助的方法
* 用户单击 What's This 按钮,然后单击一个控件,在这种情况下,VB自动显示与被单击控件的 WhatsThisHelpID 属性相关的What's This帮助.
* 用户单击一个按钮或选择一个利用 WhatsThisMode 方法有计划地输入 What's This 帮助模式的菜单项(参见前面的程序段),然后单击一个控件.VB也显示与被单击控件的 WhatsThisHelpID 属性相关的 What's This 帮助.
* 可以通过执行控件的 ShowWhatsThis 方法来有计划地调用与控件的 WhatsThisHelpID 属性相关的 What's This 帮助(所有的内部和外部控件都支持这种方法).
无论使用何种方法,不要忘记必须为应用程序的每一个窗体的每一个控件准备一个帮助页.多个控件共享同一帮助页是合法的,但这种安排对用户来说可能特别混乱.因此,一般不同的页对应不同的控件.
在前5章中,我已经尽可能多地展示了VB环境和VBA语言.到现在为止,你已获得了足够的编写有价值软件的信息.但是本书的焦点在于如何用 OOP 来建立复杂的.真实世界的应用程序.

.........................................p207....

猜你在找的VB相关文章