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

Советы тем, кто программирует на VB & VBA

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

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


Совет 261. Как обеспечить совместимость VBA и VB

В статье об интеграции VBA в VB-приложения ("Интеграция VBA в бизнес-приложения независимых разработчиков", КомпьютерПресс N 3/2000) мы отмечали следующую проблему с отладкой приложений.

Если мы запускали созданное приложение (с уже интегрированным в него VBA) непосредственно в среде Visual Basic, то, перейдя затем в VBA, обнаруживали, что глобальный объект приложения CApplication (он создавался мастером VB Integration) недоступен. Если же создать загрузочный EXE-модуль, то с объектом можно будет нормально работать.

Для иллюстрации этой ситуации продемонстрируем несложный пример. При этом имеется в виду, что вы работаете с VB и у вас на компьютере установлен VBA 6.0 SDK 6.1.

  1. В среде VB создайте самый простой проект — Standard EXE. Обратите внимание: у нас получилось вполне работоспособное приложение, которое просто загружает формы. Но нам для примера ничего больше и не нужно. Далее запустите VB Integration Wizard из меню Add-ins и пройдите все этапы его работы, просто нажимая кнопку Next (ничего не меняя в полях окон).

    Итак, за полминуты мы создали приложение, которое само по себе ничего не умеет делать, но зато имеет среду VBA, где можно разрабатывать и выполнять автономные VBA-проекты любой сложности.

  2. Запустим наше приложение Project1, перейдем в среду VBA (Alt-F11) и напишем в окне Immediate:

    Print Project1.CApplication.Name
    

    Нажав Enter (выполнив эту строку), мы увидим сообщение: "Method or data member not found".

  3. Теперь создадим EXE-модуль нашего проекта и повторим с ним пункт 2. Ошибки не будет, и мы увидим в окне Immediate имя нашего проекта — Project1.

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

Но, оказывается, есть очень простое решение этой проблемы. Нужно просто переименовать объект CApplication в Application, внеся также соответствующие изменения во все упоминания этого объекта, например используя команду Replace. (Еще проще сделать это в момент работы мастера VB Integration, исправив предлагаемое имя глобального объекта приложения.)

Теперь объект Project1.Application будет доступен и в среде VB, и при работе с EXE-модулем. Почему так получается, что в среде VB доступен только объект с именем Application, — непонятно. Но тем не менее это так!

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

Совет 262. Как контролировать макросы в шаблонах

В своем обзоре по MS Office 2000 (КомпьютерПресс N 12/99) мы отмечали некоторые проблемы при использовании шаблонов в Word 2000. Так, при загрузке шаблонов в виде автономных документов или присоединенного файла из каталога Шаблоны пользователя не производилась проверка на наличие макрокода, хотя такой режим контроля был задан. Оказывается, проблема заключается в более детальных установках контроля за макрокодом.

Управление режимом контроля за наличием макрокода при загрузке документов производится в диалоговом окне Security[Безопасность] (команда меню Tools|Macro|Securiry [Сервис|Макрос|Безопасность]). На его вкладке Trusted Sources [Надежные источники] имеется флажок Trust all installed Add-ins and templates [Доверять всем установленным надстройкам и шаблонам]. Под "установленными" подразумеваются дополнения и шаблоны, помещенные в каталог "Шаблоны пользователя". (Конкретное имя этого каталога указывается в поле User Templates [Шаблоны пользователя] во вкладке File Locations [Расположение] диалогового окна Tools|Options [Сервис|Параметры].)

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

В Word 97 такого специального режима для загрузки шаблонов не было. Но в начальной версии программы иногда имела место ошибка — когда шаблоны загружались без проверки на макрокод. Этот дефект уже устранен — заплатку, которая решает данную проблему, можно скачать по адресу http://www.microsoft.com/rus/download/wd97sp.htm.

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

Совет 263. Использование даты в SQL-запросах

В своей статье "Особенности обработки дат в Visual Basic" (КомпьютерПресс N 07'99) мы обращали внимание читателей на необходимость учета национальных особенностей обработки дат. Вот еще один из примеров подобной ситуации.

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

SELECT * FROM SomeTable WHERE Time <= #02/29/2000#
Обратите внимание, что дата задается здесь как литерал, но самое главное — литерал может быть представлен только в формате американской даты. Поэтому вариант, часто предлагаемый американскими авторами:

Sql1$ = "SELECT * FROM SomeTable WHERE Time <= "
SQL$ = Sql1$ & "#" & Now & "#"

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

D$ = Format(Now, "MM/dd/yyyy HH:mm")
Mid$(D$, 3) = "/" ' меняем разделители
Mid$(D$, 6) = "/"
SQL$ = Sql1$ & "#" & D$ & "#"

Здесь нужно обязательно в явном виде установить разделители в дате, так как, хотя мы и указали "MM/dd/yyyy" в Windows с российскими региональными установками, в символьном представлении даты будут записаны точки.

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

Совет 264. Программный сброс хранителя экрана

Наш читатель Владимир Чикин прислал нам такой вопрос: каким образом выключить хранитель экрана программным способом из приложения, чтобы вновь был виден программный интерфейс? Это бывает нужно, если приложение работает без использования диалога с пользователем (например, программа отслеживает данные, поступающие из COM-порта, или просто выполняет очень длительные вычисления) и требуется выдать сообщения о каких-либо критических событиях в программе.

Здесь видятся два варианта решений:

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

    Для этого создайте форму, на которую поместите элемент управления Label, содержащий какой-либо текст, а также элемент управления Timer, для которого установите свойство Interval как 1000 (то есть 1 секунда). Для формы установите свойства WindowState = Maximized, Border Style = None и выберите черный цвет в качестве фона BackColor. Теперь введите следующий код для вашей формы:

    Private Sub Form_Click()
        ' хранитель экрана выгружается, если щелкнуть форму
        Unload Me
    End Sub
    
    Private Sub Timer1_Timer()
        ' мигание метки каждую секунду
        Label1.Visible = Not (Label1.Visible)
    End Sub
    

    Соответственно в программе, когда потребуется погасить экран, нужно загрузить форму, а в необходимый момент — выгрузить ее.

    Если требуется обеспечить такой режим, чтобы по щелчку мыши хранитель исчезал на некоторое время, а потом появлялся опять, то можно написать такой код формы:

    Private Sub Form_Click()
        ' убрать хранитель
        Me.WindowState = 1 'Minimized
        Timer1.Enabled = False
        Timer1.Interval = 60000 ' минута
        Timer1.Enabled = True
    End Sub
    Private Sub Form_KeyPress(KeyAscii As Integer)
        ' если нажата Esc - выгрузить форму
        If KeyAscii = 27 Then ' Esc
            Unload Me
        End If
    End Sub
    Private Sub Timer1_Timer()
        If Me.WindowState = 1 Then ' хранитель свернут
            Me.WindowState = 2 ' развернуть хранитель экрана
            Timer1.Interval = 1000 ' секунда
        Else ' управляем выводом метки
            Label1.Visible = Not (Label1.Visible)
        End If
    End Sub
    

  2. Но что же делать, если вам действительно нужно программно сбросить внешний "фирменный" хранитель экрана? В этом случае можно воспользоваться функцией keybd_event из состава Win API, которая имитирует манипуляции с клавиатурой. (Здесь также нужно упомянуть об аналогичной функции mouse_event для имитации мыши.)

    Пример процедуры SendMyKey, которая обеспечивает имитацию записи в буфер указанного ASCI-символа, приведен на листинге 264. В этом случае для сброса внешнего хранителя экрана можно использовать такую программную конструкцию:

    ' посылка символа в буфер клавиатуры
    Call SendMyKey(Chr$(9)) ' клавиша Tab
    ' почему-то нужно сделать вывод окна
    ' — тогда срабатывает посылка символа
    MsgBox "Hello!"
    

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

    keybd_event 9, 15, 0, 0
    keybd_event 9, 15, 2, 0
    

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

Листинг 264. Имитация нажатия клавиши клавиатуры

Option Explicit
Public Const KEYEVENTF_EXTENDEDKEY = &H1
Public Const KEYEVENTF_KEYUP = &H2
Declare Sub keybd_event Lib "user32" _
    (ByVal bVk As Byte, ByVal bScan As Byte, _
    ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
Declare Function VkKeyScan Lib "user32" Alias "VkKeyScanA"_
    (ByVal cChar As Byte) As Integer
Declare Function CharToOem Lib "user32" Alias "CharToOemA"_
    (ByVal lpszSrc As String, ByVal lpszDst As String) As Long
Declare Function OemKeyScan Lib "user32" _
    (ByVal wOemChar As Integer) As Long
Public Sub SendMyKey(ByVal c$)
'
' Посылка одиночного ASCI-символа для имитации
' нажатия клавиши клавиатуры
    Dim vk%, scan%, oemchar$
        ' Получаем значение виртуального кода
        ' клавиши для данного символа
    vk% = VkKeyScan(Asc(c$)) And &HFF
    oemchar$ = " " ' буфер на два символа
    ' получение OEM-символа
    CharToOem Left$(c$, 1), oemchar$
        ' получение scan-кода для этой клавиши
    scan% = OemKeyScan(Asc(oemchar$)) And &HFF
        ' Нажатие клавиши
    keybd_event vk%, scan%, 0, 0
        ' Отжатие клавиши
    keybd_event vk%, scan%, KEYEVENTF_KEYUP, 0
End Sub

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

Совет 265. Как подключить VB-процедуры обратного вызова к API-функции

Оператор AddressOf, появившийся в VB версии 5.0, позволяет передавать указатель для определяемой пользователем подпрограммы, функции или свойства в API-функцию, которая затем может передавать различные данные в процедуру обратного вызова (callback procedure). Для передачи такого указателя в API-функцию следует использовать

AddressOf procedurename

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

Private Sub Form_Load()
    gWH = Me.hwnd
    OldWndProc = SetWindowLong(gWH, GWL_WNDPROC, _
    AddressOf WindowProc)
End Sub

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

SetWindowLong gWH, GWL_WNDPROC, AddressOf OldWndProc

При использовании ключевого слова AddressOf необходимо, однако, соблюдать следующие правила. Названия определяемых пользователем подпрограмм, функций или свойств, которые будут использоваться в качестве обратного вызова, должны следовать непосредственно за оператором AddressOf. Проект, в котором имеются подпрограммы, функции и свойства обратного вызова, должен содержать соответствующие описания API-функций и процедуры. Нельзя применять AddressOf ни с какими другими функциями, кроме определяемых пользователем подпрограмм, функций или свойств. Например, вы не можете использовать другую API-функцию или функцию из библиотеки типов. Обратный вызов должен находиться в стандартном модуле. И, наконец, последнее — определяемая пользователем подпрограмма или функция должна иметь тип Any либо Long.

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

Совет 266. Как избежать ошибок сравнения битовой маскировки при использовании API-функций в VB

При использовании API-функций достаточно часто возникает необходимость определить, содержат ли получаемые результаты определенный признак (flag) или нет. Для этого данный признак маскируется и применяется оператор And. Например, предположим, что мы хотим узнать, в каком состоянии находится текущее окно — развернуто оно или свернуто. Вначале получим установки этого окна с помощью API-функции GetWindowLong(), которую опишем в стандартном модуле следующим образом:

Public Const WS_MAXIMIZE = &H1000000
Public Const GWL_STYLE = -16
Public Declare Function GetWindowLong Lib _
  "user32" Alias "GetWindowLongA" _
  (ByVal hwnd As Long, ByVal nIndex As Long) As Long

Затем поместим на форму командную кнопку и введем такой код в ее событие Click():

Dim lWinStyle As Long
lWinStyle = GetWindowLong(Me.hwnd, GWL_STYLE)
If lWinStyle And WS_MAXIMIZE Then
    MsgBox "Форма развернута"
Else
    MsgBox "Форма свернута"
End If

Теперь запустим наш пример на выполнение и щелкнем на кнопке. Приведенный выше код определяет, содержит ли значение стиля битовое значение признака. Если да, то логическое сравнение возвращает число, которое VB интерпретирует как True (Истина). В противном случае возвращается 0 или False (Ложь).

Ошибки же могут возникнуть, если мы попытаемся проверить исключение какого-либо признака. Так, предположим, мы хотим выполнить определенные действия, когда окно находится в свернутом состоянии. Попробуем просто написать:

If Not (lWinStyle And WS_MAXIMIZE) Then

(Конечно, в данном конкретном случае мы могли бы выполнить тестирование признака WS_MINIMIZE, но иногда API-функции не имеют признака, представляющего собой противоположную установку.) К сожалению, такой условный оператор всегда возвращает значение True. Это происходит потому, что, когда мы используем оператор Not с числом, VB возвращает значение этого числа с противоположным знаком минус 1. Например, выражение Not 15 возвращает -16, которое интерпретируется VB как True. В нашем примере с битовой маскировкой выражение Not (lWinStyle And WS_MAXIMIZE) для развернутого окна имеет ненулевое значение, которое опять оценивается как True.

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

If Not Cbool(lWinStyle And WS_MAXIMIZE) Then

или

If Not ((lWinStyle And WS_MAXIMIZE)<>0) Then

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

Совет 267. Используйте VB-объект RegExp для проверки синтаксиса адреса электронной почты

В современных приложениях часто бывает необходимо, чтобы пользователь вводил информацию о своей компании, включая адрес электронной почты. При этом нужно быть уверенным не только в том, что такой адрес содержит знак @ и точку, но и в том, что все остальные символы представлены буквами, числами или знаками подчеркивания. На первый взгляд такая задача может показаться не очень простой, если вы будете пользоваться только стандартными VB-функциями обработки строк. К счастью, в VB существует объект RegExp, который существенно упрощает работу.

Однако прежде чем приступить к использованию этого объекта в VB, следует загрузить библиотеку VBScript 5.0 DLL, которая находится по адресу: www.microsoft.com/msdownload/vbscript/scripting.asp

После установки библиотеки в диалоговом окне References в VB появится строка Microsoft VBScript Regular Expressions. Добавьте эту ссылку к проекту — и можно приступать к работе. Следующий код проверяет адрес электронной почты, который вводится в текстовом поле Text1:

Dim myReg As RegExp
Private Sub Form_Load()
    Set myReg = New RegExp
    myReg.IgnoreCase = True
    myReg.Pattern = "^[\w-\.]+@\w+\.\w+$"
End Sub
Private Sub Text1_Validate(Cancel As Boolean)
    Cancel = Not myReg.Test(Text1)
End Sub

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

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

Совет 268. Как осуществить прокрутку элемента управления ListView в VB до заданного элемента

Известно, что элемент управления ListView позволяет простым способом связать элементы списка со значками, рисунками и строками отчетов. С его помощью легко выбрать определенный элемент списка. Однако это не всегда означает, что данный элемент будет виден пользователю. Например, если список содержит много элементов, то для просмотра последнего из них необходимо пользоваться линейкой прокрутки. К счастью, элемент управления ListView предоставляет возможность программным образом перейти к заданному элементу.

Для каждого элемента списка существует метод EnsureVisible, при вызове которого указанный элемент списка выводится в видимой части элемента управления ListBox. Для иллюстрации поместите этот компонент на форму, а затем щелкните на нем правой кнопкой мыши и выберите команду Properties из быстрого меню. В диалоговом окне Properties Pages измените свойство View на 3 — lvwReport. Затем перейдите во вкладку Column Headers, выберите поле теста Insert Column и введите там "Какой элемент". Щелкните OK. И наконец напишите следующий код для события Load формы:

Private Sub Form_Load()
  Dim x As Integer
  With ListView1
    For x = 1 To 20
      .ListItems.Add Key:="Элемент" & x, Text:="Элемент" & x
    Next x
    .SelectedItem = .ListItems("Элемент11")
    .SelectedItem.EnsureVisible
  End With
End Sub

Запустите проект на выполнение. VB откроет форму, выведет окно списка и выделит в нем одиннадцатый элемент.

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

Совет 269. Работа в VB с формулами, представленными в виде строковых переменных

Если до того, как начать программировать на VB, вы уже работали с Microsoft Access, то вы, вероятно, ощутили нехватку в VB очень удобного метода Eval, который вычисляет заданную строковую переменную, как если бы она была кодом. Так, в Access вы могли легко вычислять математические выражения, передаваемые как строки, например выражения подобного вида:

iResult = Eval("(2 * 3) + 5)")

где iResult принимает значение 11.

Чтобы получить те же возможности в VB, вам, несомненно, пришлось обратиться к сложным функциям синтаксического разбора строк. Или же вы были вынуждены добавить к своему проекту библиотеку из Access, что существенно усложняет его для реализации столь простого метода.

Теперь, с выходом VB6, вам больше не придется прибегать к подобным ухищрениям, чтобы воспользоваться методом Eval, поскольку Microsoft включила его в элемент управления Script. С помощью этого компонента вы можете добавлять к своим приложениям возможность написания сценариев конечным пользователем. Потому-то для реализации данной возможности в состав Script и вошел метод Eval, благодаря которому вы сможете легко вычислять математические строковые переменные.

Элемент управления Script находится по адресу http://msdn.microsoft.com/scripting/. Загрузите его и выполните следующий простой пример, где для события Click командной кнопки введен такой код:

Private Sub Command1_Click()
    MsgBox txtFormula & " = " & SC1.Eval(txtFormula)
End Sub

Здесь элемент управления SC1 вычисляет математическую формулу, вводимую в поле текста txtFormula.

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

Совет 270. Как сделать невидимым курсор мыши в VB-приложении

APi-функция ShowCursor предоставляет простой способ сделать невидимым курсор мыши:

Private Declare Function ShowCursor Lib "user32" _
    ByVal bShow As Long) As Long

Если вы зададите параметр bShow как 0, курсор мыши исчезнет, если как 1, — появится вновь. Только помните, что при использовании этой функции курсор просто становится невидимым, а это совсем не означает, что он отключен. Чтобы продемонстрировать, что мы имели в виду, добавим приведенное выше описание функции к проекту и введем следующий код для формы:

Dim blnShow As Boolean
Private Sub Form_Click()
    blnShow = Not blnShow
    ShowCursor blnShow
End Sub
Private Sub Form_Load()
    blnShow = True
End Sub
Private Sub Form_QueryUnload _
    (Cancel As Integer, UnloadMode As Integer)
    ShowCursor True
End Sub

Запустим проект на выполнение. Щелкнем форму, и VB уберет с экрана курсор мыши. Снова щелкнем форму, и курсор появится вновь. Если бы курсор мыши был действительно отключен, то мы бы не смогли вернуть его на экран одним только щелчком формы. Теперь опять щелкнем форму, чтобы курсор сделался невидимым, и перетащим его к линейке меню интегрированной среды разработки VB. Когда невидимый курсор будет перемещаться от одной кнопки меню к другой, последняя будет приподниматься над поверхностью, сообщая о том, что она готова для щелчка. И действительно, вы по-прежнему можете пользоваться мышью для вызова команд меню или выбора объектов на экране.

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