Visual2000 · Архив статей А.Колесова & О.Павловой
Андрей Колесов, Ольга Павлова
© Андрей Колесов, Ольга Павлова, 2000
ShellExecute является одной из наиболее гибких функций в Win32 API. С ее помощью можно передавать любое имя файла, а если расширение файла связано с какой-либо из программ, зарегистрированных на машине пользователя, то запускается соответствующее приложение, которое выводит или проигрывает указанный файл.
Здесь мы покажем, как использовать функцию ShellExecute для отправки электронных сообщений. Вы сможете задавать не только адрес получателя, но и списки получателей (CC и BCC), тему и текст сообщения, а также вставлять файл или его часть в отправляемое сообщение. Для этого необходимо создать строковую переменную, добавить список основных адресов (отделенных друг от друга точкой с запятой) и знак вопроса:
для копий CC (Копия): &CC= (список получателей) для невидимых (слепых) копий: &BCC= (список получателей) для темы сообщения: &Subject= (тема сообщения) для текста сообщения: &Body= (текст сообщения) для присоединения файла: &Attach= (путь к файлу, заключенный в кавычки)
Продемонстрируем это на примере. Создайте новый VB-проект, добавьте форму и разместите на ней шесть текстовых полей и одну командную кнопку cmdSendIt (см. рис. 290).
Рис. 290
Напишите такой код в разделе Declarations:
Private Declare Function ShellExecute Lib _ "shell32.dll" Alias "ShellExecute" _ (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 If Len(txtBCC) Then sAddedText = sAddedText & "&BCC=" & txtBCC If Len(txtSubject) Then _ sAddedText = sAddedText & "&Subject=" & txtSubject If Len(txtBody) Then _ sAddedText = sAddedText & "&Body=" & txtBody If Len(txtAttachementFileLocation) Then _ sAddedText = sAddedText & "&Attach=" & Chr(34) & _ txtAttachementFileLocation & Chr(34) sText = "mailto:" & sText If Len(sAddedText) <> 0 Then Mid$(sAddedText, 1, 1) = "?" sText = sText & sAddedText If Len(sText) Then _ Call ShellExecute(Me.hWnd, "open", sText, _ vbNullString, vbNullString, SW_SHOWNORMAL) End Sub
Здесь следует обратить внимание на два момента:
Тем не менее, используя предложенный здесь способ, вы сможете создавать работающие почтовые аплеты всего за несколько секунд.
Примечание. Полная функциональность полей электронного сообщения может быть достигнута только в почтовых клиентах, совместимых с Microsoft Exchange. С другими почтовыми клиентами некоторые или даже все эти поля могут не работать.
Предположим, вы хотите сделать так, чтобы с помощью щелчка правой кнопки мыши можно было открыть 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-файлов, чтобы увидеть, что они собой представляют. Но самое главное — всякий раз, работая с Реестром, будьте крайне осторожны.
Если вам приходится выполнять много арифметических операций деления чисел с плавающей запятой в VB, можете попробовать оптимизировать эти действия, осуществляя умножение на обратную величину. Например, вместо операции:
X / Y
Выполните:
X * (1 / Y)
Чтобы увидеть, как это работает на практике, создайте новый проект в VB и введите следующий код в событие Form_Click:
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....
Довольно часто в программах встречаются ситуации, когда нужно просто целиком скопировать содержимое файла в оперативную память. Например, это необходимо для выполнения копирования файлов или операций быстрого поиска контекста.
Для текстовых файлов можно предложить такие две процедуры чтения и записи файлов:
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 (подробнее об этом см. КомпьютерПресс N 10'99, CD-ROM). При записи производится обратное преобразование.
Поэтому для создания точной копии содержимого файлов следует использовать байтовый динамический массив:
ReDim ArrByte (1 To LOF(FileNumber)) Get #FileInp,, ArrByte ... Get #FileOut,, ArrByte
При создании 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, а затем запустите — все открытые окна проекта закроются в одно мгновение!
Здесь приводится изящный способ передачи событий, таких как щелчки на панели инструментов или выделение команд меню, из родительской MDI-формы в активную дочернюю MDIChild-форму в многодокументном приложении. Предположим, что MDI-форма содержит элемент управления Toolbar с именем tbrMain, для которого введите следующий код:
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.
В общем случае при резервировании массива нужно указывать в явном виде его нижнюю и верхнюю границу индекса:
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) будет следующим:
Все это нужно иметь в виду, если вы вставляете фрагмент программного кода в какие-либо модули (например, из своей библиотеки повторно используемых процедур), — значение нижней границы будет зависеть от наличия в данном модуле оператора Option Base.
Совет: Если для программы принципиально важно наличие или отсутствие нулевого индекса, то указывайте обе границы индекса в явном виде.
Еще одно замечание. В VB 5/6 имеется возможность автоматического создания и заполнения байтового массива при перезаписи в него строковой переменной:
Dim arrByte () As Byte Source$ = "Строковая переменная" arrByte = Source$
В этом случае всегда создается массив размерностью от 0 до (LenB(Source$)-1) независимо от наличия/отсутствия оператора Option Base.
Здесь приведена простая процедура, которая выводит в виде текстового файла записи из таблицы базы данных или из таблицы, сформированной в результате 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