Snapping windows to monitor halves / quadrants

You probably know about the Windows 7+ feature to snap a window to the left or right side of the monitor via Windows+Left / Windows+Right hotkey. You might even know that Windows 10 extended this to snap a window to the top or bottom and even to one of the quadrants of your monitor. (I for one had read about that Windows 10 feature but had already forgotten it, since I don’t plan to upgrade in the near future.)

But what if you don’t have Windows 10? Can this be done programmatically? Of course it can! Just add appropriate Actions, assign the shortcuts to your form and write the code. Or set the form’s KeyPreview property to true and put the code into the FormKeyDown event handler. Unfortunately this requires some rather tiresome copy and paste plus clicking on the object inspector for each of the forms. And, of course, copy and paste is evil, because it violates the DRY principle: If you make a mistake once, you will have to correct it in all the copies you have made in the meantime.

Enter TForm_ActivatePositoning: A utility function which I have added to dzLib today:

Just by calling

TForm_ActivatePositioning(Self);

you can add this functionality to every form in every project you’ll ever write.

Ok, so what does it do?

It subclasses the window (that is: Replaces its WindowProc (Wow, I just learnt that there is a new approach to subclassing, must have missed that for a few years)) to intercept the CM_CHILDKEY message which is sent by the VCL whenever any child control of a form receives a WM_KEYDOWN message:

type
  TFormPositioningActivator = class(TWindowProcHook)
  private
    FModifier: TShiftState;
    procedure CmChildKey(var _Msg: TMessage);
    function TheForm: TForm;
  protected
    procedure NewWindowProc(var _Msg: TMessage); override;
  public
    constructor Create(_Form: TForm; _Modifier: TShiftState);
  end;

constructor TFormPositioningActivator.Create(_Form: TForm; _Modifier: TShiftState);
begin
  inherited Create(_Form);
  FModifier := _Modifier;
end;

procedure TFormPositioningActivator.CmChildKey(var _Msg: TMessage);
var
  Key: Word;
begin
  Key := (_Msg.WParamLo and $FF);
  if GetModifierKeyState = FModifier then begin
    case Key of
      VK_LEFT, VK_NUMPAD4: TForm_MoveTo(TheForm, dwpLeft);
      VK_RIGHT, VK_NUMPAD6: TForm_MoveTo(TheForm, dwpRight);
      VK_UP, VK_NUMPAD8: TForm_MoveTo(TheForm, dwpTop);
      VK_DOWN, VK_NUMPAD2: TForm_MoveTo(TheForm, dwpBottom);
      VK_PRIOR, VK_NUMPAD9: TForm_MoveTo(TheForm, dwpTopRight);
      VK_NEXT, VK_NUMPAD3: TForm_MoveTo(TheForm, dwpBottomRight);
      VK_HOME, VK_NUMPAD7: TForm_MoveTo(TheForm, dwpTopLeft);
      VK_END, VK_NUMPAD1: TForm_MoveTo(TheForm, dwpBottomLeft);
    else
      Exit; //==> exit, so Result doesn't get set to 1
    end;
    _Msg.Result := 1;
  end;
end;

procedure TFormPositioningActivator.NewWindowProc(var _Msg: TMessage);
begin
  if _Msg.Msg = CM_CHILDKEY then
    CmChildKey(_Msg);

  inherited NewWindowProc(_Msg);
end;

function TFormPositioningActivator.TheForm: TForm;
begin
  Result := TForm(FCtrl);
end;

function TForm_ActivatePositioning(_Form: TForm; _Modifier: TShiftState = [ssCtrl, ssAlt]): TObject;
begin
  Result := TFormPositioningActivator.Create(_Form, _Modifier);
end;

It uses the existing TWindowProcHook class which is already used for drag&drop and autocomplete (wow, was that really two years ago?) so the actual code is quite simple, as you can see above.

I did not use the Windows key as modifier but rather Ctrl+Alt because Microsoft says we should not use the Windows key. But I’m not quite sure about this. Maybe I could instead check for the Windows version to see if those hotkeys are already available and only add those that aren’t, using the Windows key.

But for now, the following hotkeys are implemented:

  • Ctrl+Alt+Up – snap window to the top of the monitor, or if already there, to the bottom of the monitor above it
  • Ctrl+Alt+Down – same for the bottom of the monitor
  • Ctrl+Alt+Left – same for the left of the monitor
  • Ctrl+Alt+Right – same for the left of the monitor
  • Ctrl+Alt+Home – snap the window to the upper left quadrant of the monitor
  • Ctrl+Alt+End – same for the lower left quadrant
  • Ctrl+Alt+PgUp – same for the upper right quadrant
  • Ctrl+Alt+PgDown- same for the lower right quadrant

Those hotkeys also work for the keys on the numeric keypad, regardless whether NumLock is active or not. If you look at your keyboard you will notice why I choose these particular keys: The nicely correspond to the quadrants / halves of the monitor and should it easier to memorize them.

Numpad
(Picture courtesy of Wikipedia.)