oldi

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

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

Совет 285. Как получить значения цветовой палитры

Совет 286. Будьте внимательны при использовании оператора Debug.Print

Совет 287. Как упростить математические вычисления в VBA

Совет 288. Как вычислить интервал между двумя датами, измеряемый в разных единицах

Совет 289. Как правильно прочитать имя каталога

Совет 290. Как заполнить поля электронного сообщения

Совет 291. Как открыть VB-файл в Notepad или в любом другом текстовом редакторе

Совет 292. Как ускорить операцию деления чисел с плавающей запятой

Совет 293. Как ускорить выполнение операций ввода-вывода для файлов

Совет 294. Как создать дополнение, закрывающее все окна проекта

Совет 295. Используйте ключевое слово WithEvents для связи форм MDI и MDIChild

Совет 296. Помните об операторе Option Base

Совет 297. Экспорт таблиц в виде текстовых файлов

Совет 285. Как получить значения цветовой палитры

В состав VB входит удобное средство преобразования отдельных значений Red, Green и Blue в одно цветовое значение типа Long – это функция RGB. К сожалению, VB не позволяет проводить обратное преобразование, но вы можете получить конкретные цветовые значения из шестнадцатеричного представления значения типа Long, создаваемого функцией RGB. Для этого создадим следующую функцию и поместим ее в стандартном модуле программы:

Public Type RGB_Type
     R As Long
     G As Long
     B As Long
End Type
Public Function ToRGB(ByVal Color _
     As Long) As RGB_Type
     '
     Dim ColorStr As String
     ColorStr = Right$(“000000” & _
          Hex$(Color), 6)
     With ToRGB
          .R = Val(“&h” & Right$(ColorStr, 2))
          .G = Val(“&h” & Mid$(ColorStr, 3, 2))
          .B = Val(“&h” & Left$(ColorStr, 2))
     End With
End Function

Чтобы воспользоваться данной функцией, поместите в форму какое-либо изображение, задав необходимое имя в свойстве Picture формы, а затем введите такой код:

Private Sub Form_MouseUp(Button _
     As Integer, Shift As Integer, _
     X As Single, Y As Single)
     '
     Dim RGB_Point As RGB_Type
     RGB_Point = ToRGB(Point(X, Y))
     With RGB_Point
          Me.Caption = “R = “ & .R & _
               “ G = “ & .G & “ B = “ & .B
     End With
End Sub

Запустите программу на выполнение. Щелкая мышью на различных частях изображения, вы будете видеть в заголовке формы соответствующие значения RGB. Обратите внимание, что при работе в VB3 вам нужно получать эти значения по отдельности, поскольку VB не поддерживал возвращение заданных пользователем типов данных до версии VB4. Подобное преобразование можно осуществить более быстрым способом, если воспользоваться командой LSet, которая копирует содержимое одного заданного пользователем типа данных (user-defined type, udt) в другой. Для этого заменим функцию, находящуюся в стандартном модуле программы, на такую:

Public Type RGB_Type
     R As Byte
     G As Byte
     B As Byte
     Filler As Byte
End Type
Private Type RGB_Full_Type
     lngRGB As Long
End Type
Public Function ToRGB(ByVal _
     vlngColor As Long) As RGB_Type
     '
     Dim udtRGBFull As RGB_Full_Type
     udtRGBFull.lngRGB = vlngColor
     LSet ToRGB = udtRGBFull
End Function
В начало

В начало

Совет 286. Будьте внимательны при использовании оператора Debug.Print

Вопреки распространенному мнению, операторы Debug.Print не всегда удаляются из исполняемых файлов. Продемонстрируем, например, такой случай. Создайте новый проект, поместите на форму командную кнопку Command1 и напишите для нее такой код:

Private Sub Command1_Click()
     Debug.Print DebugTime
End Sub
Public Function DebugTime()
     MsgBox “Привет!”
End Function

Скомпилируйте программу, запустите ее на выполнение и щелкните командную кнопку. На экране, как ни удивительно, появится окно сообщения. Это, конечно же, искусственная ситуация, но можно легко представить себе случаи, когда оператор Debug.Print используется для печати возвращаемого значения функции. Если переменные передаются как параметры ByRef и если функция изменяет значения этих переменных, то подобная ошибка распространится и на исполняемый файл, а обнаружить ее будет крайне трудно.

Таким образом, сформулируем основные выводы:

  • Передавайте параметры с помощью ключевого слова ByVal, за исключением тех случаев, когда вы уверены, что не будете изменять их, или когда вы применяете их в качестве «выходных» параметров.
  • Соблюдайте осторожность при использовании оператора Debug.Print. Он может выполнять больше действий, чем вы думаете.
В начало

В начало

Совет 287. Как упростить математические вычисления в VBA

Существует возможность упростить некоторые вычислительные процедуры в Excel, а также добавить в Word отсутствующие в нем функции. К сожалению, большинство учебных пособий по VBA в Office 97 предназначены для изучения нематематических функциональных возможностей пакетов, например форматирования текста или вывода графических изображений. Отсутствие необходимой документации ставит в затруднительное положение тех, кто хочет использовать макросы для решения своих математических задач, поскольку обработка числовых значений в ячейке таблицы Word происходит иначе, чем в ячейке электронной таблицы Excel. Потратив некоторое время, вы, конечно же, найдете необходимое описание синтаксиса математических функций в справочной системе Word или Excel. Кроме того, можно воспользоваться руководством Microsoft Office 97: Visual Basic Programmer's Guide, изданным корпорацией Microsoft (русская версия была выпущена «Русской Редакцией»), где рассматриваются практически все вопросы на данную тему.

Здесь мы приводим два примера, которые в явном виде демонстрируют разницу при работе в Word и Excel. Они вычисляют кубическую сумму значений первых восьми ячеек столбца 1 и помещают результат в девятую ячейку столбца 1:

Для Word VBA:

Sub ColumnMath()
     Dim x As Long, i As Long
     Dim myTable As Table
     Dim myStr As String
     x = 0
     Set myTable = _
          ActiveDocument.Tables(1)
     For i = 1 To 8
          x = x + myTable.Cell _
               (i, 1).Range.Calculate ^ 3
     Next I
     myStr = Str(x)
     myTable.Cell(9, 1).Range. _
          InsertAfter (myStr)
End Sub

Для Excel VBA:

Sub ColumnMath()
     Dim x As Long, i As Long
     Sheets(“Sheet1”).Activate x = 0
     For i = 1 To 8
          x = x + Cells(i, 1).Value ^ 3
     Next i
     Range(“a9”).Value = x
End Sub

Данные вычисления могут быть проведены целиком в рамках функциональных возможностей Excel, однако для этого потребуется создать дополнительный столбец. А вот как выполнить подобные действия внутри Word? Попытаемся разобраться в этом. Напомним, что Word VBA имеет неинтуитивный синтаксис для обработки ячеек. Чтобы прочитать значение ячейки, следует использовать ключевое слово Calculate. А чтобы записать какую-либо величину в ячейку, надо вначале преобразовать ее в строковую переменную, а затем применить абсолютно неочевидное ключевое слово InsertAfter. Нумерация таблиц в документе Word осуществляется последовательно, поэтому в первом примере мы имеем дело с первой таблицей документа. Каждая таблица рабочей книги Excel представляет собой одну большую таблицу, поэтому во втором примере мы работаем с первой таблицей рабочей книги. Кроме того, во втором примере Range(“a9”) можно заменить на Cells(9, 1) или на один из других вариантов, требующих использования ключевого слова Range.

В начало

В начало

Совет 288. Как вычислить интервал между двумя датами, измеряемый в разных единицах

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

Отгадайте такую загадку. Заданы две даты в виде переменных DateStart и DateFinish. Чтобы определить временной интервал между ними, мы написали такую процедуру:

Print “Интервал в годах = “; DateDiff(“yyyy”, DateStart, DateFinish)
Print “Интервал в месяцах= “; DateDiff(“m”, DateStart, DateFinish)
Print “Интервал в днях = “; DateDiff(“d”, DateStart, DateFinish)
Print “Интервал в часах = “ ; DateDiff(“h”, DateStart, DateFinish)
Print “Интервал в минутах = “; DateDiff(“n”, DateStart, DateFinish)
Print “Интервал в секундах= “ ; DateDiff(“s”, DateStart, DateFinish)

И получили такой парадоксальный результат:

Интервал в годах = 1
Интервал в месяцах = 1
Интервал в днях = 1
Интервал в часах = 1
Интервал в минутах = 1
Интервал в секундах = 1

ВОПРОС. Почему так произошло и о каких датах шла речь?

ОТВЕТ. Дело в том, что функция DateDiff определяет временной интервал элементарно — в соответствии с заданным первым параметром просто отбрасывает значения даты «после этой точки». То есть если вы задали «день», то отбрасываются часы (0 часов), если месяц — дни (первое число месяца). В соответствии с этим алгоритмом получается, что между 31 мая 2000-го и 1 июня 2000-го в единицах «месяц» разница — один месяц (что в определенном смысле совершенно верно).

В нашем же примере исходные значения даты были равны

DateS = “31.12.2000 23:59:59”
DateF = “01.01.2001”

Изменение показателя текущего момента привело к изменению минут, часов, суток, месяца и года (и даже века и тысячелетия). Очевидно, что самое точное определение интервала дается в данном случае в секундах (этой точности вполне достаточно для решения большинства бытовых и деловых проблем). Но как интерпретировать величину типа 12 345 678 сек? Конечно, желательно получить информацию в более привычных единицах — месяцах, днях, минутах.

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

Public Sub DateIntervals(ByVal DateS _
      As Date, ByVal DateF As Date, _
      ParamArray Prams())
      '
      If UBound(Prams) < 0 Then Exit Sub
      '
      Dim i As Long, itr As String
'
' Если не задан день, то считаем его “сегодняшним”
      If DateValue(DateS) = 0 Then _
                DateS = DateS + DateValue(Now)
If DateValue(DateF) = 0 Then _
               DateF = DateF + DateValue(Now)
'
For i = 0 To IIf(UBound(Prams) > 5, 5, UBound(Prams))
If Not IsMissing(Prams(i)) Then
If i = 0 Then
itr = “yyyy”
Else
           itr = Mid$(“mdhns”, i, 1)
End If
                Prams(i) = DateDiff(itr, DateS, DateF)
If DateAdd(itr, Prams(i), DateS) <= DateF Then _
                Prams(i) = Prams(i) - 1
                DateS = DateAdd(itr, Prams(i), DateS)
           End If
Next i
End Sub

Подпрограмма DateIntervals возвращает наибольший полный интервал указанного вами типа (год, месяц, день, час, минута, секунда) между двумя датами. Например, чтобы получить интервал времени в часах и минутах между 09:00 и 17:15, передайте в подпрограмму эти две даты, а также две переменные, задающие размерность интервала. Используйте запятые, чтобы пропустить более крупные ненужные интервалы:

Dim Hours As Variant, Minutes As Variant
Call DateIntervals(Now, “23.05.2000”, _
               , , , Hours, Minutes)
MsgBox “Часов = “ & Hours & _
          “, Минут = “ & Minutes

Подпрограмма вернет число “Часов = 8, Минут = 15”.

Однако здесь следует обратить внимание на такой любопытный момент. Если вы выполните такое обращение к функции:

Call DateIntervals(“28.02.2000”, “01.03.2001”, Years, , Days)
MsgBox Years & “ “ & Days
Call DateIntervals(“29.02.2000”, “01.03.2001”, Years, , Days)
MsgBox Years & “ “ & Days

то получите для разных начальных дат один и тот же результат — 1 год и 1 день. Казалось бы, в подпрограмме есть ошибка, но это не так. Данный парадокс объясняется неопределенностью интервала в один год — он может быть 365 и 366 дней (так же, как и в один месяц). Соответственно в первом случае «год» является високосным (366 дней), а во втором — обычным (365 дней). Чтобы представить эту ситуацию, вообразите, что ваш знакомый говорит 31 января: «Позвони мне ровно через месяц» (или 29 февраля 2000 года — «ровно через год»). Когда же будет эта точная дата намеченного звонка?

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

В процедуре DateIntervals хотелось бы обратить внимание еще на три используемые нами конструкции:

1. Для передачи возвращаемых параметров мы используем массив Param() с ключевым словом ParamArray. Такая конструкция применима только в конце списка аргументов подпрограммы и указывает, что данный аргумент является массивом типа Optional элементов типа Variant. С его помощью можно задать произвольное число аргументов. Кроме того, ParamArray нельзя использовать вместе с ключевыми словами ByVal, ByRef, или Optional.

В принципе, можно было бы просто зарезервировать в вызывающей подпрограмме массив Param (0 To 5) и использовать непосредственно его. Но в данном случае подпрограмма выполнила бы расчет для всех элементов этого массива. Применение ParamArray позволяет нам пропускать «ненужные» параметры. Например, в нашем обращении мы получим результат в полных годах и весь остаток интервала — в секундах:

Call DateIntervals(“28.02.2000”, “01.03.2001”, Years, , , , ,Secs)

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

MyVal = IIf(expr, truepart, falsepart)

которая равнозначна такому варианту:

If expr Then
MyVal = truepart
Else
MyVal = falsepart
End If

3. При коррекции временного интервала мы использовали такую конструкцию:

If DateAdd(itr, Prams(i), DateS) <= DateF Then _
                Prams(i) = Prams(i) - 1

Любители хитроумных преобразований данных могли бы предложить более «изящный» вариант:

Prams(i) = Prams(i) + (DateAdd(itr, Prams(i), DateS) > DateF)

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

В начало

В начало

Совет 289. Как правильно прочитать имя каталога

Проблема заключается в том, что в некоторых случаях имя каталога содержит символ обратной косой черты в конце, а иногда — нет. Пример такой путаницы — обращение к свойству Path объекта App. Если приложение находится в подкаталоге, то такой черты в конце не будет (например,

C:\dir_x\dir_y\dir_z), но если оно располагается в корневом каталоге диска, то черта появится (например, С:\). Это нужно, в частности, учитывать при формировании полного имени файла, то есть вместо:

strFullFileName = App.Path & strFileName

нужно применять, например, такую конструкцию:

strFullFileName = App.Path & IIf(Right$( _
     App.Path, 1) = “\”, ““, “\”) & strFileName

Если вам лень каждый раз писать этот код, можете создать функцию AppPath:

Public Function AppPath() As String
          ' Замените App.Path на
          ' AppPath во всем тексте программы
AppPath = App.Path & _
IIf(Right$(     App.Path, 1) = “\”, ““, “\”)
End Function

Обратите внимание, что если вы автоматически добавляете обратную косую черту ко всем вызовам App.Path, а путь окажется корневым каталогом, то вы получите совершенно неинформативное сообщение об ошибке: Run-time error '5': Invalid procedure call.

В начало

В начало

Совет 290. Как заполнить поля электронного сообщения

ShellExecute является одной из наиболее гибких функций в Win32 API. С ее помощью можно передавать любое имя файла, а если расширение файла связано с какой-либо из программ, зарегистрированных на машине пользователя, то запускается соответствующее приложение, которое выводит или проигрывает указанный файл.

Здесь мы покажем, как использовать функцию ShellExecute для отправки электронных сообщений. Вы сможете задавать не только адрес получателя, но и списки получателей (CC и BCC), тему и текст сообщения, а также вставлять файл или его часть в отправляемое сообщение. Для этого необходимо создать строковую переменную, добавить список основных адресов (отделенных друг от друга точкой с запятой) и знак вопроса:

для копий CC (Копия): &CC= (список получателей)
для невидимых (слепых) копий: &BCC= (список получателей)
для темы сообщения: &Subject= (тема сообщения)
для текста сообщения: &Body= (текст сообщения)
для присоединения файла: &Attach= (путь к файлу, заключенный в кавычки)

Продемонстрируем это на примере. Создайте новый VB-проект, добавьте форму и разместите на ней шесть текстовых полей и одну командную кнопку cmdSendIt (см. рис. 1 — tip290.bmp). Напишите такой код в разделе Declarations:

Private 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
Private Const SW_SHOWNORMAL = 1

Затем введите следующий код для события Click командной кнопки:

Private Sub cmdSendIt_Click()
     Dim sText As String
     Dim sAddedText As String
     If Len(txtMainAddresses) Then
          sText = txtMainAddresses
     End If
     If Len(txtCC) Then
          sAddedText = sAddedText & “&CC=“ & txtCC
     End If
     If Len(txtBCC) Then
          sAddedText = sAddedText & “&BCC=“ & txtBCC
     End If
     If Len(txtSubject) Then
          sAddedText = sAddedText &      “&Subject=“ & txtSubject
     End If
     If Len(txtBody) Then
          sAddedText = sAddedText & “&Body=“ & txtBody
     End If
     If Len(txtAttachementFileLocation) Then
          sAddedText = sAddedText & “&Attach=“ & Chr(34) & _
               txtAttachementFileLocation & Chr(34)
     End If
     sText = “mailto:” & sText
     If Len(sAddedText) <> 0 Then
          Mid$(sAddedText, 1, 1) = “?”
     End If
     sText = sText & sAddedText
     If Len(sText) Then
          Call ShellExecute(Me.hWnd, “open”, _
               sText, vbNullString, vbNullString, _
               SW_SHOWNORMAL)
     End If
End Sub

Здесь следует обратить внимание на два момента:

  1. Между знаками «амперсанд» (&) и «тэг» или знаками «тэг» и «равно» не нужно ставить пробелы.
  2. Из-за отсутствия возможностей форматирования текст сообщения будет состоять из одного параграфа.

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

Примечание. Полная функциональность полей электронного сообщения может быть достигнута только в почтовых клиентах, совместимых с Microsoft Exchange. С другими почтовыми клиентами некоторые или даже все эти поля могут не работать.

В начало

В начало

Совет 291. Как открыть VB-файл в Notepad или в любом другом текстовом редакторе

Предположим, вы хотите сделать так, чтобы с помощью щелчка правой кнопки мыши можно было открыть VB-файл в редакторе Notepad и скопировать фрагмент кода для другого приложения, например для того, над которым вы сейчас работаете. Попробуйте сделать следующее.

Создайте текстовый файл с именем FormEdit.reg, введите в него такой код, сохраните, а затем закройте его:

REGEDIT4
[HKEY_CLASSES_ROOT\VisualBasic.Form\ _
     shell\edit]
@=“&Edit”
[HKEY_CLASSES_ROOT\VisualBasic.Form\ _
          shell\edit\command]
@=“C:\Windows\Notepad.exe %1”

Дважды щелкните мышью на этом файле, и его содержимое автоматически загрузимся в Системный Реестр. Теперь щелкните правой кнопкой мыши на любом FRM-файле, и в появившемся «быстром» меню вы увидите команду Edit. Выполните ту же самую операцию для других текстовых VB-файлов – VisualBasic.ClassModule, VisualBasic.Module и VisualBasic.Project, а затем с помощью программы Regedit.exe в Windows проверьте полученные результаты.

Если вместо Notepad вы хотите использовать Word или любой другой текстовый редактор, напишите примерно такую командную строку (для Word):

@=“c:\\Program Files\\Microsoft Office\\ _
Office\\Winword.exe %1”

Здесь следует быть особенно внимательными — используйте две обратные косые черты и, кроме того, не ошибитесь, указывая полный путь к файлу Winword.exe.

Хотя VB4, VB5 и VB6 имеют различные точки входа в Реестре, подобную методику можно применять для любого текстового VB-файла, включая BAS, CLS, FRM и VBP. Она не подходит для FRX- или других двоичных файлов, но с ее помощью можно реализовать просмотр HTML-файлов. Указав путь к используемому по умолчанию браузеру, можно, щелкнув правой кнопкой мыши на любом HTML-файле, редактировать его как простой текст. Кроме того, можно настроить браузер на просмотр GIF-файлов, чтобы увидеть, что они собой представляют. Но самое главное — всякий раз, работая с Реестром, будьте крайне осторожны.

В начало

В начало

Совет 292. Как ускорить операцию деления чисел с плавающей запятой

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

X / Y

выполните:

X * (1 / Y)

Чтобы увидеть, как это работает на практике, создайте новый проект в VB и введите следующий код в событие Form_Click:

Option Explicit
Private Declare Function GetTickCount _
     Lib “kernel32” () As Long
Const NORMAL As Double = 1453
Const RECIPROCAL As Double = 1 / NORMAL
Const TOTAL_COUNT As Long = 10000000
Private Sub Form_Click()
     Dim dblRes As Double
     Dim lngC As Long
     Dim lngStart As Long
     '
     On Error GoTo Error_Normal
     '
     lngStart = GetTickCount
     For lngC = 1 To TOTAL_COUNT
          dblRes = Rnd / NORMAL
     Next lngC
     MsgBox “Обычное время: “ & _
     GetTickCount - lngStart
     lngStart = GetTickCount
     For lngC = 1 To TOTAL_COUNT
          dblRes = Rnd * RECIPROCAL
     Next lngC
     MsgBox “Время для обратной величины: “ _
          & GetTickCount - lngStart
Exit Sub
Error_Normal:
     MsgBox Err.Number & “ - “ & Err.Description
End Sub

Вы обнаружите, что скорость выполнения вычислений увеличится примерно на 15% при использовании метода умножения на обратную величину. Тем не менее будьте осторожны при округлении чисел — так, 3 / 3 = 1, но 3 * (0,333333...) = 0,999999....

В начало

В начало

Совет 293. Как ускорить выполнение операций ввода-вывода для файлов

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

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

Public Function ReadFile(FileName _
          As String) As String
     Dim FileNumber As Integer
     FileNumber = FreeFile
     Open FileName For Input As #FileNumber
     ReadFile = Input(LOF(FileNumber), FileNumber)
     Close #FileNumber
End Function
Public Sub WriteFile(FileName As _
          String, Contents As String)
     Dim FileNumber As Integer
     FileNumber = FreeFile
     Open FileName For Output As #FileNumber
     Print #FileNumber, Contents;
     Close #FileNumber
End Sub

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

Call WriteFile(“c:\b.txt”,      ReadFile(“c:\a.txt”))

Для копирования файлов произвольного формата следует использовать тип файлов Binary. Тогда операции чтения-записи будут выглядеть так:

Open FileInput$ For Binary As #FileInp
FileString$ = Space$(LOF(FileInp))
Get #FileInp,, FileString$
ReadFile = FileString$
...
Open FileOutput$ For Bi As #FileOut
Put #FileOut,, FileString$

Но в этом случае нужно сначала сделать проверку — может быть, выходной файл уже существует (если это так, то в него просто перепишутся первые Len(FileString$) символов).

Однако следует иметь в виду, что для точного чтения содержимого произвольных файлов использование строковой переменной не очень хорошо подходит. Дело в том, что в этом случае при чтении производится преобразование байтов из однобайтовой ANSII-кодировки в двухбайтовую Unicode (подробнее об этом см. КомпьютерПресс 10’99, CD-ROM). При записи производится обратное преобразование.

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

ReDim ArrByte (1 To LOF(FileNumber))
Get #FileInp,, ArrByte
...
Get #FileOut,, ArrByte
В начало

В начало

Совет 294. Как создать дополнение, закрывающее все окна проекта

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

Создайте новый проект типа AddIn (значок Addin в окне New Project). Вы получите шаблон MyAddIn, в котором содержится некоторый программный код для построения дополнения. В код формы frmAddIn введите следующее:

Private Sub Form_Load()
     Dim w As Window
     For Each w In VBInstance.Windows
          ' закрывает все видимые
          ' окна кода и форм
          If (w.Type = vbext_wt_CodeWindow _
               Or w.Type = vbext_wt_Designer) _
               And w.Visible Then w.Close
          End If
     Next
     Unload Me
End Sub

В окне Object Browser щелкните правой кнопкой мыши на проекте MyAddIn. В появившемся «быстром меню» выберите команду Properties и измените имя и описание дополнения. Затем в коде шаблона замените My Add-In на то имя, которое бы вы хотели видеть в меню Add-Ins в среде разработки VBE. Если вы работаете в VB6, то помимо этого необходимо еще поменять имя дополнения в конструкторе AddInDesigner. Создайте DLL-библиотеку (команда File|Make MyAddIn.dll), и ваше дополнение должно появиться в списке в Add-In Manager. Добавьте его к командам меню Add-Ins, а затем запустите — все открытые окна проекта закроются в одно мгновение!

В начало

В начало

Совет 295. Используйте ключевое слово WithEvents для связи форм MDI и MDIChild

Здесь приводится изящный способ передачи событий, таких как щелчки на панели инструментов или выделение команд меню, из родительской MDI-формы в активную дочернюю MDIChild-форму в многодокументном приложении. Предположим, что MDI-форма содержит элемент управления Toolbar с именем tbrMain, для которого введите следующий код:

Option Explicit
Event ButtonClick(strKey As String)
Private Sub tbrMain_ButtonClick(ByVal _
     Button As MSComctlLib.Button)
     RaiseEvent ButtonClick(Button.Key)
End Sub

Затем напишите такой код для каждой MDIChild-формы, которая должна получить событие ButtonClick:

Private WithEvents m_mdiParent As mdiParent
Private Sub tbrMain_ButtonClick(ByVal _
     Button As MSComctlLib.Button)
     RaiseEvent ButtonClick(Button.Key)
End Sub
Private Sub Form_Acitivate()
     Set m_mdiParent = mdiParent
End Sub
Private Sub Form_Deactivate()
     Set m_mdiParent = Nothing
End Sub
Private Sub m_mdiParent_ButtonClick _
     (strKey As String)
     ' Пример кода, в котором значения
     ' Button.Key соответствуют кнопкам
     ' New, Change, Delete и Save
     Select Case strKey
          Case “New”
               PerformNewAction
          Case “Change”
               PerformChangeAction
          Case “Delete”
               PerformDeleteAction
          Case “Save”
               PerformSaveAction
     End Select
End Sub

Использование этой подпрограммы аналогично объявлению элемента управления с именем m_mdiParent, у которого есть событие ButtonClick. Используйте события Activate и Deactivate, чтобы форма MDIChild являлась единственной, которая бы получала событие ButtonClick.

В начало

В начало

Совет 296. Помните об операторе Option Base

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

ReDim MyArray (LowBoundary To UpBoundary)

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

ReDim MyArray (UpBoundary)
Dim MyArrayStat(100)

Но чему же равна нижняя граница и сколько элементов на самом деле содержится в массиве?

В языках программирования используется два варианта соглашений: например, в Фортране — нумерация традиционно начинается с единицы, а в Бейсике — с нуля.

Во всех версиях Microsoft Basic (Quick for DOS, Visual for Windows) программист может управлять установкой значения нижней границы по умолчанию с помощью оператора Option Base. Он помещается в секции Declarations в начале программного модуля (форма, модуль кода, модуль класса и пр.) и его действие распространяется на все объявления массивов внутри модуля.

Таким образом, значение нижней границы для приведенного выше примера Dim MyArrayStat(100) будет следующим:

= 0 — если оператор Option Base отсутствует;

= 0 — задан Option Base 0;

= 1 — задан Option Base 1.

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

Совет: Если для программы принципиально важно наличие или отсутствие нулевого индекса, то указывайте обе границы индекса в явном виде.

Еще одно замечание. В VB 5/6 имеется возможность автоматического создания и заполнения байтового массива при перезаписи в него строковой переменной:

Dim arrByte () As Byte
Source$ = “Строковая переменная”
arrByte = Source$

В этом случае всегда создается массив размерностью от 0 до (LenB(Source$)-1) независимо от наличия/отсутствия оператора Option Base.

В начало

В начало

Совет 297. Экспорт таблиц в виде текстовых файлов

Здесь приведена простая процедура, которая выводит в виде текстового файла записи из таблицы базы данных или из таблицы, сформированной в результате SQL-запроса. Эта информация потом может быть считана любым текстовым редактором или программой электронных таблиц. В этом примере Db — это глобальная переменная объекта, которая должна быть до обращения к процедуре определена как база данных. sSource — имя таблицы или SQL-запрос. Кроме имени выходного файла пользователь должен также задать код разделителя полей записи в текстовом файле:

 

Public Function TableToSpreadsheetMy(sSource As String, _
sFile As String, sSeparator As String) As Boolean
' Включение обработки ошибок
On Error GoTo TableToSpreadsheet_Err
'
' Синтаксис обращения:
' If TableToSpreadsheet(“SELECT * FROM _
' Authors”, “C:\Temp\Authors.csv”, Chr$(9)) = True _
' Then....
'
Dim rsTemp As Recordset
Dim sHeader As String
Dim sRow As String
Dim i As Integer, nFile As Integer
' Формирование набора данных
' Глобальная объект-переменная Db должна
' быть открыта ранее как база данных
Set rsTemp = Db.OpenRecordset(sSource)
With rsTemp
TableToSpreadsheet = False
' Есть ли записи в наборе?
If .RecordCount > 0 Then
' Создание выходного файла
nFile = FreeFile
Open sFile For Output As #nFile
' Вывод имен полей таблицы
sHeader = .Fields(0).Name
If .Field.Count > 0 Then
For i = 1 To .Fields.Count - 1
sHeader = sHeader & sSeparator & .Fields(i).Name
Next i
End If
Print #nFile, sHeader
' Вывод содержимого полей таблицы
.MoveFirst
Do Until .EOF
sRow = .Fields(0).Value
If .Field.Count > 0 Then
For i = 1 To .Fields.Count - 1
sRow = sRow & sSeparator & .Fields(i).Value
Next i
End If
Print #nFile, sRow
.MoveNext
Loop
Close #nFile ' Target file is complete
TableToSpreadsheet = True
End If
.Close
End With
Set rsTemp = Nothing ' закрываем временный объект
Exit Function
TableToSpreadsheet_Err:
LogIt “TableToSpreadsheet : “ & Err.Description
' Запись информации об ошибке
Resume Next
End Function

КомпьютерПресс 7'20000