среда, 1 июня 2016 г.

Как запомнить привязку окна в Win7+

Иллюстрации с сайта windows.microsoft.com

Раньше для сохранения позиции окна приложения я часто использовал простой код:

procedure Store( F: TForm );
var
  wp: TWindowPlacement;
begin
  with TIniFile.Create( ChangeFileExt( Application.ExeName, '.ini' ) ) do
  try
    wp.length := SizeOf( TWindowPlacement );
    GetWindowPlacement( F.Handle, @wp );
    with wp.rcNormalPosition do begin
      WriteInteger( F.ClassName, 'Left', Left );
      WriteInteger( F.ClassName, 'Top', Top );
      WriteInteger( F.ClassName, 'Width', Right - Left );
      WriteInteger( F.ClassName, 'Height', Bottom - Top );
    end;
    WriteBool(F.ClassName, 'Maximized', F.WindowState = wsMaximized );
  finally Free;
  end;
end;

Я использовал не "внешние" (Left, Top, Width, Height) координаты окна, а "нормальные", полученные ф-ей GetWindowPlacement, т.к. в случае разворачивания окна на весь экран узнать потом его нормальный размер по "внешним" координатам нельзя. Разумеется, в случае, если окно и так имеет нормальный размер, "нормальные" координаты совпадают с "внешними". Вызов всего одной ф-ии работал на все случаи жизни.

 А для восстановления позиции и размеров я делал так:

procedure Restore( F: TForm );
begin
  with TIniFile.Create( ChangeFileExt( Application.ExeName, '.ini' ) ) do
  try
    F.Left   := ReadInteger( F.ClassName, 'Left'  , F.Left );
    F.Top    := ReadInteger( F.ClassName, 'Top'   , F.Top );
    F.Width  := ReadInteger( F.ClassName, 'Width' , F.Width );
    F.Height := ReadInteger( F.ClassName, 'Height', F.Height );
    if ReadBool(F.ClassName, 'Maximized', F.WindowState = wsMaximized ) then
      F.WindowState := wsMaximized;
  finally Free;
  end;
end;

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

Однако, в новых (конечно, всё относительно) версиях Windows появилась "привязка" окна мышью. Теперь картина стала менее радостной - для программного кода состояние "привязанного" окна TForm.WindowState остаётся прежним, "нормальные" координаты окна и флаги в структуре TWindowPlacement не меняются, и, соответственно, при перезапуске приложения его окно появляется совсем не в том виде, в каком оно было при предыдущем закрытии. Это неприятно.

Процедуру сохранения координат пришлось переписать:

procedure Store( F: TForm );
var
  wp: TWindowPlacement;
  R: Trect;
  M: Boolean;
begin
  M := F.WindowState = wsMaximized;
 
  if M then begin
    wp.length := SizeOf( TWindowPlacement );
    GetWindowPlacement( F.Handle, @wp );
    R := wp.rcNormalPosition;
  end
  else
    GetWindowRect( F.Handle, R );
 
  with TIniFile.Create( ChangeFileExt( Application.ExeName, '.ini' ) ) do
  try
    with R do begin
      WriteInteger( F.ClassName, 'Left', Left );
      WriteInteger( F.ClassName, 'Top', Top );
      WriteInteger( F.ClassName, 'Width', Right - Left );
      WriteInteger( F.ClassName, 'Height', Bottom - Top );
    end;
    WriteBool(F.ClassName, 'Maximized', M );
  finally Free;
  end;
end;
 
Т.е. теперь прежние, полученные ф-ей GetWindowPlacement "нормальные" координаты я беру только в случае, если окно "развёрнуто на весь экран". В остальных случаях я использую "внешние" координаты, полученные ф-ей GetWindowRect.

Вероятно, в будущих версиях Delphi на это обстоятельство обратят внимание. Вероятно, уже сейчас в WinAPI есть методы определения этой ситуации, просто я пока не смог этого найти. Но также вероятно, что ничего этого нет и не будет. Об этом говорит хотя бы поведение окна при привязке, разворачивании и восстановлении.


Eсли вы знаете о том, как иначе определить и использовать новый вид состояния окна, напишите.