Cómo crear el formulario "No activar" en Firemonkey

147

En XCode al agregar estos métodos a su subclase NSView puede evitar que la ventana se active al hacer clic en ella:

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

En la plataforma de Windows se realiza mediante este simple código:

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

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

¿Cómo puedo subclasificar NSView para evitar que mi FMX TForm se active al hacer clic en él?

¿Cómo puedo crear el formulario " No activar " en firemonkey ?

mh taqia
fuente
3
No estoy seguro de si también se aplica a Firemonkey, o si responde a su pregunta correctamente, pero es posible que desee ver este ejemplo: delphi.about.com/od/delphitips2008/qt/ex_noactivate.htm
TildalWave
Gracias, pero es solo para Windows y la forma más fácil es mi solución descrita anteriormente por "SetWindowLong". La pregunta es sobre MacOS.
mh taqia
Devon: ¿Cómo podría ayudarme este enlace?
mh taqia
¡Gracias a WBAR, es la segunda recompensa!
mh taqia

Respuestas:

13

Es posible usar NSPanel con el indicador NSNonactivatingPanelMask . El formulario NSView de fmx debería convertirse en hijo de NSPanel. He escrito una clase auxiliar que funciona tanto para plataformas Windows como Mac ( funciona en 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.

Uso de la unidad anterior:

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

Si frmKeyboard es su formulario principal, no coloque el código anterior en el constructor de formularios, se recomienda ponerlo en OnShow.

ingrese la descripción de la imagen aquí

Nota : WindowHandleToPlatform no parece existir en XE3, por lo que la línea se puede reemplazar con

window := NSWindow(NSWindowFromObjC(FmxHandleToObjC(Form.Handle)));
mh taqia
fuente
1
Gracias por una excelente solución: la ventana handletoplatform no parece existir en XE3, por lo que la línea se puede reemplazar con window: = NSWindow (NSWindowFromObjC (FmxHandleToObjC (Form.Handle)));
David Peters
2

Puede desactivar el manejo del mouse de formularios para evitar que se enfoque. Asumiendo que su formulario se llama 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);

Hay un pequeño problema en que no detiene un clic derecho del mouse. Si eso es un problema, deberá responder al evento mousedown en el formulario y llamar a los formularios principales mousedown para que no pierda el evento mouse. Dado que el botón derecho del mouse hacia abajo capturará los eventos del mouse, también deberá responder a los eventos de movimiento del mouse y arriba del mouse, remitiéndolos a su formulario principal. Aunque captura el mouse con el botón derecho, no enfocará el formulario.

Dave Peters DP Software

David Peters
fuente
Incorrecto, no funciona. El formulario cambia el foco del teclado al hacer clic.
mh taqia
Bueno, no se está enfocando, pero lo que sucede es que cualquier clic del mouse pasa por el formulario a lo que esté debajo. Si puede organizar que el formulario sin foco tenga establecida la propiedad TopMost y solo una parte en blanco de su propio formulario principal esté debajo, funcionará. Si tiene controles de formulario principal debajo de la ventana, se enfocarán cuando haga clic con el mouse, ya que la ventana sin foco se comporta como si no estuviera allí. Del mismo modo, si la ventana se coloca sobre el escritorio, entonces el escritorio recibe el clic del mouse y su aplicación pierde el foco.
David Peters
Tenga en cuenta que necesito eventos del mouse. No puedo ignorar los eventos del mouse. Quiero hacer clic en un botón, también quiero tener animaciones de firemonkey cuando el puntero del mouse entre en un control. Suponga que quiero crear un teclado virtual, la aplicación en primer plano es (por ejemplo) TextEdit. Cuando hago clic en un botón en mi formulario fmx, se generará un evento de teclado y se escribirá un carácter.
mh taqia 01 de