如何在 Firemonkey 中创建“无激活”表单

Posted

技术标签:

【中文标题】如何在 Firemonkey 中创建“无激活”表单【英文标题】:How to create "No Activate" form in Firemonkey 【发布时间】:2012-09-27 22:57:14 【问题描述】:

在 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 中创建“No Activate”表单?

【问题讨论】:

不确定它是否也适用于 Firemonkey,或者它是否正确回答了你的问题,但你可能想看看这个例子:delphi.about.com/od/delphitips2008/qt/ex_noactivate.htm 谢谢,但它仅适用于 Windows,更简单的方法是我上面由“SetWindowLong”描述的解决方案,问题是关于 MacOS。 链接:***.com/questions/9048346/… Devon:这个链接对我有什么帮助? 感谢WBAR,第二次赏金! 【参考方案1】:

您可以关闭表单鼠标处理以防止它被聚焦。假设您的表单名为 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 以便它不会丢失鼠标事件。由于鼠标右键按下将捕获鼠标事件,因此您还需要响应鼠标移动和鼠标向上事件 - 将它们转发到您的主窗体。尽管它在右键单击时捕获了鼠标,但它仍然不会聚焦表单。

戴夫·彼得斯 DP软件

【讨论】:

不正确,不起作用。表单在单击时更改键盘焦点。 好吧,它没有获得焦点,但发生的情况是,任何鼠标点击都会通过表单到达其下方的任何内容。如果您可以安排非焦点表单设置了 TopMost 属性,并且您自己的主表单的只有一个空白部分位于其下方,那么它将起作用。如果您在窗口下方有任何主窗体控件,那么当您单击鼠标时它们将获得焦点,因为非焦点窗口的行为就像它不存在一样。同样,如果窗口被放置在桌面上,那么桌面会获得鼠标点击,并且您的应用程序会失去焦点。 请注意,我需要鼠标事件。我不能忽略鼠标事件。我想点击一个按钮,当鼠标指针进入控件时,我也想有 firemonkey 动画。假设我要创建一个虚拟键盘,前台应用程序是(例如)TextEdit。当我单击 fmx 表单上的按钮时,将生成一个键盘事件并输入一个字符。【参考方案2】:

可以使用带有 NSNonactivatingPanelMask 标志的 NSPanel。 fmx 形式的 NSView 应该成为 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)));

【讨论】:

感谢您提供出色的解决方案 - XE3 中似乎不存在 windowhandletoplatform,因此该行可以替换为 window:=NSWindow(NSWindowFromObjC(FmxHandleToObjC(Form.Handle)));

以上是关于如何在 Firemonkey 中创建“无激活”表单的主要内容,如果未能解决你的问题,请参考以下文章

如何托管 FireMonkey 表单客户端在另一个内部对齐?

如何在 Informix SQL 中创建屏幕表单?

颤振 - 如何在弹出窗口中创建表单

如何在 Magnolia 中创建自定义表单处理器?

如何在 WordPress 中创建自定义表单?

如何在 Django 中创建“填空”表单