前言

css颜色表

QWidget详解

QWidget

QWidget类是所有可视控件的基类,控件是用户界面的最小元素,用于接受各种事件(如:鼠标、键盘等)并且绘制出来给用户观看

每个控件都是 矩形的,他们按照 Z轴顺序排列

如果控件没有父控件,则称之为窗口,窗口会被一个框架包裹(包含标题栏,边框等),可以通过某些函数来修改边框属性

QDialog

话框窗口是一个顶级窗口,主要用于短期任务和与用户的简短通信。对话框可以是模态的也可以是非模态的。QDialog可以提供返回值,并且它们可以具有默认按钮

注意QDialog(以及任何其他Qt::Dialog类型的小部件)使用父小部件与Qt中的其他类略有不同。 对话框始终是顶级小部件,但如果它有父部件, 它的默认位置将位于父部件的顶级小部件的顶部中心。它还将共享父任务栏条目(也就是说不会出现在任务栏)

对话框有两种模式: 模态对话框非模态对话框

模态对话框

模态对话框是阻止输入到同一应用程序中其他可见窗口的对话框,直到对话框被关闭

当打开应用程序模态对话框时,用户必须完成与对话框的交互并关闭它,然后才能访问应用程序中的任何其他窗口

显示模态对话框最常用的方法是调用它的 exec() 函数。当用户关闭对话框时, exec() 将提供一个有用的返回值。要关闭对话框并返回适当的值,必须连接一个默认按钮, 例如,一个OK按钮连接到accept()槽,一个Cancel按钮连接到reject()槽。或者,您可以使用Accepted或Rejected调用done()槽

使用 open() 函数,以窗口模式显示对话框。窗口模式的对话框会阻塞窗口的响应,但是不会影响后续代码的执行

非模态对话框

非模态对话框是在同一应用程序中独立于其他窗口运行的对话框。文字处理程序中的查找和替换对话框通常是非模态的,允许用户与应用程序的主窗口和对话框进行交互

非模态对话框使用 show() 显示,它会立即将控制权返回给调用者

默认按钮

对话框的默认按钮是用户按 Enter(返回) 键时按下的按钮。此按钮用于表示用户接受对话框的设置并希望关闭对话框。使用 QPushButton::setDefault()QPushButton::isDefault()QPushButton::autoDefault() 来设置和控制对话框的默认按钮

ESC键

如果用户在对话框中按Esc键, QDialog::reject() 将被调用。这将导致 窗口关闭: 关闭事件不能被忽略

展开性

可扩展性是以两种方式显示对话框的能力: 显示最常用选项的部分对话框和显示所有选项的完整对话框。通常,可扩展的对话框最初会以部分对话框的形式出现,但带有More切换按钮。如果用户按下More按钮,对话框将展开

main.cpp【测试】
cpp
// #include "widget.h"
#include <QApplication> // 包含一个应用程序类的头文件
#include <QtGlobal>
#include <QObject>
#include <QWidget>
#include <QDir> // 路径头文件
#include <QPushButton>
#include <QPointer>
#include <QDialog>  // 添加头文件模态对话框

class Dialog : public QDialog
{

public:
    Dialog(QWidget *parent = nullptr)
        : QDialog(parent)
        , m_btn1(new QPushButton("确认", this))
        , m_btn2(new QPushButton("取消", this))
    {
        m_btn1->move(160, 0);
        m_btn1->setDefault(false);   // 设置按钮默认值  false则默认取消

        connect(m_btn1, &QPushButton::clicked, [ = ]()  // 接受
        {
            // 方式1
            // accept();
            // 方式2--通过返回值
            done(QDialog::Accepted);
        });

        connect(m_btn2, &QPushButton::clicked, [ = ]()  // 取消
        {
            // rejected(); // 取消不一定关闭窗口,所以需要手动关闭
            // close();
            done(QDialog::Rejected);
            m_data = "hello";
        });
    }
    QString getDataStr()
    {
        return m_data;
    }
private:
    QPushButton *m_btn1{};
    QPushButton *m_btn2{};
    QString m_data;
};

/*
* @class: Widget
* @brief:
*/
class widget : public QWidget
{

public:
    widget(QWidget *parent = nullptr):
        QWidget(parent),
        m_btn(new QPushButton("显示模态对话框", this)),
        m_btn1(new QPushButton("显示非模态对话框", this)),
        m_btn2(new QPushButton("显示半模态对话框", this))
    {
        m_btn1->move(0, 50);
        m_btn2->move(0, 100);

        connect(m_btn, &QPushButton::clicked, []()
        {
            // 显示模态对话框
            Dialog loginDlg;
            // 阻塞程序执行
            int ret = loginDlg.exec();

            if (ret == QDialog::Accepted)
            {
                qDebug() << "accepted" << loginDlg.getDataStr();
            }
            else
            {
                qDebug() << "rejected" << loginDlg.getDataStr();
            }
            qDebug() << "exec";
        });
        connect(m_btn1, &QPushButton::clicked, [ = ]()
        {
            // 显示非模态对话框
            // 不阻塞
            loginDlg.show();
        });
        connect(m_btn2, &QPushButton::clicked, [ = ]()
        {
            // 显示半模态对话框

            // 不会阻塞程序执行,但是会阻塞窗口的消息
            loginDlg.open();
            qDebug() << "m_btn2";
        });
    }
protected:
private:
    QPushButton *m_btn{};
    QPushButton *m_btn1{};
    QPushButton *m_btn2{};
    QDialog loginDlg;
};



int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    widget w;

    w.show();

    return a.exec();

}

窗口相关

在窗口没有创建完成之前获取的坐标总是0

注意电脑的屏幕是不是100%缩放,如果不是则会影响

main.cpp【测试】
cpp
// #include "widget.h"
#include <QApplication> // 包含一个应用程序类的头文件
#include <QtGlobal>
#include <QObject>
#include <QWidget>
#include <QDir> // 路径头文件
#include <QPushButton>
#include <QPointer>

class widget : public QWidget
{
public:
    widget(QWidget *parent = nullptr)
        : QWidget(parent),
          m_btn(new QPushButton("点我", this)),
          m_btn2(new QPushButton("最小", this)),
          m_btn3(new QPushButton("最大", this)),
          m_btn4(new QPushButton("关闭", this)),
          m_btn5(new QPushButton("全屏", this)),
          subwidget(new QWidget)
    {
        // 设置按钮大小
        m_btn->setFixedSize(32, 32);
        m_btn2->setFixedSize(32, 32);
        m_btn3->setFixedSize(32, 32);
        m_btn4->setFixedSize(32, 32);
        m_btn5->setFixedSize(32, 32);

        // 移动位置
        m_btn4->move(width() - m_btn4->width(), 0);
        m_btn3->move(width() - m_btn4->width() * 2, 0);
        m_btn2->move(width() - m_btn4->width() * 3, 0);
        m_btn5->move(width() - m_btn4->width() * 4, 0);

        subwidget->show();   // 显示子窗口

        // 信号触发
        connect(m_btn4, &QPushButton::clicked, [ = ]()
        {
            close();    // 关闭
        });
        connect(m_btn3, &QPushButton::clicked, [ = ]()
        {
            // 判断窗口是最大化的话,恢复原样
            if (isMaximized())
            {
                showNormal();   // 正常显示
            }
            // 如果窗口不是最大化的话,则最大化
            else
            {
                showMaximized();    // 最大化
            }
        });
        connect(m_btn2, &QPushButton::clicked, [ = ]()
        {
            showMinimized();    // 最小化
        });
        connect(m_btn5, &QPushButton::clicked, [ = ]()
        {
            showFullScreen();   // 全屏
        });

        connect(m_btn, &QPushButton::clicked, [ = ]()
        {


            if (subwidget)
            {
                // subwidget.deleteLater();    // 调用这个才是销毁
                subwidget->close();  // 如果程序有多个窗口,关闭一个窗口并不会销毁窗口这是隐藏了
            }
            else    // 使用智能指针且关闭删除窗口才会触发这个
            {
                qDebug() << "subwidget is null";
            }


            // 显示和隐藏

            // if (subwidget.isHidden())
            // {
            //     // 方式1
            //     // subwidget.show();   // 显示
            //     // 方式2
            //     // subwidget.setHidden(false);
            //     // 方式3 --- 仅对控件生效
            //     // subwidget.setVisible(false);
            // }
            // else
            // {
            //     // 方式1
            //     // subwidget.hide();    // 隐藏
            //     // 方式2
            //     // subwidget.setHidden(true);
            //     // 方式3 --- 仅对控件生效
            //     // subwidget.setVisible(true);
            // }
        });
        // 因为不会销毁窗口所以此函数不会触发
        connect(subwidget, &QWidget::destroyed, [ = ]()
        {
            qDebug() << "destroyed";
        });

        // 设置属性,让窗口调用close时自动销毁
        subwidget->setAttribute(Qt::WA_DeleteOnClose);



        // // 设置窗口的标题
        // this->setWindowTitle("我的第一个窗口");

        // // 输出当前项目的路径
        // qDebug() << QDir::currentPath();
        // // 资源文件
        // setWindowIcon(QIcon(":Resource/zay.png"));
        // // 窗口大小,位置
        // qDebug() << width() << height() << size();  // 默认 640 480 QSize(640, 480)
        // // 几何  因为窗口没创建完成所以获取的坐标总是0
        // qDebug() << geometry() << frameGeometry() << rect();    // QRect(0,0 640x480) QRect(0,0 640x480) QRect(0,0 640x480)

        // // move(0, 0); // 设置窗口坐标,这样的话标题栏会贴紧屏幕左上角(它是以标题栏区左上角为起点的)
        // // resize(640, 480);   // 设置窗口大小
        // setFixedSize(640, 480); // 设置固定大小(这样的话就不能被拉大或者拉小)

        // // 设置最大,最小大小(这样就不能拉出这个范围)
        // setMinimumSize(250, 250);
        // setMaximumSize(640, 480);

        // // 设置几何 --- 它是以用户区左上角为起点的
        // setGeometry(50, 50, 150, 150);

        // connect(m_btn, &QPushButton::clicked, [ = ]()
        // {
        //     // rect -> 128x30是标题栏的宽度x高度
        //     qDebug() << geometry() << frameGeometry() << rect();   // QRect(-1279,280 128x30) QRect(-1279,249 128x61) QRect(0,0 128x30)
        // });


    }

private:
    QPushButton *m_btn{};
    QPushButton *m_btn2{};
    QPushButton *m_btn3{};
    QPushButton *m_btn4{};
    QPushButton *m_btn5{};
    QPointer<QWidget> subwidget;
};


int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    widget w;

    w.show();

    return a.exec();

}

QT事件系统

事件系统

在Qt中,事件是派生自抽象 QEvent类 的对象,它表示应用程序内发生的事情或应用程序需要知道的外部活动的结果。事件可以由QObject子类的任何实例接收和处理,但它们与小部件尤其相关

Qt程序需要在main()函数创建一个 QApplication对象 ,然后调用它的 exec() 函数。这个函数就是开始Qt的事件循环。在执行exec()函数之后,程序将进入事件循环来监听应用程序的事件,当事件发生时,Qt将创建一个事件对象

事件类型

大多数事件类型都有特殊的类,特别是 QResizeEvent、QPaintEvent、QMouseEvent、QKeyEvent和QCloseEvent。每个类都是QEvent的子类,并添加特定于事件的函数。例如, QResizeEvent 添加了 size()oldSize(),以使小部件能够发现它们的尺寸是如何被更改的

有些类支持多个实际事件类型。 QMouseEvent 支持按下鼠标按钮、双击、移动和其他相关操作

每个事件都有一个关联的类型,在 QEvent:: type 中定义,这可以用作运行时类型信息的方便来源,以快速确定给定事件对象是从哪个子类构建的

由于程序需要以各种复杂的方式进行响应,Qt的事件传递机制非常灵活

事件处理

传递事件的通常方式是调用虚函数,这个虚函数负责进行适当的响应,通常是重新绘制小部件。如果在虚函数的实现中不执行所有必要的工作,则可以调用基类的实现

  • 虚函数查找

鼠标事件

  • 鼠标按下
  • 鼠标释放
  • 鼠标移动

如果关闭了鼠标跟踪,则只有在移动鼠标时按下鼠标按钮时才会发生鼠标移动事件。如果打开了鼠标跟踪,即使没有按下鼠标按钮,也会发生鼠标移动事件

  • 鼠标滚轮

返回轮子旋转的相对量,单位为八分之一度。正值表示转轮向前旋转,远离用户;负值表示转轮向后向用户旋转

大多数鼠标类型的工作步长为15度,在这种情况下,delta值是120的倍数;即 120单位* 1/8 = 15度

cpp
//【测试】
#include <QWidget>
#include <QApplication>
#include <iostream>
#include <QPushButton>
#include<QMouseEvent>

class Button : public QPushButton
{
public:
	Button(QWidget* parent = nullptr)
		:QPushButton(parent)
	{
		
	}
protected:
	//重写了父类的虚函数,我不再使用父类的实现,而是由我来实现
	//按钮的点击信号,是在mousePressEvent函数里面触发的
	void mousePressEvent(QMouseEvent* ev) override
	{
		if (ev->button() == Qt::LeftButton)
		{
			qDebug() << "pressed";
		}

		//剩下的交给父类处理 则会执行【鼠标点击事件1】如果不写这个则不会执行
		QPushButton::mousePressEvent(ev);
	}
};

class widget : public QWidget
{
public:
	QPushButton* m_btn{};
	widget(QWidget* parent = nullptr)
		:QWidget(parent)
		, m_btn(new QPushButton("X", this))
	{
		//m_btn->setFixedSize(32, 32);
		auto btn = new Button(this);
		btn->setText("hello");

		// 鼠标点击事件1
		connect(btn, &Button::clicked, [=]
			{
				qDebug() << "clicked";
				isPress = true;
			}
		);
		setMouseTracking(true);	//设置鼠标追踪 这样就不需要按直接移动就能触发 【mouseMoveEvent事件】
	}
	// 处理一下鼠标点击事件
	// 重写虚函数即可!函数的原型必须和父类中的虚函数完全一致!
	// 鼠标点击事件处理函数~
	void mousePressEvent(QMouseEvent* ev) override
	{
		// 判断一下是那个键按下了?左键 中间 右键 侧键
		if (ev->button() == Qt::MouseButton::LeftButton)
		{
			qDebug() << "left button" << ev->pos() << ev->globalPos() << ev->scenePosition();
		}
	}
	// 鼠标释放事件
	void mouseReleaseEvent(QMouseEvent* ev) override
	{
		if (ev->button() == Qt::LeftButton)
		{
			isPress = false;
		}
	}
	// 鼠标移动事件
	void mouseMoveEvent(QMouseEvent* ev) override
	{
		//如果鼠标左键按下,并且移动了鼠标....
		if (isPress)
		{
			qDebug() << "left button and move";
		}
		//鼠标右键按下了 1 | 2
		if (ev->buttons() & Qt::RightButton)
		{
			qDebug() << "right button and move";
		}
		qDebug() << "move";
	}
	// 鼠标双击事件
	void mouseDoubleClickEvent(QMouseEvent* ev)override
	{
		qDebug() << "double click";
	}
	// 滚轮事件
	void wheelEvent(QWheelEvent* ev) override
	{
		// 判断滚轮的方向
		qDebug() << ev->angleDelta().y() / 8 << ev->angleDelta().x() / 8;
	}
protected:
private:
	bool isPress = false;
};


int main(int argc, char* argv[])
{

	QApplication a(argc, argv);
	widget w;

	w.show();

	return a.exec();
}

键盘事件

  • 按键按下
  • 按键释放
cpp
// 【测试】

for (size_t i = 0; i < 70; i++)
{
    // 打印键
    //qDebug()<<QKeySequence::StandardKey(i)<<"    " << QKeySequence::keyBindings(QKeySequence::StandardKey(i));
}


// ----------------键盘事件
	// 按键事件
	void keyPressEvent(QKeyEvent* ev) override
	{
		//当前是什么键按下的(上下左右没有)
		qDebug() << Qt::Key(ev->key());	// 构造成枚举类型才能输出对应按键否则输出数值

		//描述键 Ctrl shift alt ...
		if (ev->modifiers() & Qt::KeyboardModifier::ControlModifier && ev->key() == Qt::Key_A)
		{
			qDebug() << "全选";
		}

		if (ev->matches(QKeySequence::StandardKey::Save))
		{
			qDebug() << "保存";
		}
	}

窗口事件

  • 窗口关闭

  • 窗口隐藏,显示

  • 窗口移动

  • 窗口大小改变

  • 窗口焦点

  • 右键菜单

  • 程序状态发生改变

如果需要检测程序中,某些东西是否发生了改变,可以通过void QWidget::changeEvent(QEvent *event)来检测

以下是常用事件:

  1. QEvent::FontChange

  2. QEvent::WindowTitleChange

  3. QEvent::IconTextChange

  4. QEvent::ModifiedChange

  5. QEvent::MouseTrackingChange

  6. QEvent::WindowStateChange

cpp
// 【测试】
#include<QMouseEvent>
#include<QMessageBox>	// 消息盒头文件

m_btn->setFixedSize(32, 32);
// 鼠标点击事件1
connect(m_btn, &Button::clicked, [ = ]
{
    setWindowTitle("maya");
}
       );


// ----------------窗口事件
	//窗口关闭事件处理
	void closeEvent(QCloseEvent* ev) override
	{
		// 弹出消息盒子
		auto ret = QMessageBox::question(this, "关闭窗口?", "有未保存的文件,是否退出并保存?");
		qDebug() << ret;
		// 判断返回值
		if (ret == QMessageBox::StandardButton::Yes)
		{
			//保存并退出,接受事件
			//save();...
			ev->accept();
		}
		else
		{
			//啥也不干,忽略关闭事件
			ev->ignore();
		}
	}
	// 窗口显示事件
	void showEvent(QShowEvent* ev)override
	{
		qInfo() << "我显示啦~";
	}
	// 窗口隐藏事件
	void hideEvent(QHideEvent* ev)override
	{
		qInfo() << "我隐藏啦~";
	}
	// 窗口移动事件
	void moveEvent(QMoveEvent* ev)override
	{
		qInfo() << "Widget moved" << "oldPos" << ev->oldPos() << "newPos" << ev->pos();
	}
	// 窗口大小改变
	void  resizeEvent(QResizeEvent* ev)override
	{
		// 按钮的位置保持不变
		m_btn->move(ev->size().width() - m_btn->width(), 0);
	}
	// 右键菜单
	void  contextMenuEvent(QContextMenuEvent* ev)
	{
		qInfo() << "报告长官,请求弹出上下文菜单,也就是右键菜单!"
			<< "请弹出在\n"
			<< "全局坐标:" << ev->globalPos()
			<< "局部坐标:" << ev->pos() << "位置";
	}
	// 程序状态发生改变
	void changeEvent(QEvent* ev)override
	{
		switch (ev->type())
		{
		case QEvent::Type::WindowTitleChange:	// 窗口标题发送改变时触发
			qDebug() << "windowtitlechagne" << this->windowTitle();
			break;
		default:
			break;
		}
	}

定时器事件

cpp
// 【测试】
#include<QTimer>

class SWidget : public QWidget
{
public:
	SWidget(QWidget* parent = nullptr)
		:QWidget(parent)
	{
		//两种定时器,周期性的处理
		//1,----------------------QTimer
		auto timer = new QTimer(this);
		//	timer->callOnTimeout([]
		//		{
		//			qDebug() << "update";
		//		});

		//	timer->callOnTimeout(this, &SWidget::game_update);

		//	connect(timer, &QTimer::timeout, this, &SWidget::game_update);		// QT4写法

		timer->start(1000 / 60);	// 开启定时器。多少ms触发一次
		
		QTimer::singleShot(1000, [] {qDebug() << "once"; });	// 延时多少ms后触发一次


		//2,--------------------定时器事件
		//开启定时器,通过返回值可以知道是哪个定时器
		timer_id1 = startTimer(500);
		timer_id2 = startTimer(200);
	}

	// 定时器事件
	void timerEvent(QTimerEvent* ev)override
	{
		static int i = 0;
		if (i == 3)
		{
			//killTimer(ev->timerId());	//杀死定时器
			
		}
		if (ev->timerId() == timer_id1)
		{
			qDebug() << ev->timerId() << __FUNCTION__;

		}
		else if (ev->timerId() == timer_id2)
		{
			qDebug() << ev->timerId() << __FUNCTION__;

		}
		i++;
	}

	void game_update()
	{
		qDebug() << __FUNCTION__;
	}

	int timer_id1;
	int timer_id2;
};

int main(int argc, char* argv[])
{

	QApplication a(argc, argv);
	SWidget w;

	w.show();

	return a.exec();
}

事件分发

传递事件的通常方式是调用虚函数。 例如,QMouseEvent 通过调用 QWidget::mousePressEvent() 来传递。这个虚函数负责进行适当的响应,通常是处理鼠标点击,并发出对应的信号。如果在虚函数的实现中不执行所有必要的工作,则可能需要调用基类的实现

cpp
// 【测试】
class MWidget : public QWidget
{
public:
	MWidget(QWidget* parent = nullptr)
		:QWidget(parent)
	{

	}
	//所有的事件处理函数都是从event()事件派发函数调用的
	bool event(QEvent* ev)override
	{
		switch (ev->type())
		{
		case QEvent::MouseButtonPress:
				//mousePressEvent(dynamic_cast<QMouseEvent*>(ev));	// 父类转子类
			break;
		default:
			break;
		}

		//return QObject::event(ev);
		return QWidget::event(ev);	// 交给父类即可这样就不需要上面的mousePressEvent传递
	}
	void mousePressEvent(QMouseEvent* ev)override
	{
		qDebug() << ev->button();
	}
};

int main(int argc, char* argv[])
{

	QApplication a(argc, argv);
	MWidget w;

	w.show();

	return a.exec();
}

发送事件

许多应用程序都希望创建和发送它们自己的事件。通过构造合适的事件对象并使用 QCoreApplication::sendEvent()QCoreApplication::postEvent() 发送事件,您可以以与Qt自己的事件循环完全相同的方式发送事件

sendEvent() 立即处理事件。当它返回时,事件过滤器和/或对象本身已经处理了该事件。对于许多事件类,都有一个名为isAccepted()的函数,它告诉您事件是被最后一个调用的处理程序接受还是拒绝的。

postEvent() 将事件发送到队列中,以便稍后进行分派。下次Qt的主事件循环运行时,它会分发所有发布的事件,并进行一些优化。例如,如果有几个调整大小事件,它们将被压缩为一个。同样适用于绘制事件: QWidget::update() 调用 postEvent(),它通过避免多次重绘来消除闪烁并提高速度

自定义事件

要创建自定义类型的事件,需要定义一个事件号, 该事件号必须大于QEvent::User并且可能需要继承QEvent的子类,以便传递关于自定义事件的特定信息

主要涉及到两个函数:

  1. sendEvent

sendEvent会让接受事件的对象立即处理事件,处理完成之后函数返回
—对于栈区的事件会自动释放(超出作用域释放)
—对于堆区的事件需要函数返回之后自己释放 (超出作用域,也不会自动释放)

  1. postEvent

postEvent是把事件提交到事件队列,不管有没有处理事件,都立即返回
—必须把事件放在堆区,处理完毕之后自动释放

cpp
// 【测试】
/*自定义事件*/
class CustomEvent : public QEvent
{
public:
	enum Type { Custom = QEvent::User };
	CustomEvent(const QString& data)
		:QEvent(static_cast<QEvent::Type>(Custom))
		, m_data(data)
	{

	}
	~CustomEvent() { qDebug() << __FUNCTION__; }
	QString data()const { return m_data; }

	QString m_data;
};

class MWidget : public QWidget
{
public:
	MWidget(QWidget* parent = nullptr)
		:QWidget(parent)
	{

	}
	//所有的事件处理函数都是从event()事件派发函数调用的
	bool event(QEvent* ev)override
	{
		switch (ev->type())
		{
		case QEvent::MouseButtonPress:
				//mousePressEvent(dynamic_cast<QMouseEvent*>(ev));	// 父类转子类
			break;
		default:
			break;
		}

		//return QObject::event(ev);
		return QWidget::event(ev);	// 交给父类即可这样就不需要上面的mousePressEvent传递
	}
	void mousePressEvent(QMouseEvent* ev)override
	{
		if (ev->button() == Qt::RightButton)
		{
#if 0
			//向指定的对象发送事件
			//1,sendEvent 发送栈区的事件,直到事件处理完成后,才会返回(函数是阻塞的)
			
			// ---方式1
			//CustomEvent *evv = new CustomEvent("sendEvent customEvent");
			//qApp->sendEvent(this, evv);	// 发送事件
			//delete evv;	//堆区的要手动释放
			// ---方式2
			CustomEvent evv("sendEvent customEvent");
			qApp->sendEvent(this, &evv);	// 发送事件
#else
			// 2,postEvent 只能发送堆区的对象,一旦发送,直接返回,不需要等事件处理完成,一旦事件处理完成,会自动释放

			// ---方式1  右键则直接报错!!!因为这边不需要等待发送完所以直接销毁了所以相当于发送了个空的过去
			//CustomEvent ev("sendEvent customEvent");
			//qApp->postEvent(this, &ev);
			// ---方式2  不需要手动delete会自动释放
			CustomEvent* ev = new CustomEvent("sendEvent customEvent");
			qApp->postEvent(this, ev);
#endif
		}
		qDebug() << ev->button();
	}
	//专门处理自定义事件的处理函数
	void customEvent(QEvent* ev)override
	{
		if (ev->type() == CustomEvent::Custom)
		{
			auto cev = dynamic_cast<CustomEvent*>(ev);	// 父类转子类
			if (ev)
			{
				qDebug() << __FUNCTION__ << cev->data();
			}
		}
	}
};

int main(int argc, char* argv[])
{

	QApplication a(argc, argv);
	MWidget w;

	w.show();

	return a.exec();
}

事件传播机制

事件接受和忽略

当控件忽略事件时,事件会继续往上传播,这里的传播是指传播给父组件

QEvent有 accept()函数 和 ignore()函数:

  • **accept():**本组件处理该事件,这个事件就不会被继续传播给其父组件;

  • **ignore():**本组件不想要处理这个事件,这个事件会被继续传播给其父组件;

  • 值得注意的是所有的事件默认都是接受的

cpp
// 【测试】
#include <QWidget>
#include <QApplication>
#include <iostream>
#include <QPushButton>
#include <QMouseEvent>
#include <QMessageBox>	// 消息盒头文件
#include<QTimer>


class Button : public QPushButton
{
public:
	Button(const QString& text, QWidget* parent = nullptr)
		:QPushButton(text, parent)
	{

	}
protected:
	bool event(QEvent* ev)override
	{
		if (ev->type() == QEvent::Type::KeyPress)
		{
			QKeyEvent* kev = dynamic_cast<QKeyEvent*>(ev);
			qDebug() << Qt::Key(kev->key());
			// 这里return true或false都可以主要是为了不执行QPushButton::event(ev)
			return false;		// 这里我们忽略了按钮作为默认按钮时,回车键按下的事件,不会触发clicked信号
		}

		return QPushButton::event(ev);	//返回true表示事件已经处理完成,否则表示忽略
	}
	void mousePressEvent(QMouseEvent* ev)override
	{
		qDebug() << __FUNCSIG__;	// 输出函数全名(微软特有的)
		//ev->accept();	//默认调用的	// 相当于打印当前
		ev->ignore();	//忽略事件处理,事件将传播到父组件 --- 相当于打印两个
	}
};

class widget : public QWidget
{
public:
	widget(QWidget* parent = nullptr)
		:QWidget(parent)
	{
		auto btn = new Button("Button", this);

		btn->setDefault(true);

		connect(btn, &Button::clicked, []
			{
				qDebug() << "clicked";
			});
	}
protected:
	void mousePressEvent(QMouseEvent* ev)override
	{
		qDebug() << __FUNCSIG__;	// 输出函数全名(微软特有的)
	}
};

int main(int argc, char* argv[])
{

	QApplication a(argc, argv);

	widget w;
	w.show();

	return a.exec();
}

事件分发

Qt的事件产生之后,不是直接传递给了对象的,需要经过一系列的过程

cpp
// 【测试】
#include <QWidget>			// QWidget 类的头文件
#include <QApplication>		// QApplication 类的头文件
#include <iostream>			// 标准输入输出流的头文件
#include <QPushButton>		// QPushButton 类的头文件
#include <QMouseEvent>		// QMouseEvent 类的头文件
#include <QMessageBox>		// QMessageBox 类的头文件
#include<QTimer>			// QTimer 类的头文件

#define myApp static_cast<MyApp*>(qApp)	// 定义宏,用于访问应用程序类的实例

// 应用程序类
class MyApp : public QApplication
{
public:
	using QApplication::QApplication;	// 使用基类 QApplication 的构造函数
	void addValue(const QString& key, const QVariant& v)	// 添加配置项
	{
		m_config.insert(key, v);	// 将 key 和 v 插入到 QMap 集合中
	}
	QVariant value(const QString& key)	// 获取配置项
	{
		return m_config.value(key);		// 从 QMap 集合中获取 key 对应的值
	}
private:
	QMap<QString, QVariant> m_config;	// 保存配置项的 QMap 集合
};

// 自定义窗口类
class widget : public QWidget
{
public:
	widget(QWidget* parent = nullptr)	// 构造函数
		:QWidget(parent)				// 调用父类构造函数
		, btn(new QPushButton("按钮", this))	// 创建按钮控件
	{
		btn->setDefault(true);			// 将按钮设置为默认按钮

		connect(btn, &QPushButton::clicked, []	// 连接按钮的 clicked 信号
			{
				qDebug() << "clicked";	// 输出调试信息
				qDebug() << myApp->value("appName") << myApp->value("mobile");	// 获取配置项的值并输出
			});
	}
protected:
	void mousePressEvent(QMouseEvent* ev)override	// 重写鼠标按下事件处理函数
	{
		qDebug() << __FUNCSIG__;	// 输出调试信息
	}
private:
	QPushButton* btn{};	// 按钮控件指针
};

// 主函数
int main(int argc, char* argv[])
{
	MyApp a(argc, argv);	// 创建应用程序类实例
	a.addValue("appName", "day08_event");	// 添加配置项
	a.addValue("version", "1.0");			// 添加配置项
	a.addValue("mobile", 123456789);		// 添加配置项

	widget w;	// 创建窗口对象
	w.show();	// 显示窗口

	return a.exec();	// 运行应用程序的主事件循环,直到退出
}

事件过滤

cpp
//安装事件过滤器
installEventFilter(this);

// 取消安装事件过滤器的话就是置空即可
installEventFilter(nullptr);

// 需要重写, 在里面进行处理
bool eventFilter(QObject* watched, QEvent* event)override
{
    
}

示例–无边框窗口拖动

就是把标题栏去掉,只留下最小化,最大化,关闭等

SEventFilterObject.h
cpp
#ifndef __SEVENTFILTEROBJECT_H
#define __SEVENTFILTEROBJECT_H
#include "AllHead.h"

class SEventFilterObject : public QObject
{
public:
	SEventFilterObject(QObject* parent = nullptr);
	bool eventFilter(QObject* watched, QEvent* event)override;

private:
	QPoint m_pos;
};


#endif
SEventFilterObject.cpp
cpp
#include "SEventFilterObject.h"

SEventFilterObject::SEventFilterObject(QObject* parent)
{

}

bool SEventFilterObject::eventFilter(QObject* watched, QEvent* event)
{
	QWidget* w = dynamic_cast<QWidget*>(watched);		// 转换
	// 如果窗口没有边框,才能让他点击窗口客户区移动窗口
	if (w->windowFlags() & Qt::FramelessWindowHint)
	{
		QMouseEvent* ev = dynamic_cast<QMouseEvent*>(event);

		// 鼠标点击触发+左键
		if (event->type() == QEvent::MouseButtonPress && ev->button() == Qt::MouseButton::LeftButton)
		{
			m_pos = ev->pos();
		}
		// 鼠标松开触发+左键  MouseButton::可省略
		else if (event->type() == QEvent::MouseButtonRelease && ev->button() == Qt::LeftButton)
		{
			m_pos = { 0, 0 };
		}
		// 鼠标移动触发+左键同时
		else if (event->type() == QEvent::MouseMove && ev->buttons() == Qt::LeftButton)
		{
			w->move(ev->globalPos() - m_pos);	// 全局坐标减去当前位置坐标
		}
	}
	return false;
}
main.cpp
cpp
#include "AllHead.h"

class widget : public QWidget
{
public:
	widget(QWidget* parent = nullptr)
		: QWidget(parent)
	{
		// 去掉窗口的边框
		setWindowFlag(Qt::FramelessWindowHint);
		setMouseTracking(true);	// 鼠标跟踪
		// 安装事件过滤器
		installEventFilter(new SEventFilterObject(this));
		
		// 取消安装事件过滤器
		//installEventFilter(nullptr);
	}
protected:
private:
};


// 主函数
int main(int argc, char* argv[])
{
	QApplication a(argc, argv);	// 创建应用程序类实例

	widget w;
	w.show();

	return a.exec();	// 运行应用程序的主事件循环,直到退出
}
AllHead.h
cpp
#ifndef __ALLHEAD_H
#define __ALLHEAD_H

#include <QApplication>		// QApplication 类的头文件
#include <QWidget>			// QWidget 类的头文件
#include "SEventFilterObject.h"
#include <QMouseEvent>
#include <QPoint>
#include <QEvent>

#endif

事件和信号的区别

事件(QEvent) 信号(SIGNAL)
与QObject的关系 由具体对象进行处理 由具体对象主动产生
对程序影响 改写事件处理函数可能导致程序行为发生改变 信号是否存在对应的槽函数不会改变程序行为
两者的联系 一般而言,信号在具体的事件处理函数中产生

信号和事件是两个不同层面的东西,发出者不同,作用不同。Qt中,所有的QObject的子类实例均可对事件接收和处理!

事件类型

在文档助手有

  • QEvent::Type

绘图

基础

Qt的绘制系统支持在屏幕和打印设备上使用相同的API进行绘制,主要基于 QPainter、QPaintDevice和QPaintEngine

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

可以看到 QWidget 父类也是一个画图设备

基本绘制和填充

QPainter 中提供了一些便捷函数来绘制常用的图形,还可以设置线条、边框的画笔以及进行填充的画刷

所有对控件的绘图操作都要放进函数 paintEvent() 中,否则绘图无法显示

  • 绘制图形
cpp
// 【测试】
class widget : public QWidget
{
public:
	widget(QWidget* parent = nullptr)
		: QWidget(parent)
	{

	}
protected:
	//所有在Widget上面的绘制,必须放到paintEvent函数里面,paintevent在需要绘图时自动调用
	void paintEvent(QPaintEvent*)
	{
		qDebug() << __FUNCTION__;

		//1,创建一个画家
		QPainter painter;	// 也可以使用构造函数QPainter painter(this);这样就不需要下面的begin和end了
		//QPainter painter(this);
		//设置绘图设备(绘制到哪里)
		// 所有代码必须放在begin()和end()之间
		painter.begin(this);

		//painter.drawLine(0, 0, width(), height());	// 画一条线在左上角到右下角,而且会随窗口变化而变化(重新绘制)
		
		// 画一个三角形在中间(QPointF类型相当于坐标点{x,y},而且必须是小数)
		QList<QPointF> points =
		{
			{width() / 2.,height() / 4.},
			{width() * 3 / 4.0,height() * 3 / 4.0},
			{width() * 1 / 4.0,height() * 3 / 4.0},
			//{width() / 2.,height() / 4.}
		};
		//painter.drawLines(points);	// 它不会把两个点连起来
		//painter.drawPolyline(points.data(), points.size());	// 会把4个点连起来闭合(需要打开注释第4个点)
		painter.drawPolygon(points.data(), points.size());	// 画图形直接3个坐标即可连起来
		//让画家离开绘图设备
		painter.end();
	}
private:
};
函数 功能 函数 功能
drawArc 绘制圆弧 drawPoint 绘制点
drawChord 绘制弦 drawPolygon 绘制多边形
drawConvexPolygon 绘制凸多边形 drawPolyline 绘制折线
drawElipse 绘制椭圆 drawRect 绘制矩形
drawLine 绘制线条 drawRoundedRect 绘制圆角矩形
drawPie 绘制扇形 fillRect 绘制填充矩形

使用画笔(QPen)

如果需要对绘制的线条设置不同的颜色,那么我们就需要给painter设置一个画笔QPen

Pen有 样式(style),宽度(width), 颜色(brush), 笔帽样式(capStyle),(连接样式)joinStyle

  1. style使用 Qt::PenStyle 定义线条类型。默认是 Qt::PenStyle::SolidLine

  2. brush用于填充画笔笔生成的笔触。 使用 QBrush类 来指定画笔的颜色

  3. capStyle帽样式决定了可以使用 QPainter 绘制的线结束帽

  4. joinStyle连接样式描述了如何绘制两条线之间的连接

注意,当改变画笔的属性时,画家的画笔必须重置 setPen 函数

  • 线条样式

  • 笔帽样式

capStyle帽样式决定了可以使用QPainter绘制的线结束帽

  • 连接样式

joinStyle连接样式描述了如何绘制两条线之间的连接

cpp
// 【测试】
#include "AllHead.h"

class widget : public QWidget
{
public:
	widget(QWidget* parent = nullptr)
		: QWidget(parent)
	{

	}
protected:
	//所有在Widget上面的绘制,必须放到paintEvent函数里面,paintevent在需要绘图时自动调用
	void paintEvent(QPaintEvent*)
	{
		penStyle();
		//capStyle();
		//joinSytle();
	}
	void penStyle()
	{
		QPainter painter(this);
		// 设置渲染提示,Antialiasing高质量渲染(反锯齿)
		painter.setRenderHint(QPainter::RenderHint::Antialiasing);

		for (int i = 0; i < 6; i++)
		{
			painter.setPen(QPen(Qt::red, 5, Qt::PenStyle(i)));
			painter.drawLine(i * 50, 0, i * 50 + 100, 200);
		}
	}
	void capStyle()
	{
		QPainter painter(this);
		painter.setRenderHint(QPainter::RenderHint::Antialiasing);

		painter.setPen(QPen(QBrush(Qt::red), 5, Qt::PenStyle::DotLine, Qt::PenCapStyle::FlatCap));
		painter.drawLine(0, 0, 100, 200);

		painter.setPen(QPen(QBrush(Qt::red), 5, Qt::PenStyle::DotLine, Qt::PenCapStyle::MPenCapStyle));
		painter.drawLine(100, 0, 100 + 100, 200);

		painter.setPen(QPen(QBrush(Qt::red), 5, Qt::PenStyle::DotLine, Qt::PenCapStyle::RoundCap));
		painter.drawLine(200, 0, 100 + 200, 200);

		painter.setPen(QPen(QBrush(Qt::red), 5, Qt::PenStyle::DotLine, Qt::PenCapStyle::SquareCap));
		painter.drawLine(300, 0, 100 + 300, 200);
	}
	void joinSytle()
	{
		QPainter painter(this);
		painter.setRenderHint(QPainter::RenderHint::Antialiasing);

		int s[] = { Qt::MiterJoin, Qt::BevelJoin, Qt::RoundJoin , Qt::SvgMiterJoin, Qt::MPenJoinStyle };
		for (int i = 0; i < 5; i++)
		{
			painter.setPen(QPen(QBrush(Qt::red), 5, Qt::SolidLine, Qt::PenCapStyle::SquareCap, Qt::PenJoinStyle(s[i])));
			QPoint points[] = { {20 + i * 100,30},{50 + i * 100,60},{100 + i * 100,10} };
			painter.drawPolygon(points, 3);
		}
	}

private:
};


// 主函数
int main(int argc, char* argv[])
{
	QApplication a(argc, argv);	// 创建应用程序类实例

	widget w;
	w.show();

	return a.exec();	// 运行应用程序的主事件循环,直到退出
}

画刷(QBrush)

QBrush类提供了画刷来对图形进行填充,一个画刷使用它的颜色和风格(比如它的填充模式)来定义

Brush有 样式(style)、颜色(color)、渐变gradient、纹理(texture)

style使用 Qt::BrushStyle 定义填充模式。 默认的笔刷样式是 Qt::NoBrush

color定义填充图形的颜色

gradient() 定义当前样式为 Qt::LinearGradientPattern, Qt::RadialGradientPatternQt::ConicalGradientPattern 时使用的渐变填充。 当创建QBrush时,通过给QGradient作为构造函数参数来创建渐变笔刷。 Qt提供三个不同的梯度: QLinearGradient, QConicalGradient,和QRadialGradient -所有继承QGradient

cpp
setBrush
  • 填充样式
  • 颜色填充
  • 纹理填充
cpp
// 【测试】
class widget : public QWidget
{
public:
	widget(QWidget* parent = nullptr)
		: QWidget(parent)
	{

	}
protected:
	//所有在Widget上面的绘制,必须放到paintEvent函数里面,paintevent在需要绘图时自动调用
	void paintEvent(QPaintEvent*)
	{
		//brushStyle();
		//brushStyle2();
		brushStyle3();
	}
	// 样式填充
	void brushStyle()
	{
		QPainter painter(this);
		painter.setBrush(Qt::red);	// 设置画刷颜色

		int w = 150;
		int h = 150;
		for (int r = 0; r < 3; r++)
		{
			for (int c = 0; c < 5; c++)
			{
				painter.setBrush(Qt::BrushStyle(r * 5 + c));
				painter.drawRect(c * w, r * h, w, h);
			}
		}
	}
	// 颜色填充
	void brushStyle2()
	{
		QPainter painter(this);
		painter.setBrush(Qt::red);

		int w = 150;
		int h = 150;
		for (int r = 0; r < 3; r++)
		{
			for (int c = 0; c < 5; c++)
			{
				painter.setBrush(QBrush(QColor(58, 118, 198), Qt::BrushStyle(r * 5 + c)));
				painter.drawRect(c * w, r * h, w, h);
			}
		}
	}
	// 纹理填充
	void brushStyle3()
	{
		QPainter painter(this);
		painter.setBrush(Qt::red);
		painter.setPen(Qt::PenStyle::NoPen);	// 去掉画笔
		painter.setBrush(QBrush(QPixmap(":/images/snowball.jpg")));
		int w = 150;
		int h = 150;
		for (int r = 0; r < 3; r++)
		{
			for (int c = 0; c < 5; c++)
			{
				painter.drawRect(c * (w + 10), r * h, w, h);
			}
		}
	}

private:
};
  • 渐变填充

Qt目前支持三种类型的渐变填充:

  1. **线性渐变(QLinearGradient)**在开始点和结束点之间插入颜色

  2. **径向渐变(QRadialGradient)**在围绕它的圆上的焦点和端点之间插入颜色

  3. **锥形渐变(QConicalGradient)**在中心点周围插值颜色

可以使用type()函数检索渐变的类型。 每一种类型都由QGradient的一个子类表示

线性渐变(QLinearGradient)

QLinearGradient 显示从起点到终点的渐变

cpp
// 【测试】
class widget : public QWidget
{
public:
	widget(QWidget* parent = nullptr)
		: QWidget(parent)
	{

	}
protected:
	//所有在Widget上面的绘制,必须放到paintEvent函数里面,paintevent在需要绘图时自动调用
	void paintEvent(QPaintEvent*)
	{
		linearGradient();
	}
	// 线性渐变
	void linearGradient()
	{
#if 0   //效果1 --- 定义线性渐变(慢慢变红)
		QLinearGradient lgra(0, 0, 100, 100);
		lgra.setColorAt(0, Qt::blue);
		lgra.setColorAt(1, Qt::red);

		QPainter painter(this);
		//把渐变色设置给画刷
		painter.setBrush(lgra); painter.setBrush(lgra);
		//绘制矩形,观察渐变区域之外的区域的填充
		painter.drawRect(50, 50, 500, 500);
		//绘制圆形(这里一般和开始点和结束点的坐标对应)
		painter.drawEllipse(0, 0, 100, 100);
#endif

#if 1   // 效果2
		int cx = width() / 2;
		int cy = height() / 2;

		QPainter painter(this);
		QLinearGradient lgra(cx - 50, cy - 50, cx + 50, cy + 50);
		lgra.setColorAt(0, Qt::blue);
		lgra.setColorAt(0.5, Qt::yellow);
		lgra.setColorAt(1, Qt::red);

		painter.setBrush(lgra);

#if 0
		//绘制矩形,观察渐变区域之外的区域的填充 
		painter.drawRect(cx, cy, cx, cy);
		painter.drawRect(0, 0, cx, cy);
#else
		painter.drawRect(rect());
#endif
		//绘制圆形(这里一般和开始点和结束点的坐标对应)
		painter.drawEllipse(cx - 50, cy - 50, 100, 100);
#endif

	} 

private:
};

径向渐变(QRadialGradient)

待学

锥形渐变(QConicalGradient)

待学

坐标变换

QTransform 用于指定坐标系的 2D 转换 - 平移、缩放、扭曲(剪切)、旋转或投影坐标系。通常在渲染现图形时使用

可以使用 setMatrix()、scale()、rotate()、translate()shear() 函数来构建 QTransform 对象。或者,也可以通过应用基本的矩阵操作来构建它。矩阵也可以在构造时定义,并且可以使用 reset() 函数将其重置为单位矩阵(默认值)

QPainter 具有平移、缩放、剪切和旋转坐标系统的功能,无需使用 QTransform

  • 平移

translate(qreal dx, qreal dy):平移 - 对坐标系沿着 x 轴移动 dx、沿 y 轴移动 dy

这里,将坐标原点由 (0, 0) 变为 (50, 50)

cpp
painter->translate(50, 50);
painter->drawPixmap(0, 0, QPixmap(":/images/snowball.jpg"));	// 显示一张图片在{0,0}
  • 缩放

scale(qreal sx, qreal sy):缩放 - 通过水平的 sx 和垂直的 sy 缩放坐标系

使绘制的图像缩小到原来的1.3倍

  • 旋转

rotate(qreal angle, Qt::Axis axis = Qt::ZAxis):旋转 - 对指定的轴用给定的角度逆时针旋转坐标系统

  • 错切

图像错切变换也称为图像剪切、错位或错移变换。

shear(qreal sh, qreal sv):错切 - 通过水平的 sh 和垂直的 sv 扭曲坐标系,前面的参数实现横向变形,后面的参数实现纵向变形。当它们的值为 0 时,表示不扭曲

cpp
// 【测试】
class widget : public QWidget
{
public:
	widget(QWidget* parent = nullptr)
		: QWidget(parent)
	{
		auto timer = new QTimer(this);
		timer->callOnTimeout([=] {
			// 如果需要更新绘图,不能手动调用paintEvent
			// 必须调用update函数来更新绘图
			this->update();
			});
		timer->start(1000 / 60);	// 启动定时器
	}
protected:
	//所有在Widget上面的绘制,必须放到paintEvent函数里面,paintevent在需要绘图时自动调用
	void paintEvent(QPaintEvent*)
	{
		QPainter painter(this);
		//transform(&painter);
		//ZoomTest(&painter);
		//rotateTest(&painter);
		wrong_cut_Test(&painter);
	}
	// 平移
	void transform(QPainter* painter)
	{
		painter->translate(50, 50);
		painter->drawPixmap(0, 0, QPixmap(":/images/snowball.jpg"));	// 显示一张图片在{0,0}
	}
	// 缩小
	void ZoomTest(QPainter* painter)
	{
		painter->setRenderHint(QPainter::RenderHint::Antialiasing);
		//平移
		painter->translate(120, 50);
		//缩放
		painter->scale(1.3, 1.3);

		painter->drawPixmap(QPoint(0, 0), QPixmap(":/images/snowball.jpg").scaled(150, 150));
	}
	// 旋转
	void rotateTest(QPainter* painter)
	{
		// 平移必须在其他变换之前被设置

		painter->setRenderHint(QPainter::RenderHint::Antialiasing);	// 开启抗锯齿效果
		static double angle = 0;
		QTransform transform;	// 创建了一个用于变换的 QTransform 对象
		//平移
		transform.translate(width() / 2, height() / 2);
		//缩放
		transform.scale(0.5, 0.5);

#if 0	// 旋转x度 --- 相当于围绕z轴
		// 旋转x度 旋转是以图片左上角的点绕的
		transform.rotate(angle);	// 旋转45度
		angle++;
#elif 0	// 围绕x轴转
		transform.rotate(angle++, Qt::Axis::XAxis);
		painter->setTransform(transform);
#elif 0	// 围绕y轴转
		transform.rotate(angle--, Qt::Axis::YAxis);
		painter->setTransform(transform);
#elif 0 // 围绕z轴转
		transform.rotate(angle++, Qt::Axis::ZAxis);
		painter->setTransform(transform);
#elif 1 // 中心旋转1 --- 必须先平移再变换调换顺序的话可能有不同现象
		transform.rotate(angle++, Qt::Axis::ZAxis);	// 旋转
		painter->setTransform(transform);
		QPixmap pixmap = QPixmap(":/images/snowball.jpg");
		painter->drawPixmap(0 - pixmap.width() / 2, 0 - pixmap.height() / 2, pixmap);
#endif
	}
	void wrong_cut_Test(QPainter* painter)
	{
		painter->setRenderHint(QPainter::RenderHint::Antialiasing);	// 设置渲染提示,开启抗锯齿效果

		QTransform transform;
		//平移
		transform.translate(120, 50);		// 平移变换,将坐标原点平移到 (120, 50)

		transform.shear(0.5, 0);	// 剪切变换,使得 x 坐标向 y 坐标方向倾斜

		painter->setTransform(transform);	// 应用变换矩阵到 painter 上

		painter->drawPixmap(QPoint(0, 0), QPixmap(":/images/snowball.jpg").scaled(150, 150));	// 绘制图像
	}
private:
};

绘图函数

【QPixmap、QImage、QBitmap和QPicture的区别】

Qt提供了四种处理图像数据的类: QImage, QPixmap, QBitmap和QPicture

QImage 是为I/O和直接像素访问和操作而设计和优化的

QPixmap 是为在屏幕上显示图像而设计和优化的

QBitmap 只是一个继承了QPixmap的方便类,确保深度为1。 如果QPixmap对象是位图,则isQBitmap()函数返回true,否则返回false

QPicture 是一个记录和回放QPainter命令的绘图设备

  • 测试
cpp
// 【测试】
#include "AllHead.h"

class widget : public QWidget
{
public:
	QImage m_image;
	widget(QWidget* parent = nullptr)
		: QWidget(parent)
		, m_image(200, 200, QImage::Format_RGBA8888)
	{
		QPainter painter(&m_image);
		painter.fillRect(m_image.rect(), QGradient::WinterNeva);	// 渐变色
	}
protected:
	//所有在Widget上面的绘制,必须放到paintEvent函数里面,paintevent在需要绘图时自动调用
	void paintEvent(QPaintEvent*)
	{
		QPainter painter(this);
		//pixmapTest(&painter);
		//imageTest(&painter);
		pictureTest(&painter);
	}
	// pixmap
	void pixmapTest(QPainter * painter)
	{
#if 0	// 把图片绘制到矩形中
		QPixmap pix(":/images/snowball.jpg");
		painter->drawPixmap(QRect(0, 0, 50, 50), pix);
#elif 1
		QPixmap pix(":/images/snowball.jpg");
		painter->drawPixmap(0, 50, pix, 0, 0, 100, 50);	// 把图片的宽100高50部分印在坐标{0, 50} 中
#endif
	}
	// image
	void imageTest(QPainter* painter)
	{
		painter->drawImage(0, 0, m_image);
	}
	void pictureTest(QPainter* painter)
	{
#if 0	// 显示天空颜色矩形
		painter->drawLine(0, 200, width(), height());
		painter->fillRect(100, 400, 150, 35, QGradient::Preset::SkyGlider);
#elif 0
		QPicture pic;
		{
			QPainter pt(&pic);
			pt.drawLine(0, 200, width(), height());
			pt.fillRect(100, 400, 150, 35, QGradient::Preset::SkyGlider);
		}
		// 必须要把pt和pic取消关联才能保存,最简单方法就是加{}作用域
		pic.save("txt.pic");	// 保存到文件

		// 用的话直接读取即可
		QPicture picc;
		picc.load("txt.pic");
		//painter->drawPicture(0, 0, picc);
		picc.play(painter);	// 跟上面一样效果
#elif 1
		QImage img(250, 250, QImage::Format_RGBA8888);
		{
			QPainter pt(&img);
			pt.drawLine(0, 0, width(), height());
			pt.fillRect(100, 200, 150, 35, QGradient::Preset::SkyGlider);
		}
		img.save("test.png");		// 保存

		// 重新显示
		painter->drawImage(0, 0, img);
#endif
	}
private:
};


// 主函数
int main(int argc, char* argv[])
{
	QApplication a(argc, argv);	// 创建应用程序类实例

	widget w;
	w.show();

	return a.exec();	// 运行应用程序的主事件循环,直到退出
}

示例-QGame

待写

布局

盒子布局

QBoxLayout可以在水平方向或垂直方向上排列控件,分别派生了 QHBoxLayoutQVBoxLayout 子类

  • QHBoxLayout:水平布局,在水平方向上排列控件,即:左右排列
  • QVBoxLayout:垂直布局,在垂直方向上排列控件,即:上下排列

水平布局、垂直布局除了构造时的方向(LeftToRight、TopToBottom)不同外,其它均相同

公有函数

序号 函数&描述
1 void addLayout(QLayout* layout,int stretch = 0)
将layout添加到框的末端,使用连续拉伸因子拉伸。
2 void addSpacerItem(QSpacerItem * spacerItem)
将spaceeritem添加到该盒子布局的末尾,通常不使用这个函数,请使用addSpacing(int size)
3 void addSpacing(int size)
添加一个大小为size的不可伸缩空间(QSpacerItem)到这个框布局的末尾
4 void addStretch(int stretch = 0)
添加一个可伸缩空间(一个QSpacerItem),最小尺寸为零,拉伸因子stretch到这个框布局的末尾。
5 void addStrut(int size)
限制盒子的垂直尺寸最小为size
6 void addWidget(QWidget* widget,int stretch = 0,Qt::Alignment alignment = 0)
将小部件添加到此框布局的末尾,并使用拉伸因子拉伸和对齐对齐。
7 void setDirection(QBoxLayout::Direction direction)
设置此布局的方向为direction。
8 void setSpacing(int spacing)
设置小部件之间的间距
9 void setStretch(int index,int stretch)
给index位置的控件设置拉伸因子stretch
10 bool setStretchFactor(QWidget* widget,int stretch)
bool setStretchFactor(QWidget* widget,int stretch)
设置小部件的拉伸因子,如果在布局中发现小部件(不包括子布局),则返回true; 否则返回false

简单布局

cpp
// 【测试】
class widget : public QWidget
{
public:
	QImage m_image;
	widget(QWidget* parent = nullptr)
		: QWidget(parent)
	{
		boxLayout();
	}
	void boxLayout()
	{
		// 创建控件
		auto tipLab = new QLabel("用户名");
		auto usernameEdit = new QLineEdit;

		// 创建布局
		auto hlayout = new QHBoxLayout;

		// 把控件添加到布局里面(按顺序)
		hlayout->addWidget(tipLab);
		hlayout->addWidget(usernameEdit);

		// 把布局交给窗口(布局应用在谁上面)
		setLayout(hlayout);
	}
protected:
private:
};

布局嵌套

cpp
// 【测试】
void boxLayout()
{
    //姓名
    QLabel *nameLabel = new QLabel("Name");	// 创建标签控件,用于显示“姓名”
    QLineEdit *nameEdit = new QLineEdit;	// 创建文本框控件,用于输入姓名

    QBoxLayout *nameHlayout = new QBoxLayout(QBoxLayout::Direction::LeftToRight);	// 创建水平布局 其方向设置为从左到右
    nameHlayout->addWidget(nameLabel);	// 将“姓名”标签控件添加到水平布局中
    nameHlayout->addWidget(nameEdit);	// 将姓名文本框控件添加到水平布局中
    //电话
    QLabel *phoneLabel = new QLabel("Phone");
    QLineEdit *phoneEdit = new QLineEdit;

    QBoxLayout *phoneHlayout = new QBoxLayout(QBoxLayout::Direction::LeftToRight);	// 创建水平布局 其方向设置为从左到右
    phoneHlayout->addWidget(phoneLabel);
    phoneHlayout->addWidget(phoneEdit);

    //布局嵌套
    QBoxLayout *mainlayout = new QBoxLayout(QBoxLayout::Direction::TopToBottom);	// 创建垂直布局 其方向设置为从上到下
    mainlayout->addLayout(nameHlayout);	// 将姓名的水平布局添加到垂直布局中
    mainlayout->addLayout(phoneHlayout);	// 将电话号码的水平布局添加到垂直布局中
    this->setLayout(mainlayout);	// 设置窗口的主布局为垂直布局
}

基本使用

cpp
// 【测试】
void boxLayout2()
{
    QPushButton *btn1 = new QPushButton("One");
    QPushButton *btn2 = new QPushButton("Two");
    QPushButton *btn3 = new QPushButton("Three");
    QPushButton *btn4 = new QPushButton("Four");
    QPushButton *btn5 = new QPushButton("Five");

    QHBoxLayout *hlayout = new QHBoxLayout();	// QHBoxLayout---水平布局
    hlayout->addWidget(btn1);
    hlayout->addWidget(btn2);
    hlayout->addWidget(btn3);
    hlayout->addWidget(btn4);
    hlayout->addWidget(btn5);

    this->setLayout(hlayout);	// 水平布局设置为当前窗口的主布局,以实现水平排列的按钮布局效果
}

设置边距

cpp
// 设置边距
hlayout->setContentsMargins(0, 0, 0, 0);

设置间距

cpp
// 设置间距
hlayout->setSpacing(0);

添加拉伸空间(QSpacerItem)

cpp
hlayout->addStretch();	// 添加弹簧
// 可添加弹簧系数

添加控件addWidget

  1. 设置控件大小
cpp
btn3->setFixedHeight(50);	// 控件高度

网格布局

网格布局也叫格栅布局(多行多列)

QGridLayout占用它可用的空间(通过它的父布局或parentWidget()),将它分成行和列,并将它管理的每个小部件放入正确的单元格中

列和行表现相同; 我们将讨论列,但行也有等价的函数

每一列都有一个最小宽度和一个拉伸因子。 最小宽度是使用 setColumnMinimumWidth() 设置的最大宽度和该列中每个小部件的最小宽度。 拉伸因子使用 setColumnStretch() 设置,并确定列将获得的可用空间超过或超过必要的最小空间的多少

公有函数

序号 函数&描述
2 void addLayout(QLayout *layout, int row, int column, Qt::Alignment alignment = 0)
void addLayout(QLayout *layout, int row, int column, int rowSpan, int columnSpan, Qt::Alignment alignment = 0)
将layout放置在网格中的位置(row、column)。 左上角的位置是(0,0)。
跨越多行/多列。 该单元格将从跨rowSpan行和columnSpan列的行、列开始。
3 void addWidget(QWidget *widget, int row, int column, Qt::Alignment alignment = 0)
void addWidget(QWidget *widget, int fromRow, int fromColumn, int rowSpan, int columnSpan, Qt::Alignment alignment = 0)
同上
4 void setRowStretch(int row, int stretch)
将row的拉伸因子设置为stretch
5 void setColumnStretch(int column, int stretch)
将column的拉伸因子设置为stretch
6 void setRowMinimumHeight(int row, int minSize)
将行的最小宽度设置为minSize像素。
7 void setColumnMinimumWidth(int column, int minSize)
将列的最小宽度设置为minSize像素。

测试案例

脑子需要有一个行列思想,大概分成下面这样

cpp
// 【测试】
	void gridLayout()
	{
		// 类型名称也可以使用auto
		QLabel* imageLabel = new QLabel; // 创建一个 QLabel 对象用于显示图片
		QLineEdit* userNamaeEdit = new QLineEdit; // 创建一个 QLineEdit 对象用于输入用户名
		QLineEdit* passwdEdit = new QLineEdit; // 创建一个 QLineEdit 对象用于输入密码
		QCheckBox* rememberCheckBox = new QCheckBox; // 创建一个 QCheckBox 对象用于记住密码
		QCheckBox* autoLoginCheckBox = new QCheckBox; // 创建一个 QCheckBox 对象用于自动登录
		QPushButton* registerBtn = new QPushButton; // 创建一个 QPushButton 对象用于注册账号按钮
		QPushButton* forgetBtn = new QPushButton; // 创建一个 QPushButton 对象用于找回密码按钮
		QPushButton* loginBtn = new QPushButton; // 创建一个 QPushButton 对象用于登录按钮

		// 设置图片
		imageLabel->setFixedSize(90, 90); // 设置图片标签的固定大小
		imageLabel->setPixmap(QPixmap(":/images/snowball.jpg")); // 设置图片标签的图片
		imageLabel->setScaledContents(true); // 设置图片自适应大小

		// 设置输入框
		userNamaeEdit->setPlaceholderText("QQ号码/手机/邮箱"); // 设置用户名输入框的占位文本
		passwdEdit->setPlaceholderText("密码"); // 设置密码输入框的占位文本

		// 设置复选框
		rememberCheckBox->setText("记住密码"); // 设置记住密码复选框的文本
		autoLoginCheckBox->setText("自动登录"); // 设置自动登录复选框的文本
		// 默认选中
		rememberCheckBox->setChecked(true);



		// 设置按钮
		registerBtn->setText("注册账号"); // 设置注册账号按钮的文本
		forgetBtn->setText("找回密码"); // 设置找回密码按钮的文本
		loginBtn->setText("登录"); // 设置登录按钮的文本

		QGridLayout* layout = new QGridLayout; // 创建一个网格布局对象

		// 将控件添加到布局中
		layout->addWidget(imageLabel, 0, 0, 3, 1); // 放第0行第0列 图片标签占据第一列,并跨越三行
		layout->addWidget(userNamaeEdit, 0, 1, 1, 2); // 放第0行第1列 用户名输入框占据第二列,并跨越一行两列
		layout->addWidget(registerBtn, 0, 3); // 放第0行第3列 注册账号按钮占据第四列
		layout->addWidget(passwdEdit, 1, 1, 1, 2); // 放第1行第1列 密码输入框占据第二列,并跨越一行两列
		layout->addWidget(forgetBtn, 1, 3); // 放第1行第3列 找回密码按钮占据第四列
		layout->addWidget(rememberCheckBox, 2, 1); // 放第2行第1列 记住密码复选框占据第二列
		layout->addWidget(autoLoginCheckBox, 2, 2); // 放第2行第2列 自动登录复选框占据第三列
		layout->addWidget(loginBtn, 3, 1, 1, 2); // 放第3行第1列 登录按钮占据第二列,并跨越一行两列

		layout->setHorizontalSpacing(10); // 设置水平间距为10
		layout->setVerticalSpacing(10); // 设置垂直间距为10
		layout->setContentsMargins(20, 20, 20, 20); // 设置布局的边距

		this->setLayout(layout); // 将布局设置给当前窗口
	}

表单布局

QFormLayout 类管理输入小部件的表单及其关联的标签

QFormLayout 是一个方便的布局类,它以两列形式布置其子项。 左列由标签组成,右列由“字段”小部件(行编辑器、旋转框等)组成。 传统上,这种两列表单布局是使用 QGridLayout 实现的。

QFormLayout 是一种更高级别的替代方案,具有以下优点:

  • 遵守不同平台的外观和感觉准则
    例如,macOS Aqua 和 KDE 指南指定标签应该右对齐,而 Windows 和 GNOME 应用程序通常使用左对齐。

  • 支持长行换行

    对于显示较小的设备,QFormLayout可以设置为对长行进行换行,甚至对所有行进行换行。

  • 创建标签-字段对,有非常方便的API
    我们可以通过addRow(const QString &labelText, QWidget *field)来创建一个带有给定文本的QLabel及QWidget控件行,它们可以自动的设置为伙伴关系。

公有函数

序号 函数&描述
1 void addRow(QWidget* label,QWidget* field)
void addRow(QWidget* label,QLayout* field)
使用给定的label和field在此表单布局的底部添加新行
2 void addRow(const QString &labelText, QWidget* field)
void addRow(const QString &labelText, QLayout* field)
这个重载会在后台自动创建一个以labelText作为文本的QLabel。 field被设置为新的QLabel的伙伴
3 void addRow(QWidget *widget)
void addRow(QLayout* layout)
在表单布局的末尾添加指定的小部件。 这个小部件横跨两列
9 void setRowWrapPolicy(QFormLayout::RowWrapPolicy policy)
设置行换行策略
10 void setSpacing(int spacing)
将垂直和水平间距设置为spacing。
11 void setVerticalSpacing(int spacing)
将垂直间距设置为spacing
12 void setWidget(int row, QFormLayout::ItemRole role, QWidget *widget)
将给定的row中的role设置为widget,必要时使用空行扩展布局。 如果单元格已被占用,则不插入小部件,并将错误消息发送到控制台。

测试

cpp
// 测试
void formLayout()
{
    QLineEdit *userEdit = new QLineEdit;
    QLineEdit *passwdEdit = new QLineEdit;
    QPushButton *loginBtn = new QPushButton("确定");

    QFormLayout *fromLayout = new QFormLayout;
    fromLayout->addRow("userName:", userEdit);
    fromLayout->addRow("password:", passwdEdit);
    fromLayout->addWidget(loginBtn);

    this->setLayout(fromLayout);
}

换行策略

使用函数 void setRowWrapPolicy(QFormLayout::RowWrapPolicy policy)

枚举 描述 效果
QFormLayout::DontWrapRows 字段总是放在它们的标签旁边(默认样式)
QFormLayout::WrapLongRows 标签有足够的空间适应,如果字段对的最小大小大于可用空间,输入框会被换到下一行
QFormLayout::WrapAllRows 字段总是在它们的标签下面。
cpp
// 【测试】
void formLayout()
{
	QLineEdit* userEdit = new QLineEdit;
	QLineEdit* passwdEdit = new QLineEdit;
	QPushButton* loginBtn = new QPushButton("确定");

	QFormLayout* fromLayout = new QFormLayout;

	//fromLayout->setRowWrapPolicy(QFormLayout::RowWrapPolicy::DontWrapRows);	// 设置字段总是放在它们的标签旁边
	//fromLayout->setRowWrapPolicy(QFormLayout::RowWrapPolicy::WrapLongRows);	// 字段对的最小大小大于可用空间,输入框会被换到下一行
	fromLayout->setRowWrapPolicy(QFormLayout::RowWrapPolicy::WrapAllRows);	// 总是换行
	fromLayout->addRow("userName:", userEdit);
	fromLayout->addRow("password:", passwdEdit);
	fromLayout->addWidget(loginBtn);

	this->setLayout(fromLayout);
}
cpp
// 【测试】
void formLayout()
{
    QLineEdit *userEdit = new QLineEdit;
    QLineEdit *passwdEdit = new QLineEdit;
    QPushButton *loginBtn = new QPushButton("确定");

    // 下拉框
    auto provinceCmb = new QComboBox;
    for (size_t i = 0; i < 20; i++)
    {
        provinceCmb->addItem("中国" + QString::number(i), i);	// 相当于把对应城市绑定了一个数值i
    }

    connect(provinceCmb, &QComboBox::currentTextChanged, [ = ](const QString & text)
    {
        qDebug() << text << provinceCmb->currentData().toInt();	// 获取城市名称和对应绑定的键值
    });



    QFormLayout *fromLayout = new QFormLayout;

    //fromLayout->setRowWrapPolicy(QFormLayout::RowWrapPolicy::DontWrapRows);	// 设置字段总是放在它们的标签旁边
    //fromLayout->setRowWrapPolicy(QFormLayout::RowWrapPolicy::WrapLongRows);	// 字段对的最小大小大于可用空间,输入框会被换到下一行
    fromLayout->setRowWrapPolicy(QFormLayout::RowWrapPolicy::WrapAllRows);	// 总是换行
    // <font color = red size = 3></font>  设置文本样式
    fromLayout->addRow("<font color = red size = 5>*</font>userName:", userEdit);

    // 添加部件
    fromLayout->addWidget(new QLabel("中文 英文 特殊字符"));

    // 下拉框
    fromLayout->addRow("住址", provinceCmb);

    fromLayout->addRow("<font color = red>*</font>password:", passwdEdit);
    fromLayout->addWidget(loginBtn);

    this->setLayout(fromLayout);
}

堆栈布局

QStackedLayout 继承自 QLayout

QStackedLayout 类提供了多页面切换的布局,一次只能看到一个界面。

QStackedLayout 可用于创建类似于 QTabWidget 提供的用户界面。也有建立在 QStackedLayout 之上的便利类QStackedWidget

分割器

QSplitter类实现了一个分离小部件。 splitter允许用户通过拖动子部件之间的边界来控制它们的大小。 任何数量的小部件都可以由单个拆分器控制。QSplitter的典型用法是创建几个小部件并使用 insertWidget()或addWidget()添加它们

待写

示例-登陆窗口

QMainWindow

默认结构最复杂的标准窗口

  • 提供了菜单栏, 工具栏, 状态栏, 停靠窗口
  • 菜单栏: 只能有一个, 创建的最上方
  • 工具栏: 可以有多个, 默认提供了一个, 窗口的上下左右都可以停靠
  • 状态栏: 只能有一个, 窗口最下方
  • 停靠窗口: 可以有多个, 默认没有提供, 窗口的上下左右都可以停靠

菜单栏

  • 创建MenuBar

Ui界面Qt Designer

菜单栏添加项的话如果需要输入中文只能粘贴,直接打中文是不行的

转到槽

递归搜索给定对象的所有子对象,并将来自它们的匹配信号连接到遵循以下形式的对象槽:

css
void on_<object name>_<signal name>(<signal parameters>);

让我们假设我们的对象有一个 QPushButton 类型的子对象,对象名为 button1。 捕捉按钮的 clicked() 信号的插槽是:

css
void on_button1_clicked();

如果对象本身具有正确设置的对象名称,则其自身的信号也连接到其各自的插槽

关联ui界面中对象的信号

控件需要设置最小和最大值,不然可能没效果会恢复原样

右键控件可以选择跳转槽,然后会生成槽函数

Ui界面里面的对象可以通过 ui 指针去访问

注意点

拉了一个控件过去后如果想复制多个,可以按住 Ctrl 键然后拖即可

做界面布局整体思路:

(1)画界面时,先不要考虑任何布局,直接先把控件拖拉到相应的位置

(2)先从最内层开始布局。也就是说从内到外,从小到大的顺序开始布局

(3)内部的、小的控件布局完成后,再把这些布局作为控件考虑,完成外面的布局

(4)最后调整layout的参数,和控件的大小、位置

自定义控件

自定义控件又有两种方法

  1. 提升法
  2. 插件法

提升法 — 1

  1. 新建界面类
  • 先创建一个带界面文件的项目,然后右击项目名称,【添加新文件…】
  • 选择 【Qt】->【Qt设计师界面类】
  • 界面模板选择【Widget】,并修改好类名为 Scheckbox,点击下一步,直至完成
  • 将创建的界面类的文件添加到【CMakeLists.txt】中,并保存
  • 这边我要自定义的是一个开关控件,所以选择了在ui界面上添加了一个label,修改控件名称并调整合适大小
  1. 初始化界面样式
  • 将下面两种图片添加到,资源文件中(资源文件要添加到CMakeLists.txt文件中)

  • 回到 scheckbox.ui 文件,选中【lable】,找到属性中的【styleSheet】,添加资源,选择【image】,点击【OK】

  • 这里将label的文本置空,同时修改控件的objectName,可以调整一下大小

  1. 主界面进行提升
  • 回到widget.ui,界面中添加一个widget控件,修改名称后选中控件,右键提升为…
  • 新建提升的类,这里输入前面添加的ui界面设计师类名,点击添加
  • 勾选中刚刚添加的ImageSwitch类,选择提升
  • 编译运行(可能会出现头文件包含错误,解决方法见附录)

注意:没有UI界面文件的类也可以提升

提升法 — 2

  1. 只需要创建自定义控件的 .cpp 和 .h
  2. 在工程ui里面进行添加一个控件(这个看你自定义控件类继承于哪个,一般是继承于QWidget,那就拖一个【widget】进来)
  3. 然后右键提升,这里输入前面添加的自定义控件的类名,点击添加(可以不需要全局,全局区别是 " "<>),名字也可以改一下,这个控件是透明的,相当于占位,可以拉大拉小来调整大小
  4. 然后出现头文件报错的话看附录解决方法

【程序示例】

需要去ui界面添加对应的控件,这里直接拖12个widget改名称即可

widget.h
cpp
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui
{
    class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void initForm();
    void initBtn1();
    void initBtn2();
    void initBtn3();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp
#include "widget.h"
#include "./ui_widget.h"

#define color1 QColor(34, 163, 169)
#define color2 QColor(162, 121, 197)
#define color3 QColor(255, 107, 107)
#define color4 QColor(72, 103, 149)

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->initForm();
}

Widget::~Widget()
{
    delete ui;
}

void Widget::initForm()
{
    this->initBtn1();
    this->initBtn2();
    this->initBtn3();
}

void Widget::initBtn1()
{
    ui->switchButton11->setBgColorOn(color1);
    ui->switchButton12->setBgColorOn(color2);
    ui->switchButton13->setBgColorOn(color3);
    ui->switchButton14->setBgColorOn(color4);

    ui->switchButton11->setShowText(false);
    ui->switchButton12->setShowText(false);
    ui->switchButton13->setShowText(true);
    ui->switchButton14->setShowText(true);
    ui->switchButton12->setShowCircle(true);
    ui->switchButton14->setAnimation(true);
    ui->switchButton12->setChecked(true);
    ui->switchButton14->setChecked(true);

    ui->switchButton13->setTextOff("停止");
    ui->switchButton13->setTextOn("启动");
    ui->switchButton14->setTextOff("禁用");
    ui->switchButton14->setTextOn("启用");
}

void Widget::initBtn2()
{
    ui->switchButton21->setButtonStyle(SwitchButton::ButtonStyle_Rect);
    ui->switchButton22->setButtonStyle(SwitchButton::ButtonStyle_Rect);
    ui->switchButton23->setButtonStyle(SwitchButton::ButtonStyle_Rect);
    ui->switchButton24->setButtonStyle(SwitchButton::ButtonStyle_Rect);

    ui->switchButton21->setBgColorOn(color1);
    ui->switchButton22->setBgColorOn(color2);
    ui->switchButton23->setBgColorOn(color3);
    ui->switchButton24->setBgColorOn(color4);

    ui->switchButton21->setShowText(false);
    ui->switchButton22->setShowText(false);
    ui->switchButton23->setShowText(true);
    ui->switchButton24->setShowText(true);
    ui->switchButton22->setShowCircle(true);
    ui->switchButton24->setAnimation(true);
    ui->switchButton22->setChecked(true);
    ui->switchButton24->setChecked(true);

    ui->switchButton23->setTextOff("停止");
    ui->switchButton23->setTextOn("启动");
    ui->switchButton24->setTextOff("禁用");
    ui->switchButton24->setTextOn("启用");
}

void Widget::initBtn3()
{
    ui->switchButton31->setButtonStyle(SwitchButton::ButtonStyle_CircleOut);
    ui->switchButton32->setButtonStyle(SwitchButton::ButtonStyle_CircleOut);
    ui->switchButton33->setButtonStyle(SwitchButton::ButtonStyle_CircleOut);
    ui->switchButton34->setButtonStyle(SwitchButton::ButtonStyle_CircleOut);

    int space = 8;
    ui->switchButton31->setSpace(space);
    ui->switchButton32->setSpace(space);
    ui->switchButton33->setSpace(space);
    ui->switchButton34->setSpace(space);

    int radius = 8;
    ui->switchButton31->setRectRadius(radius);
    ui->switchButton32->setRectRadius(radius);
    ui->switchButton33->setRectRadius(radius);
    ui->switchButton34->setRectRadius(radius);

    ui->switchButton31->setBgColorOn(color1);
    ui->switchButton32->setBgColorOn(color2);
    ui->switchButton33->setBgColorOn(color3);
    ui->switchButton34->setBgColorOn(color4);

    ui->switchButton31->setSliderColorOn(color1);
    ui->switchButton32->setSliderColorOn(color2);
    ui->switchButton33->setSliderColorOn(color3);
    ui->switchButton34->setSliderColorOn(color4);

    ui->switchButton31->setShowText(false);
    ui->switchButton32->setShowText(false);
    ui->switchButton33->setShowText(true);
    ui->switchButton34->setShowText(true);
    ui->switchButton34->setAnimation(true);
    ui->switchButton32->setChecked(true);
    ui->switchButton34->setChecked(true);
}

用Ui文件生成头文件

有时候只有一个 ui文件,那怎么用呢?

  1. 打开终端,并切换工作目录到.ui文件所在的目录
  2. 输入下面命令(xxx表示你的ui名称),输入完就会生成.h了

里面的类名也可以修改,不过文件名也要对应(反正搜索一下几处地方有这个就全部替换)

bash
uic xxx.ui -o ui_xxx.h
  1. 如何使用头文件?

只要能设法调用Ui_xxx类的setupUi函数就行了

cpp
/*widget.h*/

// 1. 前置声明
namespace Ui 
{
    class widget;
}

// 2. 类中定义
private:
    Ui::widget* ui;


/*widget.cpp*/

//  3. 申请内存,设置ui
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    ,ui(new Ui::widget)	//给ui指针分配内存
{
        ui->setupUi(this);	//给当前窗口this,设置ui界面
}

QSS样式表

翻看Qt官方文档,所有控件都有案例:在索引栏输入Qt Style Sheets

盒子模型

盒子模型是qss技术所使用的一种思维模型。盒子模型是指将界面设计页面中的内容元素看作一个个装了东西的矩形盒子。每个矩形盒子包括: 内容(content)、内边距(padding)、边框(border)、外边距(margin)

  • 内容(Content)
    • 内容区是盒子模型的中心,它呈现了盒子的主要信息内容,这些内容可以是文本、图片等多种类型
  • 内边距/填充(Padding)
    • 内边距内容区边框之间的空间。填充的属性有五种 ,即 padding -top、padding -bottom、padding -left、padding-right 以及综合了以上四种方向的快捷填充属性 padding。使用这五种属性可以指定内容区信息内容与各方向边框间的距离
  • 边框(Border)
    • 边框是环绕内容区和填充的边界。边框的属性有 border-style、border-width和border-color 以及综合了以上三类属性的快捷边框属性 border
    • border-style 属性是边框最重要的属性,如果没有指定边框样式,其他的边框属性都会被忽略,边框将不存在。qss规定了dotted(点线)、dashed(虚线)、solid(实线)等十一种边框样式
    • 使用border-width属性可以指定边框的宽度
    • 使用border-color属性可以为边框指定相应的颜色,其属性值可以是RGB值,也可以是CSS 规定的颜色名
  • 外边距(Margin)
    • 空白边位于盒子的最外围,是添加在边框外周围的空间。空白边使盒子之间不会紧凑地连接在一起,是CSS 布局的一个重要手段。空白边的属性有五种 ,即 margin-top、margin-bottom、margin- left、margin-right 以及综合了以上四种方向的快捷空白边属性 margin,其具体的设置和使用与填充属性类似。对于两个相邻的(水平或垂直方向 )且设置有空白边值的盒子,他们邻近部分的空白边将不是二者空白边的相加,而是二者的并集。若二者邻近的空白边值大小不等,则取二者中较大的值。同时,CSS 容许给空白边属性指定负数值,当指定负空白边值时,整个盒子将向指定负值方向的相反方向移动,以此可以产生盒子的重叠效果。采用指定空白边正负值的方法可以移动网页中的元素

选择器

QT样式表支持CSS2定义的所有选择器

选择器类型

选择器 示例 描述
通用选择器 * 匹配所有控件
类型选择器 QPushButton 匹配给定类型控件,包括子类
类选择器 .QPushButton 匹配给定类型控件,不包括子类
属性选择器 QPushButton[flat=“false”] 匹配给定类型控件中符合[属性]的控件
ID选择器 QPushButton#closeBtn 匹配给定类型,且对象名为closeBtn的控件
子孙对象选择器 QDialog QPushButton 匹配给定类型的子孙控件
子对象选择器 QDialog>QPushButton 匹配给定类型的直接子控件
辅助(子控件)选择器 QComboBox::drop-down 复杂对象的子控件
伪状态选择器 QPushButton:hover 控件的特定状态下的样式
并集选择器

伪状态选择器

状态 描述
:disabled 控件禁用
:enabled 控件启用
:focus 控件获取输入焦点
:hover 鼠标在空间上悬停
:pressed 鼠标按下
:checked 控件被选中
:unchecked 控件没有选中
:indeterminate 控件部分被选中
:open 控件
:closed 空间关闭
:on 控件可以切换,且处于on状态
:off 控件可以切换,且处于off状态
! 对以上状态的否定

CSS方式

创建一个css文件,在里面进行编写样式代码,然后把这个文件放入QRC里,然后通过文件读取的方式读取即可

cpp
#include<QFile>

// 从文件加载,然后设置样式表
QFile file(":/QSS/style.css");
if (file.open(QIODevice::ReadOnly))
{
    setStyleSheet(file.readAll());
}

控件示例

QLable标签

  • 设置字体样式

font-family 为设置字体类型,标准形式需要加双引号,不加也可能会生效,具体看系统是否支持,中英文都支持,但要保证字体编码支持,一般程序编码为"utf-8"时没问题。

font-size 为设置字体大小,单位一般使用 px 像素

font-style 为设置字体斜体样式,italic 为斜体, normal 为不斜体

font-weight 为设置字体加粗样式,bold 为加粗, normal 为不加粗

font 为同时设置字体 style weight size family 的样式,但是 style 和 weight 必须出现在开头,size 和 family 在后面,而且 size 必须在 family 之前,否则样式将不生效,font 中不能设置颜色,可以单独设置 style weight 和 size,不能单独设置 family

color 为设置字体颜色,可以使用十六进制数表示颜色,也可以使用某些特殊的字体颜色:red, green, blue 等,或者使用 rgb(r,g,b) 和 rgba(r,g,b,a) 来设置,其中 r、g、b、a 值为0~255,如果想不显示颜色可以设置值为透明 transparent

cpp
void Widget::test_qss()
{
	auto btn = new QPushButton("啊啊啊,你好野", this);
	auto lab = new QLabel("hello,我是杨", this);
	auto lab0 = new QLabel("好的,你的", this);

	// 设置大小
	lab->resize(100, 100);
	// 设置位置,大小
	lab0->setGeometry(0, 250, 200, 150);
#if 0
	//直接给label设置样式(太麻烦)
	lab->setStyleSheet("font-family:'楷体'; font-size:26px; font-style:italic; font-weight:bold; color:rgb(27,115,27); background-color:#F5CC84;");
	lab0->setStyleSheet("font-family:'楷体'; font-size:26px; font-style:italic; font-weight:bold; color:rgb(27,115,27); background-color:#F5CC84;");
#elif 0
	// 给所有的控件设置样式
	this->setStyleSheet("font-family: '楷体'; font-size: 11px;");
	// 单独给一个控件类型加样式  "QLabel{....}"
	this->setStyleSheet(R"(QLabel
    {
        font-family: '楷体';
        font-size: 26px;
        color:red;
    })");
#elif 1
	// 从文件加载,然后设置样式表
	QFile file(":/QSS/style.css");
	if (file.open(QIODevice::ReadOnly))
	{
		setStyleSheet(file.readAll());
	}
#endif
}
css
QLable
{
	    /*---------------设置字体样式--------------------*/
    /*分开设置*/
    /*font-family: "楷体";
    font-size: 20px;
    font-style: italic;
    font-weight:bold ;
    /*快捷设置*/
    font: bold italic 18px "微软雅黑";
    color: cornflowerblue;    
}
  • 文字位置
css
QLable
{
    /*---------------文字位置--------------------*/
    padding-left: 10px;
    padding-top: 8px;
    padding-right: 7px;
    padding-bottom: 9px;
}
  • 边框样式
css
QLable
{
       /*---------------边框样式--整体设置--------------------*/
   /*分开设置*/
   /*    border-style: solid;
   border-width: 2px;
   border-color: darkgoldenrod;*/
   /*快捷设置*/
   /*    border: 2px solid red;*/
   /*---------------边框样式--单独设置某条边框的样式--------------------*/
   /*    border-left: 2px solid red;
   border-top: 2px solid black;
   border-right: 2px solid blue;
   border-bottom-color: transparent; 下边框透明,不显示*/
   /*---------------边框样式--设置边框半径(圆角)--------------------*/
   /*    border-left: 2px solid red;
   border-top: 2px solid black;
   border-right: 2px solid blue;
   border-bottom: 2px solid yellow;
   border-top-left-radius: 20px;
   border-top-right-radius: 15px;
   border-bottom-left-radius: 10px;
   border-bottom-right-radius: 5px;*/
   /*border-radius: 20px;*/
}
  • 背景样式
css
QLable
{
    /*---------------背景样式--------------------*/
	background-color: #2E3648;
    background-image: url("./image.png");
    background-repeat: no-repeat;
    background-position: left center;
    /*background: url("./image.png") no-repeat left center #2E3648;*/
}

QPushButton按钮

cpp
#include "widget.h"
#include "ui_widget.h"
#include<QLabel>
#include<QPushbutton>
#include<QFile>

Widget::Widget(QWidget* parent)
	: QWidget(parent)
	, ui(new Ui::Widget)
{
	ui->setupUi(this);

	test_qss();
}

Widget::~Widget()
{
	delete ui;
}

void Widget::test_qss()
{
	auto btn = new QPushButton("你好呀", this);

	btn->setObjectName("btn");	// 设置对象名称

	// 从文件加载,然后设置样式表
	QFile file(":/QSS/style.css");
	if (file.open(QIODevice::ReadOnly))
	{
		setStyleSheet(file.readAll());
	}
	else
	{
		qDebug() << "error";
	}
}
css
QPushButton#btn {
    /*1*/
    border: none; /*去掉边框*/
    border-radius: 10px;
    /*1,添加图片*/
/*    background-image: url(:/images/quit.png);*/
    background-repeat: none;
    background-position: center;
    /*3,把图片作为边框,会自动铺满背景*/
/*    border-image: url(:/images/quit.png);*/
}
/*鼠标悬停*/
QPushButton:hover {
    background-color: rgba(102,205,227,255);
}

/*鼠标按下*/
QPushButton:pressed {
    background-color: rgb(255, 106, 0);
}

QCheckBox复选框、QRadioButton单选按钮

cpp
#include "widget.h"
#include "ui_widget.h"
#include<QLabel>
#include<QPushbutton>
#include<QFile>
#include<QCheckbox>
#include<QRadiobutton>

Widget::Widget(QWidget* parent)
	: QWidget(parent)
	, ui(new Ui::Widget)
{
	ui->setupUi(this);

	test_qss();
}

Widget::~Widget()
{
	delete ui;
}

void Widget::test_qss()
{
	auto box = new QCheckBox("选我", this);
	auto button = new QRadioButton("点我", this);

	box->move(50, 50);

	box->setObjectName("box");
	button->setObjectName("button");

	// 从文件加载,然后设置样式表
	QFile file(":/QSS/style.css");
	if (file.open(QIODevice::ReadOnly))
	{
		setStyleSheet(file.readAll());
	}
	else
	{
		qDebug() << "error";
	}
}
css
QCheckBox#box {
    color: red;
}
    /*定义 QCheckBox 中复选框的样式*/
    QCheckBox#box::indicator {
        width: 20px;
        height: 20px;
        /*填充那个复选框内*/
        border-image: url(:/images/zzz.jpg);
    }
/*定义复选框被选中时的样式*/
QCheckBox#boxQCheckBox#box::indicator:checked {
    /*    border-image: url(:/images/checkbox-checked.png);*/
}

/*当复选框未选中且鼠标悬停在上面时应用的样式*/
QCheckBox#box::indicator:unchecked:hover {
    /*    border-image: url(:/images/checkbox-unchecked-hover.png);*/
}

/*当复选框被选中且鼠标悬停在上面时应用的样式*/
QCheckBox#box::indicator:checked:hover {
    /*    border-image: url(:/images/checkbox-checked-hover.png);*/
}

QGroupBox组合框

cpp
#include "widget.h"
#include "ui_widget.h"
#include<QLabel>
#include<QPushbutton>
#include<QFile>
#include<QCheckbox>
#include<QRadiobutton>
#include<QGroupbox>


Widget::Widget(QWidget* parent)
	: QWidget(parent)
	, ui(new Ui::Widget)
{
	ui->setupUi(this);

	test_qss();
}

Widget::~Widget()
{
	delete ui;
}

void Widget::test_qss()
{
	auto gbox = new QGroupBox("选我", this);

	gbox->move(50, 50);

	gbox->setObjectName("gbox");

	// 从文件加载,然后设置样式表
	QFile file(":/QSS/style.css");
	if (file.open(QIODevice::ReadOnly))
	{
		setStyleSheet(file.readAll());
	}
	else
	{
		qDebug() << "error";
	}
}
css
/* 设置组合框的背景渐变色和边框样式 */
QGroupBox#gbox {
    background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #E0E0E0, stop: 1 #EEEEEE); /* 设置背景渐变色 */
    border: 2px solid gray; /* 设置边框样式 */
    border-radius: 5px; /* 设置边框圆角 */
    margin-top: 10px; /* 在顶部留出空间给标题 */
}

    /* 设置组合框标题的样式 */
    QGroupBox#gbox::title {
        subcontrol-origin: margin;
        subcontrol-position: top center; /* 位于顶部中间位置 */
        padding: 2px 3px;
        color: white; /* 设置标题文字颜色 */
        margin-top: 2px;
        background-color: gray; /* 设置标题背景色 */
        border-radius: 3px; /* 设置标题背景圆角 */
        spacing: 5px; /* 设置标题内部控件之间的间距 */
    }

    /* 设置组合框指示器的样式 */
    QGroupBox#gbox::indicator {
        width: 13px;
        height: 13px;
        border: 1px solid black; /* 设置指示器边框样式 */
        background: white; /* 设置指示器背景色 */
    }

        /* 设置选中状态下组合框指示器的样式 */
        QGroupBox#gbox::indicator:checked {
            background: yellow; /* 设置选中状态下指示器的背景色 */
        }

QComboBox下拉框

cpp
#include "widget.h"
#include "ui_widget.h"
#include<QLabel>
#include<QPushbutton>
#include<QFile>
#include<QCheckbox>
#include<QRadiobutton>
#include<QGroupbox>
#include<QComboBox>


Widget::Widget(QWidget* parent)
	: QWidget(parent)
	, ui(new Ui::Widget)
{
	ui->setupUi(this);

	test_qss();
}

Widget::~Widget()
{
	delete ui;
}

void Widget::test_qss()
{
	auto gbox = new QComboBox(this);

	gbox->move(50, 50);
	gbox->setObjectName("gbox");
	// 添加选项
	gbox->addItem("Option 1");
	gbox->addItem("Option 2");
	gbox->addItem("Option 3");

	// 从文件加载,然后设置样式表
	QFile file(":/QSS/style.css");
	if (file.open(QIODevice::ReadOnly))
	{
		setStyleSheet(file.readAll());
	}
	else
	{
		qDebug() << "error";
	}
}
css
/* 设置 QComboBox 样式 */
QComboBox#gbox {
    color: black; /* 文字颜色为黑色 */
    border: 1px solid black; /* 边框为 1 像素的黑色实线 */
    border-radius: 5px; /* 边框圆角半径为 5 像素 */
    padding: 1px; /* 内边距为 1 像素(上、右、下、左) */
}

    /* 设置下拉按钮样式 */
    QComboBox#gbox::drop-down {
        width: 25px; /* 下拉按钮宽度为 25 像素 */
    }

        QComboBox#gbox::drop-down:hover {
            border-image: url(:/images/zzz.jpg); /* 鼠标悬停时显示自定义背景图片 */
        }

        QComboBox#gbox::drop-down:checked {
            /*    border-image: url(:/images/comboBox/drop-down-on.png); */ /* 当下拉按钮被选中时显示自定义背景图片 */
        }

            QComboBox#gbox::drop-down:checked:hover {
                /* border-image: url(:/images/comboBox/drop-down-on-hover.png); */ /* 当下拉按钮被选中且鼠标悬停时显示自定义背景图片 */
            }

    /* 设置下拉菜单样式 */
    QComboBox#gbox QAbstractItemView {
        border: none; /* 下拉菜单无边框 */
        background-color: rgb(255, 255, 255); /* 背景颜色为纯白色 */
        outline: 0px; /* 去除轮廓线 */
    }

        /* 设置下拉菜单每一项样式 */
        QComboBox#gbox QAbstractItemView::item {
            height: 50px; /* 每一项高度为 50 像素 */
            /* 设置高度不生效,需要给 QComboBox 设置如下属性(二选一) */
            /* 
    1. usernameEdit->setItemDelegate(new QStyledItemDelegate);
    2. usernameEdit->setView(new QListView);
    */
        }

QSpinBox数值调节框、QTimeEdit时间编辑框、QDateEdit日期编辑框、QDateTimeEdit日期时间编辑框

  • up-button 显示在 QSpinBox 里,它的 subcontrol-origin 是相对于 QSpinBox的
  • down-button 显示在 QSpinBox 里,它的 subcontrol-origin 是相对于 QSpinBox的
  • up-arrow 显示在 up-button 里,它的 subcontrol-origin 是相对于 up-button 的
  • down-arrwo 显示在 down-button 里,它的 subcontrol-origin 是相对于 down-button 的
css
/* 设置 QSpinBox 样式 */
QSpinBox {
    border: 1px solid black; /* 边框为 1 像素的黑色实线 */
    border-radius: 5px; /* 边框圆角半径为 5 像素 */
}

    /* 设置按钮样式 */
    QSpinBox:down-button,
    QSpinBox:up-button {
        width: 16px; /* 按钮宽度为 16 像素 */
        height: 15px; /* 按钮高度为 15 像素 */
        subcontrol-origin: padding; /* 子控件原点为 padding */
        background: white; /* 背景颜色为白色 */
        border: 2px solid rgb(217, 217, 217); /* 边框为 2 像素的 rgb(217, 217, 217) 实线 */
        border-radius: 5px; /* 边框圆角半径为 5 像素 */
    }

    QSpinBox:down-button {
        subcontrol-position: left center; /* 子控件位置为左侧居中 */
    }

    QSpinBox:up-button {
        subcontrol-position: right center; /* 子控件位置为右侧居中 */
    }

        QSpinBox:down-button:hover,
        QSpinBox:up-button:hover {
            border: 2px solid rgb(138, 138, 138); /* 鼠标悬停时边框为 2 像素的 rgb(138, 138, 138) 实线 */
        }

    /* 设置箭头样式 */
    QSpinBox:down-arrow {
        border-image: url(:/images/spinBox/down-arrow.png); /* 下降箭头使用自定义边框图片 */
    }

    QSpinBox:up-arrow {
        border-image: url(:/images/spinBox/up-arrow.png); /* 上升箭头使用自定义边框图片 */
    }

    QSpinBox:down-arrow:hover {
        border-image: url(:/images/spinBox/down-arrow-hover.png); /* 鼠标悬停时下降箭头使用自定义边框图片 */
    }

    QSpinBox:up-arrow:hover {
        border-image: url(:/images/spinBox/up-arrow-hover.png); /* 鼠标悬停时上升箭头使用自定义边框图片 */
    }

QSlider滑块

QSlider 的 subcontrol 有 ::groove(槽),::handle::add-page::sub-page

  • groove 显示在 QSlider 里,它的 subcontrol-origin 是相对于 QSlider 的
  • handle 显示在 groove 里,它的 subcontrol-origin 是相对于 groove 的
  • sub-page 显示在 groove 里,它的 subcontrol-origin 是相对于 groove 的
  • add-page 显示在 groove 里,它的 subcontrol-origin 是相对于 groove 的
  • handle, sub-page, add-page 虽然都显示在 groove 里,但是都可以把它们扩展到 groove 外
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QLabel>
#include<QPushbutton>
#include<QFile>
#include<QCheckbox>
#include<QRadiobutton>
#include<QGroupbox>
#include<QComboBox>
#include<QSpinbox>
#include<QSlider>


Widget::Widget(QWidget* parent)
	: QWidget(parent)
	, ui(new Ui::Widget)
{
	ui->setupUi(this);

	test_qss();
}

Widget::~Widget()
{
	delete ui;
}

void Widget::test_qss()
{
	// 创建滑块控件
	QSlider* slider = new QSlider(Qt::Horizontal, this);
	// 设置滑块范围和初始值
	slider->setMinimum(0);
	slider->setMaximum(100);
	slider->setValue(50);

	// 从文件加载,然后设置样式表
	QFile file(":/QSS/style.css");
	if (file.open(QIODevice::ReadOnly))
	{
		setStyleSheet(file.readAll());
	}
	else
	{
		qDebug() << "error";
	}
}
css
QSlider::groove:horizontal {
    /* 滑块轨道的样式 */
    border: 1px solid skyblue; /* 边框样式 */
    background-color: skyblue; /* 背景颜色 */
    height: 10px; /* 高度 */
    border-radius: 5px; /* 边框圆角 */
}

QSlider::handle:horizontal {
    /* 滑块的样式 */
    background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0.7 white,stop:0.8 rgb(143,212,255)); /* 背景渐变 */
    width: 20px; /* 宽度 */
    border-radius: 10px; /* 边框圆角 */
    margin-top: -5px; /* 顶部外边距 */
    margin-bottom: -5px; /* 底部外边距 */
}

QSlider::sub-page:horizontal {
    /* 已选择部分的样式 */
    background: #999; /* 背景颜色 */
    margin: 5px; /* 外边距 */
    border-radius: 5px; /* 边框圆角 */
}

QSlider::add-page:horizontal {
    /* 未选择部分的样式 */
    background: #666; /* 背景颜色 */
    margin: 5px; /* 外边距 */
    border-radius: 5px; /* 边框圆角 */
}

QProgressBar进度条

对于 QProgressBar 的 QSS,大多数都是想把 chunk 定义为圆角矩形的样子,但是当它的 value 比较小时,chunk 的圆角会变成直角,即使使用图片都不行,效果很不理想,所以如果要修改 QProgressBar 的外观的话,推荐继承 QProgressBar 自己绘制或者使用 QStyle

  • 样式1
css
/* 边框样式 */
QProgressBar {
    border: 1px solid skyblue; /* 边框样式 */
    border-radius: 5px; /* 边框圆角 */
    height: 10px; /* 高度 */
    text-align: center; /* 文本对齐方式 */
}

    /* 进度条样式 */
    QProgressBar::chunk {
        background-color: steelblue; /* 背景颜色 */
        border-radius: 5px; /* 圆角 */
    }

  • 样式2
css
/* 进度条样式 */
QProgressBar {
    border-color: 1px solid blue; /* 边框颜色 */
    border-radius: 5px; /* 边框圆角 */
    text-align: center; /* 文本对齐方式 */
}

/* 进度块样式 */
QProgressBar::chunk {
    background-color: aqua; /* 设置进度块的颜色 */
    width: 5px; /* 进度块的宽度 */
    margin: 0.5px; /* 进度块之间的间隔 */
}
  • 样式3

会显示繁忙一直移动

cpp
#include "widget.h"
#include "ui_widget.h"
#include<QLabel>
#include<QPushbutton>
#include<QFile>
#include<QCheckbox>
#include<QRadiobutton>
#include<QGroupbox>
#include<QComboBox>
#include<QSpinbox>
#include<QSlider>
#include <QProgressBar>

Widget::Widget(QWidget* parent)
	: QWidget(parent)
	, ui(new Ui::Widget)
{
	ui->setupUi(this);

	test_qss();
}

Widget::~Widget()
{
	delete ui;
}

void Widget::test_qss()
{
	QProgressBar* progressBar = new QProgressBar(this); // 创建一个进度条控件
	progressBar->move(20, 20);
	progressBar->setMinimum(0); // 设置最小值
	progressBar->setMaximum(100); // 设置最大值
	progressBar->setValue(50); // 设置当前值
	//progressBar->setRange(0, 0);	// 显示繁忙一直移动

	// 从文件加载,然后设置样式表
	QFile file(":/QSS/style.css");
	if (file.open(QIODevice::ReadOnly))
	{
		setStyleSheet(file.readAll());
	}
	else
	{
		qDebug() << "error";
	}
}

QToolTip工具提示

cpp
#include "widget.h"
#include "ui_widget.h"
#include<QLabel>
#include<QPushbutton>
#include<QFile>
#include<QCheckbox>
#include<QRadiobutton>
#include<QGroupbox>
#include<QComboBox>
#include<QSpinbox>
#include<QSlider>
#include <QProgressBar>
#include<QTooltip>

Widget::Widget(QWidget* parent)
	: QWidget(parent)
	, ui(new Ui::Widget)
{
	ui->setupUi(this);

	test_qss();
}

Widget::~Widget()
{
	delete ui;
}

void Widget::test_qss()
{
	QPushButton* button = new QPushButton("Button", this);
	button->setToolTip("This is a tooltip"); // 设置工具提示文本

	// 从文件加载,然后设置样式表
	QFile file(":/QSS/style.css");
	if (file.open(QIODevice::ReadOnly))
	{
		setStyleSheet(file.readAll());
	}
	else
	{
		qDebug() << "error";
	}
}
css
/* 工具提示样式 */
QToolTip {
    border: 1px solid black; /* 边框样式 */
    background-color: #ffa02f; /* 背景颜色 */
    padding: 1px; /* 内边距 */
    border-radius: 3px; /* 边框圆角 */
}

细节、注意事项

  • 继承自QWidget的类,设置qss样式表没有效果

定义一个类STitleBar,继承自QWidget,使用样式表设置背景颜色之后,发现没有任何效果

css
QWidget#titleBar{
    background-color:rgb(52,52,52);
}

解决方法也很简单,使用 style()->drawPrimitive(QStyle::PE_Widget...),也就是画一个最简单最原始的QWidget,不要牵扯其它这么多东西

cpp
void STitleBar::paintEvent(QPaintEvent* ev)
{
    // 初始化样式选项
	QStyleOption op;
	op.initFrom(this);
	// 创建绘图对象
	QPainter painter(this);
    // 使用样式绘制小部件(即窗口标题栏)
	style()->drawPrimitive(QStyle::PE_Widget, &op, &painter,this);
}