Занятие второе

На первом занятии мы познакомились с созданием обработчиков сообщений. Вся концепция Borland C++ Builder построена на размещении рабочего кода внутри обработчиков. Обработчики трудны для понимания тех, кто писал программы для DOS без использования собщений. В таком случае нужно представить себе электрический провод, по которому течет ток к противоположному полюсу. Если вам нужно удостовериться в том, что ток по этому проводу протекает, вы можете разомкнуть цепь и вставить в нее измерительный прибор или, может быть, простую лампочку. Далее вы можете либо замкнуть другой контакт лампочки на землю, обесточив, таким образом, все остальные приборы в цепи, либо подключить второй контакт к другому участку цепи, позволив электричеству течь дальше и питать другие приборы. Поток сообщений в программе, выполненной в C++ Builder, похож на поток электронов. Они так же проходят через цепь подпрограмм, завершая свое существование где-то в недрах библиотеки компонентов VCL. Устанавливая обработчик, вы как бы "врезаетесь" в поток сообщений, перехватывая его. Только в отличие от лампочки в электроцепи обработчик "интеллектуален" и забирает лишь те сообщения, которые предназначены ему. Точно так же по аналогии с электричеством вы либо пропускаете поток сообщений дальше (вызываете обработчик класса-предка), либо не пропускаете (сообщение обработчику класса-предка не передается). Если не передать сообщение дальше по цепочке, возможно нарушение нормальной работы программы с разнообразными эффектами. Правда, библиотека VCL из C++ Builder достаточно умна и не допускает таких ситуаций.

В процессе выполнения кода компонента, отвечающего за прохождение события, VCL определяет, имеется ли у компонента обработчик для данного события и, если есть, вызывает его. Это выглядит примерно так:

if( Assigned( FOnClick ) ) 
        FOnClick( this );

Этот фрагмент проверяет, присвоен ли полю FOnClick, в котором хранится адрес вашего обработчика, корректный адрес функции-обработчика события OnClick. Если да, то обработчик вызывается.

Создание обработчиков событий в C++ Builder наполовину автоматизировано. Когда вы хотите создать обработчик, вы переключаете окно инспектора объектов в режим Events и делаете двойной щелчок на названии того события, которое вы хотите перехватить. Обратите внимание на то, что компонент, чье событие вы перехватываете, должен быть выделен.

Маленькая лабораторная работа вам не помешает. Создайте новый проект и на его форму положите компонент Button. Теперь переключите инспектор объектов, щелкнув на закладке Events, и дважды щелкните на строке OnClick. Напротив названия этого события, которое генерируется, когда пользователь нажал кнопку, появится имя только что созданного обработчика события, а в редакторе исходного текста в модуле, связанном с формой, автоматически добавилось тело обработчика события с тем же именем, которое появилось в инспекторе объектов.

Замечание. Для каждого компонента имеется так называемое событие по умолчанию. К примеру, для Button это OnClick. Если дважды щелкнуть на компоненте, C++ Builder создаст обработчик этого события.

По умолчанию имя создается из имени выделенного компонента и имени события. Например, Button1Click. Префикс On не добавляется. Но это не означает, что вы должны использовать именно это имя. Вы можете набрать новое имя, которое вам по душе, прямо в инспекторе объектов.

После того как новое имя введено, C++ Builder автоматически заменит название функции-обработчика в исходном тексте программы.

На самом деле происходящее за кадром несколько сложнее. Когда вы создаете новый обработчик, его описание появляется не только в модуле, но и в заголовочном файле этого модуля в секции __published. Именно в эту секцию C++ Builder складывает ссылки на все компоненты, имеющиеся в вашей форме, и обработчики событий. Вот пример заголовочного файла для программы с одной кнопкой на форме и обработчиком ее нажатия. Выделенные строки иллюстрируют этот подход.

#ifndef Unit1H
#define Unit1H
#include 
#include 
#include 
#include 
class TForm1 : public TForm
{
__published: // IDE-managed Components
        TButton *Button1;
        void __fastcall Button1Click(TObject *Sender);
private:        // User declarations
public:         // User declarations
        __fastcall TForm1(TComponent* Owner);
};
extern TForm1 *Form1;
#endif

Но вернемся к самому обработчику. Как вы сами можете видеть, у него нет возвращаемого значения и единственный аргумент Sender - указатель на компонент, сгенерировавший событие. В нашем примере это будет указатель на кнопку Button1. Чтобы использовать его, вам нужно будет привести его к типу самой кнопки. Это можно сделать двумя способами: старым

( TButton* ) Sender

или новым

static_cast< TButton* > ( Sender )

Внутри обработчика вы можете разместить любые операторы для выполнения любой задачи. Обработчики некоторых событий имеют дополнительные аргументы. Так, обработчик запроса на закрытие формы OnCloseQuery получает второй параметр, описанный как bool&CanClose. Это ссылка, устанавливая которую в true вы можете разрешить форме закрыться и наоборот, установив ее в false, вы не дадите программе закрыть форму и таким образом запретите завершение программы.

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

Следующая лабораторная работа будет связана с определением событий по умолчанию для разных компонентов. Для этого создайте командой File - New Application новое приложение. Кладите компоненты на форму по одному и производите на них двойной щелчок мышью. Посмотрите, обработчик какого события будет сгенерирован для того или иного компонента. Впоследствии это поможет вам разрабатывать новые программы настолько быстро, насколько это возможно.

Давайте теперь напишем маленькое приложение с типичным обработчиком сообщений. Оно будет перехватывать все сообщения от формы и записывать их в список компонента Memo. Создайте новый проект и добавьте к форме компонент Memo. Установите его свойство Enabled равным false, чтобы все нажатия на клавиатуре передавались форме, а не этому компоненту. Оставьте на форме свободное место, чтобы было куда щелкать мышью. Теперь добавьте обработчики для всех событий формы, кроме OnDragDrop и OnDragOver, - это совсем отдельный предмет для разговора. В каждом обработчике нужно добавить по строке, выводящей строку текста с названием обработанного события. Это выглядит так:

Memo1->Lines->Add("OnDestroy");

Здесь происходит обращение к свойству Lines компонента Memo. А через получаемый указатель на массив строк вызывается метод Add(), который добавляет строку в массив и отображает ее в окне компонента Memo. Все обработчики похожи как близнецы-братья, кроме OnDestroy. В него добавлена строчка

Memo1->Lines->SaveToFile("trace.txt");

которая сохраняет весь текст из окна компонента Memo в файл trace.txt. Это сделано для того, чтобы после завершения программы вы могли разобраться со всеми событиями, даже с теми, которые происходят уже тогда, когда форма уничтожается. Метод SaveToFile() занимается сохранением данных.

Полный исходный текст приложения приводится в листинге.

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

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

Возвращаясь к целесообразности написания таких "тупых" программ. Часто бывает удобнее и быстрее написать аналогичную маленькую программку для компонента и проследить его события, чем разбираться в документации и решать, к какому событию привязать тот или иной код. Что я вам и рекомендую сделать. Вы потратите день, но зато снимете с повестки дня большое количество вопросов.

Еще одна маленькая, но очень удобная возможность предоставляется вам C++ Builder. Это привязка разделяемых обработчиков событий. Когда у вас имеется обработчик, вы можете использовать его для нескольких событий. Все что нужно сделать, - в инспекторе объектов выделить событие, к которому вы привязываете обработчик, и из раскрывающегося списка выбрать подходящую функцию.

На следующем занятии мы с вами рассмотрим методику отладки приложений в среде C++ Builder. А в качестве домашнего задания потренируйтесь с обработчиками событий для разных компонентов.


Листинг

//-------------
//      Файл Unit1.h
//-------------
#ifndef Unit1H
#define Unit1H
#include 
#include 
#include 
#include 
#include 
class TForm1 : public TForm
{
__published:    // IDE-managed Components
        TMemo *Memo1;
        void __fastcall FormActivate(TObject *Sender);
        void __fastcall FormClick(TObject *Sender);
        void __fastcall FormClose(TObject *Sender, 
TCloseAction &Action);
        void __fastcall FormCloseQuery(TObject *Sender, 
bool &CanClose);
        void __fastcall FormCreate(TObject *Sender);
        void __fastcall FormDblClick(TObject *Sender);
        void __fastcall FormDeactivate(TObject *Sender);
        void __fastcall FormDestroy(TObject *Sender);
        void __fastcall FormHide(TObject *Sender);
        void __fastcall FormKeyDown(TObject *Sender, WORD &Key,
TShiftState Shift);
        void __fastcall FormKeyUp(TObject *Sender, WORD &Key, 
TShiftState Shift);
        void __fastcall FormKeyPress(TObject *Sender, char &Key);
        void __fastcall FormMouseDown(TObject *Sender, 
TMouseButton Button,
        TShiftState Shift, int X, int Y);
        void __fastcall FormMouseMove(TObject *Sender, TShiftState Shift, int X, int Y);
        void __fastcall FormMouseUp(TObject *Sender, 
TMouseButton Button,
        TShiftState Shift, int X, int Y);
        void __fastcall FormPaint(TObject *Sender);
        void __fastcall FormResize(TObject *Sender);
        void __fastcall FormShow(TObject *Sender);
private:        // User declarations
public:         // User declarations
        __fastcall TForm1(TComponent* Owner);
};
extern TForm1 *Form1;
#endif

//--------------
//      Файл Unit1.cpp
//--------------
#include 
#pragma hdrstop

#include "Unit1.h"
#pragma resource "*.dfm"
TForm1 *Form1;
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
}
void __fastcall TForm1::FormActivate(TObject *Sender)
{
        Memo1->Lines->Add("OnActivate");
}
void __fastcall TForm1::FormClick(TObject *Sender)
{
        Memo1->Lines->Add("OnClick");
}
void __fastcall TForm1::FormClose(TObject *Sender, 
TCloseAction &Action)
{
        Memo1->Lines->Add("OnClose");
}
void __fastcall TForm1::FormCloseQuery(TObject *Sender, 
bool &CanClose)
{
        Memo1->Lines->Add("OnCloseQuery");
  CanClose = true;      
}
void __fastcall TForm1::FormCreate(TObject *Sender)
{
        Memo1->Lines->Add("OnCreate");
}
void __fastcall TForm1::FormDblClick(TObject *Sender)
{
        Memo1->Lines->Add("OnDblClick");
}
void __fastcall TForm1::FormDeactivate(TObject *Sender)
{
        Memo1->Lines->Add("OnDeactivate");
}
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
        Memo1->Lines->Add("OnDestroy");
        Memo1->Lines->SaveToFile("trace.txt");
}
void __fastcall TForm1::FormHide(TObject *Sender)
{
        Memo1->Lines->Add("OnHide");
}
void __fastcall TForm1::FormKeyDown(TObject *Sender, 
WORD &Key,
        TShiftState Shift)
{
        Memo1->Lines->Add("OnKeyDown");
}
void __fastcall TForm1::FormKeyUp(TObject *Sender, WORD &Key, 
TShiftState Shift)
{
        Memo1->Lines->Add("OnKeyUp");
}
void __fastcall TForm1::FormKeyPress(TObject *Sender, 
char &Key)
{
        Memo1->Lines->Add("OnKeyPress");
}
void __fastcall TForm1::FormMouseDown(TObject *Sender, 
TMouseButton Button,
        TShiftState Shift, int X, int Y)
{
        Memo1->Lines->Add("OnMouseDown");
}
void __fastcall TForm1::FormMouseMove(TObject *Sender, 
TShiftState Shift, int X,
        int Y)
{
        Memo1->Lines->Add("OnMouseMove");
}
void __fastcall TForm1::FormMouseUp(TObject *Sender, 
TMouseButton Button,
        TShiftState Shift, int X, int Y)
{
        Memo1->Lines->Add("OnMouseUp");
}
void __fastcall TForm1::FormPaint(TObject *Sender)
{
        Memo1->Lines->Add("OnPaint");
}
void __fastcall TForm1::FormResize(TObject *Sender)
{
        Memo1->Lines->Add("OnResize");
}
void __fastcall TForm1::FormShow(TObject *Sender)
{
        Memo1->Lines->Add("OnShow");    
}