以编程方式调用Snap/Aero最大化功能

7
有没有办法使用C或C ++针对特定的窗口/窗口ID以编程方式调用Aera最大化效果?
例如:

enter image description here

或者

在此输入链接描述
(来源:thebuzzmedia.com)

我正在使用一个无边框的Qt窗口,并且Qt有一个API可以获取窗口ID。我想通过编程方式触发窗口效果,而不是使用已知的触发器。


@SLaks 我不想伪造标题栏拖动。我想在按下按钮时触发捕捉效果。 - Jacob Krieg
1
@karlphillip,我在谈论这个视频中的Aero Snap效果:http://youtu.be/xO_7sbFEJrE?t=0m31s。当我说“teardrop”时,我指的是鼠标指针触碰屏幕边缘后发生的效果。其余的效果由扩展的玻璃状效果表示。 - Jacob Krieg
好的,为什么切尔诺贝利的答案不够充分? - karlphillip
因为它没有触发玻璃效果,它只是调整窗口大小。我知道如何做到这一点。 - Jacob Krieg
@JacobKrieg 请看我的编辑,请注意,我写的代码最终结果非常类似于 Aero Snap。它可以完美地在无框窗口上运行。 - Jablonski
显示剩余10条评论
2个回答

7

我不想讨论实现这种效果所涉及的每一个细节,不仅有很多事情要做,而且您还提到了您理解将窗口放置在特定位置的逻辑。 在这个答案中,我将解决我认为的两个主要挑战:

  • 如何接收和处理最大化事件?

  • 如何创建类似于aero snap的效果的近似值?

为了回答第一个问题,我们必须分析窗口最大化时触发哪些event handlers:

void resizeEvent(QResizeEvent* evt);   // Invoked first,
void paintEvent(QPaintEvent* event);   // then second, 
void changeEvent(QEvent* evt);         // and at last.

一个Qt应用程序首先会收到resizeEvent()通知,此后会执行paintEvent()来绘制窗口(或部件),只有在所有内容都显示之后,才会调用changeEvent()来告诉你该部件已被最大化(也许这个通知有点晚了,我不确定)。
其中唯一我们关心的是resizeEvent()。此事件处理程序会通知新窗口/部件大小,可将其与桌面大小进行比较,从而允许我们知道该事件是否实际上是一个最大化请求。一旦我们确定了最大化请求,我们就可以确定应用程序应该最大化(并锚定)到屏幕的右侧、左侧还是中心。
现在是创建aero snap 部件并将其放置在屏幕上作为用户的视觉提示的时候了。 回答第二个问题,我认为不可能调用本机Windows API并向其请求在您的窗口上执行此效果。唯一的其他逻辑选择是编写代码以近似这种效果。
可通过绘制具有阴影边框的透明窗口来复制视觉外观。下面源代码演示的方法是创建并自定义一个 QWidget 使其表现和外观类似于 aero snap 窗口:

enter image description here

这个演示并不是世界上最美的事物,我知道。该演示创建一个常规窗口供用户交互,一旦窗口最大化,它就会将自己放置在屏幕左侧。在屏幕右侧,它显示类似于aero snap窗口的内容(如上所示)。
aero snap小部件的想法非常简单:一个具有透明背景和自定义绘图过程的QWidget。换句话说,它是一个透明窗口,绘制带阴影的圆角矩形而已。
为了使其更加真实,您应该添加一些动画来逐渐调整小部件的大小。一个for循环可能会起到作用,但如果您需要更高级的动画效果,您最终将使用定时器。如果您查看此处,您可以看到使用Qt执行动画的最快和最脏方法,并了解更好的处理动画的方法。然而,对于这样简单的任务,请坚持使用基于帧的动画

main.cpp:

#include "window.h"
#include <QApplication>

int main(int argc, char* argv[])
{
    QApplication app(argc, argv);
    Window window;
    window.show();

    return app.exec();
}

window.h:

#pragma once
#include "snapwindow.h"
#include <QMainWindow>
#include <QEvent>

class Window : public QMainWindow
{    
public:
    Window();

    void resizeEvent(QResizeEvent* evt);
    //void paintEvent(QPaintEvent* event);
    void changeEvent(QEvent* evt);

private:
    SnapWindow* _sw;
};

window.cpp:

#include "window.h"
#include "snapwindow.h"

#include <QDebug>
#include <QWindowStateChangeEvent>
#include <QApplication>
#include <QDesktopWidget>

Window::Window()
{
    setWindowTitle("AeroSnap");
    resize(300, 300);    

    _sw = new SnapWindow(this);
    _sw->hide();
}

void Window::changeEvent(QEvent* evt)
{
    if (evt->type() == QEvent::WindowStateChange)
    {
        QWindowStateChangeEvent* event = static_cast<QWindowStateChangeEvent*>(evt);

        if (event->oldState() == Qt::WindowNoState &&
                windowState() == Qt::WindowMaximized)
        {
            qDebug() << "changeEvent: window is now maximized!";
        }
    }
}

// resizeEvent is triggered before window_maximized event
void Window::resizeEvent(QResizeEvent* evt)
{
    qDebug() << "resizeEvent: request to resize window to: " << evt->size();

    QSize desktop_sz = QApplication::desktop()->size();
    //qDebug() << "resizeEvent: desktop sz " << desktop_sz.width() << "x" << desktop_sz.height();

    // Apparently, the maximum size a window can have in my system (1920x1080)
    // is actually 1920x990. I suspect this happens because the taskbar has 90px of height:
    desktop_sz.setHeight(desktop_sz.height() - 90);

    // If this not a request to maximize the window, don't do anything crazy.
    if (desktop_sz.width() != evt->size().width() ||
        desktop_sz.height() != evt->size().height())
        return;

    // Alright, now we known it's a maximize request:
    qDebug() << "resizeEvent: maximize this window to the left";

    // so we update the window geometry (i.e. size and position)
    // to what we think it's appropriate: half width to the left
    int new_width = evt->size().width();
    int new_height = evt->size().height();
    int x_offset = 10;
    setGeometry(x_offset, 45, new_width/2, new_height-45); // y 45 and height -45 are due to the 90px problem

    /* Draw aero snap widget */

    _sw->setGeometry(new_width/2-x_offset, 0, new_width/2, new_height);
    _sw->show();

    // paintEvent() will be called automatically after this method ends,
    // and will draw this window with the appropriate geometry.
}

snapwindow.h:

#pragma once
#include <QWidget>

class SnapWindow : public QWidget
{
public:
    SnapWindow(QWidget* parent = 0);

    void paintEvent(QPaintEvent *event);
};

snapwindow.cpp:

#include "snapwindow.h"
#include <QPainter>
#include <QGraphicsDropShadowEffect>

SnapWindow::SnapWindow(QWidget* parent)
: QWidget(parent)
{      
    // Set this widget as top-level (i.e. owned by user)
    setParent(0);

    /* Behold: the magic of creating transparent windows */

    setWindowFlags(Qt::Widget | Qt::FramelessWindowHint);
    setStyleSheet("background:transparent;");
    setAttribute(Qt::WA_NoSystemBackground, true); // speed up drawing by removing unnecessary background initialization
    setAttribute(Qt::WA_TranslucentBackground);
    //setAutoFillBackground(true);

    /* Use Qt tricks to paint stuff with shadows */

    QGraphicsDropShadowEffect* effect = new QGraphicsDropShadowEffect();
    effect->setBlurRadius(12);
    effect->setOffset(0);
    effect->setColor(QColor(0, 0, 0, 255));
    setGraphicsEffect(effect);
}

void SnapWindow::paintEvent(QPaintEvent *event)
{
    QWidget::paintEvent(event);

    /* Lazy way of painting a shadow */

    QPainter painter(this);
    QPen pen(QColor(180, 180, 180, 200));
    pen.setWidth(3);
    painter.setPen(pen);

    // Offset 6 and 9 pixels so the shadow shows up properly
    painter.drawRoundedRect(QRect(6, 6, (width()-1)-9, (height()-1)-9), 18, 18);
}

这只是一个快速演示,指引你朝正确的方向前进。它并不完整实现你所寻求的效果。

非常感谢您的提示!我不想成为一个彻底的混蛋,只是拿走您的代码,并期待一切都能像开箱即用那样工作,但我看了一下,对我来说,它的行为与期望的不同。我意识到 SnapWindow 在调整大小事件上被触发,所以当我调整窗口大小时,它应该做出反应。但对我来说并没有发生任何事情。我在主窗口上调用了 setWindowFlags(Qt::FramelessWindowHint)setStatusBar(new QStatusBar());,但调整大小并没有起作用,尽管事件函数已经被调用:http://i.imgur.com/2WyCPv3.png 这可能是因为我使用的是 Windows 7 吗? - Jacob Krieg
resizeEvent() 函数决定了是否发生了最大化事件。花些时间在这个函数中,添加调试信息以熟悉它。根据你的描述,这个逻辑在你的电脑上出现了问题,最有可能的原因是使用了某个“魔法数字”(90)(搜索字符串“1920x1080”并阅读注释)。然而,如果您使用了2个显示器,也可能会失败,因为 QApplication::desktop()->size() 返回的是监视器1 + 监视器2的总大小。 - karlphillip
我会尝试一下并回来告诉你消息,非常感谢!关于双显示器的问题,肯定有一些API可以提供显示器数量和可能所在的显示器,这应该是可以解决的。再次感谢! - Jacob Krieg
我没有尝试过这段代码,但它应该可以工作,所以我会给你赏金。 如果它起作用了,我会告诉你的,但我不知道为什么它不会起作用。谢谢! - Jacob Krieg

3
也许这并不是你需要的,但这个效果只是调整和移动窗口,然后尝试使用Qt方法来实现。
bool left = false;
QSize size = QApplication::desktop()->size();//resolution of current screen
if(left)
{//left side
    this->setGeometry(0, 0, size.width()/2, size.height());//(maybe need do some changes)
}
else
{//right side
    this->setGeometry(size.width()/2, 0, size.width()/2, size.height());
}

使用QApplication::desktop()可以在不同分辨率的屏幕上正常工作。

在网上我找到了类似于winapi的东西,但它不能正常工作:

HWND act = GetForegroundWindow();
PostMessage((HWND)act,WM_NCLBUTTONDBLCLK, HTTOP, 0);

最佳方式

将这些方法结合起来。例如:

HWND act = GetForegroundWindow();
bool left = false;
QSize size = QApplication::desktop()->size();
if(left)
{
    this->move(0,0);
    PostMessage((HWND)act,WM_NCLBUTTONDBLCLK, HTTOP, 0);
    this->resize(size.width()/2,QApplication::desktop()->height());

}
else
{
    this->move(size.width()/2,0);
    PostMessage((HWND)act,WM_NCLBUTTONDBLCLK, HTTOP, 0);
    this->resize(size.width()/2,QApplication::desktop()->height());
}

为什么要这样做呢?因为move()函数控制窗口左右移动,但是使用PostMessage(winapi)可以在每个屏幕上适当地设置窗口高度(窗口不会定位到任务栏下方,如您的示例中所示)。
编辑:我稍微改了一下代码,现在更好了。是的,它再次进行调整大小,但现在它没有winapi代码(PostMessage等),所以Photoshop不会捕捉到它,在Qt中有一个有趣的方法叫做availableGeometry,它返回我们需要的正常屏幕高度,利用这个方法,无边框窗口可以完美地模拟不同方向的Aero Snap效果。虽然效果可能不太好,但我看不出有什么API可以实现Aero效果。也许这种方法对你来说是正常的。
在Qt中有Aero Peek:http://qt-project.org/doc/qt-5/qtwinextras-overview.html,但它也不能解决这个问题。
代码:
bool left = true;
bool upper = true;

if(upper)
{
    QRect rect = QApplication::desktop()->availableGeometry(-1);
    this->setGeometry(rect);
}
else if(left)
    {
        QRect rect = QApplication::desktop()->availableGeometry(-1);
        rect.setWidth(rect.width()/2);
        this->setGeometry(rect);
    }
    else
    {
        QRect rect = QApplication::desktop()->availableGeometry(-1);
        int half = rect.width()/2;
        rect.setX(half);
        rect.setWidth(half);
        this->setGeometry(rect);
    }

尝试使用无框窗口!您应该选择一个方向或让用户选择。

1
这通常在多显示器设置中会失败。它还未能实现其余的Aero Snap效果(如将高度最大化而保持宽度不变,或将大小调整为显示器大小的一半)。对于具有标题栏双击自定义处理程序的应用程序(例如Photoshop曾经使用的),它也会失败。 - IInspectable
非常感谢您的努力(+1)! 不幸的是,该代码解决了调整窗口大小的问题,而不是生成我想要生成的“增长玻璃”效果(请参见上面的图片)。 - Jacob Krieg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接