摇摆小部件示例#

此示例展示如何以两种不同的方式与自定义小部件交互

  • 从 C++ 示例的完整 Python 翻译

  • 从 C++ 文件生成的 Python 绑定

原始示例包含三个不同的文件

  • main.cpp/h,翻译为 main.py

  • dialog.cpp/h,翻译为 dialog.py

  • wigglywidget.cpp/h,翻译为 wigglywidget.py,但同时也保持原样,以启用通过 Shiboken 的绑定生成。

dialog.py 文件中,您将找到两个与前面描述的两种方法相关的导入:

# Python translated file
from wigglywidget import WigglyWidget

# Binding module create with Shiboken
from wiggly import WigglyWidget

构建绑定的步骤#

最重要的文件是

  • bindings.xml,用于指定我们想要从 C++ 暴露给 Python 的类

  • bindings.h,用于包含我们想要暴露的类头文件

  • CMakeList.txt,包含构建共享库(DLL 或 dylib)的所有指令

  • pyside_config.py,位于 utils 文件夹中的一级目录中,用于获取 Shiboken 和 PySide 的路径。

现在创建一个 build/ 目录,然后在其中运行 cmake 以使用提供的 CMakeLists.txt

macOS/Linux

cd ~/pyside-setup/examples/widgetbinding

Windows

cd C:\pyside-setup\examples\widgetbinding
mkdir build
cd build
cmake -S.. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release
ninja
ninja install
cd ..

然后可以通过以下方式运行最终示例:

python main.py

你应该看到两个相同的自定义小部件,一个是 Python 翻译版本,另一个是 C++ 版本。

结语#

由于本示例是通过混合 scriptableapplicationsamplebinding 示例的概念生成的,因此您可以补充 README 中的这些目录中的示例。

下载示例

// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

#ifndef BINDINGS_H
#define BINDINGS_H
#include "wigglywidget.h"
#endif // BINDINGS_H
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

from PySide6.QtWidgets import QDialog, QLineEdit, QVBoxLayout

# Python binding from the C++ widget
from wiggly import WigglyWidget as WigglyWidgetCPP

# Python-only widget
from wigglywidget import WigglyWidget as WigglyWidgetPY


class Dialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        wiggly_widget_py = WigglyWidgetPY(self)
        wiggly_widget_cpp = WigglyWidgetCPP(self)
        lineEdit = QLineEdit(self)

        layout = QVBoxLayout(self)
        layout.addWidget(wiggly_widget_py)
        layout.addWidget(wiggly_widget_cpp)
        layout.addWidget(lineEdit)
        lineEdit.setClearButtonEnabled(True)
        wiggly_widget_py.running = True
        wiggly_widget_cpp.setRunning(True)

        lineEdit.textChanged.connect(wiggly_widget_py.setText)
        lineEdit.textChanged.connect(wiggly_widget_cpp.setText)
        lineEdit.setText("🖖 Hello world!")

        self.setWindowTitle("Wiggly")
        self.resize(360, 145)
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

#ifndef MACROS_H
#define MACROS_H

#include <QtCore/qglobal.h>

// Export symbols when creating .dll and .lib, and import them when using .lib.
#if BINDINGS_BUILD
#    define BINDINGS_API Q_DECL_EXPORT
#else
#    define BINDINGS_API Q_DECL_IMPORT
#endif

#endif // MACROS_H
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import sys

from PySide6.QtWidgets import QApplication

from dialog import Dialog

if __name__ == "__main__":
    app = QApplication()
    w = Dialog()
    w.show()
    sys.exit(app.exec())
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
from wigglywidget import WigglyWidget

# Set PYSIDE_DESIGNER_PLUGINS to point to this directory and load the plugin


TOOLTIP = "A cool wiggly widget (Python)"
DOM_XML = """
<ui language='c++'>
    <widget class='WigglyWidget' name='wigglyWidget'>
        <property name='geometry'>
            <rect>
                <x>0</x>
                <y>0</y>
                <width>400</width>
                <height>200</height>
            </rect>
        </property>
        <property name='text'>
            <string>Hello, world</string>
        </property>
    </widget>
</ui>
"""

if __name__ == '__main__':
    QPyDesignerCustomWidgetCollection.registerCustomWidget(WigglyWidget, module="wigglywidget",
                                                           tool_tip=TOOLTIP, xml=DOM_XML)
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

#include "wigglywidget.h"

#include <QtGui/QFontMetrics>
#include <QtGui/QPainter>
#include <QtCore/QTimerEvent>

//! [0]
WigglyWidget::WigglyWidget(QWidget *parent)
    : QWidget(parent)
{
    setBackgroundRole(QPalette::Midlight);
    setAutoFillBackground(true);

    QFont newFont = font();
    newFont.setPointSize(newFont.pointSize() + 20);
    setFont(newFont);
}
//! [0]

//! [1]
void WigglyWidget::paintEvent(QPaintEvent * /* event */)
//! [1] //! [2]
{
    if (m_text.isEmpty())
        return;
    static constexpr int sineTable[16] = {
        0, 38, 71, 92, 100, 92, 71, 38, 0, -38, -71, -92, -100, -92, -71, -38
    };

    QFontMetrics metrics(font());
    int x = (width() - metrics.horizontalAdvance(m_text)) / 2;
    int y = (height() + metrics.ascent() - metrics.descent()) / 2;
    QColor color;
//! [2]

//! [3]
    QPainter painter(this);
//! [3] //! [4]
    int offset = 0;
    const auto codePoints = m_text.toUcs4();
    for (char32_t codePoint : codePoints) {
        const int index = (m_step + offset++) % 16;
        color.setHsv((15 - index) * 16, 255, 191);
        painter.setPen(color);
        QString symbol = QString::fromUcs4(&codePoint, 1);
        const int dy = (sineTable[index] * metrics.height()) / 400;
        painter.drawText(x, y - dy, symbol);
        x += metrics.horizontalAdvance(symbol);
    }
}
//! [4]

//! [5]
void WigglyWidget::timerEvent(QTimerEvent *event)
//! [5] //! [6]
{
    if (event->timerId() == m_timer.timerId()) {
        ++m_step;
        update();
    } else {
        QWidget::timerEvent(event);
    }
//! [6]
}

QString WigglyWidget::text() const
{
    return m_text;
}

void WigglyWidget::setText(const QString &newText)
{
    m_text = newText;
}

bool WigglyWidget::isRunning() const
{
    return m_timer.isActive();
}

void WigglyWidget::setRunning(bool r)
{
    if (r == isRunning())
        return;
    if (r)
        m_timer.start(60, this);
    else
        m_timer.stop();
}
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

#ifndef WIGGLYWIDGET_H
#define WIGGLYWIDGET_H

#include "macros.h"

#include <QtWidgets/QWidget>
#include <QtCore/QBasicTimer>

//! [0]
class BINDINGS_API WigglyWidget : public QWidget
{
    Q_OBJECT
    Q_PROPERTY(bool running READ isRunning WRITE setRunning)
    Q_PROPERTY(QString text READ text WRITE setText)

public:
    WigglyWidget(QWidget *parent = nullptr);

    QString text() const;
    bool isRunning() const;

public slots:
    void setText(const QString &newText);
    void setRunning(bool r);

protected:
    void paintEvent(QPaintEvent *event) override;
    void timerEvent(QTimerEvent *event) override;

private:
    QBasicTimer m_timer;
    QString m_text;
    int m_step = 0;
};
//! [0]

#endif
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

from PySide6.QtCore import QBasicTimer, Property
from PySide6.QtGui import QColor, QFontMetrics, QPainter, QPalette
from PySide6.QtWidgets import QWidget


class WigglyWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._step = 0
        self._text = ""
        self.setBackgroundRole(QPalette.Midlight)
        self.setAutoFillBackground(True)

        new_font = self.font()
        new_font.setPointSize(new_font.pointSize() + 20)
        self.setFont(new_font)
        self._timer = QBasicTimer()

    def isRunning(self):
        return self._timer.isActive()

    def setRunning(self, r):
        if r == self.isRunning():
            return
        if r:
            self._timer.start(60, self)
        else:
            self._timer.stop()

    def paintEvent(self, event):
        if not self._text:
            return

        sineTable = [0, 38, 71, 92, 100, 92, 71, 38, 0, -38, -71, -92, -100,
                     -92, -71, -38]

        metrics = QFontMetrics(self.font())
        x = (self.width() - metrics.horizontalAdvance(self.text)) / 2
        y = (self.height() + metrics.ascent() - metrics.descent()) / 2
        color = QColor()

        with QPainter(self) as painter:
            for i in range(len(self.text)):
                index = (self._step + i) % 16
                color.setHsv((15 - index) * 16, 255, 191)
                painter.setPen(color)
                dy = (sineTable[index] * metrics.height()) / 400
                c = self._text[i]
                painter.drawText(x, y - dy, str(c))
                x += metrics.horizontalAdvance(c)

    def timerEvent(self, event):
        if event.timerId() == self._timer.timerId():
            self._step += 1
            self.update()
        else:
            QWidget.timerEvent(event)

    def text(self):
        return self._text

    def setText(self, text):
        self._text = text

    running = Property(bool, isRunning, setRunning)
    text = Property(str, text, setText)