Visual2000 · Архив статей А.Колесова & О.Павловой
Андрей Колесов, Ольга Павлова
© 1998, Андрей Колесов, Ольга ПавловаВ составе Win32 API есть функции, с помощью которых можно узнать имена каталогов, необходимых часто для работы приложения: текущего (где находится само приложение), временного (для хранения временных файлов), операционной системы (Windows) и системного (System). (Не совсем понятно, зачем нужно иметь отдельную функцию для определения системного каталога - он всегда имеет имя \System внутри основного каталога ОС.)
Вариант подпрограммы StandardDirNameAPI$, которая выполняет эти операции, приведен в листинге 2 (модуль SDirName.BAS). По поводу этого программного кода можно сделать еще несколько замечаний. Во-первых, видно, что все четыре API-функции совершенно однотипны, но почему-то в двух из них параметры (имя строкового буфера и его длина) записаны в одном порядке, а в других - в обратном. Во-вторых, при обращении к GetTempPath имя каталога возвращается с символом "\" в конце, в остальных функциях - без него. Именно поэтому в процедуре StandardDirNameAPI$ было бы полезно использовать код, который приводил бы имя каталога в некоторый стандартный вид.
Attribute VB_Name = "SDirName" Option Explicit Declare Function GetCurrentDirectory Lib "kernel32" _ Alias "GetCurrentDirectoryA" (ByVal nBufferLength _ As Long, ByVal lpBuffer As String) As Long Declare Function GetTempPath Lib "kernel32" Alias _ "GetTempPathA" (ByVal nBufferLength As Long, ByVal _ lpBuffer As String) As Long Declare Function GetSystemDirectory Lib "kernel32" _ Alias "GetSystemDirectoryA" (ByVal lpBuffer As _ String, ByVal nSize As Long) As Long Declare Function GetWindowsDirectory Lib "kernel32" _ Alias "GetWindowsDirectoryA" (ByVal lpBuffer As _ String, ByVal nSize As Long) As Long Public Static Function StandardDirNameAPI$(Index%) ' ' Определение имен стандартных каталогов Windows ' ' Длина буфера для имени каталога Const BufferLength& = 256 Dim Result&, Buffer$, DirName$ Buffer$ = Space$(BufferLength) Select Case Index% Case 0 ' Текущий каталог Result& = GetCurrentDirectory(BufferLength&, _ Buffer$) Case 1 ' Временный каталог Result& = GetTempPath(BufferLength&, Buffer$) Case 2 ' Каталог Windows Result& = GetWindowsDirectory(Buffer$, _ BufferLength&) Case 3 ' Системный каталог Result& = GetSystemDirectory(Buffer$, _ BufferLength&) End Select ' DirName$ = Left$(Buffer$, Result&) ' Убрать "\" справа в имени каталога 'If Right$(DirName$, 1) = "\" Then DirName$ = _ ' Left$(DirName$, Result& - 1) или наоборот добавить "\" If Right$(DirName$, 1) <> "\" Then DirName$ = _ DirName$ + "\" StandardDirNameAPI$ = DirName$ End Function
В результате мы формулируем еще один общий СОВЕТ: "Разработчики (в том числе и создатели Win API)! Общайтесь между собой и на рабочие темы!"
Это совет вытекает из предыдущего и является его продолжением. Для тестирования процедуры StandardDirNameAPI$ мы предлагаем создать небольшое приложение Tip164.vbp в виде формы, на которой расположен массив из четырех командных кнопок, двух меток и одного текстового поля (рис. 3) и подключенного модуля SDirName.BAS.
Рис. 3
Весь программный код формы состоит из одной событийной процедуры:
Private Sub cmdAction_Click(Index As Integer) ' Определение имени каталога Dim DirName$ DirName$ = StandardDirNameAPI$(Index) If DirName$ = "" Then _ DirName$ = " Не смогли определить " Text1.Text = cmdAction(Index).Caption + " = " + _ DirName$ End Sub
Если нумерация кнопок (сверху вниз) правильно согласуется с их названиями, то после щелчка командной кнопки в текстовом поле будет появляться имя соответствующего каталога. Теперь начнем наше исследование.
Что такое каталоги Windows и System - в целом понятно. Их имена раз и навсегда жестко фиксируются в момент инициализации Windows. С текущим каталогом дела обстоят посложнее. Это каталог, где содержится запущенное на выполнение приложение. Соответственно, если вы запускаете Tip164.vbp в среде VB, то текущим будет каталог, где находится VB. Если же вы создадите и запустите автономное приложение Tip164.exe, то текущим будет каталог, где размещается этот файл.
А что такое временный каталог? Вопрос довольно актуальный, лично нас всегда поражало какое-то не поддающееся простой логике хаотическое появление временных файлов в различных разделах диска.
В литературе по Windows 95 встречается такое определение: "Это каталог, описанный переменной окружения TMP. Если переменная TMP не задана - то переменной TEMP. Если TEMP также отсутствует, то тогда это текущий каталог приложения". Однако наши эксперименты показали, что эта формулировка не верна.
Действительно, временным является каталог, описанный переменной окружения TMP (например, в AUTOEXEC.BAT). Но если TMP не задана, то временным является подкаталог \TEMP в каталоге операционной системы (Windows). Причем, если его не было раннее, то он создается автоматически. Но это еще не все.
Предположим, у вас есть несколько логических дисков и вы в качестве временного почему-то решили определить корневой каталог диска D:. Если вы запишете SET TMP=D:\, то все будет OK. Но если забыли в конце строки "\", то тут нужно разбираться отдельно.
Так вот, в последнем случае определение временного каталога зависит от местонахождения проекта Tip164. Если он расположен вне диска D:, то временным каталогом будет D:\. Если же он находится на самом диске D:, то временным будет... каталог самого проекта (этот случай приведен на рис. 3). Причем независимо от того, запущен Tip164 из среды VB или как автономный файл.
Короче говоря, будет время - поэкспериментируйте. Вы обнаружите много любопытных вещей и еще раз подивитесь витиеватости замыслов разработчиков Microsoft Windows.
Как известно, VB не позволяет передавать массив элементов управления в качестве аргумента в подпрограмму или функцию. И тем не менее вполне возможно придумать решение этой проблемы. Скажем, вы хотите включать/отключать любой набор элементов управления на форме. Это можно сделать с помощью следующей подпрограммы, помещенной в отдельный в модуль:
Public Sub EnableControls(Form As Form, _ ControlArray As Control, State As Boolean) ' Dim ctl As Control For Each ctl In Form.Controls If ctl.Name = ControlArray.Name Then ' Данный элемент управления является ' элементом массива ctl.Enabled = State End If Next End Sub
Чтобы проверить ее работу, создайте форму и разместите на ней массив полей текста с именем txtSample и массив флажков chkSample. Добавьте две командные кнопки (cmdText и cmdCheck) и для каждой из них в событие Click введите соответственно два таких кода:
Call EnableControls(Me, txtSample(0), Not (txtSample(0).Enabled)) Call EnableControls(Me, chkSample(0), Not (chkSample(0).Enabled))
Запустите на выполнение ваше приложение и по очереди щелкните кнопки, чтобы включить/отключить присоединенные группы элементов управления.
Предположим, что у вас есть пронумерованные элементы (в нашем случае - файлы):
FILE1.BMP FILE2.BMP FILE3.BMP FILE10.BMP
Если вы поместите их в элемент управления ListBox или ComboBox, то после сортировки они будут представлены в списке следующим образом:
FILE1.BMP FILE10.BMP FILE2.BMP FILE3.BMP
А вы хотите, чтобы они выводились так:
FILE1.BMP FILE2.BMP FILE3.BMP FILE10.BMP
Для этого можно использовать подпрограмму ReSort (листинг 3). После того как вы заполните окно списка, вызовите ReSort, передав исходный элемент управления ListBox или ComboBox в качестве единственного параметра, например Call ReSort(List1).
Sub ReSort(L As Control) ' Dim P%, PP%, C%, Pre$, S$, V&, NewPos%, CheckIt% Dim TempL$, TempItemData&, S1$ ' For P = 0 To L.ListCount - 1 S = L.List(P) For C = 1 To Len(S) V = Val(Mid$(S, C)) If V > 0 Then Exit For Next If V > 0 Then If C > 1 Then Pre = Left$(S, C - 1) NewPos = -1 For PP = P + 1 To L.ListCount - 1 CheckIt = False S1 = L.List(PP) If Pre <> "" Then If InStr(S1, Pre) = 1 Then _ CheckIt = True Else If Val(S1) > 0 Then CheckIt = True End If If CheckIt Then If Val(Mid$(S1, C)) < V Then _ NewPos = PP Else Exit For End If Next If NewPos > -1 Then TempL = L.List(P) TempItemData = L.ItemData(P) L.RemoveItem (P) L.AddItem TempL, NewPos L.ItemData(L.NewIndex) = TempItemData P = P - 1 End If End If Next End Sub
Если вы используете какие-либо объекты доступа к данным (DAO, RDO или ADO) в своей программе, следует перед выходом из нее закрыть все открытые наборы записей, базы данных и рабочие области. Конечно, когда вы выходите из программы, все указатели к этим объектам автоматически уничтожаются. Но если же вы не закроете в явном виде все открытые элементы, то связи с базой данных освободятся не сразу, а память, используемая этими объектами, может так никогда и не быть перераспределена операционной системой.
Ниже дается короткая подпрограмма, которую вы можете добавить к своему событию Form_Unload (или любому другому модулю, завершающему выполнение программы) и которая закрывает все открытие рабочие области, базы данных и наборы записей для объекта доступа к данным (DAO) и освобождает память, резервируемую этими объектами. Приведенный здесь код будет работать всегда при выходе из формы независимо от того, сколько связей у вас будет открыто - одна, сто или даже ни одной.
Private Sub Form_Unload(Cancel As Integer) ' ' Закрывает все объекты доступа к данным и ' освобождает всю память ' On Error Resume Next Dim ws As Workspace Dim db As Database Dim rs As Recordset ' For Each ws In Workspaces For Each db In ws.Databases For Each rs In db.Recordsets rs.Close Set rs = Nothing Next db.Close Set db = Nothing Next ws.Close Set ws = Nothing Next End Sub
В приложениях довольно часто приходится делать проверку наличия определенных файлов. Здесь приводится простая подпрограмма, которая предназначена для выполнения этого:
Public Function VerifyFile(FileName$) As Boolean ' Проверка - существует ли указанный файл On Error Resume Next ' Файл открывается как входной, последовательный Open FileName$ For Input As #1 If Err Then ' Ошибка при открытии - нет файла VerifyFile = False Else VerifyFile = True: Close #1 End If End Function
Для проверки ее работоспособности разместите на форме текстовое поле и командную кнопку, для которой напишите такой код в событии Click:
Private Sub Command1_Click() Dim FileName$, a$ FileName$ = Text1.Text If VerifyFile(FileName$) Then _ a$ = "" Else a$ = " не" MsgBox ("Файл " & FileName$ & a$ & " найден") End Sub
Щелкнув кнопку, вы получите сообщение о наличии файла, имя которого было набрано в текстовом поле.
Если вашей программе часто требуются случайные числа, вам, вероятно, надоело каждый раз вставлять команду Randomize, инициализирующую датчик случайных чисел, а после нее - уравнение. Приведенная здесь простая подпрограмма будет выполнять обе эти операции. Вначале поместите следующую функцию в один из модулей своего проекта:
Public Function GenRndNumber(Lower%, Upper%) Randomize GenRndNumber = Int((Upper% - Lower% + 1) * Rnd + Lower%) End Function
Чтобы получить случайное число в диапазоне от -99 до 99, просто введите:
RandomNumber = GenRndNumber(-99, 99)
Вы можете также получить случайную букву в диапазоне от A до M следующим образом:
RandomLetter = Chr$(GenRndNumber(Asc("A"), Asc("M")))
Кроме того, можно удалить середину диапазона, поместив вызов к функции GenRndNumber внутри цикла Do...Loop. Так вы получите случайное число в диапазоне от 50 до 99 или от -50 до -99:
' Инициализирует число, которое будет сгенерировано ' внутри области, которую вы хотите исключить RandomNumber = 0 Do Until Abs(RandomNumber) > 49 RandomNumber = GenRndNumber (-99, 99) Loop
Однако использовать такую универсальную процедуру следует только тогда, когда время ее выполнения не очень критично для программы. В случае же, когда вычисление случайных чисел нужно выполнять в очень больших объемах, этот код лучше вставить внутрь соответствующей программы.