Главная » C++, C++ Builder » Формы до создания C++ Builder

0

Каким именно образом вы создаете новое окно? Это один из самых важных аспектов всей системы многодокументных приложений. Если вам доводилось работать с другими системами, вы, должно быть, привыкли доверять создание дочерних окон самой системе и знаете, что создание дочерних форм самостоятельно — обычно весьма болезненное предприятие. Каркасы (frameworks) созданы для конкретного вида работы, и поэтому очень затруднительно бывает обходить их ограничения для того, чтобы сделать что-то по-своему. Поскольку система CBuilder основана на компонентах, она даже не пытается делать вещи по-своему, предоставляя вам возможность делать все так, как вам хочется.

Для того чтобы обосновать вышесказанное, давайте обратимся  к  обработчику  команды  меню Файл – Новый. Этот обработчик должен создать и отобразить на экране новую дочернюю форму. Как правило,  вам хотелось бы иметь возможность изменять название формы, чтобы оно несло смысловую нагрузку. В данном  случае мы озаглавим форму «Scribble — дочернее».  Для этого добавьте новый обработчик для пункта меню Файл – Новый и в него добавьте следующие строки:

void __fastcall TForm2::New1Click(TObject *Sender)

{

TForm1 *pForm1 = new TForm1(Application); pForm1->Caption = "Scribble _ дочернее";

}

Странным выглядит отсутствие в вышеуказанном коде каких-либо упоминаний о многодокументности (MDI). Новая форма создается как  дочернее  окно  приложения.  Свойство Form Style установлено в fsMDIChild, и поэтому при создании форма автоматически становится дочерним окном многодокументного приложения. Как вы видите, вся работа по созданию формы как дочерней для главной формы делается за вас компонентом как таковым.

Вторая строка кода просто присваивает свойству Caption формы значение «Scribble — дочернее»; это название и будет отображено на панели заголовка дочернего окна,  когда  оно  появится  на экране. После того как наберете указанные строки кода, скомпилируйте и запустите приложение. Выберите команду меню Файл – Новый, и, с красивым отступом от предыдущего, появится новое дочернее окно.

Замечание

Как вы, наверное, заметили, первое дочернее окно, которое появляется на экране  сразу  при запуске программы, не имеет заголовка «Scribble — дочернее» , поскольку оно было создано автоматически и не прошло через процесс, предусмотренный выбором команды Файл д Новый. Для разрешения этой проблемы есть два пути. Отмените автоматическое создание  дочерней формы в своем приложении и начинайте работу программы с пустого родительского окна — такой вариант широко распространен в программах Windows. Второе решение лежит в присвоении изначального значения свойству Caption уже во время проектирования приложения и присоединении к нему уникального значения переменной, соответствующего  номеру дочернего окна, при создании окон во время исполнения. Стандартный в Windows вариант — использование некоторого имени («Scribble — дочернее», например) и номера дочернего окна (по порядку создания). Так, например, первое окно будет иметь название «Scribble — дочернее 1», второе —

«Scribble — дочернее 2» и т. д.

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

Лучшее место, где можно хранить данные в нашем примере, — это внутри какой-нибудь формы, которая представлена в единственном экземпляре. Поскольку дочерние формы не отвечают данному критерию, единственным местом, куда можно положить данные, остается родительская форма (класс Form2). Она прекрасно отвечает всем требованиям и позволяет нам в определенной степени контролировать данные, имеющиеся в приложении. Итак, давайте так и сделаем.

Для того чтобы убрать данные из дочерних форм, нам придется изменить код для дочерних форм

(Form1). Вот измененный код для исходного файла:

//——————————————————————-

#include <vcl\vcl.h>

#pragma hdrstop

#include "Unit1.h"

#include "MainForm.h"

//——————————————————————-

#pragma resource "*.dfm" TForm1 *Form1;

//——————————————————————-

__fastcall TForm1::TForm1(TComponent* Owner)

: TForm(Owner)

{

FbMouseDown = FALSE;

}

//——————————————————————-

void __fastcall TForm1::OnMouseDown(TObject *Sender, TMouseButton Button,

TShiftState Shift, int X, int Y)

{

FbMouseDown = TRUE;

// Переместиться в начальную точку

Canvas->MoveTo(X,Y);

// Вводим в главную форму новые данные

// Заметьте: мы очищаем все существующие точки

Form2->ClearPoints; Form2->AddPoint( X,Y );

}

//——————————————————————-

void __fastcall TForm1::OnMouseMove(TObject *Sender, TShiftState Shift,

int X, int Y)

{

if ( FbMouseDown )

{

Canvas->LineTo( X,Y );

// Обновляем главную форму новыми данными

Form2->AddPoint( X,Y );

}

}

//——————————————————————  -

void __fastcall TForm1::OnMouseUp(Toblect *Sender, TMouseButton Button,

TShiftState Shift, int X, int Y)

{

FbMouseDown = FALSE;

}

//——————————————————————-

void __fastcall TForm1::OnPaint(TObject *Sender)

{

if ( Form2->NumberOfPoints() > 0 )

{

int X = 0, Y = 0;

// Получаем первую точку Form2->GetPoint( 0,X,Y ); Canvas->MoveTo(X,Y);

// Проходим по каждой точке, получаем ее из

// главной формы и перерисовываем

for ( int i=1;i<Form2->NumberOfPoints();++i )

{

Form2->GetPoint( i,X,Y ); Canvas->Lineto( X,Y );

}

}

}

//——————————————————————-

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

//——————————————————————-

#ifndef Unit1H

#define Unit1H

//——————————————————————-

#include <vcl\Classes.hpp>

#include <vcl\Controls.hpp>

#include <vcl\StdCtrls.hpp>

#include <vcl\Forms.hpp>

//——————————————————————-

class TForm1 : public TForm

{

__published: // IDE-managed Components

void __fastcall OnMouseDown(TObject *Sender, TMouseButton Button,

TShiftState Shift, int X, int Y);

void __fastcall OnMouseMove(TObject *Sender, TShiftState Shift, int X, int Y);

void __fastcall OnMouseUp(TObject *Sender, TMouseButton Button,

TShiftState Shift, int X, int Y);

void __fastcall OnPaint(TObject *Sender); private: // User declarations

BOOL FbMouseDown;

public: // User declarations

__fastcall TForm1(TComponent* Owner);

};

//——————————————————————-

extern TForm1 *Form1;

//——————————————————————-

#endif

Итак, мы убрали все эти замечательные строки кода из описания класса Form1 и переместили их, добавив странные вызовы методов в класс Form2. Какова же функция этих строк в классе Form2? Вы поймете, что же на самом деле происходит, если добавите нижеследующие строки в заголовочный файл Unit2.h:

const int MaxPoints = 100; class TForm2 : public TForm

{

__published: // IDE-managed components TMainMenu *MainMenu;

TMenuItem *File1;

TMenuItem *New1; TMenuItem *Exit1; TMenuItem *Update1; TMenuItem *AllWindows1;

void __fastcall New1Click(TObject *Sender); private: // User declarations

int FnPoint;

int FPointX[MaxPoints+1]; int FPointY[MaxPoints+1]; public // User declarations

__fastcall TForm2(TComponent* Owner); void ClearPoint(void)

{

FnPoint = 0;

}

void AddPoint(int X, int Y)

{

if ( FnPpoint < MaxPOints )

{

FPointX[FnPoint] = X; FPointY[FnPoint] = Y; FnPoint++;

}

}

int NumberOfPoints(void)

{

return FnPoint;

}

void GetPoint( int Index, int& X, int& Y )

{

if ( Index >= 0 && Index < FnPoint )

{

X = FPointX[Index]; Y = FPointY{index];

}

}

};

//——————————————————————-

extern TForm2 *Form2;

//——————————————————————-

#endif

Не кажется ли вам приведенный код знакомым? Должен  бы.  Большая  часть  его  просто скопирована из старой формы Scribble и упакована в методы этого объекта. Этот процесс называется инкапсуляцией (encapsulation) и является важным моментом в  программировании  на C++. Инкапсулируя доступ к изменению и восстановлению данных в методы объекта, мы защищаем данные от порчи, которую могут осуществить объекты. Кроме того,  поскольку  мы убрали данные из непосредственно изменяющихся блоков программы, мы оставили открытой возможность изменять способ хранения данных. Представьте, например, что данные больше не хранятся в виде простого статического массива, как сейчас. Представь те, что они хранятся в виде некоего динамического массива или даже хэш-таблицы (hash table). Возможно, наконец, что точки на самом деле хранятся на диске. Безотносительно того, как хранятся точки на самом деле, подобное представление позволит нам спрятать настоящий  формат  данных  от  остальных объектов. Абсолютно не важно, как преобразуется объект Form2 с точки зрения данных, ведь пока сигнатура (параметры и типы возвращаемых значений функций) методов ClearPoints, AddPoint, NumberOfPoints  и  GetPoint  не  меняется,  объекту  Form1  незачем  знать  об  этом.  Это  и  есть

«маскировка данных» в С++. Сами данные спрятаны в методах, которые используются для обращения к данным. Маскировка данных в чистом виде применяется в концепции свойств компонентов VCL, которые мы рассмотрим несколько позже. Я завел этот разговор только потому, что многие почему-то считают концепцию свойств не объектно-ориентированной и неудобной. Как мы увидим несколько дальше, это совсем не так.

Что же привлекает нас во всем этом? Ответ прост. Переместив все данные в один объект и обращаясь к ним через этот объект, мы сделали так, что все формы Scribble выглядят теперь одинаково и отражают все изменения, сделанные в одной из них. Не верите? Попробуйте сами прямо   сейчас,  скомпилировав  и  запустив  приложение.   Откройте  несколько   дочерних  окон,

несколько раз выбрав команду Файл д Новый. Выберите одно из окон и нарисуйте что-нибудь в его поле. Перейдите  теперь к другому окну. Вы увидите, что окно автоматически обновилось, использовав данные из другого окна.

Извечная проблема

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

Помните, некоторое время назад я просил вас добавить пункт меню, озаглавленный Обновить все, в меню Вид. Сейчас объясню, зачем. Метод, связанный с этой командой меню, призван решить рассматриваемую проблему, заставляя все дочерние окна обновиться.

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

void __fastcall TForm2::AllWindowsClick(TObject *Sender)

{

for ( int i=0; i<MDIChildCount; ++i )

{

Form1 *pForm = static_cast<TForm1 *>(MDIChildren[i]); if ( pForm )

pForm->Invalidate();

}

}

Что же здесь происходит? У формы есть свойство MDIChildCount, которое равно текущему количеству открытых дочерних окон. Это свойство есть на самом деле у всех форм,  но  его значение равно 0, если стиль формы не является fsMDIForm. Кроме количества дочерних окон, у формы есть свойство, называемое MDIChildren, которое является динамическим массивом указателей на формы. Мы проходим по всем дочерним формам нашей родительской формы и обновляем их, используя метод Invalidate.

Если программирование под Windows не является для вас привычным, то модель рисования окна покажется вам немного странной. Рисование сопряжено с отменой действия (invalidation) частей форм. Отмена действия означает, что операционной системе  посылается сообщение о том,  что часть окна (или окно целиком) надо перерисовать. Кто угодно может сообщить окну, что оно должно быть перерисовано, просто вызвав метод Invalidate этого окна (или формы).

Последнее, о чем стоит упомянуть в связи с нашим небольшим кусочком кода, — это оператор static_cast. Функция static_cast является новой для C++, она появилась только в последней версии стандарта ANSI C++. Функция static_cast пытается привести объект к заданному типу. Общий вид записи оператора static_cast выглядит следующим образом:

T *pT = static_cast<T *>(someobjpointer);

где T — это тип (как TForm1 в примере выше), который определен в системе, а someobjpointer — заданный указатель. Обратите внимание на то, чтобы этот параметр был указателем, иначе вы получите ошибку.

Если преобразование прошло успешно, возвращаемое значение — это корректный указатель на объект типа T. Если преобразование не было успешным, возвращается значение NULL. Поэтому проверьте возвращенное значение на равенство NULL  перед тем, как использовать метод Invalidate для этого объекта.  Это основная смысловая  проверка,  которая доступна вам в вашем приложении. Если вы работаете с указателями, перед обращением проверяйте их значения на равенство NULL.

Последний шаг

Осталась  последняя  вещь,  которую мы  должны  сделать,  чтобы  завершить приложение Scribble.

Тем более впечатляющую, что для ее осуществления нам потребуется единственная строка кода.

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

Добавьте новый обработчик для пункта меню Файл д Выход и добавьте в него следующие строки: void __fastcall TForm2::ExitClick(Tobjest *Sender)

{

Application->Terminate();

}

Объект Application (приложение) — это глобальный интерфейс с задачей в операционной системе, которая соответствует нашему приложению. Метод приложения Terminate освобождает все существующие формы, указатели и управляющие элементы и затем закрывает все окна приложения. Application->Terminate() — это наиболее предпочтительный метод для завершения работы приложения, и он может безопасно вызываться почти что из любого места в программе. Не надо просто закрывать окна, надеясь, что все хорошо; используйте метод Terminate.

Форма может быть создана без заголовка. Что более важно, начальные параметры формы могут быть изменены программно до того, как форма будет отображена на экране. Это осуществляется при помощи метода формы CreateParams (создать параметры).

Формы обладают множеством свойств.  Например,  свойство  Caption  (заголовок)  управляет текстом, отображенным на панели заголовка формы. Свойство Canvas (холст) используется для рисования в клиентской области формы.

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

опосредованно, ассоциировав их с событием. Это позволяет динамически изменять обработчики событий во время исполнения.

Файлы описания формы (Form Definition Files, DFM) можно просмотреть в редакторе как обычные текстовые файлы.

Копирование проекта должно производиться отдельно от сохранения проекта под новым именем при использовании команды File д Save Project As (сохранить проект как).

В CBuilder легко и просто использовать растровые рисунки. Они могут быть  загружены посредством метода LoadFromFile (загрузить из файла) и нарисованы с помощью метода Draw (нарисовать) объекта Canvas.

Формы многодокументных приложений (Multiple Document Interface, MDI) — это, в  принципе, обычные формы со специфическим значением свойства Form Style (стиль формы) — fsMDIForm. Дочерние окна точно такие же, но у них значение свойства Form Style установлено в fsMDIChild.

При помощи метода Application->Terminate() рекомендуется заканчивать работу приложения.  Вот  и  все  со  второй  главой.  В  следующей  главе  мы  начнем  изучение  графики  в  CBuilder  на

примере создания небольшой забавной игрушки для скрашивания досуга.

Источник: Теллес М. – Borland C++ Builder. Библиотека программиста – 1998

По теме:

  • Комментарии