【问题标题】:Programmatically invoke Snap/Aero maximize以编程方式调用 Snap/Aero 最大化
【发布时间】:2014-11-08 03:36:27
【问题描述】:

有没有办法使用 C 或 C++ 为特定窗口/窗口 ID 以编程方式调用 Aera 最大化效果?

例如:


(来源:thebuzzmedia.com

我正在使用一个无边框的 Qt 窗口,并且 Qt 有一个用于获取窗口 ID 的 API。我想在没有已知触发器的情况下以编程方式触发 windows 效果。

【问题讨论】:

  • 如果你想伪造一个标题栏拖动,从WM_NCHITTEST返回HTCAPTION
  • @SLaks 我不想伪造标题栏拖动。例如,我想在按下按钮时触发快照效果。
  • 很遗憾,Microsoft 选择不为 Aero 快照效果提供任何形式的 API。
  • @JonathanPotter 真的吗?我想这回答了我的问题...... :(
  • @karlphillip 我在此视频中谈论的是 Aero Snap 效果:youtu.be/xO_7sbFEJrE?t=0m31s。当我说泪珠时,我指的是鼠标指针接触到屏幕边缘后立即发生的效果。其余的效果由膨胀的玻璃状效果表示

标签: c++ qt winapi aero aero-snap


【解决方案1】:

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

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

  • 如何创建 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()。此事件处理程序通知可用于与桌面大小进行比较的新窗口/小部件大小,从而让我们知道该事件是否实际上是最大化请求。一旦我们确定了最大化请求,我们就可以确定应用程序是否应该最大化(并锚定)到 rightleftcenter的屏幕。

现在是时候创建 aero snap 小部件并将其放置在屏幕上作为用户的视觉线索。

回答第二个问题,我认为不可能调用本机 Windows API 并礼貌地要求它在您的窗口上执行此效果。唯一合乎逻辑的选择是我们自己编写一个近似这种效果的代码。

可以通过绘制带有阴影边框的透明窗口来复制视觉外观。下面的源代码中演示的方法创建并自定义了 QWidget 以使其行为和看起来像 aero snap 窗口:

我知道,这不是世界上最美好的事物。这个演示创建了一个常规窗口供用户交互,一旦最大化,它就会将自己放置在屏幕的左侧。在屏幕的正确尺寸上,它会显示类似于 aero snap 窗口的内容(如上所示)。

aero snap 小部件背后的想法非常简单:带有透明背景和自定义绘画程序的QWidget。换句话说,它是一个透明窗口,它绘制一个带有阴影的圆角矩形,仅此而已。

为了让它更真实一点,您应该添加一些动画来一点一点地调整小部件的大小。 for 循环可能会成功,但如果你需要一些花哨的东西,你最终会使用计时器。如果您使用look here,您可以看到使用 Qt 执行动画的最快和最脏的方法,以及处理动画的更好方法。但是,对于这样的简单任务,请坚持使用frame-based animation

ma​​in.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 在 resize 事件上被触发,所以当我调整窗口大小时它应该做它的事情。但这不会发生在我身上。我在主窗口上调用了setWindowFlags(Qt::FramelessWindowHint)setStatusBar(new QStatusBar());,但是虽然调用了事件函数,但调整大小并没有做任何事情:i.imgur.com/2WyCPv3.png 难道是因为我使用的是 Windows 7?
  • resizeEvent() 有一些逻辑来决定它是否是发生的最大化事件。花一些时间在该功能上,添加调试消息以熟悉它。根据您的描述,此逻辑在您的计算机上失败,最可能的原因是使用了一些 magic number (90)(搜索字符串 1920x1080 和阅读 cmets)。但是,如果您使用 2 个监视器,这也可能会失败,因为 QApplication::desktop()-&gt;size() 返回监视器 1 + 监视器 2 的总大小。
  • 我要试一试,然后回来提供消息,非常感谢!关于 2 个监视器的问题,必须肯定有一些 API 可以为您提供监视器的数量,也许还有您所在的监视器,这应该是可以解决的。再次感谢!
  • 我没有尝试代码,但它应该可以工作,所以我给你赏金。我会让你知道它是否有效,但我不明白它不应该如何。谢谢!
【解决方案2】:

也许这不是你需要的,但这个效果只是调整大小和移动窗口然后尝试使用 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) 在每个屏幕上正确设置窗口的高度(窗口不会低于taskbar,如您的示例)

编辑

我稍微修改了代码,现在更好了。是的,它再次调整大小,但现在它没有 winapi 代码(PostMessage 等),所以 Photoshop 没有捕捉到它,Qt 中有一个有趣的方法,称为 availableGeometry。它返回我们需要的正常屏幕高度,使用这种方法无边框窗口完美地模拟了不同方向的 Aero Snap 效果。它是有效的,也许不是那么好,但正如我所见,没有用于 Aero 效果的 API。也许这种方法对你来说是正常的。

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);
    }

用无框窗试试吧!您应该选择一个方向或让用户选择它。

【讨论】:

  • 这对于多显示器设置通常会失败。它也无法实现剩余的 Aero Snap 效果(例如在保持宽度不变的同时最大化高度,或者将大小调整为显示大小的一半)。对于具有自定义标题栏双击处理程序的应用程序(如 Photoshop 过去那样),它也会失败。
  • 非常感谢您的努力(+1)!不幸的是,代码解决了调整窗口大小的问题,而不是生成我想要生成的“生长玻璃”效果(见上图)。
猜你喜欢
  • 2012-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-07
  • 1970-01-01
相关资源
最近更新 更多