Закрытие темы о сохранении позиции окон. |
|||||
Банников Н.А. | www.stikriz.narod.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 г.