Закрытие темы о сохранении позиции окон.

  Банников Н.А. www.stikriz.narod.ru Почта На главную страницу  

Рейтинг@Mail.ru

Профессионально сделанная программа должна правильно сохранять позицию и размеры окон, а так же давать возможность дополнительного контроля над окнами, особенно это касается приложения SDI. Казалось бы чего проще. У всех форм есть свойства Left, Top, Height, Width. Сохраняем их в реестре, а при создании читаем… Но, не все так просто. Допустим, пользователь, работая с Вашей программой, распахнул окно на весь экран. В следующий раз при запуске хорошо было бы, чтобы окно появлялось в том же виде – распахнутым на весь экран. Но, когда он захочет вернуться в прежний режим, то какого размера должно быть окно? Если Вы сохраняли свойства окна, как было сказано ранее, то окно, сколько не кликай по заголовку, останется прежнего размера. Вторая сложность – это управление окнами во время работы программы. Какого размера и куда поставить окно, когда пользователь его распахивает? Посмотрите на Delphi1-7. Посмотрите, как ведет себя главное окно и окно редактора текста. Решению этих проблем и посвящается моя статья.

Сохранение позиции и размера окон.

Для удобства использования, можно создать отдельный модуль, в который поместим две процедуры: LoadIniForm и SaveIniForm.  Процедуры принимают указатель на класс формы и имя секции в реестре куда мы будем записывать данные о положении формы. Самое главное в этих простых процедурах – это использование структуры FormPlacement: TwindowPlacement и вызов API GetWindowPlacement для заполнения структуры. После вызова в этой структуре размеры окна до распахивания. Именно эти размеры и нужно сохранять, а не размеры распахнутого окна.

unit FormServices;

interface

 uses Windows, Classes, SysUtils, Forms;

procedure SaveIniForm(Form: TForm; const Section: string);

procedure LoadIniForm(Form: TForm; const Section: string);

 var IniName: string = 'MyProgramm';

const

  FORM_HEIGHT = 'Height';

  FORM_WIDTH  = 'Width';

  FORM_LEFT   = 'Left';

  FORM_TOP    = 'Top';

  FORM_MAX    = 'IsMaxForm';

implementation

 uses Registry;

procedure SaveIniForm(Form: TForm; const Section: string);

var FIniFile: TRegIniFile;

    FormPlacement: TWindowPlacement;

    Top, Left, Height, Width: integer;

begin

// Если форма в нормальном состоянии, то записывать нужно её размеры

 if Form.WindowState = wsNormal then

  begin

   Top := Form.Top;

   Left := Form.Left;

   Height := Form.Height;

   Width := Form.Width;

  end

 else

// Если форма в распахнутом состоянии, то используем вызов API GetWindowPlacement

  begin

   FormPlacement.length := SizeOf( TWindowPlacement );

   GetWindowPlacement( Form.Handle, @FormPlacement );

   Top := FormPlacement.rcNormalPosition.Top;

   Left := FormPlacement.rcNormalPosition.Left;

   Height := FormPlacement.rcNormalPosition.Bottom -

                        FormPlacement.rcNormalPosition.Top;

   Width := FormPlacement.rcNormalPosition.Right -

                        FormPlacement.rcNormalPosition.Left;

  end;

// Далее, все просто – создаем TregIniFile и пишем туда параметры расположения окна 

FIniFile := TRegIniFile.Create(IniName);

 try

  if (Form.BorderStyle = bsSizeable) or (Form.BorderStyle = bsSizeToolWin) then

   begin

    FIniFile.WriteInteger(Section, FORM_HEIGHT, Height);

    FIniFile.WriteInteger(Section, FORM_WIDTH, Width);

   end;

  if (Form.Position = poDesigned) then

   begin

    FIniFile.WriteInteger(Section, FORM_LEFT, Left);

    FIniFile.WriteInteger(Section, FORM_TOP, Top);

   end;

  FIniFile.WriteBool(Section, FORM_MAX, Form.WindowState = wsMaximized);

 finally

  FIniFile.Free;

 end;

end;

Процедура для восстановления параметров расположения окна должна учитывать BorderStyle, чтобы не менять размеры окна, если оно диалоговое. После восстановления позиции и размеров окна, проверяем, было ли оно распахнуто, и если да, то распахиваем.

 procedure LoadIniForm(Form: TForm; const Section: string);

var FIniFile: TRegIniFile;

    isMax: boolean;

begin

 FIniFile := TRegIniFile.Create(IniName);

 try

  if (Form.BorderStyle = bsSizeable) or (Form.BorderStyle = bsSizeToolWin) then

   begin

    Form.Height:=FIniFile.ReadInteger(Section, FORM_HEIGHT, Form.Height);

    Form.Width:=FIniFile.ReadInteger(Section, FORM_WIDTH, Form.Width);

   end;

  if (Form.Position <> poScreenCenter) then

   begin

    Form.Left:=FIniFile.ReadInteger(Section, FORM_LEFT, Form.Left);

    Form.Top:=FIniFile.ReadInteger(Section, FORM_TOP, Form.Top);

   end;

  isMax:=FIniFile.ReadBool(Section, FORM_MAX, false);

  if isMax then

   Form.WindowState:=wsMaximized;

 finally

  FIniFile.Free;

 end;

end;

 end.

 

Форма, сохраняющая свои размеры и позицию на экране.

 

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

unit CustomForm;

 interface

 uses

  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms;

 type

   TfdCustomForm = class(TForm)

  private

    procedure _WM_GETMINMAXINFO(var mmInfo : TWMGETMINMAXINFO);  message wm_GetMinMaxInfo;

  protected

    // Контролирует степень распахиваемости окна

    // Отнаследуйся, если нужно поменять стандартное поведение

    procedure CheckMinMaxInfo(var _X, _Y, _Width, _Height: Integer); virtual;

    // Контролирует размеры окна

    // Отнаследуйся, если нужно поменять стандартное поведение

    procedure CheckWidthHeightInfo(var _Width, _Height: Integer); virtual;

    procedure DoShow; override;

    // Имя ветки реестра для сохранения настроек

    // Обязательно отнаследоваться в каждой форме !

    function GetSectionName: string; virtual; abstract;

    procedure Resize; override;

    procedure DoClose(var Action: TCloseAction); override;

  public

  end;

 var

  fdCustomForm: TfdCustomForm;

implementation

 uses FormServices;

 {$R *.dfm}

 { TfCustomForm }

 

Это событие происходит всякий раз, когда мы распахиваем окно. Здесь можно контролировать процесс. Все размеры окна в TWMGETMINMAXINFO. Если их переустановить, то окно распахнется не на весь экран, а примет размеры и положение, указанное в структуре TWMGETMINMAXINFO. Чтобы более удобно использовать событие, лучше сделать еще один виртуальный метод CheckMinMaxInfo, который можно перегрузить, если надо, и вставить свою обработку.

 

procedure TfdCustomForm._WM_GETMINMAXINFO(var mmInfo: TWMGETMINMAXINFO);

var X, Y, W, H: Integer;

begin

 with mmInfo.minmaxinfo^ do

  begin

   X:=ptmaxposition.x;

   Y:=ptmaxposition.y;

   W:=ptmaxsize.x;

   H:=ptmaxsize.y;

   CheckMinMaxInfo(X, Y, W, H);

   ptmaxposition.x:=X;

   ptmaxposition.y:=Y;

   ptmaxsize.x:=W;

   ptmaxsize.y:=H;

  end;

end;

 

Вот, как раз чтение позиции и размера окна как мы сделали в предыдущем модуле. Подразумевается, что в наследнике обязательно будет перегружена функция GetSectionName.

 

procedure TfdCustomForm.DoShow;

begin

 LoadIniForm(Self, GetSectionName);

 inherited;

end;

 procedure TfdCustomForm.CheckWidthHeightInfo(var _Width, _Height: Integer);

begin // Ничего не делаем. Метод не виртуальный только для того, чтобы не надо было обязательно его перегружать.

end;

 

Этот метод вызывается всякий раз, когда окно собирается менять свой размер. Здесь можно вставить свою обработку. Например, запретить форме менять свой размер по высоте как это сделано в Delphi у главного окна. Чтобы более удобно использовать метод, лучше сделать еще один виртуальный метод CheckWidthHeightInfo, который можно перегрузить, если надо, и вставить свою обработку.

 

 procedure TfdCustomForm.Resize;

var W,H: Integer;

begin

 inherited;

W:=Width;

 H:=Height;

 CheckWidthHeightInfo(W, H);

 if (Height <> H) or (Width <> W) then

  begin

   SetBounds(Left, Top, W, H);

   Abort;

  end;

end;

 procedure TfdCustomForm.DoClose(var Action: TCloseAction);

begin

 SaveIniForm(Self, GetSectionName);

 inherited;

end;

 procedure TfdCustomForm.CheckMinMaxInfo(var _X, _Y, _Width, _Height: Integer);

begin // Ничего не делаем. Метод не виртуальный только для того, чтобы не надо было обязательно его перегружать.

end;

 end.

 

Пример использования.

 

Для иллюстрации работы программы, создайте новый проект и три формы, отнаследованных от TfdCustomForm. Пусть fMain будет главной формой, TfLeftDoc – той, что предпочтительно слева как ObjectInspector в Delphi, а fEditor – как редактор текста. 

Первое окно fMain будет ограничено высотой 200 пикселей. Причем, при распахивании его на весь экран, или при попытке растянуть его за бордюр, высота окна будет все равно 200 пикселей.

 

unit Main;

 interface

 uses

  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

  Dialogs, CustomForm;

 type

  TfMain = class(TfdCustomForm)

    procedure FormShow(Sender: TObject);

    procedure FormClose(Sender: TObject; var Action: TCloseAction);

  private

    { Private declarations }

  protected

    function GetSectionName: string; override;

    procedure CheckWidthHeightInfo(var _Width, _Height: Integer); override;

  public

    { Public declarations }

  end;

 var

  fMain: TfMain;

 implementation

  uses Editor, LeftDock;

 {$R *.dfm}

 { TfMain }

 

Метод CheckWidthHeightInfo жестко устанавливает высоту окна в 200 пикселей.

 

procedure TfMain.CheckWidthHeightInfo(var _Width, _Height: Integer);

begin

 _Height:=200;

end;

 function TfMain.GetSectionName: string;

begin

 Result:='MAIN';

end;

 procedure TfMain.FormClose(Sender: TObject; var Action: TCloseAction);

begin

 inherited;

 fEditor.Close;

 fLeftDoc.Close;

end;

 procedure TfMain.FormShow(Sender: TObject);

begin

 inherited;

 fLeftDoc.Show;

 fEditor.Show;

end;

 end.

 Окно fLeftDock будет всегда иметь ширину 200 пикселей, а при распахивании на весь экран, будет аккуратно устанавливаться под окно fMain с левой стороны экрана.

 

unit LeftDock;

 interface

 uses

  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

  Dialogs, CustomForm;

 type

  TfLeftDoc = class(TfdCustomForm)

  private

    { Private declarations }

  protected

   function GetSectionName: string; override;

   procedure CheckMinMaxInfo(var _X, _Y, _Width, _Height: Integer); override;

   procedure CheckWidthHeightInfo(var _Width, _Height: Integer); override;

  public

    { Public declarations }

  end;

 var

  fLeftDoc: TfLeftDoc;

 implementation

  uses Main;

 {$R *.dfm}

 { TfdCustomForm1 }

 function TfLeftDoc.GetSectionName: string;

begin

 Result:='LEFT_DOC';

end;

 

Метод CheckWidthHeightInfo жестко устанавливает ширину окна в 200 пикселей.

  

procedure TfLeftDoc.CheckWidthHeightInfo(var _Width, _Height: Integer);

begin

 _Width:=200;

end;

 

Метод CheckMinMaxInfo производит вычисления и устанавливает окно fLeftDock так, чтобы оно встало под fMain и заняло всю высоту экрана.

 

 procedure TfLeftDoc.CheckMinMaxInfo(var _X, _Y, _Width, _Height: Integer);

begin

 _X:=fMain.Left;

 _Y:=fMain.Height+fMain.Top;

 _Width:=200;

 _Height:=Screen.WorkAreaHeight-fMain.Height-fMain.Top;

end;

 end.

 

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

 

unit Editor;

 interface

uses

  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

  Dialogs, CustomForm;

 type

  TfEditor = class(TfdCustomForm)

  private

    { Private declarations }

  protected

   function GetSectionName: string; override;

   procedure CheckMinMaxInfo(var _X, _Y, _Width, _Height: Integer); override;

  public

    { Public declarations }

  end;

 var

  fEditor: TfEditor;

 implementation

  uses LeftDock;

 {$R *.dfm}

 { TfdCustomForm2 }

 function TfEditor.GetSectionName: string;

begin

 Result:='EDITOR';

end;

 

Метод CheckMinMaxInfo производит вычисления и устанавливает окно fEditor так, чтобы оно встало под fMain и немного ниже, справа от fLeftDock  и заняло всю оставшуюся свободную область экрана.

 procedure TfEditor.CheckMinMaxInfo(var _X, _Y, _Width, _Height: Integer);

begin

 _X:=fLeftDoc.Left+fLeftDoc.Width;

 _Y:=fLeftDoc.Top+100;

 _Width:=Screen.WorkAreaWidth-_X;

 _Height:=Screen.WorkAreaHeight-_Y;

end;

 end.

 

Я думаю, Вы легко разберетесь, сами как это лучше применить в своих проектах. Вот картинка, которая показывает что будет, если все окна распахнуть «на весь экран»:

  

 

Обратите внимание, что кнопки на заголовках окна показывают, что окна распахнуты.

     Банников Н.А. www.stikriz.narod.ru почта 1999 - 2005 г.

Сайт создан в системе uCoz