Как создать форму «Не активировать» в Firemonkey

147

В XCode добавление этих методов в ваш подкласс NSView может помешать активизации окна при нажатии на него:

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

В платформе Windows это делается с помощью простого кода:

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

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

Как я могу создать подкласс NSView, чтобы предотвратить активацию моей FMX TForm при нажатии на нее?

Как я могу создать форму « без активации » в firemonkey ?

MH Taqia
источник
3
Не уверен, применимо ли это к Firemonkey, или же он правильно отвечает на ваш вопрос, но вы можете взглянуть на этот пример: delphi.about.com/od/delphitips2008/qt/ex_noactivate.htm
TildalWave
Спасибо, но это только для Windows, и более простым способом является моё решение, описанное выше "SetWindowLong". Вопрос в MacOS.
Mh Taqia
Девон: Как эта ссылка может помочь мне?
mh taqia
Благодаря WBAR, это вторая награда!
Mh Taqia

Ответы:

13

Это возможно, используя NSPanel с флагом NSNonactivatingPanelMask . NSView формы fmx должен стать потомком NSPanel. Я написал вспомогательный класс, который работает для платформ Windows и Mac ( работает на 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.

Использование вышеуказанного блока:

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

Если frmKeyboard - ваша основная форма, то не помещайте вышеуказанный код в конструктор формы, рекомендуется поместить его в OnShow.

введите описание изображения здесь

Примечание : WindowHandleToPlatform, по-видимому, не существует в XE3, поэтому строку можно заменить на

window := NSWindow(NSWindowFromObjC(FmxHandleToObjC(Form.Handle)));
MH Taqia
источник
1
Спасибо за отличное решение - похоже, что windowhandletoplatform не существует в XE3, поэтому строку можно заменить на window: = NSWindow (NSWindowFromObjC (FmxHandleToObjC (Form.Handle)));
Дэвид Питерс
2

Вы можете отключить манипулирование мышью с формами, чтобы оно не фокусировалось. Предполагая, что ваша форма называется 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);

Есть небольшая проблема в том, что она не останавливает щелчок правой кнопкой мыши. Если это проблема, вам придется ответить на событие mousedown в форме и вызвать mousedown основных форм, чтобы оно не потеряло событие мыши. Так как правая мышь вниз захватит события мыши, вам также нужно будет реагировать на события перемещения мыши и мыши вверх - перенаправляя их в основную форму. Хотя он захватывает мышь при щелчке правой кнопкой мыши, он по-прежнему не фокусирует форму.

Программное обеспечение Dave Peters DP

Дэвид Питерс
источник
Неправильно, не работает. Форма меняет фокус клавиатуры при нажатии.
Mh Taqia
Ну, это не фокус, но происходит то, что любые щелчки мышью проходят через форму к тому, что находится под ней. Если вы можете договориться о том, что в форме без фокуса установлено свойство TopMost, и под ним всегда будет только пустая часть вашей главной формы, тогда это будет работать. Если у вас есть какие-либо основные элементы управления формой под окном, они будут фокусироваться при щелчке мышью, поскольку окно без фокуса ведет себя так, как будто его там нет. Точно так же, если окно расположено над рабочим столом, то рабочий стол получает щелчок мыши, и ваше приложение теряет фокус.
Дэвид Питерс
Обратите внимание, что мне нужны события мыши. Я не могу игнорировать события мыши. Я хочу нажать на кнопку, также я хочу иметь анимацию огненной клавиши, когда указатель мыши входит в элемент управления. Предположим, что я хочу создать виртуальную клавиатуру, приоритетным приложением является (например) TextEdit. Когда я нажимаю кнопку в форме FMX, генерируется событие клавиатуры и вводится символ.
Mh Taqia