Доступ к компонентам формы

Систематизированный и переработанный материал.

Создавая программу в среде Borland C++ Builder программист помещает на форму множество компонент (различные кнопки, меню,таймеры, контейнеры для графики, списки и т.д). Естественно, возникает вопрос - как программно достучаться до того или иного компонента. Ответ прост - по имени компонента. Но, например, когда на один обработчик событий замкнуто несколько элементов, то вопрос о том как определить какой именно компонент вызвал событие становится совсем не таким простым. И, в тоже время, несколько механизмов Borland C++ Builder позволяют выполнить данную и другие, возникающие в практике задачи.

Конструкция TObject *Sender (ссылка на объект вызвавший событие) присутствует практически во всех обработчиках собитий компонент C++ Builder и может адресовать любые объекты. Для того, чтобы воспользоваться таким указателем, необходимо выполнить преобразование типа от абстрактного прородителя компонента (TObject) к конкретному. Преобразования можно выполнить двумя способами - статическим в стиле обычного C++ и динамическим (расширенным ANSI).

В начало

Доступ через статическое преобразование типов

Прямое преобразование типа

Данный обработчик прячет кнопку, вызвавшую событие ее нажатия:

void __fastcall 
TForm1::Button1Click(TObject *Sender)
{
 TButton *ptButton;
 ptButton=(TButton*)Sender;
 ptButton->Visible=false;
}

Этот обработчик можно назначить сразу нескольким компонентам, но только TButton. При попытке преобразования к другого типу компонента программа выдаст исключение на преобразование типов. Если же необходимо выполнить преобразование из неизвестного класса, то можно использовать конструкции try...catch, но проще воспользоваться динамическим преобразованием - оператором dynamic_cast.

Преобразование к TTreeView

Следующий код делает тоже, что и предыдущий (прячет кнопку):

.....
#include <vcl\comctrls.hpp>
......
void __fastcall 
TForm1::Button1Click(TObject *Sender)
{
 TTreeView * ptTreeViev=(TTreeView*) Sender;
 ptTreeViev->Visible=false;
}

Преобразование к классу TTreeView позволяет достаточно просто получить любое свойство объекта. Например, пусть обработчики событий OnClick двух кнопок направлены на один код Button1Click(). Для кнопки 2 установим свойство Cursor в crHandPoint.

.....
#include <vcl\comctrls.hpp>
......

void __fastcall 
TForm1::Button1Click(TObject *Sender)
{
 Memo1->Lines->Clear();
 TTreeView * ptTreeViev=(TTreeView*) Sender;
 Memo1->Lines->Add(ptTreeViev->Name);
 Memo1->Lines->Add(ptTreeViev->Align);
 Memo1->Lines->Add(ptTreeViev->Left);
 Memo1->Lines->Add(ptTreeViev->Cursor);
 ............
}

Результат:

Button1
0
264
0      //Курсор по умолчанию

Для кнопки 2:

Button2
0
450
-21   //Курсор в виде ладони

В начало

Доступ через динамическое преобразование типов

Динамическое преобразование типов доступно в программе через конструкцию:

dynamic_cast<TButton*>

Динамическое преобразование позволяет осуществить попытку преобразования некоторого типа объекта к указанному типу и при удаче возвращается значение отличное от нуля. Так, если обработчики нажатия двух кнопок компонента TButton и одной кнопки компонента TSpeedButton замкнуты на один код, то при нажатии кнопок компонентов TButton они будут скрыты, при нажатии кнопки компонента TSpeedButton ничего не произойдет:

void __fastcall 
TForm1::Button1Click(TObject *Sender)
{
 TButton* ptButton=dynamic_cast<TButton*>(Sender);
 if(ptButton == NULL) return;
 ptButton->Visible=false;
}

Если события нескольких одинаковых компонент используют одни и теже функции, то целесообразно их замкнуть на один обработчик, а различать объекты внутри обработчика можно по свойству Tag, присвоив ему разные значения для компонент.

Например, пусть у нескольких кнопок компонент TSpeedButton в соответствии с их номером установлено свойство Tag равное номеру кнопки, тогда:

void __fastcall
TForm1::SpeedButton1Click(TObject *Sender)
{
 if(dynamic_cast<TSpeedButton *>(Sender))
 {
  int i=((TSpeedButton *)(Sender))->Tag;
  switch(i)
  {
   case 0:
    .... //Код для кнопки 1
   break;
   case 1:
    .... //Код для кнопки 2   
   break;
  }
  .......// Общий код
 }
}

Посредством конструкции ((класс компонента *)(Sender))-> также можно получить доступ к любому свойству объекта.

В начало

Доступ по номеру компонента в иерархии объектов

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

void    __fastcall
vNewText(int viNumber,AnsiString vasNewText)
{
 AnsiString vasName=AnsiString("Label") + IntToStr(viNumber);
 for(int i = 0;i < Form1->ComponentCount; i++)
 {
  if(Form1->Components[i]->ClassNameIs("TLabel"))
  {
   if(((TLabel *)Form1->Components[i])->Name == vasName)
   {
    ((TLabel *)Form1->Components[i])->Name == vasNewText;
   }
  }    
 }
}

Кроме того можно использовать свойство Tag для определения конкретного компонента. Например, пусть на форме имеется 100 панелей, свойство Tag панели определяет номер панели (1-100). При передачи в функцию числа n - все панели с номером большим n исчезнут.

void    __fastcall
TForm1::vVisibleFalse(int viNumber)
{
 for(int i = 0;i < ComponentCount; i++)
 {
  if(Components[i]->ClassNameIs("TPanel") && 
                      Components[i]->Tag > viNumber)
  {
   if(Components[i]->Tag <= 100)
    ((TPanel *)Components[i])->Visible=false;
  }
  else if(Components[i]->ClassNameIs("TPanel") && 
                            Components[i]->Tag <= viNumber
   && Components[i]->Tag != 0)
    ((TPanel *)Components[i])->Visible=true;
 }
}

Аналогично свойству Tag можно использовать любое свойство, которое имеет компонент. В следующем коде ( кусочек кода из игры "Bricks" в которой изображение может быть разбито на от 4*4 до 10*10 фрагментов) эта конструкция использована для позиционирования TImage в зависимости от его номера и коэффициэнтов, рассчитанных для определения требуемого положения фрагмента изображения:

 for(int i = 0;i < ComponentCount; i++)
 {
  if(Components[i]->ClassNameIs("TImage") && Components[i]->Tag <= viNumX
     && Components[i]->ClassNameIs("TImage") && Components[i]->Tag >= viNumStartX
     && Components[i]->Tag != 0)
  {
   ((TImage *)Components[i])->Left=Components[i]->Tag*viK_X;
   ((TImage *)Components[i])->Top=Components[i]->Tag*viK_Y;
  }
 }//for(int i = 0;i < ComponentCount; i++)

В начало

Подчиненность объекта и определение его типа

Используя функцию ClassType() можно определить класс источника события (в примере для компонента TButton), а далее, применяя функцию ClassParent() можно двигаться вверх по иерархии объектов. Этот код размещен здесь по причине того, что метод ClassName() может быть использован для проверки типа объекта перед его использованием.

void __fastcall 
TForm1::Button1Click(TObject *Sender)
{
 TClass tClass;
 Memo1->Lines->Clear();
 tClass = Sender->ClassType();
 int i=0;
 while(tClass != NULL)
 {
  Memo1->Lines->Add(String(i)+"  "+tClass->ClassName());
  tClass = tClass->ClassParent();
  i++;
 }
}

Результат:

0  TButton
1  TButtonControl
2  TWinControl
3  TControl
4  TComponent
5  TPersistent
6  TObject

Пример использования метода ClassType() для проверки источника вызвавшего данное событие:

void __fastcall 
TForm1::Button1Click(TObject *Sender)
{
 TClass tClass;
 tClass = Sender->ClassType();
 AnsiString vasS=tClass->ClassName();
 if(vasS == "TButton")
 {
  TButton *ptButton;
  ptButton=(TButton*)Sender;
  ptButton->Visible=false;
 }
}

В начало

Домой