📄 49.asp
字号:
<P> </P>
</DIR>
</DIR>
<P>在以上程式中参数一 folder 最值得注意, 由於「启动」资料夹是「程式集」的子资料夹,
所以将此一参数写成 "\启动", 再举个例子, 假设我们想把同样的捷径建立在「桌面」上,
则此一参数应设定为 "..\..\Desktop"(NT 中文版则为 "..\..\桌面"),
因为 "..\.." 代表 Windows 的所在目录, 而所谓「桌面」其实就是
Windows 底下的 Desktop(桌面)子资料夹, 所以将此一参数写成 "..\..\Desktop"("..\..\桌面")。</P>
<P> </P>
<P>除了 4 个参数之外, fCreateShellLink 的传回值表示「是否成功地建立了捷径」,
如果等於 1, 表示成功, 等於 0, 表示失败。</P>
<P> </P>
<P>在 VB5 里面使用 fCreateShellLink, 必须撰写的 API 宣告式如下:</P>
<P> </P>
<DIR>
<DIR>
<P><FONT SIZE=+0>Declare Function fCreateShellLink Lib "vb5stkit.DLL"
(ByVal folder As String, ByVal ShortCutName As String, ByVal ExePath As
String, ByVal Params As String) As Long</FONT></P>
<P> </P>
</DIR>
</DIR>
<P>但如果您使用的是 VB4 32-bit 版, 则必须将以上的 vb5stkit.dll 改成 stkit432.dll。最後请注意,
vb5stkit.dll(stkit432.dll) 不是 Windows 所提供 API 函数, 呼叫之前, 必须将此一档案复制到
Windows、Windows 的 System 目录、或应用程式所在目录, 但如果您使用 VB 的「安装精灵」安装应用程式,
则「安装精灵」會自动复制此一档案到 Windows 的 System 目录。</P>
<P> </P>
<P>为了让您进一步体验 fCreateShellLink 函数的使用, 笔者特别准备了如图-1的表单(放置於范例程式的
Form3):</P>
<P> </P>
<CENTER><P><A HREF="49-1.gif">图-1 笔者所撰写的 fCreateShellLink 试验程式</A></P></CENTER>
<P> </P>
<P>您可以利用此一表单设定不同的参数(参数与表单上各栏位的对应如图-1之标示),
然後检测建立捷径的情况, 检测时, 笔者必须说明的是, 呼叫建立 fCreateShellLink
之後, 再按下「开始」工具列时, 被建立的捷径不一定會马上出现在其中, 这是因为「开始」工具列未即时更新的缘故,
但您可以利用档案总管功能表的「检视/重新整理」让「开始」工具列立即更新。</P>
<P> </P>
<P>附带说明:中文 Windows 的「启动」资料夹名称是 "启动", 但英文
Windows 却是 "StartUp", 而不同语言的 Windows 可能又所不同,
「桌面」的情况亦然, 因此如果您要在「启动」资料夹中或「桌面」上建立捷径,
必须考虑不同语言的问题。</P>
<P> </P>
<H3><A NAME="Q7"></A>问题7:如何启动 Windows 预设的执行档开启某一文件?
<HR WIDTH="100%"></H3>
<P> </P>
<P>举例来说, .txt 的文件希望用「记事本」开启、.doc 的文件用 Word 开启、.bmp
的文件用「小画家」开启…, 就好像利用「档案总管」开启文件一样。</P>
<P> </P>
<P>当我们想在 VB 程式中执行某一个程式时, 最简单的方法是呼叫 Shell 叙述,
例如「Shell "Notepadc:\test.txt"」, 但 Shell 叙述必须指定好执行档,
所以并不适用於此一问题。想要像档案总管一样开启文件, 需呼叫 ShellExecute
API 函数, 先举个简单的例子, 假设想开启 c:\Windows 目录的 general.txt
文件, 则方法如下:</P>
<P> </P>
<DIR>
<DIR>
<P><FONT SIZE=-1>Call ShellExecute(Me.hwnd, "open", "c:\Windows\general.txt",
"", "", SW_SHOW)</FONT></P>
<P> </P>
</DIR>
</DIR>
<P>以上叙述笔者省略了参数四及参数五, 其中参数四表示传递给执行档的参数,
但由於此一 ShellExcute 叙述已经是用来开启文件, 所以此一参数通常设定为
"", 参数五则表示工作目录, 若设定为 "", 则以文件的所在目录为工作目录。此外,
参数六表示文件开启後显示的方式, SW_SHOW 表示正常大小, 若设定成 SW_SHOWMINIMIZED,
则以最小化的视窗来显示, 若设定成 SW_SHOWMAXIMIZED, 则以最大化的视窗来显示。</P>
<P> </P>
<H3><A NAME="Q8"></A>问题8:如何在启动某一个程式之後, 等待此一程式结束执行後才继续执行。
<HR WIDTH="100%"></H3>
<P> </P>
<P>当我们呼叫 Shell 时, 會传回一个数值, 此一数值称为 Process Id, 利用此一
Process Id, 我们可以呼叫 OpenProcess API 取得 Process Handle, 然後再利用
Process Handle 呼叫 WaitForSingleObject, 即可等待被 Shell 执行的程式执行完毕後,
才继续向下执行。程式如下:(以执行 Notepad 程式为例)</P>
<P> </P>
<DIR>
<DIR>
<P><FONT SIZE=+0>Dim pId As Long ' 宣告</FONT> Process Id 变数</P>
<P><FONT SIZE=+0>Dim pHndn As Long ' 宣告</FONT> Process Handle 变数</P>
<P><FONT SIZE=+0>pId = Shell("Notepad", vbNormalFocus) ' Shell
传回</FONT> Process Id</P>
<P><FONT SIZE=+0>pHnd = OpenProcess(SYNCHRONIZE, 0, pId) ' 取得</FONT>
Process Handle</P>
<P><FONT SIZE=+0>If pHnd <> 0 Then </FONT></P>
<P><FONT SIZE=+0>Call WaitForSingleObject(pHnd, INFINITE) ' 无限等待,
直到程式结束</FONT></P>
<P><FONT SIZE=+0>Call CloseHandle(pHnd) </FONT></P>
<P><FONT SIZE=+0>End If</FONT></P>
<P> </P>
</DIR>
</DIR>
<P>至於程式的工作原理, 由於故事很长, 原谅笔者暂时不做进一步的解说。使用此一方法时,
请特别注意, 在等待的时候, 原来的程式是完全不能操作的, 因此笔者建议在呼叫
WaitForSingleObject 之前, 先将原程式的视窗隐藏起来, 直到等待结束时(也就是
WaitForSingleObject 之後), 才重新显示视窗。</P>
<P> </P>
<H3><A NAME="Q9"></A>问题9:在多行的 TextBox 中, 如何计算行数?
<HR WIDTH="100%"></H3>
<P> </P>
<P>这个问题如果不使用 Windows API, 使用 VB, 则方法如下:</P>
<P> </P>
<DIR>
<DIR>
<P><FONT SIZE=+0>Dim S As String, N As Integer, pos As Integer</FONT></P>
<P><FONT SIZE=+0>S = Text1.Text</FONT></P>
<P><FONT SIZE=+0>pos = InStr(S, vbCr + vbLf) ' vbCr + vbLf 为</FONT> TextBox
的断行字元</P>
<P><FONT SIZE=+0>While pos > 0</FONT></P>
<P><FONT SIZE=+0>N = N + 1</FONT></P>
<P><FONT SIZE=+0>S = Mid(S, pos + 2)</FONT></P>
<P><FONT SIZE=+0>pos = InStr(S, vbCr + vbLf)</FONT></P>
<P><FONT SIZE=+0>Wend</FONT></P>
<P><FONT SIZE=+0>N = N + 1</FONT></P>
<P><FONT SIZE=+0>' N 即等於</FONT> Text1 的行数</P>
<P> </P>
</DIR>
</DIR>
<P>但以上程式遇到 TextBox 行数很多时, 执行效能會比较差一点, 因此可以考虑使用以下的
API 方法:</P>
<P> </P>
<DIR>
<DIR>
<P><FONT SIZE=+0>Dim N As Long </FONT></P>
<P><FONT SIZE=+0>N = SendMessage(Text1.hwnd, EM_GETLINECOUNT, 0, ByVal
0&)</FONT></P>
<P><FONT SIZE=+0>' N 即等於</FONT> Text1 的行数</P>
<P> </P>
</DIR>
</DIR>
<H3><A NAME="Q10"></A>问题10:如何判断某一个 Drive 是否为光碟机?
<HR WIDTH="100%"></H3>
<P> </P>
<P>须呼叫 Windows API 的 GetDriveType 函数, 假设我们想判断 "D:"
碟是否为光碟机, 则方法如下:</P>
<P> </P>
<DIR>
<DIR>
<P><FONT SIZE=+0>DriveType = GetDriveType ( "D:\")</FONT></P>
<P><FONT SIZE=+0>If DriveType = DRIVE_CDROM Then ' 表示光碟机</FONT></P>
<P> </P>
</DIR>
</DIR>
<P>请注意 GetDriveType 的参数不可以写成 "D" 或 "D:",
必须写成 "D:\"。GetDriveType 除了可以用判断光碟机之外, 以下是各种传回值的意义:</P>
<P> </P>
<CENTER><TABLE CELLSPACING=2 WIDTH="399" >
<TR>
<TD WIDTH="53%" VALIGN="TOP" BGCOLOR="#ffff00"><FONT SIZE=+0>传回值</FONT></TD>
<TD WIDTH="47%" VALIGN="TOP" BGCOLOR="#ffff00"><FONT SIZE=+0>意义</FONT></TD>
</TR>
<TR>
<TD WIDTH="53%" VALIGN="TOP"><FONT SIZE=+0>0</FONT></TD>
<TD WIDTH="47%" VALIGN="TOP"><FONT SIZE=+0>无从判断</FONT></TD>
</TR>
<TR>
<TD WIDTH="53%" VALIGN="TOP"><FONT SIZE=+0>1</FONT></TD>
<TD WIDTH="47%" VALIGN="TOP"><FONT SIZE=+0>根目录不存在</FONT></TD>
</TR>
<TR>
<TD WIDTH="53%" VALIGN="TOP"><FONT SIZE=+0>DRIVE_REMOVABLE(= 2)</FONT></TD>
<TD WIDTH="47%" VALIGN="TOP"><FONT SIZE=+0>可移式磁碟, 例如软碟</FONT></TD>
</TR>
<TR>
<TD WIDTH="53%" VALIGN="TOP"><FONT SIZE=+0>DRIVE_FIXED(= 3)</FONT></TD>
<TD WIDTH="47%" VALIGN="TOP"><FONT SIZE=+0>硬碟</FONT></TD>
</TR>
<TR>
<TD WIDTH="53%" VALIGN="TOP"><FONT SIZE=+0>DRIVE_REMOTE(= 4)</FONT></TD>
<TD WIDTH="47%" VALIGN="TOP"><FONT SIZE=+0>远端</FONT>(网路)储存装置</TD>
</TR>
<TR>
<TD WIDTH="53%" VALIGN="TOP"><FONT SIZE=+0>DRIVE_CDROM(= 5)</FONT></TD>
<TD WIDTH="47%" VALIGN="TOP"><FONT SIZE=+0>光碟机</FONT></TD>
</TR>
<TR>
<TD WIDTH="53%" VALIGN="TOP"><FONT SIZE=+0>DRIVE_RAMDISK(= 6)</FONT></TD>
<TD WIDTH="47%" VALIGN="TOP"><FONT SIZE=+0>RAM Disk</FONT></TD>
</TR>
</TABLE></CENTER>
<P> </P>
<P>如果我们想列举出所有磁碟机的类型, 则可先在表单上布置一个 DriveListBox(假设它的名称是
Drive1) 控制元件, 然後再利用以下程式列举:</P>
<P> </P>
<DIR>
<DIR>
<P><FONT SIZE=+0>Dim dTypeStr(0 To 6) As String, dType As Long</FONT></P>
<P><FONT SIZE=+0> </FONT></P>
<P><FONT SIZE=+0>dTypeStr(0) = "无从判断</FONT>" : dTypeStr(1)
= "根目录不存在"</P>
<P><FONT SIZE=+0>dTypeStr(2) = "软碟</FONT>" : dTypeStr(3) =
"硬碟"</P>
<P><FONT SIZE=+0>dTypeStr(4) = "远端</FONT>(网路)储存装置"</P>
<P><FONT SIZE=+0>dTypeStr(5) = "光碟机</FONT>" : dTypeStr(6)
= "RAM Disk"</P>
<P><FONT SIZE=+0>For I = 0 To Drive1.ListCount - 1</FONT></P>
<P><FONT SIZE=+0>Drv = Left(Drive1.List(I), 2) & "\"</FONT></P>
<P><FONT SIZE=+0>dType = GetDriveType(Drv)</FONT></P>
<P><FONT SIZE=+0>Debug.Print Drv & " is " & dTypeStr(dType)</FONT></P>
<P><FONT SIZE=+0>Next</FONT></P>
<P> </P>
</DIR>
</DIR>
<H3><A NAME="Q11"></A>问题11:如何读取档案的建立时间及存取时间?
<HR WIDTH="100%"></H3>
<P> </P>
<P>如果我们利用 VB 所提供的 FileDateTime 来读取档案的时间, 则所得到的是档案最後一次被修改的时间,
但是当我们利用档案总管来检视某一个档案时, 除了档案「修改时间」之外, 却还可以看到档案的「建立时间」与「存取时间」,
如图-2。</P>
<P> </P>
<CENTER><P><A HREF="49-2.gif">图-2 档案总管所显示的档案内容除了「修改时间」之外,
还有「建立时间」与「存取时间」。</A></P></CENTER>
<P> </P>
<P>想要进一步读取档案的相关资讯, 必须先呼叫 API 函数的 OpenFile 取得档案的
Handle, 然後再利用 Handle 呼叫 GetFileInformationByHandle 读取档案的相关资讯,
而在读取的档案相关资讯中便含有档案建立、修改、及存取时间, 程式执行过程如下:(假设想读取的档案是
"c:\autoexec.bat")</P>
<P> </P>
<UL>
<P><FONT SIZE=+0>Dim FileHandle As Long</FONT></P>
<P><FONT SIZE=+0>Dim FileInfo As BY_HANDLE_FILE_INFORMATION</FONT></P>
<P><FONT SIZE=+0>Dim lpReOpenBuff As OFSTRUCT, ft As SYSTEMTIME</FONT></P>
<P><FONT SIZE=+0>Dim tZone As TIME_ZONE_INFORMATION</FONT></P>
</UL>
<P><FONT SIZE=+0> </FONT></P>
<UL>
<P><FONT SIZE=+0>Dim dtCreate As Date ' 建立时间</FONT></P>
<P><FONT SIZE=+0>Dim dtAccess As Date ' 存取日期</FONT></P>
<P><FONT SIZE=+0>Dim dtWrite As Date ' 修改时间</FONT></P>
<P><FONT SIZE=+0>Dim bias As Long</FONT></P>
<P><FONT SIZE=+0> </FONT></P>
<P><FONT SIZE=+0>' 先取得</FONT> autoexec.bat 的 File Handle</P>
<P><FONT SIZE=+0>FileHandle = OpenFile("c:\autoexec.bat", lpReOpenBuff,
OF_READ)</FONT></P>
<P><FONT SIZE=+0>' 利用</FONT> File Handle 读取档案资讯</P>
<P><FONT SIZE=+0>Call GetFileInformationByHandle(FileHandle, FileInfo)</FONT></P>
<P><FONT SIZE=+0>Call CloseHandle(FileHandle)</FONT></P>
</UL>
<P><FONT SIZE=+0> </FONT></P>
<UL>
<P><FONT SIZE=+0>' 读取</FONT> Time Zone 资讯, 因为上一步骤的档案时间是「格林威治」时间</P>
<P><FONT SIZE=+0>Call GetTimeZoneInformation(tZone)</FONT></P>
<P><FONT SIZE=+0>bias = tZone.bias ' 时间差, 以「分」为单位</FONT></P>
</UL>
<P><FONT SIZE=+0> </FONT></P>
<UL>
<P><FONT SIZE=+0>Call FileTimeToSystemTime(FileInfo.ftCreationTime, ft)
' 转换时间资料结构</FONT></P>
<P><FONT SIZE=+0>dtCreate = DateSerial(ft.wYear, ft.wMonth, ft.wDay) +
TimeSerial(ft.wHour, ft.wMinute - bias, ft.wSecond)</FONT></P>
<P><FONT SIZE=+0>Call FileTimeToSystemTime(FileInfo.ftLastAccessTime, ft)</FONT></P>
<P><FONT SIZE=+0>dtAccess = DateSerial(ft.wYear, ft.wMonth, ft.wDay) +
TimeSerial(ft.wHour, ft.wMinute - bias, ft.wSecond)</FONT></P>
<P><FONT SIZE=+0>Call FileTimeToSystemTime(FileInfo.ftLastWriteTime, ft)</FONT></P>
<P><FONT SIZE=+0>dtWrite = DateSerial(ft.wYear, ft.wMonth, ft.wDay) + TimeSerial(ft.wHour,
ft.wMinute - bias, ft.wSecond)</FONT></P>
</UL>
<P> </P>
<P>执行以上程式所得到的 dtCreate、dtWrite、及 dtAccess 变数, 即分别为档案建立、修改、及存取时间。</P>
<P> </P>
<H3><A NAME="Q12"></A><FONT SIZE=+1>问题</FONT>12:如何以程式控制多行 TextBox
的卷动?
<HR WIDTH="100%"></H3>
<P> </P>
<P>首先请回顾问题-9的程式, 在问题-9 的程式中, 我们利用 SendMessage 传送
EM_GETLINECOUNT 讯息给 TextBox, 而 TextBox 收到讯息时, 會判断讯息的编号,
然後计算行数并且回传, 此一工作模式, 我们可以把传送给 TextBox 的讯息当成对
TextBox 所下的指令, 而对於控制 TextBox 的卷动来说, 所传送的讯息(下达的指令)是
EM_LINESCROLL, 程式则如下:</P>
<P> </P>
<UL>
<P><FONT SIZE=+0>Dim N As Long <BR>
Call SendMessage(Text1.hwnd, EM_LINESCROLL, 0&, ByVal N ) ' 下卷</FONT>N行</P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -