Глава 4. Работа с диалоговым блоком


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

Шаг 6: Изменение атрибутов пера


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

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

Создание объекта пера


    Хотя Windows ссылается на свои изобразительные средства как на "объекты" (отсюда и имена типа SelectObject и DeleteObject), они не являются объектами в истинном объектно-ориентированном смысле, так как не используют наследование и полиморфизм. Перо на самом деле представляет собой просто группу из трех характеристик отображения, на которые Windows ссылается при изображении линии. Эти характеристики являются просто свойствами контекста дисплея, но полезно рассматривать их, как встроенные в перо.

Характеристики пера

    Три характеристики пера - это его стиль, размер и цвет. В шаге 3 вы изменяли размер пера и отслеживали текущий размер пера в поле объекта окна. Вместо реализации трех отдельных полей для отслеживания характеристик пера вы можете инкапсулировать их в единый объект TPen. Описание TPen имеет следующий вид:

     type
       PPen = ^TPen;
       TPen = object(TObject)
          Width, Style: Integer;
          Color: Longint;
          constructor Init(AStyle, AWidth: Integer;
                           AColor: Longint);
          constructor Load(var S: TStream);
          procedure ChangePen;
          procedure Delete;
          procedure Select(ADC: HDC);
          procedure SetAttributes(AStyle, AWidth: Integer;
                           AColor: Longint);
          procedure Store(var S: TStream);
     private
        PenHandle, OldPen: HPen;
        TheDC: HDC;
        PenData: TPenData;
     end;

    Примечание: Большую часть исходного кода из данной главы вы можете найти в файле PEN.PAS. Для использования модуля Pen в STEP06A.PAS и STEP06B.PAS нужно внести минимальные изменения.

    Примечание: Тип TPen определен в модуле Pen. Конструктор Init создает новый объект пера с заданным стилем, размером и цветом. SetAttributes изменяет атрибуты уже созданного объекта пера. ChangePen выводит диалоговое окно, позволяющее пользователю задать атрибуты пера. Load и Store позволяют сохранять объекты пера в потоке.

Выбор и удаление объектов пера

    Наиболее интересную работу выполняют процедуры Select и Delete. Select создает изобразительное средство Windows на основе характеристик, записанных в полях атрибутов. Вместо того, чтобы вызывать в графической программе для создания пера, получения его описателя, выбора пера в контексте дисплея, использования пера и его удаления функцию API Windows, вы строите объект пера, а затем можете его использовать, выделять и удалять.

    Метод Delete отменяет описатель пера, освобождая ресурс для Windows. Select проверяет, имеется ли уже выделенное перо, и перед созданием и выбором нового отменяет существующее перо. Это полезно использовать, если это же перо предполагается применять повторно, так что вам не понадобиться вызывать Delete при каждом использовании пера. С другой стороны, в шаге 7 вы увидите, как можно сохранять нарисованные линии, и каждая линия будет иметь свой собственный объект пера. Если бы каждый объект пера создавался и сохранялся в пере Windows, Windows скоро исчерпала бы ресурсы. Поэтому важно непосредственно после использования пера вызывать для его отмены метод Delete.

    Основное достоинство TPen в том, что вам не нужно больше беспокоиться о получении, сохранении и удалении объекта пера. TPen имеет два частных поля, в одном их которых записывается описатель пера. Объект пера отслеживает описатель и взаимодействия с Windows, а ваша программа просто имеет дело с объектом. Другое частное поле, PenData, содержит используемый на этом шаге буфер передачи.

    Файл STEP06A.PAS содержит код программы Steps, модифицированный для использования объекта TPen в модуле Pen. В основном изменения невелики (например, поле ThePen изменяет тип с HPen на PPen, а метод SetPenSize заменяется вызовом метода SetPenAttributes объекта пера, поскольку объект пера может управлять цветом и стилем наряду с размером).

Создание сложного диалогового блока


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

    В модуле Pen определяется более сложный ресурс диалогового блока с именем 'PenDlg', который дает вам возможность изменения атрибутов только что определенного объекта пера. Этот диалоговый блок показан на Рис. 4.1.


Рис. 4.1 Диалоговый блок с изменением атрибутов пера.

    Set Pen Attributes - установка атрибутов пера; Color - цвет; Black - черный; Purple - фиолетовый; Blue - голубой; Cyan - бирюзовый; Green - зеленый; Yellow - желтый; Red - красный; White белый; Style - стиль; Solid - непрерывная линия; Dash - пунктир; Dot - точки; DashDot - точки и тире; DasDotDot - тире и две точки; Null - пусто; Width - ширина; OK - подтверждение; Cancel отмена.

    Построение объекта из ресурса 'PenDlg' выполняется также, как это делается для окна About Box (за исключением порождающего окна). Поскольку диалоговый блок атрибута пера выполняется из объекта TPen, а не из оконного объекта, вы не можете в качестве порождающего окна использовать @Self. Вместо этого TPen присоединяет диалоговый блок к одному из окон, о присутствии которых известно заранее - основному окну приложения:

     procedure TPent.ChangePen;
     var PenDlg: PPenDialog;
     begin
      .
      .
      .
       PenDlg := New(PPenDialog, Init(Application^.MainWindow,
                     'PenDlg'));
      .
      .
      .
     end;

    Другим важным отличием является то, что на этот раз вы имеете новый производный объектный тип TPenDialog. Так как окно About box не использует ничего, кроме назначенного по умолчанию поведения диалогового окна, инкапсулированного в TDialog, вам не требуется создавать для него новый объектный тип. Однако диалог атрибутов пера отличается более сложным поведением и требует настройки объекта.

    Приведем определение TPenDialog из модуля Pen:

     type
       PPenDialog = ^TPenDialog;
       TPenDialog = object(TDialog);
          constructor Init(AParent: PWindowsObject; AName;
                           PChar);
     end;
     constructor TPenDialog.Init(AParent: PWindowsObject;
                           AName: PChar;
     var
       AControl: PRadioButton;
       i: Integer;
     begin
       inherited Init(AParent, AName);
       AControl := New(PRadioButton, InitResource(@Self,
        1100 + i));
       for i := 0 to 5 do
       AControl := New(PRadioButton, InitResource(@Self,
        1200 + i));
     end;

    Построенные в TPenDialog управляющие объекты поясняются в следующем разделе.

Управляющие объекты


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

Использование интерфейсных объектов


    При "обычном" программировании в Windows (то есть без ObjectWindows), ваша прикладная программа должна взаимодействовать с каждым элементом экрана через функции API Windows. Как вы уже видели, ObjectWindows облегчает создание и управление диалоговыми блоками, изолируя вас от Windows путем максимально возможного использования для представления элементов экрана объектов. Эти интерфейсные объекты также значительно облегчают взаимодействие с управляющими элементами в диалоговых блоках.

    Примечание: Интерфейсные объекты описываются в Главе 9, а управляющие объекты описываются, в частности, в Главе 12. Если вам не требуются управляющие объекты, вы все равно сможете взаимодействовать с управляющими элементами, но это приведет к необходимости частого вызова функций API Windows, передачи управляющим элементам сообщений и интерпретации результатов. ObjectWindows значительно облегчает эту задачу, инкапсулируя поведение каждого управляющего элемента в объекте. Передаются и обрабатываются те же сообщения, но ObjectWindows заботится обо всех деталях.

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

Конструктор InitResource


    Когда вы на этапе выполнения создаете управляющий объект (в противоположность созданию его из ресурса), вам нужно задать расположение, размер и начальное значение (или состояние) управляющего элемента, а также указать, какой объект является порождающим. Все эти элементы передаются в качестве параметров конструктору Init объекта.

    Связь объекта с управляющим элементом из ресурса намного проще, так как такая информация как расположение и размер, определяется ресурсом. Требуется передать конструктору InitResource только порождающий объект и идентификатор управляющего элемента. Так как управляющие объекты обычно строятся внутри конструктора их порождающих диалоговых блоков, указатель порождающего объекта почти всегда равен @Self.

    Как показано в приведенном выше примере, диалог пера модуля Pen связывает объекты с их управляющими элементами редактирования (для задания размера пера) и обоими наборами кнопок с зависимой фиксацией (для задания цвета и стиля).

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

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

Создание буфера передачи


    Теперь, когда в диалоговом блоке у вас есть объект, связанный с управляющими элементами диалогового окна, необходим способ для установки и чтения их значений. Это делается с помощью буфера передачи. Буфер передачи - это запись, которая содержит одно поле для каждого управляющего элемента в диалоговом окне, в который или из которого происходит передача.

    Например, диалоговый блок, созданный в шаге 6, имеет поле редактирования и четырнадцать кнопок с зависимой фиксацией. В управляющий элемент редактирования требуется передавать строку, а каждая кнопка с зависимой фиксацией получает значение Word, указывающее на его выбор. Модуль Pen определяет тип записи, передаваемый TPenDialogs и из него:

     
     type
       TPenData = record
         XWidth: array[0..6] of Char;
         ColorArray: arra[0..7] of Word;
         StyleArray: array[0..5] of Word;
     end;
    Вы можете также управлять кнопками с независимой фиксацией, используя 14 отдельных полей или один массив из 14 значений типа Word; передаваемые данные будут теми же. Однако, так как ваша прикладная программа будет интерпретировать их как две группы из 8 и 6 кнопок соответственно, удобно задать поле для каждой группы.

Присваивание буфера

    Каждый потомок TWindowsObject имеет поле TransferBuffer. Когда вы хотите передать данные в диалоговое окно, нужно задать объект TransferBuffer диалогового блока, указывающий на запись передачи:

     PenDlg := New(PPenDialog, Init(Application^.MainWindow,
                                    'PenDlg'));
     PenDlg^.TransferBuffer := @PenData;

    Если ваши программы создают объекты диалогового окна динамически, убедитесь, что они каждый раз назначают буфер передачи. TransferBuffer по умолчанию имеет значение nil. Это означает, что данные не переданы.

Заполнение буфера

    Перед фактической передачей данных в диалоговое окно, вам нужно установить значение полей в буфере передачи. Перед выводом диалогового окна пера это делает TPen.ChangePen:

     procedure TPen.ChangePen;
     var
       PenDlg: PPenDialog;
       TempWidth, ErrorPos: Integer;
     begin
       SetColorAttr(PenDate, Color);
       SetStyle(PenDate, Style);
       wvsprintf(PenDialog, Init(Application^.MainWindows,
                 'PenDlg'));
       PenDlg^.TransferBuffer := @PenData;
       if Application^.ExecDialog(PenDlg) <> id_Cancel then
       begin
          Val(PenData.XWidth, TempWidth, ErrorPos);
          if ErrorPos = 0 then
              SetAttributes(SetStyle(PenData), TempWidth,
                   GetColorAttr(PenData));
       end;
     end;

    SetColorAttr и SetStyle используют то преимущество, что буфер передачи задает кнопки с зависимой фиксацией в виде массива значений Word. SetStyle, например, выглядит следующим образом:

     procedure SetStyle(var ARec: TPenData; AStyle: Integer);
     var i: Integer;
     begin
       for i := 0 to 5 do
           if = AStyle then ARec.StyleArray[i] := bf_Checked
           else ARec.StyleArray[i] := bf_Unchecked;
     end;

    Примечание: SetColorAttr выполняет то же назначение, что и ColorArray. bf_Checked и bf_Unchecled - это константы ObjectWindows.

Передача данных


    После того как вы создадите буфер передачи и заполняет его значениями, получение этой информации в диалоговом блоке не представляет труда, поскольку все за вас делает ObjectWindows. Когда для выполнения диалогового блока вызывается ExecDialog, он вызывает TransferDatа для копирования значений из буфера передачи в отдельные объекты управляющих элементов.

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

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

Чтение возвращаемых значений


    Считывание значений обратно в буфер передачи - это обратный процесс по отношению к заполнению буфера перед заполнением диалогового окна. В модуле Pen определены некоторые функции, способствующие интерпретации выбора кнопки с зависимой фиксацией в каждой группе.

     function GetStyle(ARec: TPenDate): Longint;
     var i: Integer;
     begin
       for i := 0 to 5 do
          if ARec.StyleArray[i] = bf_Cheched then GetStyle := i;
     end;

    Если пользователь отменяет диалоговый блок, то вас, конечно, не должно беспокоить считывание значений: они совпадают с переданными значениями. Обычно когда вы выполняете диалоговый блок с помощью ExecDialog, то чтобы определить, возвратил ли диалоговый блок какие-либо полезные данные, проверяется возвращаемое значение (id_Ok, если пользователь щелкнул "мышью" на командной кнопке OK, в противном случае id_Cancel).

     if Application^.ExecDialog(PenDlg) <> id_Cancel then
     begin
       Val(PenDate.XWith, TempWith, ErrorPos);
       SetAttributes(GetStyle(PenData), TempWidth,
                     GetColorAttr(PenData));
     end;

Вызов диалогового блока пера


    Чтобы вывести диалоговый блок пера, вызовите его метод ChangePen. Программа STEP06B.PAS делает это в ответ на команду cm_Pen, генерируемую выбором пункта меню Options|Pen и щелчком правой кнопкой "мыши".

     procedure TStepWindow.CMPen(var Msg: TMessage);
     begin
       CurrentPen^.ChangePen;           { CurrentPen - это объект
                                                         блока пера }
     end;
     procedure TStepWindow.WMRButtonDown(var Msg: TMessage);
     begin
       if not ButtonDown then CurrentPen^.ChangePen;
     end;
    Примечание: Данные методы можно найти в файле STEP06B.PAS.