半透明背景

示例展示了如何创建一个具有半透明背景的圆窗。

将背景设置为半透明的控件将对所有未绘制的像素透明,并且背景会通过小于100%的不透明度绘制的像素发光。未进行任何绘制的像素也不会接收任何鼠标输入。这可以用于自定义顶级控件的形状。在大多数窗口系统中,设置某些窗口标志将会导致窗口装饰(标题栏、窗口框架、按钮)被禁用,从而可以创建特殊形状的窗口。在这个例子中,我们使用这个特性来创建一个包含模拟时钟的圆形窗口。

由于这个示例的窗口不提供文件菜单或关闭按钮,我们提供了一个带有退出条目的上下文菜单,以便示例可以关闭。在窗口上单击鼠标右键以打开此菜单。

ShapeClock 类定义

ShapeClock 类基于模拟时钟示例中定义的 AnalogClock 类。整个类定义如下:

class ShapedClock : public QWidget
{
    Q_OBJECT

public:
    ShapedClock(QWidget *parent = nullptr);
    QSize sizeHint() const override;

protected:
    void mouseMoveEvent(QMouseEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;
    void paintEvent(QPaintEvent *event) override;

private:
    QPoint dragPosition;
};

paintEvent() 实现将在半透明背景(表盘)上绘制一个模拟时钟。此外,我们还实现了 sizeHint(),这样我们就不需要显式调整控件的大小。

由于包含时钟控件的窗口将没有任何标题栏,我们提供了 mouseMoveEvent() 和 mousePressEvent() 的实现,以允许将时钟拖到屏幕上。变量 dragPosition 允许我们记录用户最后在控件上单击的位置。

ShapeClock 类实现

ShapeClock 构造函数设置了一个定时器并将其连接到控件的 update() 槽。此外,我们在控件上添加了一个操作,在控件上右击时,它将通过上下文菜单自动可用。

ShapedClock::ShapedClock(QWidget *parent)
    : QWidget(parent, Qt::FramelessWindowHint | Qt::WindowSystemMenuHint)
{
    setAttribute(Qt::WA_TranslucentBackground);
    QTimer *timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, QOverload<>::of(&ShapedClock::update));
    timer->start(1000);

    QAction *quitAction = new QAction(tr("E&xit"), this);
    quitAction->setShortcut(tr("Ctrl+Q"));
    connect(quitAction, &QAction::triggered, qApp, &QCoreApplication::quit);
    addAction(quitAction);

    setContextMenuPolicy(Qt::ActionsContextMenu);
    setToolTip(tr("Drag the clock with the left mouse button.\n"
                  "Use the right mouse button to open a context menu."));
    setWindowTitle(tr("Shaped Analog Clock"));
}

我们通过设置控件属性 Qt::WA_TranslucentBackground 来请求一个透明窗口。我们通过设置控件的 Qt::FramelessWindowHint 标志来告知窗口管理器不要用窗口框架装饰控件。因此,我们需要提供一种方法,让用户可以围绕屏幕移动时钟。

鼠标按钮事件被传送到 mousePressEvent() 处理器。

void ShapedClock::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        dragPosition = event->globalPosition().toPoint() - frameGeometry().topLeft();
        event->accept();
    }
}

如果鼠标左键在控件上按下,我们记录控件框架左上角(即使隐藏)与鼠标单击点之间的全局(屏幕)坐标位移。如果用户在按下左键的同时移动鼠标,将使用这个位移。由于我们处理了这个事件,我们通过调用它的 accept() 函数来接受它。

当鼠标在部件上移动时,会调用 mouseMoveEvent() 处理器。

void ShapedClock::mouseMoveEvent(QMouseEvent *event)
{
    if (event->buttons() & Qt::LeftButton) {
        move(event->globalPosition().toPoint() - dragPosition);
        event->accept();
    }
}

如果在鼠标移动时按下左键,部件的左上角将被移动到通过从全局坐标系中当前光标位置减去 dragPosition 所得到的点。如果我们拖动部件,我们也会接收该事件。

paintEvent() 函数主要与在 Analog Clock 示例中描述的相同。唯一的增加是我们使用 QPainter::drawEllipse() 绘制一个圆形钟面。我们将画笔的不透明度降低到 90%,并使用调色板的自定义背景色。

void ShapedClock::paintEvent(QPaintEvent *)
{
    static const QPoint hourHand[4] = {
        QPoint(5, 14),
        QPoint(-5, 14),
        QPoint(-4, -71),
        QPoint(4, -71)
    };
    static const QPoint minuteHand[4] = {
        QPoint(4, 14),
        QPoint(-4, 14),
        QPoint(-3, -89),
        QPoint(3, -89)
    };
    static const QPoint secondsHand[4] = {
       QPoint(1, 14),
       QPoint(-1, 14),
       QPoint(-1, -89),
       QPoint(1, -89)
    };

    const QColor hourColor(palette().color(QPalette::Text));
    const QColor minuteColor(palette().color(QPalette::Text));
    const QColor secondsColor(palette().color(QPalette::Accent));

    int side = qMin(width(), height());
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.translate(width() / 2, height() / 2);
    painter.scale(side / 200.0, side / 200.0);

    painter.setPen(Qt::NoPen);
    painter.setBrush(palette().window());
    painter.setOpacity(0.9);
    painter.drawEllipse(QPoint(0, 0), 98, 98);
    painter.setOpacity(1.0);

    QTime time = QTime::currentTime();
    painter.setPen(Qt::NoPen);
    painter.setBrush(hourColor);

    painter.save();
    painter.rotate(30.0 * ((time.hour() + time.minute() / 60.0)));
    painter.drawConvexPolygon(hourHand, 4);
    painter.restore();

    for (int i = 0; i < 12; ++i) {
        painter.drawRect(73, -3, 16, 6);
        painter.rotate(30.0);
    }

    painter.setBrush(minuteColor);

    painter.save();
    painter.rotate(6.0 * time.minute());
    painter.drawConvexPolygon(minuteHand, 4);
    painter.restore();

    painter.setBrush(secondsColor);

    painter.save();
    painter.rotate(6.0 * time.second());
    painter.drawConvexPolygon(secondsHand, 4);
    painter.drawEllipse(-3, -3, 6, 6);
    painter.drawEllipse(-5, -68, 10, 10);
    painter.restore();

    painter.setPen(minuteColor);

    for (int j = 0; j < 60; ++j) {
        painter.drawLine(92, 0, 96, 0);
        painter.rotate(6.0);
    }
}

最后,我们为部件实现了 sizeHint(),以便在第一次显示时给出一个合理的默认大小。

QSize ShapedClock::sizeHint() const
{
    return QSize(200, 200);
}

示例项目 @ code.qt.io

© 2024 Qt 公司。本文件中包含的文档贡献归各自所有者所有。提供的文档在 Free Software Foundation 发布的 GNU 自由文档许可协议 1.3 版 的条款下获得许可。Qt 及其相关标志是芬兰的 Qt 公司和/或全球其他地区的商标。所有其他商标归各自所有者所有。