Qt 在Mac下无边框后无法最大化和最小化解决方案

Qt 5.0以上mac版本一致存在这个bug,网上也有解决方案,一个是修改Qt源码重新编译,这个比较费事,另一个是将文件改为.mm后缀,使用oc代码:

 NSView* view = (NSView*)this->winId();
 NSWindow* wnd = [view window];
[wnd miniaturize:nil];
这句代码好像在qt5.2中可以解决,然后我测试了之后的所有版本(qt5.1-qt5.9)均失败。

所以这个方案不是合适方案。我给出一个通用的方案,也是使用oc:

不要使用

this->setWindowFlags(Qt::FramelessWindowHint);
这样的代码去修改Qt未无边框,改用oc:
 //去掉系统标题栏(qt 在mac使用Qt::frameless标志有bug)
    NSView* view = (NSView*)this->winId();
    NSWindow* wndd = [view window];
    wndd.titlebarAppearsTransparent = YES;
    wndd.titleVisibility = NSWindowTitleHidden;
    wndd.styleMask |= NSFullSizeContentViewWindowMask;
    [[wndd standardWindowButton:NSWindowZoomButton] setHidden:YES];
    [[wndd standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES];
    [[wndd standardWindowButton:NSWindowCloseButton] setHidden:YES];

这样就实现了无边框,然后可以自己定义标题栏:

this->showMinimized();
this->showMaximized();

这样的函数功能也正常。

这里还有一个问题如果窗体开始是最大化显示时,调用

this->showMinimized(); 会调用一次showNomal函数,导致在恢复时与开始状态不一致 所以这里最小化最好也使用oc

  NSView* view = (NSView*)this->winId();
  NSWindow* wnd = [view window];
  [wnd miniaturize:nil];

到这里问题就解决了么?

答案是否定的,上面的方法表面上却是解决了, 但是有个一个问题,会导致qt的内存异常,我测试大概要增长1.5倍,如果你的程序只有100M左右,那么着不算什么,上面的解决方案可用,如果你的程序本身就有500M+。这个方案就悲剧了。

QMacNativeWidget

这个类就是用来将qt的窗体潜入oc的。 大家应该知道我的思路了

上代码:


//以下oc代码为了解决qt设置无边框后 不能最小化的问题

/*******************oc start*************************/
@interface SGRoundView : NSView

@end


@implementation SGRoundView

- (void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];

    // Drawing code here.
    [[NSColor colorWithDeviceCyan:0.23 magenta:0.17 yellow:0.17 black:0 alpha:1] set];
    NSRectFill(dirtyRect);
}

@end

@interface SGRoundWindow : NSWindow

@end

@implementation SGRoundWindow

- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag
{
    self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask  backing:bufferingType defer:flag];

    if ( self )
    {
        //设置窗口为无边界
        [self setStyleMask:NSBorderlessWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask];
        //设置窗口为透明
        [self setOpaque:NO];
        //设置背景无色
        [self setBackgroundColor:[NSColor clearColor]];
        //设置为点击背景可以移动窗口
        [self setMovableByWindowBackground:YES];

        [self setHasShadow:YES];
    }
    return self;
}
//实现圆角,直播晕是直角故这里不需要

//- (void) setContentView:(NSView *)aView
//{
//    aView.wantsLayer            = YES;
//    aView.layer.frame           = aView.frame;
//    aView.layer.cornerRadius    = 0;
//    aView.layer.masksToBounds   = YES;


//    [super setContentView:aView];

//}


//如果不写此方法,生产的窗口上,添加的一些控件或文本处于editable
//加上此方法是使window变为keywindow
//一般是在无TitleBar时,也就是BorderlessWindow,才覆写此方法
//有TitleBar的Window默认是KeyWindow
- (BOOL)canBecomeKeyWindow
{
    return YES;
}

-(BOOL)canBecomeMainWindow {
    return YES;
}


@end

/*******************oc end*************************/

QMacNativeWidget* changeWidgetToMacNativeWidge(QWidget *widget, int x = 0, int y = 0)

QMacNativeWidget* WindowMgr::changeWidgetToMacNativeWidget(QWidget *widget, int x, int y)
{
    if(Q_NULLPTR == widget)
        return Q_NULLPTR;
    SGRoundWindow*nsWindow = [[SGRoundWindow alloc] initWithContentRect:NSMakeRect(x, y, widget->width(), widget->height())
      styleMask:NSTitledWindowMask | NSClosableWindowMask
                                       | NSMiniaturizableWindowMask | NSResizableWindowMask
                        backing:NSBackingStoreBuffered defer:NO];

     nsWindow.collectionBehavior = NSWindowCollectionBehaviorFullScreenPrimary;
    QMacNativeWidget *nativeWidget = new QMacNativeWidget();
    nativeWidget->setWindowFlags(Qt::FramelessWindowHint);
    nativeWidget->move(0,0);
    QVBoxLayout *layout = new QVBoxLayout();
    layout->setContentsMargins(0, 0, 0, 0);
    layout->addWidget(widget);
    nativeWidget->setLayout(layout);
    // Adjust Cocoa layouts
    NSView *nativeWidgetView = reinterpret_cast<NSView *>(nativeWidget->winId());
    NSView *contentView = [nsWindow contentView];
    [contentView setAutoresizesSubviews:YES];
    [nativeWidgetView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
    [nativeWidgetView setAutoresizesSubviews:YES];
    [nativeWidgetView setAutoresizingMask:NSViewWidthSizable];

    // Add the nativeWidget to the window.
   [contentView addSubview:nativeWidgetView positioned:NSWindowAbove relativeTo:nil];
    return nativeWidget;
}

{
    if(Q_NULLPTR == widget)
        return Q_NULLPTR;
    NSWindow*nsWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(x, y, widget->width(), widget->height())
                        styleMask:NSTitledWindowMask | NSClosableWindowMask
                                  | NSMiniaturizableWindowMask | NSResizableWindowMask
                        backing:NSBackingStoreBuffered defer:NO];

    //去掉标题栏
    nsWindow.titlebarAppearsTransparent = YES;
    nsWindow.titleVisibility = NSWindowTitleHidden;
    nsWindow.styleMask |= NSFullSizeContentViewWindowMask;
    [[nsWindow standardWindowButton:NSWindowZoomButton] setHidden:YES];
    [[nsWindow standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES];
    [[nsWindow standardWindowButton:NSWindowCloseButton] setHidden:YES];

    QMacNativeWidget *nativeWidget = new QMacNativeWidget();
    nativeWidget->setWindowFlags(Qt::FramelessWindowHint);
    QVBoxLayout *layout = new QVBoxLayout();
    layout->addWidget(widget);
    nativeWidget->setLayout(layout);
    // Adjust Cocoa layouts
    NSView *nativeWidgetView = reinterpret_cast<NSView *>(nativeWidget->winId());
    NSView *contentView = [nsWindow contentView];
    [contentView setAutoresizesSubviews:YES];
    [nativeWidgetView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
    [nativeWidgetView setAutoresizesSubviews:YES];
    [nativeWidgetView setAutoresizingMask:NSViewWidthSizable];

    // Add the nativeWidget to the window.
   [contentView addSubview:nativeWidgetView positioned:NSWindowAbove relativeTo:nil];
    // Show the window.
    [nsWindow makeKeyAndOrderFront:nsWindow];
    return nativeWidget;
}

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MainWindow *mainwindow = new MainWindow();
    mainwindow->setFixedSize(app.desktop()->width(), app.desktop()->height());
    QWidget *widget = changeWidgetToMacNativeWidget(mainwindow, 0, 40);
    widget->show();

    return app.exec();
}

然后你就可以对MainWindow实现最小化,如放一个按钮,链接一下槽:

void MainWindow::minize()
{
   [windo1 miniaturize:nil];
}

跑一下,点一下最小化,效果还可以跟系统的一样。

但还有一个问题,这里应该是

QMacNativeWidget

的一个bug,最小化有点级dock栏的按钮无法显示窗体,但是不要怕,我既然写了,就有解决方案:

connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(slot_restoreWindow(Qt::ApplicationState)));

应用程序激活的时候会信号,链接一下

void MainWindow:slot_restoreWindow
(Qt::ApplicationState state)
{
    if(state == Qt::ApplicationActive)
    {
        [window makeKeyAndOrderFront:window];
    }
}

这里的window是

changeWidgetToMacNativeWidge返回的QMacNativeWidget所以要保存一下,QMacNativeWidget可以获取NSVIew,NSview可以获取到window

到这里这个问题算是解决了,期待各位更好的解决方案


不过还是期望Qt新版本能解决这个问题。毕竟这个bug已经出现好久了

发表评论

电子邮件地址不会被公开。 必填项已用*标注