Jak utworzyć formularz „Nie aktywuj” w Firemonkey

147

W XCode dodanie tych metod do podklasy NSView może zapobiec aktywowaniu okna po jego kliknięciu:

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

Na platformie Windows odbywa się to za pomocą prostego kodu:

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

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

Jak mogę podklasować NSView, aby zapobiec aktywowaniu mojego FMX TForm po kliknięciu na niego?

Jak mogę utworzyć formularz „ Nie aktywuj ” w programie firemonkey ?

mh taqia
źródło
3
Nie jestem pewien, czy dotyczy to również Firemonkey, czy też poprawnie odpowiada na Twoje pytanie, ale możesz rzucić
TildalWave
Dziękuję, ale to tylko dla Windows i łatwiejszym sposobem jest moje rozwiązanie opisane powyżej przez "SetWindowLong", Pytanie dotyczy MacOS.
mh taqia
Devon: Jak ten link może mi pomóc?
mh taqia
Dzięki WBAR to drugie zlecenie!
mh taqia

Odpowiedzi:

13

Jest to możliwe przy użyciu NSPanel z flagą NSNonactivatingPanelMask . NSView formularza fmx powinno stać się elementem potomnym NSPanel. Napisałem pomocniczą klasę, która działa na platformach Windows i Mac ( działa na 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.

Wykorzystanie powyższej jednostki:

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

Jeśli frmKeyboard jest twoim głównym formularzem, nie umieszczaj powyższego kodu w konstruktorze formularza, zaleca się umieszczenie go w OnShow.

wprowadź opis obrazu tutaj

Uwaga : Wygląda na to, że WindowHandleToPlatform nie istnieje w XE3, więc wiersz można zastąpić

window := NSWindow(NSWindowFromObjC(FmxHandleToObjC(Form.Handle)));
mh taqia
źródło
1
Dzięki za świetne rozwiązanie - wydaje się, że w XE3 nie ma platformy windowhandletoplatform, aby można było zastąpić linię window: = NSWindow (NSWindowFromObjC (FmxHandleToObjC (Form.Handle)));
David Peters
2

Możesz wyłączyć obsługę myszy w formularzach, aby zapobiec jej skupieniu. Zakładając, że twój formularz nazywa się 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);

Istnieje niewielki problem polegający na tym, że nie zatrzymuje kliknięcia prawym przyciskiem myszy. Jeśli to jest problem, będziesz musiał odpowiedzieć na zdarzenie myszy w formularzu i wywołać główne formularze, aby nie utracić zdarzenia myszy. Ponieważ prawy przycisk myszy w dół przechwytuje zdarzenia myszy, musisz również reagować na zdarzenia ruchu myszy i myszy w górę - przesyłając je do formularza głównego. Chociaż przechwytuje mysz po kliknięciu prawym przyciskiem myszy, nadal nie skupia się na formularzu.

Oprogramowanie Dave Peters DP

David Peters
źródło
Niepoprawnie, nie działa. Formularz zmienia fokus klawiatury po kliknięciu.
mh taqia
Cóż, nie skupia się na tym, ale co się dzieje, to każde kliknięcie myszą przechodzi przez formularz do tego, co znajduje się pod nim. Jeśli możesz tak ustawić, że formularz niefokusu ma ustawioną właściwość TopMost i tylko pusta część twojego własnego formularza głównego jest zawsze pod nią, to zadziała. Jeśli masz jakiekolwiek kontrolki formularza głównego pod oknem, zostaną one aktywowane po kliknięciu myszą, ponieważ okno bez fokusa zachowuje się tak, jakby go tam nie było. Podobnie, jeśli okno zostanie umieszczone na pulpicie, kliknięcie myszą zostanie kliknięte, a aplikacja przestanie być aktywna.
David Peters
Zauważ, że potrzebuję zdarzeń myszy. Nie mogę ignorować zdarzeń myszy. Chcę kliknąć przycisk, a także chcę mieć animacje firemonkey, gdy wskaźnik myszy znajdzie się na kontrolce. Załóżmy, że chcę utworzyć klawiaturę wirtualną, aplikacja na pierwszym planie to (na przykład) TextEdit. Kiedy kliknę przycisk w moim formularzu fmx, zostanie wygenerowane zdarzenie klawiatury i zostanie wpisany znak.
mh taqia