Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Конспект VB.doc
Скачиваний:
1
Добавлен:
01.07.2025
Размер:
2 Мб
Скачать
☆

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, shiftAs 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

При работе с приложениями Windows часто используется такой прием, когда юзер может нажать над объектом левую кнопку мыши, и не отпуская ее переместить мышь в другое место. При этом образ объекта следует за курсором мыши. При отпускании кнопки объект перемещается в новое место (если это предусмотрено программистом). Такая технология называется Drag & Drop - перетаскивание (или перетащил и оставил).

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

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

Private Sub Объект_DragDrop([индекс As Integer,]source As Control, x As Single, y As Single)

Происходит при завершении операции drag&drop, т.е. перетаскиваемый объект находится в зоне объекта-приемника и кнопка мыши отпускается.  Индекс определяет элемент массива , если имеется массив объектов.  source -аргумент, определяющий перетаскиваемый элемент (содержимое). К нему могут добавляться методы и свойства (Sourse.Tag, Sourse.Visible и т.п.). Если используется несколько объектов для перетаскивания, то для определения типа элемента-приемника используется ключевое слово TypeOf с оператором If, а для однозначного определения объекта используется свойство Tag. x, y - текущие координаты мыши на форме или объекте. Всегда измеряются в единицах объекта-приемника. Событие используется для создания процедуры объекта-приемника после окончания перетаскивания для операторов, описывающих действия результата пертаскивания.

Private Sub Объект_DragOver([индекс As Integer,]source As Control, x As Single, y As Single,state As Integer)

Происходит во время выполнения операции drag&drop, т. е. при нажатой кнопки мыши. Индекс определяет элемент массива , если имеется массив объектов.  source -аргумент, определяющий перетаскиваемый элемент (содержимое). К нему могут добавляться методы и свойства (Sourse.Tag, Sourse.Visible и т.п.). x, y - текущие координаты мыши на форме или объекте. Всегда измеряются в единицах объекта-приемника. state - показывает мгновенное состояние перетаскиваемого объекта по отношению к области объекта-приемника (область цели) и принимает значения: 0 (vbEnter)= Вход (элемент управления–источник входит в область цели). 1 (vbLeave)= Выход (элемент управления–источник покидает область цели). 2 (vbOver)= Над (элемент управления источник перемещается в пределах области цели из одного положения в другое).  С помощью этого события создаются процедуры для визуализации операции перетаскивания (изменения курсора, подсвечивание объекта).

Теперь можно начинать чего-нибудь потаскать. Начнем с малого, а именно с автоматического перетаскивания.

Автоматическое перетаскивание.

Автоматическое перетаскивание - наиболее простая и наиболее худшая реализация технологии Drag&Drop. Делается это элементарно. У многих объектов имеется свойство DragMode. Оно определяет режим перетаскивания для объекта и может принимать два значения: .DragMode=0 , т. e. вручную. и.DragMode=1 , т.е. автоматически. По умолчанию оно в проекте всегда установлено состояние 0, то бишь ручное, и это правильно. Но мы идем идиотским путем (а его нужно пройти обязательно), поэтому установим свойство DragMode=1. Это можно сделать в окне свойств, а можно программно. Давайте создадим новый exe-прокт и посмотрим, как это все работает. Положим на форму Text1, Text2 и Text3 со свойствами Multiline=True. Сейчас мы попытаемся организовать перетаскивание содержимого Text1 и Tex2 в Text3. И это нам удастся. Пишем код. Начнем со стандартного (хоть тут и нет переменных) Option Explicit В загрузке формы установим автоматический режим свойства DragMode для Text1 и Text2 и заполним их текстом, а Text3 очистим: Private Sub Form_Load() Text1.DragMode = 1 Text2.DragMode = 1 Text1.Text = "Это какой-то текст, записанный в Text1" Text2.Text = "А это другой текст, записанный в Text2"  Text3.Text = "" Да, чуть не забыл. Нам дана крутая возможность во время перетаскивания менять изображение курсора мышки. Обалдеть можно. Хотя, на самом деле, это важно. Юзер должен контролировать состояние перетаскивания. Ну и на этом спасибо. Курсор меняется с помощью свойства .DragIcon. Это свойство определяет значок, который будет использоваться в качестве указателя мыши при операции перетаскивания. Ты можешь в окне свойств установить свойству DragIcon какую-нибудь иконку - файл с расширением .ico. Мы для ясности сделаем это программно. Text1.DragIcon = LoadPicture(App.Path & "\Move.ico") Text2.DragIcon = LoadPicture(App.Path & "\Move.ico") Но при программной загрузке картинок их всегда надо таскать с проектом, а при загрузке картинок в окне свойств они становятся частью проектов и отдельно их хранить не надо. Все. Процедура Form_Load закончена. End Sub Теперь логика процесса следующая. Мы будем использовать для создания процедуры событие DragDrop, а поскольку оно возникает по окончании процесса перетаскивания, то процедуру надо создавать в Text3. Ведь именно там конечный пункт нашего процесса. Private Sub Text3_DragDrop(Source As Control, X As Single, Y As Single)  Координаты мыши X и Y нас сейчас мало интересуют, все равно VB сам определит, перетащили мы объект на Text3 или нет. А вот аргумент Source мы используем. С его помощью и ключевого словаTypeOf мы проведем проверку типа объекта (то что мы перетаскиваем Text в Text, а не Text в Picture) и присобачив к нему свойство .Text изменим содержимое Text3. Это и будет результат перетаскивания. If TypeOf Source Is TextBox Then Text3.Text = Source.Text End If End Sub  Теперь ты можешь опробовать результат, запустив проект и поперетаскивать в Text3 и Text1 и Text2. Если ты не смог написать эти пятнадцать строк кода самостоятельно, то скачать исходник примера можешь здесь.  Откровенно говоря в данном случае ключевое слово TypeOf притянуто за уши, так как других объектов на форме нет. Однако, проверять, соответствует ли тип данных передатчика типу данных приемника все-таки надо. Можно вместо этого проверять конкретно каждый элемент и присваивать значение того, которого захотим. Для идентификации элемента есть простенькое свойство Tag. Смысл его в том, что по нему с помощью аргумента Source мы можем однозначно идентифицировать объект-источник. В отличие от других свойств, значение свойства Tag не используется языком Visual Basic, но его можно использовать для идентификации объектов. Этому свойству мы можем присвоить любое имя-идентификатор, главное, чтоб они не были одинаковыми. Для этого в процедуру Form_Load допишем две строчки: Text1.Tag = "Text один" Text2.Tag = "002" Теперь наша процедура Text3_DragDrop будет выглядеть следующим образом: Private Sub Text3_DragDrop(Source As Control, X As Single, Y As Single) If Source.Tag = "Text один" Or Source.Tag = "002" Then Text3.Text = Source.Text End If End Sub Опять же, если чего не получилось, скачать эту фигню можно здесь. Теперь рассмотрим результаты нашей деятельности. Юзер кайфует, радостно перетаскивая текст из одного объекта в другой. Это несомненно хорошо. Потом юзеру надоедает это делать и он с удивлением обнаруживает тот факт, что отредактировать-то текст он не может ни в Text1, ни в Text2. Это несомненно плохо. А дело в том, что инициируемые юзером события мыши или клавиатуры KeyDown, KeyPress, KeyUp, MouseDown, MouseMove и MouseUp распознаваться НЕ БУДУТ! Видя такое дело, нам целесообразно перейти к более сложному, но и более контролируему делу - ручному перетаскиванию.

Метод Drag - ручное перетаскивание.

Этот метод начинает, завершает или отменяет операцию перетаскивания любого из элементов управления кроме Line, Menu, Shape, Timer и CommonDialog. Он позволит нам не блокировать объект для редактирования, использовать всевозможные иконки, чтоб юзер видел, что творит при перетаскивании и вообще правильный метод. Еще раз напоминаю очень важную вещь, если ты не понял из предыдущего: результат перетаскивания над перемещаемым объектом (копирование, удаление, вставка и т.д.) определяется в объекте-приемнике в процедуре обработки события DragDrop. Итак, для того, чтобы начать чего-нибудь перетаскивать, надо перейти в режим перетаскивания. Для этого обычно используется процедура события MouseDown для объекта-источника. В ней мы включаем метод Drag. Для включения-выключения метода и отмены перетаскивания используются следующие константы, их объявлять не надо: vbCancel =0 'отменяет операцию перетаскивания vbBeginDrag =1 'включает метод Drop vbEndDrag =2 'выключает метод Drop

Давай-ка лучше сделаем новый exe-проект и попробуем все на практике. Наш новый проект мало чем отличается от старого по внешнему виду, но не по коду. На форме у нас будут Text1, Text2, Text3 и еще кнопка Command1. Смысл проекта в том, чтобы перетаскивать содержимое Text1 в Text3, а Text2 запрещен для перетаскивания. И для прикола, потаскаем по форме командную кнопку Command1. Итак, начнем. Объявим переменную Flag как булеву. На фига я ее ввожу? Ну не знаю, чтобы позволить юзеру беспрепятственно редактировать текст в объекте-источнике, т.е в Text1. Т.е. изначально Flag=False и при первом щелчке метод Drag в процедуре Text1_MouseDown не включается. Юзер спокойно может редактировать текст. Но в конце процедуры Flag меняет свое значение на противоположный (Flag = Not Flag) и при повторном нажатии кнопки мыши уже включается метод Drag.  Option Explicit Dim Flag As Boolean В процедуре загрузки формы зададим какое-нибудь содержимое TextBox'ам (можем и не задавать и вообще наплевать на нее): Private Sub Form_Load() Text1.Text = "Это какой-то текст, записанный в Text1" Text2.Text = "А я вообще не знаю, что здесь делаю" Text3.Text = "А это другой текст, записанный в Text3" End Sub Далее в событии MouseDown для объекта-источника Text1 включим метод Drag для левой кнопки мыши. И меняем состояние переменной Flag: Private Sub Text1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) 'проверяем, если нажата левая кнопка мыши и это повторное нажатие то If Button = vbLeftButton And Flag = True Then 'включаем метод Drag Text1.Drag vbBeginDrag 'загружаем общую иконку для курсора в виде молнии. Она будет обозначать, 'что объект готов к перетаскиванию Text1.DragIcon = LoadPicture(App.Path & "\Move.ico") End If 'меняем значение флага на противоположное Flag = Not Flag End Sub В объекте-приемнике по событию DragDrop производим результат перетаскивания, т.е. присваиваем значение Text3=Text1. При этом проверяем тип объекта-источника: Private Sub Text3_DragDrop(Source As Control, X As Single, Y As Single) 'проверяем, соответствует ли тип объекта-источника TextBox'у If TypeOf Source Is TextBox Then 'если да, то  Text3.Text = Source.Text 'и для солидности передадим фокус в объект-приемник (Text3) Text3.SetFocus End If  End Sub Поскольку наглядность перетаскивания для юзера чрезвычайно важна, загрузим иконки в соответствии с положением мыши для объекта-источника (Text1): Private Sub Text1_DragOver(Source As Control, X As Single, Y As Single, State As Integer) 'если мышь над объектом-источником грузим обычную иконку (молния) If State = vbEnter Then Text1.DragIcon = LoadPicture(App.Path & "\Move.ico") 'если покидает объект, то критический треугольник ElseIf State = vbLeave Then Text1.DragIcon = LoadPicture(App.Path & "\No.ico") End If End Sub и для объекта-приемника. Причем, обратите внимание, что в процедуре Text3 объекта-приемника мы определяем иконки для объекта-источника (Text1). В противном случае возникнет путаница.  Private Sub Text3_DragOver(Source As Control, X As Single, Y As Single, State As Integer) 'если мышь над объектом-приемником, то листик с кнопкой - готовность принять объект  If State = vbEnter Then Text1.DragIcon = LoadPicture(App.Path & "\Yes.ico") 'если вне объекта-приемника - то критический треугольник ElseIf State = vbLeave Then Text1.DragIcon = LoadPicture(App.Path & "\No.ico") End If End Sub  Вот вобщем-то и все. В заключении я хочу показать, что использование проверки типа объекта-источника важна. На этой же форме включим метод Drag для командной кнопки Command1. Private Sub Command1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) If Button = vbLeftButton Then Command1.Drag vbBeginDrag End If End Sub и напишем процедуру ее таскания по форме Private Sub Form_DragDrop(Source As Control, X As Single, Y As Single) If TypeOf Source Is CommandButton Then Source.Move X, Y End If End Sub Если бы мы не проверяли тип объекта с помощью ключевого слова TypeOf, то при попытке перетащить кнопку в Text3 получили бы ошибку и фатальное завершение программы. Можешь попробовать. А при наличии проверки наши методы не мешают друг другу и все проходит успешно.

В этой главе мы разобрали технологию Drag&Drop для перетаскивания в пределах одного нашего приложения. Скачать исходник примера можно вверху страницы. В следующей главе мы попробуем исследовать эту технологию для обмена данных между разными приложениями.

Итак, в прошлой главе мы проанализировали технологию Drag&Drop при перетаскивании под управлением событий. Это хорошо при работе внутри нашего проекта. Если же нам необходимо осуществить перетаскивание из окна совсем другого приложения, то навряд-ли мы дождемся наступления какого-нибудь события для нашей формы (или ее объектов), если будем щелкать по чужому окну. Поэтому сейчас мы проанализируем перетаскивание с помощью OLE технологий. Принцип перетаскивания здесь тот же, что и при перетаскивании с использованием событий - есть объект-приемник и объект-источник. Один и тот же объект может быть и приемником и источником (в случае если он используется и для приема и для передачи). Как и в прошлой главе, начнем с чего попроще - с автоматического перетаскивания.

Автоматическое перетаскивание.

Очень простая и удобная технология. С его помощью мы просто перемещаем содержимое одного объекта в другой объект. Для включения автоматического перетаскивания мы имеем два свойства:

.OLEDropMode - активизирует объект в качестве приемника

может принимать значения:

0 (vbOLEDropNone) - Нет

1 (vbOLEDropManual) - Вручную

2(vbOLEDropAutomatic) - Авто

.OLEDragMode - активизирует объект в качестве передатчика

может принимать значения:

0 (vbOLEDragManual) - Вручную

1 (vbOLEDragAutomatic) - Авто

Эти свойства можно задавать как в окне свойств, так и программно, в ходе выполнения программы.  Но, к сожалению, не все элементы управления (объекты) поддерживают автоматическое перетаскивание. Некоторые элементы поддерживают оба свойства - и .OLEDropMode и .OLEDragMode, а некоторые - только OLEDragMode. А ряд элементов вообще нельзя использовать для автоматического перетаскивания. Вот элементы и поддерживаемые ими свойства.

Элемент управления с режимом авто (OLE перетаскивание)

.OLEDropMode

OLEDragMode

ComboBox

НЕТ

ДА

DBComboBox

НЕТ

ДА

DBGrid

ДА

ДА

DBListBox

НЕТ

ДА

DirecoryListBox

НЕТ

ДА

FileListBox

НЕТ

ДА

Image

ДА

ДА

ListBox

НЕТ

ДА

ListView

НЕТ

ДА

MaskedEdit

ДА

ДА

PictureBox

ДА

ДА

RichTextBox

ДА

ДА

TextBox

ДА

ДА

TreeView

НЕТ

ДА

Создадим новый exe-проект. Положим на форму PictureBox1 и Image1. Установим программно свойства перетаскивания в автоматический режим:

Private Sub Form_Load() Picture1.OLEDropMode = vbOLEDropAutomatic 'автоматический прием Picture1.OLEDragMode = vbOLEDragAutomatic 'автоматическая передача Image1.OLEDropMode = vbOLEDropAutomatic Image1.OLEDragMode = vbOLEDragAutomatic End Sub

Это, собственно, все. Запустим проект. Если теперь мы откроем какую-нибудь картинку в приложении, поддерживающем технологию OLE-перетаскивания (например Photoshop, но при этом должен быть включен инструмент "Двигать" ("Move")), то запросто можем перетащить картинку из него в наш проект и обратно.  Также можно перетаскивать и внутри нашего проекта из Picture1 в Image1 и наоборот. При этом обратите внимание на то, что при перетаскивании нажатой кнопкой мыши картинка в объекте-передатчике уничтожается, т.е. происходит перемещение. Чтобы происходило копирование (картинка сохранялась) перетаскивание надо осуществлять с нажатой кнопкой Ctrl.  При автоматической реализации OLE-технологии Drag&Drop мы не обрабатываем результаты перетаскивания. Картинка (или другие данные) должны быть загружены в объект-передатчик. Т.е. мы не можем, например, перетащить в Image1 иконку графического файла .jpg из проводника Windows в надежде, что этот файл там неожиданно откроется. Поскольку мы программно этого не описали, никаких сюрпризов ждать не приходится. Для более гибкого (и соответственно более сложного) управления перетаскиванием можно попробовать ручное перетаскивание.

Метод OLEDrag - ручное перетаскивание.

Ручное управление перетаскиванием позволяет программировать различные дополнительные действия. Кроме того с его помощью можно работать с такими элементами управления, не поддерживающими автоматическое перетаскивание, как CommandButton, Label, CheckBox, OptionButton, Frame и DriveListBox.

Итак, приступим. Помимо рассмотренных нами уже свойств .OLEDropMode и .OLEDragMode мы имеем еще целый ряд событий и их нам, как говорится, надо:

Private Sub объект_OLEDragDrop(data As DataObject,effect As Long, button As Integer, shift As Integer, x As Single, y As Single)

Возникает при перетаскивании объекта-источника в объект-приемник. data - объект DataObject содержит форматы (и возможно данные для этих форматов), которые обеспечивает источник. Если в объекте никаких данных не содержится, то они предоставляются при вызове метода GetData. Методы SetData и Clear использованы быть не могут. effect - число long, устанавливаемое приемником, для опознавания действия, которое выполнено (например удалять или копировать содержимое источника). Может принимать значения следующих констант: 0 (vbDropEffectNone) - приемник не может принять данные 1 (vbDropEffectCopy) - передача копии (исходник источника сохраняется) 2 (vbDropEffectMove) - перемещение данных (исходник источника удаляется) button - соответствует состоянию кнопки мыши. 1 - левая кнопка, 2 - правая и 4 - средняя. shift - указывает состояние кнопок shift, alt и ctrl (1- shift, 2- alt и 4 - ctrl). При удержании больше одной кнопки их значения складываются (ctrl-alt = 4+2=6).  Значения переменных x и y возвращают текущие координаты курсора мыши (x - по-горизонтали, y - по-вертикали) в единицах измерения, заданных свойствами ScaleHeight, ScaleWidth.

Private Sub объект_OLEDragOver(data As DataObject,effect As Long, button As Integer, shift As Integer, x As Single, y As Single, state As Integer)

 

Возникает при перетаскивании одного компонента над другим. data - объект DataObject содержит форматы (и возможно данные для этих форматов), которые обеспечивает источник. Если в объекте никаких данных не содержится, то они предоставляются при вызове метода GetData. Методы SetData и Clear использованы быть не могут. effect - число long, устанавливаемое приемником, для опознавания действия, которое выполнено (например удалять или копировать содержимое источника). Может принимать значения следующих констант: 0 (vbDropEffectNone) - приемник не может принять данные 1 (vbDropEffectCopy) - передача копии (исходник источника сохраняется) 2 (vbDropEffectMove) - перемещение данных (исходник источника удаляется) -2147483648 (&H80000000) (VbDropEffectScroll) - происходит или подготавливается прокрутка в приемнике (только при выполнении пользовательской прокрутки в приемнике). button - соответствует состоянию кнопки мыши. 1 - левая кнопка, 2 - правая и 4 - средняя. shift - указывает состояние кнопок shift, alt и ctrl (1- shift, 2- alt и 4 - ctrl). При удержании больше одной кнопки их значения складываются (ctrl-alt = 4+2=6).  Значения переменных x и y возвращают текущие координаты курсора мыши (x - по-горизонтали, y - по-вертикали) в единицах измерения, заданных свойствами ScaleHeight, ScaleWidth.  state - показывает состояние перетаскиваемого объекта по отношению к форме. Может принимать следующие значения: 0 (vbEnter) - источник в пределах приемника 1 (vbLeave) - источник вне окрестности приемника 2 (vbOver) - источник перемещается в приемнике из одного положения в другое

Private Sub объект_OLEGiveFeedback (effect As Long,defaultcursors As Boolean) 

Происходит после каждого события _OLEDragOver. Позволяет показывать пользователю (например с помощью изменения курсора мыши) ход перетаскивания. effect - число long, устанавливаемое в событии _OLEDragOver, и определяет действие, которое может быть педпринято, если пользователь опускает объект на компонент. Используется для визуализации перетаскивания. Может принимать значения следующих констант: 0 (vbDropEffectNone) - приемник не может принять данные 1 (vbDropEffectCopy) - передача копии (исходник источника сохраняется) 2 (vbDropEffectMove) - перемещение данных (исходник источника удаляется) -2147483648 (&H80000000) (VbDropEffectScroll) - происходит или подготавливается прокрутка в приемнике (только при выполнении пользовательской прокрутки в приемнике). defaultcursors - значение True, если использовать курсор мыши по умолчанию и False, если не использовать. (Курсор может быть установлен свойством .MousePointer объекта Screen)

Private Sub объект_OLEStartDrag (data As DataObject, allowedeffects As Long)

Происходит, когда выполняется метод OLEDrag или когда объект инициирует операцию перемещения, если свойство OLEDragMode установлено в значение 1 - авто. data - объект DataObject содержит форматы (и возможно данные для этих форматов), которые обеспечивает источник. Если в объекте никаких данных не содержится, то они предоставляются при вызове метода GetData. Методы SetData и Clear использованы быть не могут. allowedeffect - число long, содержит результаты, поддерживаемые приемником. Значения данного параметра устанавливаются программистом. Может принимать значения следующих констант: 0 (vbDropEffectNone) - приемник не может принять данные 1 (vbDropEffectCopy) - передача копии (источник сохраняется) 2 (vbDropEffectMove) - перемещение данных (источник удаляется)

Private Sub объект_OLESetData(data As DataObject,dataformat As Integer)

Происходит на источнике, когда приемник выполняет метод GetData на объекте DataObject, но данные указанного формата еще не загружены.  data - объект DataObject, в который необходимо поместить запрашиваемые данные. Компонент вызывает метод SetData, чтобы загрузить запрашиваемый формат. dataformat - целое число, уточняющее формат данных. Используется для указания источнику, что загружать в объект DataObject.

Private Sub объект_OLECompleteDrag ([effect As Long])

Возникает, когда источник опускается на получатель, информируя источник, что операция перетаскивания выполнена или отменена. effect - число long, устанавливаемое источником, позволяющее определить действие, которое выполнено. Может принимать значения следующих констант: 0 (vbDropEffectNone) - приемник не может принять данные 1 (vbDropEffectCopy) - передача копии (источник сохраняется) 2 (vbDropEffectMove) - перемещение данных (источник удаляется)

Прочитав этот полубезумный бред, мы не будем впадать в истерику. Нам известна аксиома - "Visual Basic - это не просто просто, а очень просто". Исходя из этой аксиомы, морща лоб от напряжения и пуская слюни от усердия, мы попытаемся сейчас реализовать технологию перетаскивания на раз-два-готово.  Изначально будем считать, что у нас есть наше приложение и некое чужое. Вот между ними мы и будем таскать объекты, как китайские кули.

Итак, указаные выше события возникают каждое на своем объекте:

на стороне объекта-источника

на стороне объекта-приемника

OLEStartDrop

OLEDragDrop

OLESetData

OLEDragOver

OLEGetFeedback

OLECompleteDrag

Вроде как на стороне приемника их меньше, вот и начнем с конца. Создадим новый exe-проект и положим на него Picture1 и Text1. В них мы должны перетащить картинку и текст соответственно из неких сторонних приложений. Таким образом, на нашем проекте имеется только объекты-приемники, а формирование на стороне объекта-источника мы оставим на совести чужого приложения. Сперва мы должны инициализировать наши Picture1 и Text1 как объекты-приемники. Для этого мы используем то же свойство .OLEDropMode, что и при автоматическом перетаскивании, только значение его должно быть в этот раз vbOLEDropManual, т.е. ручное. Сделаем это в процедуре загрузки формы (хотя можно и в окне свойств проекта): Private Sub Form_Load() Picture1.OLEDropMode = vbOLEDropManual Text1.OLEDropMode = vbOLEDropManual End Sub Зная, что при окончании перетаскивания на стороне приемника возникает событие _OLEDragDrop, используем его для загрузки картинки в Picture1. Но как же нам передать в нашу процедуру, что за картинка у нас, собственно, перетаскивается? А данные об этом хранятся в объекте DataObject. Прежде всего, этот объект позволяет нам проверить, того ли формата данные мы пытаемся загрузить в приемник (Picture1), так как, например, текст в PictureBox загружать не очень хорошо. Для этого объект DataObject имеет метод GetFormat. Его синтаксис следующий: объект.GetFormat (format) где объект - это наш объект DataObject format - это константа, указывающая на формат объекта и может принимать следующие значения: 

Константа

Значение константы

Описание формата

vbCFText

1

Текст (.txt-фйлы)

vbCFBitmap

2

Растр (.bmp, .jpg-файлы)

vbCFMetafile

3

Метафайл (.wmf-файлы)

vbCFEMetafile

14

Улучшеный метафайл (.emf)

vbCFDIB

8

Независимые от устройств растровые рисунки (DIB)

vbCFPalette

9

Цветовая палитра

vbCFFiles

15

Список файлов

vbCFRTF

-16639

Формат RTF (.rtf файлы)

Коль уж мы работаем с растровыми файлами, то можно использовать константу vbCFBitmap, однако некоторые чужие приложения могут имет другой формат, поэтому для картинок предпочтительнее использовать формат vbCFDIB, хотя это тоже не гарантия, что формат перетаскиваемого объекта будет соответствовать поддерживаемому VB. Таким образом проверка формата объекта будет выглядеть так: If Data.GetFormat(vbCFDIB) Then .... Теперь далее. Если наш формат правильный, то мы можем загрузить картинку, используя метод GetData для все того же объекта DataObject. Метод GetData возвращает данные объекта DataObject в установленном формате. Обратный ему метод SetData устанавливает данные в нужном формате объекта DataObject.  Синтаксис этих методов аналогичен синтаксису метода GetFormat: объект.GetData (format) объект.SetData данные,format где объект - это наш объект DataObject данные - это то, что мы будем перетаскивать. format - это константа, указывающая на формат объекта и может принимать те же значения, что и для метода GetFormat.

Теперь, собственно, загрузка нашей картинки будет выглядеть так: Picture1.Picture = Data.GetData(vbCFDIB) Кроме того, мы можем использовать параметр effect процедуры _OLEDragDrop для определения характеристики перетаскивания: копирования, переноса или ничего. Effect = vbDropEffectCopy 'например для копирования Но надо сказать, что поскольку пакет данных для перетаскивания готовился в объекте-источнике в неизвестном нам приложении, то толку от использования этого параметра будет мало. Т.е. вообще не будет. Этот параметр сообщает в объект-источник, какое действие было выполнено. Но объект источник-то - в чужом приложении и ему в большей степени наплевать на наш параметр. Этот параметр сработает, если только второе приложение - тоже создавалось нами и для его объекта-источника определены соответствующие процедуры. Но мы все-таки оставим этот оператор для, так сказать, чистоты эксперимента. В результате наша процедура будет такая:

Private Sub Picture1_OLEDragDrop(Data As DataObject, Effect As Long, Button As Integer, Shift As Integer, X As Single, Y As Single) If Data.GetFormat(vbCFDIB) Then  Picture1.Picture = Data.GetData(vbCFDIB) Effect = vbDropEffectCopy End If End Sub

Как видишь все на самом деле просто. У нас еще осталось неиспользованое событие _OLEDragOver. С его помощью и с помощью параметра Effect мы можем управлять курсором мыши. В зависимости от параметра Effect курсор мыши может иметь следующий вид:

Effect = vbDropEffectNone

Effect = vbDropEffectCopy

Effect = vbDropEffectMove

Для курсора копирования мы можем записать такую процедурку: Private Sub Picture1_OLEDragOver(Data As DataObject, Effect As Long, Button As Integer, Shift As Integer, X As Single, Y As Single, State As Integer) Effect = vbDropEffectCopy End Sub Надо сказать, что все прочие параметры события _OLEDragOver имеют контрольный характер и нас не интересуют. Хотя, отслеживая значение параметра State мы можем подгружать изображение нужных нам курсоров (если есть желание), как мы делали в прошлой, 18-й главе. Теперь можно перетаскивать картинку из того же Photoshopa в наш проект. Но, например, перетащить из ACDSee (из окна превьюшек) не удастся из-за несовместимости форматов. А сейчас давай напишем две аналогичные процедурки для перетаскивания текста в Text1. Суть их совершенно та же. В качестве формата объекта возьмем константу vbCFText.

Private Sub Text1_OLEDragDrop(Data As DataObject, Effect As Long, Button As Integer, Shift As Integer, X As Single, Y As Single) If Data.GetFormat(vbCFText) Then Text1.SelText = Data.GetData(vbCFText) Effect = vbDropEffectCopy End If End Sub

Private Sub Text1_OLEDragOver(Data As DataObject, Effect As Long, Button As Integer, Shift As Integer, X As Single, Y As Single, State As Integer) Effect = vbDropEffectCopy End Sub

Теперь, если мы выделим в чужом окне, например, хоть окне кода того же Basic'а кусочек строки, то его можно будет перетянуть в Text1. Единственное, что я хочу заметить, мы в качестве свойства Text1 использовали незнакомое нам свойство .SelText. Мы могли бы использовать и свойство .Text, но тогда вставляемый текст просто заменял бы уже существующий. А с использованием свойства .SelTextмы можем вставлять фрагмент текста в уже существующий текст в любое место. Подробней это и сопутствующие ему свойства рассмотрены в следующей 20-й главе.

Итак, как видите, ничего сложного, больше пугающих таблиц со свойствами и константами, чем строк кода.  А теперь также радостно попробуем создать на нашей форме объекты-источники и перетащить наши данные в чужое приложение. Сперва мы изменим процедуру Form_Load. В ней мы добавим две строчки для инициализации Picture1 и Text1 как объекты-источники и заполним их данными и будет это выглядеть так: Private Sub Form_Load() Picture1.OLEDropMode = vbOLEDropManual Picture1.OLEDragMode = vbOLEDragManual Text1.OLEDropMode = vbOLEDropManual Text1.OLEDragMode = vbOLEDropManual Picture1 = LoadPicture(App.Path & "\images_07.jpg") Text1 = "Visual Basic - это не просто просто, а очень просто" End Sub

Обрати внимание, что я не писал свойство .Text у Text1 и свойство .Picture у объекта Picture1. Дело в том, что это главные свойства этих объектов и понимаются VB по умолчанию (как, например,.Capture у Label и т.д.) и писать их нет никакой необходимости. Так же я буду поступать и далее.

Напишем необходимые процедуры для источника перетаскивания - Text1. Для того, чтобы начать перетаскивание, используется метод OLEDrag. Он не имеет аргументов и просто начинает перетаскивание. Его удобно поставить в процедуру _MouseMove или _MouseDown, которые позволят отследить нажатие левой кнопки мыши (ведь при нажатой левой кнопке мыши мы и осуществляем перетаскивание): Private Sub Text1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) If Button = vbLeftButton Then 'если нажата левая кнопка мыши Text1.OLEDrag 'включаем перетаскивание End If End Sub При запуске метода OLEDrag возникает событие _OLEStartDrag, которое позволяет нам сформировать данные для объекта-приемника. Данные эти надо поместить в объект DataObject с помощью метода SetData. Так же, с помощью параметра AllowedEffects мы можем определить порядок перетаскивания (копирование или перемещение): Private Sub Text1_OLEStartDrag(Data As DataObject, AllowedEffects As Long) AllowedEffects = vbDropEffectCopy Or vbDropEffectMove Data.Clear 'очистка DataObject Data.SetData Text1, vbCFText 'заполнение DataObject данными End Sub Обрати внимание, что если нам надо включить в DataObject несколько форматов, это достигается повторным вызовом метода .SetData каждый раз с новым форматом. Поэтому, чтобы гарантировать, что у нас нет ранее вызванных форматов, перед первым вызовом объект DataObject очищается с помощью метода Clear.

Мы можем использовать тот вид курсора, который нам захочется. Сделать это можно в процедуре события _OLEGiveFeedback, в зависимости от значения его параметра Effect. Для этого используется объект Screen и его свойства .MousePointer и .MouseIcon: Private Sub Text1_OLEGiveFeedback(Effect As Long, DefaultCursors As Boolean) 'убираем курсор по-умолчанию DefaultCursors = False 'включаем пользовательский курсор Screen.MousePointer = vbCustom 'в зависимости от параметра Effect подгружаем нужные нам иконки If Effect = vbDropEffectNone Then Screen.MouseIcon = LoadPicture(App.Path & "\No.ico") ElseIf vbDropEffectMove And Effect Then Screen.MouseIcon = LoadPicture(App.Path & "\Move.ico") ElseIf vbDropEffectCopy And Effect Then Screen.MouseIcon = LoadPicture(App.Path & "\Yes.ico") End If End Sub

После окончания перетаскивания для объекта-источника возникает событие _OLECompleteDrag. В этой процедуре мы можем очиститьText1, если перетаскивание было перемещением, а не копированием и кроме того, надо обязательно вернуть курсор к виду, который у него был до начала перетаскивания.

Private Sub Text1_OLECompleteDrag(Effect As Long) 'проверка состояния Effect If vbDropEffectMove And Effect Then Text1 = "" 'восстановление курсора по-умолчанию Screen.MousePointer = vbDefault End Sub

Обратите внимание, что проверка константы vbDropEffectMove осуществляется не так, как можно было бы предположить, исходя из здравого смысла, а именно: If Effect = vbDropEffectMove..., а маскируется с помощью логической операции And. В полном виде это выглядело бы так: If Effect and vbDropEffectMove = vbDropEffectMove, мы в программе использовали сокращенный аналог. Делается это для совместимости с будущими версиями VB, хотя с появлением VB Net это, наверное, неактуально. Вот в основном и все. Теперь ты можешь перетащить текст из Text1 в другое приложение, например Word. Процедуры для перетаскивания из Picture1 абсолютно аналогичны и приведены в коде примера "OLEDrag.zip", который можно скачать вверху страницы.

В Visual Basic существуют объекты, не отображаемые на форме, но использование которых несложно и может принести некоторую пользу. В прошлых главах мы говорили об объектах Printer, Err и DataObject. Давайте поговорим еще о парочке.

Объект Screen.

По-сути, объект Screen представляет собой весь экран или иначе Десктоп (DeskTop). Как и другие объекты, объект Screen имеет определенные свойства. Так, например, почему-то считается, что для определения разрешения экрана надо использовать API-функции. Но это прекрасно можно сделать и внутренними возможностями VB. У объекта Screen есть свойства .Height и .Width, которые и возвращают высоту и ширину экрана в твипах. Эти самые твипы легко преобразовать в пиксели, если использовать два других свойства объекта Screen - .TwipsPerPixelX (которое возвращает количество твипов в пикселе по-горизонтали) и .TwipsPerPixelY (которое возвращает количество твипов в пикселе по-вертикали). Таким образом, чтобы определить разрешение экрана, мы пишем приблизительно следующий код:

Private Sub Command1_Click() 'Объявить переменные Dim VisotaScr As Long 'для высоты Dim ShirinaScr As Long 'для ширины VisotaScr = Screen.Height / Screen.TwipsPerPixelX 'узнаем ширинуэкрана и преобразовываем ее в пиксели ShirinaScr = Screen.Width / Screen.TwipsPerPixelY 'узнаем высоту экрана и преобразовываем ее в пиксели Text1 = VisotaScr & " x " & ShirinaScr 'выводим результат в Text1  End Sub

Еще одно свойство .ActiveControl содержит ссылку на элемент управления (и его свойства), имеющий фокус. Смысл использования этого свойства следующий. Например, у тебя на формe три текстбокса. И ты хочешь активный текстбокс очистить. Вот в этом случае и можно использовать свойство .ActiveControl, например Screen.ActiveControl.Text=""

т. е. очищается тот объект ( в нашем случае TextBox) , на котором в данный момент находится фокус. Это, конечно, несколько упрощено, поскольку, во-первых, необходимо проверить, а текстбокс ли это? Может у тебя на форме есть другие элементы, например, PictureBox. Во-вторых, реально это свойство можно использовать только в меню, так как меню не получает фокус и активным элементом остается выбранный объект. При использовании, например, свойства .ActiveControl в процедуре CommandButton, при нажатии на кнопку Command1 фокус перейдет с нужного нам элемента на командную кнопку и мы не получим желаемого результата. Ниже, в этой главе, мы на примере работы с объектом Clipboard будем использовать это свойство применительно к меню.

Аналогично свойству .ActiveControl и свойство и .ActiveForm, но содержит ссылку на активную форму. Тогда, чтобы добраться до какого-либо элемента можно применить, например, такой оператор: Screen.ActiveForm.ActiveControl.Picture Надо сказать, что объект Screen имеет еще свойства MouseIcon и MousePointer, которые позволяют менять вид курсора, но их использование совершенно аналогично этим свойствам для формы, которые мы анализировали в главе 5. Поэтому я не стану занимать этой дребеденью наше драгоценное время и предлагаю перейти к более нужному и интересному.

Свойства SelLength, SelStart, SelText.

Сейчас мы коротенько поговорим о довольно-таки нужных текстовых свойствах, тем более, что мы будем их использовать далее при работе с объектом Clipboard. Мы уже использовали свойство SelTextв прошлой главе, но на этом не остановимся. Если мы выделим кусочек текста в Text, то мы можем получить (или установить) об этом выделенном фрагменте некоторую полезную информацию как раз с помощью этих свойств:

.SelLength — возвращает или устанавливает число выделенных символов. (синтаксис объект.SelLength [= число выбранных символов]) .SelStart — возвращает или устанавливает начало выделенного текста, а если текст не выбран, то указывает положение места вставки. (синтаксис объект.SelStart [= положение начала вставки]) .SelText — возвращает или устанавливает строку, в которой содержится выделенный текст. Если никаких символов не выбрано, то представляет строку нулевой длины ("").(синтаксис объект.SelText [= текст выделения]).

Наиболее часто эти свойства как раз и используются с объектом Clipboard для операций вставки и вырезания. Причем установка .SelLenght меньше нуля приводит к ошибке выполнения. Изменение.SelStart устанавливает .SelLength в значение ноль. Если установить .SelLength больше, чем длина текста, то оно автоматически станет равной длине текста. Установка .SelText в новое значение устанавливает .SelLength в ноль.  Ну вот, теперь можно двигать дальше.

Объект Clipboard.

С системным буфером обмена данных Clipboard мы сталкиваемся постоянно. Именно в него помещаются данные приложения при использовании операций Cut (Вырезать) и Copy (Копировать) и из него берутся при операции Paste (Вставить) в меню Edit, которое есть в практически любой виндусовой программе. Буфер обмена представляет своего рода промежуточное место для хранения данных, которое позволяет, не устанавливая связь между приложениями, тем не менее, переносить данные одного типа из одной программы в другую.  Буфер обмена для всего Windows один. Забрав в буфер данные одного приложения, а затем другого, мы обнаружим, что в буфере хранятся лишь данные последней операции Cut или Copy. Это вызвано тем, что в большинстве случаев, чтобы в буфере не хранилось данных разных приложений, перед загрузкой данных в буфер программа-донор обычно производит его очистку. Посмотреть содержимое буфера можно с помощью утилиты CLIPBRD.EXE, которая обычно имеется в директории C:\Windows\System32 или аналогичной (в зависимости от Windows, установленной на твоем компьютере). В буфер обмена можно забрать и текст и картинку и черте что, потому что он поддерживает разные форматы данных.  Из VB доступ к буферу организуется с помощью объекта Clipboard. Методы объекта практически те же, что и разобранные нами в главе 19 с объектом DataObject, однако повторенье - мать ученья (банальность). Они представлены в нижеследующей таблице:

Методы объекта Clipboard

Clipboard.SetText data, format

Помещает текстовые данные в буфер обмена, где data - строка данных, помещаемая в Clipboard

Значение format для текстовых данных может принимать следующие значения: vbCFLink (&HBF00) - Диалоговая информация DDE  vbCFRTF (&HBF01) - Текст в формате RTF(Rich Text Format) vbCFText (1) - Текст (Установка по умолчанию)

Clipboard.GetText (format)

Возвращает текстовые данные из буфера обмена. Необязательный. Если ожидаемого формата в буфере нет, то возвращается пустая строка.

Clipboard.SetData data, format

Помещает графические данные в буфер обмена, где data - графическое изображение, помещаемое в Clipboard. Устанавливается функцией LoadPicture либо свойствами Picture.

Значение format для графических данных может принимать следующие значения: vbCFBitmap (2) - Растр (.bmp-файлы) vbCFMetafile (3) - Метафайл (.wmf-файлы) vbCFDIB (8) - Независимые от устройств растровые рисунки (DIB) vbCFPalette (9) - Цветовая палитра vbCFEMetafile (14) - Расширенный метафайл (.emf-файлы)  vbCFFiles (15) - Список имен файлов (Microsoft Windows Explorer)

Clipboard.GetData (format)

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

Clipboard.GetFormat (format)

Возвращает логические True или False, в зависимости от содержащихся в буфере данных. Для проверки более чем одного метод вызывается повторно.

Значение format может принимать следующие значения: vbCFLink (&HBF00) - Диалоговая информация DDE  vbCFText (1) - Текст vbCFBitmap (2) - Растр (.bmp-файлы) vbCFMetafile (3) - Метафайл (.wmf-файлы) vbCFDIB (8) - Независимые от устройств растровые рисунки (DIB) vbCFPalette (9) - Цветовая палитра  vbCFEMetafile (14) - Расширенный метафайл (.emf-файлы)  vbCFFiles (15) - Список имен файлов (Microsoft Windows Explorer)

Clipboard.Clear

Очищает содержание системного буфера обмена.

Использовать эти методы очень просто. Для примера создадим новый exe-проект и положим на него два TextBox'а и два PictureBox'а. В качестве управления нашими процессами с помощью Редактора меню создадим простое меню Edit. Подробно о создании меню написано в Главе 9.  Имя нашего меню будет mnuEdit, а строками mnuCut - Вырезать mnuCopy - Копировать mnuPaste - Вставить mnuDelate - Удалить Вообще-то у TextBox'а есть свое контекстное меню, но мы сделаем свое общее.

Ну сперва в процедуре Form_Load загрузим в Text1 и Picture1 данные, чтобы нам было с чем работать. В примере я использовал маленькую картинку P00027.jpg. (Кстати поставь свойства TextBox'ов Multiline=True) Option Explicit 'хоть у нас и не будет ни одной переменной, но дело принципа Private Sub Form_Load() Text1 = "Проще Visual Basic ничего нету" Picture1 = LoadPicture(App.Path & "\P00027.jpg") End Sub

Начнем с процедуры для первого пункта подменю "Вырезать" - mnuCut, которая должна копировать данные в буфер и затем очищать объект (Text или Picture):

Private Sub mnuCut_Click() Сперва нам надо очистить буфер, чтобы в нем не осталась другая информация. Используя метод .Clear это предельно просто. Clipboard.Clear Далее мы будем использовать свойство .ActiveControl объекта Screen. С его помощью мы легко затащим в буфер содержимое объекта, которое в настоящий момент активно, т.е. по которой мы щелкнули мышью. Но сперва нам надо определить, а какой это объект, ведь для текста используется метод .SetText, а для графики - .SetData. Здесь мы снова, как и в Главе 18 используем ключевое слово TypeOf, которое поможет определить тип элемента управления, а значит и тип данных. 'итак, если это текст If TypeOf Screen.ActiveControl Is TextBox Then 'забираем выделенный текст в буфер Clipboard.SetText Screen.ActiveControl.SelText 'а затем удаляем этот выделенный текст из TextBox'а Screen.ActiveControl.SelText = "" 'а теперь пишем ветвь, если это картинка ElseIf TypeOf Screen.ActiveControl Is PictureBox Then 'забираем картинку из активного элемента в буфер Clipboard.SetData Screen.ActiveControl.Picture 'и удаляем картинку Screen.ActiveControl.Picture = LoadPicture() End If End Sub

Готово, наши данные в буфере обмена. Если у тебя открыт CLIPBRD.EXE, то ты можешь видеть, что творится в буфере. Теперь напишем процедуру для копирования mnuCopy. Единственное, чем она отличается от вырезания, так это тем, что мы не очищаем Text или Picture от копируемых данных.

Private Sub mnuCopy_Click() 'Очищаем буфер Clipboard.Clear 'проверяем тип данных 'если если это текст If TypeOf Screen.ActiveControl Is TextBox Then 'забор текста в буфер Clipboard.SetText Screen.ActiveControl.SelText 'если это картинка ElseIf TypeOf Screen.ActiveControl Is PictureBox Then 'забор картинки в буфер Clipboard.SetData Screen.ActiveControl.Picture End If End Sub

Теперь нам хорошо-бы из буфера вставить данные в элемент управления. Для этого пишем процедуру Вставить - mnuPaste.

Private Sub mnuPaste_Click() 'Ну сперва, конечно, проверяем тип, куда мы хотим запихнуть содержимое буфера и если это текст If TypeOf Screen.ActiveControl Is TextBox Then 'но все равно сразу не пихаем. А вдруг даные были забраны из PictureBox, поэтому мы еще и проверяем 'с помощью метода GetFormat, а текстовой ли тип в буфере If Clipboard.GetFormat(vbCFText) Then 'вот если да, то тогда уж вставляем без сомнения Screen.ActiveControl.SelText = Clipboard.GetText End If 'а теперь проверка для графических данных ElseIf TypeOf Screen.ActiveControl Is PictureBox Then 'и здесь мы проверяем тип данных буфера If Clipboard.GetFormat(vbCFDIB) Then 'и если совпадает, то вставляем Screen.ActiveControl.Picture = Clipboard.GetData End If End If End Sub

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

Private Sub mnuDelate_Click() 'проверяем тип данных 'если это текст If TypeOf Screen.ActiveControl Is TextBox Then 'удаление выделенного текста Screen.ActiveControl.SelText = "" ElseIf TypeOf Screen.ActiveControl Is PictureBox Then 'удаление картинки Screen.ActiveControl.Picture = LoadPicture() End If End Sub

Казалось бы, вот оно счастье, рядом, но фиг, дело не закончено. Если ты обратил внимание на фирменные программы, то не всегда все строки меню редактирования доступны юзеру. Например, если нет выделения в тексте, то и нечего копировать или вырезать. Или наоборот, в буфере нет данных? Поэтому в определенные моменты доступ к определенным строкам меню надо перекрыть! Для решения этой проблемы надо написать небольшую, но ответственную процедуру. И используем мы для этого самый верхний, казалось бы совершенно ненужный пункт меню mnuEdit. Именно по клику юзера по этой строчке нам будет очень удобно проверять, что можно делать из наших процедур, а что нельзя. Private Sub mnuEdit_Click() Сначала обязательно проверяем, соответствует ли тип данных элемента, на котором находится фокус. If TypeOf Screen.ActiveControl Is TextBox Then В случае, если из текста ничего не выделено, мы должны сделать недоступными строки "Вырезать", "Копировать" и "Удалить". Для этого надо ее свойству .Enabled присвоить значение False. Если ничего не выделено, значит длина выделения (свойство .SelLength) равна нулю. Это можно было бы записать приблизительно так:

 

If Screen.ActiveControl.SelLength=0 Then mnuCut.Enabled = False

Однако, для краткости записи мы можем напрямую присвоить свойству .Enabled значение длины выделения .SelLength, исходя из того, что значение ноль всегда соответствует для логических типов False, а любое положительное - True. В итоге то мы получаем более короткую, но по смыслу абсолютно такую же запись. Кроме того, при этом, нам не надо каждый раз в начале процедуры восстанавливать доступность строк, так как длина строки больше нуля сама переведет .Enabled в состояние True. mnuCut.Enabled = Screen.ActiveControl.SelLength аналогично две другие строки mnuCopy.Enabled = Screen.ActiveControl.SelLength mnuDelate.Enabled = Screen.ActiveControl.SelLength Далее мы должны запретить Вставить, если формат данных в буфере не соответствует требуемому. Здесь даже еще проще. Метод .GetFormat возвращает True, если формат соответствет проверяемому и False - если нет. Именно эти значения мы напрямую и присваиваем для свойства .Enabled строки mnuPaste. mnuPaste.Enabled = Clipboard.GetFormat(vbCFText) Абсолютно также мы поступаем с веткой для графических данных ElseIf TypeOf Screen.ActiveControl Is PictureBox Then mnuCut.Enabled = Screen.ActiveControl.Picture mnuCopy.Enabled = Screen.ActiveControl.Picture mnuDelate.Enabled = Screen.ActiveControl.Picture mnuPaste.Enabled = Clipboard.GetFormat(vbCFDIB) End If End Sub

В Visual Basic существуют объекты, не отображаемые на форме, но использование которых несложно и может принести некоторую пользу. В прошлых главах мы говорили об объектах Printer, Err и DataObject. Давайте поговорим еще о парочке.

Объект Screen.

По-сути, объект Screen представляет собой весь экран или иначе Десктоп (DeskTop). Как и другие объекты, объект Screen имеет определенные свойства. Так, например, почему-то считается, что для определения разрешения экрана надо использовать API-функции. Но это прекрасно можно сделать и внутренними возможностями VB. У объекта Screen есть свойства .Height и .Width, которые и возвращают высоту и ширину экрана в твипах. Эти самые твипы легко преобразовать в пиксели, если использовать два других свойства объекта Screen - .TwipsPerPixelX (которое возвращает количество твипов в пикселе по-горизонтали) и .TwipsPerPixelY (которое возвращает количество твипов в пикселе по-вертикали). Таким образом, чтобы определить разрешение экрана, мы пишем приблизительно следующий код:

Private Sub Command1_Click() 'Объявить переменные Dim VisotaScr As Long 'для высоты Dim ShirinaScr As Long 'для ширины VisotaScr = Screen.Height / Screen.TwipsPerPixelX 'узнаем ширинуэкрана и преобразовываем ее в пиксели ShirinaScr = Screen.Width / Screen.TwipsPerPixelY 'узнаем высоту экрана и преобразовываем ее в пиксели Text1 = VisotaScr & " x " & ShirinaScr 'выводим результат в Text1  End Sub

Еще одно свойство .ActiveControl содержит ссылку на элемент управления (и его свойства), имеющий фокус. Смысл использования этого свойства следующий. Например, у тебя на формe три текстбокса. И ты хочешь активный текстбокс очистить. Вот в этом случае и можно использовать свойство .ActiveControl, например Screen.ActiveControl.Text=""

т. е. очищается тот объект ( в нашем случае TextBox) , на котором в данный момент находится фокус. Это, конечно, несколько упрощено, поскольку, во-первых, необходимо проверить, а текстбокс ли это? Может у тебя на форме есть другие элементы, например, PictureBox. Во-вторых, реально это свойство можно использовать только в меню, так как меню не получает фокус и активным элементом остается выбранный объект. При использовании, например, свойства .ActiveControl в процедуре CommandButton, при нажатии на кнопку Command1 фокус перейдет с нужного нам элемента на командную кнопку и мы не получим желаемого результата. Ниже, в этой главе, мы на примере работы с объектом Clipboard будем использовать это свойство применительно к меню.

Аналогично свойству .ActiveControl и свойство и .ActiveForm, но содержит ссылку на активную форму. Тогда, чтобы добраться до какого-либо элемента можно применить, например, такой оператор: Screen.ActiveForm.ActiveControl.Picture Надо сказать, что объект Screen имеет еще свойства MouseIcon и MousePointer, которые позволяют менять вид курсора, но их использование совершенно аналогично этим свойствам для формы, которые мы анализировали в главе 5. Поэтому я не стану занимать этой дребеденью наше драгоценное время и предлагаю перейти к более нужному и интересному.

Свойства SelLength, SelStart, SelText.

Сейчас мы коротенько поговорим о довольно-таки нужных текстовых свойствах, тем более, что мы будем их использовать далее при работе с объектом Clipboard. Мы уже использовали свойство SelTextв прошлой главе, но на этом не остановимся. Если мы выделим кусочек текста в Text, то мы можем получить (или установить) об этом выделенном фрагменте некоторую полезную информацию как раз с помощью этих свойств:

.SelLength — возвращает или устанавливает число выделенных символов. (синтаксис объект.SelLength [= число выбранных символов]) .SelStart — возвращает или устанавливает начало выделенного текста, а если текст не выбран, то указывает положение места вставки. (синтаксис объект.SelStart [= положение начала вставки]) .SelText — возвращает или устанавливает строку, в которой содержится выделенный текст. Если никаких символов не выбрано, то представляет строку нулевой длины ("").(синтаксис объект.SelText [= текст выделения]).

Наиболее часто эти свойства как раз и используются с объектом Clipboard для операций вставки и вырезания. Причем установка .SelLenght меньше нуля приводит к ошибке выполнения. Изменение.SelStart устанавливает .SelLength в значение ноль. Если установить .SelLength больше, чем длина текста, то оно автоматически станет равной длине текста. Установка .SelText в новое значение устанавливает .SelLength в ноль.  Ну вот, теперь можно двигать дальше.

Объект Clipboard.

С системным буфером обмена данных Clipboard мы сталкиваемся постоянно. Именно в него помещаются данные приложения при использовании операций Cut (Вырезать) и Copy (Копировать) и из него берутся при операции Paste (Вставить) в меню Edit, которое есть в практически любой виндусовой программе. Буфер обмена представляет своего рода промежуточное место для хранения данных, которое позволяет, не устанавливая связь между приложениями, тем не менее, переносить данные одного типа из одной программы в другую.  Буфер обмена для всего Windows один. Забрав в буфер данные одного приложения, а затем другого, мы обнаружим, что в буфере хранятся лишь данные последней операции Cut или Copy. Это вызвано тем, что в большинстве случаев, чтобы в буфере не хранилось данных разных приложений, перед загрузкой данных в буфер программа-донор обычно производит его очистку. Посмотреть содержимое буфера можно с помощью утилиты CLIPBRD.EXE, которая обычно имеется в директории C:\Windows\System32 или аналогичной (в зависимости от Windows, установленной на твоем компьютере). В буфер обмена можно забрать и текст и картинку и черте что, потому что он поддерживает разные форматы данных.  Из VB доступ к буферу организуется с помощью объекта Clipboard. Методы объекта практически те же, что и разобранные нами в главе 19 с объектом DataObject, однако повторенье - мать ученья (банальность). Они представлены в нижеследующей таблице:

Методы объекта Clipboard

Clipboard.SetText data, format

Помещает текстовые данные в буфер обмена, где data - строка данных, помещаемая в Clipboard

Значение format для текстовых данных может принимать следующие значения: vbCFLink (&HBF00) - Диалоговая информация DDE  vbCFRTF (&HBF01) - Текст в формате RTF(Rich Text Format) vbCFText (1) - Текст (Установка по умолчанию)

Clipboard.GetText (format)

Возвращает текстовые данные из буфера обмена. Необязательный. Если ожидаемого формата в буфере нет, то возвращается пустая строка.

Clipboard.SetData data, format

Помещает графические данные в буфер обмена, где data - графическое изображение, помещаемое в Clipboard. Устанавливается функцией LoadPicture либо свойствами Picture.

Значение format для графических данных может принимать следующие значения: vbCFBitmap (2) - Растр (.bmp-файлы) vbCFMetafile (3) - Метафайл (.wmf-файлы) vbCFDIB (8) - Независимые от устройств растровые рисунки (DIB) vbCFPalette (9) - Цветовая палитра vbCFEMetafile (14) - Расширенный метафайл (.emf-файлы)  vbCFFiles (15) - Список имен файлов (Microsoft Windows Explorer)

Clipboard.GetData (format)

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

Clipboard.GetFormat (format)

Возвращает логические True или False, в зависимости от содержащихся в буфере данных. Для проверки более чем одного метод вызывается повторно.

Значение format может принимать следующие значения: vbCFLink (&HBF00) - Диалоговая информация DDE  vbCFText (1) - Текст vbCFBitmap (2) - Растр (.bmp-файлы) vbCFMetafile (3) - Метафайл (.wmf-файлы) vbCFDIB (8) - Независимые от устройств растровые рисунки (DIB) vbCFPalette (9) - Цветовая палитра  vbCFEMetafile (14) - Расширенный метафайл (.emf-файлы)  vbCFFiles (15) - Список имен файлов (Microsoft Windows Explorer)

Clipboard.Clear

Очищает содержание системного буфера обмена.

Использовать эти методы очень просто. Для примера создадим новый exe-проект и положим на него два TextBox'а и два PictureBox'а. В качестве управления нашими процессами с помощью Редактора меню создадим простое меню Edit. Подробно о создании меню написано в Главе 9.  Имя нашего меню будет mnuEdit, а строками mnuCut - Вырезать mnuCopy - Копировать mnuPaste - Вставить mnuDelate - Удалить Вообще-то у TextBox'а есть свое контекстное меню, но мы сделаем свое общее.

Ну сперва в процедуре Form_Load загрузим в Text1 и Picture1 данные, чтобы нам было с чем работать. В примере я использовал маленькую картинку P00027.jpg. (Кстати поставь свойства TextBox'ов Multiline=True) Option Explicit 'хоть у нас и не будет ни одной переменной, но дело принципа Private Sub Form_Load() Text1 = "Проще Visual Basic ничего нету" Picture1 = LoadPicture(App.Path & "\P00027.jpg") End Sub

Начнем с процедуры для первого пункта подменю "Вырезать" - mnuCut, которая должна копировать данные в буфер и затем очищать объект (Text или Picture):

Private Sub mnuCut_Click() Сперва нам надо очистить буфер, чтобы в нем не осталась другая информация. Используя метод .Clear это предельно просто. Clipboard.Clear Далее мы будем использовать свойство .ActiveControl объекта Screen. С его помощью мы легко затащим в буфер содержимое объекта, которое в настоящий момент активно, т.е. по которой мы щелкнули мышью. Но сперва нам надо определить, а какой это объект, ведь для текста используется метод .SetText, а для графики - .SetData. Здесь мы снова, как и в Главе 18 используем ключевое слово TypeOf, которое поможет определить тип элемента управления, а значит и тип данных. 'итак, если это текст If TypeOf Screen.ActiveControl Is TextBox Then 'забираем выделенный текст в буфер Clipboard.SetText Screen.ActiveControl.SelText 'а затем удаляем этот выделенный текст из TextBox'а Screen.ActiveControl.SelText = "" 'а теперь пишем ветвь, если это картинка ElseIf TypeOf Screen.ActiveControl Is PictureBox Then 'забираем картинку из активного элемента в буфер Clipboard.SetData Screen.ActiveControl.Picture 'и удаляем картинку Screen.ActiveControl.Picture = LoadPicture() End If End Sub

Готово, наши данные в буфере обмена. Если у тебя открыт CLIPBRD.EXE, то ты можешь видеть, что творится в буфере. Теперь напишем процедуру для копирования mnuCopy. Единственное, чем она отличается от вырезания, так это тем, что мы не очищаем Text или Picture от копируемых данных.

Private Sub mnuCopy_Click() 'Очищаем буфер Clipboard.Clear 'проверяем тип данных 'если если это текст If TypeOf Screen.ActiveControl Is TextBox Then 'забор текста в буфер Clipboard.SetText Screen.ActiveControl.SelText 'если это картинка ElseIf TypeOf Screen.ActiveControl Is PictureBox Then 'забор картинки в буфер Clipboard.SetData Screen.ActiveControl.Picture End If End Sub

Теперь нам хорошо-бы из буфера вставить данные в элемент управления. Для этого пишем процедуру Вставить - mnuPaste.

Private Sub mnuPaste_Click() 'Ну сперва, конечно, проверяем тип, куда мы хотим запихнуть содержимое буфера и если это текст If TypeOf Screen.ActiveControl Is TextBox Then 'но все равно сразу не пихаем. А вдруг даные были забраны из PictureBox, поэтому мы еще и проверяем 'с помощью метода GetFormat, а текстовой ли тип в буфере If Clipboard.GetFormat(vbCFText) Then 'вот если да, то тогда уж вставляем без сомнения Screen.ActiveControl.SelText = Clipboard.GetText End If 'а теперь проверка для графических данных ElseIf TypeOf Screen.ActiveControl Is PictureBox Then 'и здесь мы проверяем тип данных буфера If Clipboard.GetFormat(vbCFDIB) Then 'и если совпадает, то вставляем Screen.ActiveControl.Picture = Clipboard.GetData End If End If End Sub

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

Private Sub mnuDelate_Click() 'проверяем тип данных 'если это текст If TypeOf Screen.ActiveControl Is TextBox Then 'удаление выделенного текста Screen.ActiveControl.SelText = "" ElseIf TypeOf Screen.ActiveControl Is PictureBox Then 'удаление картинки Screen.ActiveControl.Picture = LoadPicture() End If End Sub

Казалось бы, вот оно счастье, рядом, но фиг, дело не закончено. Если ты обратил внимание на фирменные программы, то не всегда все строки меню редактирования доступны юзеру. Например, если нет выделения в тексте, то и нечего копировать или вырезать. Или наоборот, в буфере нет данных? Поэтому в определенные моменты доступ к определенным строкам меню надо перекрыть! Для решения этой проблемы надо написать небольшую, но ответственную процедуру. И используем мы для этого самый верхний, казалось бы совершенно ненужный пункт меню mnuEdit. Именно по клику юзера по этой строчке нам будет очень удобно проверять, что можно делать из наших процедур, а что нельзя. Private Sub mnuEdit_Click() Сначала обязательно проверяем, соответствует ли тип данных элемента, на котором находится фокус. If TypeOf Screen.ActiveControl Is TextBox Then В случае, если из текста ничего не выделено, мы должны сделать недоступными строки "Вырезать", "Копировать" и "Удалить". Для этого надо ее свойству .Enabled присвоить значение False. Если ничего не выделено, значит длина выделения (свойство .SelLength) равна нулю. Это можно было бы записать приблизительно так:

 

If Screen.ActiveControl.SelLength=0 Then mnuCut.Enabled = False

Однако, для краткости записи мы можем напрямую присвоить свойству .Enabled значение длины выделения .SelLength, исходя из того, что значение ноль всегда соответствует для логических типов False, а любое положительное - True. В итоге то мы получаем более короткую, но по смыслу абсолютно такую же запись. Кроме того, при этом, нам не надо каждый раз в начале процедуры восстанавливать доступность строк, так как длина строки больше нуля сама переведет .Enabled в состояние True. mnuCut.Enabled = Screen.ActiveControl.SelLength аналогично две другие строки mnuCopy.Enabled = Screen.ActiveControl.SelLength mnuDelate.Enabled = Screen.ActiveControl.SelLength Далее мы должны запретить Вставить, если формат данных в буфере не соответствует требуемому. Здесь даже еще проще. Метод .GetFormat возвращает True, если формат соответствет проверяемому и False - если нет. Именно эти значения мы напрямую и присваиваем для свойства .Enabled строки mnuPaste. mnuPaste.Enabled = Clipboard.GetFormat(vbCFText) Абсолютно также мы поступаем с веткой для графических данных ElseIf TypeOf Screen.ActiveControl Is PictureBox Then mnuCut.Enabled = Screen.ActiveControl.Picture mnuCopy.Enabled = Screen.ActiveControl.Picture mnuDelate.Enabled = Screen.ActiveControl.Picture mnuPaste.Enabled = Clipboard.GetFormat(vbCFDIB) End If End Sub

В этой главе мы разберем по косточкам еще одно средство управления ActiveX, которое Несомненно украсит твой проект и сделает его привлекательным для потенциального пользователя. Это панель состояния, которая представляет собой полосу внизу окна формы, в прямоугольниках которой отображается разнообразная информация о ходе выполнения программы, например имя открытого файла, его параметры, сообщения для юзера и все, что хочешь туда запихнуть. Практически всякая уважающая себя Windows-программа имеет панель Status Bar. Мы тоже сделаем такую: Этот контрол хранится в файле mscomctl.ocx и чтобы воспользоваться его возможностями, необходимо добавить его в твой проект. О добавлении ActiveX в проект подробно и увлекательно рассказано вглаве 12.  Итак, создаем новый exe-проект и добавляем наш контрол: Проект - Компонеты - Microsoft Windows Common Controls 6.0 в панель инструментов и кладем его на форму. Кроме того нам понадобится контрол, с которым мы уже работали (Глава 15) - CommonDialog для красивого открытия файла, добавим и его: Проект - Компоненты - Microsoft Common Dialog Control 6.0 и также кладем на форму. Ну и положим на форму текстбокс со свойствами Multiline=True и ScrollBars - 2 Вертикаль и кнопку Command1.

Ну вот теперь займемся вплотную панелью StatusBar. Щелкнем правой кнопки мыши по StatusBar1 (она уже находится у нас на форме) и в выпавшем меню выберем последнюю строчку - Properties (Свойства). У нас появится окно свойств (рис. 31) Рисунок 31. Точнее это первая вкладка - General. Эта главная вкладка, она нам не очень интересна, т.к. устанавливает общие свойства панели StatusBar, но тем не менее на ней мы видим четыре поля Style - определяет стиль объекта (0 - sbrNormal - обычный многооконный и 1 - sbrSimple - одно большое окно по всей ширине формы. MousePointer - как для формы и других объектов определяет тип указателя мыши (99 - пользовательский) SimpleText - текст, выводимый в StatusBar при его стиле 1 - sbrSimple OLEDropMode - определяет отключения возможности перетаскивания (0 -ccOLEDropNone) или ручное (1-ccOLEDropManual) Кроме того здесь имеются два флажка:

Enabled - делает доступным или недоступным (т.е. не реагирующим на события) Show Tips - отключает или включает показ контекстной подсказки (желтые окошки).  Ну здесь ничего менять не будем и перейдем к следующей вкладке (рис. 32) Рисунок 32. Что мы тут интересного находим: Во-первых, на этой вкладке мы можем добавлять или удалять поля (максимальное число полей - 16) кнопками "Insert Panel" и "Remove Panel".  Index - номер поля, он же индекс, с которым мы в настоящий момент работаем. По этому индексу мы и будем записывать информацию в нужное поле. Text - сюда записывают текст, который выводится в соответствующем поле StatusBar. Но мы будем заносить текст программно (например StatusBar1.Panels(1).Text = CommonDialog1.FileTitle), поэтому оставим это поле свободным. Key - идентификатор члена коллекции. Использовать не будем. Tag - дополнительное средство идентификации объекта - любое уникальное строковое выражение. Alignment - ориентирует выводимый текст в поле: прижимает вправо или влево или центрирует. Style - определяет вид и поведение панели StatusBar. Может принимать следующие значения:

 

Константа

Значение

Описание

 

sbrText

0

Принято по умолчанию. Текст или графическая картинка (bitmap)

 

sbrCaps

1

Отображает символы CAPS при включенных заглавных буквах на клавиатуре (клавиша Caps Lock).

 

sbrNum

2

Отображает символы NUM при включенной малой цифровой (калькуляторной) клавиатуре (клавиша Num Lock).

 

sbrIns

3

Отображает символы INS, когда включен режим вставки (клавиша Insert).

 

sbrScrl

4

Отображает символы SCRL, когда включен режим прокрутки. (клавиша Scroll Lock)

 

sbrTime

5

Отображает текущее время в системном формате.

 

sbrDate

6

Отображает текущую дату системном формате.

 

sbrKana

7

Отображает символы KANA, когда включен режим прокрутки (клавиша Kana Lock). Только для Японии.

Bevel - показывает общий стиль поля панели: вдавленное (sbrInset) - по умолчанию, выпуклое (sbrRaised) или ровное(sbrNoBevel). AutoSize - позволяет настроить размер поля панели. Может принимать значения: ширина поля понели неизменна и соответствут свойству Width (sbrNoAutoSize), ширина поля панели регулируется так, что все поля занимают всю ширину окна, но не менее заданного свойством MinWidth (sbrSpring), ширина панели зависит от ширины выводимого текста, но не менее заданного свойством MinWidth (sbrContents). В поле Minimum Width можно задать свойство MinWidth, т.е. минимальную ширину поля панели в твипах. Так же с помощью кнопки Browse можно запихнуть в поле подходящую картинку. Только мы этого тоже делать не будем. Ну и на этой же вкладке есть флажки доступности Enable и видимости Visible каждого поля панели. На следующей вкладке Font мы можем просто выставить шрифт, жирность и прочие атрибуты. Специальных объяснений это не требует.  Ну и на последней вкладке Picture можно присобачить иконку к свойству MouseIcon, если конечно на первой вкладке General свойство MousePointer было установлено в 99 - ccCustom.

Давайте напишем программку работы с текстовым файлом, данные о котором и будем выводить в Status Bar. Создадим семь полей панели со следующими свойствами:

 

1 - для картинки и имени файла

Alignment =1 - sbrCenter

Style=0 - sbrText

Bevel=1 - sbrInsert

AutoSize=2 - sbrContents

MinWidth=1440

 

2 - для размера файла

Alignment =1 - sbrCenter

Style=0 - sbrText

Bevel=1 - sbrInsert

AutoSize=2 - sbrContents

MinWidth=400

 

3 - для позиции выделения

Alignment =1 - sbrCenter

Style=0 - sbrText

Bevel=1 - sbrInsert

AutoSize=2 - sbrContents

MinWidth=800

 

4 - для размера выделения

Alignment =1 - sbrCenter

Style=0 - sbrText

Bevel=1 - sbrInsert

AutoSize=1 - sbrSpring

MinWidth=1440

 

5 - для статуса CAPS

Alignment =1 - sbrCenter

Style=1 - sbrCaps

Bevel=1 - sbrInsert

AutoSize=2 - sbrContents

MinWidth=430

 

6 - для статуса INS

Alignment =1 - sbrCenter

Style=3 - sbrIns

Bevel=1 - sbrInsert

AutoSize=2 - sbrContents

MinWidth=430

 

7 - для системного времени

Alignment =1 - sbrCenter

Style=5 - sbrTime

Bevel=1 - sbrInsert

AutoSize=2 - sbrContents

MinWidth=430

А кроме того, на вкладке General установим свойство MousePointer=99 - ccCustom и соответственно на вкладке Picture установим свой собственный курсор "d.ico" (он есть в исходнике примера вверху страницы).

Теперь, собственно, код. В кнопке Command1 мы будем открывать и считывать текстовой файл. Тут ничего нового нет. Код я передрал из главы 15. Единственное отличие в том, что при задании свойств часто приходится повторять имя объекта, довольно длинного (CommandDialog1 или StatusBar1). Чтобы этого избежать, в коде использована инструкция With, использование которой предельно просто и ясно из кода. Итак, пишем: Option Explicit Private Sub Command1_Click() Dim F As Long Dim Stroka As String On Error GoTo Cansel '**************************используем инструкцию With With CommonDialog1 .CancelError = True .Filter = "Текстовые файлы (*.txt) Файлы данных (*.dat)|*.txt;*.dat" .Flags = cdlOFNFileMustExist .DialogTitle = "Загрузить текстовой файл" .ShowOpen End With '*************************кончилась инструкция With F = FreeFile Text1.Text = "" Open CommonDialog1.FileName For Input As #F Do While Not EOF(F) Line Input #F, Stroka Text1.Text = Text1.Text & Stroka Loop 'в инструкции With присваиваем первому полю имя файла и картинку With StatusBar1 .Panels(1).Text = CommonDialog1.FileTitle .Panels(1).Picture = LoadPicture(App.Path & "\icon.jpg") 'а второму полю - длину файла .Panels(2).Text = LOF(F) & " байт" End With Close #F Exit Sub

Cansel: If Err.Number = cdlCancel Then Exit Sub Else MsgBox Err.Description End If End Sub

Кроме того в процедуре Text1_MouseMove мы получаем информацию о выделении и тоже запихиваем ее в панель StatusBar: Private Sub Text1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single) 'начальная позиция выделения StatusBar1.Panels(3) = "Позиция: " & Text1.SelStart 'количество выделенных знаков StatusBar1.Panels(4) = "Выделено: " & Text1.SelLength & " знаков"

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

Функция DoEvents.

Для того, чтобы твое приложение, имеющее длинные циклы, не мешало пользователю на время их выполнения заняться другой работой, временно передвинув или даже свернув твою красивую форму, в нашем могучем арсенале имеется невостребованная пока функция DoEvents, синтаксис которой приятно ласкает взгляд: DoEvents() и никаких тебе аргументов. Эта функция, согласно мануалу, возвращает значение типа Integer, представляющее собой количество открытых форм, если фокус находится на форме Visual Basic и ноль, если фокус находится на окнах других приложений. Ну это кому что интересно. Главное это то, что эта функция передает управление операционной системе, то бишь Windows, для обработки других событий. И после обработки Windows'ом всех событий из очереди и передачи всех нажатий клавиш из очереди функции SendKeys возвращает управление обратно в Visual Basic.

Хотя в использовании этой функции нет особенных трудностей, учитывая специфику сайта, лучше один раз пощупать функцию в маленькой процедурке, чем искать ошибку в большом проекте. Создадим Exe-проектик, положим на форму кнопку Command1 и текстбокс Text1и напишем маленькую процедурку:

Private Sub Command1_Click() Dim x As Long For x = 1 To 300000 Text1 = x Next x End Sub

Этот цикл по нашему разумению должен выводить в Text1 постоянно меняющееся значение переменной X от 1 до 300000. Запустим проект, нажмем кнопку Command1 и вместо созерцания мигающих циферек в текстбоксе мы видим... ничего мы не видим - форма наша не обновляется до окончания цикла. Да мы еще и не можем корректно приостановит выполнение цикла (я имею в виду , если он откомпилирован). Более того, попробовав передвинуть мышью форму мы убедимся, что это невозможно, а Менеджер задач (Task Manager) может выдать сообщение, что программа зависла и не отвечает (not responding). Вот так, ходим по минам, а подрываемся на говне. Однако всего этого можно избежать, если ввести в цикл функцию DoEvents. Тогда наша процедурка может выглядеть так: Private Sub Command1_Click() Dim x As Long For x = 1 To 300000 Text1 = x DoEvents Next x End Sub Конечно мигание цифр не радует глаз. Кроме того, при передаче управления Windows выполнение программы Visual Basic (в нашем случае цикла) приостанавливается, но зато все наши проблемы мгновенно решились: и форму можно двигать и форма обновляется. В связи с тем, что повторный вызов функции DoEvents в момент, когда функция еще не передала управление из операционной системы назад в Visual Basic может вызвать непредвиденные последствия, нежелательно вызывать процедуру DoEvents из других процедур программы. Видимо эти же соображения имеют место, когда предполагается, что нежелательно использовать функцию DoEvents в процедурах обработки событий Click. Но мне не удалось добиться никаких особенных фатальных результатов, кроме перезапуска цикла, даже беспрестанно кликая по кнопке Command1. Основным недостатком этой функции является очень значительное уменьшение быстродействия в длинных по времени циклах (в коротких ее вообще нет смысла использовать). Самое простое решение этого вопроса - запуск DoEvents не при каждой итерации, а например, при каждом сотом (или тысячном) проходе цикла, в зависимости от его параметров. Решить это можно, например, вводя дополнительную переменную-счетчик (в нашем примере y). С каждым проходом цикла счетчик увеличивается на единицу, а при достижении им значения, например 1000, вызывает функцию DoEvents и обнуляется, чтобы при дальнейшей работе цикла снова увеличится до 1000. Тогда наша процедура будет выглядеть следующим образом:

Private Sub Command1_Click() Dim x As Long Dim y As Long For x = 1 To 300000 Text1 = x y = y + 1 If y = 1000 Then 'проверяем счетчик итераций 'и если он совпадает с заданым числом, запускаем DoEvents и обнуляем счетчик DoEvents y = 0 End If Next x End Sub

Если тебя уже раздражает такое "ламерское" (но на мой взгляд удобное и наглядное решение), можно отследить тысячный проход цикла по-другому, например деля значения счетчика цикла x на эту тысячу с остатком (Mod) при каждой итерации, тогда при остатке ноль можно считать, что значение х кратно нашей тысяче. Тогда наш код будет выглядеть так: Private Sub Command1_Click() Dim x As Long Dim y As Long For x = 1 To 300000 Text1 = x y = y + 1 If x Mod 1000 = 0 Then DoEvents 'получаем остаток от деления 'и если он равен нулю, запускаем DoEvents  Next x End Sub А может быть снижение быстродействия не так уж и значительно и им можно пренебречь? Чтобы нам не принимать все на веру, мы сами проведем соответствующее сравнительное исследование зависимости быстродействия от частоты срабатывания функции DoEvents. Для этого нам на форме дополнительно понадобятся Timer1, и еще два текстбокса Text2 и Text3. В уже имеющийся Text1 мы будем вводить интервал цикла (по умолчанию он у нас будет 300000), в Text2 - на какой итерации (1-ой, т.е. каждой, 100-ой, 10000-й и т.д ) будет включаться функция DoEvents. В Text3 выведем результат измерений в миллисекундах. Объявим три переменных: Option Explicit Dim Iter As Long 'итерация срабатывания DoEvents Dim RunTime As Integer 'время работы цикла Dim RunLong As Long 'длина цикла В процедуре Form_Load присвоим начальные значения цикла и итераций: Private Sub Form_Load() Text2 = 1 Text1 = 1000000 End Sub

Код процедуры Command1_Click Изменим следующим образом: Private Sub Command1_Click() Dim x As Long  Dim y As Long Iter = Val(Text2) 'Присваиваем переменным значения для цикла RunLong = Val(Text1) RunTime = 0 Text3 = "Цикл в работе" Timer1.Interval = 1 'устанавливаем интервал таймера в 1 мск Timer1.Enabled = True 'запускаем таймер

For x = 1 To RunLong 'обратите внимание на то, что вывод значений переменной цикла X не выводится в Text1, 'так как это непомерные затраты времени и исказят наши значения. 'Text1 = x  y = y + 1 If y = Iter Then DoEvents y = 0 End If Next x Timer1.Enabled = False 'выключаем таймер Text3 = RunTime 'выводим значение счетчика таймера в текстбокс End Sub 'В процедуре таймера мы просто имеем счетчик, который увеличивается  ' на единицу при каждом включении, т. е. каждую миллисекунду.

Private Sub Timer1_Timer() RunTime = RunTime + 1 End Sub Скажу сразу, что данный эксперимент не ставит собой целью получить точные данные, т. к. это невозможно хотя бы из-за того, что процессор в Windows'е постоянно выполняет определенную работу в фоновом режиме, и хотя мы этого не видим, но отнимает время у нашей программы. Для получения корректных результатов промежуток времени работы цикла должен быть большой, с тем, чтобы погрешность, скажем 5 мск была бы не критичной. Попробуй запустить тест. При этом, конечно, не надо двигать форму по экрану, чтобы не занимать дополнительного времени. Мы увидим, что при включении функции DoEvents каждую 10-ю итерацию ускоряет цикл в 7-8 раз, каждую 100-ю - в 18-20 раз. Т.е. функция DoEvents снижает быстродействие просто в разы!!! Но тем не менее, использовать надо, но очень разумно. Используя эту функцию мы можем позволить пользователю прервать продолжительный процесс. Для этого достаточно ввести булеву переменную для флага, например Flag, которая и будет сигнализировать о том, что процесс необходимо прервать. Добавим на форму кнопку Command2, которой и будем прерывать выполнение цикла. В секцию (General) добавим объявление флага Dim Flag As Boolean В процедуре Command1_Click, прямо в самом начале установим начальное для цикла значения флага: Flag = False а в самом цикле поставим условие на выход If Flag = True Then Exit Sub Теперь, для выхода из цикла надо просто изменить значение флага False на True. Это мы и сделаем в процедуре Command2_Click:

Private Sub Command2_Click() Flag = True Text3 = "Цикл прерван" End Sub Вот и готово. Теперь наша кнопка Command2 прерывает цикл. Скачать исходник примера можно вверху страницы. С помощью функции DoEvents можно также организовать цикл ожидания, прерываемый пользователем. В качестве примера сделаем программку-шутку, которая иммитирует предупреждение пользователю о начале форматирования жесткого диска. Для этого в новом Exe-проекте на форме нам понадобится Label1, Command1 и Timer1. Объявим пару переменных: Option Explicit Dim RunTime As Integer 'время в секундах Dim Flag As Boolean 'флаг, сигнализирующей о прерывании цикла Для процедуры, собственно, цикла ожидания я выбрал событие формы _Activate, так как оно возникает, когда форма уже загружена и инициализирована и элементы на ней видимы. Private Sub Form_Activate() RunTime = 10 'устанавливаем начальные значения секунд Timer1.Interval = 1000 'интервал срабатывания таймера 1 сек Timer1.Enabled = True ' включаем таймер 'организуем цикл, работающий до тех пор, пока переменная RunTime 'не стане равной нулю (изменяется таймером) Do Until RunTime = 0 DoEvents 'постоянно передаем управление Windows If Flag Then 'проверяется, нажата ли кнопка Command1 для  'прерывания цикла (Flag=True) или нет (Flag=False) Label1 = "Молодец, успел, но все равно форматирование начинается!"  Exit Sub 'выход из процедуры, если кнопка Command1 нажималась End If 'конец условия Loop 'конец цикла Timer1.Enabled = False 'выключение таймера Label1 = "Не успел, не успел, надеюсь на винчестере ничего ценного нет?" End Sub В процедуре таймера при каждом срабатывании (ежесекундно) уменьшаем значение RunTime на единицу Private Sub Timer1_Timer() RunTime = RunTime - 1 'и выводим соответствующее сообщение в лейбл Label1 = "Форматирование диска начнется через " & RunTime & " сек. " _ & " Успеешь нажать кнопку?" End Sub Процедура Command1_Click() управляет флагом и выключает таймер Private Sub Command1_Click() Timer1.Enabled = False Flag = True End Sub  Скачать исходник этого примера можно отсюда.