云计算百科
云计算领域专业知识百科平台

【Qt】《Qt 5.9 C++开发指南》笔记-Chapter4-常用界面设计组件

第4章 常用界面设计组件

第2章已经介绍了设计GUI应用程序的基本原理和方法,在掌握了用Qt Creator设计应用程序的基本方法之后,要应用Qt编写各种应用程序,重要的就是熟练掌握Qt类库里各种用于界面设计或其他功能的类的使用。

Qt类库为应用程序设计提供了大量的类,本章主要介绍设计GUI应用程序常用的各种界面组件的使用,这些是设计GUI应用程序的基础。

4.1 字符串与输入输出

4.1.1字符串与数值之间的转换

界面设计时使用最多的组件恐怕就是QLabel和QLineEdit了,QLabel用于显示字符串,QLineEdit用于显示和输入字符串。这两个类都有如下的两个函数用于读取和设置显示文字。

QString text() const
void setText(const QString &)

这两个函数都涉及到QString类。QString类是Qt程序里经常使用的类,用于处理字符串。QString类可以进行字符串与数字之间的转换,使用QLineEdit就可以实现数字量的输入与输出。

原书的图4-1是实例samp4_1设计时的窗体,是基于QWidget创建的可视化窗体。界面设计使用了布局管理,窗体上组件的布局是:上方的几个组件是一个GridLayout,下方的9个组件也是一个GridLayout,两个GridLayout和中间一个VerticalSpacer又组成一个VerticalLayout。

在布局设计时,要巧妙运用VerticalSpacer和HorizontalSpacer,还要会设置组件的MaximumSize和MinimumSize属性,以取得期望的布局效果。例如,在图4-1中,两个GridLayout之间放了一个垂直方向的分隔,当窗体变大时,两个GridLayout的高度并不会发生变化;而如果不放置这个垂直分隔,两个GridLayout的高度都会发生变化,GridLayout内部组件的垂直距离会发生变化。

1.普通数值与字符串之间的转换

在UI设计器里,选中图4-1中的“计算”按钮,在右键快捷菜单里单击“Go to slot…”,并在出现的对话框里选择clicked()信号创建槽函数,在自动生成的函数体里编写如下的代码,实现从界面上分别读取数量和单价,相乘计算后将结果再显示到文本框里。

void Widget::on_btnCal_clicked()
{
QString str=ui->editNum->text();//读取"数量"
int num=str.toInt();
str=ui->editPrice->text();//读取"单价"
float price=str.toFloat();
float total=num*price;
str=str.sprintf("%.2f",total);
ui->editTotal->setText(str);
}

QString类从字符串转换为整数的函数有:

int toInt(bool *ok = Q_NULLPTR, int base = 10) const
long toLong (bool *ok = Q_NULLPTR, int base = 10) const
short toShort (bool *ok = Q_NULLPTR, int base = 10) const
uint toUInt (bool *ok = Q_NULLPTR, int base = 10) const
ulong toULong (bool *ok = Q_NULLPTR, int base = 10) const

这些函数如果不设置参数,缺省表示从十进制表示的字符串转换为整数;若指定整数基参数,还可以直接将二进制、十六进制字符串转换为整数。

QString将字符串转换为浮点数的函数有:

double toDouble(bool *ok = Q_NULLPTR) const
float toFloat (bool *ok = Q_NULLPTR) const

在得到单价和数量后做相乘运算,得到计算结果变量total,再将此数值显示在编辑框editTotal中。由于计算结果是浮点数,希望显示两位小数,下面4行语句都可以实现这个功能。

str=QString::number(total,'f',2);
str=QString::asprintf("%.2f",total);
str=str.setNum(total,'f',2);
str=str.sprintf("%.2f",total);

可以使用QString的静态函数number()和asprintf(),也可以使用其公共函数setNum()和sprintf()。QString的sprintf()函数与C语言里的sprintf()函数的格式是一样的,而setNum()和number()函数使用另外一种格式定义,而且setNum和number有多个重载函数定义,可以处理各种类型的整数和浮点数,在处理整数时还可以指定进制,例如将一个整数直接转换为十六进制或二进制字符串。

2.进制转换

以下是读取十进制数转换为二进制和十六进制字符串的按钮的槽函数代码:

void Widget::on_btnDec_clicked()
{ //读取十进制数,转换为其他进制
QString str=ui->editDec->text();
int val=str.toInt();//缺省为十进制
// str=QString::number(val,16);// 转换为十六进制的字符串
str=str.setNum(val,16);//十六进制
str=str.toUpper();
ui->editHex->setText(str);

str=str.setNum(val,2); //二进制
// str=QString::number(val,2);
ui->editBin->setText(str);
}

将一个整数转换为不同进制的字符串,可以使用QString的函数setNum()或静态函数number(),它们的函数原型是:

Qstring &setNum (int n, int base = 10)
QString number (int n, int base = 10)

其中n是待转换的整数,base是使用的进制,缺省为十进制,也可以指定为十六进制和二进制。

下面是读取二进制字符串,然后转换为十进制和十六进制显示的按钮的槽函数代码。

void Widget::on_btnBin_clicked()
{ //读取二进制数,转换为其他进制的数
QString str=ui->editBin->text(); //读取二进制字符串
bool ok;
int val=str.toInt(&ok,2);//以二进制数读入
str=QString::number(val,10);//数字显示为十进制字符串
ui->editDec->setText(str);

str=str.setNum(val,16);//显示为十六进制
str=str.toUpper();
ui->editHex->setText(str);
}

4.1.2 QString的常用功能

QString是Qt编程中常用的类,除了用作数字量的输入输出之外,QString还有很多其他功能,熟悉这些常见的功能,有助于灵活地实现字符串处理功能。

QString存储字符串采用的是Unicode码,每一个字符是一个16位的QChar,而不是8位的char,所以QString处理中文字符没有问题,而且一个汉字算作是一个字符。

原书的图4-2是对QString常用函数的测试实例samp4_2的运行界面。下面在说明函数功能时,对于同名不同参数的函数,只说明某种参数下的使用实例。QString还有很多功能函数没有在此介绍,在使用中如果遇到,可查询Qt的帮助文件。

  • append()和prepend()

append()在字符串的后面添加字符串,prepend()在字符串的前面添加字符串,如:

QString str1="卖", str2="拐";
QString str3=str1;
str1.append(str2); //str1="卖拐"
str3.prepend(str2); //str3="拐卖"

  • toUpper()和toLower()

toUpper()将字符串内的字母全部转换为大写形式,toLower()将字母全部转换为小写形式,如:

QString str1="Hello, World", str2;
str2=str1.toUpper(); //str2="HELLO,WORLD"
str2=str1.toLower(); //str2="hello, world"

  • count()、size()和length()

count()、size()和length()都返回字符串的字符个数,这3个函数是相同的,但是要注意,字符串中如果有汉字,一个汉字算一个字符。

QString str1="NI好"
N=str1.count() //N=3
N=str1.size() //N=3
N=str1.length() //N=3

  • trimmed()和simplified()

trimmed()去掉字符串首尾的空格,simplified()不仅去掉首尾的空格,中间连续的空格也用一个空格替换。

QString str1=" Are you OK? ", str2;
str2=str1.trimmed(); //str1="Are you OK? "
str2=str1.simplified(); //str1="Are you OK? "

  • indexOf ()和lastIndexOf ()

indexOf()函数的原型为:

int indexOf (const QString &str, int from = 0, Qt::CaseSensitivity cs = Qt::CaseSensitive) const

其功能是在自身字符串内查找参数字符串str出现的位置,参数from是开始查找的位置,Qt::CaseSensitivity cs参数指定是否区分大小写。

lastIndexOf()函数则是查找某个字符串最后出现的位置。

QString str1="G:\\Qt5Book\\QT5.9Study\\qw.cpp";
N=str1.indexOf("5.9"); // N=13
N=str1.lastIndexOf("\\\\"); //N=21

“\\”是转义字符,如果要查找“\\”,需要输入“\\”。

  • isNull()和isEmpty()

两个函数都判读字符串是否为空,但是稍有差别。如果一个空字符串,只有“\\0”,isNull()返回false,而isEmpty()返回true;只有未赋值的字符串,isNull()才返回true。

QString str1, str2="";
N=str1.isNull(); // N=true 未赋值字符串变量
N=str2.isNull(); // N=false 只有"\\0"的字符串,也不是Null
N=str1.isEmpty(); // N=true
N=str2.isEmpty(); // N=true

QString只要赋值,就在字符串的末尾自动加上“\\0”,所以,如果只是要判断字符串内容是否为空,常用isEmpty()。

  • contains()

判断字符串内是否包含某个字符串,可指定是否区分大小写。

QString str1="G:\\Qt5Book\\QT5.9Study\\qw.cpp";
N=str1.contains(".cpp", Qt::CaseInsensitive); // N=true,不区分大小写
N=str1.contains(".CPP", Qt::CaseSensitive); // N=false,区分大小写

  • endsWith()和startsWith()

startsWith ()判断是否以某个字符串开头,endsWith()判断是否以某个字符串结束。

QString str1="G:\\Qt5Book\\QT5.9Study\\qw.cpp";
N=str1.endsWith(".cpp", Qt::CaseInsensitive); // N=true,不区分大小写
N=str1.endsWith(".CPP", Qt::CaseSensitive); // N=false,区分大小写
N=str1.startsWith("g: "); // N=true,缺省为不区分大小写

  • left()和right()

left表示从字符串中取左边多少个字符,right表示从字符串中取右边多少个字符。注意,一个汉字被当作一个字符。

QString str2, str1="学生姓名,男,1984-3-4,汉族,山东";
N=str1.indexOf (","); // N=4,第一个","出现的位置
str2=str1.left(N); //str2="学生姓名"
N=str1.lastIndexOf (","); // N=18,最后一个逗号的位置
str2=str1.right(str1.size()-N-1); //str2="山东",提取最后一个逗号之后的字符串

  • section()

section()函数的原型为:

QString section (const QString &sep, int start, int end = -1, SectionFlags flags = SectionDefault) const

其功能是从字符串中提取以sep作为分隔符,从start端到end端的字符串。

QString str2, str1="学生姓名,男,1984-3-4,汉族,山东";
str2=str1.section (",",0,0); // str2="学生姓名", 第1段的编号为0
str2=str1.section (",",1,1); // str2="男"
str2=str1.section (",",0,1); // str2="学生姓名,男"
str2=str1.section (",",4,4); // str2="山东"

4.2 SpinBox的使用

QSpinBox用于整数的显示和输入,一般显示十进制数,也可以显示二进制、十六进制的数,而且可以在显示框中增加前缀或后缀。

QDoubleSpinBox用于浮点数的显示和输入,可以设置显示小数位数,也可以设置显示的前缀和后缀。

实例samp4_3演示QSpinBox和QDoubleSpinBox这两个组件的使用,原书的图4-3是程序运行界面。程序功能与实例samp4_1类似,但是使用QSpinBox和QDoubleSpinBox作为数字输入输出组件。

QSpinBox和QDoubleSpinBox都是QAbstractSpinBox的子类,具有大多数相同的属性,只是参数类型不同。在UI设计器里进行界面设计时,就可以设置这些属性。QSpinBox和QDoubleSpinBox的主要属性见表4-1。

表4-1 QSpinBox和QDoubleSpinBox的主要属性

属性名称描述
prefix 数字显示的前缀,例如“$”
suffix 数字显示的后缀,例如“kg”
minimum 数值范围的最小值,如0
maximum 数值范围的最大值,如255
singlestep 单击右侧上下调整按钮时的单步改变值,如设置为1,或0.1
value 当前显示的值
displayIntegerBase QSpinBox特有属性,显示整数使用的进制,例如2就表示二进制
decimals QDoubleSpinBox特有属性,显示数值的小数位数,例如2就显示两位小数

提示:一个属性在类的接口中一般有一个读取函数和一个设置函数,如QDoubleSpinBox的decimals属性,读取属性值的函数为int decimals(),设置属性值的函数为void setDecimals(int prec)。

原书的图4-3中各个SpinBox的类型及属性设置一目了然,不再赘述。使用QSpinBox和Qdouble SpinBox进行数值量的输入输出很方便,下面是图4-3中“计算”按钮和“十进制”后面的按钮的槽函数代码。

void Widget::on_btnCal_clicked()
{ //计算
int num=ui->spinNum->value();
float price=ui->spinPrice->value();
float total=num*price;
ui->spinTotal->setValue(total);
}
void Widget::on_btnBin_clicked()
{ //读取二进制数,以其他进制显示
int val=ui->spinBin->value();
ui->spinDec->setValue(val);
ui->spinHex->setValue(val);
}

在使用QSpinBox和QDoubleSpinBox读取和设置数值时,无需做字符串与数值之间的转换,也无需做进制的转换,其显示效果(前缀、后缀、进制和小数位数)在设置好之后就自动按照效果进行显示,这对于数值的输入输出是非常方便的。

4.3 其他数值输入和显示组件

4.3.1 实例功能

除了LineEdit和SpinBox之外,还有其他一些用于数值输入和显示的组件。实例samp4_4演示如何使用这些组件,原书的图4-4是实例samp4_4的设计界面。

在这个实例中,用到如下一些组件。

  • QSlider:滑动条,通过滑动来设置数值,可用于数值输入。实例中使用4个滑动条输入红、绿、蓝三色和Alpha值,然后合成颜色,作为一个QTextEdit组件的底色。

  • QScrollBar:卷滚条,与QSlider功能类似,还可以用于卷滚区域。

  • QProgressBar:进度条,一般用于显示任务进度,可用于数值的百分比显示。实例程序中滑动一个Slider,获取其值并更新ScrollBar和ProgressBar。

  • QDial:表盘式数值输入组件,通过转动表针获得输入值。

  • QLCDNumber:模仿LCD数字的显示组件,可以显示整数或浮点数,显示整数时可以不同进制显示。实例程序中转动表盘,获得的值显示在LCD组件中。单击“LCD显示进制”的RadioButton时,设置LCD的显示进制。

4.3.2 各组件的主要功能和属性

1.QSlider

QSlider、QScrollBar和Qdial 3个组件都从QAbstractSlider继承而来,有一些共有的属性。QSlider是滑动的标尺型组件,滑动标尺上的一个滑块可以改变值。

基类QAbstractSlider的主要属性包括以下几种。

  • minimum、maximum:设置输入范围的最小值和最大值,例如,用红、绿、蓝配色时,每种基色的大小范围是0~255,所以设置minimum为0,maximum为255。

  • singleStep:单步长,拖动标尺上的滑块,或按下左/右光标键时的最小变化数值。

  • pageStep:在Slider上输入焦点,按PgUp或PgDn键时变化的数值。

  • value:组件的当前值,拖动滑块时自动改变此值,并限定在minimum和maximum定义的范围之内。

  • sliderPosition:滑块的位置,若tracking属性设置为true,sliderPosition就等于value。

  • tracking:sliderPosition是否等同于value,如果tracking=true,改变value时也同时改变sliderPosition。

  • orientation:Slider的方向,可以设置为水平或垂直。方向参数是Qt的枚举类型enum Qt::Orientation,取值包括以下两种。

    • Qt::Horizontal 水平方向

    • Qt::Vertical 垂直方向

  • invertedAppearance:显示方式是否反向,invertedAppearance=false时,水平的Slider由左向右数值增大,否则反过来。

  • invertedControls:反向按键控制,若invertedControls=true,则按下PgUp或PgDn按键时调整数值的方向相反。

属于QSlider的专有属性有两个,如下。

  • tickPosition:标尺刻度的显示位置,使用枚举类型QSlider::TickPosition,取值包括以下6种。

    QSlider::NoTicks    不显示刻度
    QSlider::TicksBothSides 标尺两侧都显示刻度
    QSlider::TicksAbove  标尺上方显示刻度
    QSlider::TicksBelow 标尺下方显示刻度
    QSlider::TicksLeft 标尺左侧显示刻度
    QSlider::TicksRight 标尺右侧显示刻度

  • QSlider::NoTicks    不显示刻度

  • QSlider::TicksBothSides 标尺两侧都显示刻度

  • QSlider::TicksAbove  标尺上方显示刻度

  • QSlider::TicksBelow 标尺下方显示刻度

  • QSlider::TicksLeft 标尺左侧显示刻度

  • QSlider::TicksRight 标尺右侧显示刻度

  • tickInterval:标尺刻度的间隔值,若设置为0,会在singleStep和pageStep之间自动选择。

2.QScrollBar

QScrollBar从QAbstractSlider继承而来的,具有QAbstractSlider的基本属性,没有专有属性。

3.QDial

QDial是仪表盘式的组件,通过旋转表盘获得输入值。QDial的特有的属性包括以下两种。

  • notchesVisible:表盘的小刻度是否可见。

  • notchTarget:表盘刻度间的间隔像素值。

4.QProgressBar

QProgressBar的父类是QWidget,一般用于进度显示,常用属性如下。

  • minimum、maximum:最小值和最大值。

  • value:当前值,可以设定或读取当前值。

  • textVisible:是否显示文字,文字一般是百分比表示的进度。

  • orientation:可以设置为水平或垂直方向。

  • format:显示文字的格式,“%p%”显示百分比,“%v”显示当前值,“%m”显示总步数。缺省为“%p%”。

5.QLCDNumber

QLCDNumber是模拟LCD显示数字的组件,可以显示整数或小数,但就如实际的LCD一样,要设定显示数字的个数。显示整数时,还可以选择以不同进制来显示,如十进制、二进制、十六进制。其主要属性如下。

  • digitCount:显示的数的位数,如果是小数,小数点也算一个数位。

  • smallDecimalPoint:是否有小数点,如果有小数点,就可以显示小数。

  • mode:数的显示进制,通过调用函数setDecMode()、setBinMode()、setOctMode()、setHexMode()可以设置为常用的十进制、二进制、八进制、十六进制格式。

  • value:返回显示值,浮点数。若设置为显示整数,会自动四舍五入后得到整数,设置为intValue的值。如果smallDecimalPoint=true,设置value时可以显示小数,但是数的位数不能超过digitCount。

  • intValue:返回显示的整数值。

例如,若smallDecimalPoint=true,digitCount=3,设置value=2.36,则界面上LCDNumber组件会显示为2.4;若设置value=1456.25,则界面上LCDNumber组件只会显示145。所以,用QLCDNumber作为显示组件时,应注意这些属性的配合。

4.3.3 实例功能的代码实现

1.红绿蓝配色

使用Red、Green、Blue和Alpha 4个滑动条配色,然后设置为旁边的TextEdit的底色。界面操作是在拖动滑块时就进入响应程序,为QSlider的valueChanged()信号设计槽函数。在UI设计器里,选中滑动条SliderRed,然后在“Go to slot”对话框里选择valueChanged(int)信号,生成槽函数原型声明和函数体,编写代码如下:

void Widget::on_SliderRed_valueChanged(int value)
{ //拖动Red、Green、Blue 颜色滑动条时设置textEdit的底色
Q_UNUSED(value);
QColor color;
int R=ui->SliderRed->value();
int G=ui->SliderGreen->value();
int B=ui->SliderBlue->value();
int alpha=ui->SliderAlpha->value();
color.setRgb(R,G,B,alpha); //使用QColor的setRgb()函数获得颜色
QPalette pal=ui->textEdit->palette();
pal.setColor(QPalette::Base,color); //设置底色
ui->textEdit->setPalette(pal);
}

在这段代码里,使用QSlider::value()函数获得滑动条的当前值,然后调用QColor::setRgb()函数生成颜色。QColor的静态函数setRgb()定义为:

void QColor::setRgb(int r, int g, int b, int a = 255)

其中r、g、b是红、绿、蓝颜色值,均在0在255之间,a是颜色的alpha值,缺省是255,取值范围也是0~255。

以上代码是SliderRed的valueChanged(int)信号的槽函数代码,其他3个滑动条的响应代码也与此完全相同。那么在设计程序时,无需为其他3个Slider的valueChanged(int)信号再生成槽函数,只需将它们的valueChanged(int)信号与槽函数on_SliderRed_valueChanged()关联即可。在窗口类Widget的构造函数里实现此功能,代码如下:

Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
this->setLayout(ui->horizontalLayout);
QObject::connect(ui->SliderGreen,SIGNAL(valueChanged(int)),
this,SLOT(on_SliderRed_valueChanged(int)));
QObject::connect(ui->SliderBlue,SIGNAL(valueChanged(int)),
this,SLOT(on_SliderRed_valueChanged(int)));
QObject::connect(ui->SliderAlpha,SIGNAL(valueChanged(int)),
this,SLOT(on_SliderRed_valueChanged(int)));
}

这样设置信号与槽的关联后,这4个滑动条中任何一个的值改变时,发射valueChanged(int)信号,都调用同一个槽函数on_SliderRed_valueChanged(int),避免了重复编写代码。

2.LCD值显示

滑动表盘组件的指针时,设定LCD的显示值等于表盘的值。单击进制设置的RadioButton可以设置LCD的显示进制和显示数位数。为Dial组件的valueChanged(int)信号和每个RadioButton的clicked()信号生成槽函数,编写代码实现相应的功能,代码如下:

void Widget::on_dial_valueChanged(int value)
{ //设置LCD的显示值等于Dial的值
ui->LCDDisplay->display(value);
}
void Widget::on_radioBtnDec_clicked()
{ //设置LCD显示十进制数
ui->LCDDisplay->setDigitCount(3); //设置位数
ui->LCDDisplay->setDecMode();
}
void Widget::on_radioBtnBin_clicked()
{ //设置LCD显示二进制数
ui->LCDDisplay->setDigitCount(8);
ui->LCDDisplay->setBinMode();
}
void Widget::on_radioBtnOct_clicked()
{ //设置LCD显示八进制数
ui->LCDDisplay->setDigitCount(4);
ui->LCDDisplay->setOctMode();
}
void Widget::on_radioBtnHex_clicked()
{ //设置LCD显示十六进制数
ui->LCDDisplay->setDigitCount(3);
ui->LCDDisplay->setHexMode();
}

4.4 时间日期与定时器

4.4.1 时间日期相关的类

时间日期是经常遇到的数据类型,Qt中时间日期类型的类如下。

  • QTime: 时间数据类型,仅表示时间,如 15:23:13。

  • QDate: 日期数据类型,仅表示日期,如2017-4-5。

  • QDateTime:日期时间数据类型,表示日期和时间,如2017-03-23 08:12:43。

Qt中有专门用于日期、时间编辑和显示的界面组件,介绍如下。

  • QTimeEdit: 编辑和显示时间的组件。

  • QDateEdit:编辑和显示日期的组件。

  • QDateTimeEdit:编辑和显示日期时间的组件。

  • QCalendarWidget:一个用日历形式选择日期的组件。

定时器是用来处理周期性事件的一种对象,类似于硬件定时器。例如设置一个定时器的定时周期为1000毫秒,那么每1000毫秒就会发射定时器的timeout()信号,在信号关联的槽函数里就可以做相应的处理。Qt中的定时器类是QTimer,它直接从QObject类继承而来,不是界面组件类。

实例程序samp4_5演示这些时间日期相关类的使用,其运行时界面如原书的图4-5所示。

4.4.2 日期时间数据与字符串之间的转换

1.时间、日期编辑器属性设置

在图4-5窗体左上方的“日期时间”Groupbox中,使用QTimeEdit、QDateEdit、QDateTimeEdit组件作为时间、日期、日期时间编辑器;在其右侧,各放置一个QLineEdit组件用于字符串显示。

QDateEdit和QTimeEdit都从QDateTimeEdit继承而来,实现针对日期或时间的特定显示功能。实际上,QDateEdit和QTimeEdit的显示功能都可以通过QDateTimeEdit实现,只需设置好属性即可。

QDateTimeEdit类的主要属性的介绍如下。

  • datetime:日期时间。

  • date:日期,设置datetime时会自动改变date,同样,设置date时,也会自动改变datetime里的日期。

  • time:时间,设置datetime时会自动改变time,同样,设置time时,也会自动改变datetime里的时间。

  • maximumDateTime、minimumDateTime: 最大、最小日期时间。

  • maximumDate、minimumDate: 最大、最小日期。

  • maximumTime、minimumTime: 最大、最小时间。

  • currentSection:当前输入光标所在的时间日期数据段,是枚举类型QDateTimeEdit::Section。QDateTimeEdit显示日期时间数据时分为多个段,单击编辑框右侧的上下按钮可修改当前段的值。如输入光标在YearSection段,就修改“年”的值。

  • currentSectionIndex:用序号表示的输入光标所在的段。

  • calendarPopup:是否允许弹出一个日历选择框。当取值为true时,右侧的输入按钮变成与QComboBox类似的下拉按钮,单击按钮时出现一个日历选择框,用于在日历上选择日期。对于QTimeEdit,此属性无效。

  • displayFormat:显示格式,日期时间数据的显示格式,例如设置为“yyyy-MM-dd HH:mm:ss”,一个日期时间数据就显示为“2016-11-02 08:23:46”。

2.日期时间数据的获取与转换为字符串

“读取当前日期时间”按钮的clicked()信号的槽函数代码如下:

void Dialog::on_btnGetTime_clicked()
{ //获取当前日期时间,为三个专用编辑器设置日期时间数据,并转换为字符串
QDateTime curDateTime=QDateTime::currentDateTime();
ui->timeEdit->setTime(curDateTime.time());
ui->editTime->setText(curDateTime.toString("hh:mm:ss"));
ui->dateEdit->setDate(curDateTime.date());
ui->editDate->setText(curDateTime.toString("yyyy-MM-dd"));
ui->dateTimeEdit->setDateTime(curDateTime);
ui->editDateTime->setText(curDateTime.toString("yyyy-MM-dd hh:mm:ss"));
}

首先用QDateTime类的静态函数currentDateTime()获取当前日期时间,并赋值给变量curDate Time。

然后用curDateTime变量设置界面上3个日期、时间编辑器的时间或日期值,利用了QDateTime的time()和date()分别提取时间和日期。

将curDateTime表示的日期时间数据转换为字符串,然后在LineEdit编辑框上显示。时间日期转换为字符串使用了QDateTime的toString()函数,分别用不同的格式显示时间、日期、日期时间。

ui->editTime->setText(curDateTime.toString("hh:mm:ss"));
ui->editDate->setText(curDateTime.toString("yyyy-MM-dd"));
ui->editDateTime->setText(curDateTime.toString("yyyy-MM-dd hh:mm:ss"));

QDateTime::toString()函数的函数原型是:

QString QDateTime::toString(const QString &format) const

它将日期时间数据按照format指定的格式转换为字符串。format是一个字符串,包含一些特定的字符,表示日期或时间的各个部分,表4-2是用于日期时间显示的常用格式符。

表4-2 用于日期显示的格式符及其意义

字符意义
d 天,不补零显示,1-31
dd 天,补零显示,01-31
M 月,不补零显示,1-12
MM 月,补零显示,01-12
yy 年,两位显示,00-99
yyyy 年,4位数字显示,如2016
h 小时,不补零,0-23或1-12(如果显示AM/PM)
hh 小时,补零2位显示,00-23或01-12(如果显示AM/PM)
H 小时,不补零,0-23(即使显示AM/PM)
HH 小时,补零显示,00-23(即使显示AM/PM)
m 分钟,不补零,0-59
mm 分钟,补零显示,00-59
z 毫秒,不补零,0-999
zzz 毫秒,补零3位显示,000-999
AP或A 使用AM/pm显示
ap或a 使用am/pm显示

在设置日期时间显示字符串格式时,还可以使用填字符,甚至使用汉字。例如,日期显示格式可以设置为:

curDateTime.toString("yyyy年MM月dd日");

这样得到的字符串是“2016年11月21日”。

3.字符串转换为日期时间

同样的,也可以将字符串转换为QTime、QDate或QDateTime类型,使用静态函数QDateTime:: fromString(),其函数原型为:

QDateTime QDateTime::fromString(const QString &string, const QString &format)

其中,第1个参数string是日期时间字符串,第2个参数format是字符串表示的格式,按照表4-2的格式字符定义。

在程序运行时,手工修改“日期时间”后面文本框里的日期时间字符串,单击“设置日期时间”按钮,可以将文本框里的字符串转换为QDateTime变量,并设置为左侧DateTimeEdit组件的日期时间数据,代码如下:

void Dialog::on_btnSetDateTime_clicked()
{//字符串转换为 QDateTime
QString str=ui->editDateTime->text();
str=str.trimmed();
if (!str.isEmpty())
{
QDateTime datetime=QDateTime::fromString(str,"yyyy-MM-dd hh:mm:ss");
ui->dateTimeEdit->setDateTime(datetime);
}
}

静态函数QDateTime::fromString()将一个字符串按照格式转换为日期时间类型。程序中的代码是:

datetime=QDateTime::fromString(str,"yyyy-MM-dd hh:mm:ss");

这里将字符串str按照格式"yyyy-MM-dd hh:mm:ss"转换为日期时间变量,格式是指字符串str所表示的日期时间的格式。

4.4.3 QCalendarWidget日历组件

原书的图4-5窗体右侧是一个QCalendarWidget组件,它以日历的形式显示日期,可以用于日期选择。

QCalendarWidget有一个信号selectionChanged(),在日历上选择的日期变化后会发射此信号,为此信号创建槽函数,编写代码如下:

void Dialog::on_calendarWidget_selectionChanged()
{ //在日历上选择日期
QDate dt=ui->calendarWidget->selectedDate();
QString str=dt.toString("yyyy年M月d日");
ui->editCalendar->setText(str);
}

4.4.4 定时器的使用

Qt中的定时器类是QTimer。QTimer不是一个可见的界面组件,在UI 设计器的组件面板里找不到它。实例程序实现了一个计时器的功能,就是计算定时器开始到停止持续的时间长度,计时器是QTime类。

QTimer主要的属性是interval,是定时中断的周期,单位毫秒。QTimer主要的信号是timeout(),在定时中断时发射此信号,要想在定时中断里做出响应,这就需要编写timeout()信号的槽函数。下面是窗口类中增加的定义(省略了其他不相关的定义):

class Dialog : public QDialog
{
private:
QTimer *fTimer; //定时器
QTime fTimeCounter;//计时器
private slots:
void on_timer_timeout(); //定时溢出处理槽函数
};

这里定义了一个定时器fTimer,一个计时器fTimeCounter。还定义了一个槽函数on_timer_timeout(),作为定时器的timeout()信号的响应槽函数。

需要在窗口类的构造函数里创建定时器,并进行信号与槽的关联。代码如下:

Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog)
{
ui->setupUi(this);
fTimer=new QTimer(this);
fTimer->stop();
fTimer->setInterval(1000);//设置定时周期, 单位:毫秒
connect(fTimer,SIGNAL(timeout()),this,SLOT(on_timer_timeout()));
}

槽函数on_timer_timeout()的实现代码如下:

void Dialog::on_timer_timeout()
{ //定时器中断响应
QTime curTime=QTime::currentTime(); //获取当前时间
ui->LCDHour->display(curTime.hour()); //显示 小时
ui->LCDMin->display(curTime.minute());//显示 分钟
ui->LCDSec->display(curTime.second());//显示 秒
int va=ui->progressBar->value();
va++;
if (va>100)
va=0;
ui->progressBar->setValue(va);
}

这段代码首先用QTime类的静态函数QTime::currentTime()获取当前时间,然后用QTime的成员函数hour()、minute()、second()分别获取小时、分钟、秒,并在几个LCDNumber组件上显示。循环更新progressBar的值,是为了让界面有变化,表示定时器在运行。

设置定时器的周期,只需调用QTimer::setInterval()函数即可。

QTimer::start()函数用于启动定时器,界面上的“开始”按钮代码如下:

void Dialog::on_btnStart_clicked()
{
fTimer->start();//定时器开始工作
fTimeCounter.start();//计时器开始工作
ui->btnStart->setEnabled(false);
ui->btnStop->setEnabled(true);
ui->btnSetIntv->setEnabled(false);
}

计时器fTimeCounter执行start()是将当前时间作为计时器的时间。

QTimer::stop()函数停止定时器,界面上的“停止”按钮可实现这一功能,其代码如下:

void Dialog::on_btnStop_clicked()
{
fTimer->stop(); //定时器停止
int tmMsec=fTimeCounter.elapsed();//毫秒数
int ms=tmMsec%1000;
int sec=tmMsec/1000;
QString str=QString::asprintf("流逝时间:%d 秒,%d 毫秒",sec,ms);
ui->LabElapsTime->setText(str);
ui->btnStart->setEnabled(true);
ui->btnStop->setEnabled(false);
ui->btnSetIntv->setEnabled(true);
}

4.5 QComboBox和QPlainTextEdit

4.5.1 实例功能概述

QComboBox是下拉列表框组件类,它提供一个下拉列表供用户选择,也可以直接当作一个QLineEdit用作输入。QComboBox除了显示可见下拉列表外,每个项(item,或称列表项)还可以关联一个QVariant类型的变量,用于存储一些不可见数据。

QPlainTextEdit是一个多行文本编辑器,用于显示和编辑多行简单文本。实例samp4_6演示QComboBox和QPlainTextEdit的使用,其运行时界面如原书的图4-6所示。

4.5.2 QComboBox的使用

1.设计时属性设置

QComboBox主要的功能是提供一个下拉列表供选择输入。在界面上放置一个QComboBox组件后,双击此组件,可以出现如原书的图4-7所示的对话框,对QComboBox组件的下拉列表的项进行编辑。在图4-7所示的对话框中,可以进行编辑,如添加、删除、上移、下移操作,还可以设置项的图标。

2.用代码添加简单项

窗口上的“初始化列表”按钮初始化下拉列表框的列表内容,其代码如下:

void Widget::on_btnIniItems_clicked()
{ //"初始化列表"按钮
QIcon icon;
icon.addFile(":/images/icons/aim.ico");
ui->comboBox->clear();
for (int i=0;i<20;i++)
ui->comboBox->addItem(icon,QString::asprintf("Item %d",i)); //带图标
// ui->comboBox->addItem(QString::asprintf("Item %d",i)); //不带图标
}

添加一个项时可以指定一个图标,图标来源于资源文件。

addItem()用于添加一个列表项,如果只是添加字符串列表项,而且数据来源于一个QStringList变量,可以使用addItems()函数,示例代码如下:

ui->comboBox->clear();
QStringList strList;
strList<<"北京"<<"上海"<<"天津"<<"河北省"<<"山东省"<<"山西省";
ui->comboBox->addItems(strList);

3.添加具有用户数据的项

QComboBox::addItem()函数的两种参数的原型定义如下:

void addItem (const QString &text, const QVariant &userData = QVariant())
void addItem (const QIcon &icon, const QString &text, const QVariant &userData = QVariant())

不管是哪一个addItem()函数,后面都有一个可选的QVariant类型的参数userData,可以利用这个变量存储用户定义数据。

界面上另一个ComboBox组件使用了用户数据,“初始化城市+区号”按钮的槽函数代码如下:

void Widget::on_btnIni2_clicked()
{//初始化具有自定义数据的ComboBox
QMap<QString, int> City_Zone;
City_Zone.insert("北京",10);
City_Zone.insert("上海",21);
City_Zone.insert("天津",22);
City_Zone.insert("大连",411);
City_Zone.insert("锦州",416);
City_Zone.insert("徐州",516);
City_Zone.insert("福州",591);
City_Zone.insert("青岛",532);
ui->comboBox2->clear();
foreach(const QString &str,City_Zone.keys())
ui->comboBox2->addItem(str,City_Zone.value(str));
}

这里定义了一个关联容器类QMap<QString, int> City_Zone,用于存储<城市,区号>映射表。为City_Zone填充数据后,给comboBox2添加项时,使用了foreach关键字遍历City_Zone.keys()。添加项的语句如下:

ui->comboBox2->addItem(str,City_Zone.value(str));

城市名称作为项显示的字符串,电话区号作为项关联的用户数据,但是在列表框里只能看到城市名称。

注意:将City_Zone的内容添加到列表框之后,列表框里显示的列表项的顺序与源程序中设置City_Zone的顺序不一致,因为QMap<Key, T>容器类会自动按照Key排序。

4.QComboBox列表项的访问

QComboBox存储的项是一个列表,但是QComboBox不提供整个列表用于访问,可以通过索引访问某个项。访问项的一些函数主要有以下几种。

  • int currentIndex(),返回当前项的序号,第一个项的序号为0。

  • QString currentText(),返回当前项的文字。

  • QVariant currentData(int role = Qt::UserRole),返回当前项的关联数据,数据的缺省角色为role = Qt::UserRole,角色的意义在5.1节详细介绍。

  • QString itemText(int index),返回指定索引号的项的文字。

  • QVariant itemData(int index, int role = Qt::UserRole),返回指定索引号的项的关联数据。

  • int count(),返回项的个数。

在一个QComboBox组件上选择项发生变化时,会发射如下两个信号:

void currentIndexChanged(int index)
void currentIndexChanged(const QString &text)

这两个信号只是传递的参数不同,一个传递的是当前项的索引号,一个传递的当前项的文字。

为使用方便,选择为currentIndexChanged(const QString &text)信号编写槽函数。窗体上只存储字符串列表的comboBox的槽函数代码如下:

void Widget::on_comboBox_currentIndexChanged(const QString &arg1)
{
ui->plainTextEdit->appendPlainText(arg1);
}

关联有城市区号的comboBox2的槽函数代码如下:

void Widget::on_comboBox2_currentIndexChanged(const QString &arg1)
{
QString zone=ui->comboBox2->currentData().toString(); //项关联的数据
ui->plainTextEdit->appendPlainText(arg1+":区号="+zone);
}

4.5.3 QPlainTextEdit的使用

1.QPlainTextEdit的功能

QPlainTextEdit是用于编辑多行文本的编辑器,可以编辑普通文本。另外,还有一个QTextEdit组件,是一个所见即所得的可以编辑带格式文本的组件,以HTML格式标记符定义文本格式。

从前面的代码已经看出,使用QPlainTextEdit::appendPlainText(const QString &text)函数就可以向PlainTextEdit组件添加一行字符串。

QPlainTextEdit提供cut()、copy()、paste()、undo()、redo()、clear()、selectAll()等标准编辑功能的槽函数,QPlainTextEdit还提供一个标准的右键快捷菜单。

2.逐行读取文字内容

如果要将QPlainTextEdit组件里显示的所有文字读取出来,有一个简单的函数toPlainText()可以将全部文字内容输出为一个字符串,其定义如下:

QString QPlainTextEdit::toPlainText() const

但是如果要逐行读取QPlainTextEdit组件里的字符串,则稍微麻烦一点。

下面是原书的图4-6窗口中“文本框内容添加到ComboBox”按钮的响应代码,它将plainTextEdit里的每一行作为一个项添加到comboBox里。

void Widget::on_btnToComboBox_clicked()
{ //plainTextEdit 的内容逐行添加为 comboBox 的项
QTextDocument* doc=ui->plainTextEdit->document();//文本对象
int cnt=doc->blockCount();//回车符是一个block
QIcon icon(":/images/icons/aim.ico");
ui->comboBox->clear();
for (int i=0; i<cnt;i++)
{
QTextBlock textLine=doc->findBlockByNumber(i);// 文本中的一段
QString str=textLine.text();
ui->comboBox->addItem(icon,str);
}
}

QPlainTextEdit的文字内容以QTextDocument类型存储,函数document()返回这个文档对象的指针。

QTextDocument是内存中的文本对象,以文本块的方式存储,一个文本块就是一个段落,每个段落以回车符结束。QTextDocument提供一些函数实现对文本内容的存取。

  • int blockCount(),获得文本块个数。

  • QTextBlock findBlockByNumber(int blockNumber),读取某一个文本块,序号从0开始,至blockCount()-1结束。

一个document有多个TextBlock,从document中读取出的一个文本块类型为QTextBlock,通过QTextBlock::text()函数可以获取其纯文本文字。

3.使用QPlainTextEdit自带的快捷菜单

QPlainTextEdit是一个多行文字编辑框,有自带的右键快捷菜单,可实现常见的编辑功能。在UI设计器里,选择为plainTextEdit的customContextMenuRequested()信号生成槽函数,编写如下的代码,就可以创建并显示QPlainTextEdit的标准快捷菜单:

void Widget::on_plainTextEdit_customContextMenuRequested(const QPoint &pos)
{ //创建并显示标准弹出式菜单
QMenu* menu=ui->plainTextEdit->createStandardContextMenu();
menu->exec(pos);
}

4.6 QListWidget和QToolButton

4.6.1 实例功能简介

Qt中用于项(Item)处理的组件有两类,一类是Item Views,包括QListView、QTreeView、QTableView、QColumnView等;另一类是Item Widgets,包括QListWidget、QTreeWidget和QTableWidget。

Item Views基于模型/视图(Model/View)结构,视图(View)与模型数据(Model Data)关联实现数据的显示和编辑,模型/视图结构的使用在第5章详细介绍。

Item Widgets是直接将数据存储在每一个项里,例如,QListWidget的每一行是一个项,QTreeWidget的每个节点是一个项,QTableWidget的每一个单元格是一个项。一个项存储了文字、文字的格式、自定义数据等。

Item Widgets是GUI设计中常用的组件,本节通过实例samp4_7先介绍QListWidget以及其他一些组件的用法,实例运行时界面如原书的图4-8所示。

本实例不仅介绍QListWidget的使用,还包括如下一些功能的实现。

  • 使用QTabWidget设计多页界面,工作区右侧就是一个具有3个页面的TabWidget组件。

  • 使用QToolBox设计多组工具箱,工作区左侧是一个有3个组的ToolBox组件。

  • 使用分隔条(QSplitter)设计可以左右分割的界面,工作区的ToolBox和TabWidget之间有一个splitter,运行时可以分割调整两个组件的大小。

  • 创建Actions,用Actions设计主工具栏,用Action关联QToolButton按钮。

  • 使用QToolButton按钮,设置与Action关联,设计具有下拉菜单功能的ToolButton按钮,在主工具栏上添加具有下拉菜单的ToolButton按钮。

  • 使用QListWidget,演示如何创建和添加项,为项设置图标和复选框,如何遍历列表进行选择。

  • QListWidget的主要信号currentItemChanged()的功能,编写响应槽函数。

  • 为QListWidget组件利用已设计的Actions创建自定义快捷菜单。

4.6.2 界面设计

1.混合式界面设计

本实例的主窗口从QMainWindow继承而来,采用混合式界面设计。在UI设计器里完成的窗体界面如原书的图4-9所示,与原书的图4-8所示运行界面有一些区别。运行时在工具栏上增加了一个具有下拉菜单的工具栏按钮,为各个ToolButton按钮设置了关联的Action。

图4-9中界面上的按钮都使用QToolButton组件,在设计时只为其命名,图中按钮上显示的文字就是其objectName。QToolButton有一个setDefaultAction()函数,可以使其与一个Action关联,按钮的文字、图标、ToolTip都将自动设置为与关联的Action一致,单击一个QToolButton按钮就是执行Action的槽函数,与工具栏上的按钮一样。实际上,主工具栏上的按钮就是根据Action自动创建的QToolButton按钮。

QToolButton还有一个setMenu()函数,可以为其设置一个下拉式菜单,配合QToolButton的一些属性设置,可以有不同的下拉菜单效果。在图4-8中,工具栏上的“项选择”直接显示下拉菜单,而在列表框上方的“项选择”按钮,只有单击右侧的向下箭头才弹出下拉菜单,直接单击按钮会执行按钮关联的Action的代码。

混合式界面设计中用代码实现的部分,就是为界面上的各ToolButton按钮设置关联的Action,在工具栏上动态添加一个ToolButton,并设置其下拉菜单功能。

2.QToolBox组件的设置

在UI设计器里设计界面时,在窗口的工作区放置一个QToolBox组件。

在ToolBox组件上调出右键快捷菜单,可以使用“Insert Page”“Delete Page”等菜单项实现分组的添加或删除。单击某个分组的标题,就可以选择为ToolBox组件的当前分组,在Property Editor中主要的属性设置如下。

  • currentIndex,当前分组编号,第1个分组的编号是0,通过改变这个值,可以选择不同的分组页面。

  • currentItemText,当前分组的标题。

  • currentItemName,当前分组的对象名称。

  • currentItemIcon,为当前分组设置一个图标,显示在文字标题的左侧。

在一个ToolBox内可以放置任何界面组件,如QGroupBox、QLineEdit、QPushButton等。在第一个分组里放置几个QToolButton按钮,并设置为Grid布局。注意不要使用水平布局,因为使用水平布局时,组内的ToolButton按键都是自动向左靠齐的,而使用Grid布局时,自动居中。

3.QTabWidget组件的设置

QTabWidget是一个多页的容器类组件。在窗口上放置一个QTabWidget组件,通过其快捷菜单的“Insert Page”“Delete Page”等菜单项实现页面的添加或删除。在Property Editor中主要的属性设置如下。

  • tabPosition:页标签的位置,东、西、南、北四个方位中选择一个。

  • currentIndex:当前页的编号。

  • currentTabText:当前页的标题。

  • currentTabName:当前页的对象名称。

  • currentTabIcon:可以为当前页设置一个图标,显示在文字标题的左侧。

4.使用QSplitter设计分割界面

具有分割效果的典型界面是Windows的资源管理器,QSplitter用于设计具有分割效果的界面,可以左右或上下分割。

本实例主窗口两个主要的组件是toolBox和tabWidget,希望这两个组件设计为左右分割的效果。同时选择这两个组件,单击主窗口工具栏上的“Lay Out Horizontally in Splitter”按钮,就可以为这两个组件创建一个水平分割的布局组件splitter。在主窗口构造函数里使用下面一行语句就可以使splitter填充满整个工作区。

setCentralWidget(ui->splitter);

在使用分割条调整大小时,如果不希望ToolBox的宽度变得太小而影响按钮的显示,可以设置toolBox的minimumSize.Width属性,设置一个最小宽度。

5.QListWidget的设置

在TabWidget组件的第一个页面上放置一个QListWidget组件,以及其他几个按钮和编辑框,组成如原书的图4-9所示的界面。QListWidget是存储多个项的列表组件,每个项是一个QListWidgetItem类型的对象。

双击ListWidget组件,可以打开其列表项编辑器,如原书的图4-10所示。在这个编辑器里可以增加、删除、上移、下移列表项,可以设置每个项的属性,包括文字内容、字体、文字对齐方式、背景色、前景色等。

比较重要的是其flags属性(如图4-10所示),用于设置项的一些标记,这些标记是枚举类型Qt::ItemFlag的具体值,包括以下几种。

  • Selectable:项是否可被选择,对应枚举值Qt::ItemIsSelectable。

  • Editable:项是否可被编辑,对应枚举值Qt::ItemIsEditable。

  • DragEnabled:项是否可以被拖动,对应枚举值Qt::ItemIsDragEnabled。

  • DropEnabled:项是否可以接收拖放的项,对应枚举值Qt::ItemIsDropEnabled。

  • UserCheckable:项是否可以被复选,若为true,项前面出现一个CheckBox,对应枚举值Qt::ItemIsUserCheckable。

  • Enabled:项是否被使能,对应枚举值Qt::ItemIsEnabled。

  • Tristate:是否允许Check的第三种状态,若为false,则只有checked和unChecked两种状态,对应枚举值Qt::ItemIsAutoTristate。

在代码中设置项的flags属性时,使用函数setFlags(),例如:

aItem->setFlags(Qt::ItemIsSelectable |Qt::ItemIsUserCheckable |Qt::ItemIsEnabled);

QListWidget的列表项一般是在程序里动态创建,后面会演示如何用程序完成添加、删除列表项等操作。

6.创建Action

在2.4节已经介绍了创建Action,学习用Action设计主菜单和主工具栏的方法。本实例也采用Action设计工具栏,并且将Action用于QToolButton按钮。创建的Action列表如原书的图4-11所示。利用这些Action创建主工具栏,设计时完成的主工具栏如原书的图4-9所示。

actSelPopMenu用于“项选择”的ToolButton按钮,也就是窗口上具有下拉菜单的两个按钮。将actSelPopMenu的功能设置为与actSelInvs完全相同,在“Signals &Slots Editor”里设置这两个Action关联(如原书的图4-12所示),这样,执行actSelPopMenu就是执行actSelInvs。

4.6.3 QListWidget的操作

1.初始化列表

actListIni实现listWidget的列表项初始化,其trigger()信号槽函数代码如下:

void MainWindow::on_actListIni_triggered()
{ //初始化列表
QListWidgetItem *aItem; //每一行是一个QListWidgetItem
QIcon aIcon;
aIcon.addFile(":/images/icons/check2.ico");
bool chk=ui->chkBoxListEditable->isChecked();//是否可编辑

ui->listWidget->clear();
for (int i=0; i<10; i++)
{
QString str=QString::asprintf("Item %d",i);
aItem=new QListWidgetItem();
aItem->setText(str); //设置文字标签
aItem->setIcon(aIcon); //设置图标
aItem->setCheckState(Qt::Checked); //设置为选中状态
if (chk) //可编辑, 设置flags
aItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable
|Qt::ItemIsUserCheckable |Qt::ItemIsEnabled);
else//不可编辑, 设置flags
aItem->setFlags(Qt::ItemIsSelectable |Qt::ItemIsUserCheckable
|Qt::ItemIsEnabled);
ui->listWidget->addItem(aItem); //增加一个项
}
}

列表框里一行是一个项,是一个QListWidgetItem类型的对象,向列表框添加一个项,就需要创建一个QListWidgetItem类型的实例aItem,然后设置aItem的一些属性,再用QListWidget::AddItem()函数将该aItem添加到列表框里。

QListWidgetItem有许多函数方法,可以设置项的很多属性,如设置文字、图标、选中状态,还可以设置flags,就是原书的图4-10对话框里设置功能的代码化。

2.插入项

插入项使用QListWidget的insertItem(int row, QListWidgetItem *item)函数,在某一行row的前面插入一个QListWidgetItem对象item,也需要先创建这个item,并设置好其属性。actListInsert实现这个功能,其槽函数代码如下:

void MainWindow::on_actListInsert_triggered()
{ //插入一个项
QIcon aIcon;
aIcon.addFile(":/images/icons/check2.ico");
bool chk=ui->chkBoxListEditable->isChecked();

QListWidgetItem* aItem=new QListWidgetItem("New Inserted Item");
aItem->setIcon(aIcon);
aItem->setCheckState(Qt::Checked);
if (chk)
aItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable
|Qt::ItemIsUserCheckable |Qt::ItemIsEnabled);
else
aItem->setFlags(Qt::ItemIsSelectable |Qt::ItemIsUserCheckable
|Qt::ItemIsEnabled);
ui->listWidget->insertItem(ui->listWidget->currentRow(),aItem);
}

这里设置新插入的项是可选择的、能用的、可复选的。还根据界面设置,确定项是否可编辑。

3.删除当前项和清空列表

删除当前项的actListDelete的槽函数代码如下:

void MainWindow::on_actListDelete_triggered()
{ //删除当前项
int row=ui->listWidget->currentRow();
QListWidgetItem* aItem=ui->listWidget->takeItem(row);
delete aItem; //手工删除对象
}

takeItem()函数只是移除一个项,并不删除项对象,所以还需要用delete从内存中删除它。

要清空列表框的所有项,只需调用QListWidget::clear()函数即可。

4.遍历并选择项

界面上有“全选”“全不选”“反选”3个按钮,由3个Action实现,用于遍历列表框里的项,设置选择状态。用于“全选”的actSelALL的槽函数代码如下:

void MainWindow::on_actSelALL_triggered()
{ //全选
int cnt=ui->listWidget->count();//个数
for (int i=0; i<cnt; i++)
{
QListWidgetItem *aItem=ui->listWidget->item(i);//获取一个项
aItem->setCheckState(Qt::Checked);//设置为选中
}
}

函数QListWidgetItem::setCheckState(Qt::CheckState state)设置列表项的复选状态,枚举类型Qt::CheckState有3种取值。

  • Qt::Unchecked,不被选中。

  • Qt::PartiallyChecked,部分选中。在目录树中的节点可能出现这种状态,比如其子节点没有全部被勾选时。

  • Qt::Checked,被选中。

5.QListWidget的常用信号

QListWidget在当前项切换时发射两个信号,只是传递的参数不同。

  • currentRowChanged(int currentRow):传递当前项的行号作为参数。

  • currentItemChanged(QListWidgetItem current, QListWidgetItem previous):传递两个QList WidgetItem对象作为参数,current表示当前项,previous是前一项。

当前项的内容发生变化时发射信号currentTextChanged(const QString &currentText)。

为listWidget的currentItemChanged()信号编写槽函数,代码如下:

void MainWindow::on_listWidget_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)
{ //listWidget当前选中项发生变化
QString str;
if (current != NULL)
{
if (previous==NULL)
str="当前项:"+current->text();
else
str="前一项:"+previous->text()+"; 当前项:"+current->text();
ui->editCutItemText->setText(str);
}
}

响应代码里需要判断current和previous指针是否为空,否则使用对象时可能出现访问错误。

4.6.4 QToolButton与下拉式菜单

1.QToolButton关联QAction

原书的图4-9所示的界面上,在ToolBox里放置了几个QToolButton按钮,希望它们实现与工具栏上的按钮相同的功能;列表框上方放置了几个QToolButton按钮,希望它们完成列表项选择的功能。

这些功能都已经有相应的Actions来实现,要让ToolButton按钮实现这些功能,无需再为其编写代码,只需设置一个关联的QAction对象即可。

QToolButton有一个函数setDefaultAction(),其函数原型为:

void QToolButton::setDefaultAction(QAction *action)

使用setDefaultAction()函数为一个QToolButton按钮设置一个Action之后,将自动获取Action的文字、图标、ToolTip等设置为按钮的相应属性。所以,在界面设计时无需为QToolButton做过多的设置。

在主窗体类里定义一个私有函数setActionsForButton(),用来为界面上的QToolButton按钮设置关联的Actions,setActionsForButton()在主窗口的构造函数里调用,其代码如下:

void MainWindow::setActionsForButton()
{//为QToolButton按钮设置Action
ui->tBtnListIni->setDefaultAction(ui->actListIni);
ui->tBtnListClear->setDefaultAction(ui->actListClear);
ui->tBtnListInsert->setDefaultAction(ui->actListInsert);
ui->tBtnListAppend->setDefaultAction(ui->actListAppend);
ui->tBtnListDelete->setDefaultAction(ui->actListDelete);

ui->tBtnSelALL->setDefaultAction(ui->actSelALL);
ui->tBtnSelNone->setDefaultAction(ui->actSelNone);
ui->tBtnSelInvs->setDefaultAction(ui->actSelInvs);
}

在程序启动后,界面上的ToolButton按钮自动根据关联的Actions设置其按钮文字、图标和ToolTip。单击某个ToolButton按钮,就是执行了其关联的Action的槽函数代码。使用Actions集中设计功能代码,用于菜单、工具栏、ToolButton的设计,是避免重复编写代码的一种方式。

2.为QToolButton按钮设计下拉菜单

还可以为QToolButton按钮设计下拉菜单,在原书的图4-8的运行窗口中,单击工具栏上的“项选择”按钮,会在按钮的下方弹出一个菜单,有3个菜单项用于项选择。

在主窗口类里定义一个私有函数createSelectionPopMenu(),并在窗口的构造函数里调用,其代码如下:

void MainWindow::createSelectionPopMenu()
{//创建下拉菜单
QMenu* menuSelection=new QMenu(this); //创建弹出式菜单
menuSelection->addAction(ui->actSelALL);
menuSelection->addAction(ui->actSelNone);
menuSelection->addAction(ui->actSelInvs);
//listWidget上方的tBtnSelectItem按钮
ui->tBtnSelectItem->setPopupMode(QToolButton::MenuButtonPopup);
ui->tBtnSelectItem->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
ui->tBtnSelectItem->setDefaultAction(ui->actSelPopMenu);//关联Action
ui->tBtnSelectItem->setMenu(menuSelection); //设置下拉菜单
//工具栏上的 下拉式菜单按钮
QToolButton *aBtn=new QToolButton(this);
aBtn->setPopupMode(QToolButton::InstantPopup);
aBtn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);//按钮样式
aBtn->setDefaultAction(ui->actSelPopMenu);
aBtn->setMenu(menuSelection);//设置下拉菜单
ui->mainToolBar->addWidget(aBtn); //工具栏添加按钮
//工具栏添加分隔条,和"退出"按钮
ui->mainToolBar->addSeparator();
ui->mainToolBar->addAction(ui->actQuit);
}

这段代码首先创建一个QMenu对象menuSelection,将3个用于选择列表项的Action添加作为菜单项。

tBtnSelectItem是窗体上ListWidget上方具有下拉菜单的QToolButton按钮的名称,调用了QToolButton类的4个函数对其进行设置。

  • setPopupMode(QToolButton::MenuButtonPopup),设置其弹出菜单的模式。QToolButton::MenuButtonPopup是一个枚举常量,这种模式下,按钮右侧有一个向下的小箭头,必须单击这个小按钮才会弹出下拉菜单,直接单击按钮会执行按钮关联的Action,而不会弹出下拉菜单。

  • setToolButtonStyle(Qt::ToolButtonTextBesideIcon),设置按钮样式,按钮标题文字在图标右侧显示。

  • setDefaultAction(ui->actSelPopMenu),设置按钮的关联Action,actSelPopMenu与actSelInvs有信号与槽的关联,所以,直接单击按钮会执行“反选”的功能。

  • setMenu(menuSelection),为按钮设置下拉菜单对象。

工具栏上具有下拉菜单的按钮需要动态创建。先创建QToolButton对象aBtn,同样用以上4个函数进行设置,但是设置的参数稍微不同,特别是设置弹出菜单模式为:

aBtn->setPopupMode(QToolButton::InstantPopup)

这种模式下,工具按钮的右下角显示一个小的箭头,单击按钮直接弹出下拉菜单,即使为这个按钮设置了关联的Action,也不会执行Action的功能。这是这两个具有下拉菜单的QToolButton按钮的区别。

setActionsForButton()和createSelectionPopMenu()函数在窗口的构造函数里调用,构造函数的完整代码如下:

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
setCentralWidget(ui->splitter);
setActionsForButton();
createSelectionPopMenu();
}

4.6.5 创建右键快捷菜单

每个从QWidget继承的类都有信号customContextMenuRequested(),这个信号在鼠标右击时发射,为此信号编写槽函数,可以创建和运行右键快捷菜单。

本实例为listWidget组件的customContextMenuRequested()信号创建槽函数,实现快捷菜单的创建与显示,代码如下:

void MainWindow::on_listWidget_customContextMenuRequested(const QPoint &pos)
{
Q_UNUSED(pos);
QMenu* menuList=new QMenu(this); //创建菜单
//添加Actions创建菜单项
menuList->addAction(ui->actListIni);
menuList->addAction(ui->actListClear);
menuList->addAction(ui->actListInsert);
menuList->addAction(ui->actListAppend);
menuList->addAction(ui->actListDelete);
menuList->addSeparator();
menuList->addAction(ui->actSelALL);
menuList->addAction(ui->actSelNone);
menuList->addAction(ui->actSelInvs);
menuList->exec(QCursor::pos()); //在鼠标光标位置显示右键快捷菜单
delete menuList;
}

在这段代码里,首先创建一个QMenu类型的对象menuList,然后利用QMenu的addAction()方法添加已经设计的Actions作为菜单项。

创建完菜单后,使用QMenu::exec()函数显示快捷菜单。

menuList->exec(QCursor::pos());

这样会在鼠标当前位置显示弹出式菜单,静态函数QCursor::pos()获得鼠标光标当前位置。快捷菜单的运行效果如原书的图4-13所示。

4.7 QTreeWidget和QDockWidget

4.7.1 实例功能概述

本节介绍QTreeWidget、QDockWidget的使用,以及用QLabel显示图片的方法。实例samp4_8以QTreeWidget为主要组件,创建一个照片管理器,实例运行时的界面如原书的图4-14所示。

这个实例主要演示如下几个组件的使用方法。

  • QTreeWidget目录树组件

QTreeWidget类是创建和管理目录树结构的类。实例使用一个QTreeWidget组件管理照片目录,可以添加、删除节点,每个节点设置一个自定义类型,另外,还设置了一个自定义数据,图片节点存储完整文件名,以便单击节点时显示该图片。

  • QDockWidget停靠区域组件

QDockWidget是可以在QMainWindow窗口停靠,或在桌面最上层浮动的界面组件。本实例将一个QTreeWidget组件放置在QDockWidget区域上,设置其可以在主窗口的左或右侧停靠,也可以浮动。

  • QLabel组件显示图片

右侧是一个QScrollArea组件,ScrollArea上面放置一个QLabel组件,通过为QLabel设置一个QPixmap显示图片。通过QPixmap操作可进行缩放显示,包括放大、缩小、实际大小、适合宽度、适合高度等。

4.7.2 界面设计

1.界面布局设计

实例Samp4_8的主窗口从QMainWindow继承而来,界面采用可视化设计,程序功能主要用Action实现,主菜单和主工具栏也都由其实现。

工作区左侧是一个QDockWidget组件,在DockWidget上放置一个QTreeWidget组件,用水平布局使treeWidget填充满停靠区。

工作区右侧是一个QScrollArea组件,QScrollArea组件里放置一个QLabel组件,利用QLabel的pixmap属性显示图片。scrollArea内部的组件采用水平布局,当图片较小时,Label显示的图片可以自动居于scrollArea的中间;当Label显示的图片超过scrollArea可显示区域的大小后,scrollArea会自动显示水平或垂直方向的卷滚条,用于显示更大的区域。

在主窗口构造函数里将ScrollArea组件设置为主窗口工作区的中心组件后,DockWidget与ScrollArea之间自动出现分割条,可以分割两个组件的大小。

2.QDockWidget组件属性设置

在UI设计器里对DockWidget组件的主要属性进行设置,主要属性如下。

  • allowedAreas属性,设置允许停靠区域。

由函数setAllowedAreas(Qt::DockWidgetAreas areas)设置允许停靠区,参数areas是枚举类型Qt::DockWidgetArea的值的组合,可以设置在窗口的左、右、顶、底停靠,或所有区域都可停靠,或不允许停靠。

本实例设置为允许左侧和右侧停靠。

  • features属性,设置停靠区组件的特性。

由setFeatures(DockWidgetFeatures features)函数设置停靠区组件的特性,参数features是枚举类型QDockWidget::DockWidgetFeature的值的组合,枚举值如下。

– QDockWidget::DockWidgetClosable:停靠区可关闭。
– QDockWidget::DockWidgetMovable:停靠区可移动。
– QDockWidget::DockWidgetFloatable:停靠区可浮动。
– QDockWidget::DockWidgetVerticalTitleBar:在停靠区左侧显示垂直标题栏。
– QDockWidget::AllDockWidgetFeatures:使用以上所有特征。
– QDockWidget::NoDockWidgetFeatures:不能停靠、移动和关闭。

本实例设置为可关闭、可停靠、可浮动。

3.QTreeWidget组件的设置

在UI设计器里,双击界面上的QTreeWidget组件,可以打开原书的图4-15所示的设计器,设计器有两页,可分别对Columns和Items进行设计。

Columns页用于设计目录树的列,目录树可以有多个列。在设计器里可以添加、删除、移动列,设置列的文字、字体、前景色、背景色、文字对齐方式、图标等。本实例设置了两个列,标题分别为“节点”和“节点类型”。

Items页面用于设计目录树的节点,可对每个节点设置属性,如文字、字体、图标等,特别是flags属性,可以设置节点是否可选、是否可编辑、是否有CheckBox等,还可以设置节点的CheckState。在原书的图4-15下方有一组按钮可以新增节点、新增下级节点、删除节点、改变节点级别、平级移动节点等。

使用设计器设计目录树的列和节点,适用于创建固定结构的目录树,但是目录树一般是根据内容动态创建的,需要运用代码实现节点的创建。

4.Action设计

本例的功能代码大多采用Action实现,在Action Editor里设计Action,然后利用Action设计主菜单和主工具栏。设计完成的Action如原书的图4-16所示。

4.7.3 QTreeWidget操作

1.本实例的目录树节点操作规则

本实例的目录树节点操作定义如下一些规则。

  • 将目录树的节点分为3种类型,顶层节点、分组节点和图片节点。

  • 窗口创建时初始化目录树,它只有一个顶层节点,这个顶层节点不能被删除,而且不允许再新建顶层节点。

  • 顶层节点下允许添加分组节点和图片节点。

  • 分组节点下可以添加分组节点和图片节点,分组节点的级数无限制。

  • 图片节点是终端节点,可以在图片节点同级再添加图片节点。

  • 每个节点创建时设置其类型信息,图片节点存储其完整文件名作为自定义数据。

  • 单击一个图片文件节点时,显示其关联文件的图片。

为便于后面说明代码的实现,将主窗口类MainWindow中增加的自定义内容先列出来,代码如下。这些枚举类型、变量和函数的功能在后面再具体介绍。

class MainWindow : public QMainWindow
{
private:
//枚举类型treeItemType,创建节点时用作type参数,自定义类型必须大于1000
enum treeItemType{itTopItem=1001, itGroupItem, itImageItem};
enum treeColNum{colItem=0, colItemType=1}; //目录树列的编号

QLabel *LabFileName; //用于状态栏文件名显示
QPixmap curPixmap; //当前的图片
float pixRatio; //当前图片缩放比例

void iniTree();//目录树初始化
void addFolderItem(QTreeWidgetItem *parItem, QString dirName);//添加目录
QString getFinalFolderName(const QString &fullPathName); //提取目录名称
void addImageItem(QTreeWidgetItem *parItem,QString aFilename);//添加图片
void displayImage(QTreeWidgetItem *item); //显示一个图片节点的图片
void changeItemCaption(QTreeWidgetItem *item); //遍历改变节点标题
};

2.目录树初始化添加顶层节点

主窗口MainWindow的构造函数会调用自定义函数iniTree(),对目录树进行初始化,窗口构造函数和iniTree()代码如下:

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
LabFileName=new QLabel("");
ui->statusBar->addWidget(LabFileName);
this->setCentralWidget(ui->scrollArea);
iniTree();//初始化目录树
}

void MainWindow::iniTree()
{ //初始化目录树
QString dataStr=""; // Item的Data 存储的string
ui->treeFiles->clear();
QIcon icon;
icon.addFile(":/images/icons/15.ico");

QTreeWidgetItem* item=new QTreeWidgetItem(MainWindow::itTopItem);
item->setIcon(MainWindow::colItem,icon); //第1列的图标
item->setText(MainWindow::colItem,"图片文件"); //第1列的文字
item->setText(MainWindow::colItemType,"type=itTopItem"); //第2列
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsAutoTristate);
item->setCheckState(colItem,Qt::Checked);

item->setData(MainWindow::colItem,Qt::UserRole,QVariant(dataStr));
ui->treeFiles->addTopLevelItem(item);//添加顶层节点
}

QTreeWidget的每个节点都是一个QTreeWidgetItem对象,添加一个节点前需先创建它,并做好相关设置。

创建节点的语句是:

item=new QTreeWidgetItem(MainWindow::itTopItem);

传递了一个枚举常量MainWindow::itTopItem作为构造函数的参数,表示节点的类型。在构造函数里传递一个类型值之后,就可以用QTreeWidgetItem::type()返回这个节点的类型值。

itTopItem是在MainWindow里定义的枚举类型treeItemType的一个常量值。枚举类型treeItemType定义了节点的类型,自定义的节点类型值必须大于1000。

QTreeWidgetItem的setIcon()和setText()都需要传递一个列号作为参数,指定对哪个列进行设置。列号可以直接用数字,但是为了便于理解代码和统一修改,在MainWindow里定义了枚举类型treeColNum,colItem表示第1列,colItemType表示第2列。

setFlags()函数设置节点的一些属性标记,是Qt::ItemFlag枚举类型常量的组合。

setData()函数为节点的某一列设置一个角色数据,setData()函数原型为:

void QTreeWidgetItem::setData(int column, int role, const QVariant &value)

其中,column是列号,role是角色的值,value是一个QVariant类型的数。

代码中设置节点数据的语句是:

item->setData(MainWindow::colItem,Qt::UserRole,QVariant(dataStr));

它为节点的第1列,角色Qt::UserRole,设置了一个字符串数据dataStr。Qt::UserRole是枚举类型Qt::ItemDataRole中一个预定义的值,关于节点的角色和Qt::ItemDataRole在5.1节详细介绍。

创建并设置好节点后,用QTreeWidget::addTopLevelItem()函数将节点作为顶层节点添加到目录树。

3.添加目录节点

actAddFolder是用于添加组节点的Action,当目录树上的当前节点类型是itTopItem或itGroupItem类型时,才可以添加组节点。actAddFolder的triggered()信号的槽函数,以及相关自定义函数的代码如下:

void MainWindow::on_actAddFolder_triggered()
{// 添加组节点
QString dir=QFileDialog::getExistingDirectory();//选择目录
if (!dir.isEmpty())
{
QTreeWidgetItem *parItem=ui->treeFiles->currentItem(); //当前节点
addFolderItem(parItem,dir);//在父节点下面添加一个组节点
}
}

void MainWindow::addFolderItem(QTreeWidgetItem *parItem, QString dirName)
{//添加一个组节点
QIcon icon(":/images/icons/open3.bmp");
QString NodeText=getFinalFolderName(dirName); //获得最后的文件夹名称

QTreeWidgetItem *item;
item=new QTreeWidgetItem(MainWindow::itGroupItem); //节点类型itGroupItem
item->setIcon(colItem,icon);
item->setText(colItem,NodeText);
item->setText(colItemType,"type=itGroupItem");
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsAutoTristate);
item->setCheckState(colItem,Qt::Checked);
item->setData(colItem,Qt::UserRole,QVariant(dirName));
parItem->addChild(item); //在父节点下面添加子节点
}

QString MainWindow::getFinalFolderName(const QString &fullPathName)
{//从一个完整目录名称里,获得最后的文件夹名称
int cnt=fullPathName.length();
int i=fullPathName.lastIndexOf("/");
QString str=fullPathName.right(cnt-i-1);
return str;
}

actAddFolder的槽函数首先用文件对话框获取一个目录名称,再获取目录树的当前节点,然后调用自定义函数addFolderItem()添加一个组节点,新添加的节点将会作为当前节点的子节点。

addFolderItem()函数根据传递来的父节点parItem和目录全称dirName,创建并添加节点。首先用自定义函数getFinalFolderName()获取目录全称的最后一级的文件夹名称,这个文件夹名称将作为新建节点的标题;然后创建一个节点,创建时设置其节点类型为itGroupItem,表示分组节点,再设置属性和关联数据,关联数据就是目录的全路径字符串;最后调用QTreeWidgetItem:: addChild()函数,将创建的节点作为父节点的一个子节点添加到目录树。

4.添加图片文件节点

actAddFiles是添加图片文件节点的Action,目录树的当前节点为任何类型时这个Action都可用。actAddFiles的槽函数,以及相关自定义函数的代码如下:

void MainWindow::on_actAddFiles_triggered()
{//添加图片文件节点
QStringList files=QFileDialog::getOpenFileNames(this,
"选择一个或多个文件","","Images(*.jpg)");
if (files.isEmpty())
return;
QTreeWidgetItem *parItem,*item;
item=ui->treeFiles->currentItem();
if (item->type()==itImageItem) //当前节点是图片节点
parItem=item->parent();
else
parItem=item;

for (int i = 0; i < files.size(); ++i)
{
QString aFilename=files.at(i); //得到一个文件名
addImageItem(parItem,aFilename); //添加一个图片节点
}
}
void MainWindow::addImageItem(QTreeWidgetItem *parItem, QString aFilename)
{//添加一个图片文件节点
QIcon icon(":/images/icons/31.ico");
QString NodeText=getFinalFolderName(aFilename); //获得最后的文件名称
QTreeWidgetItem *item;
item=new QTreeWidgetItem(MainWindow::itImageItem); //节点类型itImageItem
item->setIcon(colItem,icon);
item->setText(colItem,NodeText);
item->setText(colItemType,"type=itImageItem");
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsAutoTristate);
item->setCheckState(colItem,Qt::Checked);
item->setData(colItem,Qt::UserRole,QVariant(aFilename));//完整文件名称
parItem->addChild(item); //在父节点下面添加子节点
}

actAddFiles的槽函数首先用QFileDialog::getOpenFileNames(),获取图片文件列表,通过QTreeWidget::currentItem()函数获得目录树的当前节点item。

item->type()将返回节点的类型,也就是创建节点时传递给构造函数的那个参数。如果当前节点类型是图片节点(itImageItem),就使用当前节点的父节点,作为将要添加的图片节点的父节点,否则就用当前节点作为父节点。

然后遍历所选图片文件列表,调用自定义函数addImageItem()逐一添加图片节点到父节点下。

addImageItem()根据图片文件名称,创建一个节点并添加到父节点下面,在使用setData()设置节点数据时,将图片带路径的文件名aFilename作为节点的数据,这个数据在单击节点打开图片时会用到。

5.当前节点变化后的响应

目录树上当前节点变化时,会发射currentItemChanged()信号,为此信号创建槽函数,实现当前节点类型判断、几个Action的使能控制、显示图片等功能,代码如下:

void MainWindow::on_treeFiles_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous)
{ //当前节点变化时的处理
Q_UNUSED(previous);
if (current==NULL)
return;
int var=current->type();//节点的类型
switch(var)
{
case itTopItem: //顶层节点
ui->actAddFolder->setEnabled(true);
ui->actAddFiles->setEnabled(true);
ui->actDeleteItem->setEnabled(false); //顶层节点不能删除
break;

case itGroupItem: //组节点
ui->actAddFolder->setEnabled(true);
ui->actAddFiles->setEnabled(true);
ui->actDeleteItem->setEnabled(true);
break;

case itImageItem: //图片文件节点
ui->actAddFolder->setEnabled(false); //图片节点下不能添加目录节点
ui->actAddFiles->setEnabled(true);
ui->actDeleteItem->setEnabled(true);
displayImage(current); //显示图片
break;
}
}

current是变化后的当前节点,通过current->type()获得当前节点的类型,根据节点类型控制界面上3个Action的使能状态。如果是图片文件节点,还调用displayImage ()函数显示节点关联的图片。

displayImage()函数的功能实现在后面介绍QLabel图片显示的部分会详细说明。

6.删除节点

除了顶层节点之外,选中一个节点后也可以删除它。actDeleteItem实现节点删除,其代码如下:

void MainWindow::on_actDeleteItem_triggered()
{//删除节点
QTreeWidgetItem* item =ui->treeFiles->currentItem(); //当前节点
QTreeWidgetItem* parItem=item->parent(); //父节点
parItem->removeChild(item);//移除一个子节点,并不会删除
delete item;
}

一个节点不能移除自己,所以需要获取其父节点,使用父节点的removeChild()函数来移除自己。removeChild()移除一个节点,但是不从内存中删除它,所以还需调用delete。

若要删除顶层节点,则使用QTreeWidget::takeTopLevelItem(int index)函数。

7.节点的遍历

目录树的节点都是QTreeWidgetItem类,可以嵌套多层。有时需要在目录树中遍历所有节点,比如按条件查找某些节点、统一修改节点的标题等。遍历节点需要用到QTreeWidgetItem类的一些关键函数,还需要设计嵌套函数。

actScanItems实现工具栏上“遍历节点”的功能,其槽函数及相关自定义函数代码如下:

void MainWindow::on_actScanItems_triggered()
{//遍历节点
for (int i=0;i<ui->treeFiles->topLevelItemCount();i++)
{
QTreeWidgetItem *item=ui->treeFiles->topLevelItem(i); //顶层节点
changeItemCaption(item); //更改节点标题
}
}
void MainWindow::changeItemCaption(QTreeWidgetItem *item)
{ //改变节点的标题文字
QString str="*"+ item->text(colItem); //节点标题前加“*”
item->setText(colItem,str);
if (item->childCount()>0)
for (int i=0;i< item->childCount();i++) //遍历子节点
changeItemCaption(item->child(i)); //调用自己,可重入的函数
}

QTreeWidget组件的顶层节点没有父节点,要访问所有顶层节点,用到两个函数。

  • int topLevelItemCount():返回顶层节点个数。

  • QTreeWidgetItem* topLevelItem(int index):返回序号为index的顶层节点。

on_actScanItems_triggered()函数的for循环访问所有顶层节点,获取一个顶层节点item之后,调用changeItemCaption(item)改变这个节点及其所有子节点的标题。

changeItemCaption(QTreeWidgetItem *item)是一个嵌套调用函数,即在这个函数里还会调用它自己。它的前两行更改传递来的节点item的标题,即在标题前加星号。后面的代码根据item->childCount()是否大于0,判断这个节点是否有子节点,如果有子节点,则在后面的for循环里,逐一获取,并作为参数调用changeItemCaption()函数。

4.7.4 QLabel和QPixmap显示图片

1.显示节点关联的图片

在目录树上单击一个节点后,如果其类型为图片节点(itImageItem),就会调用displayImage(QTreeWidgetItem *item)函数显示节点的图片,当前节点作为函数的传递参数。displayImage()函数的代码如下:

void MainWindow::displayImage(QTreeWidgetItem *item)
{//显示图片,节点item存储了图片文件名
QString filename=item->data(colItem,Qt::UserRole).toString();//文件名
LabFileName->setText(filename);
curPixmap.load(filename);
on_actZoomFitH_triggered(); //自动适应高度显示
}

QTreeWidgetItem::data()返回节点存储的数据,也就是用setData()设置的数据。前面在添加图片节点时,将文件名的带路径全名存储为节点的数据,这里的第一行语句就可以获得节点存储的图片文件全名。

curPixmap是在MainWindow 中定义的一个QPixmap类型的变量,用于操作图片。QPixmap::load(QString &fileName)直接将一个图片文件载入。

最后调用函数on_actZoomFitH_triggered()显示图片,这是actZoomFitH的槽函数,以适应高度的形式显示图片。

2.图片缩放与显示

有几个Action实现图片的缩放显示,包括适合宽度、适合高度、放大、缩小、实际大小,部分槽函数代码如下:

void MainWindow::on_actZoomFitH_triggered()
{//适应高度显示图片
int H=ui->scrollArea->height();
int realH=curPixmap.height();
pixRatio=float(H)/realH; //当前显示比例,必须转换为浮点数
QPixmap pix=curPixmap.scaledToHeight(H-30); //图片缩放到指定高度
ui->LabPicture->setPixmap(pix);
}
void MainWindow::on_actZoomIn_triggered()
{//放大显示
pixRatio=pixRatio*1.2;
int w=pixRatio*curPixmap.width();
int h=pixRatio*curPixmap.height();
QPixmap pix=curPixmap.scaled(w,h);
ui->LabPicture->setPixmap(pix);
}
void MainWindow::on_actZoomRealSize_triggered()
{ //实际大小显示
pixRatio=1;
ui->LabPicture->setPixmap(curPixmap);
}

QPixmap存储图片数据,可以缩放图片,有以下几个函数。

  • QPixmap scaledToHeight(int height):返回一个缩放后的图片的副本,图片缩放到一个高度height。

  • QPixmap scaledToWidth(int width):返回一个缩放后的图片的副本,图片缩放到一个宽度width。

  • QPixmap scaled(int width, int height):返回一个缩放后的图片的副本,图片缩放到宽度width和高度height,缺省为不保持比例。

变量curPixmap保存了图片的原始副本,要缩放只需调用curPixmap的相应函数,返回缩放后的图片副本。

在界面上的一个标签LabPicture上显示图片,使用了QLabel的setPixmap(const QPixmap &)函数。

4.7.5 QDockWidget的操作

程序运行时,主窗口上的DockWidget组件可以被拖动,在主窗口的左、右两侧停靠,或在桌面上浮动。工具栏上“窗体浮动”和“窗口可见”两个按钮可以用代码控制停靠区是否浮动、是否可见,其代码如下:

void MainWindow::on_actDockVisible_toggled(bool arg1)
{// 停靠区的可见性
ui->dockWidget->setVisible(arg1);
}
void MainWindow::on_actDockFloat_triggered(bool checked)
{//停靠区浮动性
ui->dockWidget->setFloating(checked);
}

单击DockWidget组件标题栏的关闭按钮时,会隐藏停靠区并发射信号visibilityChanged(bool);当拖动DockWidget组件,使其浮动或停靠时,会发射信号topLevelChanged(bool)。为这两个信号编写槽函数,可更新两个Actions的状态。

void MainWindow::on_dockWidget_visibilityChanged(bool visible)
{//停靠区可见性变化
ui->actDockVisible->setChecked(visible);
}
void MainWindow::on_dockWidget_topLevelChanged(bool topLevel)
{//停靠区浮动性变化
ui->actDockFloat->setChecked(topLevel);
}

4.8 QTableWidget的使用

4.8.1 QTableWidget概述

QTableWidget是Qt中的表格组件类。在窗体上放置一个QTableWidget组件后,可以在Property Editor里对其进行属性设置,双击这个组件,可以打开一个编辑器,对其Colum、Row和Item进行编辑。一个QTableWidget组件的界面基本结构如原书的图4-17所示,这个表格设置为6行5列。

表格的第1行称为行表头,用于设置每一列的标题,第1列称为列表头,可以设置其标题,但一般使用缺省的标题,即为行号。行表头和列表头一般是不可编辑的。

除了行表头和列表头之外的表格区域是内容区,内容区是规则的网格状,如同一个二维数组,每个网格单元称为一个单元格。每个单元格有一个行号、列号,原书的图4-17表示了行号、列号的变化规律。

在QTableWidget表格中,每一个单元格是一个QTableWidgetItem对象,可以设置文字内容、字体、前景色、背景色、图标,也可以设置编辑和显示标记。每个单元格还可以存储一个QVariant数据,用于设置用户自定义数据。

实例samp4_9以QTableWidget为主要组件,演示QTableWidget一些主要操作的实现。实例运行时的界面如原书的图4-18所示,该实例将演示以下功能的实现方法。

  • 设置表格的列数和行数,设置表头的文字、格式等。

  • 初始化表格数据,设置一批实例数据填充到表格里。

  • 插入行、添加行、删除当前行的操作。

  • 遍历表格所有单元格,读取表格内容到一个QPlainTextEdit里,表格的一行数据作为一行文本。

  • 表格上选择的当前单元格变化时,在状态栏显示单元格存储的信息。

4.8.2 界面设计与初始化

Samp4_9的主窗体从QMainWindow继承而来。在原书的图4-18所示的窗口上,一个QTableWidget组件和一个QPlainTextEdit组件组成上下分割布局splitter。左侧的按钮都放在一个QGroupBox组件里,采用Grid布局,然后将groupBox与splitter采用左右分割布局。这是一个典型的三区分割的布局。

在主窗口类MainWindow里自定义了一些变量和函数,用于后面的代码实现,下面是在MainWindow的private部分自定义的变量和函数:

private:
// 自定义单元格Type的类型,在创建单元格的 item时使用
enum CellType{ctName=1000,ctSex,ctBirth,ctNation,ctPartyM,ctScore};
// 各字段在表格中的列号
enum FieldColNum{colName=0,colSex,colBirth,
colNation,colScore,colPartyM};
QLabel *labCellIndex; //状态栏上用于显示单元格的行号、列号
QLabel *labCellType;//状态栏上用于显示单元格的type
QLabel *labStudID; //状态栏上用于显示学号
void createItemsARow(int rowNo,QString Name,QString Sex,QDate birth,
QString Nation,bool isPM,int score); //为某一行创建items

枚举类型CellType是用来表示单元格类型的,在创建单元格时使用。

枚举类型FieldColNum用枚举常量表示各字段在表格中的列号。

在MainWindow的构造函数里对界面进行初始化,代码如下:

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
setCentralWidget(ui->splitterMain);
//状态栏初始化创建
labCellIndex = new QLabel("当前单元格坐标:",this);
labCellIndex->setMinimumWidth(250);
labCellType=new QLabel("当前单元格类型:",this);
labCellType->setMinimumWidth(200);
labStudID=new QLabel("学生ID:",this);
labStudID->setMinimumWidth(200);
ui->statusBar->addWidget(labCellIndex);//加到状态栏
ui->statusBar->addWidget(labCellType);
ui->statusBar->addWidget(labStudID);
}

4.8.3 QTableWidget操作

1.设置表头

界面上的“设置表头”按钮实现对表头的设置,其clicked()信号的槽函数代码如下:

void MainWindow::on_btnSetHeader_clicked()
{ //设置表头
QTableWidgetItem *headerItem;
QStringList headerText;
headerText<<"姓 名"<<"性 别"<<"出生日期"<<"民 族"<<"分数"<<"是否党员";
// ui->tableInfo->setHorizontalHeaderLabels(headerText);
ui->tableInfo->setColumnCount(headerText.count());
for (int i=0;i<ui->tableInfo->columnCount();i++)
{
headerItem=new QTableWidgetItem(headerText.at(i));
QFont font=headerItem->font();
font.setBold(true);//设置为粗体
font.setPointSize(12);//字体大小
headerItem->setTextColor(Qt::red);//字体颜色
headerItem->setFont(font);//设置字体
ui->tableInfo->setHorizontalHeaderItem(i,headerItem);
}
}

行表头各列的文字标题由一个QStringList对象headerText初始化存储,如果只是设置行表头各列的标题,使用下面一行语句即可:

ui->tableInfo->setHorizontalHeaderLabels(headerText);

如果需要进行更加具体的格式设置,需要为行表头的每个单元格创建一个QTableWidgetItem类型的变量,并进行相应设置。

在一个表格中,不管是表头还是工作区,每个单元格都是一个QTableWidgetItem对象。QTableWidgetItem对象存储了单元格的所有内容,包括字标题、格式设置,以及关联的数据。

上面程序中的for循环遍历headerText的每一行,用每一行的文字创建一个QTableWidgetItem对象headerItem,然后设置headerItem的字体大小为12、粗体、红色,然后将headerItem赋给表头的某一列:

ui->tableInfo->setHorizontalHeaderItem(i,headerItem);

2.初始化表格数据

界面上的“初始化表格数据”按钮根据表格的行数,生成数据填充表格,并为每个单元格生成QTableWidgetItem对象,设置相应属性。下面是btnIniData的clicked()信号的槽函数代码:

void MainWindow::on_btnIniData_clicked()
{ //初始化表格内容
QString strName,strSex;
bool isParty=false;
QDate birth;
birth.setDate(1980,4,7);//初始化一个日期
ui->tableInfo->clearContents();//只清除工作区,不清除表头
int Rows=ui->tableInfo->rowCount(); //数据区行数
for (int i=0;i<Rows;i++)
{
strName=QString::asprintf("学生%d",i);
if ((i % 2)==0) //分奇数、偶数行设置性别,及其图标
strSex="男";
else
strSex="女";
createItemsARow(i, strName, strSex, birth,"汉族",isParty,70);
birth=birth.addDays(20); //日期加20天
isParty =!isParty;
}
}

QTableWidget::clearContents()函数清除表格数据区的所有内容,但是不清除表头。

QTableWidget::rowCount()函数返回表格数据区的行数。

在for循环里为每一行生成需要显示的数据,然后调用自定义函数createItemsARow(),为表格一行的各个单元格生成QTableWidgetItem对象。

createItemsARow()是在窗体类里自定义的函数,其实现代码如下:

void MainWindow::createItemsARow(int rowNo,QString Name,QString Sex,QDate birth,QString Nation,bool isPM,int score)
{ //为一行的单元格创建 Items
QTableWidgetItem *item;
QString str;
uint StudID=201605000; //学号基数
//姓名
item=new QTableWidgetItem(Name,MainWindow::ctName);
item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
StudID +=rowNo; //学号=基数+ 行号
item->setData(Qt::UserRole,QVariant(StudID)); //设置studID为data
ui->tableInfo->setItem(rowNo,MainWindow::colName,item);
//性别
QIcon icon;
if (Sex=="男")
icon.addFile(":/images/icons/boy.ico");
else
icon.addFile(":/images/icons/girl.ico");
item=new QTableWidgetItem(Sex,MainWindow::ctSex);
item->setIcon(icon);
item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->tableInfo->setItem(rowNo,MainWindow::colSex,item);
//出生日期
str=birth.toString("yyyy-MM-dd"); //日期转换为字符串
item=new QTableWidgetItem(str,MainWindow::ctBirth);
item->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
ui->tableInfo->setItem(rowNo,MainWindow::colBirth,item);
//民族
item=new QTableWidgetItem(Nation,MainWindow::ctNation);
item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->tableInfo->setItem(rowNo,MainWindow::colNation,item);
//是否党员
item=new QTableWidgetItem("党员",MainWindow::ctPartyM);
item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
if (isPM)
item->setCheckState(Qt::Checked);
else
item->setCheckState(Qt::Unchecked);
item->setBackgroundColor(Qt::yellow);
ui->tableInfo->setItem(rowNo,MainWindow::colPartyM,item);
//分数
str.setNum(score);
item=new QTableWidgetItem(str,MainWindow::ctScore);
item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->tableInfo->setItem(rowNo,MainWindow::colScore,item);
}

该表格的每一行有6列,为每一个单元格都创建一个QTableWidgetItem类型的变量item,并做相应的设置。

创建QTableWidgetItem使用的构造函数的原型为:

QTableWidgetItem::QTableWidgetItem(const QString &text, int type = Type)

其中,第一个参数作为单元格的显示文字,第二个参数作为节点的类型。

例如创建“姓名”单元格对象时的语句是:

cellItem=new QtableWidgetItem(Name,MainWindow::ctName);

其中,MainWindow::ctName是定义的枚举类型CellType的一个常量值。

“姓名”单元格还调用setData()函数设置了一个自定义的数据,存储的是学生ID。

cellItem->setData(Qt::UserRole,QVariant(StudID));

这个自定义数据是不显示在界面上的,但是与单元格相关联。

QTableWidgetItem有一些函数对单元格进行属性设置,如下。

  • setTextAlignment (int alignment):设置文字对齐方式。

  • setBackground(const QBrush &brush):设置单元格背景颜色。

  • setForeground(const QBrush &brush):设置单元格前景色。

  • setIcon(const QIcon &icon):为单元格设置一个显示图标。

  • setFont(const QFont &font):为单元格显示文字设置字体。

  • setCheckState(Qt::CheckState state):设置单元格勾选状态,单元格里出现一个QCheckBox组件。

  • setFlags(Qt::ItemFlags flags):设置单元格的一些属性标记。

设置好item的各种属性之后,用QTableWidget的setItem函数将item设置为单元格的项,例如:

ui->tableInfo->setItem(rowNo,MainWindow::colName,item);

其中,MainWindow::colName是定义的枚举类型FieldColNum的一个常量值。

这样初始化设置后,就可以得到如原书的图4-18所示的运行时的表格内容。表格里并没有显示学号,学号是“姓名”单元格的关联数据。

3.获得当前单元格数据

当鼠标在表格上单击单元格时,被选中的单元格是当前单元格。通过QTableWidget的currentColumn()和currentRow()可以获得当前单元格的列编号和行编号。

当前单元格发生切换时,会发射currentCellChanged()信号和currentItemChanged()信号,两个信号都可以利用,只是传递的参数不同。

对currentCellChanged()信号编写槽函数,用于获取当前单元格的数据,以及当前行的学生的学号信息,代码如下:

void MainWindow::on_tableInfo_currentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn)
{//当前选择单元格发生变化时的响应
QTableWidgetItem* item=ui->tableInfo->item(currentRow,currentColumn);
if (item==NULL)
return;
labCellIndex->setText(QString::asprintf("当前单元格坐标:%d 行,%d 列",
currentRow,currentColumn));
int cellType=item->type();//获取单元格的类型
labCellType->setText(QString::asprintf("当前单元格类型:%d",cellType));
item=ui->tableInfo->item(currentRow,MainWindow::colName); //第1列的item
int ID=item->data(Qt::UserRole).toInt();//读取用户自定义数据
labStudID->setText(QString::asprintf("学生ID:%d",ID));//学生ID
}

在currentCellChanged()信号中,传递的参数currentRow和currentColumn表示当前单元格的行号和列号,通过这两个编号可以得到单元格的QTableWidgetItem对象item。

获得item之后,通过type()函数得到单元格的类型参数,这个类型就是为单元格创建QTableWidgetItem对象时传递的类型参数。

再获取同一行的“姓名”单元格的项,用data()函数提取自定义数据,也就是创建单元格时存储的学生ID。

4.插入、添加、删除行

QTableWidget处理行操作的函数如下。

  • insertRow(int row):在行号为row的行前面插入一行,如果row等于或大于总行数,则在表格最后添加一行。insertRow()函数只是插入一个空行,不会为单元格创建QTableWidgetItem对象,需要手工为单元格创建。

  • removeRow(int row):删除行号为row的行。

下面是界面上“插入行”“添加行”“删除当前行”按钮的响应代码。在插入行之后,会调用createItemsARow()函数,为新创建的空行的各单元格构造QTableWidgetItem对象。

void MainWindow::on_btnInsertRow_clicked()
{ //插入一行
int curRow=ui->tableInfo->currentRow();
ui->tableInfo->insertRow(curRow); //插入一行,不会自动为单元格创建item
createItemsARow(curRow, "新学生", "男",
QDate::fromString("1990-1-1","yyyy-M-d"),"苗族",true );
}
void MainWindow::on_btnAppendRow_clicked()
{ //添加一行
int curRow=ui->tableInfo->rowCount();
ui->tableInfo->insertRow(curRow);//在表格尾部添加一行
createItemsARow(curRow, "新生", "女",
QDate::fromString("2000-1-1","yyyy-M-d"),"满族",false );
}
void MainWindow::on_btnDelCurRow_clicked()
{//删除当前行及其items
int curRow=ui->tableInfo->currentRow();
ui->tableInfo->removeRow(curRow); //删除当前行及其items
}

5.自动调整行高和列宽

QTableWidget有几个函数自动调整表格的行高和列宽,分别如下。

  • resizeColumnsToContents():自动调整所有列的宽度,以适应其内容。

  • resizeColumnToContents(int column):自动调整列号为column的列的宽度。

  • resizeRowsToContents():自动调整所有行的高度,以适应其内容。

  • resizeRowToContents(int row):自动调整行号为row的行的高度。

这几个函数实际上是QTableWidget的父类QTableView的函数。

6.其他属性控制
  • 设置表格内容是否可编辑

QTableWidget的EditTriggers属性表示是否可编辑,以及进入编辑状态的方式。界面上的“表格可编辑”复选框的槽函数代码为:

void MainWindow::on_chkBoxTabEditable_clicked(bool checked)
{ //设置编辑模式
if (checked)
ui->tableInfo->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::SelectedClicked); //双击或获取焦点后单击,进入编辑状态
else
ui->tableInfo->setEditTriggers(QAbstractItemView::NoEditTriggers);
}

  • 设置行表头、列表头是否显示

horizontalHeader()获取行表头,verticalHeader()获取列表头,然后可设置其可见性。

void MainWindow::on_chkBoxHeaderH_clicked(bool checked)
{//是否显示行表头
ui->tableInfo->horizontalHeader()->setVisible(checked);
}
void MainWindow::on_chkBoxHeaderV_clicked(bool checked)
{//是否显示列表头
ui->tableInfo->verticalHeader()->setVisible(checked);
}

  • 间隔行底色

setAlternatingRowColors()函数可以设置表格的行是否用交替底色显示,若为交替底色,则间隔的一行会用灰色作为底色。具体底色的设置需要用styleSheet,在16.2节有介绍。

void MainWindow::on_chkBoxRowColor_clicked(bool checked)
{
ui->tableInfo->setAlternatingRowColors(checked);
}

  • 选择模式

setSelectionBehavior()函数可以设置选择方式为单元格选择,还是行选择。

void MainWindow::on_rBtnSelectItem_clicked()
{//选择行为:单元格选择
ui->tableInfo->setSelectionBehavior(QAbstractItemView::SelectItems);
}
void MainWindow::on_rBtnSelectRow_clicked()
{//选择行为:行选择
ui->tableInfo->setSelectionBehavior(QAbstractItemView::SelectRows);
}

7.遍历表格读取数据

“读取表格内容到文本”按钮演示了将表格数据区的内容全部读出的方法,它将每个单元格的文字读出,同一行的单元格的文字用空格分隔开,作为文本的一行,然后将这行文字作为文本编辑器的一行内容,代码如下:

void MainWindow::on_btnReadToEdit_clicked()
{//将所有单元格的内容提取字符串,显示在PlainTextEdit组件里
QString str;
QtableWidgetItem *cellItem;
ui->textEdit->clear();
for (int i=0;i<ui->tableInfo->rowCount();i++)
{
str=QString::asprintf("第 %d 行: ",i+1);
for (int j=0;j<ui->tableInfo->columnCount()-1;j++)
{
cellItem=ui->tableInfo->item(i,j); //获取单元格的item
str=str+cellItem->text()+" "; //字符串连接
}
cellItem=ui->tableInfo->item(i,colNoPartyM); //最后一列
if (cellItem->checkState()==Qt::Checked)
str=str+"党员";
else
str=str+"群众";
ui->textEdit->appendPlainText(str);
}
}

赞(0)
未经允许不得转载:网硕互联帮助中心 » 【Qt】《Qt 5.9 C++开发指南》笔记-Chapter4-常用界面设计组件
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!