【Qt】常用控件

发布于:2024-07-20 ⋅ 阅读:(168) ⋅ 点赞:(0)


QWidget

所有控件的父类

enabled

API Intro
isEnabled() 返回控件是否可用,可用为true,不可用为false
setEnable(bool) 设置控件的可用状态,可用为true,不可用为false

geometry

位置与尺寸,位置指的是控件在父控件中的坐标。

  • x 横坐标
  • y 纵坐标
  • width 宽度
  • height 高度

image-20240627094751437

API Intro
geometry() 获取到控件的位置和尺寸,返回结果是⼀个QRect
setGeometry(QRect) 使用QRect对象,设置控件的位置和尺寸
setGeometry(int x,int y, int width,int height) 分四个属性设置控件的位置和尺寸

QRect: 保存位置和尺寸的对象,包含了x, y, width, height,其中x, y是控件左上⻆的坐标。其接口setX(), setY()只是修改控件左上角点的坐标,width和height会随之改变。

示例:点击操控某个控件的尺寸不变,上下左右移动

void Widget::on_pushButton_up_clicked()
{
    QRect rect = ui->target->geometry();
    ui->target->setGeometry(rect.x(), rect.y()-20, rect.width(), rect.height());
}

void Widget::on_pushButton_down_clicked()
{
    QRect rect = ui->target->geometry();
    ui->target->setGeometry(rect.x(), rect.y()+20, rect.width(), rect.height());
}

void Widget::on_pushButton_left_clicked()
{
    QRect rect = ui->target->geometry();
    ui->target->setGeometry(rect.x()-20, rect.y(), rect.width(), rect.height());
}

void Widget::on_pushButton_right_clicked()
{
    QRect rect = ui->target->geometry();
    ui->target->setGeometry(rect.x()+20, rect.y(), rect.width(), rect.height());
}

window frame

image-20240626115139429

  • 如果widget作为⼀个窗⼝(带有标题栏,最⼩化,最⼤化,关闭按钮),那么在计算尺⼨和坐标的
    时候就有两种算法.包含windowframe和不包含windowframe.
  • 其中x(),y(),frameGeometry(),pos(),move()都是按照包含windowframe的⽅式来计算
    的.
  • 其中geometry(),width(),height(),rect(),size()则是按照不包含windowframe的⽅式来计
    算的.
  • 当然,如果⼀个不是作为窗⼝的widget,上述两类⽅式得到的结果是⼀致的.

windowTitle

控件的窗口标题

API Intro
windowTitle() 获取控件的窗口标题
setWindowTitle(const QString& title) 设置控件的窗口标题

上述设置操作针对不同的widget可能会有不同的⾏为。如果是顶层widget(独⽴窗⼝),这个操作才会有效。

windowIcon

控件的窗口图标

API Intro
windowIcon() 获取控件的窗口图标,返回QIcon对象
setWindowIcon(const QIcon& icon) 设置控件的窗口图标

同windowTitle,上述操作仅针对顶层widget有效

对于自定义图标,可用先创建一个QPixmap对象,导入自定义图片(qrc),再用该对象创建QIcon

qrc资源管理

  1. 右键项⽬,创建⼀个QtResourceFile(qrc⽂件),⽂件名随意起(不要带中⽂),此处叫做resource.qrc。在qrc编辑器中,添加前缀,表示图片在Qt项目中的相对路径,供代码中使用。添加资源文件,添加的⽂件必须是在qrc⽂件的同级⽬录,或者同级目录的子目录。

image-20240626113223036

  1. 将图片导入qrc后,在build构建目录中debug文件夹会生成一个qrc_resource.cpp文件,图片转化成cpp代码(一个保存二进制数据的数组)。最后项目编译时,会将这些代码编译到exe文件中,后续⽆论exe被复制到哪个⽬录下,都确保能够访问到该图片资源。

image-20240626113056667

image-20240626113109744

  1. 在Qt代码中使用来自qrc的资源
QPixmap pixmap(":/add.png");
  • : 表示从qrc中获取资源
  • / 是在qrc中已添加的(虚拟 )资源文件目录前缀
  • add.png 是资源文件名

qrc资源管理方案的优缺点

  • 优点:确保了图⽚,字体,声⾳等资源能够真正做到"目录⽆关",⽆论如何都不会出现资源丢失的情况。

  • 缺点:不适合管理体积⼤的资源。如果资源比较⼤(⽐如是⼏个MB的⽂件),或者资源特别多,⽣成的最终的exe体积就会⽐较⼤,程序运⾏消耗的内存也会增⼤,程序编译的时间也会显著增加。

windowOpacity

控件的透明度。但事实上是不透明的程度,用浮点数表示,数值越大,控件越不透明。

API Intro
windowOpacity() 获取到控件的不透明数值,返回float,取值为[0.0, 1.0]。其中0.0表示全透明,1.0表示完全不透明
setWindowOpacity(level) 设置控件的不透明度,level最大值为1,大于1自动转换为1。

cursor

控件的悬停光标样式

API Intro
cursor() 获取当前控件的光标样式,返回QCursor对象
setCursor(const QCursor& cursor) 设置控件的光标样式,仅在⿏标停留在该控件上时⽣效

QCursor是表示光标样式的对象,可以使用Qt内置的,也可以自定义。

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
	//使用内置光标
    this->setCursor(Qt::WaitCursor);

    //创建一个pixmap对象,加载自定义光标图像(来自qrc)
    QPixmap pixmap(":/add.png");
    //可以对pixmap进行缩放,scaled返回的是缩放后的图像副本
    pixmap = pixmap.scaled(30, 30);
    //通过pixmap创建一个cursor对象,可以设置热点(光标点击生效的点)坐标
    QCursor cursor(pixmap, 15, 15);
    //设置光标
    ui->pushButton_add_opacity->setCursor(cursor);
}

font

字体属性

API Intro
font() 获取控件的字体信息,返回QFont对象
setFont() 设置控件的字体信息

QFont用于表示字体的各种属性信息。

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    QLabel *label = new QLabel(this);
    label->setText("Hello World!");

    QFont font;
    //设置字体家族
    font.setFamily("Consola");
    //设置字体大小
    font.setPointSize(20);
    //设置字体加粗
    font.setBold(true);
    //设置字体倾斜
    font.setItalic(true);
    //设置下划线
    font.setUnderline(true);
    //设置删除线
    font.setStrikeOut(true);
    //将字体信息设置到文本上
    label->setFont(font);
}

toolTip

鼠标悬停在控件上时,提示用户一些信息(一般是该控件的功能、属性等)

API Intro
setToolTip(const QString& ) 设置toolTip,鼠标悬停时的提示说明
setToolTipDuration(int msec) 提示的时间,单位是ms,时间一到toolTip自动消失
this->setToolTip("Hello");
this->setToolTipDuration(2000);

focusPolicy

控件获取到焦点的策略。控件获取到焦点意味着接下来的操作都对此控件进行(常用于多个输入栏的切换)。

API Intro
focusPolicy() 获取控件获取焦点的策略,返回Qt::FocusPolicy
setFocusPolicy() 设置控件获取焦点的策略

Qt::FocusPolicy是Qt内置的枚举类型,取值如下:

namespace Qt{
    	//...
        enum FocusPolicy {
        NoFocus = 0, 								//不会获取焦点
        TabFocus = 0x1,								//控件可通过Tab键获取到焦点
        ClickFocus = 0x2,							//控件可通过鼠标点击获取到焦点
        StrongFocus = TabFocus | ClickFocus | 0x8,	//Tab和数据点击接受焦点(默认值)
        WheelFocus = StrongFocus | 0x4				//类似于Qt::StrongFocus,同时控件也通过⿏标滚轮获取到焦点
    };
}
//设置focusPolicy的方法
widget->setFocusPolicy(Qt::NoFocus);

styleSheet

层叠样式表 QSS

设置样式的格式——键值对

按钮类

image-20240627110507279

按钮都继承自QAbstractButton类,按钮的很多关键的属性都来自这个基类。

属性 (from QAbstractButton) 说明
text 按钮中的文本
icon 按钮中的图标
iconSize 图标尺寸
shortCut 按钮的快捷键
autoRepeat (true or false) 按钮是否会重复触发,即鼠标左键按住不放时,是否“连发”
autoRepeatDelay 重复触发的延时时间,即按住按钮多久后,开始“连发”
autoRepeatInterval 重复触发的周期
checkable 是否可被选中
checked 是否已被选中,checkable是checked的前提条件

PushButton

设置按钮的快捷键

void setShortcut(const QKeySequence &key);

QKeySequence:用于保存快捷键序列的对象,因为快捷键可以是单个,也可以是多个键的组合。对于QKeySequence对象的构造,可用表示按键的字符串,如QKeySequence("w");也可用Qt内置的表示按键的宏,如下:

ui->pushButton_up->setShortcut(QKeySequence(Qt::Key_Up));//单个快捷键
ui->pushButton_up->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Up));//组合快捷键

按钮设置快捷键后,按下一次快捷键,发出的信号是clicked(按下了就触发,而不是按下后释放再触发),相当于鼠标点击一次按钮(press + released)。所以当你按住快捷键时,是会重复触发clicked的(不停在按下)。

样例:上下左右方向键,控制飞机的移动

79086ad1-23ea-45b8-a0ae-e8d6cc48f2d7
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
	
    
    QIcon icon(":/image/plane.png");
    ui->plane->setIcon(icon);//设置飞机图标
    ui->plane->setIconSize(QSize(50, 50));//设置图标尺寸

    //设置方向键图标
    ui->pushButton_up->setIcon(QIcon(":/image/up.png"));
    ui->pushButton_down->setIcon(QIcon(":/image/down.png"));
    ui->pushButton_left->setIcon(QIcon(":/image/left.png"));
    ui->pushButton_right->setIcon(QIcon(":/image/right.png"));

    //设置方向键快捷键
    ui->pushButton_up->setShortcut(QKeySequence(Qt::Key_Up));//组合快捷键
    ui->pushButton_down->setShortcut(QKeySequence(Qt::Key_Down));
    ui->pushButton_left->setShortcut(QKeySequence(Qt::Key_Left));
    ui->pushButton_right->setShortcut(QKeySequence(Qt::Key_Right));
    
    //开启重复触发
    ui->pushButton_up->setAutoRepeat(true);
}

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

void Widget::on_pushButton_up_clicked()
{
    QRect rect = ui->plane->geometry();
    ui->plane->setGeometry(rect.x(), rect.y() - 5, rect.width(), rect.height());
}
void Widget::on_pushButton_down_clicked()
{
    QRect rect = ui->plane->geometry();
    ui->plane->setGeometry(rect.x(), rect.y() + 5, rect.width(), rect.height());
}
void Widget::on_pushButton_left_clicked()
{
    QRect rect = ui->plane->geometry();
    ui->plane->setGeometry(rect.x() - 5, rect.y(), rect.width(), rect.height());
}
void Widget::on_pushButton_right_clicked()
{
    QRect rect = ui->plane->geometry();
    ui->plane->setGeometry(rect.x() + 5, rect.y(), rect.width(), rect.height());
}

RadioButton

QRadioButton单选按钮,按钮之间具有“排他性”,即一组单选按钮中只能选中其中一个。

QAbstractButton中与QRadioButton较为相关的属性

属性 说明
checkable 是否可被选中
checked 是否已被选中,checkable是checked的前提条件
autoExclusive 是否“排他”,对于QRadioButton默认是“排他”的

通常情况下需要为RadioButton分组,以满足不同分类的选择,否则整个页面的单选按钮都有“排他性”,因为它们默认在一个组内。如下是一个简单的点餐菜单,我们想要实现的是每个类型中的选择唯一性,因此必须进行分组。

image-20240627143404353

Qt中提供了QButtonGroup类以实现按钮的分组

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

    //创建按钮组
    QButtonGroup* groupStaple = new QButtonGroup(this);
    QButtonGroup* groupDrink = new QButtonGroup(this);
    QButtonGroup* groupSnack = new QButtonGroup(this);
    
    //为按钮组添加按钮
    groupStaple->addButton(ui->radioButton);
    groupStaple->addButton(ui->radioButton_2);
    groupStaple->addButton(ui->radioButton_3);

    groupDrink->addButton(ui->radioButton_5);
    groupDrink->addButton(ui->radioButton_6);
    groupDrink->addButton(ui->radioButton_7);

    
    groupSnack->addButton(ui->radioButton_8);
    groupSnack->addButton(ui->radioButton_9);
}

“排他性”意味着用户无法多选或取消选中,若想要取消选中,要先暂时地取消“排他性”。如下示例,点餐(这里只点了主食)提交后,实现重置(清除)所有选中。

void Widget::on_pushButton_clicked()
{
    QButtonGroup* groupStaple = findChild<QButtonGroup*>("groupStaple");//获取按钮组

    if (groupStaple)
    {
        QList<QAbstractButton*> buttons = groupStaple->buttons();
        groupStaple->setExclusive(false);  // 取消排他性 (按钮组的排他性,决定该组中的按钮之间是否互斥)
        foreach(QAbstractButton *button, buttons)//遍历groupStaple中所有单选按钮
        {
            button->setChecked(false);// 取消groupStaple中所有单选按钮的选中状态
        }
        groupStaple->setExclusive(true);  // 恢复排他性
    }
    else
    {
        qDebug() << "groupStaple not found";
    }
}

CheckBox

QCheckBox 复选按钮,支持多选,和 QCheckBox 最相关的属性也是 checkablechecked , 都是继承⾃QAbstractButton

image-20240627151051965

Signals

image-20240627150547907

clicked有带参数和不带参数的两种类型:

  • clicked():点击“按钮”(按下+释放),不提供按钮状态信息。

  • clicked(bool checked):适用于可切换按钮,提供按钮的选中状态信息。

  • pressed():“按下”按钮

  • released(): “释放”按钮

  • toggled(bool checked): 按钮状态切换时

示例:

void Widget::on_checkBox_clicked(bool checked)
{
    //复选按钮被选中时 checked == true
    //复选按钮取消选中时 checked == false
    qDebug() << "clicked(bool)" << checked;
}
void Widget::on_radioButton_4_toggled(bool checked)
{
    //切换状态时触发: 
    //被选中 checked == true
    //未选中 checked == false
    qDebug() << "toggled(bool)" << checked;
}

显示类

Label

Qlabel可以显示文字和图片

文本格式

QLabel属性 说明
text Qlabel中的文本
textFormat 文本的格式:纯文本、富文本、markdown,通过Qt自带的宏设置
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    ui->label->setText("<b>这是一段文本</b>");
    ui->label->setTextFormat(Qt::PlainText);//纯文本

    ui->label_2->setText("<b>这是一段文本</b>");
    ui->label_2->setTextFormat(Qt::RichText);//富文本

    ui->label_3->setText("# 这是一段文本");
    ui->label_3->setTextFormat(Qt::MarkdownText);//markdown

    ui->label_4->setText("<i>这是一段文本</i>");
    ui->label_4->setTextFormat(Qt::AutoText);//根据文本内容自动决定格式
}

image-20240627183727102

设置图片

属性 说明
pixmap label中的图片 (QPixmap)
scaledContents 设为true表示自动拉伸内容(仅对图片有效),填充Qlabel;设为false则不会自动拉伸
ui->label_image->setPixmap(QPixmap(":/cat.png"));//设置QLabel的图片
ui->label_image->setScaledContents(true);//自动伸缩填满QLabel

文本控制(对齐、自动换行、缩进、边距)

属性 说明
alignment 对齐方式,由Qt内置宏提供,可以选中一种对齐方式,也可以多种组合
wordWrap 设为 true 内部的文本会自动换行,防止文本长度过长,设为 false不会自动换行
indent 设置文本缩进,水平和垂直方向都生效
margin 内部⽂本和Qlabel边框之间的边距,上下左右四个方向同时生效

伙伴

属性 说明
buddy 给 QLabel 关联⼀个 “伙伴” , 这样触发QLabel时就能激活对应的伙伴,这里的“触发”一般是为QLabel设置一个快捷键
ui->label_buddy->setText("快捷键: &A");//QLabel文本中,'&'后的字符被定义为该QLabel的快捷键
ui->label_buddy->setBuddy(ui->radioButton);//将QLabel设置为单选按钮的伙伴

用户通过alt + 快捷键来触发QLabel,从而激活对应的伙伴。

image-20240627191432190

LCDNumber

QLCDNumber显示数字,效果类似于“老式计算器”。

image-20240630100959566

QLCDNumber的主要属性

属性 说明
intValue 显示数字的数值 (int整型)
value 显示数字的数值 (double浮点型),intValue和value存在联系,如value设为3.14,intValue就是3。
digitCount 显示几位数字(从右往左,少的显示空格)
mode 显示数字的进制:十进制、十六进制、二进制、八进制。只有十进制能显示小数点后的数字
segmentStyle 设置显示的风格

注:与以往不同,对于intValue和value的设置,api是display()(重载了int, double和QString三种类型参数的设置),而不是setintValue()setValue()

例:用QLCDNumber设计一个倒计时

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

    //设置初始值
    ui->lcdNumber->display(5);
    //创建一个定时器
    QTimer* timer = new QTimer(this);
    //将定时器的 timeout信号 绑定一个槽函数
    connect(timer, &QTimer::timeout, this, [this, timer](){
        //获取lcdNumber上的数字
        int value = this->ui->lcdNumber->intValue();
        if(value <= 0){
            //倒计时结束
            timer->stop();
            return;
        }
        this->ui->lcdNumber->display(value - 1);
    });
    //启动计时器,设置触发timeout的周期(单位是毫秒)
    timer->start(1000);
}

QTimer是Qt中提供的一个定时器。在GUI编程中,很多场景会用到计时的功能(如倒计时、进度条等),一般来说可以创建一个异步线程,结合sleep()接口,以实现计时功能。

但在Qt中又规定,Qt的界面由主线程维护,不允许其它线程对界面进行操作,以保证线程安全。因此,为了满足计时功能的需求,Qt提供了QTimer,其工作机制是:每个一段时间间隔interval,就发出一次timeout信号,它只负责计时,不负责实现“时间到”之后的具体逻辑。用户可以自定义interval和绑定槽函数,以实现不同的计时需求。而信号槽机制中,默认都是由主界面接收并处理信号,这样一来便很好地满足了GUI中的计时需求。

QTimer的主要接口

API 说明
start(int msec) 启动计时器,并设置触发timeout的周期(单位是毫秒)
stop() 停止计时器

ProgressBar

QProgressBar进度条

image-20240630105856760

QProgressBar的主要属性

属性 说明
minimum 进度条最小值
maximum 进度条最大值
value 进度条当前值
format 展示数字的格式:%p百分比、%v数值、%m剩余时间(毫秒)、%t总时间(毫秒)

例:进度条走完,关闭窗口

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

    int min = ui->progressBar->minimum();
    int max = ui->progressBar->maximum();
    ui->progressBar->setValue(min);
    QTimer* timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, [=](){
       int value = ui->progressBar->value();
       if(value >= max){
           timer->stop();
           this->close();
           return;
       }
       ui->progressBar->setValue(value + 1);
    });
    timer->start(100);
}

QProgressBarQLCDNumber相同,往往需要和定时器QTimer搭配使用。

使用QSS改变进度条的颜色:

image-20240630110823055

Calendar

QCalendarWidget是Qt中的日历控件

image-20240630111003585

QCalendarWidget的主要属性

属性 说明
selectDate 当前选中的日期(QDate
dateEditEnabled 日期是否允许被编辑 (bool)
minimumDate 最大日期(QDate
maximumDate 最小日期(QDate

QCalendarWidget的主要信号

信号 说明
selectionChanged(const QDate&) 当选中的⽇期发⽣改变时发出
activated(constQDate&) 当双击⼀个有效的⽇期或者按下回⻋键时发出,形参是⼀个QDate类型,保存 了选中的⽇期
currentPageChanged(int, int) 当年份⽉份改变时发出,形参表⽰改变后的新年份和⽉份

输入类

LineEdit

QLineEdit是单行输入框,可以输入一行文本,不能换行。

QLineEdit的主要属性

属性 说明
text 输入框中的文本
inputMask 输入内容的格式约束 (QString)
maxLength 最大文本长度
echoMode 显示方式:1.QLineEdit::Normal2.QLineEdit::Password 3.QLineEdit::NoEcho
readOnly 是否为只读 (bool)
placeHolderText 当输入框为空时的显示信息,一般作为提示
clearButtonEnabled 当输入框不为空时,是否会显示清除按钮 (bool)

QLineEdit的主要信号

信号 说明
void textChanged(const QString& text) 当QLineEdit中的⽂本改变时,发出此信号,text是新的⽂本。 代码对⽂本的修改能够触发这个信号
void textEdited(const QString& text) 当QLineEdit中的⽂本改变时,发出此信号,text是新的⽂本。 代码对⽂本的修改不能触发这个信号
  • 输入内容的格式约束

    inputMask可以实现输入内容的格式约束

    ui->lineEdit_phone->setInputMask("000-0000-0000");//其中'0'代表数字
    

    但在实际开发中,通常采用“正则表达式”来实现格式约束,Qt中提供验证器QValidator,其派生类QRegExpValidator为正则验证器,我们可以为QLineEdit设置一个正则验证器,以验证输入栏中的内容是否符合预期格式。

    例:学生输入学号,只有当输入学号合法时,才允许提交

    #include "widget.h"
    #include "ui_widget.h"
    #include <QRegExpValidator>
    #include <QDebug>
    
    Widget::Widget(QWidget *parent)
        : QWidget(parent)
        , ui(new Ui::Widget)
    {
        ui->setupUi(this);
        ui->pushButton_submit->setEnabled(false);//当输入学号合法时,才允许提交
        ui->lineEdit_id->setPlaceholderText("请输入学号: 入学年份-学院号-个人号");
        ui->lineEdit_id->setClearButtonEnabled(true);
    
        //为lineEdit_id注册一个正则验证器,并为验证其设置正则表达式
        ui->lineEdit_id->setValidator(new QRegExpValidator(QRegExp("^20\\d{2}\\d{3}\\d{3}$")));
        //该正则表达式的含义
        //入学年份4位,且必须以 20 开头
        //学院号3位
        //个人号3位
    }
    void Widget::on_lineEdit_id_textEdited(const QString &arg1)
    {
        QString id = arg1;
        int pos = 0;
        //验证新输入学号的是否符合正则表达式的格式
        if(ui->lineEdit_id->validator()->validate(id, pos) == QValidator::Acceptable){
            //符合
            ui->pushButton_submit->setEnabled(true);
        }
        else{
            //不符合
            ui->pushButton_submit->setEnabled(false);
        }
    }
    void Widget::on_pushButton_submit_clicked()
    {
        qDebug() << ui->lineEdit_id->text();
    }
    

TextEdit

QTextEdit是多行输入框,可以输入多行文本。

主要属性

属性 说明
markdown 输入框中的内容支持markdown格式
html 输入框中的内容支持html格式
placeHolderText 当输入框为空时的显示信息,一般作为提示
undoRedoEnable 是否触发 undo (ctrl + z) / redo (ctrl + y) 功能
verticalScrollBarPolicy 垂直方向滚动条的出现策略,默认根据内容⾃动决定是否需要滚动条
horizontalScrollBarPolicy 水平方向滚动条的出现策略,默认根据内容⾃动决定是否需要滚动条

主要方法

接口 说明
QString toHtml() html格式获取输入框中的文本
QString toMarkdown() markdown格式获取输入框中的文本
void setHtml(const QString &text) 设置输入框中的内容为一段html格式的文本,并且此后输入都是这种格式
void setMarkdown(const QString &text) 设置输入框中的内容为一段markdown格式的文本,并且此后输入都是这种格式
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    ui->label->setTextFormat(Qt::MarkdownText);
    ui->pushButton->setText("转化为html");
}

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

void Widget::on_textEdit_textChanged()
{
    ui->label->setText(ui->textEdit->toMarkdown());
}

void Widget::on_pushButton_clicked()
{
    ui->textEdit->setHtml("<b>你好</b>");
}

image-20240701110249291

输入markdown格式文本,在label中渲染成markdown

image-20240701110315117

转化为html后,再输入的内容都是预设的<b></b>格式。

主要信号

信号 触发时机
textChanged 输入框内容发生改变时
selectionChanged() 选中的文本发生改变时
cursorPositionChanged() 光标的位置发生改变时
undoAvailable(bool) undo功能可用状态切换时(内容不为空,undo就可用)
redoAvailable(bool) redo功能可用状态切换时(undo之后,redo就可用)
copyAvaiable(bool) ⽂本被选中/取消选中时触发

QTextCursor表示文本光标,可获取选中的文本、光标的位置。通常和selectionChangedcursorPositionChanged信号搭配使用。

void Widget::on_textEdit_selectionChanged()
{
    QTextCursor cursor = ui->textEdit->textCursor();//获取textEdit的光标
    ui->label->setText(cursor.selectedText());//获取被选中的文本
}

void Widget::on_textEdit_cursorPositionChanged()
{
    QTextCursor cursor = ui->textEdit->textCursor();//获取textEdit的光标
    qDebug() << "cursorPositionChanged()" << cursor.position();//获取光标位置
}
image-20240701112931800

ComboBox

QComboBox下拉列表,多个选项供用户选择

主要属性

属性 说明
currentText 当前选中条目的文本
currentIndex 当前选中条目的下标(从0开始,无选中条目则为-1)
editable 是否可编辑
iconSize 下拉框图标(小三角)的大小
maxCount 条目最大个数

主要方法

接口 说明
addItem(const QString& ) 添加条目
currentText() 获取当前选中条目的文本
currentIndex() 获取当前选中条目的下标
setEditable(bool) 设置是否可编辑

editable设为true, 用户确认编辑的条目后, 若该条目不存在, 则添加这个条目

ui->comboBox->addItem("战士");
ui->comboBox->addItem("法师");
ui->comboBox->addItem("坦克");
ui->comboBox->setEditable(true);//设下拉列表为可编辑
image-20240701120823891

主要信号

信号 说明
currentIndexChanged(int) 当前条目下标改变时触发, 传入参数为改变后的条目下标
currentIndexChanged(QString) 当前条目下标改变时触发, 传入参数为改变后的条目文本
currentTextChanged(QString) 当前条目文本改变时触发, 传入参数为改变后的条目文本
activated(int) / activated(QString) 用户选中一个条目时触发 (不管该条目是否已选中)

SpinBox

QSpinBox微调框,对数字进行微调。QSpinBox针对整型,QDoubleSpinBox针对浮点数,用法基本相同。

主要属性

属性 说明
value 当前值
singleStep 单次调整的步长
displayInterger 数字的进制。例如displayInteger设为10,则是按照十进制表示,设为2则为二进制表示
minimum 最小值
maximum 最大值
suffix 后缀
prefix 前缀

setRange(int min, int max)可以调整最大值和最小值,即修改微调框可变的数值范围。

主要信号

信号 触发时机
valueChanged(int) 微调框的⽂本发⽣改变时触发,参数int表示当前的数值.
textChanged(QString) 微调框的⽂本发⽣改变时触发,参数QString带有前缀和后缀

DateTimeEdit

QDateTimeEdit编辑日期与时间

主要属性

属性 说明
dateTime 日期时间的值 (QDateTime)
date 日期 (QDate)
time 时间 (QTime)
displayFormat 时间日期格式。形如 yyyy/M/d H:mm
minimumDateTime 最小日期时间
maximumDateTime 最大日期时间
timeSpec Qt::LocalTime:本地时间;Qt::UTC:显⽰协调世界时(UTC);Qt::OffsetFromUTC :显⽰相对于UTC的偏移量(时差)
setDateTimeRange(const QDateTime &min, const QDateTime &max)//设置日期时间的范围
  • 关于QDateTime

    Qt中用于表示日期时间的类是QDateTime,以下是QDateTime一些常用的接口,方便我们对日期时间进行操作

    接口 说明
    daysTo(const QDateTime &other) thisother之间的天数。但有时会不符合预期,比如 2024/1/1 23:55 - 2024/1/2 0:05也会被算成一天。
    secsTo(const QDateTime &other) thisother之间的秒数
    QString toString(const QString &format) 将日期时间转化成字符串,format是日期时间的字符串格式。

样例:日期计算器,计算两个日期之间的差值(几天几时)

#include "widget.h"
#include "ui_widget.h"

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

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

void Widget::on_pushButton_calc_clicked()
{
    //1.获取两个前后两个日期时间
    QDateTime former = ui->dateTimeEdit_former->dateTime();
    QDateTime later = ui->dateTimeEdit_later->dateTime();
    //2.计算两个日期时间差值
    int secs = former.secsTo(later);
    int days = secs / (3600 * 24);//总秒数 / 一天的秒数
    int hours = (secs / 3600) % 24;// 总秒数/一小时的秒数 = 总小时数
    ui->label_ret->setText(QString::number(days) + "天" + QString::number(hours) + "时");
}

void Widget::on_dateTimeEdit_later_dateTimeChanged(const QDateTime &later)
{
    //确保第一个时间在第二个时间之前
    QDateTime former = ui->dateTimeEdit_former->dateTime();
    if(later < former){
        ui->dateTimeEdit_former->setDateTime(later);
        ui->dateTimeEdit_later->setDateTime(former);
    }
}

image-20240701164425374

Dial

QDial表示一个旋钮,通过旋转改变数值

Slider

QSlider表示一个滑动条,通过滑动改变数值

快捷键

Qt中基于信号槽机制,提供了一种通用的设置快捷键的方法。QShortcut表示快捷键,用户可使用该类自定义快捷键。当相应的快捷键被激活时,触发QShortcut的信号activated,用户可自定义槽函数,完成快捷键被激活后的特定工作。

样例:设置快捷键-=控制旋钮,旋钮的功能是控制窗口的透明度

#include "widget.h"
#include "ui_widget.h"
#include <QShortcut>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //1.设置旋钮的属性
    ui->dial->setRange(0, 100);//旋钮数值范围(透明度范围)
    ui->dial->setValue(100);//旋钮初始值
    ui->dial->setWrapping(true);//允许循环转动
    ui->dial->setNotchesVisible(true);//显示刻度线

    //2.设置快捷键
    QShortcut* shortcutSub = new QShortcut(this);
    shortcutSub->setKey(QKeySequence("-"));
    QShortcut* shortcutAdd = new QShortcut(this);
    shortcutAdd->setKey(QKeySequence("="));
    
    //3.绑定快捷键的“激活”信号和槽函数
    connect(shortcutSub, &QShortcut::activated, this, [=](){
        if(this->ui->dial->value() > 0){
            this->ui->dial->setValue(this->ui->dial->value() - 5);
        }
    });

    connect(shortcutAdd, &QShortcut::activated, this, [=](){
        if(this->ui->dial->value() < 100){
            this->ui->dial->setValue(this->ui->dial->value() + 5);
        }
    });
}

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


void Widget::on_dial_valueChanged(int value)
{
    this->setWindowOpacity(value / 100.0);
}

多元素控件

ListWidget

QListWidget是一个纵向列表

主要属性

属性 说明
currentRow 当前被选中的是第几行(行的下标从0开始),若无选中,返回-1
count 一共有多少行
sortingEnabled 列表是否允许排序(若是,默认升序,可通过sortItems修改排序方式)

主要方法

接口 说明
void addItem(const QString &label)
void addItem(QListWidgetItem *item)
添加列表元素
void insertItem(int row, QListWidgetItem *item)
void insertItem(int row, const QString &label)
在指定位置插入元素
QListWidgetItem *currentItem()
int currentRow()
返回当前选中的元素(指针或下标)
QListWidgetItem * takeItem(int row) 删除并返回指定行的元素
QListWidgetItem * item(int row) 返回指定行的元素
void setCurrentItem(QListWidgetItem *item)
void setCurrentRow(int row)
设置选中的元素(使用指针或下标)

注意,多元素控件QListWidget中,对于某一个元素,可以用行表示,也可以用QListWidgetItem指针表示。

QListWidgetItem是表示QListWidget中一个元素的类,本质是设置元素中的文本和图标,以及字体样式等。

样例:根据输入内容,新增列表的元素;删除指定列表中元素

#include "widget.h"
#include "ui_widget.h"
#include <QDebug>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    ui->lineEdit->setClearButtonEnabled(true);
    ui->listWidget->setSortingEnabled(true);
    ui->listWidget->addItem(new QListWidgetItem("初始"));//使用QListWidgetItem类添加元素
}

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


void Widget::on_pushButton_add_clicked()
{
    const QString& text = ui->lineEdit->text();
    if(!text.isEmpty()){
        ui->listWidget->addItem(text);
    }
}

void Widget::on_pushButton_del_clicked()
{
    qDebug() << ui->listWidget->currentItem()->text();//获取选中元素的文本
    int row = ui->listWidget->currentRow();
    if(row >= 0){
        ui->listWidget->takeItem(row);
    }
}
image-20240711171901668

主要信号

信号 触发时机
currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) 选中元素发生变化时(current变化后元素,previous变化前元素)
currentRowChanged(int currentRow) 选中元素发生变化时(currentRow当前行)
itemClicked(QListWidgetItem*item) 点击某个元素时触发
void Widget::on_listWidget_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)
{
    if(current) 
        qDebug() << "当前元素:" << current->text();
    if(previous) //若没有变化前没有选中元素,previous为nullptr
        qDebug() << "最近元素:" << previous->text();
}

TableWidget

QTableWidget是一个表格控件

主要属性

属性 说明
columnCount 列数
rowCount 行数

主要方法

方法 说明
QTableWidgetItem * item(int row, int column) 根据行和列获取表格中某一元素
void setItem(int row, int column, QTableWidgetItem *item) 根据行和列设置表格中某一元素
QTableWidgetItem * currentItem() 返回被选中的元素
int currentRow() const 返回被选中元素的行
int currentColumn() const 返回被选中元素的列
setColumnCount(int) 设置表格列数
setRowCount(int) 设置表格行数
void setHorizontalHeaderItem(int column, QTableWidgetItem *item) 设置列表头
void setVerticalHeaderItem(int column, QTableWidgetItem *item) 设置行表头
void insertRow(int row) 在指定位置插入新行
void insertColumn(int row) 在指定位置插入新列
void removeRow(int row) 删除指定行
void removeColumn(int column) 删除指定列

QListWidget相同,在QTableWidget中,对于某一个元素,可以用行和列表示,也可以用QTableWidgetItem 指针表示。而QTableWidgetItem 除了设置文本图标样式,还有两个特殊的方法,可以获取元素所在单元格的行和列

QTableWidgetItem::方法 说明
row() 获取元素所在单元格的行
column() 获取元素所在单元格的列

样例:通过输入内容,确定新增行或列,以及对应表头

void Widget::on_pushButton_row_clicked()
{
    int row = ui->tableWidget->rowCount();
    ui->tableWidget->insertRow(row);
    const QString& header = ui->lineEdit_row->text();
    if(!header.isEmpty()){
        ui->tableWidget->setVerticalHeaderItem(row, new QTableWidgetItem(header));
    }
}

void Widget::on_pushButton_col_clicked()
{
    int col = ui->tableWidget->columnCount();
    ui->tableWidget->insertColumn(col);
    const QString& header = ui->lineEdit_col->text();
    if(!header.isEmpty()){
        ui->tableWidget->setHorizontalHeaderItem(col, new QTableWidgetItem(header));
    }
}

主要信号

信号 触发时机
cellClicked(int row,int column) 点击单元格时
currentCellChanged(int row,int column,int previousRow,int previousColumn) 选中单元格切换时
cellEntered(int row,int column) ⿏标进⼊单元格时

TreeWidget

QTreeWidget是一个树形控件,类似于操作系统中的目录结构。QTreeWidget最上层没有根节点,而是由一系列的顶层节点(TopLevelItem)构成,然后再给顶层节点添加子节点,如此往复,最终形成树状结构。

QTreeWidget里的每个元素,都是⼀个 QTreeWidgetItem ,每个QTreeWidgetItem 可以包含多个⽂本和图标,每个⽂本/图标为⼀个列

image-20240712095358357

样例:

image-20240712104615348
void Widget::on_pushButton_topLevel_clicked()
{
    const QString& text = ui->lineEdit->text();
    if(!text.isEmpty()){
        QTreeWidgetItem* newItem = new QTreeWidgetItem();
        if(newItem){
            newItem->setText(0, text);
            ui->treeWidget->addTopLevelItem(newItem);
        }
    }
    // 可以不显式地为新创建的 QTreeWidgetItem 对象设置 parent 指针
    // 因为 addTopLevelItem 会自动将 newItem 的父指针设置为 treeWidget
}

void Widget::on_pushButton_cur_clicked()
{
    const QString& text = ui->lineEdit->text();
    if(!text.isEmpty()){
        QTreeWidgetItem* newItem = new QTreeWidgetItem();
        if(newItem){
            newItem->setText(0, text);
            ui->treeWidget->currentItem()->addChild(newItem);
        }
    }
}

void Widget::on_pushButton_del_clicked()
{
    QTreeWidgetItem* cur = ui->treeWidget->currentItem();
    QTreeWidgetItem* parent = cur->parent();
    if(parent == nullptr){
        //顶层节点
        int index = ui->treeWidget->indexOfTopLevelItem(cur);
        ui->treeWidget->takeTopLevelItem(index);
    }
    else{
        //非顶层节点
        parent->removeChild(cur);
    }
}

容器类控件

GroupBox

使⽤ QGroupBox 实现⼀个带有标题的分组框,可以把其他的控件放到里面作为⼀组,这样看起来能更好看⼀点。

TabWidget

QTabWidget实现一个带有标签页的控件,可以往标签页中设置新的widget,以实现通过切换标签页在不同的widget中跳转。注意,标签页的内容是设置在另外创建的QWidget对象中,QTabWidget只是提供标签页。

主要属性

属性 说明
tabPosition 标签所在位置
North 上方
South 下方
West 左侧
East 右侧
currentIndex 当前选中标签页的下标(从0开始)
currentTabText 当前选中标签页的文本
tabsCloseable 标签页是否可以关闭
movable 标签页是否可以移动

主要方法

方法 说明
int addTab(QWidget *page, const QString &label) 添加一个新的标签页,page是标签页本体,label是标签页的选项卡文本。返回值是新标签页的编号。
int count() 获取标签页的数量
void setCurrentWidget(QWidget *widget) 通过QWidget指针,设置当前标签页
void setCurrentIndex(int index) 通过标签页编号,设置当前标签页
void removeTab(int index) 根据标签页编号,删除标签页

主要信号

信号 触发时机
currentChanged(int) 标签页切换时,参数为切换后的标签页编号
tabBarClicked(int) 点击选项卡的标签条的时候,参数为被点击的选项卡编号
tabCloseRequest(int) tabsCloseable == true时,点击关闭标签页按钮触发,参数为被关闭的标签页编号

样例:实现新增标签页按钮,和点击标签页关闭按钮关闭标签页

#include "widget.h"
#include "ui_widget.h"
#include <QLabel>

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

    QLabel* label = new QLabel(ui->tab);
    label->setText("这是第一个标签页");
    label->resize(150,60);

    QLabel* label_2 = new QLabel(ui->tab_2);
    label_2->setText("这是第二个标签页");
    label_2->resize(150,60);
}

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

void Widget::on_pushButton_clicked()
{
    //创建新标签页的Widget,还有标签页文本
    QWidget* page = new QWidget();
    int idx = ui->tabWidget->count() + 1;
    ui->tabWidget->addTab(page, "Tab" + QString::number(idx));

    //每次创建一个新标签页,自动选中它
    ui->tabWidget->setCurrentWidget(page);
}

void Widget::on_tabWidget_tabCloseRequested(int index)
{
    ui->tabWidget->removeTab(index);
}
image-20240712114514262

布局管理器

垂直布局

QVBoxLayout是垂直布局管理器。下面是设置垂直布局管理器的demo。

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QVBoxLayout* layout = new QVBoxLayout();//创建一个垂直布局管理器
    this->setLayout(layout);//将layout设置为窗口的布局管理器

    layout->setContentsMargins(10,20,10,20);//设置布局的左、上、右、下间距
    layout->setSpacing(50);//设置相邻元素的间距

    //新建一些按钮
    QPushButton* button1 = new QPushButton("按钮1");
    QPushButton* button2 = new QPushButton("按钮2");
    QPushButton* button3 = new QPushButton("按钮3");
    
    //将按钮添加到布局管理器中
    layout->addWidget(button1);
    layout->addWidget(button2);
    layout->addWidget(button3);
}
image-20240715112248092

水平布局

QHBoxLayout水平布局管理器。下面是设置垂直布局管理器的demo。

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QHBoxLayout* layout = new QHBoxLayout();//创建一个垂直布局管理器
    this->setLayout(layout);//将layout设置为窗口的布局管理器

    layout->setContentsMargins(10,20,10,20);//设置布局的左、上、右、下间距
    layout->setSpacing(50);//设置相邻元素的间距

    //新建一些按钮
    QPushButton* button1 = new QPushButton("按钮1");
    QPushButton* button2 = new QPushButton("按钮2");
    QPushButton* button3 = new QPushButton("按钮3");

    //将按钮添加到布局管理器中
    layout->addWidget(button1);
    layout->addWidget(button2);
    layout->addWidget(button3);
    
    //设置拉伸系数,即在布局中所占的权重
    layout->setStretch(0, 1);
    layout->setStretch(1, 1);
    layout->setStretch(2, 2);
}
image-20240715112822430

每个控件(QWidget)只能有一个布局管理器(layout),在QtDesigner中在控件上创建多个layout,实际上内部为每个layout都新建了一个QWidget,再在新建的QWidget上设置layout。如果我们想在一个控件上创建多个layout,可以通过嵌套的方式实现。

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QVBoxLayout* vlayout = new QVBoxLayout();//创建一个垂直布局管理器vlayout
    this->setLayout(vlayout);//将vlayout设置为窗口的布局管理器

    //新建一些按钮
    QPushButton* button1 = new QPushButton("按钮1");
    QPushButton* button2 = new QPushButton("按钮2");
    QPushButton* button3 = new QPushButton("按钮3");
    QPushButton* button4 = new QPushButton("按钮4");

    //新建水平布局
    QHBoxLayout* hlayout = new QHBoxLayout();
    hlayout->addWidget(button3);
    hlayout->addWidget(button4);

    //将按钮添加到布局管理器中
    vlayout->addWidget(button1);
    vlayout->addWidget(button2);
    //将水平布局嵌套到垂直布局中
    vlayout->addLayout(hlayout);
}
image-20240715113513857

网格布局

QGridLayout是网格布局,实现m * n的二维网格布局效果。

void addWidget(QWidget *, int row, int column)//在网格布局中添加控件

demo

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QGridLayout* glayout = new QGridLayout();//创建网格布局,视为2行3列
    this->setLayout(glayout);

    QPushButton* btn1 = new QPushButton("按钮1");
    QPushButton* btn2 = new QPushButton("按钮2");
    QPushButton* btn3 = new QPushButton("按钮3");
    QPushButton* btn4 = new QPushButton("按钮4");
    QPushButton* btn5 = new QPushButton("按钮5");
    QPushButton* btn6 = new QPushButton("按钮6");

    glayout->addWidget(btn1, 0, 0);
    glayout->addWidget(btn2, 0, 1);
    glayout->addWidget(btn3, 0, 2);
    glayout->addWidget(btn4, 1, 0);
    glayout->addWidget(btn5, 1, 1);
    glayout->addWidget(btn6, 1, 2);

    //设置列的拉伸系数
    glayout->setColumnStretch(0,1);
    glayout->setColumnStretch(1,2);
    glayout->setColumnStretch(2,1);
    
    //设置行的拉伸系数
    glayout->setRowStretch(0, 1);
    glayout->setRowStretch(1, 2);
}

image-20240715114704133

上述案例中,直接设置 setRowStretch 效果不明显,因为每个按钮的⾼度是固定的.需要把按钮的垂直⽅向的 sizePolicy 属性设置为 QSizePolicy::Expanding 尽可能填充满布局管理器,才能看到效果,如下:

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QGridLayout* glayout = new QGridLayout();//创建网格布局,视为2行3列
    this->setLayout(glayout);

    QPushButton* btn1 = new QPushButton("按钮1");
    QPushButton* btn2 = new QPushButton("按钮2");
    QPushButton* btn3 = new QPushButton("按钮3");
    QPushButton* btn4 = new QPushButton("按钮4");
    QPushButton* btn5 = new QPushButton("按钮5");
    QPushButton* btn6 = new QPushButton("按钮6");
    //设置每个按钮的SizePolicy为 填充
    btn1->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    btn2->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    btn3->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    btn4->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    btn5->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    btn6->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);


    glayout->addWidget(btn1, 0, 0);
    glayout->addWidget(btn2, 0, 1);
    glayout->addWidget(btn3, 0, 2);
    glayout->addWidget(btn4, 1, 0);
    glayout->addWidget(btn5, 1, 1);
    glayout->addWidget(btn6, 1, 2);

    //设置列的拉伸系数
    glayout->setColumnStretch(0,1);
    glayout->setColumnStretch(1,2);
    glayout->setColumnStretch(2,1);

    //设置行的拉伸系数
    glayout->setRowStretch(0, 1);
    glayout->setRowStretch(1, 3);
}
image-20240715114957928

表单布局

QFormLayout是表单布局管理器,网格布局的一种特殊情况。这种表单布局多用于让用户填写信息的场景,一般只有两列,左侧列为提示,右侧列为输⼊框。通过addRow(QWidget *label, QWidget *field)方法可以向表单布局中添加行。

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

    QLabel* label_name = new QLabel("姓名");
    QLabel* label_phone = new QLabel("电话");
    QLabel* label_addr = new QLabel("地址");
    QLineEdit* lineEdit_name = new QLineEdit();
    QLineEdit* lineEdit_phone = new QLineEdit();
    QLineEdit* lineEdit_addr = new QLineEdit();

    flayout->addRow(label_name, lineEdit_name);
    flayout->addRow(label_phone, lineEdit_phone);
    flayout->addRow(label_addr, lineEdit_addr);

    QPushButton* btn = new QPushButton("提交");
    flayout->addRow(nullptr, btn);
}
image-20240715115915612

网站公告

今日签到

点亮在社区的每一天
去签到