Для чтения данных из файлов последовательного доступа используются операторы:
Input# ѕ данные в файле записаны оператором Write#LineInput# ѕ данные в файле записаны оператором Print#.
Оператор Input# вызывается так:
Input #номер-файла, список-переменных Параметр номер-файла - номер открытого файла,список-переменных - одно или несколько разделенных запятыми имен переменных, в которые будут считываться данные из файла. Переменные могут быть разных типов, естественное требование их тип должен быть согласован с типом записи в момент ее создания. Переменные не должны быть массивами или объектами, но могут быть элементами массивов или полями пользовательских записей.
При чтении автоматически выполняются преобразования данных, обратные тем, что использовались при записи оператором Write#. В частности, кавычки вокруг строк, разделяющие запятые и пустые строки, игнорируются. Слово #NULL# дает значение Null, #TRUE# и #FALSE# переводятся в булевы значения True и False, универсальные даты вида #yyyy-mm-dd hh:mm:ss# переводятся в формат соответствующих переменных типа дата/время, #ERROR номер-ошибки# при чтении передает в переменную типа Variant номер ошибки. Если для числовой переменной соответствующие по порядку в файле данные будут не числовыми, ей будет присвоено значение 0.
По достижении конца файла, следующая попытка чтения из него приведет к ошибке. Во избежание этого для проверки на конец файла используется функцию:
EOF(номер-файла)
Она возвращает булево значение True, когда при чтении достигается конец файла, открытого в режиме последовательного доступа Input или произвольного доступа Random.
Приведем процедуру чтения записей ранее созданного файла "readme.txt". Заметьте, мы поступаем корректно, читаемые переменные имеют тип, согласованный с типом читаемой записи.
Пример 14.4.
(html, txt)
В результате будет напечатано:
Первая строка файла Зона 1 Зона 2 Привет, старик! Мама мыла раму мылом. False 14.06.99 Null 3,1416 3,14 03,142 6 Error 2000
Обратите внимание, процедура завершается циклом, типичным при чтении последовательных файлов.
В данном случае он не работал ни разу, поскольку все записи файла уже были прочитаны к началу выполнения цикла.
Для построчного ввода данных из файлов с последовательным доступом, созданных оператором Print, вызывается оператор Line Input#:
Line Input #номер-файла, переменная
Здесь номер-файла имеет тот же смысл, что и для оператора Input, а переменная - имя строковой переменной или переменной типа Variant, в которую будет прочитана очередная строка файла. Оператор Line Input# считывает данные посимвольно, пока не обнаружит признак конца строки - символ возврата каретки (Chr(13)) или пару "возврат каретки - перевод строки" (Chr(13) + Chr(10)). Эти признаки в переменную не записываются. Никаких преобразований данных при построчном вводе не производится. Мы уже приводили пример чтения файла "read.me", созданного оператором Print#.
Следует заметить, что любой файл может быть открыт для чтения и прочитан оператором Line Input#. Другое дело, что, чаще всего, это не даст желаемого результата. Если, например, прочитать файл "readme.txt" с использованием оператора Line Input#, то будут получены следующие результаты:
"Первая строка файла" "Зона 1","Зона 2" "Привет,","старик!" "Мама ","мыла ","раму мылом. " #FALSE# #1999-06-14# #NULL# 3.1416,"3,14","03,142" 6 #ERROR 2000#
Несмотря на успех чтения, результаты не приемлемы, поскольку не выполняется нужного редактирования. Сохраняются кавычки, запятые, ограничители, в одной строке содержатся данные разных типов. Поэтому оператор Line Input # следует применять, как правило, для чтения строк из файлов, записанных с помощью оператора Print#.
Оператор Line Input # позволяет читать файл строка за строкой. Наряду с этим в языке VBA есть возможность чтения из файла произвольного числа символов или произвольного числа байтов. Для чтения символов Input или Binary файлов используется функция Input, имеющая следующий синтаксис:
Input(размер, [#]номер-файла)
где размер - количество считываемых символов, номер-файла - номер открытого файла (последовательного или бинарного). Данные, считываемые этой функцией, обычно должны быть записаны в файл операторами Print# или Put.
Еще один вариант этой функции:
InputB(размер, [#]номер-файла)
позволяет считывать из файла заданное количество байтов (не обязательно соответствующих символам). Заметьте, эта функция читает все символы, хранимые в файле, в том числе запятые, ограничители, символы конца строки и другие.
Бинарные файлы пишутся и читаются порциями, состоящими из произвольного числа байтов. Доступ возможен к любому байту этого файла в произвольном порядке. Никакого редактирования при операциях с этим файлом не производится и вся ответственность за сохранением структуры информации ложится на программиста. Операции над этим файлом выполняются операторами Get и Put, но можно читать этот файл побайтно, используя и уже упоминавшуюся функцию Input. Мы не будем строить специальный пример для работы с этим файлом, а ограничимся примером чтения ранее созданного текстового файла "readme.txt", который откроем, как Binary файл. В этом примере мы прочитаем последовательно этот файл порциями по 20 байтов. Заодно покажем, как организуется чтение "хвоста" файла ѕ его последней порции. Возможно, с содержательной точки зрения пример не очень хорош, поскольку будет нарушена реальная структура читаемых данных, нов этом то и вся суть дела при работе с такими файлами. Программист сам должен восстановить структуру данных, чем мы, в данном случае, заниматься не будем. Но вот сам пример:
Public Sub Test3() 'Чтение Binary файла Dim MyLocation As Long, MyLoc As Long, MyLine As String
Open Path & "readme.txt" For Binary As #1
MyLocation = 0 Do While MyLocation + 20 < LOF(1) ' Читаем допустимую порцию MyLine = Input(20, #1) MyLocation = Loc(1): MyLoc = Seek(1) ' 2 способа определения текущей позиции Debug.Print MyLine; Tab; MyLocation; Tab; MyLoc Loop 'Читаем последнюю порцию MyLoc = LOF(1) - MyLocation If MyLoc > 0 Then MyLine = Input(MyLoc, #1) MyLocation = Loc(1): MyLoc = Seek(1) Debug.Print MyLine, MyLocation, MyLoc, LOF(1) End If Close #1 End Sub
Перед комментариями приведем результаты работы:
"Первая строка файла 20 21 " "Зона 1","Зона 2" 40 41
"Привет,","старик! 60 61 " "Мама ","мыла "," 80 81 раму мылом. " #FALS 100 101 E# #1999-06-14# #N 120 121 ULL# 3.1416,"3,14", 140 141 "03,142" 6 #ERROR 160 161 2000# 167 168 167
Последовательный | Print#, Write# | Input#, Line Input# |
Произвольный | Put | Get |
Бинарный | Put | Get |
Файлы перечисленных видов открываются и могут создаваться оператором Open, который вызывается так:
Open имя-файла For режим [Access доступ] [блокировка] As [#]номерФайла [Len=длина-записи] Параметр имя-файла - строковое выражение, задающее имя открываемого (создаваемого) файла, в него может входить также путь ѕ имя диска и имена каталогов (папок) на пути к файлу.Параметр режим - ключевое слово, которое для файлов последовательного доступа может принимать одно из значений: Input (ввод), Output (вывод), Append (присоединение). Для файлов с произвольным доступом в качестве режима нужно указать Random, а для бинарных файлов - Binary.Параметр доступ необязателен, он ограничивает набор операций, которые разрешено выполнять над открываемым файлом; возможные значения: Read(только чтение), Write (только запись), Read Write (и то и другое).Параметр блокировка позволяет ограничить набор операций над открываемым файлом, выполняемых другими процессами; возможные значения: Shared (разделяемый, ѕ другим процессам разрешается работать с файлом без всяких ограничений), Lock Read(запретить чтение), Lock Write (запретить запись), Lock Read Write (запретить чтение и запись). Здесь требуется некоторые пояснения. С одним и тем же физическим файлом одновременно могут работать разные приложения. Возможна и ситуация, когда одно приложение одновременно работает с несколькими экземплярами файла, присваивая каждому экземпляру свой уникальный номер. Параметр блокировка позволяет регулировать отношения между клиентами, получающими доступ к файлу.Параметр номерФайла - целое число в интервале от 1 до 511, идентифицирующее файл для других операций. Для каждой операции открытия файла номер-файла должен отличаться от номеров всех открытых в текущий момент файлов. Узнать очередной свободный номер позволяет функция FreeFile.Параметр длина-записи - число не более 32767 - обязателен для файлов произвольного доступа и задает для них длину одной записи файла в байтах. Для последовательных файлов он необязателен (для них он задает размер буфера, создаваемого при открытии), для бинарных файлов он игнорируется.
Public Sub OpenTwoFiles() Dim attr As Integer, Num As Integer Dim MyFriend As Person ' объявление переменной ' Открываем файл с произвольным доступом для записи: Num = FreeFile Open "Friends" For Random Access Read Write As Num Len = Len(MyFriend) PrintAttr (Num) Debug.Print "Длина Записи:", Len(MyFriend) ' Открываем двоичный файл Num = FreeFile Open "pict.bmp" For Binary Access Read Lock Read As Num PrintAttr (Num) End Sub Public Sub PrintAttr(ByVal Num As Integer) 'Эта процедура по номеру файла определяет его атрибуты 'и выводит соответствующее сообщение в окно отладки Dim Msg As String, attr As Integer attr = FileAttr(Num, 1) Debug.Print "файл # ", Num Select Case attr Case 1: Msg = "Input" Case 2: Msg = "Output" Case 4: Msg = "Random" Case 8: Msg = "Append" Case 32: Msg = "Binary" Case Else: Msg = "Таких файлов не бывает" End Select Debug.Print "Открыт для ", Msg End Sub |
Пример 14.1. |
Закрыть окно |
Public Sub WritingWithPrint() Dim MyBool As Boolean, MyDate As Date Dim MyNull As Variant, MyFloat As Double, MyErr As Variant Print #1, "Первая строка файла" ' запись текстовой строки. 'Первая строка файла Print #1, "Зона 1"; Tab; "Зона 2" ' запись в двух позициях табуляции. 'Зона 1 Зона 2 Print #1, "Зона 1", "Зона 2" ' запятая разделяет как табуляции. 'Зона 1 Зона 2 Print #1, "Привет,"; "старик!" '; склеивает выражения. 'Привет,старик! Print #1, "Привет,"; "старик!" ' пробел действует аналогично. 'Привет,старик! Print #1, Spc(5); "5 пробелов " ' вставка 5 пробелов слева. ' 5 пробелов Print #1, Tab(20); "Старик,"; Tab(10); "привет!" ' печать в соответствующих столбцах. ' Старик ' привет! Print #1, "one"; " "; "two" 'разделение пробелом 'one two MyBool = False ' булева переменная Print #1, MyBool 'False MyDate = #6/14/1999# ' значение даты Print #1, MyDate ' печать в кратком формате '14.06.99 MyNull = Null ' нулевое значение Print #1, MyNull 'Null MyFloat = 3.1416 'вещественное значение ' использование функции Format: Print #1, MyFloat, Format(MyFloat, "#.00"), Format(MyFloat, "00.000") '3,1416 3,14 03,142 On Error Resume Next Err.Raise 6 MyErr = Err Print #1, MyErr MyErr = CVErr(2000) Print #1, MyErr End Sub |
Пример 14.2. |
Закрыть окно |
Public Sub WritingWithWrite() Dim MyStr As String, MyBool As Boolean, MyDate As Date Dim MyNull As Variant, MyFloat As Double, MyErr As Variant 'Открытие файла readme.txt Open Path & "readme.txt" For Output As #7 'Создание файла Write #7, "Первая строка файла" ' запись текстовой строки. Write #7, "Зона 1", "Зона 2" ' запись двух строк. Write #7, "Привет,", "старик!" 'еще две строки MyStr = "раму мылом. " Write #7, "Мама ", "мыла ", MyStr MyBool = False ' булева переменная Write #7, MyBool MyDate = #6/14/1999# ' значение даты Write #7, MyDate ' запись даты MyNull = Null ' нулевое значение Write #7, MyNull MyFloat = 3.1416 'вещественное значение ' использование функции Format: Write #7, MyFloat, Format(MyFloat, "0.00"), Format(MyFloat, "00.000") On Error Resume Next Err.Raise 6 MyErr = Err Write #7, MyErr MyErr = CVErr(2000) Write #7, MyErr End Sub |
Пример 14.3. |
Закрыть окно |
Public Sub ReadingWithInput() Dim MyStr As String, MyBool As Boolean, MyDate As Date Dim MyNull As Variant, MyFloat As Double, MyErr As Variant Dim i As Integer 'Открытие файла readme.txt Open Path & "readme.txt" For Input As #7 'Чтение файла 'Первые 8 строк For i = 1 To 8 Input #7, MyStr Debug.Print MyStr Next i 'Данные разных типов Input #7, MyBool, MyDate, MyNull, MyFloat Debug.Print MyBool, MyDate, MyNull, MyFloat 'читаем отформатированные числа Input #7, MyStr Debug.Print MyStr Input #7, MyStr Debug.Print MyStr 'дважды читаем данные типа Error Input #7, MyErr Debug.Print MyErr Input #7, MyErr Debug.Print MyErr 'чтение до конца файла Do While Not EOF(7) Input #7, MyStr Debug.Print MyStr Loop End Sub |
Пример 14.4. |
Закрыть окно |
Public Sub CreateRec(Rec As Товар) ' Создает запись типа Товар Randomize Rec.КодТовара = Int(Rnd * 9 + 1) 'Код от 1 до 9 Rec.Наименование = "Товар" & Rec.КодТовара Rec.Цена = Int(Rnd * 99 + 1) * Rec.КодТовара End Sub Public Sub PrintRec(Rec As Товар) 'Печать записи о товаре Debug.Print "Код Товара:", Rec.КодТовара, "Цена:", Rec.Цена End Sub Public Sub CreateRandomFile() Dim i As Integer, NewRec As Товар Open Path & "Товары.9" For Random Access Write As #1 Len = Len(NewRec) 'Создаем 10 записей For i = 1 To 10 Call CreateRec(NewRec) Call PrintRec(NewRec) Put #1, NewRec.КодТовара, NewRec Next i Debug.Print "Файл Товары.9 успешно создан" Debug.Print "Размер файла", LOF(1) Close #1 End Sub |
Пример 14.5. |
Закрыть окно |
Public Sub Test2() 'Эта процедура работает с файлом, записи которого ' содержат строки переменной длины, не превосхоящей максимума, 'а также данные типа Variant Dim Fam As String Dim Other As Variant 'Открытие файла произвольного доступа для чтения и записи Open Path & "Strings.var" For Random Access Read Write As #5 Len = 20 Fam = "Степанов" Put 5, 1, Fam Get 5, 1, Fam Debug.Print Fam, LOF(5) Fam = "Архангельский" Put 5, 2, Fam Get 5, 2, Fam Debug.Print Fam, LOF(5) Fam = "Куц" Put 5, 1, Fam Get 5, 1, Fam Debug.Print Fam, LOF(5) 'Запись типа Variant Other = "Петров" Put 5, 3, Other Get 5, 3, Other Debug.Print Other, LOF(5) Other = 125.25 Put 5, 4, Other Get 5, 4, Other Debug.Print Other, LOF(5) Other = 125 Put 5, 5, Other Get 5, 5, Other Debug.Print Other, LOF(5) Close #5 End Sub |
Пример 14.6. |
Закрыть окно |
До сих пор мы усиленно подчеркивали, что при работе с файлом произвольного доступа размер записи фиксирован и это предполагает хранение в таких файлов данных, размер которых фиксирован в момент их объявления. На самом деле, это не так и в таких файлах можно хранить и строки переменной длины и данные типа Variant. В этих случаях просто следует быть более осторожным, поскольку из-за сложности правил хранения таких данных повышается возможность ошибки. Вот некоторые правила, согласно которым оператор Put размещает данные в файле, а оператор Get их читает:
Данные всегда помещаются в файл, начиная с границ записей, установленных параметром Len при открытии файла. Если при этом длина данных, записываемых в файл, меньше размера записи, заданного параметром Len, оператор Put заполнит остающееся до длины записи место "случайными" данными из буфера файла, а новые данные поместит, начиная со следующей границы. При попытке записать данные, длина которых больше Len, появится сообщение об ошибке. Таким образом, параметр Len задает максимальный размер записи, внутри которого может храниться значение переменного размера.При записи строк переменной длины, переменных типа Variant, динамических массивов VBA помещает туда же дополнительную информацию для их расшифровки.При записи строки переменной длины Put помещает непосредственно перед ней 2 байта с ее длиной.Два дополнительных байта также помещаются в файл при записи переменной типа Variant. Их содержимое идентифицирует тип значения (как в функции VarType). Например, если оно равно 3, это соответствует типу Long, и запись такой переменной займет 6 байтов (2 - для типа и 4 - для самого числа). Значение VarType = 8 соответствует строковому типу (String). В этом случае Put запишет 2 байта с VarType, затем 2 байта с длиной строки и затем саму строку.В случае динамического массива Put записывает перед его элементами описатель с длиной 2 + 8* (число измерений массива).Массивы фиксированного размера записываются без описателей.
Без всякой дополнительной информации записываются и значения остальных типов переменных с фиксированными размерами.Для бинарных файлов параметр Len при записи роли не играет. Put записывает значения переменных подряд без пропусков и не помещает длины динамических строк и дескрипторы массивов.Для переменных с нефиксированными размерами Get выполняет преобразования, обратные тем, что при записи выполнял оператор Put. Например, при чтении в строку переменной длины Get определяет по 2-байтовому описателю длину строки, а затем читает ее содержимое в переменную. Аналогичная расшифровка происходит и при чтении в переменные типа Variant и в динамические массивы.Для бинарных файлов Get читает данные подряд. В переменные фиксированного размера считывается соответствующее их размеру число байтов. При чтении в строку переменной длины в нее считывается число символов, равное длине ее текущего значения.
Приведем пример работы с файлом произвольного доступа, записи которого содержат данные не фиксированного размера:
Пример 14.6.
(html, txt)
Вот результаты ее работы:
Степанов 10 Архангельский 35 Куц 35 Петров 50 125,25 70 125 84
Приведем комментарии:
Операторы Put и Get позволяют записать и корректно прочитать данные переменного размера, не превосходящего максимальный размер, заданный при открытии файла.При записи первой строки переменной длины размер файла стал равным 10 байтам, из которых 8 байтов занимает сама строка, а два байта, предшествующий ей описатель. Описатель позволяет оператору Get корректно прочитать эту строку.Запись второй строки начинается с 21-го байта, границы первой записи, общий размер файла становится равным 35 с учетом размера второй строки и ее описателя.Третья запись не изменяет размера файла, поскольку происходит изменение значения первой записи, так что файл по-прежнему содержит только две записи.Далее начинается запись данных типа Variant. Теперь при записи строки используются два описателя, занимающие 4 байта.Две последние записи содержат числовые данные Double (8 байтов) и Integer (2 байта) с их описателями.
Заканчивая тему работы с файлами произвольного доступа, хотим еще раз обратить внимание на некоторые опасные моменты, возникающие в процессе работы с ними и не контролируемые VBA:
Явно задавайте при открытии файла, как при чтении, так и при записи параметр Len ѕ максимальный размер записи. Если этого не сделать, то программа будет работать, но, к сожалению, результаты могут быть не предсказуемыми.Будьте особенно внимательными при работе с данными переменной длины, помните, что к данным автоматически присоединяются описатели.Размер файла определяется записью с максимальным номером. Чтобы избежать большого числа "дырок" в файле стройте разумные функции ключа так, чтобы ключи записей занимали достаточно плотный начальный интервал от 1 до N.
Описатель позволяет оператору Get корректно прочитать эту строку.Запись второй строки начинается с 21-го байта, границы первой записи, общий размер файла становится равным 35 с учетом размера второй строки и ее описателя.Третья запись не изменяет размера файла, поскольку происходит изменение значения первой записи, так что файл по-прежнему содержит только две записи.Далее начинается запись данных типа Variant. Теперь при записи строки используются два описателя, занимающие 4 байта.Две последние записи содержат числовые данные Double (8 байтов) и Integer (2 байта) с их описателями.
Заканчивая тему работы с файлами произвольного доступа, хотим еще раз обратить внимание на некоторые опасные моменты, возникающие в процессе работы с ними и не контролируемые VBA:
Явно задавайте при открытии файла, как при чтении, так и при записи параметр Len ѕ максимальный размер записи. Если этого не сделать, то программа будет работать, но, к сожалению, результаты могут быть не предсказуемыми.Будьте особенно внимательными при работе с данными переменной длины, помните, что к данным автоматически присоединяются описатели.Размер файла определяется записью с максимальным номером. Чтобы избежать большого числа "дырок" в файле стройте разумные функции ключа так, чтобы ключи записей занимали достаточно плотный начальный интервал от 1 до N.
Для последовательных файлов запись данных всегда происходит в конец файла, а чтение после открытия производится с самого начала. Для бинарных файлов и файлов произвольного доступа операция чтения выполняется оператором Get, операция записи ѕ оператором Put. Эти операции позволяют читать и записывать записи в произвольном порядке. Установить нужную позицию записи или чтения можно, вызвав оператор Seek:
Seek [#]номер-файла, позиция
Параметр номер-файла - номер открытого в режиме Random или Binary файла; позиция - число от 1 до 2 147 483 647, определяющее место в файле, куда будут помещены данные следующим оператором Put или откуда они будут считаны следующим оператором Get. Для файлов произвольного доступа позиция задает номер записи в файле, для бинарных и последовательных файлов - номер байта (символа). Иногда, в процессе поиска позиции необходимо передвинуться вперед или назад на некоторое число записей относительно текущей позиции. Функция Seek может быть использована для того, чтобы определить текущую позицию:
Seek(номер-файла)
Она возвращает число в диапазоне от 1 до 2 147 483 647. Для файлов произвольного доступа - это номер следующей считываемой или записываемой записи, для файлов, открытых в остальных режимах, - позиция байта, в которой будет выполняться следующая операция
Операторы Put и Get позволяют сами установить позицию, переопределив, тем самым, позицию, установленную оператором Seek. Эти операторы имеют одинаковый, простой и ясный синтаксис:
Put [#]номер-файла, [номер-записи], переменная Get [#]номер-файла, [номер-записи], переменная
Параметры этих операторов имеют следующий смысл:
Параметр номер-файла - номер открытого в режиме Random или Binary файла;номер-записи - числовое выражение, определяющее позицию в файле, куда будут помещены вводимые данные или откуда данные будут прочитаны. Для файлов произвольного доступа - это номер записи в файле, для бинарных файлов - порядковый номер байта в файле. Нумерация записей (байтов) в файле начинается с 1.
Для последовательных файлов запись данных всегда происходит в конец файла, а чтение после открытия производится с самого начала. Для бинарных файлов и файлов произвольного доступа операция чтения выполняется оператором Get, операция записи ѕ оператором Put. Эти операции позволяют читать и записывать записи в произвольном порядке. Установить нужную позицию записи или чтения можно, вызвав оператор Seek:
Seek [#]номер-файла, позиция
Параметр номер-файла - номер открытого в режиме Random или Binary файла; позиция - число от 1 до 2 147 483 647, определяющее место в файле, куда будут помещены данные следующим оператором Put или откуда они будут считаны следующим оператором Get. Для файлов произвольного доступа позиция задает номер записи в файле, для бинарных и последовательных файлов - номер байта (символа). Иногда, в процессе поиска позиции необходимо передвинуться вперед или назад на некоторое число записей относительно текущей позиции. Функция Seek может быть использована для того, чтобы определить текущую позицию:
Seek(номер-файла)
Она возвращает число в диапазоне от 1 до 2 147 483 647. Для файлов произвольного доступа - это номер следующей считываемой или записываемой записи, для файлов, открытых в остальных режимах, - позиция байта, в которой будет выполняться следующая операция
Операторы Put и Get позволяют сами установить позицию, переопределив, тем самым, позицию, установленную оператором Seek. Эти операторы имеют одинаковый, простой и ясный синтаксис:
Put [#]номер-файла, [номер-записи], переменная Get [#]номер-файла, [номер-записи], переменная
Параметры этих операторов имеют следующий смысл:
Параметр номер-файла - номер открытого в режиме Random или Binary файла;номер-записи - числовое выражение, определяющее позицию в файле, куда будут помещены вводимые данные или откуда данные будут прочитаны. Для файлов произвольного доступа - это номер записи в файле, для бинарных файлов - порядковый номер байта в файле. Нумерация записей (байтов) в файле начинается с 1.
Если параметр номер- записи опустить, данные будут записаны (прочитаны) в текущую позицию. Уже говорилось, что установить текущую позицию можно оператором Seek. Отметим также, что после выполнения операторов Get и Put текущей становится позиция очередной записи (байта), следующей за только что прочитанной записью (байтом). Заметьте, даже если этот параметр опущен, разделяющая запятая все же остается в операторе вызова.Параметр переменная - имя переменной, значение которой должно быть записано в файл (в которую будет прочитана запись).
Вот простой пример записи и чтения в файл произвольного доступа:
Public Sub Test1() Dim N As Integer Open Path & "test.1" For Random Access Read Write As #1 Len = 2 N = 1 Put 1, 1, N Get 1, 1, N Debug.Print N, LOF(1) N = 2 Put 1, 2, N Get 1, 2, N Debug.Print N, LOF(1) N = 3 Put 1, 1, N Get 1, 1, N Debug.Print N, LOF(1) Close #1
End Sub
Используемая здесь функция LOF(номер-файла) возвращает длину файла в байтах. Заметьте, будет создан файл из двух записей, поскольку третий по счету оператор Put изменяет значение первой записи, так что перед закрытием функция LOF возвратит значение 4.
Но давайте рассмотрим чуть более серьезный пример. Рассмотрим работу с файлом, который назовем "Товары.9". Этот файл будет содержать информацию о товарах. Код товара будет служить ключом записи. Зная код товара, можно будет найти запись в файле. На практике, когда формальным ключом записи, как в нашем случае, является ее порядковый номер, всегда задается некоторая функция, которая реальный ключ, возможно заданный несколькими параметрами, преобразует в порядковый номер. В нашем примере, чтобы не вводить таких преобразований, будем полагать, что код товара изменяется в пределах от 1 до N и, следовательно, он может служить формальным ключом записи. Вот как задается пользовательский тип, описывающий товар:
Type Товар КодТовара As Integer Наименование As String * 10 Цена As Integer End Type
Создадим файл, хранящий записи этого типа.
Чтобы можно было проследить за деталями, наш файл будет содержать не более 10 записей. Мы используем случайные числа для генерирования записей этого файла. Вот три процедуры, решающие эту задачу:
Пример 14.5.
(html, txt)
Приведем результаты отладочной печати:
Код Товара: 7 Цена: 294 Код Товара: 3 Цена: 141 Код Товара: 3 Цена: 84 Код Товара: 1 Цена: 18 Код Товара: 6 Цена: 246 Код Товара: 8 Цена: 472 Код Товара: 8 Цена: 376 Код Товара: 7 Цена: 483 Код Товара: 2 Цена: 168 Код Товара: 8 Цена: 224 Файл Товары.9 успешно создан Размер файла 112
Прокомментируем эти процедуры и полученные результаты. Первая процедура генерирует запись, используя датчик случайных чисел ѕ функцию Rnd. Ранее в своих примерах мы уже использовали эту функцию и подробно говорили о том, как она работает. Вторая процедура позволяет вывести запись в окно отладки. Основной является процедура CreateRandomFile, в которой открывается файл "Товары.9" как файл с произвольным доступом, открытый для записи. При открытии, что очень важно, мы не забыли указать длину записи. Поскольку файл предназначен для хранения значений переменных типа "Товар", то этот тип и определяет длину записи, ѕ она равна 14 байтов (10 байтов занимает строка постоянной длины и 4 байта требуется для двух полей типа Integer).В процедуре 10 раз генерируется новая запись и 10 раз выполняется оператор Put, производящий запись в файл. Но как показывают результаты отладочной печати, некоторые коды повторяются, поэтому в таких случаях происходит обновление содержимого записи. В данном примере только 6 записей имеют уникальный код, и 4 раза происходило обновление уже созданных записей. Важно обратить внимание на длину созданного файла. Анализируя эту характеристику, можно понять, что файл содержит 8 записей, а не 10 и не 6. Дело в том, что длина файла произвольного доступа определяется записью с максимальным номером. Пусть в файле есть запись с максимальным номером M. И пусть добавляется новая запись с номером N, большим M. В этот момент в файле будут созданы новые записи с номерами от M+1 до N включительно.
Запись с номером N будет действительно записана, а остальные получат значения по умолчанию, в соответствии с соглашениями, принятыми в VBA. Так что, если в файл пишется одна единственная запись с ключом 10000, то это означает, что в файле автоматически появляется 10000 записей и таков будет его размер. В нашем примере максимальный код равен 8,ѕ отсюда и размер файла равен 112 = 8*14
Рассмотрим теперь работу с этим файлом. Мы приведем две процедуры. Первая из них ведет последовательную обработку всех записей файла, читая их друг за другом. Вторая ѕ случайным образом генерирует запрос на получение записи файла со случайным ключом. Вот эти процедуры:
Public Sub PrintFile() Dim N As Integer, i As Integer, Code As Integer Dim MyRec As Товар 'Открыть файл Товаров для обработки Open Path & "Товары.9" For Random Access Read As #1 Len = Len(MyRec)
N = LOF(1) \ Len(MyRec) 'Число записей Debug.Print "Число записей файла-- ", N For i = 1 To N 'Установить позицию Seek 1, i 'Получить запись с заданным кодом Get #1,, MyRec Call PrintRec(MyRec) Next i Close #1 End Sub
Вот на какие моменты следует обратить внимание. Файл открывается теперь для чтения и при открытии корректно указывается длина записи. Для подсчета числа записей файла используется упоминавшаяся функция LOF. Заметьте, мы используем оператор Seek для установки позиции, но это скорее для проформы, поскольку в данной ситуации Seek не изменяет уже установленной позиции. Вот результаты работы этой процедуры:
Число записей файла-- 8 Код Товара: 1 Цена: 18 Код Товара: 2 Цена: 168 Код Товара: 3 Цена: 84 Код Товара: 0 Цена: 0 Код Товара: 0 Цена: 0 Код Товара: 6 Цена: 246 Код Товара: 7 Цена: 483 Код Товара: 8 Цена: 224
Мы уже пояснили, почему в файле оказалось 8 записей и почему часть из них имеет нулевые значения.
Следующая процедура работы с этим файлом демонстрирует возможность работы с ним, когда доступ к записям осуществляется в произвольном порядке, что, впрочем, демонстрировалось и при создании этого файла:
Public Sub WorkWithFile() Dim N As Integer, i As Integer, Code As Integer Dim MyRec As Товар Randomize 'Открыть файл Товаров для обработки Open Path & "Товары.9" For Random Access Read As #1 Len = Len(MyRec)
N = Int(Rnd * 9 + 1) 'Число обрабатываемых записей Debug.Print "Число обрабатываемых записей -- ", N For i = 1 To N Code = Int(Rnd * 9 + 1) ' Установить позицию Seek 1, Code 'Получить запись с заданным кодом Get #1,, MyRec If MyRec.КодТовара = 0 Then Debug.Print "В файле нет записи с кодом:", Code Else: Call PrintRec(MyRec) End If Next i Close #1 End Sub
Из новых деталей отметим следующее. Здесь оператор Seek работает по существу, хотя, конечно, и без него можно было бы обойтись, используя возможности оператора Get. Однако, более важно, обратить внимание на то, что при попытке читать запись с ключом, который не задавался при создании файла (по существу, читать несуществующую запись) не возникает ошибки. Причину этого мы уже объяснили. Поэтому приходится организовывать собственную проверку, как это и сделано в нашей программе. В заключение приведем результаты одного из экспериментов с этой процедурой:
Число обрабатываемых записей -- 2 Код Товара: 8 Цена: 224 В файле нет записи с кодом: 5
Если параметр номер- записи опустить, данные будут записаны (прочитаны) в текущую позицию. Уже говорилось, что установить текущую позицию можно оператором Seek. Отметим также, что после выполнения операторов Get и Put текущей становится позиция очередной записи (байта), следующей за только что прочитанной записью (байтом). Заметьте, даже если этот параметр опущен, разделяющая запятая все же остается в операторе вызова.Параметр переменная - имя переменной, значение которой должно быть записано в файл (в которую будет прочитана запись).
Вот простой пример записи и чтения в файл произвольного доступа:
Public Sub Test1() Dim N As Integer Open Path & "test.1" For Random Access Read Write As #1 Len = 2 N = 1 Put 1, 1, N Get 1, 1, N Debug.Print N, LOF(1) N = 2 Put 1, 2, N Get 1, 2, N Debug.Print N, LOF(1) N = 3 Put 1, 1, N Get 1, 1, N Debug.Print N, LOF(1) Close #1
End Sub
Используемая здесь функция LOF(номер-файла) возвращает длину файла в байтах. Заметьте, будет создан файл из двух записей, поскольку третий по счету оператор Put изменяет значение первой записи, так что перед закрытием функция LOF возвратит значение 4.
Но давайте рассмотрим чуть более серьезный пример. Рассмотрим работу с файлом, который назовем "Товары.9". Этот файл будет содержать информацию о товарах. Код товара будет служить ключом записи. Зная код товара, можно будет найти запись в файле. На практике, когда формальным ключом записи, как в нашем случае, является ее порядковый номер, всегда задается некоторая функция, которая реальный ключ, возможно заданный несколькими параметрами, преобразует в порядковый номер. В нашем примере, чтобы не вводить таких преобразований, будем полагать, что код товара изменяется в пределах от 1 до N и, следовательно, он может служить формальным ключом записи. Вот как задается пользовательский тип, описывающий товар:
Type Товар КодТовара As Integer Наименование As String * 10 Цена As Integer End Type
Создадим файл, хранящий записи этого типа.
Чтобы можно было проследить за деталями, наш файл будет содержать не более 10 записей. Мы используем случайные числа для генерирования записей этого файла. Вот три процедуры, решающие эту задачу:
Public Sub CreateRec(Rec As Товар) 'Создает запись типа Товар Randomize Rec.КодТовара = Int(Rnd * 9 + 1) 'Код от 1 до 9 Rec.Наименование = "Товар" & Rec.КодТовара Rec.Цена = Int(Rnd * 99 + 1) * Rec.КодТовара
End Sub
Public Sub PrintRec(Rec As Товар) 'Печать записи о товаре Debug.Print "Код Товара:", Rec.КодТовара, "Цена:", Rec.Цена End Sub
Public Sub CreateRandomFile() Dim i As Integer, NewRec As Товар Open Path & "Товары.9" For Random Access Write As #1 Len = Len(NewRec) 'Создаем 10 записей For i = 1 To 10 Call CreateRec(NewRec) Call PrintRec(NewRec) Put #1, NewRec.КодТовара, NewRec
Next i Debug.Print "Файл Товары.9 успешно создан" Debug.Print "Размер файла", LOF(1) Close #1 End Sub
Пример 14.5.
Приведем результаты отладочной печати:
Код Товара: 7 Цена: 294 Код Товара: 3 Цена: 141 Код Товара: 3 Цена: 84 Код Товара: 1 Цена: 18 Код Товара: 6 Цена: 246 Код Товара: 8 Цена: 472 Код Товара: 8 Цена: 376 Код Товара: 7 Цена: 483 Код Товара: 2 Цена: 168 Код Товара: 8 Цена: 224 Файл Товары.9 успешно создан Размер файла 112
Прокомментируем эти процедуры и полученные результаты. Первая процедура генерирует запись, используя датчик случайных чисел ѕ функцию Rnd. Ранее в своих примерах мы уже использовали эту функцию и подробно говорили о том, как она работает. Вторая процедура позволяет вывести запись в окно отладки. Основной является процедура CreateRandomFile, в которой открывается файл "Товары.9" как файл с произвольным доступом, открытый для записи. При открытии, что очень важно, мы не забыли указать длину записи. Поскольку файл предназначен для хранения значений переменных типа "Товар", то этот тип и определяет длину записи, ѕ она равна 14 байтов (10 байтов занимает строка постоянной длины и 4 байта требуется для двух полей типа Integer).В процедуре 10 раз генерируется новая запись и 10 раз выполняется оператор Put, производящий запись в файл.
Но как показывают результаты отладочной печати, некоторые коды повторяются, поэтому в таких случаях происходит обновление содержимого записи. В данном примере только 6 записей имеют уникальный код, и 4 раза происходило обновление уже созданных записей. Важно обратить внимание на длину созданного файла. Анализируя эту характеристику, можно понять, что файл содержит 8 записей, а не 10 и не 6. Дело в том, что длина файла произвольного доступа определяется записью с максимальным номером. Пусть в файле есть запись с максимальным номером M. И пусть добавляется новая запись с номером N, большим M. В этот момент в файле будут созданы новые записи с номерами от M+1 до N включительно. Запись с номером N будет действительно записана, а остальные получат значения по умолчанию, в соответствии с соглашениями, принятыми в VBA. Так что, если в файл пишется одна единственная запись с ключом 10000, то это означает, что в файле автоматически появляется 10000 записей и таков будет его размер. В нашем примере максимальный код равен 8,ѕ отсюда и размер файла равен 112 = 8*14
Рассмотрим теперь работу с этим файлом. Мы приведем две процедуры. Первая из них ведет последовательную обработку всех записей файла, читая их друг за другом. Вторая ѕ случайным образом генерирует запрос на получение записи файла со случайным ключом. Вот эти процедуры:
Public Sub PrintFile() Dim N As Integer, i As Integer, Code As Integer Dim MyRec As Товар 'Открыть файл Товаров для обработки Open Path & "Товары.9" For Random Access Read As #1 Len = Len(MyRec)
N = LOF(1) \ Len(MyRec) 'Число записей Debug.Print "Число записей файла-- ", N For i = 1 To N 'Установить позицию Seek 1, i 'Получить запись с заданным кодом Get #1,, MyRec Call PrintRec(MyRec) Next i Close #1 End Sub
Вот на какие моменты следует обратить внимание. Файл открывается теперь для чтения и при открытии корректно указывается длина записи. Для подсчета числа записей файла используется упоминавшаяся функция LOF. Заметьте, мы используем оператор Seek для установки позиции, но это скорее для проформы, поскольку в данной ситуации Seek не изменяет уже установленной позиции.
Вот результаты работы этой процедуры:
Число записей файла-- 8 Код Товара: 1 Цена: 18 Код Товара: 2 Цена: 168 Код Товара: 3 Цена: 84 Код Товара: 0 Цена: 0 Код Товара: 0 Цена: 0 Код Товара: 6 Цена: 246 Код Товара: 7 Цена: 483 Код Товара: 8 Цена: 224
Мы уже пояснили, почему в файле оказалось 8 записей и почему часть из них имеет нулевые значения.
Следующая процедура работы с этим файлом демонстрирует возможность работы с ним, когда доступ к записям осуществляется в произвольном порядке, что, впрочем, демонстрировалось и при создании этого файла:
Public Sub WorkWithFile() Dim N As Integer, i As Integer, Code As Integer Dim MyRec As Товар Randomize 'Открыть файл Товаров для обработки Open Path & "Товары.9" For Random Access Read As #1 Len = Len(MyRec)
N = Int(Rnd * 9 + 1) 'Число обрабатываемых записей Debug.Print "Число обрабатываемых записей -- ", N For i = 1 To N Code = Int(Rnd * 9 + 1) 'Установить позицию Seek 1, Code 'Получить запись с заданным кодом Get #1,, MyRec If MyRec.КодТовара = 0 Then Debug.Print "В файле нет записи с кодом:", Code Else: Call PrintRec(MyRec) End If Next i Close #1 End Sub
Из новых деталей отметим следующее. Здесь оператор Seek работает по существу, хотя, конечно, и без него можно было бы обойтись, используя возможности оператора Get. Однако, более важно, обратить внимание на то, что при попытке читать запись с ключом, который не задавался при создании файла (по существу, читать несуществующую запись) не возникает ошибки. Причину этого мы уже объяснили. Поэтому приходится организовывать собственную проверку, как это и сделано в нашей программе. В заключение приведем результаты одного из экспериментов с этой процедурой:
Число обрабатываемых записей -- 2 Код Товара: 8 Цена: 224 В файле нет записи с кодом: 5
Для закрытия файла служит оператор Close, вызываемый:
Close [список-номеров-файлов]
Параметр список-номеров-файлов может содержать один или несколько номеров ранее открытых файлов, которые должны быть закрыты. Как обычно, номера в списке разделяются запятыми. Если параметр опущен, то закрываются все открытые в данный момент файлы. После выполнения оператора Close номера закрытых файлов снова можно использовать при открытии в операторе Open.
Закрыть все открытые оператором Open файлы может также оператор Reset. Как и вызов Close без параметров, он освободит все номера открытых файлов и перенесет содержимое их буферов на диск.
При записи и чтении данных в последовательный файл происходит автоматическое редактирование данных. В зависимости от того, на кого ориентировано это редактирование, человека или программу компьютера применяются две формы редактирования. Соответственно операторы записи и чтения существуют в двух вариантах:
Для записи "человеко-ориентированных" данных применяется оператор Print#, представляющий данные в формате, подготовленном для отображения на экране дисплея. По существу, мы уже хорошо знакомы с этим оператором, поскольку ни одна наша программа не обходилась без вывода данных в окно отладки, а применяемый для этих целей метод Print объекта Debug по синтаксису и по действию подобен оператору Print#.Для записи данных, обрабатываемых после их чтения программой, используется оператор Write#. Заметим, в каком - то смысле это более универсальный оператор, чем Print, он, например, не зависит от локализации.
Оператор Print# записывает в файл форматированные данные. Его вызов имеет вид:
Print #номер-файла, [список-вывода]
Здесь номер-файла - номер файла, открытого в режиме последовательного доступа для записи (Append или Output), список-вывода - одно или несколько выражений, разделенных пробелами или точками с запятой, определяющих выводимые данные. Каждое выражение в этом списке может быть снабжено дополнительной информацией о расположении данных, делающее их восприятие более удобным для пользователя. Общий вид такого выражения:
[{Spc(n) | Tab[(n)]}] [выражение] [симв-поз] Spc(n) задает количество вставляемых пробелов.Альтернативный параметр Tab(n) указывает абсолютный номер n столбца, в котором будет начинаться текст, определяемый параметром выражение. Если не указывать аргумент у Tab, то позицией вставки является следующая зона печати. (Можно полагать, что при выводе экран разделен на зоны, и табуляция позволяет переходить от одной зоны к другой).Параметр выражение задает выражение, значение которого после редактирования и преобразования его в текстовую строку будет записано в файл.
На выражения не накладывается особых ограничений, Это могут быть как строковые, так и числовые выражения, они могут содержать данные разных типов. Даты записываются в файл в кратком формате, значения булевых переменных - как ключевые слова Ложь (False) или Истина (True), пустое значение Empty - как пустое слово, нулевое значение Null выводится как "Null". Данные об ошибке выводятся в виде кода ошибки. Числовые данные выводятся в формате, зависящем от локализации. При выводе можно вызвать для форматирования данных функцию Format.Параметр симв-поз задает положение следующего выражения. Если он равен ";" или пробелу, вставка следующего выражения будет происходить сразу за последним выведенным символом, Tab(n) указывает номер n столбца, начиная с которого будет выводиться выражение. Tab без аргумента или "," - переход в следующую позицию табуляции (зону печати).
Создадим теперь открытый ранее файл read.me, записывая в него выражения, позволяющие продемонстрировать все возможности оператора Print:
Пример 14.2.
(html, txt)
Здесь в комментариях показан ожидаемый вид показа строки при ее чтении и печати. Чтобы не томить ожиданием, давайте сразу приведем процедуру, которая читает и выводит в окно отладки записи этого файла:
Public Sub ReadingWithLine() Dim MyStr As String
Close #1 Open Path & "read.me" For Input As #1 Do While Not EOF(1) Line Input #1, MyStr Debug.Print MyStr Loop End Sub
Текст в окне отладки выглядит в полном соответствии с нашими ожиданиями. Вот он:
Первая строка файла Зона 1 Зона 2 Зона 1 Зона 2 Привет,старик! Привет,старик! 5 пробелов Старик, привет! one two False 14.06.99 Null 3,1416 3,14 03,142 6 Error 2000
Обратите внимание, ошибки, возбуждаемые методом Raise и функцией CVErr, редактируются по-разному, в первом случае записывается только код ошибки, во втором ѕ код сопровождается ключевым словом Error, не переводимым в локализованных версиях.
Заметьте, точно такой же текст можно получить, если в процедуре WritingWithPrint заменить оператор Print на Debug.Print, поскольку этот метод и этот оператор порождают одинаковый результат, разница лишь в том, что один из них помещает результаты в файл, а другой ѕ в окно отладки.
При выводе данных можно управлять шириной выводимых строк. Ширину выводимых строк для открытого файла устанавливает оператор:
Width #номер-файла, ширина
Параметр ширина принимает значения от 0 до 255. Если при печати в файл оператором Print добавление значения очередного выражения в строку приведет к превышению установленной границы, это значение будет печататься с новой строки. При этом значение одного выражения не переносится, даже если его длина превышает установленную границу. Например, если для файла с номером 1 ограничить ширину строки 5 символами:
Width #1,5
а затем напечатать строку:
Str = "раму мылом" Print #1, "мама", "мыла", Str
то в файле окажутся строки:
мама мыла раму мылом
Обращаем внимание и на то, что переход к новой строке может происходить и при встрече с разделителем Tab(n), если n задает позицию меньшую, чем текущая позиция. Пример такой ситуации встречался в нашей процедуре.
Как было показано, оператор Print вводит в файл данные разных типов, редактируя их в момент ввода и преобразуя в текстовый формат. Попутно он занимается и форматированием текста, приводя его к форме, удобной для отображения на дисплее. Во многих ситуациях форматирование, ориентированное на выдачу на экран дисплея, не является необходимым. В этих случаях для создания последовательного файла применяется Write#. Его синтаксис:
Write #номер-файла, [список-вывода ]
Параметр номер-файла - номер открытого файла с последовательным доступом, список-вывода - одно или несколько разделенных запятыми числовых или строковых выражений, значения которых записываются в файл. Разделителями выражений могут быть пробелы и точка с запятой.
Также как и оператор Print, оператор Write производит редактирование данных в момент записи. При этом приняты следующие соглашения:
В числах в качестве разделителя целой и дробной частей всегда используется точка.В качестве булевых значений используются слова #TRUE# и #FALSE#.При записи дат применяется универсальный формат.Ошибки выводятся в формате #ERROR код-ошибки#.Для пустого значения выражения (Empty) ничего не записываетсяЗначение Null записывается как #Null#.
В отличие от оператора Print, оператор Write# вставляет при записи запятые между соседними выражениями и заключает строковые значения в кавычки. После записи последнего выражения вставляется перевод на новую строку (Chr(13) + Chr(10)).
Следующая процедура создает файл с последовательным доступом "readme.txt" и записывает в него данные с помощью оператора Write#. При записи мы старались повторить содержимое предыдущего файла, чтобы можно было сравнить работу, выполняемую операторами Print и Write. Вот текст этой процедуры:
Пример 14.3.
(html, txt)
Результат выполнения этой процедуры - файл "readme.txt" - в текстовом редакторе выглядит так:
"Первая строка файла" "Зона 1","Зона 2" "Привет,","старик!" "Мама ","мыла ","раму мылом. " #FALSE# #1999-06-14# #NULL# 3.1416,"3,14","03,142" 6 #ERROR 2000#
Сравните эти строки с теми, что выдаются оператором Print.
'Создание файла Write #7, "Первая строка файла" ' запись текстовой строки. Write #7, "Зона 1", "Зона 2" ' запись двух строк. Write #7, "Привет,", "старик!" 'еще две строки MyStr = "раму мылом. " Write #7, "Мама ", "мыла ", MyStr MyBool = False ' булева переменная Write #7, MyBool MyDate = #6/14/1999# ' значение даты Write #7, MyDate ' запись даты MyNull = Null ' нулевое значение Write #7, MyNull MyFloat = 3.1416 'вещественное значение ' использование функции Format: Write #7, MyFloat, Format(MyFloat, "0.00"), Format(MyFloat, "00.000") On Error Resume Next Err.Raise 6 MyErr = Err Write #7, MyErr MyErr = CVErr(2000) Write #7, MyErr
End Sub
Пример 14.3.
Результат выполнения этой процедуры - файл "readme.txt" - в текстовом редакторе выглядит так:
"Первая строка файла" "Зона 1","Зона 2" "Привет,","старик!" "Мама ","мыла ","раму мылом. " #FALSE# #1999-06-14# #NULL# 3.1416,"3,14","03,142" 6 #ERROR 2000#
Сравните эти строки с теми, что выдаются оператором Print.
Итак, книга, посвященная, главным образом, языку VBA, закончена. Я начал работать над второй книгой, в которой, собираюсь подробно рассказать об объектах каждого из приложений Office 2000 - Word, Excel, Access, Outlook, сопровождая описание проектами, в которых участвуют объекты этих приложений. Особое внимание хочется уделить применениям в Интернет. Пока, это еще только планы. Пишу об этом по той причине, что надеюсь получить от Вас письма с предложениями, а, возможно, и готовыми проектами, небольшими, но интересными для многих, которые Вы хотели бы поместить в книгу.
Теперь несколько слов о законченной книге. Знаю по себе, что книги редко отвечают на вопросы, требующие сиюминутного решения. Цель книг другая. При прочтении хорошей книги число интересных вопросов, которые можно задать, по крайней мере, самому себе должно только возрастать. И вся прелесть состоит в том, что на эти вопросы удается найти ответы. В поисках ответов мы часто обращаемся к друзьям, коллегам, а иногда, когда есть такая возможность, и к автору книги. Я получаю достаточно много писем с различными вопросами по поводу офисного программирования. Не всегда, но, по возможности, стараюсь на них отвечать. Как правило, ответы на поставленные вопросы требуют знания чуть большего числа деталей, в сравнении с тем, что, по понятным причинам, удалось изложить в рамках книги. В качестве примера, приведу два вопроса из одного из последних писем, которое я получил от Ивана Кряжева - читателя нашей предыдущей книги по Офисному программированию. Полагаю возможным и полезным поместить мой подробный ответ на вопросы этого письма.
Для выяснения сути проблемы обратимся к примеру, который я спроектировал специально для этой задачи.
Первым делом я спроектировал форму с двумя элементами управления, включив ее в состав тестового документа. На самом тестовом документе я разместил 4 кнопки, управляющие работой формы. Кнопки Show и Hide показывают и прячут форму. Кнопка "Add Controls to Form" программно добавляет элементы управления на период существования формы, то есть до тех пор, пока форма не будет закрыта. Наконец, кнопка "Add Design Controls" решает поставленную задачу, добавляя элементы управления, как теперь принято говорить, на постоянной основе. Вот как выглядит тестовый документ с командными кнопками:
А вот как выглядит сама форма при ее открытии в ответ на щелчок кнопки Show:
Ответ, кажется, очевидным - нужно переименовать один из компонентов, поскольку, действительно не могут существовать два компонента с одним именем. Какой же из компонентов переименовывать? Здесь тоже все достаточно ясно, - импортируемый компонент программно в момент его добавления не переименуешь, поскольку он скрыт в файле. Поэтому остается переименовать компонент в самом проекте. Остается понять, какой именно компонент вызвал коллизию и требует переименования. Часто подсказкой может служить имя файла с импортируемым компонентом, поскольку по умолчанию имя файла совпадает с именем компонента. В моем примере я переименовываю все компоненты текущего проекта. Это одно из возможных решений. Главное, демонстрируется возможность переименования компонент.
Но обо всем по порядку. Несколько слов о самом примере. В наш документ, содержащий форму со стандартным именем UserForm1, программно импортируются еще две формы. Обе они были экспортированы из другого документа, сохранены в виде файлов, и поскольку имели стандартные имена, то и файлы (по два файла на форму с уточнениями "frm" и "frx") получили имена "UserForm1" и "UserForm2". Вот процедура, осуществляющая импорт:
Public Sub ImportForm() 'Эта процедура импортирует формы, сохраненные в файле Documents("Answer1.doc").VBProject.VBComponents.Import ("e:\O2000\cd2000\tests\UserForm1.frm") Documents("Answer1.doc").VBProject.VBComponents.Import ("e:\O2000\cd2000\tests\UserForm2.frm")
End Sub
Очевидно, что при запуске этой процедуры возникнет конфликт имен (два компонента - две формы имеют одно и то же имя "UserForm1") и при выполнении первого же оператора возникнет ошибка. Если же предварительно запустить процедуру Rename, переименовывающую объекты класса VBComponent, то импорт пройдет гладко. Разумно, конечно, запускать процедуру Rename в обработчике исключительной ситуации, когда конфликт реально возник. Вот возможный вариант процедуры Rename:
Public Sub Rename() Dim Comp As Object
For Each Comp In Documents("Answer1").VBProject.VBComponents Debug.Print Comp.Name Comp.Name = Comp.Name & "New" Debug.Print Comp.Name Next Comp
End Sub
Вот результаты отладочной печати, показывающей, какие компоненты проекта будут переименованы:
ThisDocument ThisDocumentNew Sample SampleNew UserForm1 UserForm1New
После переименования импорт происходит без проблем.
На два вопроса Ивана Кряжева ответы даны.
Если Вы хотите задать свои вопросы, напоминаю мой адрес: Vladimir.Billig@tversu.ru
© 2003-2007 INTUIT.ru. Все права защищены. |