Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I avoid a form getting focus when dragged

I have a simple form that only contains a TTouchKeyboard. The forms BorderStyle is set to bsToolWindow. To avoid the form getting focus when clicking the touch keyboard I handle the WM_MOUSEACTIVATE message with this implementation:

procedure TKeyboardForm.WMMouseActivate(var Message: TWMMouseActivate);
begin
  Message.Result := MA_NOACTIVATE;
end;

The BorderStyle setting allows the form to be dragged with the title bar, but in that case the form still gets the focus. Is there a way to avoid this?

Update: I tried adding WS_EX_NOACTIVATE to ExStyle in CreateParams, but unfortunately that doesn't hinder the form to receive focus when dragged.

procedure TKeyboardForm.CreateParams(var Params: TCreateParams);
begin
  inherited;
  Params.ExStyle := Params.ExStyle or WS_EX_NOACTIVATE;
end;
like image 670
Uwe Raabe Avatar asked Jun 27 '17 13:06

Uwe Raabe


2 Answers

Because I was not very pleased with the approach that requires me to manually update the focused form variable in the keyboard form, I searched for a more transparent solution and came up with this approach.

Update: The previous approach had some issues with VCL styles. In addition not all of the message handlers were really necessary, though others turned out to be helpful, too. This version works well with VCL styles avoiding any flicker as far as possible:

type
  TKeyboardForm = class(TForm)
    TouchKeyboard1: TTouchKeyboard;
  private
    FLastFocusedForm: TCustomForm;
    procedure SetLastFocusedForm(const Value: TCustomForm);
  protected
    procedure WMActivate(var Message: TWMActivate); message WM_ACTIVATE;
    procedure WMMouseActivate(var Message: TWMMouseActivate); message WM_MOUSEACTIVATE;
    procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    property LastFocusedForm: TCustomForm read FLastFocusedForm write SetLastFocusedForm;
  public
    class constructor Create;
    destructor Destroy; override;
    function SetFocusedControl(Control: TWinControl): Boolean; override;
  end;

type
  TKeyboardFormStyleHook = class(TFormStyleHook)
  protected
    procedure WMNCActivate(var Message: TWMNCActivate); message WM_NCACTIVATE;
  end;

procedure TKeyboardFormStyleHook.WMNCActivate(var Message: TWMNCActivate);
begin
  { avoids the title bar being drawn active for blink }
  Message.Active := False;
  inherited;
end;

class constructor TKeyboardForm.Create;
begin
  TCustomStyleEngine.RegisterStyleHook(TKeyboardForm, TKeyboardFormStyleHook);
end;

destructor TKeyboardForm.Destroy;
begin
  LastFocusedForm := nil;
  inherited;
end;

procedure TKeyboardForm.Notification(AComponent: TComponent; Operation: TOperation);
begin
  if (Operation = opRemove) and (AComponent = FLastFocusedForm) then begin
    FLastFocusedForm := nil;
  end;
  inherited;
end;

function TKeyboardForm.SetFocusedControl(Control: TWinControl): Boolean;
begin
  LastFocusedForm := Screen.FocusedForm;
  result := inherited;
end;

procedure TKeyboardForm.SetLastFocusedForm(const Value: TCustomForm);
begin
  if FLastFocusedForm <> Value then
  begin
    if FLastFocusedForm <> nil then begin
      FLastFocusedForm.RemoveFreeNotification(Self);
    end;
    FLastFocusedForm := Value;
    if FLastFocusedForm <> nil then begin
      FLastFocusedForm.FreeNotification(Self);
    end;
  end;
end;

procedure TKeyboardForm.WMActivate(var Message: TWMActivate);
begin
  Message.Active := WA_INACTIVE;
  inherited;
end;

procedure TKeyboardForm.WMMouseActivate(var Message: TWMMouseActivate);
begin
  inherited;
  Message.Result := MA_NOACTIVATE;
end;

procedure TKeyboardForm.WMSetFocus(var Message: TWMSetFocus);
begin
  inherited;
  if (FLastFocusedForm <> nil) and (message.FocusedWnd <> FLastFocusedForm.Handle) then begin
    SendMessage(FLastFocusedForm.Handle, WM_SETFOCUS, 0, 0);
    Message.FocusedWnd := FLastFocusedForm.Handle;
  end;
end;
like image 69
Uwe Raabe Avatar answered Oct 29 '22 17:10

Uwe Raabe


The following combination of WMMouseActivate(), WMNCActivate() and reseting focus seems to fulfill your wishes:

The keyboard form (with BorderStyle = bsToolWindow) has message handlers for WM_MOUSEACTIVATE (as you already have) and WM_NCACTIVATE. The latter for having a point where to reset focus to the window with the edit control.

In addition the keyboardform will keep track of which form holds the edit (or other) control that has focus, and does that by introducing a new method for showing, which I called ShowUnfocused() and a field called FocusedForm: THandle.

procedure TKbdForm.ShowUnfocused(FocusedWindow: THandle);
begin
  FocusedForm := FocusedWindow;
  Show;
end;

procedure TKbdForm.FormShow(Sender: TObject);
begin
  SetForegroundWindow(FocusedForm);
end;

procedure TKbdForm.WMMouseActivate(var Message: TWMMouseActivate);
begin
  Message.Result := MA_NOACTIVATE;
end;

procedure TKbdForm.WMNCActivate(var Message: TWMNCActivate);
begin
  Message.Result := 1; // important
  SetForegroundWindow(FocusedForm);
end;

The keyboardform is invoked by the following common code of the edit controls:

procedure TForm17.EditClick(Sender: TObject);
begin
  KbdForm.ShowUnfocused(self.Handle);
  (Sender as TWinControl).SetFocus;
end;

An alternative to what is said above, could be to set the BorderStyle = bsNone and arrange the dragging of the form with the Mouse Down, Move, Up events directly from the forms surface (or maybe a panel to mimic a top frame), and adding a close button. The challenge would be to get it visually acceptable.

like image 33
Tom Brunberg Avatar answered Oct 29 '22 16:10

Tom Brunberg