首页 >> 大全

qt-渲染原理

2023-10-19 大全 32 作者:考证青年

决定了各种控件在不同OS平台(win10,apple,vista,xp)等基本的样式;它的继承类实现了相应的接口使得在不同操作平台上观感,细节大不相同;也就是说,的派生类能够管理到控件的整个绘制过程

Qt 包含一组 子类,它们模拟 Qt 支持的不同平台的样式(、 等)。 默认情况下,这些样式内置在 Qt GUI 模块中。 样式也可以作为插件使用。

Qt 的内置小部件使用 来执行几乎所有的绘图,确保它们看起来与等效的原生小部件完全一样。 下图显示了九种不同样式的 。

设置样式

可以使用 ::() 函数设置整个应用程序的样式。 它也可以由应用程序的用户使用 -style 命令行选项指定:

 ./myapplication -style windows

如果没有指定样式,Qt 会为用户的平台或桌面环境选择最合适的样式。

也可以使用 ::() 函数在单个小部件上设置样式。

开发风格感知的自定义小部件

如果您正在开发自定义小部件并希望它们在所有平台上都看起来不错,您可以使用 函数来执行小部件绘制的部分内容,例如 ()、()、()、() 和 ( )。

大多数 绘图函数采用四个参数:

例如,如果你想在你的小部件上绘制一个焦点矩形,你可以这样写:

 void MyWidget::paintEvent(QPaintEvent * /* event */){QPainter painter(this);QStyleOptionFocusRect option;option.initFrom(this);option.backgroundColor = palette().color(QPalette::Background);style()->drawPrimitive(QStyle::PE_FrameFocusRect, &option, &painter, this);}

从 获取渲染图形元素所需的所有信息。 小部件作为最后一个参数传递,以防样式需要它来执行特殊效果(例如 macOS 上的动画默认按钮),但这不是强制性的。 事实上,通过正确设置 ,您可以使用 在任何绘图设备上绘制,而不仅仅是小部件。

为可以绘制的各种类型的图形元素提供了各种子类。 例如, 需要一个 t 参数。

为了确保绘图操作尽可能快, 及其子类具有公共数据成员。 有关如何使用它的详细信息,请参阅 类文档。

为方便起见,Qt 提供了 类,它结合了 、 和 。 这使得可以写

     QStylePainter painter(this);...painter.drawPrimitive(QStyle::PE_FrameFocusRect, option);

创建自定义样式

您可以通过创建自定义样式为您的应用程序创建自定义外观。创建自定义样式有两种方法。在静态方法中,您可以选择现有的 类,对其进行子类化,然后重新实现虚函数以提供自定义行为,或者从头开始创建整个 类。在动态方法中,您可以在运行时修改系统样式的行为。下面介绍静态方法。 中描述了动态方法。

静态方法的第一步是选择 Qt 提供的样式之一,您将从中构建您的自定义样式。您对 类的选择将取决于哪种风格最类似于您想要的风格。您可以用作基础的最通用类是 (不是 )。这是因为 Qt 要求它的样式是 。

根据要更改的基本样式的哪些部分,您必须重新实现用于绘制界面这些部分的函数。为了说明这一点,我们将修改 绘制的旋转框箭头的外观。箭头是由 () 函数绘制的原始元素,因此我们需要重新实现该函数。我们需要以下类声明:

 class CustomStyle : public QProxyStyle{Q_OBJECTpublic:CustomStyle();~CustomStyle() {}void drawPrimitive(PrimitiveElement element, const QStyleOption *option,QPainter *painter, const QWidget *widget) const override;};

为了绘制向上和向下箭头, 使用 和 原语元素。 以下是如何重新实现 () 函数以不同方式绘制它们:

 void CustomStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option,QPainter *painter, const QWidget *widget) const{if (element == PE_IndicatorSpinUp || element == PE_IndicatorSpinDown) {QPolygon points(3);int x = option->rect.x();int y = option->rect.y();int w = option->rect.width() / 2;int h = option->rect.height() / 2;x += (option->rect.width() - w) / 2;y += (option->rect.height() - h) / 2;if (element == PE_IndicatorSpinUp) {points[0] = QPoint(x, y + h);points[1] = QPoint(x + w, y + h);points[2] = QPoint(x + w / 2, y);} else { // PE_SpinBoxDownpoints[0] = QPoint(x, y);points[1] = QPoint(x + w, y);points[2] = QPoint(x + w / 2, y + h);}if (option->state & State_Enabled) {painter->setPen(option->palette.mid().color());painter->setBrush(option->palette.buttonText());} else {painter->setPen(option->palette.buttonText().color());painter->setBrush(option->palette.mid());}painter->drawPolygon(points);} else {QProxyStyle::drawPrimitive(element, option, painter, widget);}}

请注意,我们不使用 参数,只是将它传递给 ::() 函数。 如前所述,关于要绘制什么以及应该如何绘制的信息由 对象指定,因此无需询问小部件。

如果您需要使用 参数来获取附加信息,请在使用前小心确保它不为 0 并且类型正确。 例如:

     const QSpinBox *spinBox = qobject_cast(widget);if (spinBox) {...}

在实现自定义样式时,您不能仅仅因为枚举值称为 或 就假定小部件是 。

示例的文档更详细地介绍了这个主题。

警告:自定义 子类目前不支持 Qt 样式表。 我们计划在未来的某个版本中解决这个问题。

类封装了 GUI 的通用外观。

这个抽象类实现了作为 Qt 的一部分提供和交付的所有 GUI 样式所共有的一些小部件的外观和感觉。

由于 继承了 ,它的所有功能都完整地记录在 文档中。

qt实现了跨平台,在不同的平台下,控件的样式绘制成该平台的风格。在qt的内部,如平台,(路径:src\\\.h) 继承 ,完成风格的绘制,同理mac,linux

类是一个抽象基类,它封装了 GUI 的外观。 使用 () 函数和一个标识样式的键创建一个 对象。 样式要么是内置的,要么是从样式插件动态加载的(参见 )。

可以使用 keys() 函数检索有效密钥。 通常它们包括“”和“”。 根据平台,“”和“”可能可用。 请注意,键不区分大小写。

    QStringList key_list =  QStyleFactory::keys();qDebug() << key_list;//"Windows", "WindowsXP", "WindowsVista", "Fusion"

类提供了当前可应用的所有风格实现,在系统上我获得如下几种风格:

我们可以通过::keys()和::()来获取这些可用的风格并且设置到需要的上用以改变GUI风格。

Demo

以为例:

首先我们要来讲讲GUI控件结构,这里以为例:

一个完整的控件由一种或多种GUI元素构成:

canvas渲染原理__qt信号槽机制原理

sub . on where the user them with the mouse and which keys are .

(简称CC)包含子控件。根据用户对鼠标和键盘的不同处理,CC控件的表现也不同。上图中的仅包含一个CC控件,该复杂控件又包含三个子控件(SC,Sub )、、。

A an or to the user.

控件元素与用户交互相关,例如、等等。只有一个用以在左侧展示当前选中或者正在编辑的文字。

are GUI that are and often used by .

主元素代表那些公共的GUI元素,主要用于GUI控件复用。例如这个主元素就进场被多种控件用来绘制输入焦点。包含两个主元素n、。

是一套抽象接口,它定义了实现界面控件外观的一系列api并且不能用来被实例化:

这个自定义的样式分为两部分,箭头区域和非箭头区域。非箭头区域包含和up。由于不负责绘制下拉框(由绘制),我们只能更改下拉框的位置和大小(这里我们不做改变)。 箭头区域包含背景区和n。

箭头区域我们用一个辐射渐变来绘制背景,并且在鼠标Hover或者按下的时候更改渐变的颜色来重绘,中间的下拉箭头我们复用的实现来完成。

.h

#ifndef CUSTOMESTYLE_H
#define CUSTOMESTYLE_H#include class CustomeStyle : public QProxyStyle
{
public:virtual void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const;virtual void drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const;virtual void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const;//  not used in our demovirtual QSize sizeFromContents(ContentsType type, const QStyleOption *option, const QSize &size, const QWidget *widget) const;virtual QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, const QWidget *widget) const;virtual QRect subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const;private:void drawArrowArea(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const;private:static bool m_arrowAreaHovered;
};#endif // CUSTOMESTYLE_H

#include "CustomeStyle.h"#include 
#include 
#include 
#include 
#include bool CustomeStyle::m_arrowAreaHovered = false;void CustomeStyle::drawComplexControl(QStyle::ComplexControl control,const QStyleOptionComplex *option,QPainter *painter,const QWidget *widget) const
{switch (control) {case CC_ComboBox:{drawArrowArea(option, painter, widget);drawControl(CE_ComboBoxLabel, option, painter, widget);break;}default:QProxyStyle::drawComplexControl(control, option, painter, widget);}
}void CustomeStyle::drawArrowArea(const QStyleOptionComplex *option,QPainter *painter,const QWidget *widget) const
{QRect arrowBoxRect = option->rect;arrowBoxRect.adjust(option->rect.width() * 0.8, 0, 0, 0);auto arrowAreaColor = Qt::darkCyan;m_arrowAreaHovered = arrowBoxRect.contains(widget->mapFromGlobal(QCursor::pos()));if (option->state & State_MouseOver && m_arrowAreaHovered)arrowAreaColor = Qt::cyan;else if (option->state & State_On && m_arrowAreaHovered)arrowAreaColor = Qt::darkMagenta;QRadialGradient gradient(arrowBoxRect.center(),arrowBoxRect.width());gradient.setColorAt(0.0, Qt::gray);gradient.setColorAt(1.0, arrowAreaColor);painter->fillRect(arrowBoxRect, QBrush(gradient));auto arrowDownOption = *option;auto adjustPixel = arrowBoxRect.width() * 0.2;arrowDownOption.rect = arrowBoxRect.adjusted(adjustPixel,adjustPixel,-adjustPixel,-adjustPixel);drawPrimitive(PE_IndicatorArrowDown, &arrowDownOption, painter, widget);
}void CustomeStyle::drawControl(QStyle::ControlElement element,const QStyleOption *option,QPainter *painter,const QWidget *widget) const
{switch (element) {case CE_ComboBoxLabel:{auto comboBoxOption = qstyleoption_cast(option);if (comboBoxOption == nullptr)return;QColor gradientColors[] = {Qt::yellow,Qt::green,Qt::blue,Qt::red};QColor penColor = Qt::white;if (option->state & State_MouseOver && !m_arrowAreaHovered) {for (auto& color : gradientColors)color.setAlpha(80);penColor.setAlpha(80);} else if (option->state & State_On && !m_arrowAreaHovered) {for (auto& color : gradientColors)color = color.darker(300);penColor = penColor.darker(300);}QRect labelRect = comboBoxOption->rect;labelRect.adjust(0, 0, -(labelRect.width() * 0.2), 0);QLinearGradient linearGradient(labelRect.topLeft(), labelRect.bottomRight());for (int i = 0; i < 4; ++i) {linearGradient.setColorAt(0.25 *i, gradientColors[i]);}painter->fillRect(labelRect, QBrush(linearGradient));painter->setPen(QPen(penColor));painter->drawText(labelRect, comboBoxOption->currentText, QTextOption(Qt::AlignCenter));break;}default:QProxyStyle::drawControl(element, option, painter, widget);}
}void CustomeStyle::drawPrimitive(QStyle::PrimitiveElement element,const QStyleOption *option,QPainter *painter,const QWidget *widget) const
{switch (element) {default:QProxyStyle::drawPrimitive(element, option, painter, widget);}
}QSize CustomeStyle::sizeFromContents(QStyle::ContentsType type,const QStyleOption *option,const QSize &size,const QWidget *widget) const
{switch (type) {default:return QProxyStyle::sizeFromContents(type, option, size, widget);}
}QRect CustomeStyle::subControlRect(QStyle::ComplexControl cc,const QStyleOptionComplex *opt,QStyle::SubControl sc,const QWidget *widget) const
{switch (cc) {//  sub controls used by ComboBoxcase SC_ComboBoxFrame:{//  An QEditLine is created according to this return value.//  In this Qt version, `opt` is an entire widget rectangle.QRect labelRect = opt->rect;return labelRect.adjusted(1, 1, -(labelRect.width() * 0.2)-1, -1);}//  this value is not used by QStyle!case SC_ComboBoxListBoxPopup:   return QRect(0, 0, 0, 0);case SC_ComboBoxEditField:      return QRect(0, 0, 0, 0);case SC_ComboBoxArrow:          return QRect(0, 0, 0, 0);default:return QProxyStyle::subControlRect(cc, opt, sc, widget);}
}QRect CustomeStyle::subElementRect(QStyle::SubElement element,const QStyleOption *option,const QWidget *widget) const
{return QProxyStyle::subElementRect(element, option, widget);
}

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "CustomeStyle.h"#include MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);auto nativeStyles = QStyleFactory::keys();for (auto it = nativeStyles.begin(); it != nativeStyles.end(); ++it) {ui->styleComboBox->addItem(*it);}ui->styleComboBox->addItem("CustomeStyle");connect(ui->enableEditCheckBox, &QCheckBox::clicked,[this](bool checked) {ui->styleComboBox->setEditable(checked);});connect(ui->applyStyleButton, &QPushButton::released,[this]() {auto styleType = ui->styleComboBox->currentText();auto app = static_cast(QCoreApplication::instance());if (styleType == "CustomeStyle")app->setStyle(new CustomeStyle());elseapp->setStyle(QStyleFactory::create(styleType));});ui->styleComboBox->setCurrentIndex(0);ui->applyStyleButton->released();
}MainWindow::~MainWindow()
{delete ui;
}

优点:

缺点:

Qt 绘制引擎

绘制引擎是我们开发者用的一些常见的接口。光栅化引擎是绘制引擎一部分的实现。

一个小例子:假如要画一条线,需要哪几步

要把画一条线总共需要几个角色(要把大象装冰箱总共分几步)

第一步,需要一个人(画线的方法)

第二步,需要一个笔。

第三步,需要一张纸。

换成Qt来画线的话那就是

第一步,需要一个光栅化引擎()

第二步,需要一个笔()

第三步,需要一个设备()

所以Qt给我们暴露的接口就是这三个

Qt 绘制引擎简介

,,组成了Qt绘制界面的基础。

Qt 的绘画系统可以使用相同的 API 在屏幕和打印设备上绘画,并且主要基于、和类。

用于执行绘图操作,是二维空间的抽象,可以使用进行绘制,提供了用来在不同类型的设备上绘制的接口。类由和内部使用,并且对应用程序程序员隐藏,除非他们创建自己的设备类型。

这种方法的主要好处是所有绘画都遵循相同的绘画管道,从而可以轻松添加对新功能的支持并为不受支持的功能提供默认实现。

三个类的官方说明如下

类为提供了如何在指定绘图设备上(译者注:一般为的派生)绘制的一些抽象的方法。

Qt为不同的后端提供了一些预设实现的

译者注:提供一个更加好理解的说法。的Qt实现一般默认调用的是的方法。

现在主要提供的是Qt自带的光栅化引擎(),Qt在他所有支持的平台上,提供了一个功能完备的光栅化引擎。

在, X11 和 macOS平台上,Qt自带的光栅化引擎都是这个基础类的默认的绘制方法的提供者,亦或是的绘制方法的提供者。当然有一些特殊的绘制设备的绘制引擎不提供对应的绘制方法,这时候就会调用默认的光栅化引擎。

当然,我们也为(可通过访问)跟打印(允许在对象上绘制,用于生成pdf之类的)也提供了对应的的实现。

译者注: ,,三个是相辅相成的。

如果你想使用绘制自定义的后端(译者注:这里可以理解为)。你可以继承,并实现其所有的虚函数。然后子类化并且实现它的纯虚成员函数(::())。

由创建,并维护其生命周期。

另请参见,::()和Paint

绘画设备是可以使用 绘制的二维空间的抽象。它的默认坐标系的原点位于左上角。 X向右增加,Y向下增加。单位是一个像素。

类是可绘制对象的基类,即可以在任何子类上绘制。的绘图功能由、、、、和 实现。

类是Qt 模块中用户界面元素的基类。它从窗口系统接收鼠标、键盘和其他事件,并在屏幕上绘制自己的表示。

类提供了一种独立于硬件的图像表示,它针对 I/O 以及直接像素访问和操作而设计和优化。支持多种图像格式,包括单色、8 位、32 位和 alpha 混合图像。

使用作为绘图设备的一个优点是可以以独立于平台的方式保证任何绘图操作的像素准确性。另一个好处是可以在当前 GUI 线程之外的另一个线程中执行绘制。

qt信号槽机制原理_canvas渲染原理_

类是一种离屏图像表示,其设计和优化用于在屏幕上显示图像。与不同,像素图中的像素数据是内部的,由底层窗口系统管理,即只能通过函数或将转换为来访问像素。

为了使用优化绘图,Qt 提供了类,该类可用于存储生成成本高昂的临时像素图,而无需使用超过缓存限制的存储空间。

Qt 还提供便利类,继承。保证单色(1 位深度)像素图,主要用于创建自定义和对象,构造对象。

绘制设备

如前所述,Qt 提供的类使得在 Qt 应用程序中使用 变得容易。例如,启用 API 以使用进行渲染。

类是记录和重放命令的绘图设备。图片以独立于平台的格式将画家命令序列化到 IO 设备。也与分辨率无关,即可以显示在看起来相同的不同设备(例如 svg、pdf、ps、打印机和屏幕)上。

自定义后端

可以通过从类派生并重新实现虚拟:: () 函数来告诉应该使用哪个绘图引擎在此特定设备上绘制来实现对新后端的支持。为了真正能够在设备上绘图,此绘制引擎必须是通过派生自类创建的自定义绘制引擎。

提供了高度优化的功能来完成大多数绘图 GUI 程序所需的工作。 它可以绘制从简单的线条到复杂的形状(如馅饼和弦)的所有内容。 它还可以绘制对齐的文本和像素图。 通常,它在“自然”坐标系中绘制,但它也可以进行视图和世界变换。 可以对任何继承 类的对象进行操作。

的常见用途是在小部件的绘制事件中:构造和自定义(例如设置钢笔或画笔)画家。 然后画。 记得在绘制后销毁 对象。

绘制过程

所有的绘制在中间都必然要经过。只不过是它的一个派生,

现在主要提供的是Qt自带的光栅化引擎(),Qt在他所有支持的平台上,提供了一个功能完备的光栅化引擎。

在, X11 和 macOS平台上,Qt自带的光栅化引擎都是这个基础类的默认的绘制方法的提供者,亦或是的绘制方法的提供者。当然有一些特殊的绘制设备的绘制引擎不提供对应的绘制方法,这时候就会调用默认的光栅化引擎。而 根据所要绘制的内容,来区分绘制逻辑,比方说涂色采用填充 、统一刷新的方式;字体绘制要调用字体图元相关绘制逻辑等等。

QPaintEngine *QImage::paintEngine() const{if (!d)return 0;if (!d->paintEngine) {QPaintDevice *paintDevice = const_cast(this);QPaintEngine *paintEngine = 0;QPlatformIntegration *platformIntegration = QGuiApplicationPrivate::platformIntegration();if (platformIntegration)paintEngine = platformIntegration->createImagePaintEngine(paintDevice);d->paintEngine = paintEngine ? paintEngine : new QRasterPaintEngine(paintDevice);}return d->paintEngine;}

只是的派生类。我也说 平台下默认的 Qt 绘制是使用指令集的。原因就在于默认条件下,绝大部分的是选择用

所有的表层绘制都要经过绘制引擎来向下传递绘制信息。这是 Qt 作为一个高级框架的闪光点,在其他的 Qt 模块也有类似发现,比如控件的绘制上。这样看来 Qt 这个框架能给我们的,除了代码逻辑本身,还有设计。

在 平台 默认的 Qt 绘制,最终到底层是直接调用指令集指令的而不是 API ,

这也是 Qt 的性能的保障。如果想探究指令集部分的使用,需要到源码目录 \src\gui\:

the theme:

路径 :D:\Qt\5.9.8\Src\\src\gui\\.cpp

在函数 void (const &, const &, const &, int &argc, char **argv)中确定了平台的主题:调试以平台为例

    // Create the platform theme:// 1) Fetch the platform name from the environment if present.QStringList themeNames;if (!platformThemeName.isEmpty())themeNames.append(platformThemeName);// 2) Ask the platform integration for a list of theme namesthemeNames += QGuiApplicationPrivate::platform_integration->themeNames();// 3) Look for a theme plugin.for (const QString &themeName : qAsConst(themeNames)) {QGuiApplicationPrivate::platform_theme = QPlatformThemeFactory::create(themeName, platformPluginPath);if (QGuiApplicationPrivate::platform_theme)break;}// 4) If no theme plugin was found ask the platform integration to// create a themeif (!QGuiApplicationPrivate::platform_theme) {for (const QString &themeName : qAsConst(themeNames)) {QGuiApplicationPrivate::platform_theme = QGuiApplicationPrivate::platform_integration->createPlatformTheme(themeName);if (QGuiApplicationPrivate::platform_theme)break;}// No error message; not having a theme plugin is allowed.}// 5) Fall back on the built-in "null" platform theme.if (!QGuiApplicationPrivate::platform_theme)QGuiApplicationPrivate::platform_theme = new QPlatformTheme;

1) Fetch the name from the if .

首先读取环境变量,确定主题,跳过

2) Ask the for a list of theme names

+= te::->();

QStringList QWindowsIntegration::themeNames() const
{return QStringList(QLatin1String(QWindowsTheme::name));
}

在平台上是,返回的是中的name,该name是一个常量:const char *::name = "";(路径D:\Qt\5.9.8\Src\\src\\\\.cpp)

3) Look for a theme

查找主题插件,没有找到

4) If no theme was found ask the to a theme

平台下返回

QPlatformTheme *QWindowsIntegration::createPlatformTheme(const QString &name) const
{if (name == QLatin1String(QWindowsTheme::name))return new QWindowsTheme;return QPlatformIntegration::createPlatformTheme(name);
}

5) Fall back on the built-in "null" theme.

兜底策略,如果前几步都没有找到一个主题,则使用默认

的绘制过程

首先会走到:: 绘制

/*!\reimp
*/
void QPushButton::paintEvent(QPaintEvent *)
{QStylePainter p(this);QStyleOptionButton option;initStyleOption(&option);p.drawControl(QStyle::CE_PushButton, option);
}

是风格的设置类,定义了最基本的绘制控件所需的信息。

绘制不同控件时,控件所使用的设置类继承,且值不同。

绘制按钮的风格设置类继承时,Type = 表明是要绘制按钮,且添加了一些按钮才有的属性。 类用于描述绘制按钮的参数。

之后:: 会根据当前的平台所创建的不同的style派生类创建当前平台的按钮风格,例如下

最后 调用(Qt自带的光栅化引擎)将图片渲染出来,其中的渲染过程中调用了指令集,提高了效率:

参考文档

#-for-

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了