简 述: 使用 QStyle / DTK 来实现重绘自定义需求控件(Qt-GUI 没有的),此处以重绘 ios 的控件 MySwitchButton 举例。
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
编程环境:
💻: uos 20 amd64
📎 Qt 5.11.3
💻: MacOS 10.14.6
📎 Qt 5.12.6
💻: win10 x64
📎 Qt 5.9.8
背景铺垫:
由上上一篇 的文章,已理解 QStyle
、 QCommonStyle
和自定义的 MyStyle
三者的关系;对基础的风格控件也有所学习了;
和上一篇 文章中,已理解如何使用 MyStyle
来绘画 Qt 自带的控件的风格,也有一次的实战重绘 QCommonStyle
里面的虚函数的项目;
在此,本文收尾,分析开源社区精神小伙们,它们是怎么对 DTK 的库进行重绘控件的。想到的新学习的框架,良心说,开源,真的能够学习到很多知识。 本篇重点介绍,如何创建一个新的自定义的控件,并且对其进行绘画,写一个简单工程,进行示范。
需求分析:
因 DTk/QStyle
绘画的这个系列的完整性,得自己想一个比较好的控件啊,挠了挠头,iPhone 给了我感觉。SwitchButton
控件是 ios / Andiroad 📱的控件,不属于 PC 的控件,更非是 Qt 自带的一个控件;用电脑进行绘画,正好是妙哉。其 UI 原型图如下;功能就是点击一下,就会在 开/关 状态之间来回切换,且展示不同的颜色背景。
工程文件分析:
这里分析下,整个项目的构成组成;这里还是再次简单梳理一番:
- QtStyleEx:
- main.cpp: 程序的总入口
- widget.h: 显示 GUI 控件的背景窗口,继承于 QWidget 的类的声明
- widget.cpp: 显示 GUI 控件的背景窗口,继承于 QWidget 的类定义
- mystyle.h: 自定义风格(不属于 OS 自带风格)类的声明
- mystyle.cpp: 自定义风格(不属于 OS 自带风格)的类定义
- myswitchbutton.h: 自定义需求的控件类的声明
- myswitchbutton_p.h: 自定义需求的控件类的数据类 Private 的声明
- myswitchbutton.cpp: 自定义需求的控件类和变量 Private 类的实现
其中主要涉及的几个类如下:
整理所有类的思维导图:
这上面的这些问题,顺其自然的找到它们对应的类。按照各自的功能划分,可以得到如下的思维导图。提纲挈领,写代码不会迷失在细节👩💻;
实现流程图:
有了 UI 设计图,和熟悉的功能,已经进行重绘控件;通过以上的代码逻辑流程,复盘一下 DTK 绘画自定义需求的源码思路。将自己的逻辑插入到 Qt 原本的绘画架构中,利用已经有的 QStyle 架构来进行绘画。梳理出来的流程图如下。
代码实现:
其中代码实现的地方,有几处是的设计很精妙,可能我是才眼界初看开,很有必要的🌶出来单独讲;看着代码来实现复现一个功能很容易,属于借鉴技巧与学习,但容易让自己上钩思想懒惰,不去思考原理。有一些细节是含糊过去的地方;在正式实现之前,主要解决的问题有如下难点:
- 如何创建 MySwitchButton 控件? 需要继承哪些类❓
- 控件矩形大小❓
- 控件的 UI 样式❓
- 控件如何拆分部件 MyStyle::CE_SwitchButton ❓
- 如何进入到绘画步骤中❓
- QPainter 如何绘画该小部件?进入到控件的 paintEvent()函数❓
- MyStyle 如何区分 Qt 原生控件和自定义的控件❓
- 如何增加自定义枚举 CE_ , SE_ , PM_ , PE_ 等❓
- 如何区分重绘画的控件是 Qt 自带的还是自定义枚举元素❓
- 以及 MyStyle 如何调用这些重载的函数❓
上面👆这些想的清楚了,那么这个架构也清楚了很多。也知晓了绘画的流程。
添加自定义的枚举:
这里新增加的枚举,是属于 MyStyle:: , 而非 QStyle:: 范围。
且要从 CE_CustomBase
= QStyle::CE_CustomBase + 0xf00000
开始,新的枚举按照 int 类型依次加一。
class MyStyle : public QCommonStyle
{
public:
//这里新增加的枚举,是属于 MyStyle:: , 而非 QStyle:: 范围
enum ControlElement {
CE_SwitchButton = QStyle::CE_CustomBase + 1, //switchButton 控件
CE_CustomBase = QStyle::CE_CustomBase + 0xf00000
};
...
}
重写 QCommonStyle 的虚函数:
这里的快捷方式创建的枚举,都是不带QStyle::, 但是快捷方式的定义是带是QStyle:: , 此处声明的地方必须加上 QStyle::,后面改写更复杂的得写上MyStyle:: 因添加自定义的枚举。
这里 override 的虚函数,只能够调用旧有的 QStyle:: 的函数。主要用来绘画 Qt 、 自定义新增 的控件枚举。
后面改写更复杂的得写上MyStyle:: 因添加自定义的枚举
class MyStyle : public QCommonStyle
{
public:
virtual void drawControl(QStyle::ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w) const override;
virtual QRect subElementRect(QStyle::SubElement subElement, const QStyleOption *option, const QWidget *widget) const override;
virtual void drawComplexControl(QStyle::ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, const QWidget *widget) const override;
virtual QSize sizeFromContents(QStyle::ContentsType ct, const QStyleOption *opt, const QSize &contentsSize, const QWidget *w) const override;
virtual void polish(QWidget *widget) override;
virtual void unpolish(QWidget *widget) override;
...
}
内敛函数调用 MyStyle:: 强制转换为 QStyle:: 调用:
新增加的枚举属 MyStyle:: , 之能够在此内敛函数里面调用;主要用来绘画 自定义新增 的控件枚举 –> 实际调用在 下面的 virtual 里面绘画。
class MyStyle : public QCommonStyle
{
public: //声明如下:
inline void drawPrimitive(MyStyle::PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w = nullptr) const;
inline void drawControl(MyStyle::ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w) const;
...
}
//定义如下:
void MyStyle::drawPrimitive(MyStyle::PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w) const
{
proxy()->drawPrimitive(static_cast<QStyle::PrimitiveElement>(pe), opt, p, w);
}
void MyStyle::drawControl(MyStyle::ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w) const
{
proxy()->drawControl(static_cast<QStyle::ControlElement>(element), opt, p, w);
}
设计静态函数同名接口让 MyStylrHelp 调用:
static 函数,供 MyStylrHelp 调用 [用来绘画自增加的控件枚举]。
class MyStyle : public QCommonStyle
{
public: //声明如下:
static void drawPrimitive(const QStyle *style, MyStyle::PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w = nullptr);
static void drawControl(const QStyle *style, MyStyle::ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w);
...
}
//定义如下:
void MyStyle::drawPrimitive(const QStyle *style, MyStyle::PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w)
{
//没有使用到,因为具体的设计因为每个人的思路设计不同,决定的具体的绘画地方也不一样;这里我是放到 重写的虚函数里面,和绘画 Qt 的虚函数的框架里面一起绘画
}
辅助类 MyStyleHelp , 区分绘画控件:
辅助类 MyStyleHelp , 帮助区分绘画到底是绘画 MyStyle::PrimitiveElement 还是,它的类的实现如下:
class MyStyleHelp
{
public:
inline MyStyleHelp (const QStyle* style = nullptr);
inline void drawPrimitive(MyStyle::PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w = nullptr) const;
inline void drawControl(MyStyle::ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w) const;
...
private:
const QStyle* m_qStyle;
const MyStyle* m_myStyle;
};
其中放一个函数的实现,会简单发现这个 三目表达式,很是精华,属于一段点睛之笔;设计上面,既可以专门绘画自定义的新增加的控件,也可以绘画 Qt 的控件;有着很好的扩展性和代码的健壮性。
void MyStyleHelp::drawPrimitive(MyStyle::PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w) const
{
m_myStyle ? m_myStyle->drawPrimitive(pe, opt, p, w)
: MyStyle::drawPrimitive(m_qStyle, pe, opt, p, w);
}
MyStylePainter 画家,在指定的“画板”上绘画:
其直接继承于 class MyStylePainter : public QPainter;本也可以不这样再单独设计一个画家类,为了展现此架构的可扩展性,此处会再封装一下,是的分风格更为统一。其类的设计如下:
class MyStylePainter : public QPainter
{
public:
MyStylePainter(QWidget* w);
void drawPrimitive(MyStyle::PrimitiveElement pe, const QStyleOption *opt);
void drawPrimitive(QStyle::PrimitiveElement pe, const QStyleOption *opt);
...
private:
QWidget* m_widget;
QStyle* m_qStyle;
MyStyleHelp m_myStyleHelp;
};
这里面,相反,我觉得最重要的除了三个成员变量之外,就是这个构造函数,里面调用父类的函数 QPainter::begin(w);
。
MyStylePainter::MyStylePainter(QWidget* w)
{
m_widget = w;
m_qStyle = w->style();
m_myStyleHelp.setStyle(m_qStyle);
QPainter::begin(w); //是调用父类的 begin(), 调试半天才发现
}
控件 MySwitchButton 设计:
作为新的控件,使用 xxx 和它的数据类 xxxPrivate 进行构建,两个类之间依靠宏 Q_D 和 Q_Q 来进行互相的调用;
- MySwitchButton && MySwitchButtonPrivate
- Q_D(MySwitchButton) && Q_Q(MySwitchButton)
class MySwitchButtonPrivate;
class MySwitchButton : public QAbstractButton
{
Q_OBJECT
public:
explicit MySwitchButton(QWidget* parent = nullptr);
~MySwitchButton();
virtual QSize sizeHint() const override;
protected:
virtual void paintEvent(QPaintEvent *event) override;
private:
void initStyleOption(QStyleOptionButton *opt) const;
Q_DECLARE_PRIVATE(MySwitchButton)
};
这里的自定义控件, 在 paintEvent 函数里面,通过画笔进行绘画,与 MyStyle::CE_SwitchButton 关联上。
void MySwitchButton::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
QStyleOptionButton opt;
initStyleOption(&opt);
MyStylePainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.drawControl(MyStyle::CE_SwitchButton, &opt); //重点
}
运行效果:
附上一张完整的 gif 的动图:当然是重点看的 switchButton 控件效果,咳咳,👀 不要跟着鼠标乱动。
下载地址:
欢迎 star 和 fork 这个系列的 qt/dtk 学习,附学习由浅入深的目录。