How to create "No Activate" form in Firemonkey

MacosDelphiDelphi Xe2C++builderFiremonkey

Macos Problem Overview


In XCode by adding these methods to your NSView subclass can prevent the window from becoming active when clicking on it:

- (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent )theEvent {
    return YES;
}
- (BOOL)acceptsFirstMouse:(NSEvent )theEvent {
    return YES; 
}
- (void)mouseDown:(NSEvent )theEvent {
    [[[NSApp]] preventWindowOrdering]; 
}

In Windows platform It is done by this simple code:

HWND hWnd = FindWindowW((String("FM") + fmxForm->ClassName()).c_str(), 
    fmxForm->Caption.c_str());

SetWindowLong(hWnd, GWL_EXSTYLE,
    GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_NOACTIVATE);

How can I subclass NSView to prevent my FMX TForm becoming active when clicking on it?

How can I create "No Activate" form in firemonkey?

Macos Solutions


Solution 1 - Macos

It is possible using NSPanel with NSNonactivatingPanelMask flag. The NSView of fmx form should become child of NSPanel. I have written a helper class which works for both Windows and Mac platforms (Works on XE4):

unit NoActivateForm;

interface

uses Fmx.Forms, Fmx.Types
{$IFDEF POSIX}
	, Macapi.AppKit
{$ENDIF}
	;

type TNoActivateForm = class
private
	form: TForm;
{$IFDEF POSIX}
	panel: NSPanel;
	timer: TTimer;  // for simulating mouse hover event
{$ENDIF}
	procedure SetPosition(const x, y: Integer);
	procedure GetPosition(var x, y: Integer);
	procedure SetDimensions(const width, height: Integer);
	procedure SetLeft(const Value: Integer);
	procedure SetTop(const Value: Integer);
	procedure SetHeight(const Value: Integer);
	procedure SetWidth(const Value: Integer);
	procedure SetVisible(const Value: Boolean);
	function GetLeft: Integer;
	function GetTop: Integer;
	function GetHeight: Integer;
	function GetWidth: Integer;
	function GetVisible: Boolean;
{$IFDEF POSIX}
	procedure OnTimer(Sender: TObject);
{$ENDIF}
public
	constructor Create(AForm: TForm);
	destructor Destroy; override;
	property Left: Integer read GetLeft write SetLeft;
	property Top: Integer read GetTop write SetTop;
	property Height: Integer read GetHeight write SetHeight;
	property Width: Integer read GetWidth write SetWidth;
	property Visible: Boolean read GetVisible write SetVisible;
end;

implementation
uses
	Classes, System.Types
{$IFDEF MSWINDOWS}
	, Winapi.Windows;
{$ELSE}
	, Macapi.CocoaTypes, FMX.Platform.Mac, Macapi.CoreGraphics, Macapi.CoreFoundation;
{$ENDIF}

constructor TNoActivateForm.Create(AForm: TForm);
{$IFDEF POSIX}
var
	rect: NSRect;
	bounds: CGRect;
	window: NSWindow;
	style: integer;
	panelCount: integer;
begin
	form := AForm;
	form.Visible := false;
	bounds := CGDisplayBounds(CGMainDisplayID);
	rect := MakeNSRect(form.Left, bounds.size.height - form.Top - form.Height,
		form.ClientWidth, form.ClientHeight);
	style := NSNonactivatingPanelMask;
	style := style or NSHUDWindowMask;
	panel := TNSPanel.Wrap(
		TNSPanel.Alloc.initWithContentRect(rect, style, NSBackingStoreBuffered,
		true));
	panel.setFloatingPanel(true);
	//panel.setHasShadow(false); optional
	window := WindowHandleToPlatform(form.Handle).Wnd;

	panel.setContentView(TNSView.Wrap(window.contentView));
	TNSView.Wrap(window.contentView).retain;

	timer := TTimer.Create(form.Owner);
	timer.OnTimer := OnTimer;
	timer.Interval := 50;
end;
{$ELSE}
var hWin: HWND;
begin
	form := AForm;
	form.TopMost := true;
	hWin := FindWindow(PWideChar('FM' + form.ClassName), PWideChar(form.Caption));
	if hWin <> 0 then
		SetWindowLong(hWin, GWL_EXSTYLE,
			GetWindowLong(hWin, GWL_EXSTYLE) or WS_EX_NOACTIVATE);
end;
{$ENDIF}

destructor TNoActivateForm.Destroy;
{$IFDEF POSIX}
begin
	panel.release;
end;
{$ELSE}
begin
end;
{$ENDIF}

procedure TNoActivateForm.SetPosition(const x, y: Integer);
{$IFDEF POSIX}
var point: NSPoint;
	screen: CGRect;
begin
	screen := CGDisplayBounds(CGMainDisplayID);
	point.x := x;
	point.y := round(screen.size.height) - y - form.height;
	panel.setFrameOrigin(point);
end;
{$ELSE}
begin
	form.Left := x;
	form.Top := y;
end;
{$ENDIF}

procedure TNoActivateForm.GetPosition(var x, y: Integer);
{$IFDEF POSIX}
var screen: CGRect;
begin
	screen := CGDisplayBounds(CGMainDisplayID);
	x := round(panel.frame.origin.x);
	y := round(screen.size.height - panel.frame.origin.y - panel.frame.size.height);
end;
{$ELSE}
begin
	x := form.Left;
	y := form.Top;
end;
{$ENDIF}

procedure TNoActivateForm.SetDimensions(const width, height: Integer);
{$IFDEF POSIX}
var size: NSSize;
begin
	size.width := width;
	size.height := height;
	panel.setContentSize(size);
end;
{$ELSE}
begin
	form.width := width;
	form.height := height;
end;
{$ENDIF}

procedure TNoActivateForm.SetLeft(const Value: Integer);
begin
	SetPosition(Value, Top);
end;

procedure TNoActivateForm.SetTop(const Value: Integer);
begin
	SetPosition(Left, Value);
end;

procedure TNoActivateForm.SetHeight(const Value: Integer);
begin
	SetDimensions(Width, Value);
end;

procedure TNoActivateForm.SetWidth(const Value: Integer);
begin
	SetDimensions(Value, Height);
end;

procedure TNoActivateForm.SetVisible(const Value: Boolean);
begin
{$IFDEF POSIX}
	panel.setIsVisible(Value);
{$ELSE}
	form.visible := Value;
{$ENDIF}
end;

function TNoActivateForm.GetLeft: Integer;
var x, y: Integer;
begin
	GetPosition(x, y);
	result := x;
end;

function TNoActivateForm.GetTop: Integer;
var x, y: Integer;
begin
	GetPosition(x, y);
	result := y;
end;

function TNoActivateForm.GetHeight: Integer;
begin
{$IFDEF POSIX}
	result := round(panel.frame.size.height);
{$ELSE}
	result := form.Height;
{$ENDIF}
end;

function TNoActivateForm.GetWidth: Integer;
begin
{$IFDEF POSIX}
	result := round(panel.frame.size.width);
{$ELSE}
	result := form.Width;
{$ENDIF}
end;

function TNoActivateForm.GetVisible: Boolean;
begin
{$IFDEF POSIX}
	result := panel.isVisible();
{$ELSE}
	result := form.visible;
{$ENDIF}
end;

{$IFDEF POSIX}
procedure TNoActivateForm.OnTimer(Sender: TObject);
var event: CGEventRef;
	point: CGPoint;
	form_rect: TRectF;
	client_point, mouse_loc: TPointF;
	shift: TShiftState;
begin
	event := CGEventCreate(nil);
	point := CGEventGetLocation(event);
	CFRelease(event);
	mouse_loc.SetLocation(point.x, point.y);
	if Visible = true then
	begin
		form_rect := RectF(0, 0, form.Width, form.Height);
		client_point.X := mouse_loc.X - Left;
		client_point.Y := mouse_loc.y - Top;
		if PtInRect(form_rect, client_point) then
			form.MouseMove(shift, client_point.x, client_point.y)
		else
			form.MouseLeave();
	end;
end;
{$ENDIF}

end.

Usage of above unit:

TNoActivateForm *naKeyboard; // global scope    
void __fastcall TfrmKeyboard::TfrmKeyboard(TObject *Sender)
{
    naKeyboard = new TNoActivateForm(frmKeyboard); // frmKeyboard is a normal fmx form
    naKeyboard->Visible = true;
}

If frmKeyboard is your Main Form then do not put above code in form constructor, It is recommended to put it in OnShow.

enter image description here

Note: WindowHandleToPlatform doesn't seem to exist in XE3 so that line can be replaced with

window := NSWindow(NSWindowFromObjC(FmxHandleToObjC(Form.Handle)));

Solution 2 - Macos

You can turn off the forms mouse handling to prevent it being focused. Assuming your form is called myform:

uses fmx.platform.mac, macapi.appkit;
.
.
Var nswin:nswindow;
.
.  
NSWin:= NSWindow(NSWindowFromObjC(FmxHandleToObjC(myform.Handle))); { get the NSWindow }
NSWin.setIgnoresMouseEvents(true);                                 { ignore mouse events }
NSWin.setAcceptsMouseMovedEvents(false);

There is a slight problem in that it doesn't stop a right mouse click. If that's a problem, you will have to respond to the mousedown event in the form and call the main forms mousedown so it doesn't lose the mouse event. Since the right mouse down will then capture the mouse events, you also then need to respond to mouse move and mouse up events too - forwarding them to your main form. Although it captures the mouse on right click, it will still not focus the form.

Dave Peters DP Software

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionmh taqiaView Question on Stackoverflow
Solution 1 - Macosmh taqiaView Answer on Stackoverflow
Solution 2 - MacosDavid PetersView Answer on Stackoverflow