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

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

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

© 199x, Андрей Колесов, Ольга Павлова
Авторский вариант. Статья была опубликована c незначительной литературной правкой в журнале "КомпьютерПресс" N 12/97, с. 127-133.


Примечание. В скобках указаны номера версий VB, для которых годятся данные советы.

Совет 109. Используйте короткие имена файлов (VB4 32, VB5)

Если вы хотите обратиться из своей программы к какому-нибудь приложению, например редактору Paint, открыв его с указанным вами файлом, то будет лучше заменить его длинное имя на короткое. Такая операция имеет смысл в некоторых случаях: если, предположим, в имени файла есть пробелы, это может привести к некорректной работе редактора Paint. Для преобразования длинного имени в короткое можно использовать следующую процедуру, которая, в свою очередь, обращается к Win32 API:

Declare Function GetShortPathName Lib "kernel32" Alias _
     "GetShortPathNameA"(ByVal lpszLongPath As String, _
     ByVal lpszShortPath As String, ByVal cchBuffer As Long) As Long

Function ShortName(LongPath As String) As String
     Dim ShortPath As String, ret As String
     Const MAX_PATH = 260
     ShortPath = Space$(MAX_PATH)
     ret = GetShortPathName(LongPath, ShortPath, MAX_PATH)
     If ret Then ShortName = Left$(ShortPath, ret&)
End Function

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

Совет 110. Как обрезать полное имя файла до нужной длины (любые версии Basic)

В некоторых случаях бывает нужным вывести на экран полное имя файла, но при этом возникает проблема ограниченного пространства в заголовке формы, текстовом окне или метке. В этом случае часто применяется такой способ — если имя файла полностью выходит за некоторый предел, то выводится еще сокращенное представление, в котором внутренняя часть пути заменяется на многоточие. Например, вместо "C:\Programs\VisualB\Examples\Test.bas" будет выдаваться "C:\...\Examples\Test.bas".

Для выполнения такого преобразования имени файла можно использовать приведенную ниже процедуру. Предположим, что длина метки составляет 50 символов, тогда вы просто пишете:

Function LongDirFix$(Incoming%, Max%)
  Dim i%, LblLen%, StringLen%, TempString$
  TempString$ = Incoming$
  If Len(TempString$) <= Max% Then
    LongDirFix$ = TempString$: Exit Function
  End If
  LblLen% = Max% - 6
  For i% = Len(TempString$) - LblLen% To Len(TempString$)
     If Mid$(TempString$, i%, 1) = "\" Then Exit For
  Next
  LongDirFix$ = Left$(TempString$, 3) + "..." + _
     Right$(TempString$, Len(TempString$) - (i% - 1))
End Function

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

Совет 111. Используйте смысловые имена при работе с элементом управления DBGrid (VB4 16/32)

Столбцы в элементе управления DBGrid привязаны к полям базы данных, однако сам номер индекса столбца ничего не говорит о том, какое поле он представляет. Поэтому ссылка, например на DBGrid1.Columns(0).Value, сообщает не очень многое при работе с исходным текстом программы. Гораздо лучше, когда переменные имеют некоторые осмысленные имена, связанные с конкретными столбцами. Для этого в секции описаний для формы можно зарезервировать, например, такие переменные:

Dim ColOrder_ID As Column
Dim ColArticle_ID As Column
Dim ColAmount As Column

А в событийной процедуре Form_Load сделать следующие присвоения:

With DBGrid1
     Set ColOrder_ID = .Columns(0)
     Set ColArticle_ID = .Columns(1)
     Set ColAmount = .Columns(2)
End With

Теперь нет больше необходимости ссылаться на DBGrid1.Columns(0).Value — вместо этого вы можете использовать переменную ColOrder_ID.Value. Если меняется какое-либо значение свойства в DBGrid1.Columns(0), тогда аналогичные изменения произойдут в ColOrder_ID, и наоборот. Впоследствии, когда вы станете устанавливать связь между полями базы данных и сеткой, будет необходимо изменить только номера индексов столбцов в коде события Form_Load.

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

Совет 112. Структуры данных могут быть более сложными (VB4 16/32, VB5)

VB4 позволяет создавать более сложные структуры данных по сравнению с предыдущими версиями благодаря новой возможности, когда внутри типов могут содержаться динамические массивы. Например, следующая структура с тремя уровнями может быть создана с помощью заданных пользователем типов и динамических массивов:

Private Type LeafType
    ' пусть листья будут целыми переменными
    Leaf As Integer
End Type
Private Type BranchType
    ' ветвь представляет собой массив листьев
    Branch() As LeafType
End Type
' дерево - это массив ветвей
Private Tree() As BranchType

Следующий код задает дерево с тремя ветвями. На первой из них — три листа, на второй — два, а на третьей — пять:

ReDim Tree(1 To 3)              ' три ветви
ReDim Tree(1).Branch(1 To 3)    ' три листа на первой ветке
ReDim Tree(2).Branch(1 To 2)    ' два листа на второй ветке
ReDim Tree(3).Branch(1 To 5)    ' пять листьев на третьей ветке

Здесь можно добавить другие поля, чтобы другие части дерева, помимо листьев, хранили данные.

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

Совет 113. Быстрая проверка выходных дней (все версии Basic)

Прежде всего обратите внимание, что в Windows используется нумерация дней недели, отличающая от принятой в России: воскресенье — 1, понедельник — 2,.. Выполните следующее, чтобы проверить, не выпадает ли какая-либо дата на уик-энд (суббота, воскресенье):

nDay = weekday(sDate)  ' номер дня недели
If (nDay = 1) or (nDay = 7) Then
   ' это уик-энд
End If

Однако с помощью оператора MOD этот тест можно выполнить почти в два раза быстрее:

If (Weekday (sDate) MOD 6 = 1) Then
   ' это уик-энд
End If

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

Совет 114. Проверка високосного года (все версии Basic)

Вот еще один оригинальный способ проверки високосного года:

Function IsLeap (sYear As String) As Interger 
  IsLeap = False 
  If IsDate ("02/29/" & sYear) Then IsLeap = True 
End Function

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

Совет 115. Динамическое управление положением элементов управления (VB4 16/32, VB 5)

Иногда возникает необходимость перемещения элементов управления на форме при изменении ее размеров. Для этого можно воспользоваться свойством Align-объекта. Например, попробуйте следующее:

Private Sub Form_Resize()
   Picture1.Align = vbAlignLeft
   Picture1.Align = vbAlignTop 
End Sub

Каждое событие, которое изменяет размеры формы, автоматически перемещает рисунок в верхний левый угол. Перед выполнением процедуры убедитесь, что элемент управления, размеры которого вы хотите изменить, имеет свойство выравнивания (Align).

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

Совет 116. Используйте функции API для работы с файлами (все версии VB)

При работе в среде DOS мощным средством расширения встроенных языковых возможностей любой системы программирования, в том числе и Basic, является применение системных функций DOS/BIOS. С их помощью можно либо увеличить скорость выполнения операций по сравнению с использованием операторов языка, либо расширить функции операторов, либо получить в свое распоряжение совершенно новые функции.

С точки зрения программиста принципиальным отличием функций DOS/BIOS от других библиотек процедур было то, что этот набор был всегда под рукой (в составе DOS) и не требовал специальных операций по его подключению к программе. Кроме того, для обращения к нему использовалась одна входная точка CALL INTERRUPT, а дальнейшее определение операции и передача параметров выполнялись довольно специфичным заполнением целочисленной структуры данных. (На эту тему у авторов было довольно много публикаций, электронный вариант которых хранится у них.)

При работе в среде Windows таким средством расширения языка является набор WinAPI, многие процедуры которого являются непосредственными аналогами функций DOS/BIOS. По сути дела, последние так и остались в операционной системе, но обращение к ним производится не напрямую через системное прерывание, а через WinAPI.

Одна из групп системных функций DOS под названием "Handle-ориентированный ввод-вывод" была связана с работой с файловой системой. Фактически эти функции соответствовали операторам Basic при работе с двоичными файлами Binary, но имели целый ряд важных преимуществ. Например, операторы Get/Put могут за одну операцию передавать только одну простую переменную. Соответственно, для записи массива на диск нужно создавать некоторый цикл обращений к файлу:

Dim Array (1 To N%) As Single 
... 
For i% = 1 To N: Put #FileNumber,,Array(i): Next

При работе с функциями DOS на диск записывалась некоторая непрерывная последовательность байтов, для этого указывался начальный адрес этой области (сегмент и смещение) и ее длина. Таким образом, можно было, например, записать в файл за одно обращение к диску весь массив целиком (при работе с фрагментами многомерных массивов следует учитывать физическое хранение его элементов). Приведенный выше пример можно выполнить следующим образом с использованием функций DOS :

regsx.ax = &H4000            'операция записи
regsx.bx = FileHandle%       'описатель файла
regsx.ds = VARSEG(Array(1))  'адрес сегмента
regsx.dx = VARPTR(Array(1))  'смещение
regsx.cx = N% * 4            'число байтов
CALL interruptx(&H21, regsx, regsx)

Примечание. На самом деле определение числа байтов для переменной regsx.cx нужно выполнять по-другому. Об этом — см. совет 117.

В зависимости от размера массива скорость обмена данными могла возрастать в сотни раз. Следует также подчеркнуть, что с помощью функций DOS группы Handle можно было выполнять ряд операций, в принципе не реализуемых операторами Basic, например, переопределение стандартных устройств ввода-вывода. Поэтому в своих более ранних разработках на Basic/DOS мы часто использовали библиотеки подпрограмм, основанные на использовании этих функций DOS/Handle.

При работе с VB реализация таких расширенных Handle-функций возможна с помощью функций WinAPI: lclose, lcreate, llseek, lopen, lread, lwrite. Кроме того, еще в наборе API для Windows 3.1 появились две дополнительные функции — hwrite и hread, которые в отличие от процедур lread и lwrite (и соответствующих функций DOS) могут выполнять обмен массивами данных размером более 64 Кбайт. Все эти функции имеются как для WinAPI-16 (файл "Kernel"), так и для WinAPI-32 ("Kernel32"). Отличие этих двух наборов функций заключается в том, что в первом случае числовые параметры передаются в виде переменных Integer, а во втором — Long.

Приведенная ниже процедура WriteIntegerArrayToFile является примером записи двумерного целочисленного массива i_array в двоичный файл binary_file с помощью API-функции hwrite (вариант WinAPI-16). Данная процедура легко модифицируется для других размеров и типов массивов (за исключением массивов, определенных пользователем, и строк переменной длины). Тем не менее ее невозможно преобразовать в общую процедуру для любого числа индексов массива из-за способа вызова API-функций. Следует также обратить внимание на следующие моменты: 1. В функциях DOS в качестве параметра передавался адрес области памяти в виде двух целочисленных переменных. В функциях API передается некая переменная, которая на самом деле представляет именно ссылку на ее физический адрес. 2. Вывод данных выполняется в текущую позицию файла. Управление текущей позицией файла производится функцией llseek. 3. При работе с файлами на Basic используется понятие "логический номер файла", а при использовании функций DOS/API — "номер описателя файла" (handle). Следует обратить внимание, что это — разные величины. Для определения номера описателя файла в DOS по его логическому номеру используется оператор FileAttr. Таким образом, можно совмещать одновременную работу с открытым файлом средствами как Basic, так и DOS/API.

' Запись массивов в двоичный файл:
Declare Function hwrite Lib "Kernel" Alias "_hwrite" _
     (ByVal hFile As Integer, hpbBuffer As Any, _
     ByVal cbBuffer As Long) As Long
' В качестве значения функции возвращается
' число переданных байтов
'
Sub WriteIntegerArrayToFile (ByVal binary_file As String, _
     integer_array() As Integer)
Const INTEGER_BYTE_SIZE& = 2  ' число байтов в переменной
Dim binary_file_number As Integer, dos_file_handle As Integer
Dim bytes_written As Long, bytes_to_write As Long
Dim start1 As Integer, start2 As Integer
     ' индексы первого (физически) элемента массива
     start1 = (LBound(i_array, 1)
     start2 = (LBound(i_array, 2)
     ' Получение размера массива в байтах (произведение числа строк на
     ' число столбцов и на число байтов в одном элементе массива)
     bytes_to_write = INTEGER_BYTE_SIZE& * _
     (UBound(i_array, 1) 
- start1 + 1) * _
     (UBound(i_array, 2) - start2 + 1)
     ' открытие двоичного файла
     binary_file_number = FreeFile   ' логический номер
     Open binary_file For Output As binary_file_number
     ' получаем номер описателя файла в DOS
     dos_file_handle = FileAttr(binary_file_number, 2)
     ' вызов API-функции
     If dos_file_handle <> 0 Then
       bytes_written = hwrite(dos_file_handle, _
         integer_array (start1, start2), bytes_to_write)
     End If
     ' закрыть двоичный файл
     Close binary_file_number
End Sub

На самом деле кроме упомянутых выше процедур в составе WinAPI имеется много других функций, которые не имеют аналогов среди функций DOS. Например, это целый набор операций с INI- файлами, управление версиями файлов (Version Stamping), сжатыми файлами и пр. Но есть и старые функции DOS, которые не реализованы в API.

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

Совет 117. Работа с беззнаковыми целыми числами (все версии Basic)

Одним из явных недостатков всех версий Microsoft Basic является отсутствие в них беззнаковых целочисленных переменных с диапазоном изменения 0-65 535/&hFFFF (такой тип переменных был во всех версиях Turbo-BASIC, PowerBASIC еще десятилетней давности). Проблема заключается в том, что именно такой тип данных очень широко применяется на системном уровне. При работе в среде DOS это четко проявлялось при использовании системных функций DOS/BIOS, в частности, при представлении адреса в виде двух целочисленных регистров, и наоборот. Аналогичная проблема имеется и при работе в Windows. Например, при использовании функций lwrite/lread в WinAPI-16, как и при работе с аналогичной функцией DOS (см. предыдущий совет), число записываемых данных передается в виде целочисленной переменной, которая на самом деле может изменяться от 0 до 65 535.

Для подобных преобразований могут пригодиться следующие процедуры:

SUB ConvertIntegersToLong (LongValue&, IntegerHigh%, IntegerLow%)
'   Представление двух беззнаковых переменных типа INTEGER
'   в виде переменной типа LONG
    LongValue& = IntegerHigh% * &H10000 + (IntegerLow% AND &H7FFF)
    IF IntegerLow% < 0 THEN LongValue& = LongValue& OR 32769 ' &h8000
END SUB

SUB ConvertLongToIntegers (LongValue&, IntegerHigh%, IntegerLow%)
'   Представление переменной типа LONG в виде двух
'   беззнаковых переменных типа INTEGER
'
    IntegetHigh% = LongValue& \ &H10000   'старшие 16 разрядов
    IntegerLow% = LongValue& AND &H7FFF   'младшие 16 разрядов
    IF (LongValue& AND &H8000) <> 0 THEN
      IntegerLow% = IntegerLow% OR &H8000
    END IF
END SUB

В этом случае приведенное ранее вычисление длины передаваемого массива должно выглядеть следующим образом:

' ВМЕСТО: regsx.cx = N% * 4            'число байтов
Call  ConvertLongToIntegers (N% * 4&, IntegerHigh%, regsx.cx)
If IntegerHigh% <> 0 Then
  ' превышение допустимой длины
End If

При работе в Win32 такие проблемы возникают гораздо реже, так как для передачи целочисленных данных там изначально используются переменные типа Long.

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

Совет 118. Как определить длину файла (все версии VB)

Как было сказано в совете 116, управление указателем текущей позиции файла производится с помощью функции API llseek, которая для 16-разрядной версии описывается следующим образом:

Declare Function llseek Lib "Kernel" Alias "_llseek" _ 
               (ByVal hFile%, ByVal lOffset&, ByVal iOrigin%) As Long

где Offset& — на сколько передвинуть указатель (Long), а iOrigin% — точка отсчета (Integer): 0 — начало файла, 1 — текущая позиция, 2 — конец файла. Результатом функции является новое значение текущей позиции, например:

NewSeek& = llseek (hFile%, 2, 0) 'определение длины файла 
NewSeek& = llseek (hFile%, 1, 0) 'определение текущей позиции

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