Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_4440.html
 

Смена MDIChild формы.

Опубликовано 28.09.2010 г. 23:30
Представьте себе MDI приложение, на главной форме присутствует элемент, отображающий название активной дочерней формы. И вот вопрос, как изменять данное название при выборе очередной дочерней формы?
Родительская форма не имеет событий связанных с переключением дочерних форм. Допустим на нашей главной форме расположен элемент childTitleLabel : TLabel, который отображает название дочерней формы. Итак какими путями мы можем пойти, чтобы изменить указанный заголовок, при активации новой формы? Возможно, некоторые скажут что мы можем для каждой дочерней формы написать обработчик события OnActivate в котором напрямую будем присваивать значения заголовка в метке на главной форме. Вероятно такой вариант вовсе неплох, если у нас имеется только один тип дочерних форм, т.е данный код придется написать только один раз. Небольшим минусом будет то, что в секцию uses модуля нашей дочерней формы, придется добавить главную форму, чтобы знать о существовании нашей метки TLabel. Однако, допустим мы имеем несколько разных дочерних форм, тогда данный код придется реализовывать в каждой из них, подобный подход нас не устраивает. Тут мы можем вспомнить о наследовании (вобще говоря, мы всегда должны помнить об базовых понятиях ООП и уметь их применять). Все наши дочерние формы, как и любые другие (создаваемые пользователем имеется в виду) являются потомками класса TForm. Почему бы нам не определить новый класс, скажем, TMDIForm = class(TForm), а все дочерние формы сделать его потомками. Что же теперь? Способ простой заключается в том, что описание класса TMDIForm мы можем расширить добавив туда ссылку на нашу метку
TMDIForm = class(TForm)
  TitleLabel : TLabel;
end;
Предполагается, что при создании экземпляра дочерней формы, мы будем заполнять данное поле. Переопределив обработчик события onActivate мы можем иметь доступ к метке, и изменять ее значение. Поскольку для определения события onActivate нам понадобится переписать конструктор класса, где назначить выполнение нашего метода смены заголовка, то проще будет видоизменить конструктор целиком:
constructor TMDIForm.Create(aOwner : TComponent; aLabel : TLabel);
begin
   inherited Create(aOwner);
   titleLabel := aLabel;
   OnActivate := MDIChildActivate;
end;

procedure TMDIChild.MDIChildActivate(sender : TObject);
begin
    inherited;
    if assigned(titleLabel) then titleLabel.caption := self.caption;
end;
Теперь вы не забудете указать ссылку на элемент Label главной формы, при создании экземпляра дочерней. Однако такой метод также нельзя назвать удобным. Прочитав справку вы можете найти описание сообщения WM_MDIACTIVATE. Данное сообщение передается обеим дочерним формам, сначала той, которая фокус теряет, затем новой активной форме. Значит по большому счету нам не требуется никакой обработчик события onActivate. Итак, давайте переопределим данную процедуру в классе TMDIForm.
    TMDICHild = class(TForm)
      protected
        procedure wmMDIActivate(var msg : TWMMDIActivate); message WM_MDIACTIVATE;
    end;
Теперь у нас есть 2 прежних варианта действий:
  1. мы храним ссылку на Label главной формы и можем поменять его в данной процедуре.
    self.titleLabel.caption := caption;
  2. мы не храним ссылку на label и используя знания о главной форме, меняем текст метки сами
    TMainForm(application.mainForm).childTitleLabel.caption := caption;
Однако оба данных методы плохи тем, что в какой то момент, мы можем решить что название будет отображаться не с помощью TLabel а как нибудь иначе. Т.е мы заведомо используем информацию о другом модуле программы, при этом увеличивая так называемую связность архитектуры, что есть не хорошо. В таком случае нам на помощью приходит механизм сообщений. Отправляя заданное сообщение, нас не волнует внутренне устройство главной формы с точки зрения дочерних форм, а главная форма сама может определить, как ей использовать полученные знания о заголовке дочерней. Определим код пользовательского сообщения:
const WM_MDICHILD_ACTIVATED = WM_USER + 1;
Теперь при активации дочерней формы, мы можем уведомить об этом главную форму. В качестве параметров отправляемого сообщения мы укажем наш заголовок. Можем так же передать и ссылку на самого себя.
procedure TMDICHild.wmMDIActivate(var msg: TWMMDIActivate);
var title : PChar;
begin
    inherited;
    if msg.ActiveWnd = handle then begin
        title := PChar(caption);
        SendMessage(application.MainForm.handle, WM_MDICHILD_ACTIVATED, integer(title), handle);
    end;
end;
Осталось обработать прием подобного сообщения главной формой:
TMainForm = class (TForm)
    ...
  private
    procedure wmChildFormChanged(var msg : TMessage); message WM_MDICHILD_ACTIVATED;
end;


procedure TMainForm.wmChildFormChanged(var msg: TMessage);
begin
    childTitleLabel.Caption := PChar(msg.WParam);
end;
Может быть все же есть способы узнать об активации новой дочерней формы, не используя наследования дочерних форм, используя исключительно средства доступные главной форме? кто знает?
Метки:  MDI 

Комментарии

Bogdan2004
29.09.2010 в 07:47
Добрый день!
попробуйте Screen.FocusedForm возвращает TCustomForm которая в фокусе. Можно по таймеру проверять состояние. Кривовато, но можно вроде использовать.
ter
29.09.2010 в 10:50
боюсь подобные варианты с таймером действительно кривовато (:
зы: в случае MDI использовать не screen.focusedForm, а self.activeMDIChild которая имеет индекс 0 в списке MDIChildren главной формы
JayDi
29.09.2010 в 12:26
Через CallWndHook -- отлавливаются сообщения WM_MDIDESTROY, WM_MDICREATE, WM_MDIACTIVATE и дальше отсылаются на обработку уже на форме.
ter
29.09.2010 в 23:34
пасиба, за совет (: вроде кое что написал, не знаю то или нет правда (:
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно