Visual2000 · Архив статей А.Колесова & О.Павловой
Андрей Колесов, Ольга Павлова
© 199x, Андрей Колесов, Ольга ПавловаПримечание. В скобках указаны номера версий VB, для которых годятся данные советы.
Если вы хотите обратиться из своей программы к какому-нибудь приложению, например редактору 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
В некоторых случаях бывает нужным вывести на экран полное имя файла, но при этом возникает проблема ограниченного пространства в заголовке формы, текстовом окне или метке. В этом случае часто применяется такой способ — если имя файла полностью выходит за некоторый предел, то выводится еще сокращенное представление, в котором внутренняя часть пути заменяется на многоточие. Например, вместо "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
Столбцы в элементе управления 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.
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) ' пять листьев на третьей ветке
Здесь можно добавить другие поля, чтобы другие части дерева, помимо листьев, хранили данные.
Прежде всего обратите внимание, что в 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
Вот еще один оригинальный способ проверки високосного года:
Function IsLeap (sYear As String) As Interger IsLeap = False If IsDate ("02/29/" & sYear) Then IsLeap = True End Function
Иногда возникает необходимость перемещения элементов управления на форме при изменении ее размеров. Для этого можно воспользоваться свойством Align-объекта. Например, попробуйте следующее:
Private Sub Form_Resize() Picture1.Align = vbAlignLeft Picture1.Align = vbAlignTop End Sub
Каждое событие, которое изменяет размеры формы, автоматически перемещает рисунок в верхний левый угол. Перед выполнением процедуры убедитесь, что элемент управления, размеры которого вы хотите изменить, имеет свойство выравнивания (Align).
При работе в среде 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.
Одним из явных недостатков всех версий 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.
Как было сказано в совете 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) 'определение текущей позиции