Qt为程序员提供了非常丰富的功能,但是小部件的集合是有限的。 如果没有合适的选择,则必须自己绘制一些东西。 最简单的方法-使用现成的图片-存在严重的缺陷:需要将图像存储在文件或资源中,可伸缩性问题以及图像格式的可移植性。 下面描述了矢量图形原理的使用,而不使用实际的矢量图像。
前言
一切始于这样的事实,即一次需要指示一位符号。 某些应用程序在某些端口上接收到一些数据,必须拆开包装并将其显示在屏幕上。 同时以某种方式模仿熟悉的仪表板会很好。 为了显示数字数据,Qt提供了“开箱即用”的QLCDNumber类,类似于熟悉的七段指示器,但是在单个灯中看不到某些东西。
为此目的使用标志(它们是复选框)和开关(它们是单选按钮)是不好的,下面列出了原因:
- 这在语义上是错误的。 按钮-它们是按钮,用于用户输入,而不是向用户显示任何内容。
- 这意味着第二点:用户努力在这些按钮上进行戳戳。 如果同时信息更新不是特别快,则指示将隐藏,并且用户将恶作剧地报告程序故障。
- 如果锁定按钮以按下(setEnabled(false)),则该按钮将变为难看的灰色。 我记得在版本6的Delphi中,耳朵有些假冒:您可以在面板上放置一个标志,并禁用面板的可用性,而不是该标志,然后该标志既不是灰色也不是活动的。 这个技巧在这里不起作用。
- 这些按钮具有输入焦点。 因此,如果窗口中有输入元素,并且用户使用Tab键沿着它们走动,则他将不得不沿着输出元素走动,这是不便且丑陋的。
- 最后,这些按钮看上去看上去并不美观,尤其是在七段式按钮旁边。
结论:您需要自己画一个灯泡。
选择的面粉
首先,我寻找现成的解决方案。 在那段时间里,当我使用Delphi时,您可以找到大量的成品零件,这些零件既来自严肃的公司,也来自业余制造商。 Qt对此有很多麻烦。 QWT有一些要素,但并非如此。 我根本没有看到业余爱好。 也许,如果您在Github上正确挖掘,可以找到一些东西,但是我自己可能会做得更快。
自制的第一件事是使用两个图像文件,分别打开和关闭灯光图像。 不好:
- 有必要找到精美的图片(或绘画,但我不是画家);
- 原则上的问题是:打结不好,甚至是图片,甚至躺在你的脚下;
- 它们必须存储在某个地方。 文件非常糟糕:意外删除-而且没有按钮。 资源比较好,但是我也觉得不可以。
- 没有可扩展性;
- 可定制性(例如颜色)只能通过添加文件来实现。 也就是说,资源密集且僵化。
从第一件事开始的第二件事是使用矢量图像而不是图片。 而且,Qt可以渲染SVG。 在这里搜索图片本身已经有点容易了:网络中的矢量图形课程很多,您可以找到更多或更少的适合自己的东西并使其适应您的需求。 但是问题仍然在于存储和自定义,并且渲染不是免费的资源。 便士,当然,但仍然...
第三个是第二个:您可以使用矢量图形原理绘制自绘图像! 文本格式的矢量图像文件指示绘制内容以及绘制方式。 我可以使用矢量教程指定相同的代码。 幸运的是,QPainter对象具有必要的工具:钢笔,画笔,渐变和绘制图元,甚至还有纹理填充。 是的,工具远非所有:没有遮罩,混合模式,但绝对不需要照片写实。
我在网上寻找了一些例子。 他上了第一课:“我们很容易绘制”网站上的“我们在Inkscape图形编辑器中绘制了一个按钮”。 本课中的按钮比按钮更像一个灯泡,非常适合我。 我正在草稿:Qt中的一个项目,而不是Inkscape。
羽毛测试
我正在创建一个新项目。 我选择项目rgbled的名称(因为我想做一个RGB LED之类的东西)及其路径。 我选择基类QWidget和名称RgbLed,但我拒绝创建表单文件。 默认情况下,启动后的项目将创建一个空窗口,但仍然没有兴趣。
图纸准备
有一个空白。 现在,您需要获取类的私有成员,这将确定图片的几何形状。 矢量图形的一个基本优点是它的可伸缩性,因此应该有最小数量的常数,并且它们只能设置比例。 尺寸将在resizeEvent()事件中重新计算,需要重新定义。
在所使用的图形教程中,尺寸随即以像素为单位指定。 我需要事先确定我将使用什么以及如何重新计数。
绘制的图片包含以下元素:
- 外圈(向外倾斜,部分凸边)
- 内圈(向内倾斜)
- LED灯罩,“玻璃”
- 玻璃边缘上的阴影
- 最高亮点
- 底部耀斑
同心圆,即除眩光以外的所有东西,都由中心的位置和半径确定。 眩光由中心,宽度和高度决定,X眩光中心的位置与整个图片X中心的位置一致。
要计算几何元素,您需要确定哪个更大-宽度或高度,因为灯泡是圆形的,并且必须适合边长等于两个维度中较小者的正方形。 因此,我将相应的私有成员添加到头文件中。
代号private: int height; int width; int minDim; int half; int centerX; int centerY; QRect drawingRect; int outerBorderWidth; int innerBorderWidth; int outerBorderRadius; int innerBorderRadius; int topReflexY; int bottomReflexY; int topReflexWidth; int topReflexHeight; int bottomReflexWidth; int bottomReflexHeight;
然后,我重新定义了调整窗口小部件大小时调用的受保护函数。
代号 protected: void resizeEvent(QResizeEvent *event); void RgbLed::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); this->height = this->size().height(); this->width = this->size().width(); this->minDim = (height > width) ? width : height; this->half = minDim / 2; this->centerX = width / 2; this->centerY = height / 2; this->outerBorderWidth = minDim / 10; this->innerBorderWidth = minDim / 14; this->outerBorderRadius = half - outerBorderWidth; this->innerBorderRadius = half - (outerBorderWidth + innerBorderWidth); this->topReflexY = centerY - (half - outerBorderWidth - innerBorderWidth) / 2; this->bottomReflexY = centerY + (half - outerBorderWidth - innerBorderWidth) / 2; this->topReflexHeight = half / 5; this->topReflexWidth = half / 3; this->bottomReflexHeight = half / 5; this->bottomReflexWidth = half / 3; drawingRect.setTop((height - minDim) / 2); drawingRect.setLeft((width - minDim) / 2); drawingRect.setHeight(minDim); drawingRect.setWidth(minDim); }
在此,计算出灯泡所在的正方形的一面,该正方形的中心,边缘的半径占据最大可能面积,边缘的宽度,边缘的外部应为直径的1/10和内部的1.14。 然后计算位于上,下半径中间的眩光的位置,用眼睛选择宽度和高度。
另外,在受保护的字段中,我将立即添加一组要使用的颜色。
代号 QColor ledColor; QColor lightColor; QColor shadowColor; QColor ringShadowDarkColor; QColor ringShadowMedColor; QColor ringShadowLightColor; QColor topReflexUpColor; QColor topReflexDownColor; QColor bottomReflexCenterColor; QColor bottomReflexSideColor;
顾名思义,这些是灯泡的颜色,阴影的亮部分,阴影的暗部分,灯泡周围的环形阴影的三种颜色以及眩光渐变的颜色。
颜色应该被初始化,以便我补充设计师的作品。
代号 RgbLed::RgbLed(QWidget *parent) : QWidget(parent), ledColor(Qt::green), lightColor(QColor(0xE0, 0xE0, 0xE0)), shadowColor(QColor(0x70, 0x70, 0x70)), ringShadowDarkColor(QColor(0x50, 0x50, 0x50, 0xFF)), ringShadowMedColor(QColor(0x50, 0x50, 0x50, 0x20)), ringShadowLightColor(QColor(0xEE, 0xEE, 0xEE, 0x00)), topReflexUpColor(QColor(0xFF, 0xFF, 0xFF, 0xA0)), topReflexDownColor(QColor(0xFF, 0xFF, 0xFF, 0x00)), bottomReflexCenterColor(QColor(0xFF, 0xFF, 0xFF, 0x00)), bottomReflexSideColor(QColor(0xFF, 0xFF, 0xFF, 0x70)) { }
同样,不要忘记在头文件中插入绘图时将需要的类的包含。
代号 #include <QPainter> #include <QPen> #include <QBrush> #include <QColor> #include <QGradient>
该代码可以成功编译,但是窗口小部件窗口中没有任何更改。 是时候开始绘图了。
画图
我进入一个封闭的功能
void drawLed(const QColor &color);
并重新定义受保护的功能
void paintEvent(QPaintEvent *event);
重绘事件将导致实际绘制,“玻璃”的颜色作为参数传递到该绘制。
代号 void RgbLed::paintEvent(QPaintEvent *event) { QWidget::paintEvent(event); this->drawLed(ledColor); }
到目前为止。 然后我们开始逐步填写绘图功能。
代号 void RgbLed::drawLed(const QColor &color) { QPainter p(this); QPen pen; pen.setStyle(Qt::NoPen); p.setPen(pen); }
首先,创建一个艺术家对象,将其用于绘图。 然后创建了需要的铅笔,因此没有铅笔:在此图像中,不仅不需要轮廓笔画,而且根本不需要。
然后,按照矢量图形上的课程大致绘制第一个圆:一个大的圆,上面填充了径向渐变。 渐变在顶部(而不是在边缘)处有一个浅锚点,在底部(但不是在边缘)处有一个暗锚点。 根据渐变创建画笔,用此画笔画家绘制一个圆(即,刻在正方形上的椭圆)。 原来是这样的代码
代号 QRadialGradient outerRingGradient(QPoint(centerX, centerY - outerBorderRadius - (outerBorderWidth / 2)), minDim - (outerBorderWidth / 2))
环境强调了drawLed函数的color参数,因为未使用它。 让他宽容,还不需要他,但是他很快就会需要它。 启动的项目将产生以下结果:
添加另一批代码。
代号 QRadialGradient innerRingGradient(QPoint(centerX, centerY + innerBorderRadius + (innerBorderWidth / 2)), minDim - (innerBorderWidth / 2))
几乎相同的圆圈,但尺寸较小且倒置。 我们得到以下图片:
最后,您需要玻璃的颜色:
代号 QColor dark(color.darker(120)); QRadialGradient glassGradient(QPoint(centerX, centerY), innerBorderRadius); glassGradient.setColorAt(0, color); glassGradient.setColorAt(1, dark); QBrush glassBrush(glassGradient); p.setBrush(glassBrush); p.drawEllipse(QPoint(centerX, centerY), innerBorderRadius, innerBorderRadius);
在这里,使用透射色的较暗功能,可以获得相同的颜色,但较暗,以组织渐变。 肉眼选择的系数120。 结果如下:
在玻璃周围添加环形阴影。 这是在关于矢量图形的课程中完成的,这应该增加体积和真实感:
代号 QRadialGradient shadowGradient(QPoint(centerX, centerY), innerBorderRadius); shadowGradient.setColorAt(0, ringShadowLightColor); shadowGradient.setColorAt(0.85, ringShadowMedColor); shadowGradient.setColorAt(1, ringShadowDarkColor); QBrush shadowBrush(shadowGradient); p.setBrush(shadowBrush); p.drawEllipse(QPoint(centerX, centerY), innerBorderRadius, innerBorderRadius);
有一个三步渐变,因此阴影的边缘变粗,向中心变浅。 原来是这样的:
一次添加高光。 与下部(和所有其他元素)不同,上部高光是线性渐变。 我的画家很一般,我谨代表本课的作者。 也许有些道理,我不会尝试不同类型的渐变。
代号 QLinearGradient topTeflexGradient(QPoint(centerX, (innerBorderWidth + outerBorderWidth)), QPoint(centerX, centerY))
实际上,就像KDPV一样,这就是一个现成的灯泡。
玻璃的眩光和凸起的可见性受颜色(或多深)的影响。 根据黑暗情况,在暗功能中增加眩光的亮度和调光系数的调整可能很有意义,但这是完美主义。
下面是在程序窗口中使用的示例。
呵护
为了娱乐,您可以玩花。 例如,覆盖受保护的鼠标单击事件
void mousePressEvent(QMouseEvent *event);
这样:
代号 void RgbLed::mousePressEvent(QMouseEvent *event) { static int count = 0; if (event->button() == Qt::LeftButton) { switch (count) { case 0: ledColor = Qt::red; count++; break; case 1: ledColor = Qt::green; count++; break; case 2: ledColor = Qt::blue; count++; break; case 3: ledColor = Qt::gray; count++; break; default: ledColor = QColor(220, 30, 200); count = 0; break; } this->repaint(); } QWidget::mousePressEvent(event); }
不要忘记将鼠标事件添加到标题中:
#include <QMouseEvent>
现在,在组件上单击鼠标将切换灯泡的颜色:红色,绿色,蓝色,灰色以及灯泡发出的一些随机光。
结语
至于绘图,仅此而已。 并且小部件应添加功能。 在我的情况下,添加了一个布尔字段“使用状态”,另一个布尔字段定义了状态“开”或“关”以及这些状态的默认颜色,以及所有这些的开放式吸气剂和吸气剂。 paintEvent()函数选择传递给drawLed()的颜色作为参数,因此,您可以根据事件关闭状态的使用并将灯泡设置为任何颜色,或者根据事件打开状态并打开或关闭灯泡。连体 与必须进行监控的信号是它。
使用mousePressEvent演示了不仅可以使小部件成为指示器,而且还可以使其成为按钮,从而使其可以被按下,释放,弯曲,扭曲,着色以及任何您想要的指向,单击和释放事件。
但这已不再是根本。 目的是显示在绘制自己的窗口小部件时可以在何处扮演角色模型,以及如何在资源或文件中无需使用光栅或矢量图像的情况下轻松实现此绘制。