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

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

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

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


Совет 298. Быстрый своппинг строковых переменных

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

Sub SwapString (String1$, String2$)
  Dim Save$
  Save = String1$ ' запись в промежуточную переменную
  String1$ = String2$
  String2$ = Save$
End If

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

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

Declare Sub CopyMemory Lib "kernel32" Alias _
  "RtlMoveMemory" (Destination As Any, _
  Source As Any, ByVal Length As Long)
    
Public Sub SwapSrting(String1$, String2$)
  Dim Save As Long
  'Своппинг двух строковых переменных
  Save = StrPtr(String1)
  Call CopyMemory(ByVal VarPtr(String1), ByVal VarPtr(String2), 4)
  Call CopyMemory(ByVal VarPtr(String2), Save, 4)
End Sub

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

Здесь нужно отметить два момента:

  1. Такой прием можно использовать только для своппинга — операцию присвоения строковой переменной таким образом делать нельзя (недопустимо, чтобы одна и та же строка данных использовалась в двух разных дескрипторах).
  2. В VB описатель строки содержит только адрес ее содержимого. Число байтов строки находится в четырех байтах, которые хранятся непосредственно перед этим содержимым, то есть функция LenB(MyString$) может быть реализована в виде такой конструкции:

    Dim LenBmy&
    Call CopyMemory(LenBmy$, ByVal StrPtr(MyString$) - 4, 4)
    

Тем, кто еще пользуется QuickBasic для DOS, напомним, что здесь длина строки хранится в ее описателе вместе с адресом. Но нужно иметь в виду, что адресация строк в QB 4.x и PDS 7.x различается, и соответственно различаются описатели строк. Адрес дескриптора можно получить командами VARSEG (адрес сегмента) и VARPRT (смещение внутри сегмента); копирование областей памяти производится командами PEEK (чтение байта) и POKE (запись байта).

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

Совет 299. Динамическое управление меню

Многие программисты порой забывают о том, что меню, создаваемое с помощью Menu Editor, состоит из компонентов, которые работают так же, как и все остальные элементы управления. Это, в частности, позволяет непосредственно в процессе работы приложения управлять пользовательским интерфейсом. Так, можно изменять название команды меню (свойство Caption), делать ее недоступной для выполнения (Enable) или невидимой (Visible). Если же вы создали массив меню, назначив данному компоненту индекс (свойство Index), динамически создавать или удалять элементы такого массива, можно, например, следующим образом:

i% = mnuFileArray.Ubound + 1  ' следующий номер индекса после самого
                              ' большого уже существующего
Load mnuFileArray(i%)  ' создание нового элемента массива меню
mnuFileArray(i%).Caption = "NewArray" & i%  '  название команды
...
Unload mnuFileArray(1)        ' удалить элемент с индексом 1

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

Совет 300. Используйте каталог Template

Если у вас есть VB-проект, который вы хотите хотя бы иногда использовать в качестве шаблона при создании новых проектов, поместите его в каталог ...\VisualStudio\VB98\Template\Projects\ или другой каталог, который вы указали при установке VB 6.0. (Для VB 5.0 этот каталог называется VB5\Template\Projects\.) Тогда ваш шаблон будет автоматически появляться в окне New Project при создании нового проекта.

Такой вариант создания нового проекта на основе шаблона лучше, чем простая загрузка существующего проекта командой Open, ибо в последнем случае есть опасность забыть, что необходимо обязательно сразу же сохранить проект и все его компоненты командой Save As, иначе все изменения проекта будут записываться в исходный шаблон.

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

Template/Подкаталог   При выполнении команды Project/Add
                      компонент 
-----------------------------------
FORMS                 Form 
MDIForms              MDI Form 
MODULES               Modules 
CLASSES               Class Modules 
USERCTLS              User Control 
PROPPAGE              Property Page 
USERDOCS              User Document 
-----------------------------------

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

Совет 301. Используйте Microsoft Forms 2.0 Form (когда это нужно)

Если вы хотите создавать программные компоненты формы (модули формы), которые можно использовать как в VB, так и в Office/VBA, то вам нужно пользоваться конструктором MS Forms 2.0. В среде VBA он вызывается командой Insert|UserForm, в среде VB — Project|Add Microsoft Forms 2.0 Form (если такая команда отсутствует в среде VB, то конструктор нужно подключить с помощью команды Project|Components|Designers).

Напомним, что формы UserForms в VBA — это не то же самое, что VB-формы (Ruby Forms). VBA UserForms являются экземплярами ActiveX-конструктора Microsoft Forms 2.0 (FM20.dll), который входит в состав как VB, так и VBA. Более того, все элементы управления, представленные в нем по умолчанию, являются не встроенными, а ActiveX-компонентами (то есть при желании их можно подключить и к VB-форме). Чтобы убедиться в этом, откройте окно Tools|Additional Controls.

К сожалению, эти два вида форм различаются не только форматом их модулей. Каждая из них использует свой собственный набор встроенных элементов управления. Проблема же заключается в том, что эти наборы не только не совпадают функционально, но даже для одинаковых по значению элементов управления используются разные наименования свойств, событий и методов. MS Forms 2.0 не поддерживают ряд очень полезных встроенных элементов управления VB (Timer, FileListBox, PictureBox, DriveListBox, DirListBox, Menu, Shape и Line), но при этом включают другие полезные компоненты (TabStrip, MultiPage) и команды проектирования формы (TabOrder).

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

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

Отсутствие встроенных элементов управления (на что мы сетовали в предыдущем совете) можно компенсировать возможностью использования дополнительных элементов управления ActiveX. Если у вас имеется VB 5.0 или 6.0, то вы можете самостоятельно сделать, например, на основе встроенного элемента Timer собственный ActiveX-компонент для реализации функции таймера. Такой OCX можно было бы использовать в конструкторе MS Forms 2.0 (VBA-формы). Но прежде чем делать свои компоненты, стоит посмотреть, нет ли уже готового подходящего элемента на Web-сайтах с VB-ресурсами и разными Freeware & Shareware.

Так, очень нужный порой компонент Timer в виде файла IETimer.OCX можно найти по адресу www.basic.visual2000.ru/develop/vb/source/.

Но здесь мы хотели бы отметить некоторые различия в процедурах подключения OCX к среде VB и VBA.

  1. Обычно OCX-компоненты помещаются в системный каталог Windows/System/. Однако мы рекомендуем размещать такие дополнительные файлы в каком-нибудь специально созданном для этого пользовательском подкаталоге. Это упростит процедуры "чистки мусора" и обновления версий компонентов, так как можно будет четко следить за тем, что вы записали сами, а что автоматически прописали в System операционная система и программы установок разных приложений.
  2. Далее необходимо зарегистрировать OCX-файл. Лучше всего это сделать с помощью утилиты RegSvr32.exe, которая входит в состав VB и Office (проще всего скопировать ее в подкаталог с вашими OCX):

    RegSvr32.exe Your.OCX
    
  3. Подключение к панели Toolbox для VB- и VBA-форм несколько различается.

  4. Не менее интересна процедура отключения дополнительного компонента. Для VB-форм нужно войти в окно Components|Controls и убрать в нем флажок. Для VBA-форм эту же операцию можно сделать в окне Additional Controls. Только проще — щелкнуть правой кнопкой мыши на изображении компонента на панели Toolbox и выбрать команду Delete. Кстати, с помощью этого же меню в данном случае можно изменить название и изображение пиктограммы элемента управления (рис. 302):

    Рис. 302

  5. Еще один вопрос — как убрать имя ненужного компонента из списков в окнах Components|Controls и Additional Controls. Отменить регистрацию OCX (вернее, любого ActiveX-компонента) можно с помощью той же утилиты RegSvr32:

    RegSvr32.exe /u Your.OCX
    

    Но иногда оказывается, что этого недостаточно, и приходится вручную просматривать (искать заданный файл) и чистить Реестр.

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

Совет 303. Как погрузить приложение в "глубокий сон"

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

' описание функции
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
' обращение к ней (аргумент задается в миллисекундах)
Sleep 150010 ' ожидание 150,01 секунды

Но при использовании этой конструкции есть другая проблема — "глубокий сон" приложения не удастся прервать до истечения заданного интервала времени.

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

Совет 304. Управление расстоянием между столбцами в ListBox

С помощью API-функции SendMessage вы можете управлять позициями табулятора в элементе управления ListBox. Это может быть полезно, если каждая строка списка состоит из полей, разделенных символом Tab (код ASCII = 9), то есть если список выводится в виде таблицы, содержащей несколько колонок. Для этого в программный модуль запишите такое объявление и подпрограмму:

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
Private Const LB_SETTABSTOPS = &H192
Public Sub SetListTabStops(MyListBox As ListBox, _
               ParamArray ParmList() As Variant)
  'Коррекция позиции Tabs в списке ListBox
  '
  ' Для передачи переменного числа однородных параметров
  ' используется конструкция ParamArray
  ' ВНИМАНИЕ! Нижний индекс ParmList равен 0
  ' даже при Option Base 1!!!
  ' Позиция табулятора определяется в специальных величинах
  ' окна, которая в среднем равна 1/4 символа

  Dim i As Long
  Dim NumColumns As Long
  '
  ' формирование массива для установки табуляторов
  ReDim ListTabs(0 To UBound(ParmList)) As Long
  For i = 0 To UBound(ParmList)

          ListTabs(i) = ParmList(i)
  Next i
  NumColumns = UBound(ParmList) + 1
  '
  ' установка новых значений позиций табулятора
  Call SendMessage(MyListBox.hWnd, LB_SETTABSTOPS, _
   NumColumns, ListTabs(0))
  ' вывести новое изображение списка
  lstMyListBox.Refresh
End Sub

Чтобы протестировать эту конструкцию, создайте форму, на которой разместите элемент списка lstMyListBox и две командные кнопки Command1 и Command2. Далее запишите такой программный код для этих компонентов:

Private Sub Form_Load()
  ' Начальное формирование списка
  ' с тремя колонками, разделенными символом vbTab = Chr$(9)
  lstMyListBox.AddItem "Колонка11" & vbTab & "Колонка12" _
         & vbTab & "Колонка13"
  lstMyListBox.AddItem "C21" & vbTab & "C22" _
         &  vbTab & "C23"
End Sub

Private Sub Command1_Click()
  ' одинаковое расстояние в 52 позиции (13 символов)
  Call SetListTabStops(lstMyListBox, 52)
End Sub

Private Sub Command2_Click()
  ' переменное расстояние (10 и 30 символов)
  Call SetListTabStops(lstMyListBox, 40, 120)
End Sub

Запустите на выполнение созданный проект, и вы увидите такое изображение списка:

Щелкните кнопку Command1 — список примет следующий вид:

Щелкните Command2, и получится другое изображение:

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