4
votes

Description

I have a Delphi XE2 application with one of its forms stretched across two monitors. When I lock Windows, wait until the screen saver gets activated und then unlock windows, all of my application's forms will be resized/ repositioned to fit onto each monitor (which is obviously a default Windows behavior and applies to most applications).

Intention

Whenever this locking scenario occurs, I either want to restore my forms position or prevent my form from being resized beforehand.

Steps to reproduce

These steps work for me on Windows 7 x64.
I'm setting a blankscreen saver to be activated after 1 minute. I open my app and the appropriate stretched form. I lock my account and wait for the screen saver to pop up. After logging in I can see the form resized.

On other machines locking is enough to reproduce the behavior. On some machines the activated screen saver is enough.

Additional Info

What I have done and observed so far:

  • Using Spy++ I've seen my app receiving a WM_SETTINGCHANGE message with WParam = SPI_SETWORKAREA. At this point my form already has its new size.
  • I have registered session notifications to react on session locks, unlocks, logoff etc.
    Receving a session change when locking, my form's size seems to be okay. When receiving the WM_SETTINGCHANGE later, the form size is already altered and shrinked to one monitor.
  • Trying to resize my form to its former size when I receive an unlock event does not succeed (the form stays shrinked although its properties have been changed). I used the form's position and size properties as well as SetWindowPos.
  • The affected form's window state is wsNormal. I stretch the form programmatically above two monitors but don't touch its window state.
  • Trying to restore the old (internally saved) position/ size on WM_WTSSession_Change unlock messages, I have tried to call
    SetWindowPos(Handle, HWND_NOTOPMOST, FFormSizePos.Left, FFormSizePos.Top, FFormSizePos.Width, FFormSizePos.Height, SWP_NOACTIVATE or SWP_NOMOVE);
    or set the size properties by hand like Self.Left := FFormSizePos.Left;

Can anybody help to resolve my intention?

2
Can you show the code that attempts to restore. Can you tell us what the window state is when store, and when you restore. Is it maximized?David Heffernan
I added information. Nothing special in there. I'm trying to figure out the right event to respond to or the right time to reset the position if this is possible at all. Best way would be to intercept the appropriate message and prevent my forms from being resized at all.Erik Virtel

2 Answers

2
votes

I found a solution and am posting demo code (XE2) as a Delphi solution to this problem.

It is a combination of the answer here and solution 1 from delphiDabbler.

Basically I am registering for Windows session state change events (WM_WTSSESSION_CHANGE). In the provided example (based on a naked VCL Form Application) I am using the WM_EXITSIZEPOS message to save the current form sizepos.

Windows was showing a varying behavior regarding the moment the position change message was fired. This is why I had to revise my first draft and am now using two variables. I am preventing position changes when the session is locked and prevent the first position change after the session is unlocked. The position changes are intercepted using the WM_WINDOWPOSCHANGING message.

But to not restore the normal position if the form is maximized I am using the FRestoreNormalRect field.

unit Unit1;

interface

uses
  Winapi.Windows,
  Winapi.Messages,
  Vcl.Forms;

type
  TForm1 = class(TForm)
  private
    FSessionIsLocked: Boolean;
    FSessionWasUnlocked: Boolean;
    FRestoreNormalRect: Boolean;
    FLeft: Integer;
    FTop: Integer;
    FWidth: Integer;
    FHeight: Integer;

    procedure WMWTSSessionChange(var Msg: TMessage); message WM_WTSSESSION_CHANGE;

  protected

    procedure CreateWnd; override;
    procedure DestroyWnd; override;

    procedure WMExitSizeMove(var Msg: TMessage); message WM_EXITSIZEMOVE;
    procedure WMPosChanging(var Msg: TWmWindowPosChanging); message WM_WINDOWPOSCHANGING;
    procedure WMSize(var Msg: TWMSize); message WM_SIZE;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//--------------------------------------------------------------------------------------------------

procedure TForm1.CreateWnd;
begin
  inherited;

  WTSRegisterSessionNotification(WindowHandle, NOTIFY_FOR_THIS_SESSION);
end;

//--------------------------------------------------------------------------------------------------

procedure TForm1.DestroyWnd;
begin
  WTSUnRegisterSessionNotification(WindowHandle);

  inherited;
end;

//--------------------------------------------------------------------------------------------------

procedure TForm1.WMExitSizeMove(var Msg: TMessage);
var
  WP: TWindowPlacement;
  NormalRect: TRect;

begin
  WP.Length := SizeOf(TWindowPlacement);
  GetWindowPlacement(Self.Handle, @WP);
  NormalRect := WP.rcNormalPosition;

  FLeft := NormalRect.Left;
  FTop := NormalRect.Top;
  FWidth := NormalRect.Right - NormalRect.Left;
  FHeight := NormalRect.Bottom - NormalRect.Top;
end;

//--------------------------------------------------------------------------------------------------

procedure TForm1.WMPosChanging(var Msg: TWmWindowPosChanging);
begin
  { Sizepos changes might occur due to locks or unlocks. We need do prohibit both.
    While the session is locked we ignore all position changes.
    When the session has been unlocked we will ignore the next PosChanging message. }
  if FSessionIsLocked or FSessionWasUnlocked then
  begin
    { overwrite with the old settings }
    if FRestoreNormalRect then
    begin
      Msg.WindowPos.x := FLeft;
      Msg.WindowPos.y := FTop;
      Msg.WindowPos.cx := FWidth;
      Msg.WindowPos.cy := FHeight;

      Msg.Result := 0;
    end;

    { reset the variable, otherwise a manual resize would not be possible }
    if FSessionWasUnlocked then
      FSessionWasUnlocked := False;
  end;
end;

//--------------------------------------------------------------------------------------------------

procedure TiQForm.WMSize(var Msg: TWMSize);
begin
  inherited;

  { We need to restore our normal rect only if the form is not maximized. Because
    if it is maximized it only positioned on one monitor and will not be moved
    by windows. If we do not repsect this case we would be restoring the normal
    rect unintentionally in WMPosChanging for every maximized form. }
  FRestoreNormalRect := not (Msg.SizeType = SIZE_MAXIMIZED);
end;

//--------------------------------------------------------------------------------------------------

procedure TForm1.WMWTSSessionChange(var Msg: TMessage);
begin
  case Message.WParam of
    WTS_CONSOLE_DISCONNECT, WTS_REMOTE_DISCONNECT, WTS_SESSION_LOCK, WTS_SESSION_LOGOFF:
      begin
        FSessionIsLocked := True;
      end;
    WTS_CONSOLE_CONNECT, WTS_REMOTE_CONNECT, WTS_SESSION_UNLOCK, WTS_SESSION_LOGON:
      begin
        FSessionIsLocked := False;
        FSessionWasUnlocked := True;
      end;
  end;

  inherited;
end;

//--------------------------------------------------------------------------------------------------

end.
0
votes

You can use the TMemoryStream.WriteComponent method inside the OnDeactivate and the TMemoryStream.ReadComponent method by the OnActivate event. As parameter you are giving your form.

Please have a look on this post for more detailed information.