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

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

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

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


Совет 351. Пишите замечания, присылайте советы, задавайте вопросы

Не стоит даже повторять, как важна для авторов обратная связь с читателями. И мы бы очень хотели, чтобы программисты не только указывали нам на наши оплошности, но и делились собственными находками и "трюками".

Промашки у нас, конечно, бывают. Например, Евгений Иванов справедливо заметил, что в Совете 307 мы, рассказывая об удалении каталога с помощью Windows API, совсем забыли сказать, что в большинстве случаев с этой задачей отлично справляется давно знакомая встроенная Basic-функция rmDir.

Приятно отметить, что журнал "КомпьютерПресс" и наши "Советы" читают не только в нашей стране, но и в дальнем зарубежье (кстати, хотя русскоязычных программистов в Европе и США гораздо меньше, чем в России, их активность на электронных форумах и в переписке заметно выше). Михаил Эскин, например, живет в Мюнхене и, как оказалось, является давним читателем наших публикаций. При этом он отмечает: "Ваши статьи стали постоянным моим спутником, несмотря на появление специальной литературы на прилавках". Спасибо!

В Германию российские журналы приходят с задержкой, поэтому Михаил только в ноябре прислал некоторые замечания по поводу статьи "Календарь наших дел", опубликованной в КомпьютерПресс N 5/2000. (Кстати, Михаил — автор серии статей о создании элементов управления в среде VB, опубликованных на сервере www.vbrussian.com.) Об этих замечаниях мы поговорим в последующих Советах.

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

Совет 352. Как решить проблему с VisData

Мы уже несколько раз отмечали, что у утилиты VisData (создание баз данных) существуют проблемы с вводом и просмотром русских текстов. Михаил Эскин отмечает, что это, скорее, проблема конфигурации конкретного компьютера, которая решается следующим образом: в разделе [FontSubstitutes] файла WIN.INI нужно добавить сверху строку Tahoma,0=Tahoma,204 и затем перезагрузить компьютер.

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

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

Совет 353. Внимание при работе с булевыми типами данных

Михаил отметил также, что приведенный в статье "Календарь наших дел" код для чтения/записи свойства DeleteMe (для пользовательского элемента управления Memos) при его привязке к значению флажка chkDeleteMe

Public Property Get DeleteMe() As Boolean
  If chkDeleteMe.Value = 0 Then  
    DeleteMe = False  
  Else  
    DeleteMe = True  
  End If  
End Property  
   
Public Property Let DeleteMe(ByVal newDelete As Boolean)  
  If newDelete Then  
    chkDeleteMe.Value = 1  
  Else  
    chkDeleteMe.Value = 0  
  End If  
End Property  

Можно упростить, записав содержимое каждой из этих процедур в одну строку:

DeleteMe = -1 * chkDeleteMe.Value

chkDeleteMe.Value = -1 * newDelete

Несмотря на то что речь здесь идет вроде бы об очень частной проблеме, остановимся на ней подробнее.

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

Нам кажется, что вариант, предложенный Михаилом Эскиным, приведет именно к таким негативным последствиям. По этому поводу отметим следующие моменты:

  1. Мы считаем принципиально неверным неявное преобразование данных, в данном случае из Integer в Boolean и наоборот. К сожалению, VB позволяет делать это, хотя вполне вероятно, что в VB.NET (7.0) такие вещи будут запрещены.

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

  3. Серьезной проблемой VB является отсутствие беззнаковых целых типов данных. При этом путаница часто возникает именно из-за того, что переменные типа Integer и Long на самом деле выступают в роли то чисел со знаками (в арифметических операциях и при использовании десятичных литералов), то беззнаковыми (в логических операциях и в шестадцатеричных и восьмеричных литералах).

  4. Вообще говоря, практически в любой программе можно легко обойтись без использования типа Boolean, так как она является всего лишь частным случаем Integer (те же два байта для хранения информации). Более того, можно даже добиться экономии памяти — если использовать переменную типа Byte со значениями 0 или 1. Это легко можно сделать в "Календаре", где основная коррекция заключалась бы в замене в SQL-запроса:

    вместо "Where DeleteMe = True"
       
    написать — "Where DeleteMe = 1"  
    

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

  5. Но главное достоинство конструкции IF...Then...Else...EndIf — очевидность логики ее работы. Попробуйте мгновенно ответить, каково будет значение DeleteMe при chkValue = 0 в этом выражении:

    DeleteMe = -1 * chkValue  
    

  6. В одну более короткую строку (но все же с увеличением машинных команд) можно было бы предложить вариант без неявного преобразования данных:

    DeleteMe = (chkDeleteMe.Value = 1)
       
    chkDeleteMe.Value = IIf (1, 0, DeleteMe)  
    
    Но с точки зрения "очевидности результата" такой код также не безупречен.

  7. Несмотря на большое число строк в нашей конструкции IF...Then...Else...EndIf, очевидно, что этот код является самым компактным и быстрым (он занимает всего несколько машинных коротких команд). Более компактно его можно записать в таком виде:

    If chkDeleteMe.Value = 0 Then DeleteMe = False _ 
      Else DeleteMe = True 
    
Мы советуем использовать именно такой вариант.

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

Совет 354. Как обеспечить совместимость между VBA- и VB-проектами

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

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

Это, в частности, касается и создания модулей формы. Мы уже писали (Совет 301), что Office/VBA использует для создания форм ActiveX-конструктор Microsoft Form 2.0, который доступен также в VB. То есть VB может создавать два типа: собственные VB-формы (Ruby Forms) и UserForms (VBA Forms). Однако проблема заключается в том, что, даже используя одинаковый конструктор, VB и VBA сохраняют модули формы в разных форматах. При этом VB может читать оба формата, а VBA — только свой собственный.

Соответственно, если вы намерены создавать модули формы двойного применения, это следует делать не просто с помощью MS Forms 2.0 , а обязательно с этой целью использовать Office/VBA.

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

Перенос модулей формы в случае их несовместимости можно сделать следующим образом (например, из VB в VBA). Создайте в VBA визуальную форму со всеми компонентами. Задайте имена компонентов такие же, как в VB. Далее скопируйте содержимое кода из VB в VBA через буфер обмена.

Однако этот способ будет работать только при использовании Forms 2.0. При переносе VB-форм придется вручную корректировать имена некоторых событий и свойств. Например, в VBA события формы Initialize и Terminate соответствуют событиям Load и Unload в VB. В общем, с Microsoft не соскучишься.

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

Совет 355. Используйте свойство Button для элемента управления DataGrid

Элемент управления DataGrid позволяет установить для ячеек одной или нескольких колонок таблицы свойство Button, которое обеспечивает их работу в режиме "кнопок". Например, установите

DataGrid1.Columns.Item(1).Button = True

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

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

Совет 356. Как сделать Help для своего приложения

Программ создания HELP-файлов довольно много, ряд из них — свободно распространяемые или условно-бесплатные. Для VB и VBA, возможно, лучшим способом является использование утилиты Microsoft HTML Help Workshop, которая поставляется в составе ряда программных продуктов, в том числе MS Office 2000 Developer Edition. При желании ее можно скачать из Интернета. Описания работы этой утилиты имеются в целом ряде книг по VB.

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

Совет 357. Преобразование текстового файла в набор данных ADO

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

connCSV.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _  
  "Data Source=" & FileName$ & _  
  ";Extended Properties='text;FMT=Delimited'"  

В данном случае строка с параметрами соединения (Connection String) содержит раздел Extended Properties, который указывает, что используется текстовый файл с полями. Однако следует иметь в виду, что приведенный вариант обращения подразумевает наличие в первой строке текстового файла заголовков полей. Если же заголовков нет, следует указать в явном виде аргумент HDR:

connCSV.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _  
  "Data Source=" & FileName$ & _  
  ";Extended Properties='text;HDR=NO;FMT=Delimited'"  

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

Совет 358. Как расширить массив элементов управления во время выполнения

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

Load object(index)  

где object — имя массива, а index — номер нового элемента управления, который вы хотите добавить. Но при этом в исходном состоянии в массиве уже должен иметься хотя бы один элемент управления (нумерация индекса начинается с нуля). В более общем случае создание новой кнопки может выглядеть так, как описано ниже.

Создайте новый проект и добавьте к форме командную кнопку. Затем в окне Properties введите 0 для свойства Index — VB сразу преобразует одиночную кнопку в массив. Далее введите такой код для формы:

Private Sub cmdBtn_Click(Index As Integer)  
  ' создание новой кнопки для массива элементов управления  
  Dim btn As CommandButton  
  Dim iIndex As Integer  
  iIndex = cmdBtn.Count  'текущее числе элементов массива  
  If iIndex <= 32767 Then '  можно добавлять  
    Load cmdBtn(iIndex)  
    Set btn = cmdBtn(iIndex)  
    With btn  ' установка свойств  
      .Top = cmdBtn(iIndex - 1).Top + 620  
      .Caption = "Command" & iIndex + 1  
      .Visible = True  
    End With  
    Set btn = Nothing  
  End If  
End Sub  

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

Совет 359. Работа с реестром и INI-файлами с помощью System.PrivateProfileString

Работа с реестром Windows может выполняться не только с помощью функций Windows API или объекта Registry (см. Совет 273), но и с применением свойства PrivateProfileString объекта System, который входит в состав библиотеки Microsoft Word 8.0/9.0 Object Library. Она автоматически подключается при работе с MS Word 97/2000 и может использоваться в любых инструментах, которые поддерживают работу с ActiveX-объектами. В частности, к среде VB или MS Office/VBA она подключается с помощью команды Project|Reference или Tools|Reference соответственно.

Вот как будет выглядеть чтение полного имени каталога, где находится Internet Explorer:

RegFile$ = "" ' пустое имя означает Системный Реестр SectionName$ =
"HKEY_CURRENT_USER\Software\Microsoft\" _
    & "Windows\CurrentVersion\App Paths\IEXPLORER.EXE") ' имя раздела  
KeyName$ = "Path"  ' имя ключа  
IEPath$ = System.PrivateProfileString(RegFile$, SectionName$, KeyName$)  
If IEPath$ <> "" Then   ' есть имя каталога  
  MsgBox "Имя каталога с IE = " & IEPath$  
End If  

Соответственно запись нового значения параметра в реестр выполняется следующим образом:

System.PrivateProfileString(RegFile$, SectionName$, KeyName$)= IEPath$  

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

Например, при закрытии текущего документа Word можно автоматически фиксировать имя последнего использовавшегося документа:

System.PrivateProfileString("C:\MyWordSetting.ini", "MacroSettings", _
    "LastFile") = ActiveDocument.FullName  

А при загрузке Word можно автоматически открыть данный файл:

LastFile$ = System.PrivateProfileString("C:\Settings.Txt", _  
    "MacroSettings", "LastFile")  
If LastFile$ <> "" Then Documents.Open FileName:=LastFile$  

Необходимо обратить внимание на следующие особенности применения свойства PrivateProfileString:

Например, если файл D:\MyFile.INI не существует, то после выполнения кода:

System.PrivateProfileString("d:\ MyFile.INI", "test1", "key1") = "andy"  

Будет сформирован файл следующего содержания:

[test1]  
key1=andy  

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