Visual2000 · Архив статей А.Колесова & О.Павловой

Советы тем, кто программирует на Visual Basic

Андрей Колесов, Ольга Павлова

© 1998, Андрей Колесов, Ольга Павлова
Авторский вариант. Вторая часть статьи, опубликованой c незначительной литературной правкой в журнале "КомпьютерПресс" N 01/99, компакт-диск.


Совет 164. Как прочитать имена стандартных каталогов

В составе Win32 API есть функции, с помощью которых можно узнать имена каталогов, необходимых часто для работы приложения: текущего (где находится само приложение), временного (для хранения временных файлов), операционной системы (Windows) и системного (System). (Не совсем понятно, зачем нужно иметь отдельную функцию для определения системного каталога - он всегда имеет имя \System внутри основного каталога ОС.)

Вариант подпрограммы StandardDirNameAPI$, которая выполняет эти операции, приведен в листинге 2 (модуль SDirName.BAS). По поводу этого программного кода можно сделать еще несколько замечаний. Во-первых, видно, что все четыре API-функции совершенно однотипны, но почему-то в двух из них параметры (имя строкового буфера и его длина) записаны в одном порядке, а в других - в обратном. Во-вторых, при обращении к GetTempPath имя каталога возвращается с символом "\" в конце, в остальных функциях - без него. Именно поэтому в процедуре StandardDirNameAPI$ было бы полезно использовать код, который приводил бы имя каталога в некоторый стандартный вид.

В начало статьи

Листинг 2

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)! Общайтесь между собой и на рабочие темы!"

В начало статьи

Совет 165. Определитесь - что такое текущий и временный каталоги?

Это совет вытекает из предыдущего и является его продолжением. Для тестирования процедуры 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.

В начало статьи

Совет 166. Включение/выключение всех элементов управления в массиве

Как известно, 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))

Запустите на выполнение ваше приложение и по очереди щелкните кнопки, чтобы включить/отключить присоединенные группы элементов управления.

В начало статьи

Совет 167. Сортировка пронумерованных элементов в окне списка

Предположим, что у вас есть пронумерованные элементы (в нашем случае - файлы):

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).

В начало статьи

Листинг 3

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

В начало статьи

Совет 168. Убедитесь, что вы закрыли все объекты доступа к данным при выходе из программы

Если вы используете какие-либо объекты доступа к данным (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

В начало статьи

Совет 169. Проверка наличия файла из любого места программы

В приложениях довольно часто приходится делать проверку наличия определенных файлов. Здесь приводится простая подпрограмма, которая предназначена для выполнения этого:

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

Щелкнув кнопку, вы получите сообщение о наличии файла, имя которого было набрано в текстовом поле.

В начало статьи

Совет 170. Простой способ генерации случайных чисел

Если вашей программе часто требуются случайные числа, вам, вероятно, надоело каждый раз вставлять команду 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

Однако использовать такую универсальную процедуру следует только тогда, когда время ее выполнения не очень критично для программы. В случае же, когда вычисление случайных чисел нужно выполнять в очень больших объемах, этот код лучше вставить внутрь соответствующей программы.

В начало статьи