Comment créer un formulaire «No Activate» dans Firemonkey

147

Dans XCode, l'ajout de ces méthodes à votre sous-classe NSView peut empêcher la fenêtre de devenir active lorsque vous cliquez dessus:

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

Dans la plate-forme Windows, cela se fait par ce code simple:

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

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

Comment puis-je sous-classer NSView pour éviter que mon FMX TForm ne devienne actif en cliquant dessus?

Comment créer un formulaire " No Activate " dans firemonkey ?

mh taqia
la source
3
Je ne sais pas si cela s'applique également à Firemonkey ou s'il répond correctement à votre question, mais vous voudrez peut-être jeter un coup d'œil à cet exemple: delphi.about.com/od/delphitips2008/qt/ex_noactivate.htm
TildalWave
Merci, mais ce n'est que pour Windows et le moyen le plus simple est ma solution décrite ci-dessus par "SetWindowLong", La question concerne MacOS.
mh taqia le
Devon: Comment ce lien pourrait-il m'aider?
mh taqia
Grâce à WBAR, c'est la deuxième prime!
mh taqia le

Réponses:

13

Il est possible d'utiliser NSPanel avec l' indicateur NSNonactivatingPanelMask . Le formulaire NSView de fmx doit devenir l'enfant de NSPanel. J'ai écrit une classe d'assistance qui fonctionne pour les plates-formes Windows et Mac ( fonctionne sur 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.

Utilisation de l'unité ci-dessus:

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 est votre formulaire principal, ne mettez pas le code ci-dessus dans le constructeur de formulaire, il est recommandé de le mettre dans OnShow.

entrez la description de l'image ici

Remarque : WindowHandleToPlatform ne semble pas exister dans XE3, donc cette ligne peut être remplacée par

window := NSWindow(NSWindowFromObjC(FmxHandleToObjC(Form.Handle)));
mh taqia
la source
1
Merci pour cette excellente solution - windowhandletoplatform ne semble pas exister dans XE3, donc cette ligne peut être remplacée par window: = NSWindow (NSWindowFromObjC (FmxHandleToObjC (Form.Handle)));
David Peters
2

Vous pouvez désactiver la gestion de la souris des formulaires pour éviter qu'elle ne soit focalisée. En supposant que votre formulaire s'appelle 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);

Il y a un léger problème en ce que cela n'arrête pas un clic droit de la souris. Si c'est un problème, vous devrez répondre à l'événement mousedown dans le formulaire et appeler les formulaires principaux mousedown pour qu'il ne perde pas l'événement souris. Étant donné que le bouton droit de la souris capturera alors les événements de la souris, vous devez également répondre aux événements de déplacement de la souris et de souris vers le haut, en les transférant vers votre formulaire principal. Bien qu'il capture la souris lors d'un clic droit, il ne focalisera toujours pas le formulaire.

Logiciel Dave Peters DP

David Peters
la source
Incorrect, ne fonctionne pas. Le formulaire modifie le focus clavier lors d'un clic.
mh taqia
Eh bien, ce n'est pas le cas, mais ce qui se passe, c'est que tous les clics de souris passent par le formulaire à tout ce qui se trouve en dessous. Si vous pouvez faire en sorte que le formulaire non focus ait la propriété TopMost définie et que seule une partie vide de votre propre formulaire principal soit en dessous, cela fonctionnera. Si vous avez des contrôles de formulaire principaux sous la fenêtre, ils obtiendront le focus lorsque vous cliquerez avec la souris car la fenêtre non-focus se comporte comme si elle n'était pas là. De même, si la fenêtre est placée sur le bureau, le bureau obtient le clic de souris et votre application perd le focus.
David Peters
Notez que j'ai besoin d'événements de souris. Je ne peux pas ignorer les événements de souris. Je veux cliquer sur un bouton, aussi je veux avoir des animations firemonkey lorsque le pointeur de la souris entre sur un contrôle. Supposons que je souhaite créer un clavier virtuel, l'application de premier plan est (par exemple) TextEdit. Lorsque je clique sur un bouton de mon formulaire fmx, un événement clavier est généré et un caractère est tapé.
mh taqia