ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы.



 

Часть 6

Глава 5. Организация диалога
К этому моменту вы создали только два типа окон: головное окно (для объекта TMyWindow) и блоки сообщений (для вывода текстов "Feature not implemented" ("Возможность не реализована" и "Are you sure you want to quit ?" ("Вы уверены в том, что хотите законч
ить работу ?"). Хорошо развитая программа Windows может иметь 10 и даже более объектов окна (окна, управления и диалоги), связанных с ним. За исключением головного окна, для каждого окна есть одно родительское окно, от которого оно зависит. Это зависимое
 окно называется дочерним. Например, когда пользователь закрывает родительское окно, его дочерние окна также закрываются.
Эти взаимоотношения приводят к возникновению сети связанных между собой родительских и дочерних окон для каждого приложения с головным окном в качестве всеобщего единственного родителя. В этой сети большинство окон являются дочерними для одного окна и ро
дительскими для других. Ни одно из окон не является сиротой. Группа связанных дочерних и родительских окон для MyProgram показана на Рис.5.1.
                    приложение
                    (TMyApplication)
                         і
                         і
                         і
                    головное окно ДДДДДДДДДД файл диалога
                    (TMyWindow)              (TFileDialog)
                         і
                         і
                         і
управление  ДДДДДДД окно подсказок ДДДДДДДДД блок списка
редактированием     (THelpWindow)            (TListBox)
(TEdit)               і         і
                      і         і
                      і         і
                      і         і
                    кнопки     статическое управление
                    (TButton)  (TStatic)
Рис.5.1. Группа связанных родительских и дочерних окон
Существуют два типа дочерних окон. Один тип это независимое дочернее окно. Этот тип сам управляет своим появлением и местоположением. Блоки сообщений и всплывающие окна являются независимыми дочерними окнами. Другой тип составляют зависимые дочерние окна
. Окна этого типа появляются на поверхности своего родительского окна и перемещаются вместе с ним. Управления, например кнопки на блоках сообщений, и некоторые другие типы дочерних окон являются зависимыми дочерними окнами. На данном этапе вы создадите д
ва типа независимых дочерних окон: всплывающие окна и блоки диалога.
Не следует путать термины родительский и дочерний с предшествующим и следующим типами. Термины родительский и дочерний имеют отношение к собственности. Например, головное окно владеет окном подсказок. Построение этого окна начнется в данном разделе. Конц
епция собственности в Windows практически идентична концепции принадлежности или или компоновке экземпляров в объектно-ориентированном программировании. И действительно, как вы увидите далее в данном руководстве, объекты дочернего окна обычно хранятся в 
полях данных их объектов родительского окна. Хотя это и не является обязательным, подобные приемы являются хорошей практикой программирования, которая приводит к ясным программам, которые легко обслуживать.
Шаг 8: Добавление всплывающего окна
Давайте добавим к MyProgram всплывающее окно, которое появляется в качестве реакции на выбор варианта Help из меню. Это окно может послужить основой для системы подсказок для MyPrgramm. Сейчас сделаем всплывающее окно объектом TWindows. На Шаге 10 вы соз
дадите для него новый тип и снабдите его некоторым полезным поведением. В данный момент сосредоточимся на том, чтобы вызвать его на экран.
Поскольку это первое добавляемое вами "новое" окно, отличное от головного окна, которое выводится автоматически, это очень удобный момент рассмотрения вопросов создания и отображения объектов и элементов окна.
На данном этапе мы изменим реакцию на выбор из меню варианта Help таким образом, что будет создаваться и отображаться окно подсказок.
Создание и показ всплывающего окна
На Шаге 7 ваша программа была сделана так, что она реагировала на выбор Help из меню появлением блока сообщения. Здесь мы заменим этот блок сообщения на пустой объект всплывающего окна, HelpWnd. Хотя непосредственный результат будет еще менее интересным,
 чем блок сообщения, мы добавим окну подсказок на Шаге 10 значительно больше вариантов поведения.
А теперь давайте переопределим Help для создания и вывода окна подсказок:
procedure TMyWindow.Help(var Msg: TMessage);
var HelpWnd: PWindow;
begin
 HelpWnd:=New(PWindow, Init(@Self, 'Help System');
 with HelpWnd^.Attr do
 begin
  Style:=Style or ws_PopupWindow or ws_Caption;
  X:=100;
  Y:=100;
  W:=300;
  H:=300;
 end;
 Application^.MakeWindow(HelpWnd);
end;
На Рис.5.2 показано новое окно подсказок, которое появляется тогда, когда пользователь выберет вариант Help.
Рис.5.2. Новое окно подсказок MyProgram
Функция MakeWindow
Объект TApplication определяет очень важный метод MakeWindow. MakeWindow выполняет все действия, которые необходимы для безопасного связывания элемента интерфейса с объектом окна. Под словом "безопасное" понимается то, что MakeWindow проверяет возникнове
ние ошибочных условий до того, как они могут вызвать серьезные проблемы, подобные зависанию вашей системы. MakeWindow выполняет два важных этапа.
Сначала проверяется корректность интерфейса объекта путем вызова ValidWindow, где проверяется, что ваше приложение не превышает доступный размер памяти. Конструирование полностью прекращается, если имеется ошибочная ситуация.
Наиболее полное описание ValidWindow содержится в разделе "Разработка безопасных программ"  Главы 19.
Если объект интерфейса сконструирован успешно, то MakeWindow делает попытку выполнить свой второй этап: создание элемента интерфейса методом Create. Create использует информацию из полей объекта для информирования Windows о том, какой вид элемента интерф
ейса вам нужен. Если элемент не может быть создан, то всплывет блок с сообщением об ошибке.
MakeWindow даст на выходе nil, если не удастся либо проверка корректности, либо не удается создание элемента интерфейса. В противном случае в качестве параметра будет передан указатель на объект интерфейса.
Вызов MakeWindow это самый безопасный способ создания элементов интерфейса. Непосредственный вызов Create без проверки памяти и обработки возможных ошибок очень опасен и не рекомендуется его делать.
Добавление блока диалога
Блок диалога подобен всплывающему окну, но он обычно остается на экране очень короткое время и выполняет одну конкретную задачу, связанную с вводом, например, выбор принтера или установка страницы документа. Сейчас вы добавите в MyProgram блок диалога дл
я открытия и сохранения файлов.
Подобно всплывающему окну блок диалога является независимым дочерним окном. На концептуальном уровне добавление блока диалога в качестве дочернего окна полностью совпадает с операцией добавления всплывающего окна, которую вы решали в предыдущем разделе. 
И, вообще говоря, блок диалога очень похож на окно, за исключением следующих главных отличий:
- Тип объекта диалога происходит от типа TDialog, а не от типа   TWindow. И TDialog и TWindow оба происходят от TWindowObject.
- Блоки диалога обычно требуют ресурсов, которые задают их размер,   местоположение и появление.
- Блоки диалога обычно выполняют короткую задачу и возвращают   значение. Например, блок сообщения CanClose, созданный на Шаге 2   ограниченный тип диалога, возвращает ответ пользователя yes (да)   или no (нет).
Мы добавим один из блоков диалога ObjectWindows, блок диалога файла, с типом TFileDialog, производный от TDialog. Блок диалога файла полезен в любой ситуации, в которой вы запрашиваете  пользователя выбрать файл на диске для сохранение или загрузки. Напр
имер, приложение обработки текстов будет использовать блок диалога файла для открытия и сохранения документов. Блок диалога файла будет появляться как реакция на выбор пользователем варианта File|Open или File|SaveAs. Блок диалога файла заменит блок сооб
щения "Feature not implemented" (функция не реализована). На Шаге 9 он буде использован на реальных файлах для их сохранения и открытия с целью записи и поиска реальных данных. На данном этапе мы только покажем блоки диалога. На Рис.5.3 показано появлени
е блока диалога файла.
Рис.5.3. MyProgram с блоком диалога файла
Добавление объектного поля
Вместо хранения всего объекта блока диалога файла в поле его родительского окна, мы будем конструировать новый объект блока диалога файла каждый раз, когда он нам потребуется. Вместо этого вы будете хранить только данные, которые будут использоваться с б
локом диалога файла: имя и маска файла. Это хороший прием программирования. Вместо хранения всех объектов, которые могут нам и не потребоваться, будем просто хранить данные, которые нужны для инициализации объектов, если они вам потребуются.
Для конструирования блока диалога файла нужны три параметра: родительское окно, шаблон ресурса и имя файла или его маска, в зависимости от того, используется диалог для открытия или закрытия файла. Шаблон ресурса задает, какой из стандартных блоков диало
га файла вы будете использовать. Стандартные ресурсы диалога файла идентифицируются ID ресурса sd_FileOpen и sd_FileSave. Параметр имени файла используется для передачи маски файла по умолчанию диалогу открытия файла (и для возвращения выбранного имени ф
айла), и для передачи имени по умолчанию для записи файла.
Параметр шаблона ресурса определяет, используется ли блок диалога файла для открытия или закрытия файла. Если ресурс диалога содержит блок списка файлов с ID управления id_FList, то блок диалога предназначен для открытия файлов. Отсутствие такого блока с
писка указывает на то, что блок диалога предназначен для сохранения файлов.
Определение типа TMyWindow должно теперь выглядеть следующим образом:
          TMyWindow=object(TWindow)
          ...
          FileName: array[0..fsPathName] of Char;
          ...
Модификация конструктора Init
Вы уже создали конструктор Init для типа TMyWindow для инициализации объекта окна подсказок. Сейчас нужно добавить к нему код для инициализации FileName: 
          StrCopy(FileName, '*.PTS');
Расширение .PTS будет использоваться для файлов, которые будут хранить точки вашего изображения.
Запуск блока диалога
В зависимости от параметра шаблона ресурса, переданного конструктору объекта диалога файла, блок диалога может обеспечивать либо открытие файла, либо его запись. В любом случае результатом будет блок диалога, аналогичный показанному на Рис.5.3. Есть два 
отличия между диалогами по открытию файла и его сохранением: Диалог открытия файла содержит список файлов, находящихся в текущем каталоге и удовлетворяющих текущей маске файла. Диалог сохранения файла первоначально показывает текущее имя файла в области 
редактирования управления редактированием диалога, и не содержит списка файлов.
FileOpen и FileSaveAs нужно переписать следующим образом:
procedure TMyWindow.FileOpen(var Msg: TMessage);
begin
 if Application^.ExecDialog(New(PFileDialog,
  Init(@Self, PChar(sd_FileOpen), FileName)))=id_Ok then
  MessageBox(HWindow, FileName, 'Open the file:', mb_Ok);
end;
procedure TMyWindow.FileSaveAs(var Msg: TMessage);
begin
 if Application^.ExecDialog(New(PFileDialog,
  Init(@Self, PChar(sd_FileSaveAs), FileName)))=id_Ok then
  MessageBox(HWindow, FileName, 'Save the file:', mb_Ok);
end;
Полный исходный код программы MyProgram на данном этапе ее разработки содержится в файле STEP8.PAS .
Аналогично использованию TApplication.MakeWindow для создания "безопасных" элементов интерфейса, можно использовать TApplication.ExecDialog для создания и исполнения безопасных блоков диалога. Аналогично MakeWindow execDialog вызывает ValidWindow для под
тверждения успешного конструирования объекта блока диалога. Если это так, то ExecDialog вызывает метод Execute объекта блока диалога. После выполнения блока диалога ExecDialog возвращает значение от Execute. В случае возникновения ошибки он возвращает id
_Canceled, и для вашей программы это выглядит так, как если бы пользователь отменил блок диалога. В любом случае ExecDialog удаляет объект блока диалога.
Использование ExecDialog это самый безопасный путь работы с блоками диалога. Хотя и можно вызвать Execute непосредственно, это очень опасно, т.к. появляется возможность зависания вашей системы.
Шаг 9: Сохранение рисунка в файле
Теперь наши данные, представляющие изображение, хранятся как часть объекта окна, и нам нужно получить возможность передавать эти данные в файл (в действительности в поток DOS) и считывать их обратно. На данном шаге вы добавите поля объекта для хранения с
татуса записи и измените методы записи и открытия файла.
Управление состоянием
Есть две характеристики изображения, которыми вам нужно управлять. Первая говорит о том, нужно ли записывать файл, а вторая - загружен ли файл в настоящий момент. Эти характеристики можно рассматривать как Булевские атрибуты TMyWindow и представить их ка
к поля объекта:
          TMyWindow=object(TWindow)
               ...
               IsDirty, IsNewFile: Boolean;
               ...
          end;
IsDirty имеет значение True, если текущее изображение "грязное". Слово "грязное" означает, что его нужно сохранить, т.к. оно изменилось с момента своего последнего сохранения, либо его вообще еще не записывали. Когда пользователь начинает рисование (WMLB
uttonDown), нужно установить значение IsDirty True. Когда пользователь открывает новый файл или записывает существующий, нужно установить значение IsDirty False. Когда пользователь закрывает приложение (CanClose), нужно проверить статус IsDirty и отобраз
ить блок сообщения только в том случае, если его значение True.
IsNewFile имеет значение True только при первом запуске приложения (Init) и после того, как пользователь выбрал из меню File|New (FileNew). Его значение устанавливается False, если файл открывается (FileOpen) или сохраняется (FileSave или FileSaveAs). В 
действительности FileSave использует NewFile для проверки, можно ли записать файл немедленно (False), или пользователю нужно выбрать файл из диалога файла (True).
Далее приведен листинг метода CanClose и методов загрузки и сохранения файла. На данном этапе они делают все, кроме записи и загрузки файлов. Поведение при записи файла выделено в один новый метод с именем SaveFile, а открытие выделено в LoadFile.
function TMyWindow.CanClose: Boolean;
var
 Reply: Integer;
begin
 CanClose:=True;
 if IsDirty then
 begin
  Reply:=MessageBox(HWiindow, 'Do you want to save ?',
   'File has changed', mb_YesNo or mb_IconQuestion);
  if Reply=idYes then CanClose:=False;
 end;
end;
procedure TMyWindow.FileNew(var Msg: TMessage);
begin
 if CanClose then
 begin
  Points^.FreeAll;
  InvalidateRect(HWindow, nil, True);
  IsDirty:=False;
  IsNewFile:=True;
 end;
end;
procedure TMyWindow.FileOpen(var Msg: TMessage);
begin
 if CanClose then
  if Application^.ExecDialog(New(PFileDialog, Init(@Self,
   Pchar(sd_FileOpen), StrCopy(FileName, '*.PTS'))))=id_Ok then
   LoadFile;
end;
procedure TMyWindow.FileSave(var Msg: TMessage);
begin
 if IsNewFile then FileSaveAs(Msg) else SaveFile;
end;
procedure TMyWindow.FileSaveAs(var Msg: TMessage);
begin
 if IsNewFile then StrCopy(FileName, '');
 if Application^.ExecDialog(New(PFileDialog,
  Init(@Self, Pchar(sd_FileSave), FileName)))=id_Ok then
  SaveFile;
end;
procedure TMyWindow.LoadFile;
begin
 MessageBox(HWindow, @FileName, 'Load the file:', mb_Ok);
 IsDirty:=False;
 IsNewFile:=False;
end;
procedure TMyWindow.SaveFile;
begin
 MessageBox(HWindow, @FileName, 'Save the file:', mb_Ok);
 IsNewFile:=False;
 IsDirty:=False;
end;
Запись и загрузка файлов
Теперь у нас уже есть обрамление для записи и загрузки файлов. Еще  до сих пор не реализована возможность записи и загрузки набором точек в файл. Для этого будет использован автоматический механизм  сохранения объектов потока. Сначала нужно научить точки
 хранить и загружать себя (поскольку наборы уже умеют это делать). Затем для  использования потоков изменим методы FileSave и FileOpen.
Приведем код, необходимый для обучения объектов TDPoint для хранения и загрузки самих себя:
type
 PDPoint=^TDPoint;
 TDPoint=object(TObject)
  X, Y: Integer;
  constructor Init(AX, AY: Integer);
  constructor Load(var S: TSteam);
  constructor Store(var S: TSteam);
 end;
const
 RDPoint: TSteamRec=(
  ObjType: 200,
  VmtLink: Ofs(TypeOf(TDPoint)^);
  Load: @TDPoint.Load;
  Store: @TDPoint.Store);
procedure StreamRegistration;
begin
 RegisterType(RCollection);
 RegisterType(RDPoint);
end;
constructor TDPoint.Load(var S: TStream);
begin
 S.Read(X, SizeOf(X));
 S.Read(Y, SizeOf(Y));
end;
constructor TDPoint.Store(var S: TStream);
begin
 S.Write(X, SizeOf(X));
 S.Write(Y, SizeOf(Y));
end;
Затем, при запуске приложения нужно обязательно вызвать StreamRegistration. Этот вызов можно поставить в метод TMyWindowInit.
Итоговый этап будет состоять в переписывании методов SaveFile и LoadFile:
procedure TMyWindow.LoadFile;
var
 TempColl: PCollection;
 TheFile: TDosStream;
begin
 TheFile.Init(FileName, stOpen);
 TempColl:=PCollection(TheFile.Get);
 TheFile.Done;
 if TempColl <> nil then
 begin
  Dispose(Points, Done);
  Points:=TempColl;
  Invalidate(HWindow, nil, True);
 end;
 IsDirty:=False;
 IsNewFile:=False;
end;
procedure TMyWindow.SaveFile;
var
 TheFile: TDosStream;
begin
 TheFile.Init(FileName, stCreate);
 TheFile.Put(Points);
 TheFile.Done;
 IsNewFile:=False;
 IsDirty:=False;
end;



Яндекс цитирования