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

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

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

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


Совет 155. Используйте VB API Viewer

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

Кроме того, многие такие функции используют в качестве параметров специальные структуры данных Type, а для управления режимами выполнения функций — определенные значения параметров. Здесь нужно обратить внимание на следующую особенность современного стиля документации и литературы по VB: если в описании говорится об использовании параметра или типа данных, имя которых обозначено заглавными буквами (например LB_GETITEMHEIGHT), то имеются в виду некоторые стандартные значения констант или структур.

Для упрощения работы с наиболее часто используемыми внешними функциями — стандартным набором Windows — в состав VB 5.0 (и более ранних версий) входят два файла, содержащие описания процедур, а также константы и структуры данных: WIN32API.TXT (собственно набор Win32 API) и MAPI32.TXT (дополнительный набор Message API — почтовых функций). А для удобства работы с ними — утилита APILOAD.EXE и ее описание APILOAD.TXT. Все эти файлы находятся в подкаталоге \Winapi основного каталога Visual Basic. (Для установки файлов подкаталога \Winapi в программе Setup Visual Basic в разделе Online Help and Samples должен быть отмечен пункт Windows API Reference.)

Пользоваться информацией, содержащейся в файлах описаний, в принципе можно с помощью любого текстового редактора, копируя через буфер обмена необходимые данные в VB. Но удобнее для этого использовать стандартную программу APILOAD.EXE (VB API Viewer).

Предупреждение. Не следует загружать файл WIN32API.TXT в проект в виде программного модуля. Файл занимает слишком много места в памяти. Обычной практикой является копирование нужных описаний, констант и структур данных в соответствующие программные модули проекта.

Ее можно запускать из среды Windows, однако это лучше делать из среды VB 5.0, для чего нужно записать команду вызова APILOAD.EXE в меню Add-Ins. Для этого в среде VB меню Add-Ins выберите команду Add-In Manager, в появившемся диалоговом окне в списке Available Add-Ins установите флажок VB API Viewer (рис.1) и нажмите ОК. Точно так же можно установить обращение и к другим дополнениям VB.

Рис. 1

Примечание. Если в списке Available Add-Ins нет стандартного набора дополнений VB, то нужно запустить программу Setup VB и отметить раздел Wizards and Templates.

Далее все происходит достаточно просто — когда вам нужно получить описания, константы и типы данных для работы с API, выберите команду Add-Ins|API Viewer, после чего появится окно утилиты (рис.2). Далее необходимо загрузить нужный файл описаний, например WIN32API.TXT, с помощью команды Load Text File из меню File. Потом, устанавливая в списке API Type нужный вид информации, можно просматривать содержимое файла в списке Available Items. Для быстрого поиска можно использовать кнопку Search.

Рис. 2

Для более быстрой загрузки файла описания можно преобразовать тестовый файл в базу данных Jet (соответствующая команда находится в меню File). Но при работе с MDB-файлом почему-то пропадает кнопка Search.

Добавление описаний из исходного файла в проект VB выполняется следующим образом. В списке Available Items выделяется нужный элемент, а потом с помощью кнопки Add его содержимое переносится в окно Selected Items. Чтобы удалить информацию из окна Selected Items, надо установить на нее курсор и нажать кнопку Remove.

Скопировать содержимое окна Selected Items в проект VB можно двумя способами.

  1. Нажав кнопку Copy, скопировать данные в буфер обмена, а потом, перейдя в среду VB, вставить информацию в нужное место.
  2. Нажав кнопку Insert, сразу скопировать информацию в раздел Declarations активного в этот момент программного модуля VB. (Это самый удобный вариант — ведь обращение за описанием API-функции производится обычно как раз при активизации соответствующего модуля.)

ВНИМАНИЕ! ВСТРЕЧАЮТСЯ ОШИБКИ! К сожалению, в файле описаний встречаются ошибки. В частности, в WIN32API.TXT приведено такое описание функции GetCurrentDirectory (о ней рассказывается в Совете 164):

Declare Function GetCurrentDirectory Lib "kernel32" _
Alias "GetCurrentDirectory"...

Здесь видно, что имя функции и ее альтернативное название совпадают. Соответственно при копировании этой строки в программный код редактор VB совершенно логично автоматически убирает команду Alias. При запуске программы на выполнение выдается сообщение об отсутствии функции в DLL- библиотеке. Ошибка очевидна — в конце названия функции после команды Alias пропала буква "A": ... Alias "GetCurrentDirectoryA"... Имейте это в виду, если вам встретится аналогичная ошибка в других описаниях.

А зачем вообще нужна команда Alias? Об этом мы расскажем в одном из следующих выпусков "Советов".

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

Совет 156. Автоматическое выделение элементов списка

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

Ключевым моментом в решении данной задачи является то, что каждый элемент стандартного окна списка имеет одинаковую высоту. И если вы знаете эту высоту (значение для самого верхнего видимого элемента) и Y-координату курсора, то вычисление элемента, находящегося под курсором, не составит никакого труда.

Воспользуемся API-функцией SendMessage, чтобы получить высоту каждого элемента списка в пикселах (единица физического разрешения экрана). Для этого нужно обратиться к функции (мы писали о ней более подробно в Cовете 133) с параметрами wMsg = LB_GETITEMHEIGHT (= &H1A1) и wParam = lParam = 0. Возвращаемая величина и будет высотой элемента списка.

Событие List1_MouseMove передает Y-координату курсора в твипах (единица длины — 1/1440 дюйма). Поэтому нужно преобразовать полученную ранее высоту элемента списка из пикселов в твипы с помощью метода ScaleY.

Теперь поделим передаваемое значение Y на преобразованную высоту элемента списка, затем добавим значение свойства TopIndex — и мы получим элемент, находящийся под курсором. И, наконец, последнее — проверьте, чтобы вычисленное значение свойства ListIndex не было равно или не превышало значение свойства ListCount, иначе могут возникнуть ошибки при установке нового значения.

Примечание. Высоту элемента списка в твипах ItemHeightTwips& можно вычислить один раз вне цикла, например при загрузке формы, а потом передавать ее в процедуру List1_MouseMove в качестве общего параметра формы.

Option Explicit
Private Declare Function SendMessage Lib _
    "user32" Alias "SendMessageA" (ByVal hWnd _
    As Long, ByVal wMsg As Long, ByVal wParam _
    As Long,  lParam As Any) As Long
  Const LB_GETITEMHEIGHT = &H1A1

Private Sub List1_MouseMove(Button As Integer, _ 
    Shift As Integer, X As Single, Y As Single) 
  ' Выделение текущей позиции списка в
  ' соответствии с перемещением курсора мыши 
  Dim ItemHeightPixels&, ItemHeightTwips&, _
  NewIndex& 
  With List1
    ' Высота элемента в пикселах
    ItemHeightPixels& = SendMessage(.hWnd, _
    LB_GETITEMHEIGHT, 0, 0)
    ' Высота элемента в твипах
    ItemHeightTwips& = ScaleY(ItemHeightPixels&, _
    vbPixels,  vbTwips)
    ' Вычисление индекса
    NewIndex& = .TopIndex + (Y \ ItemHeightTwips&)
   ' Проверка — не вышли ли мы за пределы списка?
   If NewIndex& < .ListCount Then .ListIndex = NewIndex&
  End With
End Sub

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

Совет 157. Применяйте повторно используемые процедуры

Вернемся к предыдущему совету. А если такой режим работы со списком понадобится применить и для других списков? Писать такой код в каждой процедуре List_MouseMove?

Нет, нужно создать отдельный программный модуль, например ListMenu.Bas (или использовать уже имеющийся модуль, где вы храните свои повторно используемые вспомогательные процедуры), и сформировать в нем подпрограмму (ListMenuMouse) на основе программного кода подпрограммы List1_MouseMove. Текст такого модуля приведен в листинге 1. Обратите внимание на некоторые изменения в программе, в частности на то, что в качестве параметров в эту универсальную подпрограмму передается не только описание элемента управления, но и формы. Это необходимо, так как на самом деле метод ScaleY должен быть привязан к конкретной форме. В модуль ListMenu.Bas переместились также описания используемой API-функции и константы.

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

Листинг 1

Attribute VB_Name = "ListMenu"
Option Explicit

Private Declare Function SendMessage Lib "user32"_
   Alias "SendMessageA" (ByVal hWnd As Long, _
   ByVal wMsg As Long, ByVal wParam _
   As Long, lParam As Any) As Long
Const LB_GETITEMHEIGHT = &H1A1

Public Sub ListMenuMouse(cl As Control, fm As Form, _
   Y As Single)
   '
   ' Выделение текущей позиции списка
   ' в соответствии с перемещением курсора мыши
   Dim ItemHeight As Long, NewIndex As Long
   With cl
       ' Высота элемента списка в пикселах
       ItemHeight = SendMessage(.hWnd, LB_GETITEMHEIGHT, _
           0, 0)
       ' Преобразование из пикселов в твипы
       ItemHeight = fm.ScaleY(ItemHeight, vbPixels, vbTwips)
       ' Вычисление индекса элемента списка
       NewIndex = .TopIndex + (Y \ ItemHeight)
       ' Проверка — не вышел ли индекс за пределы списка?
       If NewIndex < .ListCount Then .ListIndex = NewIndex
   End With
End Sub

Теперь этот модуль можно подключить к какому-либо проекту, и, чтобы использовать режим отслеживания движения мыши в любом из его списков, нужно просто вставить следующую строку в событие ListN_MouseMove (заменив ListN на конкретное имя элемента управления):

Call ListMenuMouse(ListN, Me, Y)

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

Совет 158. Как очистить все текстовые окна

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

Dim vControl
For Each vControl In Me.Controls
  ' где Me — текущая форма
  If TypeOf vControl Is TextBox Then 
    vControl.Text = ""
  End If
Next

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

Совет 159. Используйте клавиатуру для управления размерами и расположением элементов управления

Иногда бывает гораздо удобнее использовать не мышь, а клавиатуру — стрелки Right, Left, Down и Up (Вправо, Влево, Вниз и Вверх) — для перемещения границ элементов управления. Для изменения размеров элементов управления (левый верхний угол остается на том же месте, перемещаются только правая и нижняя границы) надо использовать соответствующие клавиши стрелок при нажатой Shift. А для перемещения всего объекта, без изменения его размеров, следует нажать клавишу Ctrl.

Для того чтобы переместить левую (или верхнюю) границу элемента управления, например, вправо, нужно сначала сместить весь объект (Ctrl+Right), а потом вернуть на исходное место правую границу (Shift+Left).

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

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

Совет 160. Эмуляция события Click для правой кнопки мыши

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

Обратите внимание на специфику реализации события Click для командной кнопки. Данное событие происходит в тот момент, когда вы отпускаете кнопку мыши, но при условии, если в моменты нажатия и отпускания левой кнопки мыши (а время между ними может быть достаточно большим) курсор мыши находится в пределах изображения командной кнопки. Понятно, что событие MouseDown сразу не подходит для эмуляции Click. Что же касается события MouseUp, то оно происходит, даже если в этот момент курсор мыши уже ушел за пределы кнопки — главное, чтобы он был в ее пределах в момент события MouseDown.

Решение задачи достаточно очевидно — нужно определить координаты мыши в событии MouseUp и проверить, попадают ли они в пределы изображения кнопки. Но при этом нужно иметь в виду, что координаты курсора мыши имеют значение относительно данного элемента управления, а не формы. Сделать это можно следующим образом:

Sub Command1_MouseUp (Button As Integer, _
  Shift As Integer, X As Integer, Y As Integer)
  '
  If Button = 2 Then  ' Правая кнопка мыши
    If X >= 0 And X <= Command1.Width And _
      Y >= 0 And Y <= Command1.Height Then
      ' Здесь нужно написать код, который
      ' обрабатывает данное событие
    End If
  End If
End Sub

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

Совет 161. Установка курсора мыши в нужное место на форме

Отличительной чертой коммерческих приложений является то, что курсор может "прыгать", то есть автоматически перемещаться на кнопку в диалоговом окне, которая предлагается по умолчанию. С помощью простой API-функции вы можете сделать то же самое и в своих приложениях:

  #If Win32 Then
    Private Declare Function SetCursorPos Lib _
      "user32" (ByVal x As Long, ByVal y As Long) As Long
  #Else
    Declare Sub SetCursorPos Lib "User" _
    (ByVal x As Integer, ByVal y As Integer)
  #End If 
Private Sub Form_Load()
  #If Win32 Then
    Dim x As Long, y As Long
  #Else
    Dim x As Integer, y As Integer
  #End If
  '
  Me.Show
  ' Вычисляет координаты командной кнопки
  x = (Me.Left + Command1.Left + _
  Command1.Width / 2) / Screen.TwipsPerPixelX
  y = (Me.Top + Command1.Top + _
  Command1.Height / 2 + Me.Height - _
  Me.ScaleHeight) / Screen.TwipsPerPixelY
  ' Помещает курсор на кнопку
  SetCursorPos x, y
End Sub

Событие Form_Load вычисляет экранные координаты центра командной кнопки в пикселах. Затем функция SetCursorPos перемещает курсор мыши в требуемое место, аналогично функции "Snap-To", реализованной в продуктах Microsoft. Обратите внимание, что координаты и линейные размеры элементов управления задаются в твипах, а координаты курсора мыши при обращении к API-функции — в пикселах.

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

Совет 162. Используйте массивы элементов управления

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

Обращение к свойствам и методам отдельного элемента также выполнятся с помощью индекса, например cmdAction(Index).Caption = "Кнопка". Но у самого массива (без индекса) есть собственный набор свойств и методов.

Создается массив достаточно просто — в него включаются элементы с одинаковыми именами (свойство Name). При этом индекс формируется автоматически, но тут есть некоторые любопытные моменты. Например, вы последовательно сформировали четыре кнопки с именем cmdAction, у которых свойство Caption установили как А, Б, В и Г соответственно. Они сразу же получили индексы 0, 1, 2, 3.

Если теперь вы удалите кнопку Б, то индексы других кнопок не изменятся (не будет элемента с индексом 1). Теперь добавьте новую кнопку Д — она получит первый свободный индекс, равный 1. Далее мы хотим изменить индекс кнопки В. VB разрешит ввести только неиспользуемое значение индекса, например 4 или 10, то есть значение индекса конкретного элемента, присвоенного ему при создании, может быть изменено только самим программистов в явном виде — в отличие от свойства TabIndex (порядок обхода элементов управления на форме при нажатии клавиши Tab), где перенумерация выполняется автоматически.

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

Совет 163. Код, расположенный после вызова события Unload Me, препятствует закрытию формы

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

Private Sub cmdAction_Click(Index As Integer)
'
' *** эта подпрограмма не работает!
'
  Select Case Index
    Case 0: Me.WindowState = vbMaximized
    Case 1: Me.WindowState = vbNormal
    Case 2: Me.WindowState = vbMinimized
    Case 3: Unload Me ' не работает данная строка! 
  End Select
  MsgBox "Текущее состояние: " & _
  CStr(Me.WindowState) 
End Sub

Однако приведенная выше подпрограмма не работает, поскольку за оператором Unload Me идет строка, содержащая исполняемый код. Таким образом, форма станет невидимой, но она не будет выгружена. Чтобы исправить эту ошибку, необходимо заменить вызов события Unload Me на установку переменной и выполнить это событие в самой последней строке подпрограммы:

Private Sub cmdAction_Click(Index As Integer)
  '
  ' *** эта подпрограмма работает!
  '
  Dim blnUnload As Boolean
  Select Case Index
    Case 0: Me.WindowState = vbMaximized
    Case 1: Me.WindowState = vbNormal
    Case 2: Me.WindowState = vbMinimized
    Case 3: blnUnload = True
  End Select
  MsgBox "Текущее состояние: " & _
  CStr(Me.WindowState)
  If blnUnload = True Then Unload Me
End Sub

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