Часть 1. Основы Visual Basiс
На главную самоучителя
22.03.2005
Глава 17.
Пользовательские типы данных. Файлы произвольного и двоичного
(бинарного) доступа. Перехват нажатий кнопок клавиатуры.
Скачать исходник примера "Base"

Пользовательские типы данных.

Иногда, при использовании некоторых операторов, API-функций, а также просто для своих нужд необходимо создавать собственные типы данных. Часто их называют структурами. По своей сути структура - это как бы одномерный массив , который мы запихиваем в одну переменную. Но в него могут входить данные разных типов.
Создание собственного типа данных осуществляется с помощью инструкции Type, который используется в секции General кода формы.
Например, для какого-нибудь условного прайса нам нужно хранить следующие данные:
Name - наименование товара
Kolichestvo - количество товара
Cost - цена товара
С одной строны мы можем просто объявить эти три переменные с необходимым нам типом и использовать их каждую в отдельность по необходимости:

Dim Name As String
Dim Kolichestvo As Long
Dim Cost As Single

Но мы также можем объединить эти три переменные в единую структуру, создав тип данных, например, под названием Price. Однако, поскольку, инструкция Type предназначена для использования во всем проекте, то и пользовательский тип данных должен создаваться в стандартном модуле:
Type Price
Name As String
Kolichestvo As Long
Cost As Single
End Type

Хотя, в принципе, ее можно написать и в форме в разделе деклараций с ключевым словом Private:
(Private Type Price)
Теперь нам, там же, в стандартном модуле, надо объявить какую-нибудь глобальную переменную с нашим типом данных. Назовем ее Stroka:

Public Stroka as Price

Теперь мы смело можем пользоваться нашей переменной. Напишем в какой-нибудь процедуре (например FormLoad) имя переменной Stroka и поставим точку, у нас тут же появиться всплывающее меню, предлагающее на выбор три переменные, входящие в нашу переменную Stroka, и мы можем присвоить каждой свое значение, в соответствии с ее типом. Помотри на рисунок 29:

Рисунок 29.
Внешне это напоминает, как будто мы работаем со свойствами переменной, но это не так.
Теперь, вооруженные этими знаниями, перейдем к файлам прямого доступа.

Файлы произвольного (прямого) доступа - база данных внутренними возможностями VB (тренируемся на кошках).

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

Name
Kolichestvo
Cost
Газонокосилка
2
156.17
Сноповязалка
5
312.76

Строка этой таблицы представляет собой запись, а столбец (например Name) - это поле. Таким образом в таблице показан файл с тремя полями, в котором храняться две записи (верхняя строка - просто название полей). При такой организации возможен доступ непосредственно к любой записи по ее номеру. При этом нет необходимости считывать весь файл. Кроме того, возможно считывать и записывать данные, один раз открыв файл и не закрывая его. Причем доступ может быть разрешен и другим пользователям локальной сети.
Файл нельзя читать текстовым редактором, это не текстовой файл.
Ранее, в бейсиках, с помощью таких файлов можно было организовать что-то типа базы данных. С появлением версии Visual Basic 3.0 этот подход утратил свою актуальность, поскольку появилась возможность доступа из Visual Basic к различным "настоящим" базам данных.
Однако с помощью таких файлов можно хранить служебную информацию собственной программы, а кроме того, иногда просто нецелесообразно организовывать доступ к серьезным базам типа Access, из-за небольших объемов данных или простоты структуры БД. Кроме того, работа с такими файлами поможет разобратся с принципами организации базы данных и окажет помощь в дальнейшем, когда мы будем рассматривать связь Visual Basic и Access.


Итак. Как и при работе с файлами последовательного доступа, файл прямого доступа надо открыть. Делается это также с помощью все того же многострадального оператора Open. Вот его синтаксис:

Open Имя_ файла For Random Access Тип_доступа Права_доступа As # Номер_файла Len=Длина_записи

где:
Имя_ файла - это, естественно, просто любое имя открываемого файла. Если файла с таким именем не существует, VB его создаст (пустым).
For Random - тип доступа - произвольный
Access - ключевое слово, указывающее, что далее идет тип доступа
Тип_доступа - позволяет установить доступ к файлу: Read - для чтения, Write - для записи, Read Write - для чтения и записи, и наконец если не указывать тип доступа, то по умолчанию - для чтения и записи.
Права_доступа - определяет права доступа к файлу различных пользователей одновременно (Shared - открыто для чтения и записи всем, кому не лень, Lock Read - никто другой не может читать открытый файл, Lock Write - никто другой не может записывать в файл, Lock Read Write - никто другой не может ни писать ни считывать данные файла)
As # Номер_файла - это просто число - идентификатор, как и в файлах последовательного доступа
Len=Длина_записи - определяет длину записи. Поскольку записи не могут быть произвольной или разной длины, необходимо раз и навсегда определить длину записи для данного набора. Здесь есть проблема, так как не всегда известна длина набора данных. Тем не менее если это значение меньше реальной длины записи, то возникнет ошибка, если же задать длину записи с "запасом", то файл будет занимать большое дисковое пространство.

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

Open App.Path & "\price.bzd" For Random Access Read Write Shared As #1 Len = 100
или в более коротком виде:
Open App.Path & "\price.bzd" For Random As #1 Len = 100

Теперь, после того, как файл открыт, мы можем производить в него запись и считывание.
Для записи в файл используют оператор Put, а для считывания - оператор Get, синтаксис которых совершенно идентичен:

Put #Номер_файла, Номер_записи, Переменная
Get #Номер_файла, Номер_записи, Переменная

где:
#Номер_файла - это понятно, что тот номер, под которым мы его открыли (у нас #1)
Номер_записи - номер записи, в которую мы записываем данные (в соответствующие поля). Первой записи соответствует номер 1, второй - 2 и т.д. Если номер записи опущен, то записывается запись, на которой стоит указатель после выполнения последнего оператора Put или Get, или та, на которую установлен указатель с помощью функции Seek (см. ниже).
Переменная - это данные, которые мы записываем в файл. При наличии обычной переменной мы сможем сохранить в запись одно-единственное поле. Чтобы сохранить или считать несколько полей в записи, необходимо использовать пользовательский тип данных, о чем мы говорили в начале главы.

Попробуем создать что-то, похожее на простенькую базу данных, внешне похожую на Виндусовый Access. Мы реализуем некоторые возможности БД, такие как создание записи, считывание, удаление записи, поиск записи по содержимому поля. И по ходу разработки познакомимся с сопутствующими этому делу функциями, инструкциями и методами. Прошу учесть, это всего лишь пример, хотя и полностью рабочий. Мы сделаем базу наличия товара на складе на одной-единственной таблице в запись которой будут входит четыре поля: ID для хранения номера записи (чисто иллюстративно), Name - для наименования продукции, Kolichestvo - для количества единиц товара и Cost - для цены товара. Вычисляемые значения, такие как общая сумма товара полями не являются и как в настоящей базе, в файле не хранятся. Они будут вычисляться всякий раз при выводе данных на экран. Именно так происходит и в Accesse.

Итак создадим новый exe-проект. Положим на форму Form1 следующие объекты (рис. 30)

Рисунок 30.

В таблице слева направо:
- первый столбец - массив кнопок Command1 (Control Array). Как их создавать я не раз уже говорил (например смотри Главу 13). Мы будем выводить на экран по 10 записей, поэтому и массивы объектов у нас содержат по 10 элементов (с Command1(0) по Command1(9). Эти кнопки предназначены для удаления соответствующей записи.
-второй столбец - массив лейблов Label12(0)-Label12(9). Они служат только для показа номера записи.
-третий столбец - массив текстбоксов Text1(0)-Text1(9). Сюда будет выводиться содержимое поля Name (наименование товара).
-четвертый столбец - массив текстбоксов Text2(0)-Text2(9). Сюда будет выводиться содержимое поля Kolichestvo (количество товара).
-пятый столбец - массив текстбоксов Text3(0)-Text3(9). Сюда будет выводиться содержимое поля Cost (цена единицы товара).
-шестой столбец - массив лейблов Label1(0)-Label1(9). Здесь будет вычисляемое значение суммы по наименованию товара (Количество умножить на Цену).

Как и положено в настоящей базе, у нас будет и итог по столбцам: Label2 вычисляет количество единиц товара, а Label3 - общую сумму за весь товар. Кроме того у нас будет вертикальный скроллбар Vscroll1 для просмотра нашей таблицы, а также для поиска по наименованию кнопка Command2 и текстбокс Text4. Ну есть еще лейблы общего заголовка и заголовков столбцов и надписи Итого, но они только для красоты и могут быть любыми.

Сразу создадим стандартный модуль Module1 (или Модуль1) - это уж у кого какой VB, и зададим там пользовательский тип Price для переменной Stroka, которую мы там же и объявим:

Option Explicit
'создаем пользовательский тип
Type Price
ID As Long
Name As String * 28
Kolichestvo As Integer
Cost As Single
End Type
'объявляем переменную Stroka с типом Price
Public Stroka As Price

На фига я, спрашивается Name умножил на 28. А это я ничего не умножил, а задал длину строковой переменной, в которую будет записываться наименование товара. Все, что длинее, будет усекаться справа до 28 символов, а строки короче будут дополняться пробелами опять-таки справа до 28 символов. Почему 28? А потому, что я от фонаря на форму положил текстбокс Text1, и в него влезло только 28 буковок. Длины полей числовых переменных определяются их типом и свободное место справа тоже заполняется пробелами.
Обратите внимание на тип данных в пользовательской структуре. Поскольку я предполагаю, что количество товара - целое число и количество записей не будет чрезмерным, то Kolichestvo имеет тип Integer. А вот Cost - цена, может быть дробной, поэтому - Single. Я не использовал денежное представление числа, хотя можно было бы.
Ну вот теперь о модуле можно забыть и полностью отдаться форме.
Закрываем модуль и пишем в форме. Сперва, конечно, объявим несколько переменных:


Option Explicit
Dim BeginStroka As Long 'номер записи, с которой начинается вывод десяти строк на экран
Dim LastStroka As Long 'номер следующей за последней записью в
'файле (куда будет записываться новая запись)

Dim OldIndex As Integer 'значение старого индекса массива объектов (Control array) до обновления
Dim IndexForSub As Integer 'промежуточная переменная для передачи значения индекса
'элемента массива из одной процедуры в другую

Dim ItogProm As Single ' промежуточный итог от умножения поля Kolichestvo на Cost

Далее продолжим процедурой загрузки формы:

Private Sub Form_Load()
OldIndex = 0 'обнуляем переменную и текстбокс
Text4.Text = ""
Command2.Caption = "Поиск" 'назначаем кнопке название
Open App.Path & "\price.bzd" For Random As #1 Len = Len(Stroka) 'открываем наш долгожданный файл
' произвольного доступа. В качестве длины нашей записи используем оператор Len(Stroka). Однако при
'использовании различных типов данных возможна ситуация, когда длина записи больше фактической
'длины записи как минимум на 2 байта. Это вызвано тем, что VB прописывает собственные
' дополнительные служебные байты. Но в нашем случае все работает и так.

Do While Not EOF(1) 'Запускаем цикл чтения записей до конца файла.
Get #1, , Stroka ' Поскольку номер записи мы не указали, указатель записи с каждым проходом цикла
'сдвигатся на один и оператор читает следующую запись.

Loop
LastStroka = Seek(1) - 1'узнем количество записей в файле c помощью инструкции SEEK (см. ниже)

'Задаем свойства для Vscroll, чтобы у нас корректно выводилось на экран и менее 10 записей
VScroll1.Min = 1
If LastStroka > 10 Then
VScroll1.Max = LastStroka 'максимальное значение соответствует последней+1 записи
VScroll1.LargeChange = 10
Else
VScroll1.Max = LastStroka 'максимальное значение соответствует последней+1 записи
VScroll1.LargeChange = 1
End If

VScroll1.SmallChange = 1
VScroll1.Value = 1
BeginStroka = VScroll1.Value 'присваиваем переменной начала вывода значение VScroll
SummaCount 'переходим к процедуре вычисления итоговых значений
End Sub

А теперь разберем функцию Seek

Функция Seek.

Ее синтаксис следующий:

Seek(Номер_файла)

Эту функцию можно использовать для файлов, открытых в режиме Random (как в нашем случае) и Binary.
Для файлов, открытых в режиме Random. С помощью этой функции мы можем узнать текущее положение указателя чтения записи внутри файла. То есть в нашем примере , после считывания всего файла указатель устанавливается на последней записи. А использовав эту функцию, мы узнаем, какая запись будет считана (обработана) следующей. Но поскольку следующей записи пока не существует, это будет номер создания новой записи.
Для файлов, открытых в режиме Binary. Поскольку режимы Random и Binary аналогичны, то и функция Seek здесь работает также, за исключением того, что возвращает она не значение номера записи, а номер байта, с которого начнется выполнение следующей операции (считывания или записи). Файлы, открытые в режиме Binary мы разберем в конце этой главы.
Заметь, что кроме функции Seek существует еще инструкция Seek#
:

Инструкция Seek#.

Синтаксис:
Seek
#Номер_файла, позиция
Номер_файла - номер открытого файла
позиция - номер записи или байта, на который установится указатель

В отличие от своей одноименной функции, инструкция Seek задает положение указателя чтения или записи, т.е. номер записи в режиме Random или номер байта в режиме Binary, с которых начнется выполнение операции. Причем, если в операторах Put или Get задан номер записи, то инструкция Seek игнорируется.
Установка указателя за конец файла приводит к увеличению размера файла.

Теперь вернемся к коду нашей программы. В конце процедуры Form_Load у нас переход к процедуре SummaCount, где вычисляется итоговые значения по столбцам и обновляется переменная LastStroka . Но чтоб туды перейти, ее надо создать. В ней мы высчитываем итоговые значения по полю Kolichestvo и по сумме. Поля по сумме у нас нет - это вычисляемое значение, поэтому мы суммируем произведения Kolichestvo * Cost по каждой записи. Кроме того Здесь же мы вычисляем новое количество записей в файле. Обратите внимание, что, мы, раз открыв файл, больше его не закрываем, а постоянно обращаемся к нему и для записи и для считывания.

Private Sub SummaCount()
Dim ItogAll As Single
Dim ItogKol As Single
Seek #1, 1 'устанавливаем указатель на первую запись инструкцией Seek#
Do While Not EOF(1) ' Cчитываем весь файл до конца.
Get #1, , Stroka ' Читаем в цикле каждую запись.
ItogAll = ItogAll + (Stroka.Kolichestvo * Stroka.Cost)
Label3.Caption = ItogAll 'выводим итог по сумме
ItogKol = ItogKol + Stroka.Kolichestvo
Label2.Caption = ItogKol 'выводим итог по количеству товара
Loop
LastStroka = Seek(1) - 1 'обновляем количество записей
' переходим к процедуре вывода данных на экран
ShowRecords
End Sub

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

Функции Trim, LTrim и RTrim.

Эти функции удаляют пробелы из начала строки - слева (LTrim), с конца строки - справа (RTrim) или с обоих концов строки сразу (Trim). Синтаксис этих функций (как и использование) предельно простой:
LTrim(строка)
RTrim(строка)
Trim(строка)

где строка - любое строковое выражение

Теперь вернемся к нашим баранам, т.е., виноват, к процедуре ShowRecords. Давайте, наконец, ее создадим:

Private Sub ShowRecords()
Dim X As Long
'включаем обработку ошибок, цель ее в том, чтобы предотвратить ситуацию,
'когда при наличии менее, чем 10 записей в файле
' или если файла вообще не существует, оператор Get# в цикле For... Next будет пытаться
' считать несуществующие записи.

On Error Resume Next
For X = 0 To 9
'считываем запись, номер которой складывается из индекса текстбокса и номера строки,
' которая выводится на экран первой в текстбокс с индексом 0.

Get #1, X + BeginStroka, Stroka
'выводим содержимое полей в текстбоксы и лейбл, удаляя ненужные пробелы справа
Text1(X) = RTrim(Stroka.Name)
Text2(X) = RTrim(Stroka.Kolichestvo)
Text3(X) = RTrim(Stroka.Cost)
Label12(X).Caption = RTrim(Stroka.ID)
'а для последней, еще не существующей записи вместо нуля выведем для красоты стрелку
'в Caption кнопки Command1 (которая вообще-то предназначена для удаления записи,
' а в Label12 - стрелочку. У нас будет, ну прямо как в Access'е

If Label12(X).Caption = 0 Then
Label12(X).Caption = "new"
Command1(X).Caption = "->"
End If
'кроме того в Label1 выведем результат умножения Количества на Цену
ItogProm = Stroka.Kolichestvo * Stroka.Cost
Label1(X).Caption = ItogProm
'нижеследующие условия нужны для того, чтобы сделать невидимыми текстбоксы, лейблы
'и кнопки, которые будут после окончания файла. Таким образом, пользователь не сможет
' при последней записи, например, 20 ввести данные в запись 26.

If LastStroka - BeginStroka < 10 And X + BeginStroka > LastStroka Then
Text1(X).Visible = False
Text2(X).Visible = False
Text3(X).Visible = False
Label1(X).Visible = False
Label12(X).Visible = False
Command1(X).Visible = False
Else
Text1(X).Visible = True
Text2(X).Visible = True
Text3(X).Visible = True
Label1(X).Visible = True
Label12(X).Visible = True
Command1(X).Visible = True
End If
Next X
'отключаем обработку ошибок
On Error GoTo 0
End Sub

Чегой-то мы устанавливали свойства VScroll1, даже использовали ее значение, а самой процедуры нет. Быстренько ее пишем:

Private Sub VScroll1_Change()
BeginStroka = VScroll1.Value
ShowRecords
End Sub


Из нее мы вызываем процедуру ShowRecords, чтобы обновить записи на форме.

Ну вот, теперь нам нужна процедура, которая бы записывала данные в файл. Написать ее довольно просто, но нам нужно событие, которое мы бы могли использовать для инициализации сохранения записи на файле. Тут возможны самые разнообразные решения, зависит от степени извращенности. Я использовал событие _GotFocus, которое возникает при получении объектом фокуса. Надо заметить, что объекты, для которых пишется процедура с событием _GotFocus должны быть видимы (Visible=True) и не заблокированы (Enabled=True). Все эти условия удовлетворяют нашим текстбоксам. Поэтому для каждого из них пишем собственную процедуру:

Private Sub Text1_GotFocus(Index As Integer)
IndexForSub = Index
MakeRecord
End Sub

Private Sub Text2_GotFocus(Index As Integer)
IndexForSub = Index
MakeRecord
End Sub

Private Sub Text3_GotFocus(Index As Integer)
IndexForSub = Index
MakeRecord
End Sub

Переменная IndexForSub служит для хранения индекса текстбокса, получившего фокус, так как Index доступен в пределах только своей процедуры. И из каждой процедуры мы вызываем процедуру MakeRecord. Она-то и будет записывать нашу запись в файл (тавтология: масло маслянное, я думал об этом с начала главы, но не смог ничего придумать).
Поскольку фокус получает новая запись, а записывать-то надо старую, то номер старой записи храниться в переменной OldIndex. Изначально мы ей присвоили значение 1 в процедуре Form_Load. Эта запись и запишется первой. А в конце процедуры записи MakeRecord мы присвоим OldRecord значение IndexForSub, которая теперь станет номером старой, т.е. текущей записи. Только не спрашивайте, зачем такой гемморой, и почему я не использовал событие _LostFocus, которое возникает при утрате объектом фокуса. Ты можешь попробовать это сам, типа потренируйся. Прелесть программирования и заключается в том, что гланды можно удалить и через жопу и другими разнообразнейшими способами. Тем более, что мне захотелось показать, как применять переменную, которая постоянно обновляет свое значение (OldIndex). Это я отмазался. На самом деле просто так захотелось.

Cамое время все-таки написать (ударение на последнем слоге - замучился искать синонимы) нашу процедуру MakeRecord.

Private Sub MakeRecord()
Dim ItogProm As Single
'При условии, что хотя бы один текстбокс что-нибудь, да содержит
If Len(Text1(OldIndex)) > 0 Or Val(Text2(OldIndex)) <> 0 Or Val(Text3(OldIndex)) <> 0 Then
'присваиваем пользовательской переменной их значения
Stroka.Name = Text1(OldIndex)
Stroka.Kolichestvo = Val(Text2(OldIndex))
Stroka.Cost = Val(Text3(OldIndex))
Stroka.ID = OldIndex + BeginStroka
'сразу выводим номер записи, на случай, если это новая запись
Label12(OldIndex).Caption = RTrim(Stroka.ID)
'а также удаляем стрелочку из Caption кнопки
Command1(OldIndex).Caption = ""
'собственно записываем запись
Put #1, OldIndex + BeginStroka, Stroka
End If
'получаем номер новой старой записи
OldIndex = IndexForSub
'и отправляемся считать итоги, а оттуда - вывод полей по-новой
SummaCount
End Sub

В основном ядро программы сделано.
Далее, поскольку мы налепили целый массив кнопок Command1 для уничтожения записи, надо стало-быть ваять под это дело процедуру. Для уничтожения записи я использовал такой метод: мы просто переписываем наш файл в новый под именем Copyprice.bzd без той записи, которую мы хотим удалить, затем закрываем текущий файл и удаляем его. А новую копию переименовываем в старый и открываем его по-новой. Все это реализовано в процедуре Command1_Click:

Private Sub Command1_Click(Index As Integer)
Dim X As Integer
Dim
Z As Integer
'открываем файл-копию
Open App.Path & "\Copyprice.bzd" For Random As #2 Len = Len(Stroka)
For X = 1 To LastStroka - 1
'считываем запись с основного файла
Get #1, X, Stroka
'проверяем, чтобы не перезаписать уничтожаемую запись
If Index + BeginStroka <> X Then
Z = Z + 1
Stroka.ID = Z
'записываем запись во вспомогательный файл
Put #2, Z, Stroka
End If
Next X
'закрываем все файлы
Close
'убиваем основной файл
Kill App.Path & "\price.bzd"
'переименовываем вспомогательный в основной
Name App.Path & "\Copyprice.bzd" As App.Path & "\price.bzd"
'открываем его, как основной
Open App.Path & "\price.bzd" For Random As #1 Len = Len(Stroka)
' вперед на обсчет суммы и далее по коду
SummaCount
End Sub


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

Private Sub Command2_Click()
Dim X As Long
Dim
FoundStroka As Boolean
FoundStroka = False 'флаг - если не найдено, значение не меняется,если найдено - становится True
'проверяем не пусто ли поле с критерием поиска, а то всяко бывает
If Text4.Text <> "" Then
For X = 1 To LastStroka - 1
'Считываем записи
Get #1, X, Stroka
'находим первое вхождение
If InStr(RTrim(Stroka.Name), Text4.Text) Then
'в соответствии с номером строки меняем VScroll, чтобы искомая запись стала первой
BeginStroka = X
VScroll1.Value = X
'Устанавливаем флаг
FoundStroka = True
End If
Next
X
'выводим сообщение, если ничерта не найдено

If FoundStroka = False Then MsgBox "Результат поиска", , "Ничего не найдено"
'устанавливаем фокус на найденную строку (первую)
Text1(0).SetFocus
'переписываем текстбоксы
ShowRecords
End If
End Sub


Здесь мы для задания фокуса Text1(0) использовали метод SetFoсus. Он передает фокус заданному элементу управления, в нашем случае текстбоксу. Фокус можно передавать только видимому элементу - это как-бы понятно. Нельзя использовать этот метод внутри процедуры Form_Load, так как до ее окончания все элементы формы не видны. Чтобы сделать их видимыми до окончания процедуры Form_Load, можно использовать метод Show.

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

События и перехват кнопок клавиатуры.

Private Sub Form_KeyDown(код_клавиши As Integer, shift As Integer)
Private Sub объект_KeyDown([индекс As Integer,]код_клавиши As Integer, shift As Integer)


Private Sub Form_KeyUp(код_клавиши As Integer, shift As Integer)
Private Sub объект_KeyUp([индекс As Integer,]код_клавиши As Integer, shift As Integer)

Возникает при нажатии кнопки клавиатуры (KeyDown) и при отпускании (KeyUp), если объект получил фокус.
Индекс
определяет элемент массива , если имеется массив объектов. Код клавиши возвращает код клавиши, которые могут определятся константами VB.
Переменная shift возвращает состояние кнопок shift, alt и ctrl в момент нажатия кнопок клавиатуры (1- shift(vbShiftMask ), 2- alt (vbCtrlMask) и 4 - ctrl (VbAltMask )). При удержании больше одной кнопки их значения складываются (ctrl-alt = 4+2=6).
Свойство не вызывается для:
- клавиши ENTER, если на форме есть элемент управления CommandButton, свойство Default которого имеет значение True;
- клавиши ESC, если на форме есть элемент управления CommandButton, свойство Cancel которого имеет значение True;
·- клавиши TAB.
События различают регистр при использовании кода_клавиши совместно с shift. Значение shift сравнивается с битовой маской с использованием оператора And:
( ShiftDown = (Shift And vbShiftMask) > 0)

Private Sub Form_KeyPress(код_ascii As Integer)
Private Sub объектt_KeyPress([индекс As Integer,]код_ascii As Integer)
Возникает при нажатии и отпускании кнопки клавиатуры, если объект получил фокус.
Индекс определяет элемент массива , если имеется массив объектов.
Код_ascii - стандартный ANSI код клавиши.
Событие KeyPress может получать любые печатаемые символы, символы клавиши CTRL в сочетании с символом стандартного алфавита или один из специальных символов, а также клавиши ENTER или BACKSPACE.
Распознает верхний и нижний регистры каждого символа как различные коды клавиш.
Если присвоено значение True свойства KeyPreview, то форма получает событие раньше, чем элементы управления формы.

Теперь вернемся к нашей программе. После ввода данных в текстбокс и нажатии клавиши ENTER хорошо бы, чтобы курсор , то бишь фокус передавался следующему текстбоксу. Тогда это вызовет событие LostFocus И наша запись обновится. И выглядеть это будет красиво. Для этих целей мы можем использовать, в принципе, любое событие из перечисленных в таблице. Возьмем, например _KeyDown и для каждого техтбокса напишем по короткой процедурке:

Private Sub Text1_KeyDown(Index As Integer, KeyCode As Integer, Shift As Integer)
If KeyCode = 13 Then
IndexForSub = Index
Text2(Index).SetFocus
MakeRecord
End If
End Sub


Private Sub Text2_KeyDown(Index As Integer, KeyCode As Integer, Shift As Integer)
If KeyCode = 13 Then
IndexForSub = Index
Text3(Index).SetFocus
MakeRecord
End If
End Sub


Private Sub Text3_KeyDown(Index As Integer, KeyCode As Integer, Shift As Integer)
If KeyCode = 13 Then
Text1(Index + 1).SetFocus
IndexForSub = Index
MakeRecord
End If
End Sub


Код клавиши ENTER - 13. Мы отслеживаем ее и передаем фокус следующему текстбоксу. При этом не забываем, где нужно, запоминать индекс массива элемента объектов.
На этом код весь. Песня кончилася. Всю эту хрень ты можешь скачать вверху страницы. Еще раз повторяю, что это лишь пример, а не полнофункциональная программа.
А сейчас, напоследок нам надо разобрать пример работы с файлом двоичного доступа.

Работа с файлами посредством двоичного (бинарного) доступа.

Работа с файлами, открытыми для двоичного доступа практически такая же, как и с файлами произвольного доступа. Разница заключается в том, что мы считываем не запись, а отдельный байт. При этом файл открывается в режиме Binary. Синтаксис оператора Open cледующий:

Open Имя_ файла For Binary Access Тип_доступа Права_доступа As # Номер_файла

где все тоже самое, см. файл произвольного доступа в начале главы. Зато здесь нет необходимости парится с пользовательскими типами данных и главное с длиной записи. Поскольку ничего этого нет. Байт мы читаем прямо в переменную.
С помощью такого доступа мы можем залезть в любой файл, считать байты, которые нам нужны, записать в файл свои, какие хотим, после этого файл скорее всего работать не будет. Потому что, прежде чем лезть писать (снова ударение на последнем слоге), надо знать структуру файла данного формата, будь то .exe или там jpg, но тем не менее разберем маленький примерчик. Кстати, структуры графических файлов BMP, GIF и JPG(JFIF) и кое-какие издевательства над ними с помощью именно бинарного чтения файлов опубликованы в Части 3 "Реализация некоторых задач".
Для информации, если кому интересно: в конец exe-файла можно дописать чего угодно и он останется работоспособным.

Но продолжим.
Считывание и запись осуществляется с помощью все тех же операторов Get и Put.
Практически это делается так.
Давайте создадим exe-проектик, положим на него Text1 и Command1. В процедуре кнопки и будем писать код. Переменная, в которую мы будем считывать байт назовем Bait. Обратите внимание, что объявляться она должна с типом Byte. Итак, чтобы нам считать пятый байт из файла save.Bmp и его содержимое отобразить в Text1, нам надо написать следующее:

Private Sub Command1_Click()
Dim Bait As Byte
Text1.Text = ""
Open App.Path & "\save.Bmp" For Binary As #1
Get #1, 5, Bait
Text1.Text = Bait
Close #1
End Sub

Однако в Text1 большинство символов не отобразится, поэтому лучше использовать не содержимое Bait, а его ASCII-код. Для этого можно использовать функцию AscB(строка_байтов). Она похожа на функцию Asc, но предназначена специально для байт в строке:

Text1.Text =AscB(Bait)

Для того, чтобы считать несколько байтов, можно использовать цикл. Считаем 10 байт с 12 по 21-й:

Private Sub Command1_Click()
Dim x As Integer
Dim
Bait As Byte
Text1.Text = ""
Open App.Path & "\save.Bmp" For Binary As #1
For x = 12 To 21
Get #1, x, Bait
Text1.Text = Text1.Text & "*" & AscB(Bait)
Next x

Close #1
End Sub

Иным способом это можно сделать, исходя из того, что число считываемых байт равняется числу символов, уже содержащихся в строке. Поэтому, если установить указатель с помощью функции Seek# на 12 байт и передать в переменную 10 пробелов, мы должны получить тот же результат. К сожалению, в этом случае функция AscB считает лишь первый байт:
В этом случае переменную Bait надо объявлять как String (строковая).

Private Sub Command1_Click()
Dim x As Integer
Dim Bait As String
Text1.Text = ""
Open App.Path & "\save.Bmp" For Binary As #1
Seek #1, 12
Bait = String(10, " ")
Get #1, , Bait
Text1.Text = AscB(Bait)
Close #1
End Sub

И в конце, если мы положим на форму кнопку Command2, то в ее процедуре можем попробовать написать код, который будет записывать в первые 10 байтов нашего несчастного файла букву "Ш" или ее код - 216:

Private Sub Command2_Click()
Dim x As Integer
Dim
Bait As String
Text1.Text = ""
Open App.Path & "\save.Bmp" For Binary As #1
Bait = Chr(216)
For x = 1 To 10
Put #1, , Bait
Text1.Text = Text1.Text & "*" & (Bait)
Next x
Close #1
End Sub


При очень большом желании исходник последнего варианта можно скачать здесь.


Copyright © 2005 4us



Сайт создан в системе uCoz