Основы офисного программирования и язык VBA

         

Чтение файлов последовательного доступа


Для чтения данных из файлов последовательного доступа используются операторы:

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(размер, [#]номер-файла)

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





Несмотря на успех чтения, результаты не приемлемы, поскольку не выполняется нужного редактирования. Сохраняются кавычки, запятые, ограничители, в одной строке содержатся данные разных типов. Поэтому оператор Line Input # следует применять, как правило, для чтения строк из файлов, записанных с помощью оператора Print#.

Оператор Line Input # позволяет читать файл строка за строкой. Наряду с этим в языке VBA есть возможность чтения из файла произвольного числа символов или произвольного числа байтов. Для чтения символов Input или Binary файлов используется функция Input, имеющая следующий синтаксис:

Input(размер, [#]номер-файла)

где размер - количество считываемых символов, номер-файла - номер открытого файла (последовательного или бинарного). Данные, считываемые этой функцией, обычно должны быть записаны в файл операторами Print# или Put.

Еще один вариант этой функции:

InputB(размер, [#]номер-файла)

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


Один пример работы с Binary файлом


Бинарные файлы пишутся и читаются порциями, состоящими из произвольного числа байтов. Доступ возможен к любому байту этого файла в произвольном порядке. Никакого редактирования при операциях с этим файлом не производится и вся ответственность за сохранением структуры информации ложится на программиста. Операции над этим файлом выполняются операторами 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


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

И последний совет в этой лекции, средств VBA для работы с файлами произвольного доступа достаточно для выполнения основных операций: создания, записи, чтения и модификации данных. Но средства поиска данных в файле (оператор Seek) весьма примитивны. С их помощью трудно решать задачи упорядочения файлов, поиска записей по значениям отдельных полей и другие задачи, характерные для работы с базами данных. Для небольших файлов можно создавать индексные массивы, в которых номера записей упорядочены в соответствии с требуемым критерием, и поддерживать этот порядок при всех изменениях файла. Для больших - заводить свои индексные файлы и т. п. Но владельцам Office 2000 не следует заниматься программированием собственных СУБД. В их распоряжении - возможность переписать содержимое любого файла произвольного доступа в ячейки рабочего листа Excel или в базу данных (таблицу) Access и использовать для работы с его данными всю мощь этих инструментов.


Как правило, приложения Office имеют


Как правило, приложения Office имеют дело с файлами, хранящими документы этих приложений. Это файлы с документами Word (с расширениями.doc), рабочими книгами Excel (с расширениями.xls), базами данных Access (с расширениями.mdb). Операции по созданию, открытию и записи этих файлов выполняются с помощью соответствующих методов приложений, которые запускаются командами меню File. Но офисные системы должны уметь работать и с файлами других форматов. Например, часто требуется считывать данные из обычных текстовых файлов и заносить информацию, в них хранящуюся, в таблицы Excel или базы данных Access. Возможна и обратная ситуация, когда, используя данные из приложений Office, требуется сохранять полученную информацию в файлах. В общем, есть некоторые ситуации, когда предпочтительнее работать с собственными файлами, а не со стандартными базами данных. По этой причине в VBA включены средства для работы с внешними файлами, позволяющие их создавать, открывать для работы и осуществлять ввод-вывод данных. Файлы VBA делятся на три группы.

Файлы последовательного доступа могут открываться в режиме чтения (Input), записи (Output), или присоединения (Append). Специальных средств поиска для них нет, и чтобы получить данные из такого файла, его нужно открыть и прочесть последовательно все данные до нужного места. Изменения (запись данных) в таком файле всегда происходят в его конце. Существенной особенностью этого файла является редактирование данных в процессе записи и чтения. В файл можно записывать данные разных типов, При вводе они будут отредактированы и преобразованы в строку. Таким образом, последовательный файл можно рассматривать, как поток данных, представляющий последовательность строк переменой длины. Признаки конца строк и являются разделителями элементов файла. Доступ к элементам файла только последовательный и этот поток можно только читать или только писать, но нельзя делать то и другое одновременно.Файлы произвольного доступа состоят из записей постоянной длины, что позволяет организовать быстрый поиск данных и их локальное изменение внутри файла без его переписывания.
В таких файлах чаще всего хранят записи пользовательского типа, все поля которых имеют фиксированную длину, удобно также хранить числовые данные в двоичном формате. В этой модели файла поток данных представляет последовательность пронумерованных элементов одинаковой длины. Возможен прямой доступ к элементу по его номеру. Разрешается одновременно выполнять операции чтения и записи в файл, можно изменять содержимое отдельных элементов. Эта простая и эффективная в реализации модель файла очень удобна в тех случаях, когда она применима, например, как мы уже говорили для хранения числовых данных. Серьезным ограничением на ее применение является требование постоянства длины элементов файла.Бинарные (или бинарного доступа) файлы позволяют считывать и записывать информацию побайтно; обычно используются для хранения графических данных или любой другой информации в неструктурированном виде. Здесь также как и в предыдущей модели, элементы файла имеют постоянную длину в 1 байт. Существенным отличием является то, что никакого редактирования в момент чтения или записи не производится. Одной операцией может быть записано или прочитано произвольное число байтов.

Можно заметить, что среди допустимых моделей файлов VBA нет наиболее общей, когда разрешен прямой доступ к элементам файла, когда элементы могут иметь произвольную длину, когда ключ может быть произвольным и не обязательно должен задавать номер записи. Отсутствие такого типа файлов действительно представляет серьезное ограничение, но этому есть и не менее серьезное оправдание. В таких сложных ситуациях рекомендуется переходить на работу с базой данных. Это тем более оправдано тем, что база данных Access доступна из любого приложения Office 2000.

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

Вот какие операторы обычно применяются для записи и чтения данных из файлов в зависимости от режима доступа.

Тип доступаЗапись данныхЧтение данных
ПоследовательныйPrint#, Write#Input#, Line Input#
ПроизвольныйPut Get
БинарныйPutGet



Открытие и создание файлов


Файлы перечисленных видов открываются и могут создаваться оператором 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 - обязателен для файлов произвольного доступа и задает для них длину одной записи файла в байтах. Для последовательных файлов он необязателен (для них он задает размер буфера, создаваемого при открытии), для бинарных файлов он игнорируется.


Если последовательный файл, открываемый в режиме вывода (Output), существует, его содержимое будет утеряно, и данные будут записываться в его начало. Если его нет, создается новый файл. Точно так же будет создан новый файл, если файл, определяемый параметром имя-файла, не существует, а режим открытия задан как Append, Random или Binary. При попытке открыть несуществующий файл в режиме Input будет получено сообщение об ошибке.

Последовательные файлы в режиме чтения, файлы произвольного доступа и бинарные можно открывать по несколько раз с различными номерами. С каждым из номеров будет связан свой буфер, что может повысить эффективность работы с файлом. Файл, открытый для чтения, должен быть закрыт перед его открытием в режимах записи Output или Append.

Рассмотрим детали открытия и создания файлов. В первом примере открывается файл последовательного доступа "read.me" для чтения:

Open "read.me" For Input As #1

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

Const Path = "e:\O2000\CD2000\Ch14\"

Public Sub Openfile() Dim NewFile As String, NewNum As Integer NewFile = "read.me" NewNum = FreeFile ' очередной незанятый номер файла Open Path & NewFile For Output As NewNum

End Sub

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

Файл с последовательным доступом особенно удобен, когда при работе с ним требуется последовательно прочесть и обработать все записи файла. Если же данные из файла обрабатываются в произвольном порядке, или файл открывается для того, чтобы получить доступ к небольшому числу его записей, крайне желательно иметь возможность произвольного (прямого) доступа к нужному элементу файла. Такую возможность и обеспечивают файлы VBA с произвольным доступом, хотя у них есть серьезное ограничение, ѕ все записи должны иметь одинаковый размер.


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

Type Person ' пользовательский тип записи. Возраст As Integer Фамилия As String * 20 Имя As String * 20 End Type

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

Пример 14.1.

(html, txt)

В этом примере для определения свободного номера файла использовалась функция FreeFile, а для определения длины записи ѕ функция Len. Файл произвольного доступа открыт как для чтения, так и для записи, а бинарный файл только для чтения. Заметьте, что бинарный файл закрыт для чтения другими процессами. Поэтому если повторно запустить процедуру открытия, то файл произвольного доступа будет повторно открыт с другим номером, но возникнет ошибка при повторном открытии бинарного файла. Возможность одновременного открытия под разными номерами нескольких логических экземпляров одного и того же физического файла бывает крайне полезной в ряде ситуаций.

Функция

FileAttr(номер-файла, 1)

по номеру открытого файла возвращает число, указывающее режим открытия: 1 - Input, 2 - Output, 4 - Random, 8 - Append, 32 - Binary. Второй аргумент достался функции FileAttr в наследство от 16-разрядного режима, где при его значении 2, она возвращала системный указатель на файл.

Мы написали простую процедуру PrintAttr, которая по номеру файла определяет его атрибуты и выводит сообщение в окно отладки.Приведем результаты работы процедуры OpenTwoFiles:

файл # 1 Открыт для Random Длина Записи: 42 файл # 2 Открыт для Binary



В этом примере для определения свободного номера файла использовалась функция FreeFile, а для определения длины записи ѕ функция Len. Файл произвольного доступа открыт как для чтения, так и для записи, а бинарный файл только для чтения. Заметьте, что бинарный файл закрыт для чтения другими процессами. Поэтому если повторно запустить процедуру открытия, то файл произвольного доступа будет повторно открыт с другим номером, но возникнет ошибка при повторном открытии бинарного файла. Возможность одновременного открытия под разными номерами нескольких логических экземпляров одного и того же физического файла бывает крайне полезной в ряде ситуаций.

Функция

FileAttr(номер-файла, 1)

по номеру открытого файла возвращает число, указывающее режим открытия: 1 - Input, 2 - Output, 4 - Random, 8 - Append, 32 - Binary. Второй аргумент достался функции FileAttr в наследство от 16-разрядного режима, где при его значении 2, она возвращала системный указатель на файл.

Мы написали простую процедуру PrintAttr, которая по номеру файла определяет его атрибуты и выводит сообщение в окно отладки. Приведем результаты работы процедуры OpenTwoFiles:

файл # 1 Открыт для Random Длина Записи: 42 файл # 2 Открыт для Binary


Dim attr As Integer, Num


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" решает поставленную задачу, добавляя элементы управления, как теперь принято говорить, на постоянной основе. Вот как выглядит тестовый документ с командными кнопками:


увеличить изображение
Рис. П-1.  Тестовый документ с командными кнопками

А вот как выглядит сама форма при ее открытии в ответ на щелчок кнопки Show:


Рис. П-2.  Спроектированная форма при ее открытии


Главное, что для программиста это не требует никаких усилий, достаточно изменить значение одного свойства. Замечу, что в С++, по крайней мере, в версии 4 работа с немодальными окнами требовала профессиональных знаний, в частности, умения посылать сообщения операционной системе. Но вернемся к нашей задаче. Элементы управления достаточно просто программным путем вставляются в форму в период выполнения программы, и я об этом рассказывал в главах, посвященных проектированию интерфейса. У объекта Form есть коллекция Controls, у которой есть метод Add, позволяющий добавить любой из элементов управления. В нашем примере мы уже спроектировали форму с двумя элементами управления - командной кнопкой и окном редактирования. В обработчиках события Click командной кнопки самой формы и командной кнопки "Add Controls to Form", встроенной в тестовый документ, вызывается процедура AddControls, которая добавляет в форму еще пару таких же элементов управления. Заметьте, поскольку наша форма не является модальной, то нажимать командные кнопки можно как в самой форме, так и вне формы. Вот текст процедуры, добавляющей в форму элементы управления:
Public Sub AddControls() 'Эта процедура добавляет командную кнопку и окно редактирования в форму With UserForm1
'Добавляем окно редактирования Set Mycmd = .Controls.Add("Forms.TextBox.1", "ProgramBox", True) Mycmd.Left = .TextBox1.Left Mycmd.Top = .TextBox1.Top + 100 Mycmd.Width = .TextBox1.Width Mycmd.Height = .TextBox1.Height Mycmd.Text = "New Control - " & Mycmd.Name
'Добавляем командную кнопку Set Mycmd = .Controls.Add("Forms.CommandButton.1", "ProgramButton", True) Mycmd.Left = .CommandButton1.Left Mycmd.Top = .CommandButton1.Top + 100 Mycmd.Width = .CommandButton1.Width Mycmd.Height = .CommandButton1.Height Mycmd.Caption = "ProgramButton"
End With
End Sub
Вот как выглядит форма, после того как была нажата кнопка "Add Controls to Form" и ее обработчик вызвал процедуру AddControls:



Рис. П-3.  Форма с программно добавленными элементами управления
Добавленные таким путем элементы управления живут в форме только до ее закрытия. Но, заметьте, если скрыть форму, вызвав метод Hide, то при повторном ее открытии элементы управления сохраняются. Перейдем теперь непосредственно к ответу на первый поставленный вопрос. Для его решения потребуются средства, ранее не упоминавшиеся в книге. Нам понадобятся объекты класса VBComponent - компоненты VB-проекта (объекта класса VBProject). Чтобы можно было с ними работать, необходимо включить дополнительную ссылку на библиотеку VBIDE (Visual Basic Extensibility). Ну, а теперь отвечаю конкретно на первый поставленный вопрос. Можно программно добавить элементы управления так, чтобы они сохранялись в форме точно также как сохраняются элементы, созданные в период проектирования. Для этого нужно работать с объектом, представляющим форму в режиме проектирования, используя свойство Designer объекта VBComponent. Вот как это делается:
Public Sub AddDesignControls() 'Эта процедура добавляет командную кнопку и окно редактирования в форму Dim MyForm As UserForm 'Создаем форму этапа проектирования
Set MyForm = ActiveDocument.VBProject.VBComponents("UserForm1").Designer With MyForm
'Добавляем окно редактирования Set Mycmd = .Controls.Add("Forms.TextBox.1", "ProgramBox", True) Mycmd.Left = .TextBox1.Left Mycmd.Top = .TextBox1.Top + 100 Mycmd.Width = .TextBox1.Width Mycmd.Height = .TextBox1.Height Mycmd.Text = "New Control - " & Mycmd.Name
'Добавляем командную кнопку Set Mycmd = .Controls.Add("Forms.CommandButton.1", "ProgramButton", True) Mycmd.Left = .CommandButton1.Left Mycmd.Top = .CommandButton1.Top + 100 Mycmd.Width = .CommandButton1.Width Mycmd.Height = .CommandButton1.Height Mycmd.Caption = "DesignButton"
End With
'Удаляем командную кнопку, вызывающую эту процедуру. 'Эта кнопка может работать лишь раз, по крайней мере, 'пока в режиме проектирования не будут удалены добавленные элементы. 'Заметьте, кнопку можно только удалить, но нельзя сделать ее невидимой.


ActiveDocument.InlineShapes(4).Delete End Sub
Обратите внимание, к нашей форме, рассматриваемой как объект класса VBComponent, мы добираемся через свойство VBProject нашего документа. Имея форму, в последний момент вызываем Designer, который и возвращает форму периода проектирования. Теперь добавление элементов управления будет постоянным. Само добавление делается, как и ранее, никаких изменений в этом процессе здесь нет.
Однако есть некоторые естественные ограничения, на которые следует обратить внимание. Процедура AddDesignControls, добавляющая элементы периода проектирования, должна вызываться при закрытой форме и только один раз. Поэтому в конце этой процедуры добавлен текст, удаляющий из тестового документа командную кнопку (объект класса InlineShape), обработчик которой вызывал процедуру AddDesignControls.
Вот как выглядит форма, открытая после окончания работы этой процедуры:

Рис. П-4.  Форма с постоянно добавленными элементами

Вопрос второй: "Как импортировать


Ответ, кажется, очевидным - нужно переименовать один из компонентов, поскольку, действительно не могут существовать два компонента с одним именем. Какой же из компонентов переименовывать? Здесь тоже все достаточно ясно, - импортируемый компонент программно в момент его добавления не переименуешь, поскольку он скрыт в файле. Поэтому остается переименовать компонент в самом проекте. Остается понять, какой именно компонент вызвал коллизию и требует переименования. Часто подсказкой может служить имя файла с импортируемым компонентом, поскольку по умолчанию имя файла совпадает с именем компонента. В моем примере я переименовываю все компоненты текущего проекта. Это одно из возможных решений. Главное, демонстрируется возможность переименования компонент.

Но обо всем по порядку. Несколько слов о самом примере. В наш документ, содержащий форму со стандартным именем 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. Все права защищены.