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

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

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

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


Совет 191. Как определять високосный год

Те, кто думают, что високосный год — это тот, который делится без остатка на четыре, глубоко заблуждаются. Так определяется високосный год в Юлианском календаре (старый стиль). А вот в Григорианском (новый стиль) — для устранения несоответствия календарного и солнечного (астрономического) года 100-й год не считается високосным, но каждый 400-й — считается. Ошибка средней продолжительности года в этом алгоритме составляет всего 26 секунд.

Таким образом, алгоритм определения високосного года выглядит следующим образом:

Public Function IsLeapYear(iYear As Integer) 
  ' Проверка високосного года
  If (iYear Mod 4 = 0) And _
      ((iYear Mod 100 <> 0) Or (iYear Mod 400 = 0)) Then
    IsLeapYear = True ' високосный
  Else
    IsLeapYear = False ' не високосный
  End If
End Function

Но можно придумать алгоритм гораздо проще. Еще раз — что такое високосный год? Правильно, тот, который имеет дату 29 февраля. А значит, работает такой алгоритм:

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

Однако по нашему мнению, данный алгоритм определения високосного года можно применять только к Григорианскому календарю, который был введен в действие в 1582 году. А до этого момента следует использовать правило Юлианского календаря (подробнее об этом см. "Y2K: как вести календарь").

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

Совет 192. Форматирование числа при выводе

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

Function PadToString(myValue, Digits) As String
  Dim Digits, MyValue
  PadToString = String(Digits - Len(myValue), "0") & myValue
End Function

Сделав, например, такое обращение

NewStr$ = PadToString(1978, 8)

вы получите строковую переменную 00001978. Обратите внимание, что Digits и myValue — переменные типа Variant.

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

Совет 193. Как измерять временные интервалы

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

DateDiff (Interval, StartDate, EndDate[, FirstDay,[ FirstWeek]])

Строковая переменная Interval задает единицы измерения интервала — от секунд до года. Однако часто при тестировании скорости выполнения различных программных конструкций (BenchMark) секундной дискретности бывает недостаточно. В этом случае лучше воспользоваться еще одними внутренними системными часами Windows, которые измеряют время в "тиках" — миллисекундах с момента последнего старта или перезагрузки операционной системы. (При непрерывной работе компьютера обнуление счетчика происходит примерно каждые 49 суток.) Чтение значений "тиков" в Win32 производится с помощью API-функции GetTickCount& (в Win16 для этого использовалась функция GetCurrentTime).

Пример вычисления временных интервалов в программе может выглядеть таким образом:

Private Declare Function GetTickCount& Lib "kernel32" ()

Private Sub Form_Load() 
  Dim dStart As Date
  Dim lCount&, i&, strValue$, msec&
  '
  lCount = 1000000  ' счетчик циклов
  dStart = Now
  msec& = GetTickCount&
  For i = 1 To lCount
    strValue = "Петя" & "+" & "Вася"
  Next
  MsgBox "Интервал: сек = " & _
    DateDiff("s", dStart, Now) & _
    " мсек = " & GetTickCount& - msec&
End Sub

Однако следует иметь в виду, что вычисленный таким образом интервал не является "чистым" временем выполнения данного программного кода. В него, включается также время выполнения более приоритетных заданий операционной системы — работа других приложений, системных драйверов и пр. Для более точного определения "чистого" времени выполнения конкретного вычислительного процесса с точностью до 100 наносекунд в составе Win32 API системы Windows NT имеется функция GetProcessTime.

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

Совет 194. Как узнать значения кодовых таблиц

Как известно, Windows использует в своей работе две кодовые таблицы, которые мы обычно называем DOS и Windows, но формально они именуются OEM и ANSI. Довольно многие функции обработки строковых переменных зависят от значения кодовых таблиц, установленных при инсталляции системы, которые можно определить с помощью функций API:

Private Declare Function GetACP Lib "kernel32" () As Long
Private Declare Function GetOEMCP Lib "kernel32" () As Long

Private Sub Form_Load() 
  MsgBox "ACP = " & GetACP & vbCrLf & "OEMCP = " & GetOEMCP
End Sub

В принципе значения кодовых таблиц можно менять уже после инсталляции Windows. Мы не советуем злоупотреблять этим, но такая коррекция может быть полезной при тестировании и анализе работы программ. Параметры ACP и OEMCP хранятся в разделе HKEY_Local_Machine/System/CurrentControlSet/Control/Nls/Codepage файла Реестра и редактируются с помощью утилиты REGEDIT.EXE. Для их активизации нужно перезагрузить Windows.

Номера таблиц, которые могут понадобиться:

OEMCP = 866 (Russian), 437 (US — default), 850 (International), 855 (Cyrilic)

ACP = 1250 (Eastern European), 1251 (Cyrilic & Russian), 1252 (US & Western European), 1200 (Unicode)

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

Совет 195. Как запустить Web-браузер из VB-приложения

Здесь приводится довольно простая функция, позволяющая запустить используемый по умолчанию Web-браузер из своего VB-приложения.

Введите следующий код в раздел General для модуля:

Declare Function ShellExecute Lib "shell32.dll" _ 
  Alias "ShellExecuteA" _
  (ByVal hwnd As Long, ByVal lpOperation As String, _
  ByVal lpFile As String, ByVal lpParameters As String, _
  ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long

Public Const SW_SHOWNORMAL As Long = 1 
Public Const SW_SHOWMAXIMIZED As Long = 3 
Public Const SW_SHOWDEFAULT As Long = 10 

Затем поместите в форму код, запускающий используемый по умолчанию Web- браузер:

Public Sub RunBrowser(strURL As String, iWindowStyle As Integer) 
  Dim lSuccess As Long

  '— Shell to default browser
  lSuccess = ShellExecute(Me.hwnd, "Open", _
          strURL, 0&, 0&, iWindowStyle)
End Sub 

Для запуска какого-либо Web-узла, к примеру www.basic.visual2000.ru, используйте такой вызов функции:

Call RunBrowser ("www.basic.visual2000.ru", SW_SHOWNORMAL)

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

Совет 196. Чтобы избежать появления ошибок, преобразовывайте значения NULL в пустые строковые переменные

При получении значений NULL из объекта Recordset могут возникать ошибки. Одним из способов избежать такой ситуации является проверка значения поля, и в том случае, если оно равно NULL, преобразование его в пустую строковую переменную или в ноль. Например,

If isnull(rs("Field")) then tmp="" else tmp=rs("Field")
form.textfield=tmp

Но еще проще использовать функцию форматирования, которая бы автоматически преобразовывала значение NULL в пустую строку. Такая функция выглядит примерно следующим образом:

form.textfield=format(rs("Field"))

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

Совет 197. Создание Word-документов при помощи VB-кода

Вы никогда не хотели создавать профессионального вида документы, наподобие тех, что пишутся в Microsoft Word, при помощи программного кода на VB? Следуйте приведенным ниже инструкциям, и у вас появится такая возможность.

Шаг 1. Добавьте к проекту ссылку к Microsoft Word 8.0 Object Library (команда Project|References).

Шаг 2. Введите следующий код для создания экземпляра Word и напишите какой-либо текст в новом документе:

Dim objWord As New Word.Application

'— Выводит Microsoft Word
objWord.Visible = True

'— Добавляет новый документ
objWord.Documents.Add

'— Вводит текст в документ
objWord.Selection.TypeText "Visual Basic!"

'— Выделяет весь текст
objWord.Selection.WholeStory

'— Изменяет размер шрифта
objWord.Selection.Font.Size = 50

Set objWord = Nothing

Шаг 3. Внимательно изучите Object Browser, чтобы воспользоваться другими свойствами и методами, предоставляемыми объектом Word.

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

Совет 198. Разбор полей строковой переменной при помощи функции SPLIT

Процедуры для проведения синтаксического разбора являются наиболее часто обновляемыми среди всех функций, манипулирующими строковыми переменными. VB6 не стал исключением, и в его состав вошла новая функция SPLIT. Она очень проста в использовании — всего одна строка кода и вы можете анализировать любую строку при помощи конкретного разделителя. Эта строка будет выглядеть следующим образом:

Dim strAnimals As String
Dim iCounter As Integer
Dim arrAnimals() As String

strAnimals = "Cats,Dogs,Horses,Birds"

'— Синтаксический разбор строки
arrAnimals = Split(strAnimals, ",")

'— Просмотр массива в цикле
For iCounter = LBound(arrAnimals) To UBound(arrAnimals)
  MsgBox arrAnimals(iCounter)
Next

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

Совет 199. Создайте свою собственную функцию Format

Команда Format, входящая в состав VB5, работает практически аналогично команде Print. Отличие состоит в том, что Format укорачивает выводимую строковую переменную, если количество символов форматирования превышает ее длину Для решения этой проблемы создадим свою функцию с названием FormatNum.
Public Function FormatNum(MyNumber As Double, FormatStr As String) 

  ' Эта функция возвращает число, отформатированное как строковая
  'переменная, содержащая требуемое минимальное количество символов
  '
  ' MyNumber — Используйте CDbl(MyNumber) при вызове функции, чтобы
  ' избежать появления ошибки о несовпадении типов
  '
  FormatNum = Format(MyNumber, FormatStr)
  If Len(FormatNum) < Len(FormatStr) Then
    FormatNum = Space(Len(FormatStr) - Len(FormatNum)) & FormatNum
  End If
End Function 
Использование новой функции Format проиллюстрируем на таком примере:
Private Sub Form_Load()
Dim MyVar, FormatStr$, f1$, f2$
MyVar = 12.1
FormatStr$ = "####.##"
f1$ = Format$(MyVar, FormatStr$)
f2$ = FormatNum(MyVar, FormatStr$)

MsgBox f1$ & "  " & Len(f1$) & vbCrLf & f2$ & "  " & Len(f2$)
End Sub 

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

Совет 200. Как осуществить выход из Windows с помощью VB

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

Declare Function ExitWindowsEx& Lib _
  "user32" (ByVal uFlags&, ByVal wReserved&)

' константы, необходимые для выхода из Windows
Global Const EWX_FORCE = 4     ' закрытие неактивных приложений
Global Const EWX_LOGOFF = 0    ' выход из системы
Global Const EWX_REBOOT = 2    ' перезагрузка
Global Const EWX_SHUTDOWN = 1  ' закрытие системы

Для выключения Windows используйте такой вызов функции:

' выключает компьютер
lresult = ExitWindowsEx(EWX_SHUTDOWN, 0&)

Для выполнения других операций замените первый параметр при вызове функции ExitWindowsEx на соответствующую константу.

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

Совет 201. Для ускорения процесса загрузки изменяйте базовые адреса компонентов типа in-process

При загрузке собственного компонента типа in-process в ходе выполнения VB-приложения, этот компонент размещается, начиная с некоторого базового адреса памяти.

Как можно поменять этот адрес для своего компонента? Для этого откройте диалоговое окно Project Properties и выберите вкладку Compile. Адрес вводится в поле DLL Base Address в виде десятичного или шестнадцатиричного целого числа без знака. По умолчанию используется значение &H11000000 (285,212,672). Если вы забудете изменить его, ваш компонент вступит в противоречие с любым другим компонентом типа in-process, скомпилированным с учетом используемого по умолчанию адреса. Поэтому рекомендуем вам задавать какой-либо отличный от него адрес.

Выбирайте базовый адрес в диапазоне между 16 Мб (16,777,216 или &H1000000) и 2 Гб (2,147,483,648 или &H80000000). При этом он должен быть кратным 64 Кб. Область памяти, используемая вашим компонентом, начинается с исходного базового адреса и имеет размер скомпилированного файла, округленного до следующего числа, кратного 64 Кб.

Ваша программа не может превышать 2 Гб, поэтому максимальный базовый адрес фактически равен 2 Гб минус область памяти, занимаемая созданным компонентом. Исполняемые файлы обычно будут загружаться по логическому адресу в 4 Мб. Область памяти меньше 4 Мб резервируется для Windows 95, а области свыше 2 Гб — для Windows 95 и Windows NT.

Чтобы убедиться, что все ваши компоненты имеют различные базовые адреса, необходимо вести их учет. Для этого полезно разработать собственный инструмент, который создавал бы новые уникальные базовые адреса. Таким образом ваши компоненты не вступали бы в противоречие друг с другом.

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

Совет 202. Зачем нужен параметр Alias в операторе Declare.

Издавна (еще в версиях для DOS) в операторе Declare использовался ключ Alias для переопределения имени вызываемой процедуры. Конструкция

Declare Function vbname Lib libname [Alias aliasname]

означает, что к процедуре, записанной в библиотеке libname под именем aliasname, в VB-программе обращаются под именем vbname. Обычно это применяется в двух случаях:

  1. Когда имя aliasname просто недоступно в VB. В частности, при обращении к функциям, которые имеют (по принятым в C правилам) в начале названия символ подчеркивания, например _lOpen. Или когда внешняя функция использует имя, которое совпадает с зарезервированным ключевым словом VB (скажем SetFocus).
  2. Когда подразумевается, что VB-программа может работать с двумя версиями одной и той же (с точки зрения функциональности) процедуры. В этом случае гораздо проще исправить один оператор Declare:
    Declare Function vbname Lib libname Alias aliasname1
    

    на

    Declare Function vbname Lib libname Alias aliasname2
    

    чем менять во всей программе имя процедуры vbname.

Второй вариант особенно часто используется для обеспечения преемственности кода при переходе от Win16 к Win32 — имена многих API-функций при этом поменялись. В этой связи нужно специально выделить часто встречающийся случай, когда к привычному имени, типа SomeFunction (Win16), прибавился суффикс A и получилось SomeFunctionA (Win32). Почему так случилось, расскажет следующий совет.

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

Совет 203. Помните: VB использует кодировку ANSI при обращении к API-функциям.

Дело в том, что Windows 3.x (Win16) использовала только один формат хранения символьных данных (строковых переменных) — ANSI (набор однобайтовых символов). Система Win32 представлена двумя вариантами — Windows 9x и Windows NT, которые применяют разные внутренние форматы: соответственно ASNI и Unicode (набор целочисленных, двубайтовых символов). В результате, одна API-функция в Win32, например GetWindowText, фактически реализована в двух вариантах, с добавлением к имени суффикса A (символы передаются в кодировке ANSI) и W (Wide = Unicode): GetWindowTextA и GetWindowTextW. При этом следует иметь в виду, что физически в Windows 9x и Windows NT используется разная реализация таких функций.

Windows 9x допускает использование только ANSI-версий процедур и совсем не поддерживает Unicode. Но при этом она включает оба варианта функций, второй из которых на самом деле не работает. Представляется вполне логичным, если бы обращение, скажем к GetWindowTextW, вызывало бы сообщение об отсутствии такой функции. Однако в реальности данная функция не выполняет никаких действий и возвращает в вызывающую программу нулевое значение, которое можно интерпретировать как ошибку. Однако если программный код:

ItemHeight& = SendMessageA(.hWnd, LB_GETITEMHEIGHT, 0, 0) 
NewIndex& = ListHeight& \ ItemHeight&

работает нормально, то замена SendMessageA на SendMessageW вызовет программную ошибку "деление на ноль".

В Windows NT оба варианта функций являются рабочими, что позволяет использовать кодировку и ANSI, и Unicode. В первом случае нужно обращаться к A-процедуре, которая преобразовывает символьный код из ANSI в Unicode и передает управление на собственно обработку в W-процедуру. Во втором случае нужно обращаться напрямую в W-процедуру.

32-разрядные версии Visual Basic используют Unicode для внутреннего хранения строковых переменных. Однако при обращении к API- или DLL-функциям, описанным с помощью оператора Declare, производится автоматическое преобразование символьных данных в ANSI (а потом — обратно). Таким образом, при работе с VB нужно обращаться к ANSI-варианту API-функций как в Windows 9x, так и Windows NT.

В принципе имя, типа SendMessageA, можно использовать непосредственно в программе. Но общепринятым считается применение имени функции без суффикса, для чего применяется параметр Alias:

Declare Function SendMessage ... Alias "SendMessageA"

Во-первых, это обеспечивает определенную совместимость имен с API Win16, а во-вторых, подчеркивает, что используется один из вариантов функции SendMessage.

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