ZetCode-GUI-教程-七-
ZetCode GUI 教程(七)
原文:ZetCode
Qt5 中的日期和时间
在 Qt5 C++ 编程教程的这一部分中,我们将讨论时间和日期。
Qt5 具有QDate,QTime和QDateTime类以与日期和时间一起使用。 QDate是用于使用公历中的日历日期的类。 它具有确定日期,比较或操纵日期的方法。 QTime类使用时钟时间。 它提供了比较时间,确定时间的方法以及其他各种时间操纵方法。 QDateTime是将QDate和QTime对象结合为一个对象的类。
初始化日期和时间对象
日期和时间对象可以通过两种基本方式进行初始化。 我们可以在对象构造器中对其进行初始化,也可以创建空对象并在以后用数据填充它们。
init.cpp
#include <QTextStream>
#include <QDate>
#include <QTime>
int main(void) {
QTextStream out(stdout);
QDate dt1(2015, 4, 12);
out << "The date is " << dt1.toString() << endl;
QDate dt2;
dt2.setDate(2015, 3, 3);
out << "The date is " << dt2.toString() << endl;
QTime tm1(17, 30, 12, 55);
out << "The time is " << tm1.toString("hh:mm:ss.zzz") << endl;
QTime tm2;
tm2.setHMS(13, 52, 45, 155);
out << "The time is " << tm2.toString("hh:mm:ss.zzz") << endl;
}
我们以两种方式初始化日期和时间对象。
QDate dt1(2015, 4, 12);
QDate对象构造器采用三个参数:年,月和日。
out << "The date is " << dt1.toString() << endl;
日期将打印到控制台。 我们使用toString()方法将日期对象转换为字符串。
QTime tm2;
tm2.setHMS(13, 52, 45, 155);
空的QTime对象已创建。 我们使用setHMS()方法用数据填充对象。 参数是小时,分钟,秒和毫秒。
out << "The time is " << tm2.toString("hh:mm:ss.zzz") << endl;
我们将QTime对象打印到控制台。 我们使用的特定格式还包括毫秒,默认情况下会省略毫秒。
输出:
$ ./init
The date is Sun Apr 12 2015
The date is Tue Mar 3 2015
The time is 17:30:12.055
The time is 13:52:45.155
当前日期和时间
在以下示例中,我们将当前本地时间和日期打印到控制台。
curdatetime.cpp
#include <QTextStream>
#include <QTime>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QDate cd = QDate::currentDate();
QTime ct = QTime::currentTime();
out << "Current date is: " << cd.toString() << endl;
out << "Current time is: " << ct.toString() << endl;
}
请注意,不得将文件命名为time.cpp。
QDate cd = QDate::currentDate();
QDate::currentDate()静态函数返回当前日期。
QTime ct = QTime::currentTime();
QTime::currentTime()静态函数返回当前时间。
out << "Current date is: " << cd.toString() << endl;
out << "Current time is: " << ct.toString() << endl;
我们使用toString()方法将日期和时间对象转换为字符串。
输出:
$ ./curdatetime
Current date is: Fri Oct 30 2015
Current time is: 20:55:27
比较日期
关系运算符可用于比较日期。 我们可以比较他们在日历中的位置。
comparedates.cpp
#include <QTextStream>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QDate dt1(2015, 4, 5);
QDate dt2(2014, 4, 5);
if (dt1 < dt2) {
out << dt1.toString() << " comes before "
<< dt2.toString() << endl;
} else {
out << dt1.toString() << " comes after "
<< dt2.toString() << endl;
}
}
该示例比较两个日期。
QDate dt1(2015, 4, 5);
QDate dt2(2014, 4, 5);
我们有两个不同的日期。
if (dt1 < dt2) {
out << dt1.toString() << " comes before "
<< dt2.toString() << endl;
} else {
out << dt1.toString() << " comes after "
<< dt2.toString() << endl;
}
我们使用小于(<)的比较运算符比较日期,并确定其中哪个位于日历的更早位置。
输出:
$ ./comparedates
Sun Apr 5 2015 comes after Sat Apr 5 2014
比较运算符也可以轻松用于QTime和QDateTime对象。
确定闰年
闰年是包含另一天的年份。 日历中额外一天的原因是天文日历年与日历年之间的差异。 日历年正好是 365 天,而天文学年(地球绕太阳公转的时间)是 365.25 天。 相差 6 个小时,这意味着在四年的时间里,我们一天中都没有。 因为我们希望日历与季节同步,所以每四年将 2 月增加一天。 (有例外。)在公历中,闰年的 2 月有 29 天,而不是通常的 28 天。该年持续 366 天,而不是通常的 365 天。
QDate::isLeapYear()静态方法确定年份是否为闰年。
leapyear.cpp
#include <QTextStream>
#include <QDate>
int main() {
QTextStream out(stdout);
QList<int> years({2010, 2011, 2012, 2013, 2014, 2015, 2016});
foreach (int year, years) {
if (QDate::isLeapYear(year)) {
out << year << " is a leap year" << endl;
} else {
out << year << " is not a leap year" << endl;
}
}
}
在示例中,我们有一个年份列表。 我们每年检查是否是闰年。
QList<int> years({2010, 2011, 2012, 2013, 2014, 2015, 2016});
我们初始化一个年份列表。 这是 C++ 11 构造,因此,我们需要启用 C++ 11。 我们需要将CONFIG += c++11,QMAKE_CXXFLAGS += -std=c++11或QMAKE_CXXFLAGS += -std=c++0x添加到.pro文件。
foreach (int year, years) {
if (QDate::isLeapYear(year)) {
out << year << " is a leap year" << endl;
} else {
out << year << " is not a leap year" << endl;
}
}
我们遍历列表,确定给定的年份是否为闰年。 QDate::isLeapYear()返回布尔值true或false。
leapyear.pro
######################################################################
# Automatically generated by qmake (3.0) Fri Oct 30 21:01:31 2015
######################################################################
TEMPLATE = app
TARGET = leapyear
INCLUDEPATH += .
CONFIG += c++11
# Input
SOURCES += leapyear.cpp
QT -= gui
这是项目文件。 我们添加CONFIG += c++11声明以启用 C++ 11。 我们用QT -= gui禁用了 Qt GUI 模块,因为命令行程序不需要它。
输出:
$ ./leapyear
2010 is not a leap year
2011 is not a leap year
2012 is a leap year
2013 is not a leap year
2014 is not a leap year
2015 is not a leap year
2016 is a leap year
预定义的日期格式
Qt5 具有一些内置的日期格式。 QDate对象的toString()方法采用日期格式作为参数。 Qt5 使用的默认日期格式为Qt::TextDate。
dateformats.cpp
#include <QTextStream>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QDate cd = QDate::currentDate();
out << "Today is " << cd.toString(Qt::TextDate) << endl;
out << "Today is " << cd.toString(Qt::ISODate) << endl;
out << "Today is " << cd.toString(Qt::SystemLocaleShortDate) << endl;
out << "Today is " << cd.toString(Qt::SystemLocaleLongDate) << endl;
out << "Today is " << cd.toString(Qt::DefaultLocaleShortDate) << endl;
out << "Today is " << cd.toString(Qt::DefaultLocaleLongDate) << endl;
out << "Today is " << cd.toString(Qt::SystemLocaleDate) << endl;
out << "Today is " << cd.toString(Qt::LocaleDate) << endl;
}
在示例中,我们显示了当前日期的八种不同日期格式。
out << "Today is " << cd.toString(Qt::ISODate) << endl;
在这里,我们以Qt::ISODate格式打印当前日期,这是用于显示日期的国际标准。
输出:
$ ./dateformats
Today is Sat Oct 31 2015
Today is 2015-10-31
Today is 10/31/15
Today is Saturday, October 31, 2015
Today is 10/31/15
Today is Saturday, October 31, 2015
Today is 10/31/15
Today is 10/31/15
自定义日期格式
日期可以用多种其他格式表示。 在 Qt5 中,我们也可以创建自定义日期格式。 toString()方法的另一个版本采用格式字符串,我们可以在其中使用各种格式说明符。 例如,d指示符代表一天,而不是前导零。 dd指示符代表一天,前导零。 下表列出了可用的日期格式表达式:
| 表达式 | 输出 |
|---|---|
d |
不带前导零(1 到 31)的日期 |
dd |
带前导零(01 到 31)的日期 |
ddd |
本地化日期的缩写(例如,Mon到Sun)。 使用QDate::shortDayName()。 |
dddd |
本地化的长名称(例如,Monday到Sunday)。 使用QDate::longDayName()。 |
M |
不带前导零(1 到 12)的月份 |
MM |
前导零(01 到 12)的月份 |
MMM |
本地化月份的缩写名称(例如,"Jan"到"Dec")。 使用QDate::shortMonthName()。 |
MMMM |
本地化的长月份名称(例如,January到December)。 使用QDate::longMonthName()。 |
y |
两位数(00 到 99)的年份 |
yyyy |
四位数的年份。 如果年份为负数,则还会附加一个减号。 |
Table: Date format specifiers
customdateformats.cpp
#include <QTextStream>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QDate cd = QDate::currentDate();
out << "Today is " << cd.toString("yyyy-MM-dd") << endl;
out << "Today is " << cd.toString("yy/M/dd") << endl;
out << "Today is " << cd.toString("d. M. yyyy") << endl;
out << "Today is " << cd.toString("d-MMMM-yyyy") << endl;
}
我们有四种自定义日期格式。
out << "Today is " << cd.toString("yyyy-MM-dd") << endl;
这是国际日期格式。 日期的各部分用笔划线分隔。 yyyy是具有四个数字的年份。 MM是月份,前导零(01 到 12)。 dd是带前导零(01 到 31)的数字的日子。
out << "Today is " << cd.toString("yy/M/dd") << endl;
这是另一种常见的日期格式。 零件之间用斜杠(/)字符分隔。 M指示符代表一个月,不带前导零(1 到 12)。
out << "Today is " << cd.toString("d. M. yyyy") << endl;
此日期格式在斯洛伐克使用。 零件由点字符分隔。 日和月没有前导零。 首先是日期,然后是月份,最后是年份。
输出:
$ ./customdateformats
Today is 2015-10-31
Today is 15/10/31
Today is 31\. 10\. 2015
Today is 31-October-2015
预定义的时间格式
时间具有一些预定义的格式。 标准格式说明符与日期格式中使用的说明符相同。 Qt5 使用的默认时间格式为Qt::TextDate。
timeformats.cpp
#include <QTextStream>
#include <QTime>
int main(void) {
QTextStream out(stdout);
QTime ct = QTime::currentTime();
out << "The time is " << ct.toString(Qt::TextDate) << endl;
out << "The time is " << ct.toString(Qt::ISODate) << endl;
out << "The time is " << ct.toString(Qt::SystemLocaleShortDate) << endl;
out << "The time is " << ct.toString(Qt::SystemLocaleLongDate) << endl;
out << "The time is " << ct.toString(Qt::DefaultLocaleShortDate) << endl;
out << "The time is " << ct.toString(Qt::DefaultLocaleLongDate) << endl;
out << "The time is " << ct.toString(Qt::SystemLocaleDate) << endl;
out << "The time is " << ct.toString(Qt::LocaleDate) << endl;
}
在示例中,我们显示了当前时间的八种不同时间格式。
out << "The time is " << ct.toString(Qt::ISODate) << endl;
在这里,我们以Qt::ISODate格式打印当前时间,这是用于显示时间的国际标准。
输出:
$ ./timeformats
The time is 15:58:26
The time is 15:58:26
The time is 3:58 PM
The time is 3:58:26 PM CET
The time is 3:58 PM
The time is 3:58:26 PM CET
The time is 3:58 PM
The time is 3:58 PM
自定义时间格式
我们可以创建其他时间格式。 我们在使用时间格式说明符的地方构建自定义时间格式。 下表列出了可用的格式表达式。
| 表达式 | 输出 |
|---|---|
h |
没有前导零的小时(如果显示 AM/PM,则为 0 到 23 或 1 到 12) |
hh |
带前导零的小时(如果显示 AM/PM,则为 00 到 23 或 01 到 12) |
H |
没有前导零的小时(0 到 23,即使有 AM/PM 显示) |
HH |
带前导零的小时(00 到 23,即使有 AM/PM 显示) |
m |
没有前导零(0 到 59)的分钟 |
mm |
带前导零(00 到 59)的分钟 |
s |
不带前导零(0 到 59)的秒 |
ss |
带有前导零(00 到 59)的秒 |
z |
不带前导零的毫秒数(0 到 999) |
zz |
带有前导零的毫秒数(000 到 999) |
AP 或 A |
使用 AM/PM 显示。 AP 将被"AM"或"PM"替换。 |
ap 或 a |
使用 am/pm 显示。 ap 将被"am"或"pm"替换。 |
t |
时区(例如"CEST") |
Table: Time format specifiers
customtimeformats.cpp
#include <QTextStream>
#include <QTime>
int main(void) {
QTextStream out(stdout);
QTime ct = QTime::currentTime();
out << "The time is " << ct.toString("hh:mm:ss.zzz") << endl;
out << "The time is " << ct.toString("h:m:s a") << endl;
out << "The time is " << ct.toString("H:m:s A") << endl;
out << "The time is " << ct.toString("h:m AP") << endl;
out << "The version of Qt5 is " << qVersion() << endl;
}
我们有四种自定义时间格式。
out << "The time is " << ct.toString("hh:mm:ss.zzz") << endl;
在这种格式下,我们具有小时,分钟和秒,前导零。 我们还以毫秒为单位加上前导零。
out << "The time is " << ct.toString("h:m:s a") << endl;
此时间格式说明符使用小时,分钟和秒(不带前导零),并添加 AM/PM 周期标识符。
输出:
$ ./customtimeformats
The time is 16:23:43.542
The time is 4:23:43 pm
The time is 16:23:43 PM
The time is 4:23 PM
检索工作日
dayOfWeek()方法返回一个代表星期几的数字,其中 1 是星期一,7 是星期日。
weekday.cpp
#include <QTextStream>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QDate cd = QDate::currentDate();
int wd = cd.dayOfWeek();
out << "Today is " << QDate::shortDayName(wd) << endl;
out << "Today is " << QDate::longDayName(wd) << endl;
}
在示例中,我们打印当前工作日的简称和长名称。
QDate cd = QDate::currentDate();
我们得到当前日期。
int wd = cd.dayOfWeek();
从当前日期开始,我们得到星期几。
out << "Today is " << QDate::shortDayName(wd) << endl;
使用QDate::shortDayName()静态方法,我们得到工作日的简称。
out << "Today is " << QDate::longDayName(wd) << endl;
使用QDate::longDayName()静态方法,我们获得了工作日的长名称。
输出:
$ ./weekday
Today is Sat
Today is Saturday
天数
我们可以使用daysInMonth()方法计算特定月份中的天数,并使用daysInYear()方法计算一年中的天数。
nofdays.cpp
#include <QTextStream>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QList<QString> months;
months.append("January");
months.append("February");
months.append("March");
months.append("April");
months.append("May");
months.append("June");
months.append("July");
months.append("August");
months.append("September");
months.append("October");
months.append("November");
months.append("December");
QDate dt1(2015, 9, 18);
QDate dt2(2015, 2, 11);
QDate dt3(2015, 5, 1);
QDate dt4(2015, 12, 11);
QDate dt5(2015, 1, 21);
out << "There are " << dt1.daysInMonth() << " days in "
<< months.at(dt1.month()-1) << endl;
out << "There are " << dt2.daysInMonth() << " days in "
<< months.at(dt2.month()-1) << endl;
out << "There are " << dt3.daysInMonth() << " days in "
<< months.at(dt3.month()-1) << endl;
out << "There are " << dt4.daysInMonth() << " days in "
<< months.at(dt4.month()-1) << endl;
out << "There are " << dt5.daysInMonth() << " days in "
<< months.at(dt5.month()-1) << endl;
out << "There are " << dt1.daysInYear() << " days in year "
<< QString::number(dt1.year()) << endl;
}
创建了五个日期对象。 我们计算这些月份和特定年份的天数。
QDate dt1(2015, 9, 18);
QDate dt2(2015, 2, 11);
QDate dt3(2015, 5, 1);
QDate dt4(2015, 12, 11);
QDate dt5(2015, 1, 21);
创建了五个QDate对象。 每个代表不同的日期。
out << "There are " << dt1.daysInMonth() << " days in "
<< months.at(dt1.month()-1) << endl;
我们使用daysInMonth()方法获取日期对象中的天数。
out << "There are " << dt1.daysInYear() << " days in year "
<< QString::number(dt1.year()) << endl;
在这里,我们使用日期对象的daysInYear()方法获得一年中的天数。
输出:
$ ./nofdays
There are 30 days in September
There are 28 days in February
There are 31 days in May
There are 31 days in December
There are 31 days in January
There are 365 days in year 2015
检查日期的有效性
有isValid()方法可以检查日期是否有效。
validity.cpp
#include <QTextStream>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QList<QDate> dates({QDate(2015, 5, 11), QDate(2015, 8, 1),
QDate(2015, 2, 30)});
for (int i=0; i < dates.size(); i++) {
if (dates.at(i).isValid()) {
out << "Date " << i+1 << " is a valid date" << endl;
} else {
out << "Date " << i+1 << " is not a valid date" << endl;
}
}
}
在示例中,我们检查了三天的有效性。
QList<QDate> dates({QDate(2015, 5, 11), QDate(2015, 8, 1),
QDate(2015, 2, 30)});
前两天有效。 第三个无效。 2 月有 28 或 29 天。
if (dates.at(i).isValid()) {
out << "Date " << i+1 << " is a valid date" << endl;
} else {
out << "Date " << i+1 << " is not a valid date" << endl;
}
根据isValid()方法的结果,我们将有关日期有效性的消息打印到控制台。
输出:
$ ./validity
Date 1 is a valid date
Date 2 is a valid date
Date 3 is not a valid date
到...的天数
我们可以轻松地从特定日期算起 n 天的日期。 我们使用addDays()方法。 daysTo()方法返回到选定日期的天数。
daystofrom.cpp
#include <QTextStream>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QDate dt(2015, 5, 11);
QDate nd = dt.addDays(55);
QDate xmas(2015, 12, 24);
out << "55 days from " << dt.toString() << " is "
<< nd.toString() << endl;
out << "There are " << QDate::currentDate().daysTo(xmas)
<< " days till Christmas" << endl;
}
从 2015 年 5 月 11 日起,我们将在 55 天后获得日期。我们还将获得圣诞节前的天数。
QDate dt(2015, 5, 11);
QDate nd = dt.addDays(55);
addDays()方法返回给定日期之后 55 天的QDate。
QDate xmas(2015, 12, 24);
...
out << "There are " << QDate::currentDate().daysTo(xmas)
<< " days till Christmas" << endl;
我们使用daysTo()方法来计算直到圣诞节的天数。
输出:
$ ./daystofrom
55 days from Mon May 11 2015 is Sun Jul 5 2015
There are 54 days till Christmas
QDateTime类
QDateTime对象包含日历日期和时钟时间。 它是QDate和QTime类的组合。 它有许多相似的方法,用法与这两类相同。
datetime.cpp
#include <QTextStream>
#include <QDateTime>
int main(void) {
QTextStream out(stdout);
QDateTime cdt = QDateTime::currentDateTime();
out << "The current datetime is " << cdt.toString() << endl;
out << "The current date is " << cdt.date().toString() << endl;
out << "The current time is " << cdt.time().toString() << endl;
}
该示例检索当前日期时间。
out << "The current datetime is " << cdt.toString() << endl;
这行代码将当前日期时间打印到终端。
out << "The current date is " << cdt.date().toString() << endl;
此行使用date()方法检索日期时间对象的日期部分。
输出:
$ ./datetime
The current datetime is Sat Oct 31 17:10:50 2015
The current date is Sat Oct 31 2015
The current time is 17:10:50
朱利安日
儒略日是指儒略时期开始以来的连续天数。 它主要由天文学家使用。 不应将其与朱利安历法相混淆。 朱利安纪元开始于公元前 4713 年。 朱利安天数 0 被指定为从公元前 4713 年 1 月 1 日正午开始的那一天。 朱利安天数(JDN)是自此时间段开始以来经过的天数。 任意时刻的儒略日期(JD)是前一个正午的儒略日编号加上该时刻以来的那一天的分数。 (Qt5 不会计算此分数。)除天文学外,军事和大型机程序经常使用儒略日期。
julianday.cpp
#include <QTextStream>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QDate cd = QDate::currentDate();
out << "Gregorian date for today: " << cd.toString(Qt::ISODate) << endl;
out << "Julian day for today: " << cd.toJulianDay() << endl;
}
在示例中,我们计算了今天的公历日期和儒略日。
out << "Julian day for today: " << cd.toJulianDay() << endl;
使用toJulianDay()方法返回儒略日。
输出:
$ ./julianday
Gregorian date for today: 2015-10-31
Julian day for today: 2457327
使用儒略日期,很容易进行计算。
battles.cpp
#include <QTextStream>
#include <QDate>
int main(void) {
QTextStream out(stdout);
QDate bordate(1812, 9, 7);
QDate slavdate(1805, 12, 2);
QDate cd = QDate::currentDate();
int j_today = cd.toJulianDay();
int j_borodino = bordate.toJulianDay();
int j_slavkov = slavdate.toJulianDay();
out << "Days since Slavkov battle: " << j_today - j_slavkov << endl;
out << "Days since Borodino battle: " << j_today - j_borodino << endl;
}
该示例计算自两次历史事件以来经过的天数。
QDate bordate(1812, 9, 7);
QDate slavdate(1805, 12, 2);
我们有拿破仑纪元的两次战斗。
int j_today = cd.toJulianDay();
int j_borodino = bordate.toJulianDay();
int j_slavkov = slavdate.toJulianDay();
我们计算了今天以及斯拉夫科夫和波罗底诺战役的儒略日。
out << "Days since Slavkov battle: " << j_today - j_slavkov << endl;
out << "Days since Borodino battle: " << j_today - j_borodino << endl;
我们计算了两次战斗以来经过的天数。
输出:
$ date
Sat Oct 31 20:07:32 CET 2015
$ ./battles
Days since Slavkov battle: 76669
Days since Borodino battle: 74198
自 2015 年 10 月 31 日起,斯拉夫科夫战役已经过去了 76669 天,而波罗底诺战役已经过去了 74198 天。
UTC 时间
我们的星球是一个球体。 它绕其轴旋转。 地球向东旋转。 因此,太阳在不同位置的不同时间升起。 地球大约每 24 小时旋转一次。 因此,世界被划分为 24 个时区。 在每个时区,都有一个不同的本地时间。 夏令时通常会进一步修改此本地时间。
实际需要一个全球时间。 全球时间可以避免时区和夏令时的混淆。 UTC(世界标准时间)被选为主要时间标准。 UTC 用于航空,天气预报,飞行计划,空中交通管制通关和地图。 与当地时间不同,UTC 不会随季节变化而变化。
utclocal.cpp
#include <QTextStream>
#include <QDateTime>
int main(void) {
QTextStream out(stdout);
QDateTime cdt = QDateTime::currentDateTime();
out << "Universal datetime: " << cdt.toUTC().toString() << endl;
out << "Local datetime: " << cdt.toLocalTime().toString() << endl;
}
在示例中,我们计算当前日期时间。 我们用 UTC 日期时间和本地日期时间表示日期时间。
out << "Universal datetime" << cdt.toUTC().toString() << endl;
toUTC()方法用于获取 UTC 日期时间。
out << "Local datetime" << cdt.toLocalTime().toString() << endl;
toLocalTime()用于获取本地日期时间。
输出:
$ ./utclocal
Universal datetime: Sat Oct 31 19:09:44 2015 GMT
Local datetime: Sat Oct 31 20:09:44 2015
该示例在布拉迪斯拉发运行,该站点的中欧时间(CET)为 UTC + 1 小时。
Unix 纪元
纪元是选择作为特定纪元起源的时间瞬间。 例如,在西方基督教国家,时间从耶稣出生的第 0 天开始。 另一个例子是法国共和党日历,使用了十二年。 这个时期是 1792 年 9 月 22 日宣布的共和纪元的开始,即宣布成立第一共和国并废除君主制的那一天。
电脑也有自己的纪元。 最受欢迎的版本之一是 Unix 纪元。 Unix 纪元是 1970 年 1 月 1 日 UTC 时间 00:00:00(或1970-01-01T00:00:00Z ISO8601)。 计算机中的日期和时间是根据自该计算机或平台的定义时期以来经过的秒数或时钟滴答数确定的。
Unix 时间是自 Unix 纪元以来经过的秒数。
$ date +%s
1446318984
Unix date 命令可用于获取 Unix 时间。 在这个特定时刻,自 Unix 纪元以来已经过去了 1446318984 秒。
unixepoch.cpp
#include <QTextStream>
#include <QDateTime>
#include <ctime>
int main(void) {
QTextStream out(stdout);
time_t t = time(0);
out << t << endl;
QDateTime dt;
dt.setTime_t(t);
out << dt.toString() << endl;
QDateTime cd = QDateTime::currentDateTime();
out << cd.toTime_t() << endl;
}
在示例中,我们使用两个 Qt5 函数获取 Unix 时间并将其转换为人类可读的形式。
#include <ctime>
我们包括标准的 C++ 时间头文件。
time_t t = time(0);
out << t << endl;
使用标准的 C++ time()命令,我们可以获得 Unix 时间。
QDateTime dt;
dt.setTime_t(t);
out << dt.toString() << endl;
setTime_t()方法用于将 Unix 时间转换为日期时间,并将其格式化为易于阅读的格式。
QDateTime cd = QDateTime::currentDateTime();
out << cd.toTime_t() << endl;
Qt5 的toTime_t()方法也可以用来获取 Unix 时间。
输出:
$ ./unixepoch
1446319003
Sat Oct 31 20:16:43 2015
1446319003
在本章中,我们处理了时间和日期。
JavaScript GTK 中的小部件
在 JavaScript GTK 编程教程的这一部分中,我们将介绍一些小部件。
小部件是 GUI 应用的基本构建块。 多年来,几个小部件已成为所有 OS 平台上所有工具包中的标准。 例如,按钮,复选框或滚动条。 GTK 工具箱的理念是将小部件的数量保持在最低水平。 将创建更多专门的窗口小部件作为自定义 GTK 窗口小部件。
CheckBox
CheckButton是具有两种状态的窗口小部件:打开和关闭。 接通状态通过复选标记显示。 它用来表示一些布尔属性。
#!/usr/bin/seed
/*
ZetCode JavaScript GTK tutorial
This program toggles the title of the
window with the CheckButton widget.
author: Jan Bodnar
website: www.zetcode.com
last modified: January 2014
*/
Gtk = imports.gi.Gtk;
Gtk.init(null, null);
Example = new GType({
parent: Gtk.Window.type,
name: "Example",
init: function ()
{
init_ui(this);
function init_ui(w) {
w.signal.hide.connect(Gtk.main_quit);
w.set_default_size(300, 200);
w.set_title("Check button");
w.set_position(Gtk.WindowPosition.CENTER);
var fix = new Gtk.Fixed();
var cb = new Gtk.CheckButton.with_label("Show title");
cb.set_active(true);
cb.signal.clicked.connect(on_clicked);
fix.put(cb, 50, 50);
w.add(fix);
w.show_all();
function on_clicked(w) {
if (w.get_active()) {
window.set_title("Check Button");
} else {
window.set_title("");
}
}
}
}
});
var window = new Example();
Gtk.main();
根据CheckButton的状态,我们将在窗口的标题栏中显示标题。
var cb = new Gtk.CheckButton.with_label("Show title");
CheckButton小部件已创建。 小部件的构造器采用一个参数,即标签。 标签显示在复选框旁边。
cb.set_active(true);
默认情况下标题是可见的,因此我们默认情况下选中复选按钮。
cb.signal.clicked.connect(on_clicked);
如果我们单击复选框小部件,则会发出单击的信号。 我们将on_clicked()方法挂接到信号上。
if (w.get_active()) {
window.set_title("Check Button");
} else {
window.set_title("");
}
如果选中该按钮,我们将显示标题。 get_active()方法用于确定检查按钮的状态。 set_title()方法用于设置窗口的标题。 为了清除窗口的标题,我们使用一个空字符串。

图:CheckButton
Label
Label小部件显示文本。 此小部件不支持用户交互。
#!/usr/bin/seed
/*
ZetCode JavaScript GTK tutorial
This example demonstrates the Label widget.
author: Jan Bodnar
website: www.zetcode.com
last modified: January 2014
*/
Gtk = imports.gi.Gtk;
Gtk.init(null, null);
var lyrics = "Meet you downstairs in the bar and heard\n\
your rolled up sleeves and your skull t-shirt\n\
You say why did you do it with him today?\n\
and sniff me out like I was Tanqueray\n\
\n\
cause you're my fella, my guy\n\
hand me your stella and fly\n\
by the time I'm out the door\n\
you tear men down like Roger Moore\n\
\n\
I cheated myself\n\
like I knew I would\n\
I told ya, I was trouble\n\
you know that I'm no good";
Example = new GType({
parent: Gtk.Window.type,
name: "Example",
init: function()
{
init_ui(this);
function init_ui(w) {
w.signal.hide.connect(Gtk.main_quit);
w.set_default_size(250, 200);
w.set_title("You know I'm no Good");
w.set_position(Gtk.WindowPosition.CENTER);
w.set_border_width(10);
var label = new Gtk.Label.c_new(lyrics);
w.add(label);
w.show_all();
}
}
});
var window = new Example();
Gtk.main();
该代码示例在窗口上显示了一些歌词。
var lyrics = "Meet you downstairs in the bar and heard\n\
your rolled up sleeves and your skull t-shirt\n\
我们创建多行文本。 在 JavaScript 中,多行文本由以换行符和反斜杠结尾的文本行组成。 反斜杠使 JavaScript 中的字符串可以跨越多个源代码行。 换行符在更多行中显示字符串。
w.set_border_width(10);
Label周围有一些空白。
var label = new Gtk.Label.c_new(lyrics);
w.add(label);
Label小部件已创建并添加到窗口。

图:Label小部件
Entry
Entry是单行文本输入字段。 该小部件用于输入文本数据。
#!/usr/bin/seed
/*
ZetCode JavaScript GTK tutorial
This example demonstrates the Entry widget.
author: Jan Bodnar
website: www.zetcode.com
last modified: January 2014
*/
Gtk = imports.gi.Gtk;
Gtk.init(null, null);
Example = new GType({
parent: Gtk.Window.type,
name: "Example",
init: function()
{
init_ui(this);
function init_ui(w) {
w.signal.hide.connect(Gtk.main_quit);
w.set_default_size(250, 200);
w.set_title("Entry");
w.set_position(Gtk.WindowPosition.CENTER);
var fixed = new Gtk.Fixed();
var label = new Gtk.Label.c_new("...");
fixed.put(label, 60, 40);
var entry = new Gtk.Entry();
fixed.put(entry, 60, 100);
entry.signal.key_release_event.connect(function(sender) {
label.set_text(sender.text);
return false;
});
w.add(fixed);
w.show_all();
}
}
});
var window = new Example();
Gtk.main();
此示例显示了条目小部件和标签。 我们输入的文本将立即显示在标签小部件中。
var entry = new Gtk.Entry();
Entry小部件已创建。
entry.signal.key_release_event.connect(function(sender) {
label.set_text(sender.text);
return false;
});
我们将匿名方法插入Entry小部件的key_release_event。 我们通过text属性从窗口小部件中获取文本并将其设置为标签。

图:Entry小部件
Image
Image小部件在窗口上显示图像。
#!/usr/bin/seed
/*
ZetCode JavaScript GTK tutorial
This example demonstrates the Image widget.
author: Jan Bodnar
website: www.zetcode.com
last modified: January 2014
*/
Gtk = imports.gi.Gtk;
Gtk.init(null, null);
Example = new GType({
parent: Gtk.Window.type,
name: "Example",
init: function()
{
init_ui(this);
function init_ui(w) {
w.signal.hide.connect(Gtk.main_quit);
w.set_default_size(250, 200);
w.set_title("You know I'm no Good");
w.set_position(Gtk.WindowPosition.CENTER);
w.set_border_width(2);
var image = new Gtk.Image.from_file("redrock.png");
w.add(image);
w.show_all();
}
}
});
var window = new Example();
Gtk.main();
在我们的示例中,我们在窗口上显示图像。
w.set_border_width(2);
我们在图像周围放置了一些空边框。
var image = new Gtk.Image.from_file("redrock.png");
Image小部件已创建。 小部件会自行处理错误。 如果无法加载图像,它将显示一个损坏的图标。
w.add(image);
小部件已添加到容器中。

图:图像 widget
在 JavaScript GTK 教程的这一章中,我们展示了一些基本的小部件。
JavaScript GTK 中的菜单和工具栏
原文: http://zetcode.com/gui/javascriptgtktutorial/menustoolbars/
在 JavaScript GTK 编程教程的这一部分中,我们将使用菜单和工具栏。
菜单栏是 GUI 应用中最常见的部分之一。 它是位于各个菜单中的一组命令。 在控制台应用中,您必须记住所有这些神秘命令,在这里,我们将大多数命令分组为逻辑部分。 这些公认的标准可进一步减少学习新应用的时间。
简单菜单
在第一个示例中,我们将创建一个带有一个文件菜单的菜单栏。 该菜单将只有一个菜单项。 通过选择项目,应用退出。
#!/usr/bin/seed
/*
ZetCode JavaScript GTK tutorial
This example shows a simple menu.
author: Jan Bodnar
website: www.zetcode.com
last modified: July 2011
*/
Gtk = imports.gi.Gtk;
Gdk = imports.gi.Gdk;
Gtk.init(null, null);
Example = new GType({
parent: Gtk.Window.type,
name: "Example",
init: function ()
{
init_ui(this);
function init_ui(w) {
w.signal.hide.connect(Gtk.main_quit);
w.set_default_size(250, 200);
w.set_title("Simple menu");
w.set_position(Gtk.WindowPosition.CENTER);
w.modify_bg(Gtk.StateType.NORMAL,
new Gdk.Color({red:6400, green:6400, blue:6440}));
var mb = new Gtk.MenuBar();
var filemenu = new Gtk.Menu();
var filem = new Gtk.MenuItem.with_label("File");
filem.set_submenu(filemenu);
var exitmu = new Gtk.MenuItem.with_label("Exit");
exitmu.signal.activate.connect(Gtk.main_quit);
filemenu.append(exitmu);
mb.append(filem);
vbox = new Gtk.VBox.c_new(false, 2);
vbox.pack_start(mb, false, false, 0);
w.add(vbox);
w.show_all();
}
}
});
var window = new Example();
Gtk.main();
这是一个最小的菜单栏功能示例。
var mb = new Gtk.MenuBar();
MenuBar小部件已创建。 这是菜单的容器。
var filemenu = new Gtk.Menu();
var filem = new Gtk.MenuItem.with_label("File");
filem.set_submenu(filemenu);
创建顶层MenuItem。
var exitmu = new Gtk.MenuItem.with_label("Exit");
exitmu.signal.activate.connect(Gtk.main_quit);
filemenu.append(exitmu);
将创建出口MenuItem,并将其附加到文件MenuItem中。
mb.append(filem);
顶级MenuItem被附加到MenuBar小部件。
vbox = new Gtk.VBox.c_new(false, 2);
vbox.pack_start(mb, false, false, 0);
与其他工具包不同,我们必须自己照顾菜单栏的布局管理。 我们将菜单栏放入垂直框中。

图:简单菜单
子菜单
我们的最后一个示例演示了如何创建子菜单。 子菜单是另一个菜单中的菜单。
#!/usr/bin/seed
/*
ZetCode JavaScript GTK tutorial
This example shows a submenu.
author: Jan Bodnar
website: www.zetcode.com
last modified: July 2011
*/
Gtk = imports.gi.Gtk;
Gdk = imports.gi.Gdk;
Gtk.init(null, null);
Example = new GType({
parent: Gtk.Window.type,
name: "Example",
init: function ()
{
init_ui(this);
function init_ui(w) {
w.signal.hide.connect(Gtk.main_quit);
w.set_default_size(250, 200);
w.set_title("Submenu");
w.set_position(Gtk.WindowPosition.CENTER);
w.modify_bg(Gtk.StateType.NORMAL,
new Gdk.Color({red:6400, green:6400, blue:6440}));
var mb = new Gtk.MenuBar();
var filemenu = new Gtk.Menu();
var filem = new Gtk.MenuItem.with_label("File");
filem.set_submenu(filemenu);
mb.append(filem);
var imenu = new Gtk.Menu();
var importm = new Gtk.MenuItem.with_label("Import");
importm.set_submenu(imenu);
var inews = new Gtk.MenuItem.with_label("Import news feed...");
var ibookmarks = new Gtk.MenuItem.with_label("Import bookmarks...");
var imail = new Gtk.MenuItem.with_label("Import mail...");
imenu.append(inews);
imenu.append(ibookmarks);
imenu.append(imail);
filemenu.append(importm);
var emi = new Gtk.MenuItem.with_label("Exit");
emi.signal.activate.connect(Gtk.main_quit);
filemenu.append(emi);
var vbox = new Gtk.VBox.c_new(false, 2);
vbox.pack_start(mb, false, false, 0);
w.add(vbox);
w.show_all();
}
}
});
var window = new Example();
Gtk.main();
在此示例中,我们创建一个子菜单。
var imenu = new Gtk.Menu();
子菜单是Menu。
var importm = new Gtk.MenuItem.with_label("Import");
importm.set_submenu(imenu);
它是菜单项的子菜单,它会登录到顶级文件菜单。
var inews = new Gtk.MenuItem.with_label("Import news feed...");
var ibookmarks = new Gtk.MenuItem.with_label("Import bookmarks...");
var imail = new Gtk.MenuItem.with_label("Import mail...");
imenu.append(inews);
imenu.append(ibookmarks);
imenu.append(imail);
子菜单有其自己的菜单项。

图:子菜单
图像菜单
在下一个示例中,我们将进一步探索菜单。 我们将图像和加速器添加到我们的菜单项中。 加速器是用于激活菜单项的键盘快捷键。
#!/usr/bin/seed
/*
ZetCode JavaScript GTK tutorial
In this example, we explore image menu
items, a separator, accelerators.
author: Jan Bodnar
website: www.zetcode.com
last modified: July 2011
*/
Gtk = imports.gi.Gtk;
Gdk = imports.gi.Gdk;
Gtk.init(null, null);
Example = new GType({
parent: Gtk.Window.type,
name: "Example",
init: function ()
{
init_ui(this);
function init_ui(w) {
w.signal.hide.connect(Gtk.main_quit);
w.set_default_size(250, 200);
w.set_title("Image menu");
w.set_position(Gtk.WindowPosition.CENTER);
w.modify_bg(Gtk.StateType.NORMAL,
new Gdk.Color({red:6400, green:6400, blue:6440}));
var mb = new Gtk.MenuBar();
var filemenu = new Gtk.Menu();
var filem = new Gtk.MenuItem.with_label("File");
filem.set_submenu(filemenu);
var agr = new Gtk.AccelGroup();
w.add_accel_group(agr);
var newi = new Gtk.ImageMenuItem.from_stock(Gtk.STOCK_NEW, agr);
newi.signal.activate.connect(function() {print("new")});
filemenu.append(newi);
var openm = new Gtk.ImageMenuItem.from_stock(Gtk.STOCK_OPEN, agr);
filemenu.append(openm);
var sep = new Gtk.SeparatorMenuItem();
filemenu.append(sep);
var exitmu = new Gtk.ImageMenuItem.from_stock(Gtk.STOCK_QUIT, agr);
exitmu.signal.activate.connect(Gtk.main_quit);
filemenu.append(exitmu);
mb.append(filem);
var vbox = new Gtk.VBox.c_new(false, 2);
vbox.pack_start(mb, false, false, 0);
w.add(vbox);
w.show_all();
}
}
});
var window = new Example();
Gtk.main();
我们的示例显示了具有三个子菜单项的顶级菜单项。 每个菜单项都有一个图像和一个加速器。 退出菜单项的加速器退出应用。 新菜单项的加速器将"new"打印到控制台。
var agr = new Gtk.AccelGroup();
w.add_accel_group(agr);
要使用加速器,我们创建一个全局AccelGroup对象。 稍后将使用。
var newi = new Gtk.ImageMenuItem.from_stock(Gtk.STOCK_NEW, agr);
newi.signal.activate.connect(function() {print("new")});
filemenu.append(newi);
ImageMenuItem已创建。 图片来自图片库。 自动创建 Ctrl + N 加速器。 我们将匿名方法插入菜单项。 它将消息打印到控制台。
var sep = new Gtk.SeparatorMenuItem();
filemenu.append(sep);
这些行创建一个分隔符。 它用于将菜单项放入逻辑组。

图:图像 menu
菜单将我们可以在应用中使用的命令分组。 使用工具栏可以快速访问最常用的命令。
简单的工具栏
接下来,我们创建一个简单的工具栏。 工具栏提供对应用最常用功能的快速访问。
#!/usr/bin/seed
/*
ZetCode JavaScript GTK tutorial
In this example, we create a simple
toolbar.
author: Jan Bodnar
website: www.zetcode.com
last modified: July 2011
*/
Gtk = imports.gi.Gtk;
Gtk.init(null, null);
Example = new GType({
parent: Gtk.Window.type,
name: "Example",
init: function ()
{
init_ui(this);
function init_ui(w) {
w.signal.hide.connect(Gtk.main_quit);
w.set_default_size(250, 200);
w.set_title("Toolbar");
w.set_position(Gtk.WindowPosition.CENTER);
var toolbar = new Gtk.Toolbar();
toolbar.set_style(Gtk.ToolbarStyle.ICONS);
var newtb = new Gtk.ToolButton.from_stock(Gtk.STOCK_NEW);
var opentb = new Gtk.ToolButton.from_stock(Gtk.STOCK_OPEN);
var savetb = new Gtk.ToolButton.from_stock(Gtk.STOCK_SAVE);
var sep = new Gtk.SeparatorToolItem();
var quittb = new Gtk.ToolButton.from_stock(Gtk.STOCK_QUIT);
toolbar.insert(newtb, 0);
toolbar.insert(opentb, 1);
toolbar.insert(savetb, 2);
toolbar.insert(sep, 3);
toolbar.insert(quittb, 4);
quittb.signal.clicked.connect(Gtk.main_quit);
var vbox = new Gtk.VBox.c_new(false, 2);
vbox.pack_start(toolbar, false, false, 0);
w.add(vbox);
w.show_all();
}
}
});
var window = new Example();
Gtk.main();
该示例显示了一个工具栏和四个工具按钮。
var toolbar = new Gtk.Toolbar();
Toolbar小部件已创建。
toolbar.set_style(Gtk.ToolbarStyle.ICONS);
在工具栏上,我们仅显示图标。 没有文字。
var newtb = new Gtk.ToolButton.from_stock(Gtk.STOCK_NEW);
创建带有库存图像的ToolButton。 该图像来自图像的内置库存。
var sep = new Gtk.SeparatorToolItem();
这是一个分隔符。 它可用于将工具栏按钮放入逻辑组。
toolbar.insert(newtb, 0);
toolbar.insert(opentb, 1);
...
工具栏按钮插入到工具栏小部件中。 insert()方法的第一个参数是工具按钮。 第二个是工具栏上的位置。

图:工具栏
在 JavaScript GTK 教程的这一章中,我们展示了如何使用菜单和工具栏。
JavaScript GTK 中的对话框
在 JavaScript GTK 编程教程的这一部分中,我们将介绍对话框。
对话框窗口或对话框是大多数现代 GUI 应用必不可少的部分。 对话被定义为两个或更多人之间的对话。 在计算机应用中,对话框是一个窗口,用于与应用“对话”。 对话框用于输入数据,修改数据,更改应用设置等。对话框是用户与计算机程序之间进行通信的重要手段。
MessageDialog
消息对话框是方便的对话框,可向应用的用户提供消息。 该消息包含文本和图像数据。
#!/usr/bin/seed
/*
ZetCode JavaScript GTK tutorial
This example demonstrates a
Message dialog
author: Jan Bodnar
website: www.zetcode.com
last modified: July 2011
*/
Gtk = imports.gi.Gtk;
Gtk.init(null, null);
Example = new GType({
parent: Gtk.Window.type,
name: "Example",
init: function ()
{
init_ui(this);
function init_ui(w) {
w.signal.hide.connect(Gtk.main_quit);
w.set_default_size(250, 200);
w.set_title("Message dialog");
w.set_position(Gtk.WindowPosition.CENTER);
var fixed = new Gtk.Fixed();
var infoButton = new Gtk.Button.with_label("Information");
fixed.put(infoButton, 30, 20);
infoButton.signal.clicked.connect(on_info);
w.add(fixed);
w.show_all();
}
function on_info() {
var md = new Gtk.MessageDialog({modal:true, title:"Information",
message_type:Gtk.MessageType.INFO,
buttons:Gtk.ButtonsType.OK, text:"Download completed."});
md.run();
md.destroy();
}
}
});
var window = new Example();
Gtk.main();
我们在窗口上显示一个按钮。 当我们单击按钮时,会显示一条信息消息。
var infoButton = new Gtk.Button.with_label("Information");
这是一个按钮,当我们单击它时将显示一个对话框。
function on_info() {
var md = new Gtk.MessageDialog({modal:true, title:"Information",
message_type:Gtk.MessageType.INFO,
buttons:Gtk.ButtonsType.OK, text:"Download completed."});
md.run();
md.destroy();
}
如果单击信息按钮,将显示“信息”对话框。 Gtk.MessageType.INFO指定对话框的类型。 Gtk.ButtonsType.OK指定对话框中将显示哪些按钮。 最后一个参数是显示的消息。 该对话框使用run()方法显示。 程序员还必须调用destroy()或hide()方法。

图:MessageDialog
AboutDialog
AboutDialog显示有关应用的信息。 它可以显示徽标,应用名称,版本,版权,网站或许可证信息。 也有可能对作者,文档撰写者,翻译者和艺术家予以赞扬。
#!/usr/bin/seed
/*
ZetCode JavaScript GTK tutorial
This example demonstrates the
AboutDialog dialog.
author: Jan Bodnar
website: www.zetcode.com
last modified: July 2011
*/
Gtk = imports.gi.Gtk;
GdkPixbuf = imports.gi.GdkPixbuf;
Gtk.init(null, null);
Example = new GType({
parent: Gtk.Window.type,
name: "Example",
init: function ()
{
init_ui(this);
function init_ui(w) {
w.signal.hide.connect(Gtk.main_quit);
w.set_default_size(250, 200);
w.set_title("About dialog");
w.set_position(Gtk.WindowPosition.CENTER);
var button = new Gtk.Button.with_label("About");
button.set_size_request(80, 30);
button.signal.clicked.connect(on_clicked);
var fix = new Gtk.Fixed();
fix.put(button, 20, 20);
w.add(fix);
w.show_all();
}
function on_clicked() {
var about = new Gtk.AboutDialog();
about.set_program_name("Battery");
about.set_version("0.1");
about.set_copyright("(c) Jan Bodnar");
about.set_comments("Battery is a simple tool for battery checking");
about.set_website("http://www.zetcode.com");
about.set_logo(new GdkPixbuf.Pixbuf.from_file("battery.png"));
about.run();
about.destroy();
}
}
});
var window = new Example();
Gtk.main();
该代码示例使用具有某些功能的AboutDialog。
var about = new Gtk.AboutDialog();
我们创建AboutDialog的实例。
about.set_program_name("Battery");
about.set_version("0.1");
about.set_copyright("(c) Jan Bodnar");
在这里,我们指定程序的名称,版本和版权。
about.set_logo(new GdkPixbuf.Pixbuf.from_file("battery.png"));
此行创建徽标。

图:AboutDialog
FontSelectionDialog
FontSelectionDialog是用于选择字体的对话框。 它通常用于进行一些文本编辑或格式化的应用中。
#!/usr/bin/seed
/*
ZetCode JavaScript GTK tutorial
This example works with the
FontSelectionDialog.
author: Jan Bodnar
website: www.zetcode.com
last modified: July 2011
*/
Gtk = imports.gi.Gtk;
Pango = imports.gi.Pango;
Gtk.init(null, null);
Example = new GType({
parent: Gtk.Window.type,
name: "Example",
init: function ()
{
init_ui(this);
var label;
function init_ui(w) {
w.signal.hide.connect(Gtk.main_quit);
w.set_default_size(350, 150);
w.set_title("Font selection");
w.set_position(Gtk.WindowPosition.CENTER);
w.set_border_width(10);
var text = "The only victory over love is flight."
label = new Gtk.Label.c_new(text);
var button = new Gtk.Button.with_label("Select font");
button.signal.clicked.connect(on_clicked);
var fix = new Gtk.Fixed();
fix.put(button, 100, 30);
fix.put(label, 30, 90);
w.add(fix);
w.show_all();
}
function on_clicked() {
var fdia = new Gtk.FontSelectionDialog.c_new("Select font");
var response = fdia.run();
if (response == Gtk.ResponseType.OK) {
var fname = fdia.get_font_name();
var font_desc = Pango.Font.description_from_string(fname);
if (font_desc)
label.modify_font(font_desc);
}
fdia.destroy();
}
}
});
var window = new Example();
Gtk.main();
在代码示例中,我们有一个按钮和一个标签。 单击按钮显示FontSelectionDialog。
var fdia = new Gtk.FontSelectionDialog.c_new("Select font");
我们创建FontSelectionDialog。
if (response == Gtk.ResponseType.OK) {
var fname = fdia.get_font_name();
var font_desc = Pango.Font.description_from_string(fname);
if (font_desc)
label.modify_font(font_desc);
}
如果单击“确定”按钮,则标签小部件的字体将更改为我们在对话框中选择的字体。

图:FontSelectionDialog
在 JavaScript GTK 教程的这一部分中,我们介绍了对话框。
JavaScript GTK 中的 Cario 绘图
在 JavaScript GTK 教程的这一部分中,我们将使用 Cairo 库进行一些绘图。 目前,Seed 仅支持 Cario 库的一小部分。
Cairo 是用于创建 2D 矢量图形的库。 我们可以使用它来绘制自己的小部件,图表或各种效果或动画。
色彩
在第一个示例中,我们将处理颜色。 颜色是代表红色,绿色和蓝色(RGB)强度值的组合的对象。 Cario 有效 RGB 值在 0 到 1 的范围内。
#!/usr/bin/seed
/*
ZetCode JavaScript GTK tutorial
In this program, we will draw three
colored rectangles on the drawing area
using Cairo
author: Jan Bodnar
website: www.zetcode.com
last modified: July 2011
*/
Gtk = imports.gi.Gtk;
cairo = imports.cairo;
Gtk.init(null, null);
Example = new GType({
parent: Gtk.Window.type,
name: "Example",
init: function ()
{
init_ui(this);
function init_ui(w) {
w.signal.hide.connect(Gtk.main_quit);
w.set_default_size(360, 100);
w.set_title("Colors");
w.set_position(Gtk.WindowPosition.CENTER);
var darea = new Gtk.DrawingArea();
darea.signal.expose_event.connect(on_expose);
w.add(darea);
w.show_all();
}
function on_expose(darea) {
print(darea);
var cr = new cairo.Context.from_drawable(darea.window);
draw_colors(cr);
}
function draw_colors(cr) {
cr.set_source_rgb(0.2, 0.23, 0.9);
cr.rectangle(10, 15, 90, 60);
cr.fill();
cr.set_source_rgb(0.9, 0.1, 0.1);
cr.rectangle(130, 15, 90, 60);
cr.fill();
cr.set_source_rgb(0.4, 0.9, 0.4);
cr.rectangle(250, 15, 90, 60);
cr.fill();
}
}
});
var window = new Example();
Gtk.main();
在我们的示例中,我们将绘制三个矩形,并用三种不同的颜色填充它们。
var darea = new Gtk.DrawingArea();
我们将在DrawingArea小部件上进行绘制操作。
darea.signal.expose_event.connect(on_expose);
当需要重绘窗口时,将触发expose_event。 为响应此事件,我们调用on_expose()方法。
var cr = new cairo.Context.from_drawable(darea.window);
我们从绘图区域的GdkWindow创建 cairo 上下文对象。 上下文是我们绘制所有图纸的对象。
draw_colors(cr);
实际图形委托给draw_colors()方法。
cr.set_source_rgb(0.2, 0.23, 0.9);
set_source_rgb()方法为 Cario 上下文设置颜色。 该方法的三个参数是颜色强度值。
cr.rectangle(10, 15, 90, 60);
我们画一个矩形。 前两个参数是矩形左上角的 x,y 坐标。 最后两个参数是矩形的宽度和高度。
cr.fill();
我们用当前颜色填充矩形的内部。

图:颜色
基本形状
下一个示例将一些基本形状绘制到窗口上。
#!/usr/bin/seed
/*
ZetCode JavaScript GTK tutorial
This code example draws basic shapes
with the Cairo library
author: Jan Bodnar
website: www.zetcode.com
last modified: July 2011
*/
Gtk = imports.gi.Gtk;
cairo = imports.cairo;
Gtk.init(null, null);
Example = new GType({
parent: Gtk.Window.type,
name: "Example",
init: function ()
{
init_ui(this);
function init_ui(w) {
w.signal.hide.connect(Gtk.main_quit);
w.set_default_size(390, 240);
w.set_title("Basic shapes");
w.set_position(Gtk.WindowPosition.CENTER);
var darea = new Gtk.DrawingArea();
darea.signal.expose_event.connect(on_expose);
w.add(darea);
w.show_all();
}
function on_expose(darea) {
var cr = new cairo.Context.from_drawable(darea.window);
draw_colors(cr);
}
function draw_colors(cr) {
cr.set_source_rgb(0.6, 0.6, 0.6);
cr.rectangle(20, 20, 120, 80);
cr.rectangle(180, 20, 80, 80);
cr.fill();
cr.arc(330, 60, 40, 0, 2*Math.PI);
cr.fill();
cr.arc(90, 160, 40, Math.PI/4, Math.PI);
cr.fill();
cr.translate(220, 180);
cr.scale(1, 0.7);
cr.arc(0, 0, 50, 0, 2*Math.PI);
cr.fill();
}
}
});
var window = new Example();
Gtk.main();
在此示例中,我们将创建一个矩形,一个正方形,一个圆形,一个弧形和一个椭圆形。 我们用蓝色绘制轮廓,内部用白色绘制。
cr.rectangle(20, 20, 120, 80);
cr.rectangle(180, 20, 80, 80);
cr.fill();
这些线绘制一个矩形和一个正方形。
cr.arc(330, 60, 40, 0, 2*Math.PI);
cr.fill();
此处arc()方法绘制一个完整的圆。
cr.translate(220, 180);
cr.scale(1, 0.7);
cr.arc(0, 0, 50, 0, 2*Math.PI);
cr.fill();
translate()方法将对象移动到特定点。 如果要绘制椭圆形,请先进行一些缩放。 在这里scale()方法缩小 y 轴。

图:基本形状
透明矩形
透明性是指能够透视材料的质量。 了解透明度的最简单方法是想象一块玻璃或水。 从技术上讲,光线可以穿过玻璃,这样我们就可以看到玻璃后面的物体。
在计算机图形学中,我们可以使用 alpha 合成来实现透明效果。 Alpha 合成是将图像与背景组合以创建部分透明外观的过程。 合成过程使用 Alpha 通道。 (wikipedia.org,answers.com)
#!/usr/bin/seed
/*
ZetCode JavaScript GTK tutorial
This program shows transparent
rectangles using Cairo
author: Jan Bodnar
website: www.zetcode.com
last modified: July 2011
*/
Gtk = imports.gi.Gtk;
cairo = imports.cairo;
Gtk.init(null, null);
Example = new GType({
parent: Gtk.Window.type,
name: "Example",
init: function ()
{
init_ui(this);
function init_ui(w) {
w.signal.hide.connect(Gtk.main_quit);
w.set_default_size(590, 90);
w.set_title("Transparent rectangles");
w.set_position(Gtk.WindowPosition.CENTER);
var darea = new Gtk.DrawingArea();
darea.signal.expose_event.connect(on_expose);
w.add(darea);
w.show_all();
}
function on_expose(darea) {
var cr = new cairo.Context.from_drawable(darea.window);
draw_rectangles(cr);
}
function draw_rectangles(cr) {
for (var i=1; i<=10; i++) {
cr.set_source_rgba(0, 0, 1, i*0.1);
cr.rectangle(50*i, 20, 40, 40);
cr.fill();
}
}
}
});
var window = new Example();
Gtk.main();
在示例中,我们将绘制十个具有不同透明度级别的矩形。
cr.set_source_rgba(0, 0, 1, i*0.1);
set_source_rgba()方法的最后一个参数是 alpha 透明度。

图:透明矩形
在 JavaScript GTK 教程的这一章中,我们使用 Cairo 库进行绘图。
Qt5 中的容器
在 Qt5 教程的这一部分中,我们将讨论 Qt5 中的容器。 提及以下容器:QVector,QList,QStringList,QSet和QMap。
容器是通用类,用于将给定类型的项目存储在内存中。 C++ 具有标准模板库(STL),该模板库具有自己的容器。 对于 Qt,我们可以使用 Qt 容器或 STL 容器。
有两种容器:顺序容器和关联容器。 顺序容器一个接一个地存储项目,而关联容器存储键值对。 QList,QVector,QLinkedList属于顺序容器; QMap和QHash是关联容器的示例。
QVector
QVector是提供动态数组的模板类。 它将项目存储在相邻的内存位置,并提供基于索引的快速访问。 对于大向量,插入操作较慢,建议改用QList容器。
myvector.cpp
#include <QVector>
#include <QTextStream>
int main(void) {
QTextStream out(stdout);
QVector<int> vals = {1, 2, 3, 4, 5};
out << "The size of the vector is: " << vals.size() << endl;
out << "The first item is: " << vals.first() << endl;
out << "The last item is: " << vals.last() << endl;
vals.append(6);
vals.prepend(0);
out << "Elements: ";
for (int val : vals) {
out << val << " ";
}
out << endl;
return 0;
}
该示例使用整数向量。
QVector<int> vals = {1, 2, 3, 4, 5};
创建一个整数向量。
out << "The size of the vector is: " << vals.size() << endl;
size()方法给出向量的大小-向量中的项数。
out << "The first item is: " << vals.first() << endl;
使用first()方法检索第一项。
out << "The last item is: " << vals.last() << endl;
使用last()方法可以找到向量的最后一项。
vals.append(6);
append()方法将值插入向量的末尾。
vals.prepend(0);
prepend()方法将值插入向量的开头。
for (int val : vals) {
out << val << " ";
}
我们遍历for循环中的向量并打印其内容。
输出:
$ ./myvector
The size of the vector is: 5
The first item is: 1
The last item is: 5
Elements: 0 1 2 3 4 5 6
QList
QList是用于创建元素列表的容器。 与QVector相似。 它存储值列表,并提供基于索引的快速访问以及快速插入和删除。 它是 Qt 中最常用的容器之一。
mylist.cpp
#include <QTextStream>
#include <QList>
#include <algorithm>
int main(void) {
QTextStream out(stdout);
QList<QString> authors = {"Balzac", "Tolstoy",
"Gulbranssen", "London"};
for (int i=0; i < authors.size(); ++i) {
out << authors.at(i) << endl;
}
authors << "Galsworthy" << "Sienkiewicz";
out << "***********************" << endl;
std::sort(authors.begin(), authors.end());
out << "Sorted:" << endl;
for (QString author : authors) {
out << author << endl;
}
}
该示例介绍了QList容器。
QList<QString> authors = {"Balzac", "Tolstoy",
"Gulbranssen", "London"};
创建一个QList容器。 它存储作家的姓名。
for (int i=0; i < authors.size(); ++i) {
out << authors.at(i) << endl;
}
在for循环中,我们遍历容器并打印其元素。 at()方法返回给定索引处的项目。
authors << "Galsworthy" << "Sienkiewicz";
<<运算符用于在列表中插入两个新项目。
std::sort(authors.begin(), authors.end());
std::sort()方法按升序对列表进行排序。
out << "Sorted:" << endl;
for (QString author : authors) {
out << author << endl;
}
现在我们打印排序列表。
输出:
$ ./mylist
Balzac
Tolstoy
Gulbranssen
London
***********************
Sorted:
Balzac
Galsworthy
Gulbranssen
London
Sienkiewicz
Tolstoy
QStringList
QStringList是提供字符串列表的便捷容器。 它具有基于索引的快速访问以及快速的插入和删除。
mystringlist.cpp
#include <QTextStream>
#include <QList>
int main(void) {
QTextStream out(stdout);
QString string = "coin, book, cup, pencil, clock, bookmark";
QStringList items = string.split(",");
QStringListIterator it(items);
while (it.hasNext()) {
out << it.next().trimmed() << endl;
}
}
在示例中,我们从一个字符串创建一个字符串列表,并将元素打印到控制台中。
QString string = "coin, book, cup, pencil, clock, bookmark";
QStringList items = string.split(",");
QString的split()方法根据提供的分隔符将字符串切成子字符串。 子字符串在列表中返回。
QStringListIterator it(items);
QStringListIterator为QStringList提供了 Java 样式的常迭代器。
while (it.hasNext()) {
out << it.next().trimmed() << endl;
}
使用创建的迭代器,我们将列表的元素打印到终端。 trimmed()方法可修剪字符串元素中的空白。
输出:
$ ./mystringlist
coin
book
cup
pencil
clock
bookmark
QSet
QSet提供具有快速查找功能的单值数学集。 值以未指定的顺序存储。
myset.cpp
#include <QSet>
#include <QList>
#include <QTextStream>
#include <algorithm>
int main(void) {
QTextStream out(stdout);
QSet<QString> cols1 = {"yellow", "red", "blue"};
QSet<QString> cols2 = {"blue", "pink", "orange"};
out << "There are " << cols1.size() << " values in the set" << endl;
cols1.insert("brown");
out << "There are " << cols1.size() << " values in the set" << endl;
cols1.unite(cols2);
out << "There are " << cols1.size() << " values in the set" << endl;
for (QString val : cols1) {
out << val << endl;
}
QList<QString> lcols = cols1.values();
std::sort(lcols.begin(), lcols.end());
out << "*********************" << endl;
out << "Sorted:" << endl;
for (QString val : lcols) {
out << val << endl;
}
return 0;
}
在示例中,QSet用于存储颜色。 多次指定一种颜色值没有意义。
QSet<QString> cols1 = {"yellow", "red", "blue"};
QSet<QString> cols2 = {"blue", "pink", "orange"};
我们有两组颜色值。 两组中都有蓝色。
out << "There are " << cols1.size() << " values in the set" << endl;
size()方法返回集合的大小。
cols1.insert("brown");
我们使用insert()方法向集合中添加新值。
cols1.unite(cols2);
unite((方法执行两个集合的并集。 cols1设置将具有从cols2设置插入的所有尚不存在的项目。 在我们的案例中,除了蓝色以外的所有颜色。
for (QString val : cols1) {
out << val << endl;
}
使用for循环,我们打印cols1集中的所有项目。
QList<QString> lcols = cols1.values();
std::sort(lcols.begin(), lcols.end());
不支持对集合进行排序。 我们可以创建一个列表并对其进行排序。 values()方法返回一个新的QList,其中包含集合中的元素。 QList中元素的顺序未定义。
输出:
$ ./myset
There are 3 values in the set
There are 4 values in the set
There are 6 values in the set
pink
orange
brown
blue
yellow
red
*********************
Sorted:
blue
brown
orange
pink
red
yellow
QMap
QMap是一个存储键值对的关联数组(字典)。 它提供与键关联的值的快速查找。
myqmap.cpp
#include <QTextStream>
#include <QMap>
int main(void) {
QTextStream out(stdout);
QMap<QString, int> items = { {"coins", 5}, {"books", 3} };
items.insert("bottles", 7);
QList<int> values = items.values();
out << "Values:" << endl;
for (int val : values) {
out << val << endl;
}
QList<QString> keys = items.keys();
out << "Keys:" << endl;
for (QString key : keys) {
out << key << endl;
}
QMapIterator<QString, int> it(items);
out << "Pairs:" << endl;
while (it.hasNext()) {
it.next();
out << it.key() << ": " << it.value() << endl;
}
}
在示例中,我们有一个字典,在其中将字符串键映射到整数值。
QMap<QString, int> items = { {"coins", 5}, {"books", 3} };
创建了QMap。 它有两对。
items.insert("bottles", 7);
使用insert()方法插入一对新的对。
QList<int> values = items.values();
out << "Values:" << endl;
for (int val : values) {
out << val << endl;
}
我们获取字典的所有值并将其打印到控制台。 values()方法返回映射值列表。
QList<QString> keys = items.keys();
out << "Keys:" << endl;
for (QString key : keys) {
out << key << endl;
}
同样,我们打印字典的所有键。 keys()方法返回一个列表,其中包含字典中的所有键。
QMapIterator<QString, int> it(items);
QMapIterator是QMap的 Java 样式的迭代器。 它可用于遍历映射元素。
while (it.hasNext()) {
it.next();
out << it.key() << ": " << it.value() << endl;
}
在迭代器的帮助下,我们遍历了映射的所有元素。 key()方法返回当前键,value()方法返回当前值。
输出:
$ ./myqmap
Values:
3
7
5
Keys:
books
bottles
coins
Pairs:
books: 3
bottles: 7
coins: 5
自定义类排序
在下面的示例中,我们将在QList中对自定义类的对象进行排序。
book.h
class Book {
public:
Book(QString, QString);
QString getAuthor() const;
QString getTitle() const;
private:
QString author;
QString title;
};
这是我们的自定义Book类的头文件。
book.cpp
#include <QString>
#include "book.h"
Book::Book(QString auth, QString tit) {
author = auth;
title = tit;
}
QString Book::getAuthor() const {
return author;
}
QString Book::getTitle() const {
return title;
}
这是Book类的实现; 我们有两种访问器方法。
sortcustomclass.cpp
#include <QTextStream>
#include <QList>
#include <algorithm>
#include "book.h"
bool compareByTitle(const Book &b1, const Book &b2) {
return b1.getTitle() < b2.getTitle();
}
int main(void) {
QTextStream out(stdout);
QList<Book> books = {
Book("Jack London", "The Call of the Wild"),
Book("Honoré de Balzac", "Father Goriot"),
Book("Leo Tolstoy", "War and Peace"),
Book("Gustave Flaubert", "Sentimental education"),
Book("Guy de Maupassant", "Une vie"),
Book("William Shakespeare", "Hamlet")
};
std::sort(books.begin(), books.end(), compareByTitle);
for (Book book : books) {
out << book.getAuthor() << ": " << book.getTitle() << endl;
}
}
在示例中,我们创建了一些书本对象,并使用std::sort算法对其进行排序。
bool compareByTitle(const Book &b1, const Book &b2) {
return b1.getTitle() < b2.getTitle();
}
compareByTitle()是排序算法使用的比较功能。
std::sort(books.begin(), books.end(), compareByTitle);
std::sort算法按书名对列表中的书进行排序。
输出:
$ ./sortcustomclass
Honoré de Balzac: Father Goriot
William Shakespeare: Hamlet
Gustave Flaubert: Sentimental education
Jack London: The Call of the Wild
Guy de Maupassant: Une vie
Leo Tolstoy: War and Peace
在本章中,我们使用了 Qt 的容器。
在 Qt5 中处理文件和目录
在 Qt5 C++ 编程教程的这一部分中,我们处理文件和目录。
QFile,QDir和QFileInfo是在 Qt5 中处理文件的基本类。 QFile提供了用于读取和写入文件的接口。 QDir提供对目录结构及其内容的访问。 QFileInfo提供与系统无关的文件信息,包括文件名和在文件系统中的位置,访问时间和修改时间,权限或文件所有权。
文件大小
在下一个示例中,我们确定文件的大小。
file_size.cpp
#include <QTextStream>
#include <QFileInfo>
int main(int argc, char *argv[]) {
QTextStream out(stdout);
if (argc != 2) {
qWarning("Usage: file_size file");
return 1;
}
QString filename = argv[1];
if (!QFile(filename).exists()) {
qWarning("The file does not exist");
return 1;
}
QFileInfo fileinfo(filename);
qint64 size = fileinfo.size();
QString str = "The size is: %1 bytes";
out << str.arg(size) << endl;
}
文件的大小由QFileInfo的size()方法确定。
QString filename = argv[1];
文件名作为参数传递给程序。
if (!QFile(filename).exists()) {
qWarning("The file does not exist");
return 1;
}
使用QFile类的exists()方法检查文件的存在。 如果不存在,我们将发出警告并终止程序。
QFileInfo fileinfo(filename);
创建QFileInfo的实例。
qint64 size = fileinfo.size();
文件大小由size()方法确定。 qint64是在 Qt 支持的所有平台上保证为 64 位的类型。
QString str = "The size is: %1 bytes";
out << str.arg(size) << endl;
结果将打印到控制台。
输出:
$ ./file_size Makefile
The size is: 28029 bytes
读取文件内容
为了读取文件的内容,我们必须首先打开文件进行读取。 然后创建一个输入文件流; 从该流中读取数据。
read_file.cpp
#include <QTextStream>
#include <QFile>
int main(void) {
QTextStream out(stdout);
QFile file("colours");
if (!file.open(QIODevice::ReadOnly)) {
qWarning("Cannot open file for reading");
return 1;
}
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine();
out << line << endl;
}
file.close();
}
该示例从colours文件读取数据。 该文件包含八种颜色的名称。
QFile file("colours");
创建QFile对象的实例。
if (!file.open(QIODevice::ReadOnly)) {
qWarning("Cannot open file for reading");
return 1;
}
QFile的open()方法以只读模式打开文件。 如果该方法失败,我们将发出警告并终止程序。
QTextStream in(&file);
创建输入流。 QTextStream接收文件句柄。 将从该流中读取数据。
while (!in.atEnd()) {
QString line = in.readLine();
out << line << endl;
}
在while循环中,我们逐行读取文件,直到文件结束。 如果没有更多数据要从流中读取,则atEnd()方法返回true。 readLine()方法从流中读取一行。
file.close();
close()方法刷新数据并关闭文件句柄。
输出:
$ ./read_file colours
red
green
blue
yellow
brown
white
black
violet
写入文件
为了写入文件,我们在写入模式下打开文件,创建定向到该文件的输出流,并使用写入运算符写入该流。
write2file.cpp
#include <QTextStream>
#include <QFile>
int main(void) {
QTextStream out(stdout);
QString filename = "distros";
QFile file(filename);
if (file.open(QIODevice::WriteOnly)) {
QTextStream out(&file);
out << "Xubuntu" << endl;
out << "Arch" << endl;
out << "Debian" << endl;
out << "Redhat" << endl;
out << "Slackware" << endl;
} else {
qWarning("Could not open file");
}
file.close();
}
该示例将五个 Linux 发行版的名称写入名为distros的文件名。
QString filename = "distros";
QFile file(filename);
使用提供的文件名创建QFile对象。
if (file.open(QIODevice::WriteOnly)) {
使用open()方法,我们以只写方法打开文件。
QTextStream out(&file);
该行创建一个在文件句柄上运行的QTextStream。 换句话说,要写入的数据流被定向到文件。
out << "Xubuntu" << endl;
out << "Arch" << endl;
out << "Debian" << endl;
out << "Redhat" << endl;
out << "Slackware" << endl;
数据通过<<运算符写入。
file.close();
最后,文件句柄被关闭。
输出:
$ ./write2file
$ cat distros
Xubuntu
Arch
Debian
Redhat
Slackware
复制文件
复制文件时,我们将使用文件名或文件系统的不同位置来精确复制该文件。
copy_file.cpp
#include <QTextStream>
#include <QFile>
int main(int argc, char *argv[]) {
QTextStream out(stdout);
if (argc != 3) {
qWarning("Usage: copyfile source destination");
return 1;
}
QString source = argv[1];
if (!QFile(source).exists()) {
qWarning("The source file does not exist");
return 1;
}
QString destin(argv[2]);
QFile::copy(source, destin);
}
该示例使用QFile::copy()方法创建提供的文件的副本。
if (argc != 3) {
qWarning("Usage: copyfile source destination");
return 1;
}
该程序有两个参数。 如果未给出,则以警告消息结尾。
QString source = argv[1];
从程序的命令行参数中,我们获得源文件的名称。
if (!QFile(source).exists()) {
qWarning("The source file does not exist");
return 1;
}
我们使用QFile的exists()方法检查源文件是否存在。 如果它不存在,我们将以警告消息终止该程序。
QString destin(argv[2]);
我们得到目标文件名。
QFile::copy(source, destin);
使用QFile::copy()方法复制源文件。 第一个参数是源文件名,第二个参数是目标文件名。
文件所有者和组
每个文件都有一个拥有者的用户。 文件也属于一组用户,以更好地管理和保护文件。
owner.cpp
#include <QTextStream>
#include <QFileInfo>
int main(int argc, char *argv[]) {
QTextStream out(stdout);
if (argc != 2) {
qWarning("Usage: owner file");
return 1;
}
QString filename = argv[1];
QFileInfo fileinfo(filename);
QString group = fileinfo.group();
QString owner = fileinfo.owner();
out << "Group: " << group << endl;
out << "Owner: " << owner << endl;
}
该示例打印给定文件的所有者和主要组。
QFileInfo fileinfo(filename);
创建QFileInfo类的实例。 它的参数是作为命令行参数给出的文件名。
QString group = fileinfo.group();
文件的主要组是通过QFileInfo的group()方法确定的。
QString owner = fileinfo.owner();
文件的所有者通过QFileInfo的owner()方法确定。
输出:
$ touch myfile
$ ./owner myfile
Group: janbodnar
Owner: janbodnar
上次读取,上次修改
文件存储有关上次读取或修改它们的信息。 要获取此信息,我们使用QFileInfo类。
file_times.cpp
#include <QTextStream>
#include <QFileInfo>
#include <QDateTime>
int main(int argc, char *argv[]) {
QTextStream out(stdout);
if (argc != 2) {
qWarning("Usage: file_times file");
return 1;
}
QString filename = argv[1];
QFileInfo fileinfo(filename);
QDateTime last_rea = fileinfo.lastRead();
QDateTime last_mod = fileinfo.lastModified();
out << "Last read: " << last_rea.toString() << endl;
out << "Last modified: " << last_mod.toString() << endl;
}
该示例打印给定文件的最后读取时间和最后修改时间。
QFileInfo fileinfo(filename);
QFileInfo对象已创建。
QDateTime last_rea = fileinfo.lastRead();
lastRead()方法返回上次读取(访问)文件的日期和时间。
QDateTime last_mod = fileinfo.lastModified();
lastModified()方法返回上次修改文件的日期和时间。
输出:
$ ./file_times Makefile
Last read: Sun Nov 1 17:54:31 2015
Last modified: Sun Nov 1 17:54:30 2015
处理目录
QDir类具有用于处理目录的方法。
dirs.cpp
#include <QTextStream>
#include <QDir>
int main(void) {
QTextStream out(stdout);
QDir dir;
if (dir.mkdir("mydir")) {
out << "mydir successfully created" << endl;
}
dir.mkdir("mydir2");
if (dir.exists("mydir2")) {
dir.rename("mydir2", "newdir");
}
dir.mkpath("temp/newdir");
}
在示例中,我们提供了四种使用目录的方法。
if (dir.mkdir("mydir")) {
out << "mydir successfully created" << endl;
}
mkdir()方法创建一个目录。 如果目录创建成功,则返回true。
if (dir.exists("mydir2")) {
dir.rename("mydir2", "newdir");
}
exists()检查目录是否存在。 rename()方法重命名目录。
dir.mkpath("temp/newdir");
mkpath()一键创建一个新目录和所有必要的父目录。
特殊路径
文件系统中有一些特殊的路径。 例如主目录或根目录。 QDir类用于获取系统中的特殊路径。
special_paths.cpp
#include <QTextStream>
#include <QDir>
int main(void) {
QTextStream out(stdout);
out << "Current path:" << QDir::currentPath() << endl;
out << "Home path:" << QDir::homePath() << endl;
out << "Temporary path:" << QDir::tempPath() << endl;
out << "Rooth path:" << QDir::rootPath() << endl;
}
该示例打印四个特殊路径。
out << "Current path:" << QDir::currentPath() << endl;
当前的工作目录使用QDir::currentPath()方法检索。
out << "Home path:" << QDir::homePath() << endl;
使用QDir::homePath()方法返回主目录。
out << "Temporary path:" << QDir::tempPath() << endl;
使用QDir::tempPath()方法检索临时目录。
out << "Rooth path:" << QDir::rootPath() << endl;
根目录通过QDir::rootPath()方法返回。
输出:
$ ./special_paths
Current path:/home/janbodnar/prog/qt4/files/special_paths
Home path:/home/janbodnar
Temporary path:/tmp
Rooth path:/
文件路径
文件由文件名和路径标识。 路径由文件名,基本名和后缀组成。
file_path.cpp
#include <QTextStream>
#include <QFileInfo>
int main(int argc, char *argv[]) {
QTextStream out(stdout);
if (argc != 2) {
out << "Usage: file_times file" << endl;
return 1;
}
QString filename = argv[1];
QFileInfo fileinfo(filename);
QString absPath = fileinfo.absoluteFilePath();
QString baseName = fileinfo.baseName();
QString compBaseName = fileinfo.completeBaseName();
QString fileName = fileinfo.fileName();
QString suffix = fileinfo.suffix();
QString compSuffix = fileinfo.completeSuffix();
out << "Absolute file path: " << absPath << endl;
out << "Base name: " << baseName << endl;
out << "Complete base name: " << compBaseName << endl;
out << "File name: " << fileName << endl;
out << "Suffix: " << suffix << endl;
out << "Whole suffix: " << compSuffix << endl;
}
在示例中,我们使用几种方法来打印文件路径及其给定文件名的一部分。
QFileInfo fileinfo(filename);
文件路径是使用QFileInfo类标识的。
QString absPath = fileinfo.absoluteFilePath();
absoluteFilePath()方法返回包含文件名的绝对路径。
QString baseName = fileinfo.baseName();
baseName()方法返回基本名称-没有路径的文件名称。
QString compBaseName = fileinfo.completeBaseName();
completeBaseName()方法返回完整的基本名称-文件中的所有字符,直到(但不包括)最后一个点字符。
QString fileName = fileinfo.fileName();
fileName()方法返回文件名,该文件名是基本名称和扩展名。
QString suffix = fileinfo.suffix();
suffix()方法返回文件结尾,该结尾由文件中所有字符组成,该文件之后(但不包括)最后一个点字符。
QString compSuffix = fileinfo.completeSuffix();
文件结尾可能由几部分组成。 completeSuffix()方法返回第一个点字符之后(但不包括)后的文件中的所有字符。
输出:
$ ./file_path ~/Downloads/qt-everywhere-opensource-src-5.5.1.tar.gz
Absolute file path: /home/janbodnar/Downloads/qt-everywhere-opensource-src-5.5.1.tar.gz
Base name: qt-everywhere-opensource-src-5
Complete base name: qt-everywhere-opensource-src-5.5.1.tar
File name: qt-everywhere-opensource-src-5.5.1.tar.gz
Suffix: gz
Whole suffix: 5.1.tar.gz
权限
文件系统中的文件具有保护系统。 文件带有标志,这些标志确定谁可以访问和修改它们。 QFile::permissions()方法返回有关文件的 OR-ED 标志的枚举。
permissions.cpp
#include <QTextStream>
#include <QFile>
int main(int argc, char *argv[]) {
QTextStream out(stdout);
if (argc != 2) {
out << "Usage: permissions file" << endl;
return 1;
}
QString filename = argv[1];
QFile::Permissions ps = QFile::permissions(filename);
QString fper;
if (ps & QFile::ReadOwner) {
fper.append('r');
} else {
fper.append('-');
}
if (ps & QFile::WriteOwner) {
fper.append('w');
} else {
fper.append('-');
}
if (ps & QFile::ExeOwner) {
fper.append('x');
} else {
fper.append('-');
}
if (ps & QFile::ReadGroup) {
fper.append('r');
} else {
fper.append('-');
}
if (ps & QFile::WriteGroup) {
fper.append('w');
} else {
fper.append('-');
}
if (ps & QFile::ExeGroup) {
fper.append('x');
} else {
fper.append('-');
}
if (ps & QFile::ReadOther) {
fper.append('r');
} else {
fper.append('-');
}
if (ps & QFile::WriteOther) {
fper.append('w');
} else {
fper.append('-');
}
if (ps & QFile::ExeOther) {
fper.append('x');
} else {
fper.append('-');
}
out << fper << endl;
}
该示例为给定文件生成类似 Unix 的权限列表。 有几种可能的用户类型:所有者,文件所属的组以及其余的称为其他用户。 前三个位置属于文件的所有者,后三个位置属于文件的组,后三个字符属于其他字符。 权限共有四种:读取(r),写入或修改(w),执行(x)和无权限(-)。
QFile::Permissions ps = QFile::permissions(filename);
通过QFile::permissions()方法,我们获得了权限标志的枚举。
QString fper;
该字符串是根据给定的权限动态构建的。
if (ps & QFile::ReadOwner) {
fper.append('r');
} else {
fper.append('-');
}
我们使用&运算符确定返回的枚举是否包含QFile::ReadOwner标志。
输出:
$ ./permissions Makefile
rw-rw-r--
文件所属的所有者和用户组有权读取和修改文件。 其他用户有权读取该文件。 由于该文件不是可执行文件,因此没有执行该文件的权限。
列出目录内容
在下面的示例中,我们显示给定目录的内容。
list_dir.cpp
#include <QTextStream>
#include <QFileInfo>
#include <QDir>
int main(int argc, char *argv[]) {
QTextStream out(stdout);
if (argc != 2) {
qWarning("Usage: list_dir directory");
return 1;
}
QString directory = argv[1];
QDir dir(directory);
if (!dir.exists()) {
qWarning("The directory does not exist");
return 1;
}
dir.setFilter(QDir::Files | QDir::AllDirs);
dir.setSorting(QDir::Size | QDir::Reversed);
QFileInfoList list = dir.entryInfoList();
int max_size = 0;
foreach (QFileInfo finfo, list) {
QString name = finfo.fileName();
int size = name.size();
if (size > max_size) {
max_size = size;
}
}
int len = max_size + 2;
out << QString("Filename").leftJustified(len).append("Bytes") << endl;
for (int i = 0; i < list.size(); ++i) {
QFileInfo fileInfo = list.at(i);
QString str = fileInfo.fileName().leftJustified(len);
str.append(QString("%1").arg(fileInfo.size()));
out << str << endl;
}
return 0;
}
要列出目录的内容,我们使用QDir类及其entryInfoList()方法。 文件列表按其大小反向排序,并且排列整齐。 有两列; 第一列包含文件名,第二列包含文件大小。
QDir dir(directory);
创建具有给定目录名称的QDir对象。
dir.setFilter(QDir::Files | QDir::AllDirs);
setFilter()方法指定entryInfoList()方法应返回的文件类型。
dir.setSorting(QDir::Size | QDir::Reversed);
setSorting()方法指定entryInfoList()方法使用的排序顺序。
QFileInfoList list = dir.entryInfoList();
entryInfoList()方法返回目录中所有文件和目录的QFileInfo对象的列表,并通过过滤和排序方法进行过滤和排序。 QFileInfoList是QList<QFileInfo>的同义词。
foreach (QFileInfo finfo, list) {
QString name = finfo.fileName();
int size = name.size();
if (size > max_size) {
max_size = size;
}
}
我们遍历列表并确定最大文件名大小。 需要此信息来整齐地组织输出。
int len = max_size + 2;
我们在列的长度上再加上两个空格。
out << QString("Filename").leftJustified(len).append("Bytes") << endl;
在这里,我们打印列名。 leftJustified()方法返回给定大小的字符串,该字符串左对齐并在其右边用填充字符(默认为空格)填充。
for (int i = 0; i < list.size(); ++i) {
QFileInfo fileInfo = list.at(i);
QString str = fileInfo.fileName().leftJustified(len);
str.append(QString("%1").arg(fileInfo.size()));
out << str << endl;
}
我们浏览文件列表并打印它们的名称和大小。 第一列保持对齐,并在必要时用空格填充; 仅将第二列添加到该行的末尾。
输出:
$ ./list_dir .
Filename Bytes
list_dir.pro 291
list_dir.cpp 1092
.. 4096
. 4096
list_dir.o 10440
list_dir 19075
Makefile 28369
这是示例的示例输出。
在本章中,我们使用文件和目录。
Qt5 中的第一个程序
在 Qt5 C++ 编程教程的这一部分中,我们创建了第一个程序。
我们显示一个应用图标,一个工具提示和各种鼠标光标。 我们在屏幕上居中放置一个窗口,并介绍信号和槽机制。
简单的例子
我们从一个非常简单的例子开始。
simple.cpp
#include <QApplication>
#include <QWidget>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget window;
window.resize(250, 150);
window.setWindowTitle("Simple example");
window.show();
return app.exec();
}
该示例在屏幕上显示一个基本窗口。
#include <QApplication>
#include <QWidget>
我们包括必要的头文件。
QApplication app(argc, argv);
这是应用对象。 每个 Qt5 应用都必须创建此对象。 (控制台应用除外。)
QWidget window;
这是我们的主要小部件。
window.resize(250, 150);
window.setWindowTitle("Simple example");
window.show();
在这里,我们调整窗口小部件的大小并为我们的主窗口设置标题。 在这种情况下,QWidget是我们的主窗口。 最后,我们在屏幕上显示小部件。
return app.exec();
exec()方法启动应用的主循环。

图:简单 example
工具提示
工具提示是有关应用中项目的特定提示。 以下示例将演示如何在 Qt5 编程库中创建工具提示。
tooltip.cpp
#include <QApplication>
#include <QWidget>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget window;
window.resize(250, 150);
window.move(300, 300);
window.setWindowTitle("ToolTip");
window.setToolTip("QWidget");
window.show();
return app.exec();
}
该示例显示了主QWidget的工具提示。
window.setWindowTitle("ToolTip");
我们使用setToolTip()方法为QWidget小部件设置了工具提示。

图:工具提示
应用图标
在下一个示例中,我们显示应用图标。 大多数窗口管理器在标题栏的左上角以及任务栏上都显示图标。
icon.cpp
#include <QApplication>
#include <QWidget>
#include <QIcon>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget window;
window.resize(250, 150);
window.setWindowTitle("Icon");
window.setWindowIcon(QIcon("web.png"));
window.show();
return app.exec();
}
窗口左上角显示一个图标。
window.setWindowIcon(QIcon("web.png"));
为了显示图标,我们使用setWindowIcon()方法和QIcon类。 该图标是位于当前工作目录中的一个小 PNG 文件。

图:图标
光标
光标是一个小图标,指示鼠标指针的位置。 在下一个示例中,将显示我们可以在程序中使用的各种游标。
cursors.cpp
#include <QApplication>
#include <QWidget>
#include <QFrame>
#include <QGridLayout>
class Cursors : public QWidget {
public:
Cursors(QWidget *parent = 0);
};
Cursors::Cursors(QWidget *parent)
: QWidget(parent) {
QFrame *frame1 = new QFrame(this);
frame1->setFrameStyle(QFrame::Box);
frame1->setCursor(Qt::SizeAllCursor);
QFrame *frame2 = new QFrame(this);
frame2->setFrameStyle(QFrame::Box);
frame2->setCursor(Qt::WaitCursor);
QFrame *frame3 = new QFrame(this);
frame3->setFrameStyle(QFrame::Box);
frame3->setCursor(Qt::PointingHandCursor);
QGridLayout *grid = new QGridLayout(this);
grid->addWidget(frame1, 0, 0);
grid->addWidget(frame2, 0, 1);
grid->addWidget(frame3, 0, 2);
setLayout(grid);
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Cursors window;
window.resize(350, 150);
window.setWindowTitle("Cursors");
window.show();
return app.exec();
}
在此示例中,我们使用三个框架。 每个帧都有一个不同的光标集。
QFrame *frame1 = new QFrame(this);
QFrame小部件已创建。
frame1->setFrameStyle(QFrame::Box);
我们使用setFrameStyle()方法设置框架样式。 这样我们可以看到框架的边界。
frame1->setCursor(Qt::SizeAllCursor);
使用setCursor()方法将光标设置到该帧。
QGridLayout *grid = new QGridLayout(this);
grid->addWidget(frame1, 0, 0);
grid->addWidget(frame2, 0, 1);
grid->addWidget(frame3, 0, 2);
setLayout(grid);
这会将所有帧分组为一行。 我们将在布局管理一章中详细讨论这一点。
QButton
在下一个代码示例中,我们在窗口上显示一个按钮。 通过单击按钮,我们关闭应用。
pushbutton.cpp
#include <QApplication>
#include <QWidget>
#include <QPushButton>
class MyButton : public QWidget {
public:
MyButton(QWidget *parent = 0);
};
MyButton::MyButton(QWidget *parent)
: QWidget(parent) {
QPushButton *quitBtn = new QPushButton("Quit", this);
quitBtn->setGeometry(50, 40, 75, 30);
connect(quitBtn, &QPushButton::clicked, qApp, &QApplication::quit);
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MyButton window;
window.resize(250, 150);
window.setWindowTitle("QPushButton");
window.show();
return app.exec();
}
在此代码示例中,我们首次使用信号和槽的概念。
QPushButton *quitBtn = new QPushButton("Quit", this);
quitBtn->setGeometry(50, 40, 75, 30);
我们创建一个新的QPushButton。 我们手动调整其大小,然后使用setGeometry()方法将其放置在窗口中。
connect(quitBtn, &QPushButton::clicked, qApp, &QApplication::quit);
当我们点击按钮时,会产生一个clicked信号。 时隙是对信号做出反应的方法。 在我们的情况下,它是主应用对象的quit槽。 qApp是指向应用对象的全局指针。 它在QApplication头文件中定义。

图:QPushButton
PlusMinus
我们将在本节完成,展示小部件如何进行通信。 该代码分为三个文件。
plusminus.h
#pragma once
#include <QWidget>
#include <QApplication>
#include <QPushButton>
#include <QLabel>
class PlusMinus : public QWidget {
Q_OBJECT
public:
PlusMinus(QWidget *parent = 0);
private slots:
void OnPlus();
void OnMinus();
private:
QLabel *lbl;
};
这是示例的头文件。 在此文件中,我们定义了两个槽和一个标签小部件。
class PlusMinus : public QWidget {
Q_OBJECT
...
Q_OBJECT宏必须包含在声明自己的信号和槽的类中。
plusminus.cpp
#include "plusminus.h"
#include <QGridLayout>
PlusMinus::PlusMinus(QWidget *parent)
: QWidget(parent) {
QPushButton *plsBtn = new QPushButton("+", this);
QPushButton *minBtn = new QPushButton("-", this);
lbl = new QLabel("0", this);
QGridLayout *grid = new QGridLayout(this);
grid->addWidget(plsBtn, 0, 0);
grid->addWidget(minBtn, 0, 1);
grid->addWidget(lbl, 1, 1);
setLayout(grid);
connect(plsBtn, &QPushButton::clicked, this, &PlusMinus::OnPlus);
connect(minBtn, &QPushButton::clicked, this, &PlusMinus::OnMinus);
}
void PlusMinus::OnPlus() {
int val = lbl->text().toInt();
val++;
lbl->setText(QString::number(val));
}
void PlusMinus::OnMinus() {
int val = lbl->text().toInt();
val--;
lbl->setText(QString::number(val));
}
我们有两个按钮和一个标签小部件。 我们使用按钮增加或减少标签上显示的数字。
connect(plsBtn, &QPushButton::clicked, this, &PlusMinus::OnPlus);
connect(minBtn, &QPushButton::clicked, this, &PlusMinus::OnMinus);
在这里,我们将clicked信号连接到它们的槽。
void PlusMinus::OnPlus() {
int val = lbl->text().toInt();
val++;
lbl->setText(QString::number(val));
}
在OnPlus()方法中,我们确定标签的当前值。 标签窗口小部件显示一个字符串值,因此我们必须将其转换为整数。 我们增加数量并为标签设置新的文本。 我们将数字转换为字符串值。
main.cpp
#include "plusminus.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
PlusMinus window;
window.resize(300, 190);
window.setWindowTitle("Plus minus");
window.show();
return app.exec();
}
这是代码示例的主文件。

图:正负
在本章中,我们在 Qt5 中创建了第一个程序。
Qt5 中的菜单和工具栏
在 Qt5 C++ 编程教程的这一部分中,我们将讨论 Qt5 应用中的菜单和工具栏。
菜单栏是 GUI 应用的常见部分。 它是位于各个位置(称为菜单)的一组命令。 菜单将我们可以在应用中使用的命令分组。 使用工具栏可以快速访问最常用的命令。
简单菜单
第一个示例显示了一个简单的菜单。
simplemenu.h
#pragma once
#include <QMainWindow>
#include <QApplication>
class SimpleMenu : public QMainWindow {
public:
SimpleMenu(QWidget *parent = 0);
};
这是我们的代码示例的头文件。
simplemenu.cpp
#include "simplemenu.h"
#include <QMenu>
#include <QMenuBar>
SimpleMenu::SimpleMenu(QWidget *parent)
: QMainWindow(parent) {
QAction *quit = new QAction("&Quit", this);
QMenu *file;
file = menuBar()->addMenu("&File");
file->addAction(quit);
connect(quit, &QAction::triggered, qApp, QApplication::quit);
}
我们有一个菜单栏,一个菜单和一个动作。 为了使用菜单,我们必须从QMainWindow小部件继承。
QAction *quit = new QAction("&Quit", this);
此代码行创建一个QAction。 每个QMenu具有一个或多个动作对象。
QMenu *file;
file = menuBar()->addMenu("&File");
我们创建一个QMenu对象。
file->addAction(quit);
我们使用addAction()方法在菜单中放置一个动作。
connect(quit, &QAction::triggered, qApp, QApplication::quit);
当我们从菜单中选择此选项时,应用退出。
main.cpp
#include "simplemenu.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
SimpleMenu window;
window.resize(250, 150);
window.setWindowTitle("Simple menu");
window.show();
return app.exec();
}
主文件。

图:简单菜单
图标,快捷方式和分隔符
在以下示例中,我们将进一步增强以前的应用。 我们将图标添加到菜单,使用快捷方式和分隔符。
anothermenu.h
#pragma once
#include <QMainWindow>
#include <QApplication>
class AnotherMenu : public QMainWindow {
public:
AnotherMenu(QWidget *parent = 0);
};
该示例的头文件。
anothermenu.cpp
#include "anothermenu.h"
#include <QMenu>
#include <QMenuBar>
AnotherMenu::AnotherMenu(QWidget *parent)
: QMainWindow(parent) {
QPixmap newpix("new.png");
QPixmap openpix("open.png");
QPixmap quitpix("quit.png");
QAction *newa = new QAction(newpix, "&New", this);
QAction *open = new QAction(openpix, "&Open", this);
QAction *quit = new QAction(quitpix, "&Quit", this);
quit->setShortcut(tr("CTRL+Q"));
QMenu *file;
file = menuBar()->addMenu("&File");
file->addAction(newa);
file->addAction(open);
file->addSeparator();
file->addAction(quit);
qApp->setAttribute(Qt::AA_DontShowIconsInMenus, false);
connect(quit, &QAction::triggered, qApp, &QApplication::quit);
}
在我们的示例中,我们有一个包含三个动作的菜单。 如果我们选择退出操作,则实际上只有退出操作才可以执行某些操作。 我们还创建一个分隔符和CTRL+Q快捷方式,以终止应用。
QPixmap newpix("new.png");
QPixmap openpix("open.png");
QPixmap quitpix("quit.png");
这些是我们在菜单中使用的图像。 请注意,某些桌面环境可能不会在菜单中显示图像。
QAction *newa = new QAction(newpix, "&New", this);
QAction *open = new QAction(openpix, "&Open", this);
QAction *quit = new QAction(quitpix, "&Quit", this);
在此代码中,我们将QAction构造器与像素映射用作第一个参数。
quit->setShortcut(tr("CTRL+Q"));
在这里,我们创建键盘快捷键。 通过按下此快捷方式,我们将运行退出操作,该操作将退出应用。
file->addSeparator();
我们创建一个分隔符。 分隔符是一条水平线,使我们能够将菜单操作分组为一些逻辑组。
qApp->setAttribute(Qt::AA_DontShowIconsInMenus, false);
在某些环境中,默认情况下不显示菜单图标。 在这种情况下,我们可以禁用Qt::AA_DontShowIconsInMenus属性。
main.cpp
#include "anothermenu.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
AnotherMenu window;
window.resize(350, 200);
window.setWindowTitle("Another menu");
window.show();
return app.exec();
}
这是主文件。

图:另一个菜单示例
复选菜单
在下一个示例中,我们创建一个复选菜单。 这将是带有复选框的操作。 该选项可切换状态栏的可见性。
checkable.h
#pragma once
#include <QMainWindow>
#include <QApplication>
class Checkable : public QMainWindow {
Q_OBJECT
public:
Checkable(QWidget *parent = 0);
private slots:
void toggleStatusbar();
private:
QAction *viewst;
};
该示例的头文件。
checkable.cpp
#include "checkable.h"
#include <QMenu>
#include <QMenuBar>
#include <QStatusBar>
Checkable::Checkable(QWidget *parent)
: QMainWindow(parent) {
viewst = new QAction("&View statusbar", this);
viewst->setCheckable(true);
viewst->setChecked(true);
QMenu *file;
file = menuBar()->addMenu("&File");
file->addAction(viewst);
statusBar();
connect(viewst, &QAction::triggered, this, &Checkable::toggleStatusbar);
}
void Checkable::toggleStatusbar() {
if (viewst->isChecked()) {
statusBar()->show();
} else {
statusBar()->hide();
}
}
复选菜单项切换状态栏的可见性。
viewst = new QAction("&View statusbar", this);
viewst->setCheckable(true);
viewst->setChecked(true);
我们创建一个动作,并使用setCheckable()方法对其进行检查。 setChecked()方法进行检查。
if (viewst->isChecked()) {
statusBar()->show();
} else {
statusBar()->hide();
}
在toggleStatusbar()方法内部,我们确定菜单项是否已选中,并相应地隐藏或显示状态栏。
main.cpp
#include "checkable.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Checkable window;
window.resize(250, 150);
window.setWindowTitle("Checkable menu");
window.show();
return app.exec();
}
这是主文件。

图:可选菜单
QToolBar
QToolBar类提供了一个可移动面板,其中包含一组控件,这些控件可快速访问应用动作。
toolbar.h
#pragma once
#include <QMainWindow>
#include <QApplication>
class Toolbar : public QMainWindow {
Q_OBJECT
public:
Toolbar(QWidget *parent = 0);
};
该示例的头文件。
toolbar.cpp
#include "toolbar.h"
#include <QToolBar>
#include <QIcon>
#include <QAction>
Toolbar::Toolbar(QWidget *parent)
: QMainWindow(parent) {
QPixmap newpix("new.png");
QPixmap openpix("open.png");
QPixmap quitpix("quit.png");
QToolBar *toolbar = addToolBar("main toolbar");
toolbar->addAction(QIcon(newpix), "New File");
toolbar->addAction(QIcon(openpix), "Open File");
toolbar->addSeparator();
QAction *quit = toolbar->addAction(QIcon(quitpix),
"Quit Application");
connect(quit, &QAction::triggered, qApp, &QApplication::quit);
}
要创建工具栏,我们从QMainWindow小部件继承。
QToolBar *toolbar = addToolBar("main toolbar");
addToolBar()方法创建一个工具栏并返回指向它的指针。
toolbar->addAction(QIcon(newpix), "New File");
toolbar->addAction(QIcon(openpix), "Open File");
toolbar->addSeparator();
在这里,我们向工具栏添加了两个动作和一个分隔符。
main.cpp
#include "toolbar.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Toolbar window;
window.resize(300, 200);
window.setWindowTitle("QToolBar");
window.show();
return app.exec();
}
这是主文件。

图:QToolBar
应用框架
在 C++ Qt5 教程的这一部分的最后,我们创建了一个应用框架。 该示例主要基于QMainWindow小部件。
skeleton.h
#pragma once
#include <QMainWindow>
#include <QApplication>
class Skeleton : public QMainWindow {
Q_OBJECT
public:
Skeleton(QWidget *parent = 0);
};
该示例的头文件。
skeleton.cpp
#include "skeleton.h"
#include <QToolBar>
#include <QIcon>
#include <QAction>
#include <QMenu>
#include <QMenuBar>
#include <QStatusBar>
#include <QTextEdit>
Skeleton::Skeleton(QWidget *parent)
: QMainWindow(parent) {
QPixmap newpix("new.png");
QPixmap openpix("open.png");
QPixmap quitpix("quit.png");
QAction *quit = new QAction("&Quit", this);
QMenu *file;
file = menuBar()->addMenu("&File");
file->addAction(quit);
connect(quit, &QAction::triggered, qApp, &QApplication::quit);
QToolBar *toolbar = addToolBar("main toolbar");
toolbar->addAction(QIcon(newpix), "New File");
toolbar->addAction(QIcon(openpix), "Open File");
toolbar->addSeparator();
QAction *quit2 = toolbar->addAction(QIcon(quitpix),
"Quit Application");
connect(quit2, &QAction::triggered, qApp, &QApplication::quit);
QTextEdit *edit = new QTextEdit(this);
setCentralWidget(edit);
statusBar()->showMessage("Ready");
}
在这里,我们创建一个菜单,一个工具栏和一个状态栏。
QTextEdit *edit = new QTextEdit(this);
setCentralWidget(edit);
我们创建一个QTextEdit小部件,并将其放入QMainWindow小部件的中央部分。
main.cpp
#include "skeleton.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Skeleton window;
window.resize(350, 250);
window.setWindowTitle("Application skeleton");
window.show();
return app.exec();
}
这是主文件。

图:应用骨架
在 Qt5 教程的这一部分中,我们介绍了菜单和工具栏。
Windows API main函数
在 Windows API 教程的这一部分中,我们将讨论main函数。
main函数原型
main()函数是 C 程序的入口点。 但是,它不是第一个运行的程序。 当入口点为main()时,程序实际上从称为mainCRTStartup()的函数开始执行。 该函数位于 C 运行时库中。 它初始化诸如内存管理器,文件 I/O 支持和argv参数之类的东西。 之后,mainCRTStartup()函数将调用main()函数。
int main(void);
int main(int argc, char **argv);
int main(int argc, char *argv[]);
这些是经典控制台程序的main()函数的函数原型。
classic_console.c
#include <stdio.h>
int main(void) {
puts("This is a classic C program.");
return 0;
}
上面的源代码提供了经典控制台 C 程序的示例。
C:\Users\Jano\Documents\Pelles C Projects\ClassicConsole>ClassicConsole.exe
This is a classic C program.
这是ClassicConsole.exe程序的输出。
wmain函数原型
先前的main函数原型只能接收 ASCII 字符。 如果我们想要一个可以从命令行接收宽字符的程序,我们将使用wmain()函数原型。
int wmain(void);
int wmain(int argc, wchar_t **argv);
int wmain(int argc, wchar_t *argv[]);
上面的wmain()函数原型在命令行上接收wchar_t字符。 当我们使用这些原型时,执行从名为wmainCRTStartup()的函数开始,该函数随后将调用wmain()函数。
win_console.c
#include <windows.h>
#include <wchar.h>
int wmain(int argc, wchar_t **argv) {
PDWORD cChars = NULL;
HANDLE std = GetStdHandle(STD_OUTPUT_HANDLE);
if (std == INVALID_HANDLE_VALUE) {
wprintf(L"Cannot retrieve standard output handle\n (%d)",
GetLastError());
}
if (argv[1]) {
WriteConsoleW(std, argv[1], wcslen(argv[1]), cChars, NULL);
}
CloseHandle(std);
return 0;
}
我们有一个wmain()函数,可以接收宽字符。 该示例显示控制台程序的第一个参数。 要在 Pelles C 中插入命令行参数,我们转到项目选项,然后选择常规选项卡。 有一个名为“命令行参数”的编辑框。
int wmain(int argc, wchar_t **argv) {
wmain()函数的第二个参数的wchar_t类型告诉我们程序输入为宽字符。
HANDLE std = GetStdHandle(STD_OUTPUT_HANDLE);
GetStdHandle()函数将句柄返回到标准输出。
if (std == INVALID_HANDLE_VALUE) {
wprintf(L"Cannot retrieve standard output handle\n (%d)",
GetLastError());
}
如果发生错误,我们会收到INVALID_HANDLE_VALUE返回码。 对于这种情况,我们将显示一条错误消息。 GetLastError()函数检索上一个错误代码值。
WriteConsoleW(std, argv[1], wcslen(argv[1]), cChars, NULL);
我们使用WriteConsoleW()函数以宽字符写入控制台。
CloseHandle(std);
CloseHandle()函数将打开的手柄关闭到标准输出。
C:\Users\Jano\Documents\Pelles C Projects\WindowsConsole>WindowsConsole.exe компилятор
компилятор
我们将俄语单词(编译器)作为参数传递给程序。 该程序只需将参数打印回控制台即可。 请注意,为了查看正确的字符,我们需要将控制台的默认字体更改为 Lucida Console。 我们需要一种真正的字体来正确显示宽字符。
_tmain函数原型
_tmain()函数是 Microsoft 扩展。 它使程序员可以轻松地创建程序的 ANSI 和 UNICODE 版本。 它是一个 C 宏,它转换为wmain()或main()函数,具体取决于是否定义了_UNICODE常量。 在过去,创建 ANSI 和 UNICODE 构建都是很常见的。 如今,许多程序员建议仅创建 Unicode 程序。 这也是我们在本教程中将要做的。 我们将主要创建 Unicode 程序。
int _tmain(void);
int _tmain(int argc, TCHAR **argv);
int _tmain(int argc, TCHAR *argv[]);
这些是_tmain函数原型。 TCHAR宏转换为char或wchar_t。 它由UNICODE常数控制。
tmain_ex.c
#define _UNICODE
#define UNICODE
#include <windows.h>
#include <tchar.h>
int _tmain(int argc, TCHAR *argv[]) {
PDWORD cChars = NULL;
HANDLE std = GetStdHandle(STD_OUTPUT_HANDLE);
if (std == INVALID_HANDLE_VALUE) {
_tprintf(L"Cannot retrieve standard output handle\n (%d)",
GetLastError());
}
if (argv[1]) {
WriteConsole(std, argv[1], _tcslen(argv[1]), cChars, NULL);
}
CloseHandle(std);
return 0;
}
该示例输出其第一个参数(如果有)。
#define _UNICODE
#define UNICODE
在这里,我们定义两个常数。 这些定义意味着我们将构建一个 Unicode 程序。 它们转换 C 运行时和 Windows 头文件中的 C 宏。 _UNICODE常量在 C 运行时中转换宏。 (这些宏以下划线开头。)UNICODE常量转换 Windows 头文件中的宏。
#include <windows.h>
我们包括TCHAR宏的定义。 宏受UNICODE常数的影响。
#include <tchar.h>
我们必须为_tmain和_tcslen宏包含此头文件。 根据_UNICODE常数翻译它们。
int _tmain(int argc, TCHAR *argv[]) {
在我们的情况下,_tmain()函数将转换为wmain(),TCHAR宏将转换为wchar_t。
WriteConsole(std, argv[1], _tcslen(argv[1]), cChars, NULL);
WriteConsole()宏被转换为WriteConsoleW()函数。 WriteConsoleW()将输出写入控制台。 _tcslen宏被转换为wcslen()函数; 它返回宽字符串的长度。
C:\Users\Jano\Documents\Pelles C Projects\TMainEx>TMainEx.exe "операционная система"
операционная система
该程序将另一个俄语单词(操作系统)作为参数并将其打印到控制台。
WinMain函数原型
到目前为止,我们已经有了控制台的main函数。 对于图形用户界面开发,我们使用WinMain函数原型之一。
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR pCmdLine, int nCmdShow);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow);
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow);
这三个函数原型用于 Windows GUI 应用的入口点。 wWinMain()函数的pCmdLine参数包含作为 Unicode 字符串的命令行参数。 WinMain()函数的pCmdLine参数包含作为 ANSI 字符串的命令行参数。 _tWinMain是一个 C 宏,它取决于是否定义了_UNICODE常量,它转换为其他两个函数原型。
当入口点为WinMain()时,程序的执行从WinMainCRTStartup()开始。 在wWinMain()的情况下,执行从wWinMainCRTStartup()开始。
winmain_ex.c
#include <windows.h>
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR szCmdLine, int CmdShow) {
MessageBoxW(NULL, szCmdLine, L"Title", MB_OK);
return 0;
}
此代码在屏幕上显示一个小消息框。 它显示第一个命令行参数。
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR szCmdLine, int CmdShow)
wWinMain()函数的第三个参数是PWSTR(指向宽字符串的指针)。 它接受宽字符。

图:消息框
在 Windows API 教程的这一部分中,我们提到了main函数。
Qt5 中的布局管理
在 Qt5 编程教程的这一部分中,我们将讨论小部件的布局管理。 我们提到了QHBoxLayout,QVBoxLayout,QFormLayout和QGridLayout管理器。
典型的应用由各种小部件组成。 这些小部件放置在布局内。 程序员必须管理应用的布局。 在 Qt5 中,我们有两个选择:
- 绝对定位
- 布局管理器
绝对定位
程序员以像素为单位指定每个小部件的位置和大小。 当使用绝对定位时,我们必须了解几件事。
- 如果我们调整窗口大小,则小部件的大小和位置不会改变。
- 在各种平台上,应用看起来有所不同(通常很差)。
- 在我们的应用中更改字体可能会破坏布局。
- 如果决定更改布局,则必须完全重做布局,这既繁琐又耗时。
在某些情况下,我们可能会使用绝对定位。 但是大多数情况下,在实际程序中,程序员使用布局管理器。
absolute.cpp
#include <QApplication>
#include <QDesktopWidget>
#include <QTextEdit>
class Absolute : public QWidget {
public:
Absolute(QWidget *parent = 0);
};
Absolute::Absolute(QWidget *parent)
: QWidget(parent) {
QTextEdit *ledit = new QTextEdit(this);
ledit->setGeometry(5, 5, 200, 150);
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Absolute window;
window.setWindowTitle("Absolute");
window.show();
return app.exec();
}
setGeometry()方法用于以绝对坐标将窗口小部件放置在窗口上。
QTextEdit *edit = new QTextEdit(this);
ledit->setGeometry(5, 5, 200, 150);
我们创建一个QTextEdit小部件并手动定位。 setGeometry()方法有两件事:将窗口小部件定位到绝对坐标并调整窗口小部件的大小。

图:调整大小前

图:调整大小后
QVBoxLayout
QVBoxLayout类垂直排列小部件。 使用addWidget()方法将小部件添加到布局。
verticalbox.h
#pragma once
#include <QWidget>
class VerticalBox : public QWidget {
public:
VerticalBox(QWidget *parent = 0);
};
头文件。
verticalbox.cpp
#include "verticalbox.h"
#include <QVBoxLayout>
#include <QPushButton>
VerticalBox::VerticalBox(QWidget *parent)
: QWidget(parent) {
QVBoxLayout *vbox = new QVBoxLayout(this);
vbox->setSpacing(1);
QPushButton *settings = new QPushButton("Settings", this);
settings->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
QPushButton *accounts = new QPushButton("Accounts", this);
accounts->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
QPushButton *loans = new QPushButton("Loans", this);
loans->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
QPushButton *cash = new QPushButton("Cash", this);
cash->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
QPushButton *debts = new QPushButton("Debts", this);
debts->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
vbox->addWidget(settings);
vbox->addWidget(accounts);
vbox->addWidget(loans);
vbox->addWidget(cash);
vbox->addWidget(debts);
setLayout(vbox);
}
在我们的示例中,我们有一个垂直布局管理器。 我们在其中放入了五个按钮。 我们使所有按钮都可以在两个方向上展开。
QVBoxLayout *vbox = new QVBoxLayout(this);
vbox->setSpacing(1);
我们创建QVBoxLayout并在子窗口小部件之间设置 1px 的间距。
QPushButton *settings = new QPushButton("Settings", this);
settings->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
我们创建一个按钮并为其设置大小策略。 子窗口小部件由布局管理器管理。 默认情况下,按钮水平扩展,垂直方向固定大小。 如果要更改它,我们将设置一个新的大小策略。 在我们的例子中,按钮可以向两个方向扩展。
vbox->addWidget(settings);
vbox->addWidget(accounts);
...
我们使用addWidget()方法将子窗口小部件添加到布局管理器。
setLayout(vbox);
我们为窗口设置QVBoxLayout管理器。
main.cpp
#include "verticalbox.h"
#include <QApplication>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
VerticalBox window;
window.resize(240, 230);
window.setWindowTitle("VerticalBox");
window.show();
return app.exec();
}
主文件。

图:QVBoxLayout
按钮
在下面的示例中,我们在窗口的客户区域上显示两个按钮。 它们将位于窗口的右下角。
buttons.h
#pragma once
#include <QWidget>
#include <QPushButton>
class Buttons : public QWidget {
public:
Buttons(QWidget *parent = 0);
private:
QPushButton *okBtn;
QPushButton *applyBtn;
};
头文件。
buttons.cpp
#include "buttons.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
Buttons::Buttons(QWidget *parent)
: QWidget(parent) {
QVBoxLayout *vbox = new QVBoxLayout(this);
QHBoxLayout *hbox = new QHBoxLayout();
okBtn = new QPushButton("OK", this);
applyBtn = new QPushButton("Apply", this);
hbox->addWidget(okBtn, 1, Qt::AlignRight);
hbox->addWidget(applyBtn, 0);
vbox->addStretch(1);
vbox->addLayout(hbox);
}
假设我们想在窗口的右下角有两个按钮。
QVBoxLayout *vbox = new QVBoxLayout(this);
QHBoxLayout *hbox = new QHBoxLayout();
我们创建了两个框布局管理器:一个垂直框布局管理器和一个水平框布局管理器。
okBtn = new QPushButton("OK", this);
applyBtn = new QPushButton("Apply", this);
我们创建两个按钮。
hbox->addWidget(okBtn, 1, Qt::AlignRight);
hbox->addWidget(applyBtn, 0);
这些按钮位于水平布局管理器中。 使用addWidget()方法。 这些按钮右对齐。 第一个参数是子窗口小部件。 第二个参数是拉伸因子,最后一个参数是对齐。 通过将“确定”按钮的拉伸因子设置为 1,我们在窗口的左侧到右侧留出一定的空间。 窗口小部件不会扩展到分配给它的所有空间。 最后,Qt::AlignRight常数将小部件对齐到分配空间的右侧。
vbox->addStretch(1);
vbox->addLayout(hbox);
通过调用addStretch()方法,我们在垂直框中放入了一个可扩展的空白区域。 然后,将水平框布局添加到垂直框布局。
main.cpp
#include <QApplication>
#include "buttons.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Buttons window;
window.resize(290, 170);
window.setWindowTitle("Buttons");
window.show();
return app.exec();
}
主文件。

图:按钮
嵌套布局
以下示例的目的是说明可以合并布局管理器。 通过甚至简单布局的组合,我们可以创建复杂的对话框或窗口。 要嵌套布局,我们利用addLayout()方法。
nesting.h
#pragma once
#include <QWidget>
class Layouts : public QWidget {
public:
Layouts(QWidget *parent = 0);
};
这是头文件。
nesting.cpp
#include <QVBoxLayout>
#include <QPushButton>
#include <QListWidget>
#include "nesting.h"
Layouts::Layouts(QWidget *parent)
: QWidget(parent) {
QVBoxLayout *vbox = new QVBoxLayout();
QHBoxLayout *hbox = new QHBoxLayout(this);
QListWidget *lw = new QListWidget(this);
lw->addItem("The Omen");
lw->addItem("The Exorcist");
lw->addItem("Notes on a scandal");
lw->addItem("Fargo");
lw->addItem("Capote");
QPushButton *add = new QPushButton("Add", this);
QPushButton *rename = new QPushButton("Rename", this);
QPushButton *remove = new QPushButton("Remove", this);
QPushButton *removeall = new QPushButton("Remove All", this);
vbox->setSpacing(3);
vbox->addStretch(1);
vbox->addWidget(add);
vbox->addWidget(rename);
vbox->addWidget(remove);
vbox->addWidget(removeall);
vbox->addStretch(1);
hbox->addWidget(lw);
hbox->addSpacing(15);
hbox->addLayout(vbox);
setLayout(hbox);
}
在示例中,我们创建一个窗口,该窗口由四个按钮和一个列表小部件组成。 这些按钮被分组在一个垂直列中,并位于列表小部件的右侧。 如果我们调整窗口的大小,列表小部件也将被调整大小。
QVBoxLayout *vbox = new QVBoxLayout();
QVBoxLayout将是按钮的列。
QHBoxLayout *hbox = new QHBoxLayout(this);
QHBoxLayout将是小部件的基本布局。
QListWidget *lw = new QListWidget(this);
lw->addItem("The Omen");
lw->addItem("The Exorcist");
lw->addItem("Notes on a scandal");
lw->addItem("Fargo");
lw->addItem("Capote");
QListWidget已创建。
QPushButton *add = new QPushButton("Add", this);
QPushButton *rename = new QPushButton("Rename", this);
QPushButton *remove = new QPushButton("Remove", this);
QPushButton *removeall = new QPushButton("Remove All", this);
在这里,我们创建四个按钮。
vbox->setSpacing(3);
vbox->addStretch(1);
vbox->addWidget(add);
vbox->addWidget(rename);
vbox->addWidget(remove);
vbox->addWidget(removeall);
vbox->addStretch(1);
创建带有四个按钮的垂直框。 我们在按钮之间留了一些空间。 注意,我们在垂直框的顶部和底部添加了一个拉伸因子。 这样,按钮可以垂直居中。
hbox->addWidget(lw);
hbox->addSpacing(15);
hbox->addLayout(vbox);
列表小部件和按钮的垂直框放置在水平框布局中。 addLayout()方法用于将一个布局添加到另一个布局。
setLayout(hbox);
我们为父窗口设置基本布局。
main.cpp
#include <QApplication>
#include "nesting.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Layouts window;
window.setWindowTitle("Layouts");
window.show();
return app.exec();
}
这是主文件。

图:嵌套布局
QFormLayout
QFormLayout是一个简单的布局管理器,用于管理输入窗口小部件及其相关标签的形式。 它以两列的形式布置其子项。 左列包含标签,右列包含输入窗口小部件,例如QLineEdit或QSpinBox。
form.h
#pragma once
#include <QWidget>
class FormEx : public QWidget {
public:
FormEx(QWidget *parent = 0);
};
这是标题文件管理器。
form.cpp
#include <QFormLayout>
#include <QLabel>
#include <QLineEdit>
#include "form.h"
FormEx::FormEx(QWidget *parent)
: QWidget(parent) {
QLineEdit *nameEdit = new QLineEdit(this);
QLineEdit *addrEdit = new QLineEdit(this);
QLineEdit *occpEdit = new QLineEdit(this);
QFormLayout *formLayout = new QFormLayout;
formLayout->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter);
formLayout->addRow("Name:", nameEdit);
formLayout->addRow("Email:", addrEdit);
formLayout->addRow("Age:", occpEdit);
setLayout(formLayout);
}
该示例创建一个包含三个标签和三个行编辑的表单。
QFormLayout *formLayout = new QFormLayout;
创建QFormLayout的实例。
formLayout->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter);
使用setLabelAlignment()方法,我们设置标签小部件的对齐方式。
formLayout->addRow("Name:", nameEdit);
addRow()方法将新行添加到表单布局的底部,并带有给定的标签和输入小部件。
main.cpp
#include <QApplication>
#include "form.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
FormEx window;
window.setWindowTitle("Form example");
window.show();
return app.exec();
}
这是主文件。

图:简单 form
QGridLayout
QGridLayout将其小部件放置在网格中。 它是一个功能强大的布局管理器。
calculator.h
#pragma once
#include <QWidget>
class Calculator : public QWidget {
public:
Calculator(QWidget *parent = 0);
};
头文件。
calculator.cpp
#include <QGridLayout>
#include <QPushButton>
#include "calculator.h"
Calculator::Calculator(QWidget *parent)
: QWidget(parent) {
QGridLayout *grid = new QGridLayout(this);
grid->setSpacing(2);
QList<QString> values({ "7", "8", "9", "/",
"4", "5", "6", "*",
"1", "2", "3", "-",
"0", ".", "=", "+"
});
int pos = 0;
for (int i=0; i<4; i++) {
for (int j=0; j<4; j++) {
QPushButton *btn = new QPushButton(values[pos], this);
btn->setFixedSize(40, 40);
grid->addWidget(btn, i, j);
pos++;
}
}
setLayout(grid);
}
我们创建计算器的骨架。
QGridLayout *grid = new QGridLayout(this);
grid->setSpacing(2);
我们创建网格布局,并在子小部件之间设置 2px 的空间。
QList<QString> values({ "7", "8", "9", "/",
"4", "5", "6", "*",
"1", "2", "3", "-",
"0", ".", "=", "+"
});
这些是按钮上显示的字符。
for (int i=0; i<4; i++) {
for (int j=0; j<4; j++) {
QPushButton *btn = new QPushButton(values[pos], this);
btn->setFixedSize(40, 40);
grid->addWidget(btn, i, j);
pos++;
}
}
我们将十六个小部件放置到网格布局中。 每个按钮将具有固定的大小。
main.cpp
#include <QApplication>
#include "calculator.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Calculator window;
window.move(300, 300);
window.setWindowTitle("Calculator");
window.show();
return app.exec();
}
这是主文件。

图:QGridLayout
回顾
在本章的下一个示例中,我们使用QGridLayout管理器创建一个更复杂的窗口。
review.h
#pragma once
#include <QWidget>
class Review : public QWidget {
public:
Review(QWidget *parent = 0);
};
头文件。
review.cpp
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QTextEdit>
#include "review.h"
Review::Review(QWidget *parent)
: QWidget(parent) {
QGridLayout *grid = new QGridLayout(this);
grid->setVerticalSpacing(15);
grid->setHorizontalSpacing(10);
QLabel *title = new QLabel("Title:", this);
grid->addWidget(title, 0, 0, 1, 1);
title->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
QLineEdit *edt1 = new QLineEdit(this);
grid->addWidget(edt1, 0, 1, 1, 1);
QLabel *author = new QLabel("Author:", this);
grid->addWidget(author, 1, 0, 1, 1);
author->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
QLineEdit *edt2 = new QLineEdit(this);
grid->addWidget(edt2, 1, 1, 1, 1);
QLabel *review = new QLabel("Review:", this);
grid->addWidget(review, 2, 0, 1, 1);
review->setAlignment(Qt::AlignRight | Qt::AlignTop);
QTextEdit *te = new QTextEdit(this);
grid->addWidget(te, 2, 1, 3, 1);
setLayout(grid);
}
该代码创建了一个窗口,可用于输入作者,书名和书评。
QGridLayout *grid = new QGridLayout(this);
QGridLayout管理器已创建。
grid->setVerticalSpacing(15);
grid->setHorizontalSpacing(10);
我们使用setVerticalSpacing()方法添加垂直间距,并使用setHorizontalSpacing()方法添加水平间距。
QLabel *title = new QLabel("Title", this);
grid->addWidget(title, 0, 0, 1, 1);
这些代码行创建一个标签小部件,并将其放入网格布局中。 addWidget()方法具有五个参数。 第一个参数是子窗口小部件,在本例中为标签。 接下来的两个参数是放置标签的网格中的行和列。 最后,最后一个参数是rowspan和colspan。 这些参数指定当前窗口小部件将跨越多少行。 在我们的情况下,标签将仅跨越一列和一行。
title->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
setAlignment()方法将标题标签对准其单元格。 在水平方向上,它是右对齐的。 在垂直方向上,它居中。
QTextEdit *te = new QTextEdit(this);
grid->addWidget(te, 2, 1, 3, 1);
QTextEdit小部件位于第三行和第二列; 它跨越三行一列。
main.cpp
#include <QApplication>
#include "review.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Review window;
window.setWindowTitle("Review");
window.show();
return app.exec();
}
主文件。

图:回顾
Qt5 教程的这一部分专门用于布局管理。
Qt5 中的事件和信号
在 Qt5 C++ 编程教程的这一部分中,我们讨论事件和信号。
事件是任何 GUI 程序中的重要组成部分。 所有 GUI 应用都是事件驱动的。 应用会对在其生命周期内生成的不同事件类型做出反应。 事件主要由应用的用户生成。 但是它们也可以通过其他方式生成,例如互联网连接,窗口管理器或计时器。 在事件模型中,有三个参与者:
- 事件来源
- 事件对象
- 事件目标
事件源是状态更改的对象。 它生成事件。事件对象(事件)将状态更改封装在事件源中。事件目标是要通知的对象。 事件源对象将处理事件的任务委托给事件目标。
当我们调用应用的exec()方法时,应用进入主循环。 主循环获取事件并将其发送到对象。 Qt 具有独特的信号和槽机制。 该信号和槽机制是 C++ 编程语言的扩展。
信号和槽用于对象之间的通信。 当发生特定事件时,会发出信号。槽是正常的 C++ 方法; 发出与其连接的信号时调用它。
点击
第一个示例显示了一个非常简单的事件处理示例。 我们有一个按钮。 通过单击按钮,我们终止该应用。
click.h
#pragma once
#include <QWidget>
class Click : public QWidget {
public:
Click(QWidget *parent = 0);
};
这是头文件。
click.cpp
#include <QPushButton>
#include <QApplication>
#include <QHBoxLayout>
#include "click.h"
Click::Click(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
hbox->setSpacing(5);
QPushButton *quitBtn = new QPushButton("Quit", this);
hbox->addWidget(quitBtn, 0, Qt::AlignLeft | Qt::AlignTop);
connect(quitBtn, &QPushButton::clicked, qApp, &QApplication::quit);
}
我们在窗口上显示一个QPushButton。
connect(quitBtn, &QPushButton::clicked, qApp, &QApplication::quit);
connect()方法将信号连接到槽。 当我们单击退出按钮时,会生成clicked信号。 qApp是指向应用对象的全局指针。 它在<QApplication>头文件中定义。 发出点击信号时,将调用quit()方法。
main.cpp
#include <QApplication>
#include "click.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Click window;
window.resize(250, 150);
window.setWindowTitle("Click");
window.show();
return app.exec();
}
这是主文件。

图:点击
按键
在以下示例中,我们对按键进行反应。
keypress.h
#pragma once
#include <QWidget>
class KeyPress : public QWidget {
public:
KeyPress(QWidget *parent = 0);
protected:
void keyPressEvent(QKeyEvent * e);
};
这是keypress.h头文件。
keypress.cpp
#include <QApplication>
#include <QKeyEvent>
#include "keypress.h"
KeyPress::KeyPress(QWidget *parent)
: QWidget(parent)
{ }
void KeyPress::keyPressEvent(QKeyEvent *event) {
if (event->key() == Qt::Key_Escape) {
qApp->quit();
}
}
如果我们按 Escape 键,则应用终止。
void KeyPress::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) {
qApp->quit();
}
}
在 Qt5 中使用事件的一种方法是重新实现事件处理器。 QKeyEvent是一个事件对象,其中包含有关发生的情况的信息。 在我们的例子中,我们使用事件对象来确定实际按下了哪个键。
main.cpp
#include <QApplication>
#include "keypress.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
KeyPress window;
window.resize(250, 150);
window.setWindowTitle("Key press");
window.show();
return app.exec();
}
这是主文件。
QMoveEvent
QMoveEvent类包含移动事件的事件参数。 移动事件将发送到已移动的窗口小部件。
move.h
#pragma once
#include <QMainWindow>
class Move : public QWidget {
Q_OBJECT
public:
Move(QWidget *parent = 0);
protected:
void moveEvent(QMoveEvent *e);
};
这是move.h头文件。
move.cpp
#include <QMoveEvent>
#include "move.h"
Move::Move(QWidget *parent)
: QWidget(parent)
{ }
void Move::moveEvent(QMoveEvent *e) {
int x = e->pos().x();
int y = e->pos().y();
QString text = QString::number(x) + "," + QString::number(y);
setWindowTitle(text);
}
在我们的代码编程示例中,我们对移动事件做出反应。 我们确定窗口客户区左上角的当前 x,y 坐标,并将这些值设置为窗口标题。
int x = e->pos().x();
int y = e->pos().y();
我们使用QMoveEvent对象来确定x和y值。
QString text = QString::number(x) + "," + QString::number(y);
我们将整数值转换为字符串。
setWindowTitle(text);
setWindowTitle()方法将文本设置为窗口的标题。
main.cpp
#include <QApplication>
#include "move.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Move window;
window.resize(250, 150);
window.setWindowTitle("Move");
window.show();
return app.exec();
}
这是主文件。

图:QMoveEvent
断开信号
可以从槽断开信号。 下一个示例显示了我们如何完成此任务。
disconnect.h
#pragma once
#include <QWidget>
#include <QPushButton>
class Disconnect : public QWidget {
Q_OBJECT
public:
Disconnect(QWidget *parent = 0);
private slots:
void onClick();
void onCheck(int);
private:
QPushButton *clickBtn;
};
在头文件中,我们声明了两个槽。 slots不是 C++ 关键字,它是 Qt5 扩展名。 在代码编译之前,这些扩展由预处理器处理。 当我们在类中使用信号和时隙时,必须在类定义的开头提供Q_OBJECT宏。 否则,预处理器会抱怨。
disconnect.cpp
#include <QTextStream>
#include <QCheckBox>
#include <QHBoxLayout>
#include "disconnect.h"
Disconnect::Disconnect(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
hbox->setSpacing(5);
clickBtn = new QPushButton("Click", this);
hbox->addWidget(clickBtn, 0, Qt::AlignLeft | Qt::AlignTop);
QCheckBox *cb = new QCheckBox("Connect", this);
cb->setCheckState(Qt::Checked);
hbox->addWidget(cb, 0, Qt::AlignLeft | Qt::AlignTop);
connect(clickBtn, &QPushButton::clicked, this, &Disconnect::onClick);
connect(cb, &QCheckBox::stateChanged, this, &Disconnect::onCheck);
}
void Disconnect::onClick() {
QTextStream out(stdout);
out << "Button clicked" << endl;
}
void Disconnect::onCheck(int state) {
if (state == Qt::Checked) {
connect(clickBtn, &QPushButton::clicked, this, &Disconnect::onClick);
} else {
disconnect(clickBtn, &QPushButton::clicked, this,
&Disconnect::onClick);
}
}
在我们的示例中,我们有一个按钮和一个复选框。 复选框用于将槽与单击的信号按钮断开连接。 此示例必须从命令行执行。
connect(clickBtn, &QPushButton::clicked, this, &Disconnect::onClick);
connect(cb, &QCheckBox::stateChanged, this, &Disconnect::onCheck);
在这里,我们将信号连接到用户定义的槽。
void Disconnect::onClick() {
QTextStream out(stdout);
out << "Button clicked" << endl;
}
如果单击“单击”按钮,则将"Button clicked"文本发送到终端窗口。
void Disconnect::onCheck(int state) {
if (state == Qt::Checked) {
connect(clickBtn, &QPushButton::clicked, this, &Disconnect::onClick);
} else {
disconnect(clickBtn, &QPushButton::clicked, this, &Disconnect::onClick);
}
}
在onCheck()槽内,我们根据单击状态将onClick()槽与单击按钮连接或断开。
main.cpp
#include <QApplication>
#include "disconnect.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Disconnect window;
window.resize(250, 150);
window.setWindowTitle("Disconnect");
window.show();
return app.exec();
}
这是主文件。
计时器
计时器用于执行单发或重复性任务。 一个使用计时器的好例子是时钟。 每秒,我们必须更新显示当前时间的标签。
timer.h
#pragma once
#include <QWidget>
#include <QLabel>
class Timer : public QWidget {
public:
Timer(QWidget *parent = 0);
protected:
void timerEvent(QTimerEvent *e);
private:
QLabel *label;
};
这是头文件。
timer.cpp
#include "timer.h"
#include <QHBoxLayout>
#include <QTime>
Timer::Timer(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
hbox->setSpacing(5);
label = new QLabel("", this);
hbox->addWidget(label, 0, Qt::AlignLeft | Qt::AlignTop);
QTime qtime = QTime::currentTime();
QString stime = qtime.toString();
label->setText(stime);
startTimer(1000);
}
void Timer::timerEvent(QTimerEvent *e) {
Q_UNUSED(e);
QTime qtime = QTime::currentTime();
QString stime = qtime.toString();
label->setText(stime);
}
在我们的示例中,我们在窗口上显示当前本地时间。
label = new QLabel("", this);
为了显示时间,我们使用标签小部件。
QTime qtime = QTime::currentTime();
QString stime = qtime.toString();
label->setText(stime);
在这里,我们确定当前的本地时间。 我们将其设置为标签小部件。
startTimer(1000);
我们启动计时器。 每 1000ms 会生成一个计时器事件。
void Timer::timerEvent(QTimerEvent *e) {
Q_UNUSED(e);
QTime qtime = QTime::currentTime();
QString stime = qtime.toString();
label->setText(stime);
}
要处理计时器事件,我们必须重新实现timerEvent()方法。
main.cpp
#include <QApplication>
#include "timer.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Timer window;
window.resize(250, 150);
window.setWindowTitle("Timer");
window.show();
return app.exec();
}
这是主文件。

图:计时器
本章专门介绍 Qt5 中的事件和信号。
Qt5 小部件
在 Qt5 C++ 编程教程的这一部分中,我们将讨论一些基本的 Qt5 小部件。 我们有QLabel,QSlider,QComboBox,QSpinBox,QLineEdit和QMainWindow小部件的示例。
小部件是 GUI 应用的基本构建块。 Qt5 库具有丰富的各种小部件集。
QLabel
QLabel用于显示文本和图像。 没有用户交互。 以下示例显示文本。
label.h
#pragma once
#include <QWidget>
#include <QLabel>
class Label : public QWidget {
public:
Label(QWidget *parent = 0);
private:
QLabel *label;
};
这是我们的代码示例的头文件。
label.cpp
#include <QVBoxLayout>
#include <QFont>
#include "label.h"
Label::Label(QWidget *parent)
: QWidget(parent) {
QString lyrics = "Who doesn't long for someone to hold\n\
Who knows how to love you without being told\n\
Somebody tell me why I'm on my own\n\
If there's a soulmate for everyone\n\
\n\
Here we are again, circles never end\n\
How do I find the perfect fit\n\
There's enough for everyone\n\
But I'm still waiting in line\n\
\n\
Who doesn't long for someone to hold\n\
Who knows how to love you without being told\n\
Somebody tell me why I'm on my own\n\
If there's a soulmate for everyone";
label = new QLabel(lyrics, this);
label->setFont(QFont("Purisa", 10));
QVBoxLayout *vbox = new QVBoxLayout();
vbox->addWidget(label);
setLayout(vbox);
}
我们使用QLabel小部件在窗口中显示歌词。
label = new QLabel(lyrics, this);
label->setFont(QFont("Purisa", 10));
我们创建一个标签小部件并为其设置特定的字体。
main.cpp
#include <QApplication>
#include <QTextStream>
#include "label.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Label window;
window.setWindowTitle("QLabel");
window.show();
return app.exec();
}
这是主文件。

图:QLabel
QSlider
QSlider是具有简单句柄的小部件。 该手柄可以前后拉动。 这样,我们可以为特定任务选择一个值。
slider.h
#pragma once
#include <QWidget>
#include <QSlider>
#include <QLabel>
class Slider : public QWidget {
Q_OBJECT
public:
Slider(QWidget *parent = 0);
private:
QSlider *slider;
QLabel *label;
};
该示例的头文件。
slider.cpp
#include <QHBoxLayout>
#include "slider.h"
Slider::Slider(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
slider = new QSlider(Qt::Horizontal , this);
hbox->addWidget(slider);
label = new QLabel("0", this);
hbox->addWidget(label);
connect(slider, &QSlider::valueChanged, label,
static_cast<void (QLabel::*)(int)>(&QLabel::setNum));
}
我们显示两个小部件:一个滑块和一个标签。 滑块控制标签中显示的数字。
slider = new QSlider(Qt::Horizontal , this);
将创建水平QSlider。
connect(slider, &QSlider::valueChanged, label,
static_cast<void (QLabel::*)(int)>(&QLabel::setNum));
在此代码行中,我们将valueChanged()信号连接到标签的内置setNum()槽。 由于setNum()方法过载,因此我们使用static_cast选择正确的方法。
main.cpp
#include <QApplication>
#include "slider.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Slider window;
window.setWindowTitle("QSlider");
window.show();
return app.exec();
}
这是主文件。

图:QSlider
QComboBox
QComboBox是一个小部件,它以占用最少屏幕空间的方式向用户显示选项列表。 这是一个显示当前项目的选择小部件,可以弹出可选择项目的列表。 组合框可能是可编辑的,允许用户修改列表中的每个项目。
combobox.h
#pragma once
#include <QWidget>
#include <QComboBox>
#include <QLabel>
class ComboBoxEx : public QWidget {
Q_OBJECT
public:
ComboBoxEx(QWidget *parent = 0);
private:
QComboBox *combo;
QLabel *label;
};
我们使用两个小部件:一个组合框和一个标签。
combobox.cpp
#include <QHBoxLayout>
#include "combobox.h"
ComboBoxEx::ComboBoxEx(QWidget *parent)
: QWidget(parent) {
QStringList distros = {"Arch", "Xubuntu", "Redhat", "Debian",
"Mandriva"};
QHBoxLayout *hbox = new QHBoxLayout(this);
combo = new QComboBox();
combo->addItems(distros);
hbox->addWidget(combo);
hbox->addSpacing(15);
label = new QLabel("Arch", this);
hbox->addWidget(label);
connect(combo, static_cast<void(QComboBox::*)(const QString &)>(&QComboBox::activated),
label, &QLabel::setText);
}
在示例中,标签中显示了从组合框中选择的项目。
QStringList distros = {"Arch", "Xubuntu", "Redhat", "Debian",
"Mandriva"};
QStringList存储组合框的数据。 我们列出了 Linux 发行版。
combo = new QComboBox();
combo->addItems(distros);
创建一个QComboBox,并使用addItems()方法插入项目。
connect(combo, static_cast<void(QComboBox::*)(const QString &)>(&QComboBox::activated),
label, &QLabel::setText);
组合框的activated()信号插入标签的setText()槽。 由于信号过载,我们进行静态转换。
main.cpp
#include <QApplication>
#include "combobox.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
ComboBoxEx window;
window.resize(300, 150);
window.setWindowTitle("QComboBox");
window.show();
return app.exec();
}
这是应用的主文件。

图:QComboBox
QSpinBox
QSpinbox是一个小部件,用于处理整数和离散值集。 在我们的代码示例中,我们将有一个QSpinbox小部件。 我们可以选择数字0..99。 当前选择的值显示在标签窗口小部件中。
spinbox.h
#pragma once
#include <QWidget>
#include <QSpinBox>
class SpinBox : public QWidget {
Q_OBJECT
public:
SpinBox(QWidget *parent = 0);
private:
QSpinBox *spinbox;
};
这是QSpinbox示例的头文件。
spinbox.cpp
#include <QHBoxLayout>
#include <QLabel>
#include "spinbox.h"
SpinBox::SpinBox(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
hbox->setSpacing(15);
spinbox = new QSpinBox(this);
QLabel *lbl = new QLabel("0", this);
hbox->addWidget(spinbox);
hbox->addWidget(lbl);
connect(spinbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
lbl, static_cast<void (QLabel::*)(int)>(&QLabel::setNum));
}
我们在窗口上放置一个旋转框,并将其valueChanged()信号连接到QLabel的setNum()槽。
connect(spinbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
lbl, static_cast<void (QLabel::*)(int)>(&QLabel::setNum));
我们需要进行两次转换,因为信号和槽都过载。
main.cpp
#include <QApplication>
#include "spinbox.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
SpinBox window;
window.resize(250, 150);
window.setWindowTitle("QSpinBox");
window.show();
return app.exec();
}
这是主文件。

图:QSpinBox
QLineEdit
QLineEdit是一个小部件,允许输入和编辑单行纯文本。 QLineEdit小部件具有撤消/重做,剪切/粘贴和拖放功能。
在我们的示例中,我们显示了三个标签和三行编辑。
ledit.h
#pragma once
#include <QWidget>
class Ledit : public QWidget {
public:
Ledit(QWidget *parent = 0);
};
该示例的头文件。
ledit.cpp
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include "ledit.h"
Ledit::Ledit(QWidget *parent)
: QWidget(parent) {
QLabel *name = new QLabel("Name:", this);
name->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
QLabel *age = new QLabel("Age:", this);
age->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
QLabel *occupation = new QLabel("Occupation:", this);
occupation->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
QLineEdit *le1 = new QLineEdit(this);
QLineEdit *le2 = new QLineEdit(this);
QLineEdit *le3 = new QLineEdit(this);
QGridLayout *grid = new QGridLayout();
grid->addWidget(name, 0, 0);
grid->addWidget(le1, 0, 1);
grid->addWidget(age, 1, 0);
grid->addWidget(le2, 1, 1);
grid->addWidget(occupation, 2, 0);
grid->addWidget(le3, 2, 1);
setLayout(grid);
}
我们显示三个标签和三行编辑。 这些小部件由QGridLayout管理器组织。
main.cpp
#include "ledit.h"
#include <QApplication>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Ledit window;
window.setWindowTitle("QLineEdit");
window.show();
return app.exec();
}
这是主文件。

图:QLineEdit
状态栏
状态栏是一个面板,用于显示有关应用的状态信息。
在我们的示例中,我们有两个按钮和一个状态栏。 如果我们单击每个按钮,则会显示一条消息。 状态栏小部件是QMainWindow小部件的一部分。
statusbar.h
#pragma once
#include <QMainWindow>
#include <QPushButton>
class Statusbar : public QMainWindow {
Q_OBJECT
public:
Statusbar(QWidget *parent = 0);
private slots:
void OnOkPressed();
void OnApplyPressed();
private:
QPushButton *okBtn;
QPushButton *aplBtn;
};
该示例的头文件。
statusbar.cpp
#include <QLabel>
#include <QFrame>
#include <QStatusBar>
#include <QHBoxLayout>
#include "statusbar.h"
Statusbar::Statusbar(QWidget *parent)
: QMainWindow(parent) {
QFrame *frame = new QFrame(this);
setCentralWidget(frame);
QHBoxLayout *hbox = new QHBoxLayout(frame);
okBtn = new QPushButton("OK", frame);
hbox->addWidget(okBtn, 0, Qt::AlignLeft | Qt::AlignTop);
aplBtn = new QPushButton("Apply", frame);
hbox->addWidget(aplBtn, 1, Qt::AlignLeft | Qt::AlignTop);
statusBar();
connect(okBtn, &QPushButton::clicked, this, &Statusbar::OnOkPressed);
connect(aplBtn, &QPushButton::clicked, this, &Statusbar::OnApplyPressed);
}
void Statusbar::OnOkPressed() {
statusBar()->showMessage("OK button pressed", 2000);
}
void Statusbar::OnApplyPressed() {
statusBar()->showMessage("Apply button pressed", 2000);
}
这是statusbar.cpp文件。
QFrame *frame = new QFrame(this);
setCentralWidget(frame);
QFrame小部件放在QMainWindow小部件的中心区域。 中心区域只能容纳一个小部件。
okBtn = new QPushButton("OK", frame);
hbox->addWidget(okBtn, 0, Qt::AlignLeft | Qt::AlignTop);
aplBtn = new QPushButton("Apply", frame);
hbox->addWidget(aplBtn, 1, Qt::AlignLeft | Qt::AlignTop);
我们创建两个QPushButton小部件,并将它们放置在水平框中。 按钮的父项是框架窗口小部件。
statusBar();
要显示状态栏小部件,我们调用QMainWindow小部件的statusBar()方法。
void Statusbar::OnOkPressed() {
statusBar()->showMessage("OK button pressed", 2000);
}
showMessage()方法在状态栏上显示该消息。 最后一个参数指定消息在状态栏上显示的毫秒数。
main.cpp
#include <QApplication>
#include "statusbar.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Statusbar window;
window.resize(300, 200);
window.setWindowTitle("QStatusBar");
window.show();
return app.exec();
}
这是主文件。

图:状态栏示例
在 Qt5 教程的这一部分中,我们介绍了几个 Qt5 小部件。
Qt5 小部件 II
在 Qt5 C++ 编程教程的这一部分中,我们将继续讨论 Qt5 小部件。 我们介绍以下小部件:QCheckBox,QListWidget,QProgressBar,QPixmap,QSplitter和QTableWidget。
QCheckBox
QCheckBox是具有两种状态的窗口小部件:开和关。 这是一个带有标签的盒子。 如果选中此复选框,则在方框中用勾号表示。
在我们的示例中,我们在窗口上显示一个复选框。 如果选中此复选框,则显示窗口标题。 否则它是隐藏的。
checkbox.h
#pragma once
#include <QWidget>
class CheckBox : public QWidget {
Q_OBJECT
public:
CheckBox(QWidget *parent = 0);
private slots:
void showTitle(int);
};
这是我们的代码示例的头文件。
checkbox.cpp
#include <QCheckBox>
#include <QHBoxLayout>
#include "checkbox.h"
CheckBox::CheckBox(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
QCheckBox *cb = new QCheckBox("Show Title", this);
cb->setCheckState(Qt::Checked);
hbox->addWidget(cb, 0, Qt::AlignLeft | Qt::AlignTop);
connect(cb, &QCheckBox::stateChanged, this, &CheckBox::showTitle);
}
void CheckBox::showTitle(int state) {
if (state == Qt::Checked) {
setWindowTitle("QCheckBox");
} else {
setWindowTitle(" ");
}
}
我们在窗口上显示一个复选框,并将其连接到showTitle()槽。
cb->setCheckState(Qt::Checked);
示例开始时,该复选框已选中。
void CheckBox::showTitle(int state) {
if (state == Qt::Checked) {
setWindowTitle("QCheckBox");
} else {
setWindowTitle(" ");
}
}
我们确定复选框的状态,并相应地调用setWindowTitle()。
main.cpp
#include <QApplication>
#include "checkbox.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
CheckBox window;
window.resize(250, 150);
window.setWindowTitle("QCheckBox");
window.show();
return app.exec();
}
这是主文件。

图:QCheckBox
QListWidget
QListWidget是一个小部件,用于显示项目列表。 在我们的示例中,我们将演示如何在列表小部件中添加,重命名和删除项目。
listwidget.h
#pragma once
#include <QWidget>
#include <QPushButton>
#include <QListWidget>
class ListWidget : public QWidget {
Q_OBJECT
public:
ListWidget(QWidget *parent = 0);
private slots:
void addItem();
void renameItem();
void removeItem();
void clearItems();
private:
QListWidget *lw;
QPushButton *add;
QPushButton *rename;
QPushButton *remove;
QPushButton *removeAll;
};
该示例的头文件。
listwidget.cpp
#include "listwidget.h"
#include <QVBoxLayout>
#include <QInputDialog>
ListWidget::ListWidget(QWidget *parent)
: QWidget(parent) {
QVBoxLayout *vbox = new QVBoxLayout();
vbox->setSpacing(10);
QHBoxLayout *hbox = new QHBoxLayout(this);
lw = new QListWidget(this);
lw->addItem("The Omen");
lw->addItem("The Exorcist");
lw->addItem("Notes on a scandal");
lw->addItem("Fargo");
lw->addItem("Capote");
add = new QPushButton("Add", this);
rename = new QPushButton("Rename", this);
remove = new QPushButton("Remove", this);
removeAll = new QPushButton("Remove All", this);
vbox->setSpacing(3);
vbox->addStretch(1);
vbox->addWidget(add);
vbox->addWidget(rename);
vbox->addWidget(remove);
vbox->addWidget(removeAll);
vbox->addStretch(1);
hbox->addWidget(lw);
hbox->addSpacing(15);
hbox->addLayout(vbox);
connect(add, &QPushButton::clicked, this, &ListWidget::addItem);
connect(rename, &QPushButton::clicked, this, &ListWidget::renameItem);
connect(remove, &QPushButton::clicked, this, &ListWidget::removeItem);
connect(removeAll, &QPushButton::clicked, this, &ListWidget::clearItems);
setLayout(hbox);
}
void ListWidget::addItem() {
QString c_text = QInputDialog::getText(this, "Item", "Enter new item");
QString s_text = c_text.simplified();
if (!s_text.isEmpty()) {
lw->addItem(s_text);
int r = lw->count() - 1;
lw->setCurrentRow(r);
}
}
void ListWidget::renameItem() {
QListWidgetItem *curitem = lw->currentItem();
int r = lw->row(curitem);
QString c_text = curitem->text();
QString r_text = QInputDialog::getText(this, "Item",
"Enter new item", QLineEdit::Normal, c_text);
QString s_text = r_text.simplified();
if (!s_text.isEmpty()) {
QListWidgetItem *item = lw->takeItem(r);
delete item;
lw->insertItem(r, s_text);
lw->setCurrentRow(r);
}
}
void ListWidget::removeItem() {
int r = lw->currentRow();
if (r != -1) {
QListWidgetItem *item = lw->takeItem(r);
delete item;
}
}
void ListWidget::clearItems(){
if (lw->count() != 0) {
lw->clear();
}
}
我们显示一个列表小部件和四个按钮。 我们将使用这些按钮在列表小部件中添加,重命名和删除项目。
lw = new QListWidget(this);
lw->addItem("The Omen");
lw->addItem("The Exorcist");
lw->addItem("Notes on a scandal");
lw->addItem("Fargo");
lw->addItem("Capote);
创建QListWidget,并填充五个项目。
void ListWidget::addItem() {
QString c_text = QInputDialog::getText(this, "Item", "Enter new item");
QString s_text = c_text.simplified();
if (!s_text.isEmpty()) {
lw->addItem(s_text);
int r = lw->count() - 1;
lw->setCurrentRow(r);
}
}
addItem()方法将一个新项目添加到列表小部件。 该方法会弹出一个输入对话框。 该对话框返回一个字符串值。 我们使用simplified()方法从字符串中删除可能的空格。 如果返回的字符串不为空,则将其添加到列表末尾的列表小部件中。 最后,我们使用setCurrentRow()方法突出显示当前插入的项目。
void ListWidget::renameItem() {
QListWidgetItem *curitem = lw->currentItem();
int r = lw->row(curitem);
QString c_text = curitem->text();
QString r_text = QInputDialog::getText(this, "Item",
"Enter new item", QLineEdit::Normal, c_text);
QString s_text = r_text.simplified();
if (!s_text.isEmpty()) {
QListWidgetItem *item = lw->takeItem(r);
delete item;
lw->insertItem(r, s_text);
lw->setCurrentRow(r);
}
}
重命名项目包括几个步骤。 首先,我们使用currentItem()方法获取当前项目。 我们得到项目的文本和项目所在的行。 该项目的文本显示在QInputDialog对话框中。 从对话框返回的字符串由simplified()方法处理,以删除潜在的空格。 然后,我们使用takeItem()方法删除旧项目,然后将其替换为insertItem()方法。 我们删除了takeItem()方法删除的项目,因为删除的项目不再由 Qt 管理。 最后,setCurrentRow()选择新项目。
void ListWidget::removeItem() {
int r = lw->currentRow();
if (r != -1) {
QListWidgetItem *item = lw->takeItem(r);
delete item;
}
}
removeItem()从列表中删除特定项目。 首先,我们使用currentRow()方法获得当前选中的行。 (如果没有更多的行,则返回 -1。)使用takeItem()方法删除当前选择的项目。
void ListWidget::clearItems(){
if (lw->count() != 0) {
lw->clear();
}
}
clear()方法从列表小部件中删除所有项目。
main.cpp
#include <QApplication>
#include "listwidget.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
ListWidget window;
window.setWindowTitle("QListWidget");
window.show();
return app.exec();
}
这是主文件。

图:QListWidget
QProgressBar
QProgressBar用于向用户指示操作进度。
progressbar.h
#pragma once
#include <QWidget>
#include <QProgressBar>
#include <QPushButton>
class ProgressBarEx : public QWidget {
Q_OBJECT
public:
ProgressBarEx(QWidget *parent = 0);
private:
int progress;
QTimer *timer;
QProgressBar *pbar;
QPushButton *startBtn;
QPushButton *stopBtn;
static const int DELAY = 200;
static const int MAX_VALUE = 100;
void updateBar();
void startMyTimer();
void stopMyTimer();
};
该示例的头文件。
progressbar.cpp
#include <QProgressBar>
#include <QTimer>
#include <QGridLayout>
#include "progressbar.h"
ProgressBarEx::ProgressBarEx(QWidget *parent)
: QWidget(parent) {
progress = 0;
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &ProgressBarEx::updateBar);
QGridLayout *grid = new QGridLayout(this);
grid->setColumnStretch(2, 1);
pbar = new QProgressBar();
grid->addWidget(pbar, 0, 0, 1, 3);
startBtn = new QPushButton("Start", this);
connect(startBtn, &QPushButton::clicked, this, &ProgressBarEx::startMyTimer);
grid->addWidget(startBtn, 1, 0, 1, 1);
stopBtn = new QPushButton("Stop", this);
connect(stopBtn, &QPushButton::clicked, this, &ProgressBarEx::stopMyTimer);
grid->addWidget(stopBtn, 1, 1);
}
void ProgressBarEx::startMyTimer() {
if (progress >= MAX_VALUE) {
progress = 0;
pbar->setValue(0);
}
if (!timer->isActive()) {
startBtn->setEnabled(false);
stopBtn->setEnabled(true);
timer->start(DELAY);
}
}
void ProgressBarEx::stopMyTimer() {
if (timer->isActive()) {
startBtn->setEnabled(true);
stopBtn->setEnabled(false);
timer->stop();
}
}
void ProgressBarEx::updateBar() {
progress++;
if (progress <= MAX_VALUE) {
pbar->setValue(progress);
} else {
timer->stop();
startBtn->setEnabled(true);
stopBtn->setEnabled(false);
}
}
在示例中,我们有一个QProgressBar和两个按钮。 一键启动计时器,计时器依次更新进度条。 其他按钮停止计时器。
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &ProgressBarEx::updateBar);
QTimer用于控制QProgressBar小部件。
pbar = new QProgressBar();
创建一个QProgressBar的实例。 默认的最小值和最大值是 0 和 100。
if (!timer->isActive()) {
startBtn->setEnabled(false);
stopBtn->setEnabled(true);
timer->start(DELAY);
}
根据进度条的状态,按钮是启用还是禁用。 这是通过setEnabled()方法完成的。
void ProgressBarEx::updateBar() {
progress++;
if (progress <= MAX_VALUE) {
pbar->setValue(progress);
} else {
timer->stop();
startBtn->setEnabled(true);
stopBtn->setEnabled(false);
}
}
进度存储在progress变量中。 setValue()更新进度条的当前值。
main.cpp
#include <QApplication>
#include "progressbar.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
ProgressBarEx window;
window.resize(250, 150);
window.setWindowTitle("QProgressBar");
window.show();
return app.exec();
}
这是主文件。

图:QProgressBar
QPixmap
QPixmap是用于处理图像的小部件之一。 它经过优化,可在屏幕上显示图像。 在我们的代码示例中,我们将使用QPixmap在窗口上显示图像。
pixmap.h
#pragma once
#include <QWidget>
class Pixmap : public QWidget {
public:
Pixmap(QWidget *parent = 0);
};
该示例的头文件。
pixmap.cpp
#include <QPixmap>
#include <QLabel>
#include <QHBoxLayout>
#include "pixmap.h"
Pixmap::Pixmap(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
QPixmap pixmap("bojnice.jpg");
QLabel *label = new QLabel(this);
label->setPixmap(pixmap);
hbox->addWidget(label, 0, Qt::AlignTop);
}
我们显示了位于斯洛伐克中部的一座著名城堡的图像。
QPixmap pixmap("bojnice.jpg");
QLabel *label = new QLabel(this);
label->setPixmap(pixmap);
我们创建一个像素图并将其放在标签小部件中。
main.cpp
#include <QApplication>
#include "pixmap.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Pixmap window;
window.setWindowTitle("QPixmap");
window.show();
return app.exec();
}
这是主文件。
QSplitter
QSplitter允许用户通过拖动子控件之间的边界来控制子控件的大小。 在我们的示例中,我们显示了由两个拆分器组成的三个QFrame小部件。
splitter.h
#pragma once
#include <QWidget>
class Splitter : public QWidget {
public:
Splitter(QWidget *parent = 0);
};
该示例的头文件。
splitter.cpp
#include <QFrame>
#include <QSplitter>
#include <QHBoxLayout>
#include "splitter.h"
Splitter::Splitter(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
QFrame *topleft = new QFrame(this);
topleft->setFrameShape(QFrame::StyledPanel);
QFrame *topright = new QFrame(this);
topright->setFrameShape(QFrame::StyledPanel);
QSplitter *splitter1 = new QSplitter(Qt::Horizontal, this);
splitter1->addWidget(topleft);
splitter1->addWidget(topright);
QFrame *bottom = new QFrame(this);
bottom->setFrameShape(QFrame::StyledPanel);
QSplitter *splitter2 = new QSplitter(Qt::Vertical, this);
splitter2->addWidget(splitter1);
splitter2->addWidget(bottom);
QList<int> sizes({50, 100});
splitter2->setSizes(sizes);
hbox->addWidget(splitter2);
}
在示例中,我们有三个框架小部件和两个拆分器小部件。
QSplitter *splitter1 = new QSplitter(Qt::Horizontal, this);
splitter1->addWidget(topleft);
splitter1->addWidget(topright);
我们创建一个拆分器小部件,并将两个框架小部件添加到拆分器中。
QSplitter *splitter2 = new QSplitter(Qt::Vertical, this);
splitter2->addWidget(splitter1);
我们还可以将拆分器添加到另一个拆分器小部件。
QList<int> sizes({50, 100});
splitter2->setSizes(sizes);
使用setSizes()方法,我们设置拆分器的子窗口小部件的大小。
main.cpp
#include <QDesktopWidget>
#include <QApplication>
#include "splitter.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Splitter window;
window.resize(350, 300);
window.setWindowTitle("QSplitter");
window.show();
return app.exec();
}
这是主文件。

图:QSplitter
在某些桌面主题中,拆分器可能无法很好地显示。
QTableWidget
QTableWidget是电子表格应用中使用的唯一窗口小部件。 (也称为网格小部件)。 它是较复杂的小部件之一。 在这里,我们仅在窗口上显示小部件。
table.h
#pragma once
#include <QWidget>
class Table : public QWidget {
public:
Table(QWidget *parent = 0);
};
该示例的头文件。
table.cpp
#include <QHBoxLayout>
#include <QTableWidget>
#include "table.h"
Table::Table(QWidget *parent)
: QWidget(parent) {
QHBoxLayout *hbox = new QHBoxLayout(this);
QTableWidget *table = new QTableWidget(25, 25, this);
hbox->addWidget(table);
}
该示例在窗口上显示QTableWidget。
QTableWidget *table = new QTableWidget(25, 25, this);
在这里,我们创建具有 25 行 25 列的表小部件。
main.cpp
#include <QApplication>
#include "table.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Table window;
window.resize(400, 250);
window.setWindowTitle("QTableWidget");
window.show();
return app.exec();
}
这是主文件。

图:QTableWidget
在本章中,我们描述了其他几个 Qt5 小部件。
Qt5 中的绘图
在 Qt5 C++ 编程教程的这一部分中,我们将进行绘图。
当我们在 Qt5 中进行绘图时,QPainter类非常有用。 对paintEvent()方法的反应是使用QPainter类完成的。
直线
在第一个示例中,我们将在窗口的客户区域上绘制一些线。
lines.h
#pragma once
#include <QWidget>
class Lines : public QWidget {
public:
Lines(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *event);
void drawLines(QPainter *qp);
};
这是头文件。
lines.cpp
#include <QPainter>
#include "lines.h"
Lines::Lines(QWidget *parent)
: QWidget(parent)
{ }
void Lines::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
QPainter qp(this);
drawLines(&qp);
}
void Lines::drawLines(QPainter *qp) {
QPen pen(Qt::black, 2, Qt::SolidLine);
qp->setPen(pen);
qp->drawLine(20, 40, 250, 40);
pen.setStyle(Qt::DashLine);
qp->setPen(pen);
qp->drawLine(20, 80, 250, 80);
pen.setStyle(Qt::DashDotLine);
qp->setPen(pen);
qp->drawLine(20, 120, 250, 120);
pen.setStyle(Qt::DotLine);
qp->setPen(pen);
qp->drawLine(20, 160, 250, 160);
pen.setStyle(Qt::DashDotDotLine);
qp->setPen(pen);
qp->drawLine(20, 200, 250, 200);
QVector<qreal> dashes;
qreal space = 4;
dashes << 1 << space << 5 << space;
pen.setStyle(Qt::CustomDashLine);
pen.setDashPattern(dashes);
qp->setPen(pen);
qp->drawLine(20, 240, 250, 240);
}
我们在窗口上画了六行; 每条线都有不同的笔样式。
void Lines::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
QPainter qp(this);
drawLines(&qp);
}
更新小部件时将调用paintEvent()。 在这里我们创建QPainter对象并进行绘制。 由于我们不使用QPaintEvent对象,因此可以通过Q_UNUSED宏来抑制编译器警告。 实际图形委托给drawLines()方法。
QPen pen(Qt::black, 2, Qt::SolidLine);
qp->setPen(pen);
我们创建一个QPen对象。 笔是实心的,2px 粗,是黑色的。 笔用于绘制线条和形状轮廓。 使用setPen()方法将笔设置为画家对象。
qp->drawLine(20, 40, 250, 40);
drawLine()方法画一条线。 四个参数是窗口上两个点的坐标。
pen.setStyle(Qt::DashLine);
QPen行的setStyle()方法设置笔样式-Qt::DashLine。
main.cpp
#include <QApplication>
#include "lines.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Lines window;
window.resize(280, 270);
window.setWindowTitle("Lines");
window.show();
return app.exec();
}
这是主文件。

图:直线
颜色
颜色是代表红色,绿色和蓝色(RGB)强度值的组合的对象。 有效的 RGB 值在 0 到 255 之间。在下面的示例中,我们绘制了九个矩形,其中填充了九种不同的颜色。
colours.h
#pragma once
#include <QWidget>
class Colours : public QWidget {
public:
Colours(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *e);
private:
void doPainting();
};
这是头文件。
colours.cpp
#include <QPainter>
#include "colours.h"
Colours::Colours(QWidget *parent)
: QWidget(parent)
{ }
void Colours::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
doPainting();
}
void Colours::doPainting() {
QPainter painter(this);
painter.setPen(QColor("#d4d4d4"));
painter.setBrush(QBrush("#c56c00"));
painter.drawRect(10, 15, 90, 60);
painter.setBrush(QBrush("#1ac500"));
painter.drawRect(130, 15, 90, 60);
painter.setBrush(QBrush("#539e47"));
painter.drawRect(250, 15, 90, 60);
painter.setBrush(QBrush("#004fc5"));
painter.drawRect(10, 105, 90, 60);
painter.setBrush(QBrush("#c50024"));
painter.drawRect(130, 105, 90, 60);
painter.setBrush(QBrush("#9e4757"));
painter.drawRect(250, 105, 90, 60);
painter.setBrush(QBrush("#5f3b00"));
painter.drawRect(10, 195, 90, 60);
painter.setBrush(QBrush("#4c4c4c"));
painter.drawRect(130, 195, 90, 60);
painter.setBrush(QBrush("#785f36"));
painter.drawRect(250, 195, 90, 60);
}
我们绘制九个不同颜色填充的矩形。 矩形的轮廓是灰色的。
painter.setBrush(QBrush("#c56c00"));
painter.drawRect(10, 15, 90, 60);
QBrush类定义QPainter绘制的形状的填充图案。 drawRect()方法绘制一个矩形。 它绘制一个矩形,其左上角位于 x,y 点,并具有给定的宽度和高度。 我们使用十六进制表示法来指定颜色值。
main.cpp
#include <QApplication>
#include "colours.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Colours window;
window.resize(360, 280);
window.setWindowTitle("Colours");
window.show();
return app.exec();
}
这是主文件。

图:颜色
图案
以下编程代码示例与上一个示例相似。 这次我们用各种预定义的图案填充矩形。
patterns.h
#pragma once
#include <QWidget>
class Patterns : public QWidget {
public:
Patterns(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *e);
private:
void doPainting();
};
这是头文件。
patterns.cpp
#include <QApplication>
#include <QPainter>
#include "patterns.h"
Patterns::Patterns(QWidget *parent)
: QWidget(parent)
{ }
void Patterns::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
doPainting();
}
void Patterns::doPainting() {
QPainter painter(this);
painter.setPen(Qt::NoPen);
painter.setBrush(Qt::HorPattern);
painter.drawRect(10, 15, 90, 60);
painter.setBrush(Qt::VerPattern);
painter.drawRect(130, 15, 90, 60);
painter.setBrush(Qt::CrossPattern);
painter.drawRect(250, 15, 90, 60);
painter.setBrush(Qt::Dense7Pattern);
painter.drawRect(10, 105, 90, 60);
painter.setBrush(Qt::Dense6Pattern);
painter.drawRect(130, 105, 90, 60);
painter.setBrush(Qt::Dense5Pattern);
painter.drawRect(250, 105, 90, 60);
painter.setBrush(Qt::BDiagPattern);
painter.drawRect(10, 195, 90, 60);
painter.setBrush(Qt::FDiagPattern);
painter.drawRect(130, 195, 90, 60);
painter.setBrush(Qt::DiagCrossPattern);
painter.drawRect(250, 195, 90, 60);
}
我们用各种画笔图案绘制了九个矩形。
painter.setBrush(Qt::HorPattern);
painter.drawRect(10, 15, 90, 60);
我们绘制具有特定图案的矩形。 Qt::HorPattern是用于创建水平线条图案的常数。
main.cpp
#include <QDesktopWidget>
#include <QApplication>
#include "patterns.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Patterns window;
window.resize(350, 280);
window.setWindowTitle("Patterns");
window.show();
return app.exec();
}
这是主文件。

图:图案
透明矩形
透明性是指能够透视材料的质量。 了解透明度的最简单方法是想象一块玻璃或水。 从技术上讲,光线可以穿过玻璃,这样我们就可以看到玻璃后面的物体。
在计算机图形学中,我们可以使用 alpha 合成来实现透明效果。 Alpha 合成是将图像与背景组合以创建部分透明外观的过程。 合成过程使用 Alpha 通道。
transparent_rectangles.h
#pragma once
#include <QWidget>
class TransparentRectangles : public QWidget {
public:
TransparentRectangles(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *event);
void doPainting();
};
这是头文件。
transparent_rectangles.cpp
#include <QApplication>
#include <QPainter>
#include <QPainterPath>
#include "transparent_rectangles.h"
TransparentRectangles::TransparentRectangles(QWidget *parent)
: QWidget(parent)
{ }
void TransparentRectangles::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
doPainting();
}
void TransparentRectangles::doPainting() {
QPainter painter(this);
for (int i=1; i<=11; i++) {
painter.setOpacity(i*0.1);
painter.fillRect(50*i, 20, 40, 40, Qt::darkGray);
}
}
该示例绘制了十个透明度不同的矩形。
painter.setOpacity(i*0.1);
setOpacity()方法设置画家的不透明度。 该值应在 0.0 到 1.0 的范围内,其中 0.0 是完全透明的,而 1.0 是完全不透明的。
main.cpp
#include <QDesktopWidget>
#include <QApplication>
#include "transparent_rectangles.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
TransparentRectangles window;
window.resize(630, 90);
window.setWindowTitle("Transparent rectangles");
window.show();
return app.exec();
}
这是主文件。

图:透明矩形
甜甜圈
在下面的示例中,我们将创建一个甜甜圈形状。
donut.h
#pragma once
#include <QWidget>
class Donut : public QWidget {
public:
Donut(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *e);
private:
void doPainting();
};
这是头文件。
donut.cpp
#include <QApplication>
#include <QPainter>
#include "donut.h"
Donut::Donut(QWidget *parent)
: QWidget(parent)
{ }
void Donut::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
doPainting();
}
void Donut::doPainting() {
QPainter painter(this);
painter.setPen(QPen(QBrush("#535353"), 0.5));
painter.setRenderHint(QPainter::Antialiasing);
int h = height();
int w = width();
painter.translate(QPoint(w/2, h/2));
for (qreal rot=0; rot < 360.0; rot+=5.0 ) {
painter.drawEllipse(-125, -40, 250, 80);
painter.rotate(5.0);
}
}
“甜甜圈”是类似于此类食物的高级几何形状。 我们通过绘制 72 个旋转椭圆来创建它。
painter.setRenderHint(QPainter::Antialiasing);
我们将以抗锯齿模式绘制。 渲染将具有更高的质量。
int h = height();
int w = width();
painter.translate(QPoint(w/2, h/2));
这些行将坐标系的起点移到窗口的中间。 默认情况下,它位于 0、0 点。 换句话说,在窗口的左上角。 通过移动坐标系,绘图会容易得多。
for (qreal rot=0; rot < 360.0; rot+=5.0 ) {
painter.drawEllipse(-125, -40, 250, 80);
painter.rotate(5.0);
}
在此循环中,我们绘制了 72 个旋转的椭圆。
main.cpp
#include <QDesktopWidget>
#include <QApplication>
#include "donut.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Donut window;
window.resize(350, 280);
window.setWindowTitle("Donut");
window.show();
return app.exec();
}
这是主文件。
形状
Qt5 绘图 API 可以绘制各种形状。 以下编程代码示例显示了其中一些。
shapes.h
#pragma once
#include <QWidget>
class Shapes : public QWidget {
public:
Shapes(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *e);
private:
void doPainting();
};
这是头文件。
shapes.cpp
#include <QApplication>
#include <QPainter>
#include <QPainterPath>
#include "shapes.h"
Shapes::Shapes(QWidget *parent)
: QWidget(parent)
{ }
void Shapes::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
doPainting();
}
void Shapes::doPainting() {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(QPen(QBrush("#888"), 1));
painter.setBrush(QBrush(QColor("#888")));
QPainterPath path1;
path1.moveTo(5, 5);
path1.cubicTo(40, 5, 50, 50, 99, 99);
path1.cubicTo(5, 99, 50, 50, 5, 5);
painter.drawPath(path1);
painter.drawPie(130, 20, 90, 60, 30*16, 120*16);
painter.drawChord(240, 30, 90, 60, 0, 16*180);
painter.drawRoundRect(20, 120, 80, 50);
QPolygon polygon({QPoint(130, 140), QPoint(180, 170), QPoint(180, 140),
QPoint(220, 110), QPoint(140, 100)});
painter.drawPolygon(polygon);
painter.drawRect(250, 110, 60, 60);
QPointF baseline(20, 250);
QFont font("Georgia", 55);
QPainterPath path2;
path2.addText(baseline, font, "Q");
painter.drawPath(path2);
painter.drawEllipse(140, 200, 60, 60);
painter.drawEllipse(240, 200, 90, 60);
}
我们绘制了九种不同的形状。
QPainterPath path1;
path1.moveTo(5, 5);
path1.cubicTo(40, 5, 50, 50, 99, 99);
path1.cubicTo(5, 99, 50, 50, 5, 5);
painter.drawPath(path1);
QPainterPath是用于创建复杂形状的对象。 我们用它来绘制贝塞尔曲线。
painter.drawPie(130, 20, 90, 60, 30*16, 120*16);
painter.drawChord(240, 30, 90, 60, 0, 16*180);
painter.drawRoundRect(20, 120, 80, 50);
这些代码行绘制了一个饼图,一个和弦和一个圆角矩形。
QPolygon polygon({QPoint(130, 140), QPoint(180, 170), QPoint(180, 140),
QPoint(220, 110), QPoint(140, 100)});
painter.drawPolygon(polygon);
在这里,我们使用drawPolygon()方法绘制一个多边形。 多边形由五个点组成。
QPointF baseline(20, 250);
QFont font("Georgia", 55);
QPainterPath path2;
path2.addText(baseline, font, "Q");
painter.drawPath(path2);
Qt5 允许基于字体字符创建路径。
painter.drawEllipse(140, 200, 60, 60);
painter.drawEllipse(240, 200, 90, 60);
drawEllipse()也会绘制一个椭圆和一个圆。 圆是椭圆的特例。 参数是矩形起点的 x 和 y 坐标以及椭圆边界矩形的宽度和高度。
main.cpp
#include <QDesktopWidget>
#include <QApplication>
#include "shapes.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Shapes window;
window.resize(350, 280);
window.setWindowTitle("Shapes");
window.show();
return app.exec();
}
这是示例的主文件。

图:形状
渐变
在计算机图形学中,渐变是从浅到深或从一种颜色到另一种颜色的阴影的平滑混合。 在 2D 绘图程序和绘图程序中,渐变用于创建彩色背景和特殊效果以及模拟灯光和阴影。
以下代码示例显示了如何创建线性渐变。
linear_gradients.h
#pragma once
#include <QWidget>
class LinearGradients : public QWidget {
public:
LinearGradients(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *e);
private:
void doPainting();
};
这是头文件。
linear_gradients.cpp
#include <QApplication>
#include <QPainter>
#include "linear_gradients.h"
LinearGradients::LinearGradients(QWidget *parent)
: QWidget(parent)
{ }
void LinearGradients::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
doPainting();
}
void LinearGradients::doPainting() {
QPainter painter(this);
QLinearGradient grad1(0, 20, 0, 110);
grad1.setColorAt(0.1, Qt::black);
grad1.setColorAt(0.5, Qt::yellow);
grad1.setColorAt(0.9, Qt::black);
painter.fillRect(20, 20, 300, 90, grad1);
QLinearGradient grad2(0, 55, 250, 0);
grad2.setColorAt(0.2, Qt::black);
grad2.setColorAt(0.5, Qt::red);
grad2.setColorAt(0.8, Qt::black);
painter.fillRect(20, 140, 300, 100, grad2);
}
在代码示例中,我们绘制了两个矩形,并用线性渐变填充它们。
QLinearGradient grad1(0, 20, 0, 110);
QLinearGradient构造线性渐变,并在两个点之间作为参数提供插值区域。
grad1.setColorAt(0.1, Qt::black);
grad1.setColorAt(0.5, Qt::yellow);
grad1.setColorAt(0.9, Qt::black);
使用停止点定义渐变中的颜色。 setColorAt()在给定位置以给定颜色创建一个停止点。
painter.fillRect(20, 20, 300, 90, grad1);
我们用渐变填充矩形。
main.cpp
#include <QApplication>
#include "linear_gradients.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
LinearGradients window;
window.resize(350, 260);
window.setWindowTitle("Linear gradients");
window.show();
return app.exec();
}
这是主文件。

图:线性渐变
径向渐变
径向渐变是两个圆之间颜色或阴影的混合。
radial_gradient.h
#pragma once
#include <QWidget>
class RadialGradient : public QWidget {
public:
RadialGradient(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *e);
private:
void doPainting();
};
这是头文件。
radial_gradient.cpp
#include <QApplication>
#include <QPainter>
#include "radial_gradient.h"
RadialGradient::RadialGradient(QWidget *parent)
: QWidget(parent)
{ }
void RadialGradient::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
doPainting();
}
void RadialGradient::doPainting() {
QPainter painter(this);
int h = height();
int w = width();
QRadialGradient grad1(w/2, h/2, 80);
grad1.setColorAt(0, QColor("#032E91"));
grad1.setColorAt(0.3, Qt::white);
grad1.setColorAt(1, QColor("#032E91"));
painter.fillRect(0, 0, w, h, grad1);
}
该示例创建了一个径向渐变; 渐变从窗口的中心扩散。
QRadialGradient grad1(w/2, h/2, 80);
QRadialGradient创建一个径向渐变; 它在焦点和围绕它的圆上的端点之间插入颜色。 参数是圆心和半径的坐标。 焦点位于圆的中心。
grad1.setColorAt(0, QColor("#032E91"));
grad1.setColorAt(0.3, Qt::white);
grad1.setColorAt(1, QColor("#032E91"));
setColorAt()方法定义彩色挡块。
painter.fillRect(0, 0, w, h, grad1);
窗口的整个区域都充满了径向渐变。
main.cpp
#include <QApplication>
#include "radial_gradient.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
RadialGradient window;
window.resize(300, 250);
window.setWindowTitle("Radial gradient");
window.show();
return app.exec();
}
这是主文件。

图:径向渐变
泡泡
在本 C++ Qt5 教程章节的最后一个示例中,我们创建一个泡泡效果。 该示例显示一个不断增长的居中文本,该文本从某个点逐渐淡出。 这是一种非常常见的效果,您通常可以在网络上的 Flash 动画中看到这种效果。
puff.h
#pragma once
#include <QWidget>
class Puff : public QWidget {
public:
Puff(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *event);
void timerEvent(QTimerEvent *event);
private:
int x;
qreal opacity;
int timerId;
void doPainting();
};
在头文件中,我们定义了两个事件处理器:绘图事件处理器和计时器处理器。
puff.cpp
#include <QPainter>
#include <QTimer>
#include <QTextStream>
#include "puff.h"
Puff::Puff(QWidget *parent)
: QWidget(parent) {
x = 1;
opacity = 1.0;
timerId = startTimer(15);
}
void Puff::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
doPainting();
}
void Puff::doPainting() {
QPainter painter(this);
QTextStream out(stdout);
QString text = "ZetCode";
painter.setPen(QPen(QBrush("#575555"), 1));
QFont font("Courier", x, QFont::DemiBold);
QFontMetrics fm(font);
int textWidth = fm.width(text);
painter.setFont(font);
if (x > 10) {
opacity -= 0.01;
painter.setOpacity(opacity);
}
if (opacity <= 0) {
killTimer(timerId);
out << "timer stopped" << endl;
}
int h = height();
int w = width();
painter.translate(QPoint(w/2, h/2));
painter.drawText(-textWidth/2, 0, text);
}
void Puff::timerEvent(QTimerEvent *e) {
Q_UNUSED(e);
x += 1;
repaint();
}
这是puff.cpp文件。
Puff::Puff(QWidget *parent)
: QWidget(parent) {
x = 1;
opacity = 1.0;
timerId = startTimer(15);
}
在构造器中,我们启动计时器。 每 15ms 会生成一个计时器事件。
void Puff::timerEvent(QTimerEvent *e) {
Q_UNUSED(e);
x += 1;
repaint();
}
在timerEvent()内,我们增加字体大小并重新绘制小部件。
if (x > 10) {
opacity -= 0.01;
painter.setOpacity(opacity);
}
如果字体大小大于 10 磅,我们将逐渐降低不透明度; 文字开始消失。
if (opacity <= 0) {
killTimer(timerId);
out << "timer stopped" << endl;
}
如果文字完全消失,我们将杀死计时器。
main.cpp
#include <QApplication>
#include "puff.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Puff window;
window.resize(350, 280);
window.setWindowTitle("Puff");
window.show();
return app.exec();
}
这是主文件。
本章是关于 Qt5 中的绘图的。
Qt5 中的自定义小部件
在 Qt5 C++ 编程教程的这一部分中,我们将创建一个自定义小部件。
大多数工具箱通常仅提供最常用的小部件,例如按钮,文本小部件或滑块。 没有工具包可以提供所有可能的小部件。 程序员必须自己创建此类小部件。 他们使用工具箱提供的绘图工具来完成此任务。 有两种可能:程序员可以修改或增强现有的小部件,或者可以从头开始创建自定义小部件。
刻录小部件
在下一个示例中,我们创建一个自定义的刻录小部件。 可以在 Nero 或 K3B 之类的应用中看到此小部件。 该小部件将从头开始创建。
burning.h
#pragma once
#include <QWidget>
#include <QSlider>
#include <QFrame>
#include "widget.h"
class Burning : public QFrame {
Q_OBJECT
public:
Burning(QWidget *parent = 0);
int getCurrentWidth();
public slots:
void valueChanged(int);
private:
QSlider *slider;
Widget *widget;
int cur_width;
void initUI();
};
这是示例主窗口的头文件。
public:
Burning(QWidget *parent = 0);
int getCurrentWidth();
getCurrentWidth()方法将用于确定滑块值。
private:
QSlider *slider;
Widget *widget;
int cur_width;
void initUI();
窗口的工作区上将有两个小部件:内置滑块小部件和自定义小部件。 cur_width变量将保存滑块中的当前值。 绘制自定义窗口小部件时使用此值。
burning.cpp
#include <QVBoxLayout>
#include <QHBoxLayout>
#include "burning.h"
Burning::Burning(QWidget *parent)
: QFrame(parent) {
initUI();
}
void Burning::initUI() {
const int MAX_VALUE = 750;
cur_width = 0;
slider = new QSlider(Qt::Horizontal , this);
slider->setMaximum(MAX_VALUE);
slider->setGeometry(50, 50, 130, 30);
connect(slider, &QSlider::valueChanged, this, &Burning::valueChanged);
QVBoxLayout *vbox = new QVBoxLayout(this);
QHBoxLayout *hbox = new QHBoxLayout();
vbox->addStretch(1);
widget = new Widget(this);
hbox->addWidget(widget, 0);
vbox->addLayout(hbox);
setLayout(vbox);
}
void Burning::valueChanged(int val) {
cur_width = val;
widget->repaint();
}
int Burning::getCurrentWidth() {
return cur_width;
}
在这里,我们构建示例的主窗口。
connect(slider, &QSlider::valueChanged, this, &Burning::valueChanged);
当我们移动滑块时,将执行valueChanged()槽。
void Burning::valueChanged(int val) {
cur_width = val;
widget->repaint();
}
更改滑块的值时,我们将存储新值并重新绘制自定义窗口小部件。
widget.h
#pragma once
#include <QFrame>
class Burning;
class Widget : public QFrame {
public:
Widget(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *e);
void drawWidget(QPainter &qp);
private:
QWidget *m_parent;
Burning *burn;
static const int DISTANCE = 19;
static const int LINE_WIDTH = 5;
static const int DIVISIONS = 10;
static const float FULL_CAPACITY = 700;
static const float MAX_CAPACITY = 750;
};
这是自定义刻录窗口小部件的头文件。
private:
QWidget *m_parent;
Burning *burn;
我们存储一个指向父窗口小部件的指针。 我们通过该指针获得cur_width。
const int DISTANCE = 19;
const int LINE_WIDTH = 5;
const int DIVISIONS = 10;
const float FULL_CAPACITY = 700;
const float MAX_CAPACITY = 750;
这些是重要的常数。 DISTANCE是比例尺上的数字与其父边界顶部之间的距离。 LINE_WIDTH是垂直线的宽度。 DIVISIONS是秤的数量。 FULL_CAPACITY是媒体的容量。 达到目标后,就会发生过度刻录。 这通过红色可视化。 MAX_CAPACITY是介质的最大容量。
widget.cpp
#include <QtGui>
#include "widget.h"
#include "burning.h"
const int PANEL_HEIGHT = 30;
Widget::Widget(QWidget *parent)
: QFrame(parent) {
m_parent = parent;
setMinimumHeight(PANEL_HEIGHT);
}
void Widget::paintEvent(QPaintEvent *e) {
QPainter qp(this);
drawWidget(qp);
QFrame::paintEvent(e);
}
void Widget::drawWidget(QPainter &qp) {
QString num[] = { "75", "150", "225", "300", "375", "450",
"525", "600", "675" };
int asize = sizeof(num)/sizeof(num[1]);
QColor redColor(255, 175, 175);
QColor yellowColor(255, 255, 184);
int width = size().width();
Burning *burn = (Burning *) m_parent;
int cur_width = burn->getCurrentWidth();
int step = (int) qRound((double)width / DIVISIONS);
int till = (int) ((width / MAX_CAPACITY) * cur_width);
int full = (int) ((width / MAX_CAPACITY) * FULL_CAPACITY);
if (cur_width >= FULL_CAPACITY) {
qp.setPen(yellowColor);
qp.setBrush(yellowColor);
qp.drawRect(0, 0, full, 30);
qp.setPen(redColor);
qp.setBrush(redColor);
qp.drawRect(full, 0, till-full, PANEL_HEIGHT);
} else if (till > 0) {
qp.setPen(yellowColor);
qp.setBrush(yellowColor);
qp.drawRect(0, 0, till, PANEL_HEIGHT);
}
QColor grayColor(90, 80, 60);
qp.setPen(grayColor);
for (int i=1; i <=asize; i++) {
qp.drawLine(i*step, 0, i*step, LINE_WIDTH);
QFont newFont = font();
newFont.setPointSize(7);
setFont(newFont);
QFontMetrics metrics(font());
int w = metrics.width(num[i-1]);
qp.drawText(i*step-w/2, DISTANCE, num[i-1]);
}
}
在这里,我们绘制自定义窗口小部件。 我们绘制矩形,垂直线和数字。
void Widget::paintEvent(QPaintEvent *e) {
QPainter qp(this);
drawWidget(qp);
QFrame::paintEvent(e);
}
自定义窗口小部件的图形委托给drawWidget()方法。
QString num[] = { "75", "150", "225", "300", "375", "450",
"525", "600", "675" };
我们使用这些数字来构建刻录小部件的比例。
int width = size().width();
我们得到小部件的宽度。 自定义窗口小部件的宽度是动态的。 用户可以调整大小。
Burning *burn = (Burning *) m_parent;
int cur_width = burn->getCurrentWidth();
我们得到cur_width值。
int till = (int) ((width / MAX_CAPACITY) * cur_width);
int full = (int) ((width / MAX_CAPACITY) * FULL_CAPACITY);
我们使用width变量在比例尺值和自定义小部件的度量之间进行转换。
qp.setPen(redColor);
qp.setBrush(redColor);
qp.drawRect(full, 0, till-full, PANEL_HEIGHT);
这三行画出红色矩形,表示过度燃烧。
qp.drawLine(i*step, 0, i*step, LINE_WIDTH);
在这里,我们画出小的垂直线。
QFontMetrics metrics(font());
int w = metrics.width(num[i-1]);
qp.drawText(i*step-w/2, DISTANCE, num[i-1]);
在这里,我们绘制刻度的数字。 为了精确定位数字,我们必须获得字符串的宽度。
main.cpp
#include <QApplication>
#include "burning.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Burning window;
window.resize(370, 200);
window.setWindowTitle("The Burning widget");
window.show();
return app.exec();
}
这是主文件。

图:刻录小部件
在 Qt5 教程的这一部分中,我们创建了一个自定义的刻录小部件。
Qt5 中的贪食蛇
在 Qt5 教程的这一部分中,我们创建一个贪食蛇游戏克隆。
贪食蛇
贪食蛇是较旧的经典视频游戏。 它最初是在 70 年代后期创建的。 后来它被带到 PC 上。 在这个游戏中,玩家控制蛇。 目的是尽可能多地吃苹果。 蛇每次吃一个苹果,它的身体就会长大。 蛇必须避开墙壁和自己的身体。 该游戏有时称为 Nibbles。
开发
蛇的每个关节的大小为 10 像素。 蛇由光标键控制。 最初,蛇具有三个关节。 如果游戏结束,则在面板中间显示"Game Over"消息。
Snake.h
#pragma once
#include <QWidget>
#include <QKeyEvent>
class Snake : public QWidget {
public:
Snake(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *);
void timerEvent(QTimerEvent *);
void keyPressEvent(QKeyEvent *);
private:
QImage dot;
QImage head;
QImage apple;
static const int B_WIDTH = 300;
static const int B_HEIGHT = 300;
static const int DOT_SIZE = 10;
static const int ALL_DOTS = 900;
static const int RAND_POS = 29;
static const int DELAY = 140;
int timerId;
int dots;
int apple_x;
int apple_y;
int x[ALL_DOTS];
int y[ALL_DOTS];
bool leftDirection;
bool rightDirection;
bool upDirection;
bool downDirection;
bool inGame;
void loadImages();
void initGame();
void locateApple();
void checkApple();
void checkCollision();
void move();
void doDrawing();
void gameOver(QPainter &);
};
这是头文件。
static const int B_WIDTH = 300;
static const int B_HEIGHT = 300;
static const int DOT_SIZE = 10;
static const int ALL_DOTS = 900;
static const int RAND_POS = 29;
static const int DELAY = 140;
B_WIDTH和B_HEIGHT常数确定电路板的大小。 DOT_SIZE是苹果的大小和蛇的点。 ALL_DOTS常数定义了板上可能的最大点数(900 = (300 * 300) / (10 * 10))。 RAND_POS常数用于计算苹果的随机位置。 DELAY常数确定游戏的速度。
int x[ALL_DOTS];
int y[ALL_DOTS];
这两个数组保存着蛇所有关节的 x 和 y 坐标。
snake.cpp
#include <QPainter>
#include <QTime>
#include "snake.h"
Snake::Snake(QWidget *parent) : QWidget(parent) {
setStyleSheet("background-color:black;");
leftDirection = false;
rightDirection = true;
upDirection = false;
downDirection = false;
inGame = true;
resize(B_WIDTH, B_HEIGHT);
loadImages();
initGame();
}
void Snake::loadImages() {
dot.load("dot.png");
head.load("head.png");
apple.load("apple.png");
}
void Snake::initGame() {
dots = 3;
for (int z = 0; z < dots; z++) {
x[z] = 50 - z * 10;
y[z] = 50;
}
locateApple();
timerId = startTimer(DELAY);
}
void Snake::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
doDrawing();
}
void Snake::doDrawing() {
QPainter qp(this);
if (inGame) {
qp.drawImage(apple_x, apple_y, apple);
for (int z = 0; z < dots; z++) {
if (z == 0) {
qp.drawImage(x[z], y[z], head);
} else {
qp.drawImage(x[z], y[z], dot);
}
}
} else {
gameOver(qp);
}
}
void Snake::gameOver(QPainter &qp) {
QString message = "Game over";
QFont font("Courier", 15, QFont::DemiBold);
QFontMetrics fm(font);
int textWidth = fm.width(message);
qp.setFont(font);
int h = height();
int w = width();
qp.translate(QPoint(w/2, h/2));
qp.drawText(-textWidth/2, 0, message);
}
void Snake::checkApple() {
if ((x[0] == apple_x) && (y[0] == apple_y)) {
dots++;
locateApple();
}
}
void Snake::move() {
for (int z = dots; z > 0; z--) {
x[z] = x[(z - 1)];
y[z] = y[(z - 1)];
}
if (leftDirection) {
x[0] -= DOT_SIZE;
}
if (rightDirection) {
x[0] += DOT_SIZE;
}
if (upDirection) {
y[0] -= DOT_SIZE;
}
if (downDirection) {
y[0] += DOT_SIZE;
}
}
void Snake::checkCollision() {
for (int z = dots; z > 0; z--) {
if ((z > 4) && (x[0] == x[z]) && (y[0] == y[z])) {
inGame = false;
}
}
if (y[0] >= B_HEIGHT) {
inGame = false;
}
if (y[0] < 0) {
inGame = false;
}
if (x[0] >= B_WIDTH) {
inGame = false;
}
if (x[0] < 0) {
inGame = false;
}
if(!inGame) {
killTimer(timerId);
}
}
void Snake::locateApple() {
QTime time = QTime::currentTime();
qsrand((uint) time.msec());
int r = qrand() % RAND_POS;
apple_x = (r * DOT_SIZE);
r = qrand() % RAND_POS;
apple_y = (r * DOT_SIZE);
}
void Snake::timerEvent(QTimerEvent *e) {
Q_UNUSED(e);
if (inGame) {
checkApple();
checkCollision();
move();
}
repaint();
}
void Snake::keyPressEvent(QKeyEvent *e) {
int key = e->key();
if ((key == Qt::Key_Left) && (!rightDirection)) {
leftDirection = true;
upDirection = false;
downDirection = false;
}
if ((key == Qt::Key_Right) && (!leftDirection)) {
rightDirection = true;
upDirection = false;
downDirection = false;
}
if ((key == Qt::Key_Up) && (!downDirection)) {
upDirection = true;
rightDirection = false;
leftDirection = false;
}
if ((key == Qt::Key_Down) && (!upDirection)) {
downDirection = true;
rightDirection = false;
leftDirection = false;
}
QWidget::keyPressEvent(e);
}
在snake.cpp文件中,我们有游戏的逻辑。
void Snake::loadImages() {
dot.load("dot.png");
head.load("head.png");
apple.load("apple.png");
}
在loadImages()方法中,我们获得了游戏的图像。 ImageIcon类用于显示 PNG 图像。
void Snake::initGame() {
dots = 3;
for (int z = 0; z < dots; z++) {
x[z] = 50 - z * 10;
y[z] = 50;
}
locateApple();
timerId = startTimer(DELAY);
}
在initGame()方法中,我们创建蛇,在板上随机放置一个苹果,然后启动计时器。
void Snake::checkApple() {
if ((x[0] == apple_x) && (y[0] == apple_y)) {
dots++;
locateApple();
}
}
如果苹果与头部碰撞,我们会增加蛇的关节数。 我们称locateApple()方法为随机放置一个新的Apple对象。
在move()方法中,我们有游戏的关键算法。 要了解它,请看一下蛇是如何运动的。 我们控制蛇的头。 我们可以使用光标键更改其方向。 其余关节在链上向上移动一个位置。 第二关节移动到第一个关节的位置,第三关节移动到第二个关节的位置,依此类推。
for (int z = dots; z > 0; z--) {
x[z] = x[(z - 1)];
y[z] = y[(z - 1)];
}
该代码将关节向上移动。
if (leftDirection) {
x[0] -= DOT_SIZE;
}
这条线将头向左移动。
在checkCollision()方法中,我们确定蛇是否击中了自己或撞墙之一。
for (int z = dots; z > 0; z--) {
if ((z > 4) && (x[0] == x[z]) && (y[0] == y[z])) {
inGame = false;
}
}
如果蛇用头撞到其关节之一,则游戏结束。
if (y[0] >= B_HEIGHT) {
inGame = false;
}
如果蛇击中了棋盘的底部,则游戏结束。
void Snake::timerEvent(QTimerEvent *e) {
Q_UNUSED(e);
if (inGame) {
checkApple();
checkCollision();
move();
}
repaint();
}
timerEvent()方法形成游戏周期。 假设游戏尚未结束,我们将执行碰撞检测并进行移动。 repaint()使窗口重新绘制。
if ((key == Qt::Key_Left) && (!rightDirection)) {
leftDirection = true;
upDirection = false;
downDirection = false;
}
如果单击左光标键,则将leftDirection变量设置为true。 move()函数中使用此变量来更改蛇对象的坐标。 还要注意,当蛇向右行驶时,我们不能立即向左转。
Snake.java
#include <QApplication>
#include "snake.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Snake window;
window.setWindowTitle("Snake");
window.show();
return app.exec();
}
这是主要的类。

图:贪食蛇
这是 Qt5 中的贪食蛇游戏。
Qt5 中的打砖块游戏
在 Qt5 教程的这一部分中,我们创建了一个简单的打砖块游戏克隆。
打砖块是 Atari Inc. 开发的一款街机游戏。该游戏创建于 1976 年。在该游戏中,玩家移动桨叶并弹跳球。 目的是销毁窗口顶部的砖块。
开发
在我们的游戏中,我们只有一个桨,一个球和三十个砖头。 计时器用于创建游戏周期。 我们不使用角度,而是仅更改方向:顶部,底部,左侧和右侧。 该代码的灵感来自 Nathan Dawson 在 PyGame 库中开发的 PyBreakout 游戏。
游戏是故意简单的。 没有奖金,等级或分数。 这样更容易理解。
Qt5 库用于创建计算机应用。 但是,它也可以用于创建游戏。 开发计算机游戏是学习有关 Qt5 的好方法。
paddle.h
#pragma once
#include <QImage>
#include <QRect>
class Paddle {
public:
Paddle();
~Paddle();
public:
void resetState();
void move();
void setDx(int);
QRect getRect();
QImage & getImage();
private:
QImage image;
QRect rect;
int dx;
static const int INITIAL_X = 200;
static const int INITIAL_Y = 360;
};
这是桨对象的头文件。 INITIAL_X和INITIAL_Y是代表桨状对象的初始坐标的常数。
paddle.cpp
#include <iostream>
#include "paddle.h"
Paddle::Paddle() {
dx = 0;
image.load("paddle.png");
rect = image.rect();
resetState();
}
Paddle::~Paddle() {
std::cout << ("Paddle deleted") << std::endl;
}
void Paddle::setDx(int x) {
dx = x;
}
void Paddle::move() {
int x = rect.x() + dx;
int y = rect.top();
rect.moveTo(x, y);
}
void Paddle::resetState() {
rect.moveTo(INITIAL_X, INITIAL_Y);
}
QRect Paddle::getRect() {
return rect;
}
QImage & Paddle::getImage() {
return image;
}
桨板可以向右或向左移动。
Paddle::Paddle() {
dx = 0;
image.load("paddle.png");
rect = image.rect();
resetState();
}
在构造器中,我们启动dx变量并加载桨图像。 我们得到图像矩形并将图像移动到其初始位置。
void Paddle::move() {
int x = rect.x() + dx;
int y = rect.top();
rect.moveTo(x, y);
}
move()方法移动桨的矩形。 移动方向由dx变量控制。
void Paddle::resetState() {
rect.moveTo(INITIAL_X, INITIAL_Y);
}
resetState()将拨片移动到其初始位置。
brick.h
#pragma once
#include <QImage>
#include <QRect>
class Brick {
public:
Brick(int, int);
~Brick();
public:
bool isDestroyed();
void setDestroyed(bool);
QRect getRect();
void setRect(QRect);
QImage & getImage();
private:
QImage image;
QRect rect;
bool destroyed;
};
这是砖对象的头文件。 如果销毁了积木,则destroyed变量将设置为true。
brick.cpp
#include <iostream>
#include "brick.h"
Brick::Brick(int x, int y) {
image.load("brickie.png");
destroyed = false;
rect = image.rect();
rect.translate(x, y);
}
Brick::~Brick() {
std::cout << ("Brick deleted") << std::endl;
}
QRect Brick::getRect() {
return rect;
}
void Brick::setRect(QRect rct) {
rect = rct;
}
QImage & Brick::getImage() {
return image;
}
bool Brick::isDestroyed() {
return destroyed;
}
void Brick::setDestroyed(bool destr) {
destroyed = destr;
}
Brick类代表砖对象。
Brick::Brick(int x, int y) {
image.load("brickie.png");
destroyed = false;
rect = image.rect();
rect.translate(x, y);
}
砖的构造器加载其图像,启动destroyed标志,然后将图像移至其初始位置。
bool Brick::isDestroyed() {
return destroyed;
}
砖块具有destroyed标志。 如果设置了destroyed标志,则不会在窗口上绘制砖块。
ball.h
#pragma once
#include <QImage>
#include <QRect>
class Ball {
public:
Ball();
~Ball();
public:
void resetState();
void autoMove();
void setXDir(int);
void setYDir(int);
int getXDir();
int getYDir();
QRect getRect();
QImage & getImage();
private:
int xdir;
int ydir;
QImage image;
QRect rect;
static const int INITIAL_X = 230;
static const int INITIAL_Y = 355;
static const int RIGHT_EDGE = 300;
};
这是球形对象的头文件。 xdir和ydir变量存储球的运动方向。
ball.cpp
#include <iostream>
#include "ball.h"
Ball::Ball() {
xdir = 1;
ydir = -1;
image.load("ball.png");
rect = image.rect();
resetState();
}
Ball::~Ball() {
std::cout << ("Ball deleted") << std::endl;
}
void Ball::autoMove() {
rect.translate(xdir, ydir);
if (rect.left() == 0) {
xdir = 1;
}
if (rect.right() == RIGHT_EDGE) {
xdir = -1;
}
if (rect.top() == 0) {
ydir = 1;
}
}
void Ball::resetState() {
rect.moveTo(INITIAL_X, INITIAL_Y);
}
void Ball::setXDir(int x) {
xdir = x;
}
void Ball::setYDir(int y) {
ydir = y;
}
int Ball::getXDir() {
return xdir;
}
int Ball::getYDir() {
return ydir;
}
QRect Ball::getRect() {
return rect;
}
QImage & Ball::getImage() {
return image;
}
Ball类表示球对象。
xdir = 1;
ydir = -1;
开始时,球向东北方向移动。
void Ball::autoMove() {
rect.translate(xdir, ydir);
if (rect.left() == 0) {
xdir = 1;
}
if (rect.right() == RIGHT_EDGE) {
xdir = -1;
}
if (rect.top() == 0) {
ydir = 1;
}
}
在每个游戏周期都会调用autoMove()方法来在屏幕上移动球。 如果它破坏了边界,球的方向就会改变。 如果球越过底边,则球不会反弹回来-游戏结束。
breakout.h
#pragma once
#include <QWidget>
#include <QKeyEvent>
#include "ball.h"
#include "brick.h"
#include "paddle.h"
class Breakout : public QWidget {
public:
Breakout(QWidget *parent = 0);
~Breakout();
protected:
void paintEvent(QPaintEvent *);
void timerEvent(QTimerEvent *);
void keyPressEvent(QKeyEvent *);
void keyReleaseEvent(QKeyEvent *);
void drawObjects(QPainter *);
void finishGame(QPainter *, QString);
void moveObjects();
void startGame();
void pauseGame();
void stopGame();
void victory();
void checkCollision();
private:
int x;
int timerId;
static const int N_OF_BRICKS = 30;
static const int DELAY = 10;
static const int BOTTOM_EDGE = 400;
Ball *ball;
Paddle *paddle;
Brick *bricks[N_OF_BRICKS];
bool gameOver;
bool gameWon;
bool gameStarted;
bool paused;
};
这是突破对象的头文件。
void keyPressEvent(QKeyEvent *);
void keyReleaseEvent(QKeyEvent *);
使用光标键控制桨。 在游戏中,我们监听按键和按键释放事件。
int x;
int timerId;
x变量存储桨的当前 x 位置。 timerId用于识别计时器对象。 当我们暂停游戏时,这是必需的。
static const int N_OF_BRICKS = 30;
N_OF_BRICKS常数存储游戏中的积木数量。
static const int DELAY = 10;
DELAY常数控制游戏的速度。
static const int BOTTOM_EDGE = 400;
当球通过底边时,比赛结束。
Ball *ball;
Paddle *paddle;
Brick *bricks[N_OF_BRICKS];
游戏包括一个球,一个球拍和一系列砖块。
bool gameOver;
bool gameWon;
bool gameStarted;
bool paused;
这四个变量代表游戏的各种状态。
breakout.cpp
#include <QPainter>
#include <QApplication>
#include "breakout.h"
Breakout::Breakout(QWidget *parent)
: QWidget(parent) {
x = 0;
gameOver = false;
gameWon = false;
paused = false;
gameStarted = false;
ball = new Ball();
paddle = new Paddle();
int k = 0;
for (int i=0; i<5; i++) {
for (int j=0; j<6; j++) {
bricks[k] = new Brick(j*40+30, i*10+50);
k++;
}
}
}
Breakout::~Breakout() {
delete ball;
delete paddle;
for (int i=0; i<N_OF_BRICKS; i++) {
delete bricks[i];
}
}
void Breakout::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
QPainter painter(this);
if (gameOver) {
finishGame(&painter, "Game lost");
} else if(gameWon) {
finishGame(&painter, "Victory");
}
else {
drawObjects(&painter);
}
}
void Breakout::finishGame(QPainter *painter, QString message) {
QFont font("Courier", 15, QFont::DemiBold);
QFontMetrics fm(font);
int textWidth = fm.width(message);
painter->setFont(font);
int h = height();
int w = width();
painter->translate(QPoint(w/2, h/2));
painter->drawText(-textWidth/2, 0, message);
}
void Breakout::drawObjects(QPainter *painter) {
painter->drawImage(ball->getRect(), ball->getImage());
painter->drawImage(paddle->getRect(), paddle->getImage());
for (int i=0; i<N_OF_BRICKS; i++) {
if (!bricks[i]->isDestroyed()) {
painter->drawImage(bricks[i]->getRect(), bricks[i]->getImage());
}
}
}
void Breakout::timerEvent(QTimerEvent *e) {
Q_UNUSED(e);
moveObjects();
checkCollision();
repaint();
}
void Breakout::moveObjects() {
ball->autoMove();
paddle->move();
}
void Breakout::keyReleaseEvent(QKeyEvent *e) {
int dx = 0;
switch (e->key()) {
case Qt::Key_Left:
dx = 0;
paddle->setDx(dx);
break;
case Qt::Key_Right:
dx = 0;
paddle->setDx(dx);
break;
}
}
void Breakout::keyPressEvent(QKeyEvent *e) {
int dx = 0;
switch (e->key()) {
case Qt::Key_Left:
dx = -1;
paddle->setDx(dx);
break;
case Qt::Key_Right:
dx = 1;
paddle->setDx(dx);
break;
case Qt::Key_P:
pauseGame();
break;
case Qt::Key_Space:
startGame();
break;
case Qt::Key_Escape:
qApp->exit();
break;
default:
QWidget::keyPressEvent(e);
}
}
void Breakout::startGame() {
if (!gameStarted) {
ball->resetState();
paddle->resetState();
for (int i=0; i<N_OF_BRICKS; i++) {
bricks[i]->setDestroyed(false);
}
gameOver = false;
gameWon = false;
gameStarted = true;
timerId = startTimer(DELAY);
}
}
void Breakout::pauseGame() {
if (paused) {
timerId = startTimer(DELAY);
paused = false;
} else {
paused = true;
killTimer(timerId);
}
}
void Breakout::stopGame() {
killTimer(timerId);
gameOver = true;
gameStarted = false;
}
void Breakout::victory() {
killTimer(timerId);
gameWon = true;
gameStarted = false;
}
void Breakout::checkCollision() {
if (ball->getRect().bottom() > BOTTOM_EDGE) {
stopGame();
}
for (int i=0, j=0; i<N_OF_BRICKS; i++) {
if (bricks[i]->isDestroyed()) {
j++;
}
if (j == N_OF_BRICKS) {
victory();
}
}
if ((ball->getRect()).intersects(paddle->getRect())) {
int paddleLPos = paddle->getRect().left();
int ballLPos = ball->getRect().left();
int first = paddleLPos + 8;
int second = paddleLPos + 16;
int third = paddleLPos + 24;
int fourth = paddleLPos + 32;
if (ballLPos < first) {
ball->setXDir(-1);
ball->setYDir(-1);
}
if (ballLPos >= first && ballLPos < second) {
ball->setXDir(-1);
ball->setYDir(-1*ball->getYDir());
}
if (ballLPos >= second && ballLPos < third) {
ball->setXDir(0);
ball->setYDir(-1);
}
if (ballLPos >= third && ballLPos < fourth) {
ball->setXDir(1);
ball->setYDir(-1*ball->getYDir());
}
if (ballLPos > fourth) {
ball->setXDir(1);
ball->setYDir(-1);
}
}
for (int i=0; i<N_OF_BRICKS; i++) {
if ((ball->getRect()).intersects(bricks[i]->getRect())) {
int ballLeft = ball->getRect().left();
int ballHeight = ball->getRect().height();
int ballWidth = ball->getRect().width();
int ballTop = ball->getRect().top();
QPoint pointRight(ballLeft + ballWidth + 1, ballTop);
QPoint pointLeft(ballLeft - 1, ballTop);
QPoint pointTop(ballLeft, ballTop -1);
QPoint pointBottom(ballLeft, ballTop + ballHeight + 1);
if (!bricks[i]->isDestroyed()) {
if(bricks[i]->getRect().contains(pointRight)) {
ball->setXDir(-1);
}
else if(bricks[i]->getRect().contains(pointLeft)) {
ball->setXDir(1);
}
if(bricks[i]->getRect().contains(pointTop)) {
ball->setYDir(1);
}
else if(bricks[i]->getRect().contains(pointBottom)) {
ball->setYDir(-1);
}
bricks[i]->setDestroyed(true);
}
}
}
}
在breakout.cpp文件中,我们有游戏逻辑。
int k = 0;
for (int i=0; i<5; i++) {
for (int j=0; j<6; j++) {
bricks[k] = new Brick(j*40+30, i*10+50);
k++;
}
}
在Breakout对象的构造器中,我们实例化了三十个砖块。
void Breakout::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
QPainter painter(this);
if (gameOver) {
finishGame(&painter, "Game lost");
} else if(gameWon) {
finishGame(&painter, "Victory");
}
else {
drawObjects(&painter);
}
}
根据gameOver和gameWon变量,我们要么用消息结束游戏,要么在窗口上绘制游戏对象。
void Breakout::finishGame(QPainter *painter, QString message) {
QFont font("Courier", 15, QFont::DemiBold);
QFontMetrics fm(font);
int textWidth = fm.width(message);
painter->setFont(font);
int h = height();
int w = width();
painter->translate(QPoint(w/2, h/2));
painter->drawText(-textWidth/2, 0, message);
}
finishGame()方法在窗口中心绘制一条最终消息。 它是"Game Over"或"Victory"。 QFontMetrics' width()用于计算字符串的宽度。
void Breakout::drawObjects(QPainter *painter) {
painter->drawImage(ball->getRect(), ball->getImage());
painter->drawImage(paddle->getRect(), paddle->getImage());
for (int i=0; i<N_OF_BRICKS; i++) {
if (!bricks[i]->isDestroyed()) {
painter->drawImage(bricks[i]->getRect(), bricks[i]->getImage());
}
}
}
drawObjects()方法在窗口上绘制游戏的所有对象:球,球拍和砖头。 这些对象由图像表示,drawImage()方法将它们绘制在窗口上。
void Breakout::timerEvent(QTimerEvent *e) {
Q_UNUSED(e);
moveObjects();
checkCollision();
repaint();
}
在timerEvent()中,我们移动对象,检查球是否与桨或砖相撞,并生成绘图事件。
void Breakout::moveObjects() {
ball->autoMove();
paddle->move();
}
moveObjects()方法移动球和桨对象。 他们自己的move方法被调用。
void Breakout::keyReleaseEvent(QKeyEvent *e) {
int dx = 0;
switch (e->key()) {
case Qt::Key_Left:
dx = 0;
paddle->setDx(dx);
break;
case Qt::Key_Right:
dx = 0;
paddle->setDx(dx);
break;
}
}
当播放器释放左光标键或右光标键时,我们将板的dx变量设置为零。 结果,桨停止运动。
void Breakout::keyPressEvent(QKeyEvent *e) {
int dx = 0;
switch (e->key()) {
case Qt::Key_Left:
dx = -1;
paddle->setDx(dx);
break;
case Qt::Key_Right:
dx = 1;
paddle->setDx(dx);
break;
case Qt::Key_P:
pauseGame();
break;
case Qt::Key_Space:
startGame();
break;
case Qt::Key_Escape:
qApp->exit();
break;
default:
QWidget::keyPressEvent(e);
}
}
在keyPressEvent()方法中,我们监听与游戏相关的按键事件。 左和右光标键移动桨状对象。 他们设置dx变量,该变量随后添加到桨的 x 坐标中。 P键暂停游戏,空格键启动游戏。 Esc键退出应用。
void Breakout::startGame() {
if (!gameStarted) {
ball->resetState();
paddle->resetState();
for (int i=0; i<N_OF_BRICKS; i++) {
bricks[i]->setDestroyed(false);
}
gameOver = false;
gameWon = false;
gameStarted = true;
timerId = startTimer(DELAY);
}
}
startGame()方法重置球和桨对象; 他们被转移到他们的初始位置。 在for循环中,我们将每个积木的destroyed标志重置为false,从而将它们全部显示在窗口中。 gameOver,gameWon和gameStarted变量获得其初始布尔值。 最后,使用startTimer()方法启动计时器。
void Breakout::pauseGame() {
if (paused) {
timerId = startTimer(DELAY);
paused = false;
} else {
paused = true;
killTimer(timerId);
}
}
pauseGame()用于暂停和开始暂停的游戏。 状态由paused变量控制。 我们还存储计时器的 ID。 为了暂停游戏,我们使用killTimer()方法终止计时器。 要重新启动它,我们调用startTimer()方法。
void Breakout::stopGame() {
killTimer(timerId);
gameOver = true;
gameStarted = false;
}
在stopGame()方法中,我们终止计时器并设置适当的标志。
void Breakout::checkCollision() {
if (ball->getRect().bottom() > BOTTOM_EDGE) {
stopGame();
}
...
}
在checkCollision()方法中,我们对游戏进行碰撞检测。 如果球撞到底边,则比赛结束。
for (int i=0, j=0; i<N_OF_BRICKS; i++) {
if (bricks[i]->isDestroyed()) {
j++;
}
if (j == N_OF_BRICKS) {
victory();
}
}
我们检查了多少砖被破坏了。 如果我们摧毁了所有积木,我们将赢得这场比赛。
if (ballLPos < first) {
ball->setXDir(-1);
ball->setYDir(-1);
}
如果球碰到了桨的第一部分,我们会将球的方向更改为西北。
if(bricks[i]->getRect().contains(pointTop)) {
ball->setYDir(1);
}
如果球撞击砖的底部,我们将改变球的 y 方向; 它下降了。
main.cpp
#include <QApplication>
#include "breakout.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Breakout window;
window.resize(300, 400);
window.setWindowTitle("Breakout");
window.show();
return app.exec();
}
这是主文件。

图:打砖块游戏
这是 Qt5 中的打砖块游戏。
Tkinter 教程
这是 Tkinter 教程。 它涵盖了使用 Tkinter 在 Python 中进行 GUI 编程的基础。 本教程适合初学者和中级程序员。 这些示例是用 OOP 编写的。 源和图像可从作者的 Github Tkinter-Examples 仓库中获得。
目录
Tkinter
Tkinter 是与 Tk GUI 工具包的 Python 绑定。 Tk 是 Tcl 语言的原始 GUI 库。 Tkinter 被实现为 Python 封装程序,围绕着嵌入在 Python 解释器中的完整 Tcl 解释器。
电子书
ZetCode 上有独特的电子书 Tkinter 编程; PDF 格式,包含 200 页和 89 个代码示例。
相关教程
Tkinter 教程-程序样式是 Tkinter 的程序编程样式教程。 Tkinter 中的长期运行任务中介绍了计算 Tkinter 中的Pi数。 在 ZetCode 上有完整的 Python 教程。 Tcl/Tk 教程中介绍了 Tcl 语言的原始 Tk 工具包。 其他适用于 Python 语言的 GUI 工具包的教程为 PyQt5 教程, wxPython 教程和 Python Gtk 教程。
Tkinter 简介
在 Tkinter 教程的这一部分中,我们介绍 Tkinter 工具包并创建我们的第一个程序。
本教程的目的是帮助您开始使用 Tkinter 工具包。
Tkinter
Tkinter 是与 Tk GUI 工具包的 Python 绑定。 Tk 是 Tcl 语言的原始 GUI 库。 Tkinter 被实现为 Python 封装程序,围绕着嵌入在 Python 解释器中的完整 Tcl 解释器。 还有其他几种流行的 Python GUI 工具箱。 最受欢迎的是 wxPython,PyQt 和 PyGTK。
Python
Python 是一种通用的,动态的,面向对象的编程语言。 Python 语言的设计目的强调程序员的生产力和代码可读性。 Python 最初是由 Guido van Rossum 开发的。 它于 1991 年首次发布。Python 受 ABC,Haskell,Java,Lisp,Icon 和 Perl 编程语言的启发。 Python 是一种高级,通用,多平台,解释性语言。 Python 是一种简约语言。 它最明显的特征之一是它不使用分号或括号。 Python 使用缩进代替。 目前,Python 有两个主要分支:Python 2.x 和 Python3.x。 Python 3.x 打破了与早期版本 Python 的向后兼容性。 它的创建是为了纠正该语言的某些设计缺陷并使该语言更简洁。 本教程使用 Python 2.x 编写。 大多数代码是用 Python 2.x 版本编写的。 软件基础和程序员将需要一些时间才能迁移到 Python3.x。 今天,Python 由世界各地的一大批志愿者维护。 Python 是开源软件。
对于那些想学习编程的人来说,Python 是一个理想的起点。
Python 编程语言支持多种编程样式。 它不会强迫程序员采用特定的示例。 Python 支持面向对象和过程编程。 对函数式编程的支持也很有限。
Python 编程语言的官方网站是 python.org
Pillow
Pillow 是一个 Python 库,用于打开,操作和保存许多不同的图像文件格式。 本教程中的某些示例使用 Pillow。
$ sudo apt-get install python-pil.imagetk
在 Debian Linux 上,我们可以使用其包管理器安装 Pillow。
$ yum install python-imaging
在使用 RPM 包格式的系统上,我们使用上述命令安装 Pillow。
$ pip install pillow
或者,我们可以使用pip安装枕头。
Tkinter 的简单例子
在第一个示例中,我们在屏幕上显示一个基本窗口。
simple.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
This script shows a simple window
on the screen.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, BOTH
from tkinter.ttk import Frame
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Simple")
self.pack(fill=BOTH, expand=1)
def main():
root = Tk()
root.geometry("250x150+300+300")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
尽管这段代码很小,但是应用窗口可以做很多事情。 可以调整大小,最大化或最小化。 随之而来的所有复杂性对应用员都是隐藏的。
from tkinter import Tk, BOTH
from tkinter.ttk import Frame
在这里,我们导入Tk和Frame类,以及BOTH常量。 Tk类用于创建根窗口。 Frame是其他小部件的容器。
class Example(Frame):
def __init__(self):
super().__init__()
我们的示例类继承自Frame容器窗口小部件。 在__init__()构造器方法中,我们称为继承类的构造器。
self.initUI()
我们将用户界面的创建委托给initUI()方法。
self.master.title("Simple")
我们使用title()方法设置窗口的标题。 master属性可访问根窗口(Tk)。
self.pack(fill=BOTH, expand=1)
pack()方法是 Tkinter 中的三个几何管理器之一。 它将小部件组织成水平和垂直框。 这里,我们将通过self属性访问的Frame小部件放置到 Tk 根窗口中。 它向两个方向扩展。 换句话说,它占用了根窗口的整个客户端空间。
root = Tk()
根窗口已创建。 根窗口是我们程序中的主应用窗口。 它具有标题栏和边框。 这些由窗口管理器提供。 必须在任何其他小部件之前创建它。
root.geometry("250x150+300+300")
geometry()方法设置窗口的大小并将其放置在屏幕上。 前两个参数是窗口的宽度和高度。 最后两个参数是 x 和 y 屏幕坐标。
app = Example()
在这里,我们创建应用类的实例。
root.mainloop()
最后,我们进入主循环。 事件处理从这一点开始。 mainloop 从窗口系统接收事件,并将其分配给应用小部件。 当我们单击标题栏的关闭按钮或调用quit()方法时,它将终止。

图:简单 window
Tkinter 居中窗口
该脚本使屏幕上的窗口居中。
center_window.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
This script centers a small
window on the screen.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, BOTH
from tkinter.ttk import Frame
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Centered window")
self.pack(fill=BOTH, expand=1)
self.centerWindow()
def centerWindow(self):
w = 290
h = 150
sw = self.master.winfo_screenwidth()
sh = self.master.winfo_screenheight()
x = (sw - w)/2
y = (sh - h)/2
self.master.geometry('%dx%d+%d+%d' % (w, h, x, y))
def main():
root = Tk()
ex = Example()
root.mainloop()
if __name__ == '__main__':
main()
我们需要有窗口的大小和屏幕的大小,才能将窗口放置在监视器屏幕的中央。
w = 290
h = 150
这些是应用窗口的宽度和高度值。
sw = self.master.winfo_screenwidth()
sh = self.master.winfo_screenheight()
我们确定屏幕的宽度和高度。
x = (sw - w)/2
y = (sh - h)/2
我们计算所需的 x 和 y 坐标。
self.master.geometry('%dx%d+%d+%d' % (w, h, x, y))
最后,使用geometry()方法将窗口放置在屏幕中央。
Tkinter 退出按钮
在本章的最后一个示例中,我们创建一个具有退出按钮的应用。 当我们按下按钮时,应用终止。
quit_button.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
This program creates a Quit
button. When we press the button,
the application terminates.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, BOTH
from tkinter.ttk import Frame, Button, Style
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.style = Style()
self.style.theme_use("default")
self.master.title("Quit button")
self.pack(fill=BOTH, expand=1)
quitButton = Button(self, text="Quit",
command=self.quit)
quitButton.place(x=50, y=50)
def main():
root = Tk()
root.geometry("250x150+300+300")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
我们在窗口上放置一个Button。 单击该按钮将终止该应用。
from tkinter.ttk import Frame, Button, Style
Tkinter 支持小部件的主题。 主题的窗口小部件可以从ttk模块导入。 在撰写本文时,并非所有小部件都可使用。 例如,到目前为止,不支持菜单或列表框。
self.style = Style()
self.style.theme_use("default")
我们为小部件应用主题。 一些受支持的主题是蛤,默认,替代或经典。
quitButton = Button(self, text="Quit",
command=self.quit)
我们创建Button小部件的实例。 此按钮的父级是Frame容器。 我们为按钮和命令提供标签。 该命令指定了当我们按下按钮时调用的方法。 在我们的例子中,调用quit()方法,该方法将终止应用。
quitButton.place(x=50, y=50)
我们使用place几何图形管理器将按钮定位在绝对坐标中-从窗口的左上角起 50x50px。

图:退出按钮
本节是 Tkinter 工具包的介绍。
Windows API 中的系统函数
在 Windows API 教程的这一部分中,我们介绍了系统函数。 系统函数接收有关系统的信息并以各种方式与系统通信。
屏幕大小
GetSystemMetrics()函数检索各种系统指标和系统配置设置。
screen_size.c
#include <windows.h>
#include <wchar.h>
#pragma comment(lib, "user32.lib")
int wmain(void) {
int x = GetSystemMetrics(SM_CXSCREEN);
int y = GetSystemMetrics(SM_CYSCREEN);
wprintf(L"The screen size is: %dx%d\n", x, y);
return 0;
}
该代码示例将屏幕大小输出到控制台。
#pragma comment(lib, "user32.lib")
该程序需要user32.lib库进行编译。
int x = GetSystemMetrics(SM_CXSCREEN);
int y = GetSystemMetrics(SM_CYSCREEN);
我们用GetSystemMetrics()确定屏幕分辨率。
C:\Users\Jano\Documents\Pelles C Projects\system\ScreenSize>ScreenSize.exe
The screen size is: 1280x800
屏幕大小为1280x800。
锁定工作站
LockWorkStation()锁定工作站的显示。
lock_workstation.c
#include <windows.h>
#include <wchar.h>
#pragma comment(lib, "user32.lib")
int wmain(void) {
int r = LockWorkStation();
if( r == 0 ) {
wprintf(L"LockWorkStation() failed %d\n", GetLastError());
return 1;
}
return 0;
}
该程序需要user32.lib进行编译。
电脑名称
GetComputerNameEx()函数检索与本地计算机关联的 NetBIOS 或 DNS 名称。 名称是在系统启动时建立的。
computer_name.c
#include <windows.h>
#include <wchar.h>
int wmain(void) {
wchar_t computerName[MAX_COMPUTERNAME_LENGTH + 1];
DWORD size = sizeof(computerName) / sizeof(computerName[0]);
int r = GetComputerNameW(computerName, &size);
if (r == 0) {
wprintf(L"Failed to get computer name %ld", GetLastError());
return 1;
}
wprintf(L"Computer name: %ls\n", computerName);
return 0;
}
该示例将计算机名称打印到控制台。
wchar_t computerName[MAX_COMPUTERNAME_LENGTH + 1];
MAX_COMPUTERNAME_LENGTH常数确定计算机名称的最大长度。
int r = GetComputerNameW(computerName, &size);
我们使用GetComputerNameW()函数获得计算机的名称。 名称存储在computerName数组中。
C:\Users\Jano\Documents\Pelles C Projects\system\ComputerName>ComputerName.exe
Computer name: ANDROMEDA
我们运行代码示例。
用户名
GetUserNameW()函数返回用户名。
username.c
#include <windows.h>
#include <Lmcons.h>
#include <wchar.h>
int wmain(void) {
wchar_t username[UNLEN+1];
DWORD len = sizeof(username) / sizeof(wchar_t);
int r = GetUserNameW(username, &len);
if (r == 0) {
wprintf(L"Failed to get username %ld", GetLastError());
return 1;
}
wprintf(L"User name: %ls\n", username);
return 0;
}
该示例将用户名打印到控制台。
#include <Lmcons.h>
Lmcons.h文件具有ULEN常量的定义。
wchar_t username[UNLEN+1];
ULEN常量定义用户名的最大长度。
int r = GetUserNameW(username, &len);
GetUserNameW()函数检索用户名并将其存储到username数组中。
C:\Users\Jano\Documents\Pelles C Projects\system\Username>username.exe
User name: Jano
这是username.exe程序的示例输出。
当前目录
当前目录是用户所在或正在使用的目录。 在 Windows API 中,SetCurrentDirectoryW()更改当前目录,GetCurrentDirectoryW()检索当前目录。
current_directory.c
#include <windows.h>
#include <wchar.h>
#define BUFSIZE MAX_PATH
int wmain(int argc, wchar_t **argv) {
wchar_t buf[BUFSIZE];
if(argc != 2) {
wprintf(L"Usage: %ls <dir>\n", argv[0]);
return 1;
}
DWORD r = SetCurrentDirectoryW(argv[1]);
if (r == 0) {
wprintf(L"SetCurrentDirectoryW() failed (%ld)\n", GetLastError());
return 1;
}
r = GetCurrentDirectoryW(BUFSIZE, buf);
if (r == 0) {
wprintf(L"GetCurrentDirectoryW() failed (%ld)\n", GetLastError());
return 1;
}
if (r > BUFSIZE) {
wprintf(L"Buffer too small; needs %d characters\n", r);
return 1;
}
wprintf(L"Current directory is: %ls\n", buf);
return 0;
}
在代码示例中,我们更改并打印当前工作目录。 该程序接收一个命令行参数-要更改的目录。
#define BUFSIZE MAX_PATH
我们使用MAX_PATH常量,该常量定义系统路径的最大长度。
if(argc != 2) {
wprintf(L"Usage: %ls <dir>\n", argv[0]);
return 1;
}
如果我们不将参数传递给程序,则会显示一条错误消息。
DWORD r = SetCurrentDirectoryW(argv[1]);
我们使用SetCurrentDirectoryW()切换到目录,该目录作为参数传递。
r = GetCurrentDirectoryW(BUFSIZE, buf);
通过GetCurrentDirectoryW()函数调用获取当前的工作目录。
if (r > BUFSIZE) {
wprintf(L"Buffer too small; needs %d characters\n", r);
return 1;
}
如果返回的值大于BUFSIZE,则缓冲区太小。
Windows 版本
版本帮助器函数可用于确定当前的操作系统版本。
windows_version.c
#include <windows.h>
#include <wchar.h>
#include <VersionHelpers.h>
int wmain(void) {
//if (IsWindows10OrGreater()) {
// wprintf(L"This is Windows 10+");
// }
if (IsWindows8Point1OrGreater()) {
wprintf(L"This is Windows 8.1+\n");
} else if (IsWindows8OrGreater()) {
wprintf(L"This is Windows 8\n");
} else if (IsWindows7OrGreater ()) {
wprintf(L"This is Windows 7\n");
} else if (IsWindowsVistaOrGreater ()) {
wprintf(L"This is Windows Vista\n");
} else if (IsWindowsXPOrGreater()) {
wprintf(L"This is Windows XP\n");
}
return 0;
}
我们使用版本帮助器函数来确定操作系统版本。
#include <VersionHelpers.h>
辅助函数在VersionHelpers.h文件中声明。
//if (IsWindows10OrGreater()) {
// wprintf(L"This is Windows 10+");
// }
在撰写本文时,Pelles C 在其 SDK 中尚未定义IsWindows10OrGreater()。
if (IsWindows8Point1OrGreater()) {
wprintf(L"This is Windows 8.1+\n");
}
如果当前版本为 Windows 8.1 或更高版本,则IsWindows8Point1OrGreater()返回 true。
C:\Users\Jano\Documents\Pelles C Projects\system\WindowsVersion>WindowsVersion.exe
This is Windows 7
这是程序的示例输出。
内存
GlobalMemoryStatusEx()检索有关系统当前对物理和虚拟内存使用情况的信息。
memory.c
#include <windows.h>
#include <wchar.h>
int wmain(void) {
MEMORYSTATUSEX mem = {0};
mem.dwLength = sizeof(mem);
int r = GlobalMemoryStatusEx(&mem);
if (r == 0) {
wprintf(L"Failed to memory status %ld", GetLastError());
return 1;
}
wprintf(L"Memory in use: %ld percent\n", mem.dwMemoryLoad);
wprintf(L"Total physical memory: %lld\n", mem.ullTotalPhys);
wprintf(L"Free physical memory: %lld\n", mem.ullAvailPhys);
wprintf(L"Total virtual memory: %lld\n", mem.ullTotalVirtual);
wprintf(L"Free virtual memory: %lld\n", mem.ullAvailVirtual);
return 0;
}
该程序将有关内存使用情况的统计信息打印到控制台。
MEMORYSTATUSEX mem = {0};
GlobalMemoryStatusEx()函数在MEMORYSTATUSEX结构中存储有关内存状态的信息。
int r = GlobalMemoryStatusEx(&mem);
GlobalMemoryStatusEx()函数被执行; 信息存储在结构中。
wprintf(L"Memory in use: %ld percent\n", mem.dwMemoryLoad);
dwMemoryLoad成员指定正在使用的物理内存的大约百分比。
wprintf(L"Total physical memory: %lld\n", mem.ullTotalPhys);
ullTotalPhys成员以字节为单位指定实际的物理内存。
wprintf(L"Free physical memory: %lld\n", mem.ullAvailPhys);
ullTotalPhys成员以字节为单位指定当前可用的物理内存量。
wprintf(L"Total virtual memory: %lld\n", mem.ullTotalVirtual);
ullTotalVirtual成员以字节为单位指定虚拟内存总量。
wprintf(L"Free virtual memory: %lld\n", mem.ullAvailVirtual);
ullAvailVirtual成员以字节为单位指定可用虚拟内存量。
C:\Users\Jano\Documents\Pelles C Projects\system\Memory>Memory.exe
Memory in use: 47 percent
Total physical memory: 4226072576
Free physical memory: 2229788672
Total virtual memory: 8796092891136
Free virtual memory: 8796052586496
这是程序的示例输出。
已知数据夹
从 Windows Vista 开始,使用新系统来标识 Windows 中的重要目录。 它被称为已知文件夹。 已知文件夹使用一组 GUID(全局唯一标识符)值来引用重要文件夹。
SHGetKnownFolderPath()函数检索由文件夹 ID 标识的已知文件夹的完整路径。
documents_dir.c
#include <windows.h>
#include <initguid.h>
#include <KnownFolders.h>
#include <ShlObj.h>
#include <wchar.h>
int wmain(void) {
PWSTR path = NULL;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &path);
if (SUCCEEDED(hr)) {
wprintf(L"%ls\n", path);
}
CoTaskMemFree(path);
return 0;
}
该示例确定用户的Documents目录的完整路径。 我们需要将shell32.lib和ole32.lib添加到项目库中。
#include <initguid.h>
由于某些内部 API 问题,我们需要包含initguid.h文件; 否则,该示例将无法编译。 它失败,并显示Unresolved external symbol 'FOLDERID_Documents'错误。
HRESULT hr = SHGetKnownFolderPath(&FOLDERID_Documents, 0, NULL, &path);
SHGetKnownFolderPath()用于确定文档目录的路径。
if (SUCCEEDED(hr)) {
wprintf(L"%ls\n", path);
}
SUCCEEDED宏可用于确定函数调用是否成功。
CoTaskMemFree(path);
最后,必须使用CoTaskMemFree()函数释放分配的内存。
C:\Users\Jano\Documents\Pelles C Projects\system\DocumentsDir>DocumentsDir.exe
C:\Users\Jano\Documents
这是DocumentsDir.exe程序的示例输出。
驱动器名称
GetLogicalDriveStringsW()函数使用指定系统中有效驱动器的字符串填充缓冲区。
get_drives.c
#include <windows.h>
#include <wchar.h>
int wmain(void) {
wchar_t LogicalDrives[MAX_PATH] = {0};
DWORD r = GetLogicalDriveStringsW(MAX_PATH, LogicalDrives);
if (r == 0) {
wprintf(L"Failed to get drive names %ld", GetLastError());
return 1;
}
if (r > 0 && r <= MAX_PATH) {
wchar_t *SingleDrive = LogicalDrives;
while(*SingleDrive) {
wprintf(L"%ls\n", SingleDrive);
SingleDrive += wcslen(SingleDrive) + 1;
}
}
return 0;
}
该示例打印系统中的有效驱动器。
wchar_t LogicalDrives[MAX_PATH] = {0};
驱动器名称是路径类型,因此MAX_PATH常量与其最大长度有关。 LogicalDrives是一个字符串数组,用作GetLogicalDriveStringsW()函数的缓冲区。
DWORD r = GetLogicalDriveStringsW(MAX_PATH, LogicalDrives);
GetLogicalDriveStringsW()被调用。 缓冲区中填充了以零结尾的字符串,这些字符串代表设备名称。 该函数的第一个参数是指定缓冲区的最大大小。 缓冲区是第二个参数。
wchar_t *SingleDrive = LogicalDrives;
while(*SingleDrive) {
wprintf(L"%ls\n", SingleDrive);
SingleDrive += wcslen(SingleDrive) + 1;
}
我们遍历设备名称数组并将其打印到控制台。
C:\Users\Jano\Documents\Pelles C Projects\system\GetDrives>GetDrives.exe
C:\
D:\
系统上有两个驱动器:C:\和D:\。
可用空间
GetDiskFreeSpaceExW()检索有关磁盘卷上可用空间量的信息。 该函数提供三段信息:总空间量,可用空间量以及与调用线程关联的用户可用的可用空间。
free_disk_space.c
#include <windows.h>
#include <wchar.h>
int wmain(void) {
unsigned __int64 freeCall,
total,
free;
int r = GetDiskFreeSpaceExW(L"C:\\", (PULARGE_INTEGER) &freeCall,
(PULARGE_INTEGER) &total, (PULARGE_INTEGER) &free);
if (r == 0) {
wprintf(L"Failed to get free disk space %ld", GetLastError());
return 1;
}
wprintf(L"Available space to caller: %I64u MB\n", freeCall / (1024*1024));
wprintf(L"Total space: %I64u MB\n", total / (1024*1024));
wprintf(L"Free space on drive: %I64u MB\n", free / (1024*1024));
return 0;
}
该示例检查C:\驱动器上的磁盘空间。
unsigned __int64 freeCall,
total,
free;
数量以字节为单位; 这些数字可能非常大。 使用unsigned __int64类型,它是一个能够存储非常大的值的正 64 位整数。
int r = GetDiskFreeSpaceExW(L"C:\\", (PULARGE_INTEGER) &freeCall,
(PULARGE_INTEGER) &total, (PULARGE_INTEGER) &free);
GetDiskFreeSpaceExW()被调用。
wprintf(L"Available space to caller: %I64u MB\n", freeCall / (1024*1024));
wprintf(L"Total space: %I64u MB\n", total / (1024*1024));
wprintf(L"Free space on drive: %I64u MB\n", free / (1024*1024));
使用wprintf()函数将这三个金额打印到控制台。 这些值以 MB 表示。
C:\Users\Jano\Documents\Pelles C Projects\system\FreeDiskSpace>FreeDiskSpace.exe
Available space to caller: 20377 MB
Total space: 69999 MB
Free space on drive: 20377 MB
这是FreeDiskSpace.exe程序的示例输出。
CPU 速度
可以通过检查注册表值来确定 CPU 速度。 该值将在安装过程中写入注册表。 我们需要查询HARDWARE\DESCRIPTION\System\CentralProcessor\0键。
cpu_speed.c
#include <windows.h>
#include <wchar.h>
int wmain(void) {
DWORD BufSize = MAX_PATH;
DWORD mhz = MAX_PATH;
HKEY key;
long r = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &key);
if (r != ERROR_SUCCESS) {
wprintf(L"RegOpenKeyExW() failed %ld", GetLastError());
return 1;
}
r = RegQueryValueExW(key, L"~MHz", NULL, NULL, (LPBYTE) &mhz, &BufSize);
if (r != ERROR_SUCCESS) {
wprintf(L"RegQueryValueExW() failed %ld", GetLastError());
return 1;
}
wprintf(L"CPU speed: %lu MHz\n", mhz);
r = RegCloseKey(key);
if (r != ERROR_SUCCESS) {
wprintf(L"Failed to close registry handle %ld", GetLastError());
return 1;
}
return 0;
}
该示例确定 CPU 速度。
long r = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &key);
RegOpenKeyExW()函数用于打开提供的注册表项。
r = RegQueryValueExW(key, L"~MHz", NULL, NULL, (LPBYTE) &mhz, &BufSize);
通过RegQueryValueExW()函数读取该值。
r = RegCloseKey(key);
RegCloseKey()关闭注册表句柄。
C:\Users\Jano\Documents\Pelles C Projects\system\CpuSpeed>CpuSpeed.exe
CPU speed: 2394 MHz
这是CpuSpeed.exe程序的示例输出。
在 Windows API 教程的这一部分中,我们使用了一些系统函数。
Tkinter 中的布局管理
在 Tkinter 教程的这一部分中,我们介绍布局管理器。
在设计应用的 GUI 时,我们决定要使用哪些小部件以及如何在应用中组织这些小部件。 为了组织小部件,我们使用专门的不可见对象,称为布局管理器。
有两种小部件:容器及其子级。 容器将子项分组为合适的布局。
Tkinter 具有三个内置的布局管理器:pack,grid和place管理器。 place几何管理器使用绝对定位来定位小部件。 pack几何管理器在水平和垂直框中组织窗口小部件。 grid几何管理器将小部件放置在二维网格中。
绝对定位
在大多数情况下,程序员应使用布局管理器。 在某些情况下,我们可以使用绝对定位。 在绝对定位中,程序员以像素为单位指定每个小部件的位置和大小。 如果我们调整窗口大小,则小部件的大小和位置不会改变。 在各种平台上,应用看起来都不同,在 Linux 上看起来不错,在 Mac OS 上看起来不太正常。 在我们的应用中更改字体可能会破坏布局。 如果我们将应用翻译成另一种语言,则必须重做布局。
absolute.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
In this script, we lay out images
using absolute positioning.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from PIL import Image, ImageTk
from tkinter import Tk, BOTH
from tkinter.ttk import Frame, Label, Style
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Absolute positioning")
self.pack(fill=BOTH, expand=1)
Style().configure("TFrame", background="#333")
bard = Image.open("bardejov.jpg")
bardejov = ImageTk.PhotoImage(bard)
label1 = Label(self, image=bardejov)
label1.image = bardejov
label1.place(x=20, y=20)
rot = Image.open("rotunda.jpg")
rotunda = ImageTk.PhotoImage(rot)
label2 = Label(self, image=rotunda)
label2.image = rotunda
label2.place(x=40, y=160)
minc = Image.open("mincol.jpg")
mincol = ImageTk.PhotoImage(minc)
label3 = Label(self, image=mincol)
label3.image = mincol
label3.place(x=170, y=50)
def main():
root = Tk()
root.geometry("300x280+300+300")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
在此示例中,我们使用绝对定位放置了三个图像。 我们使用位置几何图形管理器。
from PIL import Image, ImageTk
我们使用 Python Imaging Library(PIL)模块中的Image和ImageTk。
style = Style()
style.configure("TFrame", background="#333")
我们使用样式将框架配置为具有深灰色背景。
bard = Image.open("bardejov.jpg")
bardejov = ImageTk.PhotoImage(bard)
我们从当前工作目录中的图像创建图像对象和照片图像对象。
label1 = Label(self, image=bardejov)
我们用图像创建一个Label。 标签可以包含文本或图像。
label1.image = bardejov
我们必须保留对图像的引用,以防止图像被垃圾收集。
label1.place(x=20, y=20)
将标签放置在框架上的x = 20和y = 20坐标处。

图:绝对定位
Tkinter 包管理器
pack几何管理器在水平和垂直框中组织窗口小部件。 布局由fill,expand和side选项控制。
按钮示例
在下面的示例中,我们将两个按钮放置在窗口的右下角。 我们使用pack管理器。
buttons.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
In this script, we use the pack manager
to position two buttons in the
bottom-right corner of the window.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, RIGHT, BOTH, RAISED
from tkinter.ttk import Frame, Button, Style
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Buttons")
self.style = Style()
self.style.theme_use("default")
frame = Frame(self, relief=RAISED, borderwidth=1)
frame.pack(fill=BOTH, expand=True)
self.pack(fill=BOTH, expand=True)
closeButton = Button(self, text="Close")
closeButton.pack(side=RIGHT, padx=5, pady=5)
okButton = Button(self, text="OK")
okButton.pack(side=RIGHT)
def main():
root = Tk()
root.geometry("300x200+300+300")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
我们有两个框架。 有一个基础框架和一个附加框架,该框架可在两个方向上扩展,并将两个按钮按到基础框架的底部。 这些按钮放置在水平框中,并位于此框的右侧。
frame = Frame(self, relief=RAISED, borderwidth=1)
frame.pack(fill=BOTH, expand=True)
我们创建另一个Frame小部件。 该小部件占用了大部分区域。 我们更改框架的边框,使框架可见。 默认情况下,它是平坦的。
closeButton = Button(self, text="Close")
closeButton.pack(side=RIGHT, padx=5, pady=5)
创建了closeButton。 将其放入水平盒中。 side参数使按钮放置在水平线的右侧。 padx和pady参数在小部件之间放置了一些空间。 padx在按钮小部件之间以及closeButton和根窗口的右边框之间放置一些空间。 pady在按钮小部件与框架的边框和根窗口的边框之间放置一些空间。
okButton.pack(side=RIGHT)
okButton放在closeButton旁边,它们之间有 5px 的间距。

图:按钮示例
回顾示例
pack管理器是一个简单的布局管理器。 它可用于执行简单的布局任务。 为了创建更复杂的布局,我们需要利用更多的框架,每个框架都有自己的包管理器。
review.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
In this example, we use the pack
manager to create a review example.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, Text, TOP, BOTH, X, N, LEFT
from tkinter.ttk import Frame, Label, Entry
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Review")
self.pack(fill=BOTH, expand=True)
frame1 = Frame(self)
frame1.pack(fill=X)
lbl1 = Label(frame1, text="Title", width=6)
lbl1.pack(side=LEFT, padx=5, pady=5)
entry1 = Entry(frame1)
entry1.pack(fill=X, padx=5, expand=True)
frame2 = Frame(self)
frame2.pack(fill=X)
lbl2 = Label(frame2, text="Author", width=6)
lbl2.pack(side=LEFT, padx=5, pady=5)
entry2 = Entry(frame2)
entry2.pack(fill=X, padx=5, expand=True)
frame3 = Frame(self)
frame3.pack(fill=BOTH, expand=True)
lbl3 = Label(frame3, text="Review", width=6)
lbl3.pack(side=LEFT, anchor=N, padx=5, pady=5)
txt = Text(frame3)
txt.pack(fill=BOTH, pady=5, padx=5, expand=True)
def main():
root = Tk()
root.geometry("300x300+300+300")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
该示例显示了如何创建具有多个框架和包装管理器的更复杂的布局。
self.pack(fill=BOTH, expand=True)
第一个框架是基础框架,其他框架放置在该基础框架上。 请注意,除了在框架内组织子代外,我们还在基础框架上管理框架。
frame1 = Frame(self)
frame1.pack(fill=X)
lbl1 = Label(frame1, text="Title", width=6)
lbl1.pack(side=LEFT, padx=5, pady=5)
entry1 = Entry(frame1)
entry1.pack(fill=X, padx=5, expand=True)
前两个小部件放置在第一帧上。 使用fill和expand参数水平拉伸该条目。
frame3 = Frame(self)
frame3.pack(fill=BOTH, expand=True)
lbl3 = Label(frame3, text="Review", width=6)
lbl3.pack(side=LEFT, anchor=N, padx=5, pady=5)
txt = Text(frame3)
txt.pack(fill=BOTH, pady=5, padx=5, expand=True)
在第三帧内,我们放置一个标签和一个文本小部件。 标签固定在北方。 文本小部件将占据整个剩余区域。

图:回顾 example
Tkinter 网格管理器
Tkinter 的grid几何管理器用于创建计算器的骨架。
calculator.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
In this script, we use the grid manager
to create a skeleton of a calculator.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, W, E
from tkinter.ttk import Frame, Button, Entry, Style
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Calculator")
Style().configure("TButton", padding=(0, 5, 0, 5),
font='serif 10')
self.columnconfigure(0, pad=3)
self.columnconfigure(1, pad=3)
self.columnconfigure(2, pad=3)
self.columnconfigure(3, pad=3)
self.rowconfigure(0, pad=3)
self.rowconfigure(1, pad=3)
self.rowconfigure(2, pad=3)
self.rowconfigure(3, pad=3)
self.rowconfigure(4, pad=3)
entry = Entry(self)
entry.grid(row=0, columnspan=4, sticky=W+E)
cls = Button(self, text="Cls")
cls.grid(row=1, column=0)
bck = Button(self, text="Back")
bck.grid(row=1, column=1)
lbl = Button(self)
lbl.grid(row=1, column=2)
clo = Button(self, text="Close")
clo.grid(row=1, column=3)
sev = Button(self, text="7")
sev.grid(row=2, column=0)
eig = Button(self, text="8")
eig.grid(row=2, column=1)
nin = Button(self, text="9")
nin.grid(row=2, column=2)
div = Button(self, text="/")
div.grid(row=2, column=3)
fou = Button(self, text="4")
fou.grid(row=3, column=0)
fiv = Button(self, text="5")
fiv.grid(row=3, column=1)
six = Button(self, text="6")
six.grid(row=3, column=2)
mul = Button(self, text="*")
mul.grid(row=3, column=3)
one = Button(self, text="1")
one.grid(row=4, column=0)
two = Button(self, text="2")
two.grid(row=4, column=1)
thr = Button(self, text="3")
thr.grid(row=4, column=2)
mns = Button(self, text="-")
mns.grid(row=4, column=3)
zer = Button(self, text="0")
zer.grid(row=5, column=0)
dot = Button(self, text=".")
dot.grid(row=5, column=1)
equ = Button(self, text="=")
equ.grid(row=5, column=2)
pls = Button(self, text="+")
pls.grid(row=5, column=3)
self.pack()
def main():
root = Tk()
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
网格管理器用于组织框架容器中的按钮。
Style().configure("TButton", padding=(0, 5, 0, 5),
font='serif 10')
我们将Button小部件配置为具有特定的字体并具有一些内部填充。
self.columnconfigure(0, pad=3)
...
self.rowconfigure(0, pad=3)
我们使用columnconfigure()和rowconfigure()方法在网格列和行中定义一些空间。 这样,我们可以实现按钮之间有一定的间隔。
entry = Entry(self)
entry.grid(row=0, columnspan=4, sticky=W+E)
Entry小部件是显示数字的地方。 小部件放置在第一行中,它横跨所有四列。 小部件可能不会占用网格中单元所分配的所有空间。 sticky参数沿给定方向扩展小部件。 在我们的案例中,我们确保条目小部件从左向右展开。
cls = Button(self, text="Cls")
cls.grid(row=1, column=0)
cls按钮位于第二行和第一列。 请注意,行和列从零开始。
self.pack()
pack()方法显示框架小部件并为其指定初始大小。 如果没有给出其他参数,则大小将足以显示所有子项。 此方法将框架窗口小部件打包到顶级根窗口,该窗口也是一个容器。 grid几何管理器用于组织框架小部件中的按钮。

图:计算器
Windows 示例
以下示例使用grid几何管理器创建 Windows 对话框。
windows.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
In this script, we use the grid
manager to create a more complicated Windows
layout.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, Text, BOTH, W, N, E, S
from tkinter.ttk import Frame, Button, Label, Style
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Windows")
self.pack(fill=BOTH, expand=True)
self.columnconfigure(1, weight=1)
self.columnconfigure(3, pad=7)
self.rowconfigure(3, weight=1)
self.rowconfigure(5, pad=7)
lbl = Label(self, text="Windows")
lbl.grid(sticky=W, pady=4, padx=5)
area = Text(self)
area.grid(row=1, column=0, columnspan=2, rowspan=4,
padx=5, sticky=E+W+S+N)
abtn = Button(self, text="Activate")
abtn.grid(row=1, column=3)
cbtn = Button(self, text="Close")
cbtn.grid(row=2, column=3, pady=4)
hbtn = Button(self, text="Help")
hbtn.grid(row=5, column=0, padx=5)
obtn = Button(self, text="OK")
obtn.grid(row=5, column=3)
def main():
root = Tk()
root.geometry("350x300+300+300")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
在此示例中,我们使用Label小部件,Text小部件和四个按钮。
self.columnconfigure(1, weight=1)
self.columnconfigure(3, pad=7)
self.rowconfigure(3, weight=1)
self.rowconfigure(5, pad=7)
我们在网格中的小部件之间定义一些空间。 weight参数使第二列和第四行可增长。 该行和列被文本小部件占据,因此所有多余的空间都被它占用。
lbl = Label(self, text="Windows")
lbl.grid(sticky=W, pady=4, padx=5)
标签窗口小部件已创建并放入网格中。 如果未指定列和行,则假定为第一列或行。 该标签向西粘贴,其边框周围有一些填充物。
area = Text(self)
area.grid(row=1, column=0, columnspan=2, rowspan=4,
padx=5, sticky=E+W+S+N)
文本窗口小部件已创建,并从第二行和第一列开始。 它跨越两列和四行。 小部件和根窗口的左边框之间有 4px 的间距。 最终,小部件将粘在所有四个方面。 因此,调整窗口大小时,文本小部件会向各个方向扩展。
abtn = Button(self, text="Activate")
abtn.grid(row=1, column=3)
cbtn = Button(self, text="Close")
cbtn.grid(row=2, column=3, pady=4)
这两个按钮位于文本小部件旁边。
hbtn = Button(self, text="Help")
hbtn.grid(row=5, column=0, padx=5)
obtn = Button(self, text="OK")
obtn.grid(row=5, column=3)
这两个按钮位于文本小部件下方; “帮助”按钮占据第一列,“确定”按钮占据最后一列。

图:窗口示例
在 Tkinter 教程的这一部分中,我们介绍了小部件的布局管理。
Tkinter 标准小部件属性
在 Tkinter 教程的这一部分中,我们讨论标准的窗口小部件属性,包括光标,浮雕,颜色和字体。
标准窗口小部件属性是在窗口小部件构造器中使用的关键字。
Tkinter 小部件状态
state属性定义窗口小部件的状态。 它可以具有以下值:NORMAL,ACTIVE和DISABLED。
widget_states.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
In this script, we use the state attribute.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, BOTH, NORMAL, ACTIVE, DISABLED
from tkinter.ttk import Frame, Label
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Fonts")
self.pack(fill=BOTH, expand=True)
self.columnconfigure(0, pad=5)
self.columnconfigure(1, pad=5)
self.columnconfigure(2, pad=5)
txt = "Today is a beautiful day"
label1 = Label(self, text=txt, state=NORMAL)
label1.grid(row=0, column=0)
label2 = Label(self, text=txt, state=ACTIVE)
label2.grid(row=0, column=1)
label3 = Label(self, text=txt, state=DISABLED)
label3.grid(row=0, column=2)
def main():
root = Tk()
ex = Example()
root.geometry("+300+300")
root.mainloop()
if __name__ == '__main__':
main()
我们在Label小部件上演示state属性。
label1 = Label(self, text=txt, state=NORMAL)
第一个标签具有NORMAL状态。

图:小部件状态
Tkinter 小部件填充
padx和pady属性为小部件添加了额外的水平和垂直空间。
padding.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
This program uses the padx and pady
widget attributes.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, Frame, Button
from tkinter import BOTH, LEFT, TOP
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Padding")
self.pack(fill=BOTH)
frame = Frame(self, bd=5)
frame.pack()
btn1 = Button(frame, text='Button')
btn1.pack(side=LEFT, padx=5)
btn2 = Button(frame, text='Button')
btn2.pack(side=LEFT, padx=5)
frame2 = Frame(self)
frame2.pack()
btn1 = Button(frame2, text='Button')
btn1.pack(side=TOP, pady=15)
btn2 = Button(frame2, text='Button')
btn2.pack(side=TOP, pady=15)
self.pack()
def main():
root = Tk()
root.geometry("300x250+300+300")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
在示例中,我们创建了两个框架; 他们每个人都有两个小部件。 第一个框架从按钮形成一行,第二个框架从列形成一行。 padx和pady属性在按钮之间添加空格。
btn1 = Button(frame, text='Button')
btn1.pack(side=LEFT, padx=5)
使用pack管理器,我们创建了一行按钮。 padx在按钮之间水平增加 5px 的间距。
btn1 = Button(frame2, text='Button')
btn1.pack(side=TOP, pady=15)
在这里,我们形成一列。 因此,我们使用pady属性垂直添加空间。

图:填充
背景颜色
窗口小部件的背景色可以使用background属性设置。 可以缩写为bg。
同样,可以使用foreground属性设置窗口小部件的前景色。 可以缩写为fg。
bg_colours.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
This program uses sets various background
colours with bg attribute.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, Frame, Label
from tkinter import BOTH, LEFT
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Background colours")
self.pack(fill=BOTH)
frame = Frame(self, borderwidth=10)
frame.pack()
lbl1 = Label(frame, bg='SlateGray3', width=15, height=10)
lbl1.pack(side=LEFT, padx=3)
lbl2 = Label(frame, bg='SlateGray4', width=15, height=10)
lbl2.pack(side=LEFT)
lbl3 = Label(frame, bg='DarkSeaGreen3', width=15, height=10)
lbl3.pack(side=LEFT, padx=3)
lbl4 = Label(frame, bg='DarkSeaGreen4', width=15, height=10)
lbl4.pack(side=LEFT)
self.pack()
def main():
root = Tk()
root.geometry("+300+300")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
在代码示例中,我们创建具有四个不同背景颜色的四个标签。
lbl1 = Label(frame, bg='SlateGray3', width=15, height=10)
lbl1.pack(side=LEFT, padx=3)
第一个标签具有SlateGray3背景色。

图:背景色
宽度和高度
width和height属性设置窗口小部件的宽度和高度。
width_height.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
This program uses width and height
attributes to set the size of widgets.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, Frame, Button
from tkinter import BOTH, LEFT
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Width and height")
self.pack(fill=BOTH)
frame = Frame(self, borderwidth=10)
frame.pack()
btn1 = Button(frame, text='Button')
btn1.pack(side=LEFT, padx=5)
btn2 = Button(frame, text='Button', width=8)
btn2.pack(side=LEFT, padx=5)
btn3 = Button(frame, text='Button', width=5, height=4)
btn3.pack(side=LEFT)
self.pack()
def main():
root = Tk()
root.geometry("+300+300")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
在示例中,我们使用width和height.控制按钮的大小
btn2 = Button(frame, text='Button', width=8)
此按钮的宽度设置为八个字符。

图:宽高属性
Tkinter 浮雕
浮雕是一种边界装饰。 可能的值为:SUNKEN,RAISED,GROOVE,RIDGE和FLAT。
reliefs.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
This program uses relief styles.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, Frame, Label
from tkinter import BOTH, LEFT, FLAT, SUNKEN, RAISED, GROOVE, RIDGE
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Reliefs")
self.pack(fill=BOTH)
frame = Frame(self, borderwidth=10)
frame.pack()
lbl1 = Label(frame, bg='LightSteelBlue3', width=15, height=10, relief=FLAT)
lbl1.pack(side=LEFT, padx=3)
lbl2 = Label(frame, bg='LightSteelBlue3', bd=2, width=15,
height=10, relief=SUNKEN)
lbl2.pack(side=LEFT)
lbl3 = Label(frame, bg='LightSteelBlue3', bd=2, width=15,
height=10, relief=RAISED)
lbl3.pack(side=LEFT, padx=3)
lbl4 = Label(frame, bg='LightSteelBlue3', bd=3, width=15,
height=10, relief=GROOVE)
lbl4.pack(side=LEFT)
lbl5 = Label(frame, bg='LightSteelBlue3', bd=3, width=15,
height=10, relief=RIDGE)
lbl5.pack(side=LEFT, padx=3)
self.pack()
def main():
root = Tk()
root.geometry("+300+300")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
该示例显示了应用于标签的五种不同的凸版。
lbl3 = Label(frame, bg='LightSteelBlue3', bd=2, width=15,
height=10, relief=RAISED)
第三个标签具有RAISED浮雕。 使用bd,我们增加了边框宽度,以使浮雕更加明显。

图:浮雕
Tkinter 字体
Tkinter 具有用于处理字体的tkinter.font模块。 它具有一些内置字体,例如TkTooltipFont,TkDefaultFont或TkTextFont。 字体通过font属性设置。
fonts.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
In this script, we display text in three
different fonts.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, BOTH
from tkinter.ttk import Frame, Label, Notebook, Style
from tkinter.font import Font
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Fonts")
self.pack(fill=BOTH, expand=True)
txt = "Today is a beautiful day"
myfont = Font(family="Ubuntu Mono", size=16)
label1 = Label(self, text=txt, font=myfont)
label1.grid(row=0, column=0)
label2 = Label(self, text=txt, font="TkTextFont")
label2.grid(row=1, column=0)
label3 = Label(self, text=txt, font=('Times', '18', 'italic'))
label3.grid(row=2, column=0)
def main():
root = Tk()
ex = Example()
root.geometry("+300+300")
root.mainloop()
if __name__ == '__main__':
main()
该示例显示了三个带有三种不同字体的文本标签。
myfont = Font(family="Ubuntu Mono", size=16)
label1 = Label(self, text=txt, font=myfont)
使用Font类创建特定的字体。 如果该字体在平台上不可用,则 Tkinter 会还原为某些默认字体。
label2 = Label(self, text=txt, font="TkTextFont")
在这里,我们使用内置字体名称。
label3 = Label(self, text=txt, font=('Times', '18', 'italic'))
字体也可以指定为字符串元组。

图:Tkinter 字体
Tkinter 光标
光标是一个小图标,显示鼠标指针所在的位置。 使用cursor属性设置 Tkinter 中的光标。
cursors.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
This program uses different cursors.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, Frame, Label
from tkinter import BOTH, LEFT
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Cursors")
self.pack(fill=BOTH)
frame = Frame(self, borderwidth=10)
frame.pack()
lbl1 = Label(frame, bg='SlateGray3', width=15, height=10,
cursor='tcross')
lbl1.pack(side=LEFT, padx=3)
lbl2 = Label(frame, bg='SlateGray4', width=15, height=10,
cursor='hand2')
lbl2.pack(side=LEFT)
lbl3 = Label(frame, bg='DarkSeaGreen3', width=15, height=10,
cursor='heart')
lbl3.pack(side=LEFT, padx=3)
lbl4 = Label(frame, bg='DarkSeaGreen4', width=15, height=10,
cursor='pencil')
lbl4.pack(side=LEFT)
self.pack()
def main():
root = Tk()
root.geometry("+300+300")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
在示例中,我们有四个标签。 每个标签使用不同的光标。
lbl4 = Label(frame, bg='DarkSeaGreen4', width=15, height=10,
cursor='pencil')
第四个标签有一个用于光标的铅笔图标。
在 Tkinter 教程的这一部分中,我们使用了标准的窗口小部件属性。
Tkinter 小部件
在 Tkinter 教程的这一部分中,我们将介绍一些基本的 Tkinter 小部件。 我们使用以下小部件:Checkbutton,Label,Scale和Listbox。
小部件是 GUI 应用的基本构建块。 多年以来,几个小部件已成为所有 OS 平台上所有工具包中的标准。 例如按钮,复选框或滚动条。 其中一些名称可能不同。 例如,在 Tkinter 中,复选框称为复选框。 Tkinter 具有一小组可满足基本编程需求的小部件。 可以将更多专门的窗口小部件创建为自定义窗口小部件。
Tkinter Checkbutton
Checkbutton是具有两种状态的窗口小部件:打开和关闭。 接通状态通过复选标记显示。 (某些主题可能具有不同的视觉效果。)它用来表示某些布尔属性。 Checkbutton小部件提供一个带有文本标签的复选框。
check_button.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
This program toggles the title of the
window with the Checkbutton widget.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, Frame, Checkbutton
from tkinter import BooleanVar, BOTH
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Checkbutton")
self.pack(fill=BOTH, expand=True)
self.var = BooleanVar()
cb = Checkbutton(self, text="Show title",
variable=self.var, command=self.onClick)
cb.select()
cb.place(x=50, y=50)
def onClick(self):
if self.var.get() == True:
self.master.title("Checkbutton")
else:
self.master.title("")
def main():
root = Tk()
root.geometry("250x150+300+300")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
在我们的示例中,我们在窗口上放置了一个检查按钮。 复选按钮显示或隐藏窗口的标题。
self.var = BooleanVar()
我们创建一个BooleanVar对象。 它是 Tkinter 中小部件的布尔值的值持有者。
cb = Checkbutton(self, text="Show title",
variable=self.var, command=self.onClick)
创建Checkbutton的实例。 值持有者通过variable参数连接到窗口小部件。 当我们单击检查按钮时,将调用onClick()方法。 这是通过command参数完成的。
cb.select()
最初,标题显示在标题栏中。 因此,一开始,我们使用select()方法对其进行了检查。
if self.var.get() == True:
self.master.title("Checkbutton")
else:
self.master.title("")
在onClick()方法内部,我们根据self.var变量中的值显示或隐藏标题。

图:Checkbutton
Tkinter 标签
Label小部件用于显示文本或图像。 没有用户交互。
label.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
In this script, we use the Label
widget to show an image.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from PIL import Image, ImageTk
from tkinter import Tk
from tkinter.ttk import Frame, Label
import sys
class Example(Frame):
def __init__(self):
super().__init__()
self.loadImage()
self.initUI()
def loadImage(self):
try:
self.img = Image.open("tatras.jpg")
except IOError:
print("Unable to load image")
sys.exit(1)
def initUI(self):
self.master.title("Label")
tatras = ImageTk.PhotoImage(self.img)
label = Label(self, image=tatras)
# reference must be stored
label.image = tatras
label.pack()
self.pack()
def setGeometry(self):
w, h = self.img.size
self.master.geometry(("%dx%d+300+300") % (w, h))
def main():
root = Tk()
ex = Example()
ex.setGeometry()
root.mainloop()
if __name__ == '__main__':
main()
我们的示例在窗口上显示图像。
from PIL import Image, ImageTk
默认情况下,Label小部件只能显示一组有限的图像类型。 要显示 JPG 图像,我们必须使用 PIL,Python Imaging Library 模块。 通过 Pillow 教程了解有关 PIL 的更多信息。
self.img = Image.open("tatras.jpg")
我们从当前工作目录中的图像文件创建一个Image。
tatras = ImageTk.PhotoImage(self.img)
我们根据图像创建照片图像。
label = Label(self, image=tatras)
将照片图像提供给标签窗口小部件的image参数。
label.image = tatras
为了不被垃圾收集,必须存储图像引用。
w, h = self.img.size
self.master.geometry(("%dx%d+300+300") % (w, h))
我们使窗口的大小完全适合图像的大小。
Tkinter 刻度
Scale是一个小部件,可让用户通过在有限间隔内滑动旋钮以图形方式选择一个值。 我们的示例将在标签小部件中显示一个选定的数字。
scale.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
In this script, we show how to
use the Scale widget.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, BOTH, IntVar, LEFT
from tkinter.ttk import Frame, Label, Scale, Style
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Scale")
self.style = Style()
self.style.theme_use("default")
self.pack(fill=BOTH, expand=1)
scale = Scale(self, from_=0, to=100,
command=self.onScale)
scale.pack(side=LEFT, padx=15)
self.var = IntVar()
self.label = Label(self, text=0, textvariable=self.var)
self.label.pack(side=LEFT)
def onScale(self, val):
v = int(float(val))
self.var.set(v)
def main():
root = Tk()
ex = Example()
root.geometry("250x100+300+300")
root.mainloop()
if __name__ == '__main__':
main()
上面的脚本中有两个小部件:标尺和标签。 标签控件中显示了比例控件的值。
scale = Scale(self, from_=0, to=100,
command=self.onScale)
Scale小部件已创建。 我们提供上下限。 from是常规的 Python 关键字,这就是为什么第一个参数后会有下划线的原因。 当我们移动秤的旋钮时,将调用onScale()方法。
self.var = IntVar()
self.label = Label(self, text=0, textvariable=self.var)
创建一个整数值持有者和标签小部件。 持有者的值显示在标签小部件中。
def onScale(self, val):
v = int(float(val))
self.var.set(v)
onScale()方法从缩放窗口小部件接收当前选择的值作为参数。 该值首先转换为浮点数,然后转换为整数。 最后,将值设置为标签窗口小部件的值持有者。

图:Scale
Tkinter 列表框
Listbox是显示对象列表的窗口小部件。 它允许用户选择一项或多项。
listbox.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
In this script, we show how to
use the Listbox widget.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, BOTH, Listbox, StringVar, END
from tkinter.ttk import Frame, Label
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Listbox")
self.pack(fill=BOTH, expand=1)
acts = ['Scarlett Johansson', 'Rachel Weiss',
'Natalie Portman', 'Jessica Alba']
lb = Listbox(self)
for i in acts:
lb.insert(END, i)
lb.bind("<<ListboxSelect>>", self.onSelect)
lb.pack(pady=15)
self.var = StringVar()
self.label = Label(self, text=0, textvariable=self.var)
self.label.pack()
def onSelect(self, val):
sender = val.widget
idx = sender.curselection()
value = sender.get(idx)
self.var.set(value)
def main():
root = Tk()
ex = Example()
root.geometry("300x250+300+300")
root.mainloop()
if __name__ == '__main__':
main()
在我们的示例中,我们显示了Listbox中的女演员列表。 当前选择的女演员显示在标签小部件中。
acts = ['Scarlett Johansson', 'Rachel Weiss',
'Natalie Portman', 'Jessica Alba']
这是要在列表框中显示的女演员列表。
lb = Listbox(self)
for i in acts:
lb.insert(END, i)
我们创建Listbox的实例,并插入上述列表中的所有项目。
lb.bind("<<ListboxSelect>>", self.onSelect)
当我们在列表框中选择一个项目时,将生成<<ListboxSelect>>事件。 我们将onSelect()方法绑定到此事件。
self.var = StringVar()
self.label = Label(self, text=0, textvariable=self.var)
将创建标签及其值持有者。 在此标签中,我们将显示当前选择的项目。
sender = val.widget
我们得到了事件的发送者。 这是我们的列表框小部件。
idx = sender.curselection()
我们使用curselection()方法找出所选项目的索引。
value = sender.get(idx)
使用get()方法检索实际值,该方法获取项目的索引。
self.var.set(value)
最后,标签被更新。

图:Listbox小部件
在 Tkinter 教程的这一部分中,我们介绍了几个 Tkinter 小部件。
Tkinter 中的菜单和工具栏
在 Tkinter 教程的这一部分中,我们将使用菜单和工具栏。
菜单栏是 GUI 应用中最可见的部分之一。 它是位于各个菜单中的一组命令。 在控制台应用中,我们必须记住许多奥术命令,在这里,我们将大多数命令分组为逻辑部分。 有公认的标准可以进一步减少学习新应用的时间。 菜单将我们可以在应用中使用的命令分组。 使用工具栏可以快速访问最常用的命令。
Tkinter 简单菜单
第一个示例显示了一个简单的菜单。
simple_menu.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
This program shows a simple
menu. It has one action, which
will terminate the program, when
selected.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, Frame, Menu
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Simple menu")
menubar = Menu(self.master)
self.master.config(menu=menubar)
fileMenu = Menu(menubar)
fileMenu.add_command(label="Exit", command=self.onExit)
menubar.add_cascade(label="File", menu=fileMenu)
def onExit(self):
self.quit()
def main():
root = Tk()
root.geometry("250x150+300+300")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
我们的示例将显示一个菜单项。 通过选择退出菜单项,我们关闭应用。
menubar = Menu(self.master)
self.master.config(menu=menubar)
在这里,我们创建一个菜单栏。 这是一个常规的Menu小部件,配置为根窗口的菜单栏。
fileMenu = Menu(menubar)
我们创建一个文件菜单对象。 菜单是一个包含命令的下拉窗口。
fileMenu.add_command(label="Exit", command=self.onExit)
我们向文件菜单添加命令。 该命令将调用onExit()方法。
menubar.add_cascade(label="File", menu=fileMenu)
使用add_cascade()方法将文件菜单添加到菜单栏。

图:简单菜单
Tkinter 子菜单
子菜单是插入另一个菜单对象的菜单。 下一个示例对此进行了演示。
submenu.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
In this script we create a submenu
a separator and keyboard shortcuts to menus.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, Frame, Menu
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Submenu")
menubar = Menu(self.master)
self.master.config(menu=menubar)
fileMenu = Menu(menubar)
submenu = Menu(fileMenu)
submenu.add_command(label="New feed")
submenu.add_command(label="Bookmarks")
submenu.add_command(label="Mail")
fileMenu.add_cascade(label='Import', menu=submenu, underline=0)
fileMenu.add_separator()
fileMenu.add_command(label="Exit", underline=0, command=self.onExit)
menubar.add_cascade(label="File", underline=0, menu=fileMenu)
def onExit(self):
self.quit()
def main():
root = Tk()
root.geometry("250x150+300+300")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
在示例中,文件菜单的子菜单中有三个选项。 我们创建一个分隔符和键盘快捷键。
submenu = Menu(fileMenu)
submenu.add_command(label="New feed")
submenu.add_command(label="Bookmarks")
submenu.add_command(label="Mail")
我们有一个包含三个命令的子菜单。 子菜单是常规菜单。
fileMenu.add_cascade(label='Import', menu=submenu, underline=0)
通过将菜单添加到fileMenu而不是菜单栏,我们创建一个子菜单。 underline参数创建键盘快捷键。 它提供了应加下划线的字符位置。 在我们的情况下,这是第一个。 位置从零开始。 当我们单击“文件”菜单时,将显示一个弹出窗口。 导入菜单下划线一个字符。 我们可以使用鼠标指针或 Alt + I 快捷方式选择它。
fileMenu.add_separator()
分隔符是一条水平线,可以在视觉上分隔菜单命令。 这样,我们可以将项目分组到一些合理的位置。

图:子菜单
Tkinter 弹出菜单
在下一个示例中,我们创建一个弹出菜单。 弹出菜单也称为上下文菜单。 它可以显示在窗口客户区的任何位置。
popup_menu.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
In this program, we create
a popup menu.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, Frame, Menu
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Popup menu")
self.menu = Menu(self.master, tearoff=0)
self.menu.add_command(label="Beep", command=self.bell)
self.menu.add_command(label="Exit", command=self.onExit)
self.master.bind("<Button-3>", self.showMenu)
self.pack()
def showMenu(self, e):
self.menu.post(e.x_root, e.y_root)
def onExit(self):
self.quit()
def main():
root = Tk()
root.geometry("250x150+300+300")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
在我们的示例中,我们使用两个命令创建一个弹出菜单。
self.menu = Menu(self.master, tearoff=0)
上下文菜单是常规的Menu小部件。 tearoff函数已关闭。 现在无法将菜单分离到新的顶层窗口中。
self.master.bind("<Button-3>", self.showMenu)
我们将<Button-3>事件绑定到showMenu()方法。 当我们右键单击窗口的客户区域时,将生成事件。
def showMenu(self, e):
self.menu.post(e.x_root, e.y_root)
showMenu()方法显示上下文菜单。 弹出菜单显示在鼠标单击的 x 和 y 坐标处。

图:弹出菜单
Tkinter 工具栏
菜单将我们可以在应用中使用的命令分组。 使用工具栏可以快速访问最常用的命令。 Tkinter 中没有工具栏小部件。
toolbar.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
In this program, we create a toolbar.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from PIL import Image, ImageTk
from tkinter import Tk, Frame, Menu, Button
from tkinter import LEFT, TOP, X, FLAT, RAISED
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Toolbar")
menubar = Menu(self.master)
self.fileMenu = Menu(self.master, tearoff=0)
self.fileMenu.add_command(label="Exit", command=self.onExit)
menubar.add_cascade(label="File", menu=self.fileMenu)
toolbar = Frame(self.master, bd=1, relief=RAISED)
self.img = Image.open("exit.png")
eimg = ImageTk.PhotoImage(self.img)
exitButton = Button(toolbar, image=eimg, relief=FLAT,
command=self.quit)
exitButton.image = eimg
exitButton.pack(side=LEFT, padx=2, pady=2)
toolbar.pack(side=TOP, fill=X)
self.master.config(menu=menubar)
self.pack()
def onExit(self):
self.quit()
def main():
root = Tk()
root.geometry("250x150+300+300")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
我们的工具栏位于我们放置按钮的框架上。
toolbar = Frame(self.master, bd=1, relief=RAISED)
工具栏已创建。 它是Frame。 我们创建了一个凸起的边框,以便可见工具栏的边界。
self.img = Image.open("exit.png")
eimg = ImageTk.PhotoImage(self.img)
创建工具栏按钮的图像和照片图像。
exitButton = Button(toolbar, image=eimg, relief=FLAT,
command=self.quit)
Button小部件已创建。
exitButton.pack(side=LEFT, padx=2, pady=2)
工具栏是框架,框架是容器小部件。 我们将按钮包装在左侧,并添加一些填充。
toolbar.pack(side=TOP, fill=X)
工具栏本身包装在顶层窗口的顶部。 它是水平拉伸的。

图:工具栏
在 Tkinter 教程的这一部分中,我们使用了菜单和工具栏。
Tkinter 中的对话框
在 Tkinter 教程的这一部分中,我们将使用对话框。
对话框窗口或对话框是大多数现代 GUI 应用必不可少的部分。 对话被定义为两个或更多人之间的对话。 在计算机应用中,对话框是一个窗口,用于与应用“对话”。 对话框用于输入数据,修改数据,更改应用设置等。对话框是用户与计算机程序之间进行通信的重要手段。
Tkinter 消息框
消息框是方便的对话框,可向应用的用户提供消息。 该消息由文本和图像数据组成。 Tkinter 中的消息框位于tkMessageBox模块中。
messagebox.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
In this program, we show various
message boxes.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, BOTH
from tkinter.ttk import Frame, Button
from tkinter import messagebox as mbox
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Message boxes")
self.pack()
error = Button(self, text="Error", command=self.onError)
error.grid(padx=5, pady=5)
warning = Button(self, text="Warning", command=self.onWarn)
warning.grid(row=1, column=0)
question = Button(self, text="Question", command=self.onQuest)
question.grid(row=0, column=1)
inform = Button(self, text="Information", command=self.onInfo)
inform.grid(row=1, column=1)
def onError(self):
mbox.showerror("Error", "Could not open file")
def onWarn(self):
mbox.showwarning("Warning", "Deprecated function call")
def onQuest(self):
mbox.askquestion("Question", "Are you sure to quit?")
def onInfo(self):
mbox.showinfo("Information", "Download completed")
def main():
root = Tk()
ex = Example()
root.geometry("300x150+300+300")
root.mainloop()
if __name__ == '__main__':
main()
我们使用网格管理器来设置四个按钮的网格。 每个按钮显示一个不同的消息框。
from tkinter import messagebox as mbox
我们导入messagebox,它具有显示对话框的功能。
error = Button(self, text="Error", command=self.onError)
我们创建一个错误按钮,该按钮调用onError()方法。 在方法内部,我们显示错误消息对话框。
def onError(self):
mbox.showerror("Error", "Could not open file")
如果按下错误按钮,则会显示错误对话框。 我们使用showerror()函数在屏幕上显示对话框。 此方法的第一个参数是消息框的标题,第二个参数是实际消息。

图:错误消息 dialog
Tkinter 颜色选择器
颜色选择器是用于选择颜色的对话框。
color_chooser.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
In this script, we use colorchooser
dialog to change the background of a frame.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, Frame, Button, BOTH, SUNKEN
from tkinter import colorchooser
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Color chooser")
self.pack(fill=BOTH, expand=1)
self.btn = Button(self, text="Choose Color",
command=self.onChoose)
self.btn.place(x=30, y=30)
self.frame = Frame(self, border=1,
relief=SUNKEN, width=100, height=100)
self.frame.place(x=160, y=30)
def onChoose(self):
(rgb, hx) = colorchooser.askcolor()
self.frame.config(bg=hx)
def main():
root = Tk()
ex = Example()
root.geometry("300x150+300+300")
root.mainloop()
if __name__ == '__main__':
main()
我们有一个按钮和一个框架。 单击按钮,我们显示一个颜色选择器对话框。 我们将通过从对话框中选择一种颜色来更改框架的背景颜色。
(rgb, hx) = colorchooser.askcolor()
self.frame.config(bg=hx)
askcolor()函数显示对话框。 如果单击“确定”,则返回一个元组。 它是 RGB 和十六进制格式的颜色值。 在第二行中,我们使用返回的颜色值更改框架的背景颜色。

图:颜色选择器
Tkinter 文件对话框
tkFileDialog对话框允许用户从文件系统中选择文件。
file_dialog.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
In this program, we use the
tkFileDialog to select a file from
a filesystem.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Frame, Tk, BOTH, Text, Menu, END
from tkinter import filedialog
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("File dialog")
self.pack(fill=BOTH, expand=1)
menubar = Menu(self.master)
self.master.config(menu=menubar)
fileMenu = Menu(menubar)
fileMenu.add_command(label="Open", command=self.onOpen)
menubar.add_cascade(label="File", menu=fileMenu)
self.txt = Text(self)
self.txt.pack(fill=BOTH, expand=1)
def onOpen(self):
ftypes = [('Python files', '*.py'), ('All files', '*')]
dlg = filedialog.Open(self, filetypes = ftypes)
fl = dlg.show()
if fl != '':
text = self.readFile(fl)
self.txt.insert(END, text)
def readFile(self, filename):
with open(filename, "r") as f:
text = f.read()
return text
def main():
root = Tk()
ex = Example()
root.geometry("300x250+300+300")
root.mainloop()
if __name__ == '__main__':
main()
在我们的代码示例中,我们使用tkFileDialog对话框选择一个文件,并在Text小部件中显示其内容。
self.txt = Text(self)
这是Text小部件,我们将在其中显示所选文件的内容。
ftypes = [('Python files', '*.py'), ('All files', '*')]
这些是文件过滤器。 第一个仅显示 Python 文件,另一个显示所有文件。
dlg = filedialog.Open(self, filetypes = ftypes)
fl = dlg.show()
该对话框已创建并显示在屏幕上。 我们得到返回值,它是所选文件的名称。
text = self.readFile(fl)
我们读取了文件的内容。
self.txt.insert(END, text)
文本被插入到Text小部件中。

图:文件对话框
在 Tkinter 教程的这一部分中,我们使用了对话框窗口。
Tkinter 中的绘图
在 Tkinter 教程的这一部分中,我们将进行一些绘制。 在Canvas小部件上完成了 Tkinter 的绘制。 Canvas是用于在 Tkinter 中进行图形处理的高级工具。
它可用于创建图表,自定义窗口小部件或创建游戏。
直线
线是简单的几何图元。 create_line()方法在Canvas上创建一个订单项。
lines.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
The example draws lines on the Canvas.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, Canvas, Frame, BOTH
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Lines")
self.pack(fill=BOTH, expand=1)
canvas = Canvas(self)
canvas.create_line(15, 25, 200, 25)
canvas.create_line(300, 35, 300, 200, dash=(4, 2))
canvas.create_line(55, 85, 155, 85, 105, 180, 55, 85)
canvas.pack(fill=BOTH, expand=1)
def main():
root = Tk()
ex = Example()
root.geometry("400x250+300+300")
root.mainloop()
if __name__ == '__main__':
main()
在代码示例中,我们绘制了简单的线条。
canvas.create_line(15, 25, 200, 25)
create_line()方法的参数是直线起点和终点的 x 和 y 坐标。
canvas.create_line(300, 35, 300, 200, dash=(4, 2))
画一条垂直线。 dash选项指定线条的虚线图案。 我们有一条线,由 4px 虚线和 2px 间隔的交替部分组成。
canvas.create_line(55, 85, 155, 85, 105, 180, 55, 85)
create_line()方法可以取多个点。 这条线画了一个三角形。

图:直线
颜色
颜色是代表红色,绿色和蓝色(RGB)强度值的组合的对象。
colours.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
This program draws three
rectangles filled with different
colours.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, Canvas, Frame, BOTH
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Colours")
self.pack(fill=BOTH, expand=1)
canvas = Canvas(self)
canvas.create_rectangle(30, 10, 120, 80,
outline="#fb0", fill="#fb0")
canvas.create_rectangle(150, 10, 240, 80,
outline="#f50", fill="#f50")
canvas.create_rectangle(270, 10, 370, 80,
outline="#05f", fill="#05f")
canvas.pack(fill=BOTH, expand=1)
def main():
root = Tk()
ex = Example()
root.geometry("400x100+300+300")
root.mainloop()
if __name__ == '__main__':
main()
在代码示例中,我们绘制了三个矩形,并用不同的颜色值填充了它们。
canvas = Canvas(self)
我们创建Canvas小部件。
canvas.create_rectangle(30, 10, 120, 80,
outline="#fb0", fill="#fb0")
create_rectangle()在画布上创建一个矩形项目。 前四个参数是两个边界点的 x 和 y 坐标:左上角点和右下角点。 使用outline参数,我们可以控制矩形轮廓的颜色。 同样,fill参数为矩形的内部提供颜色。

图:颜色
形状
我们可以在Canvas上绘制各种形状。 以下代码示例将显示其中的一些。
shapes.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
In this script, we draw basic
shapes on the canvas.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, Canvas, Frame, BOTH
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Shapes")
self.pack(fill=BOTH, expand=1)
canvas = Canvas(self)
canvas.create_oval(10, 10, 80, 80, outline="#f11",
fill="#1f1", width=2)
canvas.create_oval(110, 10, 210, 80, outline="#f11",
fill="#1f1", width=2)
canvas.create_rectangle(230, 10, 290, 60,
outline="#f11", fill="#1f1", width=2)
canvas.create_arc(30, 200, 90, 100, start=0,
extent=210, outline="#f11", fill="#1f1", width=2)
points = [150, 100, 200, 120, 240, 180, 210,
200, 150, 150, 100, 200]
canvas.create_polygon(points, outline='#f11',
fill='#1f1', width=2)
canvas.pack(fill=BOTH, expand=1)
def main():
root = Tk()
ex = Example()
root.geometry("330x220+300+300")
root.mainloop()
if __name__ == '__main__':
main()
我们在窗口上绘制五个不同的形状:一个圆形,一个椭圆形,一个矩形,一个弧形和一个多边形。 轮廓以红色绘制,内部以绿色绘制。 轮廓的宽度为 2 像素。
canvas.create_oval(10, 10, 80, 80, outline="#f11",
fill="#1f1", width=2)
此处create_oval()方法用于创建圈子项目。 前四个参数是圆的边界框坐标。 换句话说,它们是在其中绘制圆的框的左上和右下点的 x 和 y 坐标。
canvas.create_rectangle(230, 10, 290, 60,
outline="#f11", fill="#1f1", width=2)
我们创建一个矩形项目。 坐标还是要绘制的矩形的边界框。
canvas.create_arc(30, 200, 90, 100, start=0,
extent=210, outline="#f11", fill="#1f1", width=2)
该代码行创建了一条弧。 圆弧是圆的圆周的一部分。 我们提供边界框。 start参数是圆弧的起始角度。 extent是角度大小。
points = [150, 100, 200, 120, 240, 180, 210,
200, 150, 150, 100, 200]
canvas.create_polygon(points, outline='#f11',
fill='#1f1', width=2)
创建一个多边形。 它是具有多个角的形状。 要在 Tkinter 中创建多边形,我们向create_polygon()方法提供了多边形坐标列表。

图:形状
绘制图像
在下面的示例中,我们在画布上绘制一个图像项。
draw_image.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
In this script, we draw an image
on the canvas.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, Canvas, Frame, BOTH, NW
from PIL import Image, ImageTk
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("High Tatras")
self.pack(fill=BOTH, expand=1)
self.img = Image.open("tatras.jpg")
self.tatras = ImageTk.PhotoImage(self.img)
canvas = Canvas(self, width=self.img.size[0]+20,
height=self.img.size[1]+20)
canvas.create_image(10, 10, anchor=NW, image=self.tatras)
canvas.pack(fill=BOTH, expand=1)
def main():
root = Tk()
ex = Example()
root.mainloop()
if __name__ == '__main__':
main()
该示例在画布上显示图像。
from PIL import Image, ImageTk
从 PIL(Python 图像库)模块,导入Image和ImageTk模块。
self.img = Image.open("tatras.jpg")
self.tatras = ImageTk.PhotoImage(self.img)
Tkinter 在内部不支持 JPG 图像。 解决方法是,使用Image和ImageTk模块。
canvas = Canvas(self, width=self.img.size[0]+20,
height=self.img.size[1]+20)
我们创建Canvas小部件。 它考虑了图像的大小。 它比实际图像大小宽 20px,高 20px。
canvas.create_image(10, 10, anchor=NW, image=self.tatras)
我们使用create_image()方法在画布上创建一个图像项。 为了显示整个图像,它固定在北部和西部。 image参数提供要显示的照片图像。
绘制文字
在最后一个示例中,我们将在窗口上绘制文本。
draw_text.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
In this script, we draw text
on the window.
Author: Jan Bodnar
Last modified: April 2019
Website: www.zetcode.com
"""
from tkinter import Tk, Canvas, Frame, BOTH, W
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Lyrics")
self.pack(fill=BOTH, expand=1)
canvas = Canvas(self)
canvas.create_text(20, 30, anchor=W, font="Purisa",
text="Most relationships seem so transitory")
canvas.create_text(20, 60, anchor=W, font="Purisa",
text="They're good but not the permanent one")
canvas.create_text(20, 130, anchor=W, font="Purisa",
text="Who doesn't long for someone to hold")
canvas.create_text(20, 160, anchor=W, font="Purisa",
text="Who knows how to love without being told")
canvas.create_text(20, 190, anchor=W, font="Purisa",
text="Somebody tell me why I'm on my own")
canvas.create_text(20, 220, anchor=W, font="Purisa",
text="If there's a soulmate for everyone")
canvas.pack(fill=BOTH, expand=1)
def main():
root = Tk()
ex = Example()
root.geometry("420x250+300+300")
root.mainloop()
if __name__ == '__main__':
main()
我们在窗口上画一首歌的歌词。
canvas.create_text(20, 30, anchor=W, font="Purisa",
text="Most relationships seem so transitory")
前两个参数是文本中心点的 x 和 y 坐标。 如果我们将文本项锚定在西方,则文本将从该位置开始。 font参数提供文本的字体,text参数是要显示的文本。

图:绘制文本
在 Tkinter 教程的这一部分中,我们做了一些绘图。
Tkinter 中的贪食蛇
在 Tkinter 教程的这一部分中,我们创建一个贪食蛇游戏克隆。
贪食蛇是较旧的经典视频游戏。 它最初是在 70 年代后期创建的。 后来它被带到 PC 上。 在这个游戏中,玩家控制蛇。 目的是尽可能多地吃苹果。 蛇每次吃一个苹果,它的身体就会长大。 蛇必须避开墙壁和自己的身体。
开发
蛇的每个关节的大小为 10 像素。 蛇由光标键控制。 最初,蛇具有三个关节。 游戏立即开始。 游戏结束后,我们将在窗口中心显示分数上方的游戏结束消息。
我们使用Canvas小部件来创建游戏。 游戏中的对象是图像。 我们使用画布方法创建图像项。 我们使用画布方法使用标签在画布上查找项目并进行碰撞检测。
snake.py
#!/usr/bin/env python3
"""
ZetCode Tkinter tutorial
This is a simple Snake game
clone.
Author: Jan Bodnar
Website: zetcode.com
Last edited: April 2019
"""
import sys
import random
from PIL import Image, ImageTk
from tkinter import Tk, Frame, Canvas, ALL, NW
class Cons:
BOARD_WIDTH = 300
BOARD_HEIGHT = 300
DELAY = 100
DOT_SIZE = 10
MAX_RAND_POS = 27
class Board(Canvas):
def __init__(self):
super().__init__(width=Cons.BOARD_WIDTH, height=Cons.BOARD_HEIGHT,
background="black", highlightthickness=0)
self.initGame()
self.pack()
def initGame(self):
'''initializes game'''
self.inGame = True
self.dots = 3
self.score = 0
# variables used to move snake object
self.moveX = Cons.DOT_SIZE
self.moveY = 0
# starting apple coordinates
self.appleX = 100
self.appleY = 190
self.loadImages()
self.createObjects()
self.locateApple()
self.bind_all("<Key>", self.onKeyPressed)
self.after(Cons.DELAY, self.onTimer)
def loadImages(self):
'''loads images from the disk'''
try:
self.idot = Image.open("dot.png")
self.dot = ImageTk.PhotoImage(self.idot)
self.ihead = Image.open("head.png")
self.head = ImageTk.PhotoImage(self.ihead)
self.iapple = Image.open("apple.png")
self.apple = ImageTk.PhotoImage(self.iapple)
except IOError as e:
print(e)
sys.exit(1)
def createObjects(self):
'''creates objects on Canvas'''
self.create_text(30, 10, text="Score: {0}".format(self.score),
tag="score", fill="white")
self.create_image(self.appleX, self.appleY, image=self.apple,
anchor=NW, tag="apple")
self.create_image(50, 50, image=self.head, anchor=NW, tag="head")
self.create_image(30, 50, image=self.dot, anchor=NW, tag="dot")
self.create_image(40, 50, image=self.dot, anchor=NW, tag="dot")
def checkAppleCollision(self):
'''checks if the head of snake collides with apple'''
apple = self.find_withtag("apple")
head = self.find_withtag("head")
x1, y1, x2, y2 = self.bbox(head)
overlap = self.find_overlapping(x1, y1, x2, y2)
for ovr in overlap:
if apple[0] == ovr:
self.score += 1
x, y = self.coords(apple)
self.create_image(x, y, image=self.dot, anchor=NW, tag="dot")
self.locateApple()
def moveSnake(self):
'''moves the Snake object'''
dots = self.find_withtag("dot")
head = self.find_withtag("head")
items = dots + head
z = 0
while z < len(items)-1:
c1 = self.coords(items[z])
c2 = self.coords(items[z+1])
self.move(items[z], c2[0]-c1[0], c2[1]-c1[1])
z += 1
self.move(head, self.moveX, self.moveY)
def checkCollisions(self):
'''checks for collisions'''
dots = self.find_withtag("dot")
head = self.find_withtag("head")
x1, y1, x2, y2 = self.bbox(head)
overlap = self.find_overlapping(x1, y1, x2, y2)
for dot in dots:
for over in overlap:
if over == dot:
self.inGame = False
if x1 < 0:
self.inGame = False
if x1 > Cons.BOARD_WIDTH - Cons.DOT_SIZE:
self.inGame = False
if y1 < 0:
self.inGame = False
if y1 > Cons.BOARD_HEIGHT - Cons.DOT_SIZE:
self.inGame = False
def locateApple(self):
'''places the apple object on Canvas'''
apple = self.find_withtag("apple")
self.delete(apple[0])
r = random.randint(0, Cons.MAX_RAND_POS)
self.appleX = r * Cons.DOT_SIZE
r = random.randint(0, Cons.MAX_RAND_POS)
self.appleY = r * Cons.DOT_SIZE
self.create_image(self.appleX, self.appleY, anchor=NW,
image=self.apple, tag="apple")
def onKeyPressed(self, e):
'''controls direction variables with cursor keys'''
key = e.keysym
LEFT_CURSOR_KEY = "Left"
if key == LEFT_CURSOR_KEY and self.moveX <= 0:
self.moveX = -Cons.DOT_SIZE
self.moveY = 0
RIGHT_CURSOR_KEY = "Right"
if key == RIGHT_CURSOR_KEY and self.moveX >= 0:
self.moveX = Cons.DOT_SIZE
self.moveY = 0
RIGHT_CURSOR_KEY = "Up"
if key == RIGHT_CURSOR_KEY and self.moveY <= 0:
self.moveX = 0
self.moveY = -Cons.DOT_SIZE
DOWN_CURSOR_KEY = "Down"
if key == DOWN_CURSOR_KEY and self.moveY >= 0:
self.moveX = 0
self.moveY = Cons.DOT_SIZE
def onTimer(self):
'''creates a game cycle each timer event'''
self.drawScore()
self.checkCollisions()
if self.inGame:
self.checkAppleCollision()
self.moveSnake()
self.after(Cons.DELAY, self.onTimer)
else:
self.gameOver()
def drawScore(self):
'''draws score'''
score = self.find_withtag("score")
self.itemconfigure(score, text="Score: {0}".format(self.score))
def gameOver(self):
'''deletes all objects and draws game over message'''
self.delete(ALL)
self.create_text(self.winfo_width() /2, self.winfo_height()/2,
text="Game Over with score {0}".format(self.score), fill="white")
class Snake(Frame):
def __init__(self):
super().__init__()
self.master.title('Snake')
self.board = Board()
self.pack()
def main():
root = Tk()
nib = Snake()
root.mainloop()
if __name__ == '__main__':
main()
首先,我们将定义一些在游戏中使用的常量。
class Cons:
BOARD_WIDTH = 300
BOARD_HEIGHT = 300
DELAY = 100
DOT_SIZE = 10
MAX_RAND_POS = 27
BOARD_WIDTH和BOARD_HEIGHT常数确定电路板的大小。 DELAY常数确定游戏的速度。 DOT_SIZE是苹果的大小和蛇的点。 MAX_RAND_POS常数用于计算苹果的随机位置。
initGame()方法初始化变量,加载图像并启动超时功能。
self.createObjects()
self.locateApple()
createObjects()方法在画布上创建项目。 locateApple()在画布上随机放置一个苹果。
self.bind_all("<Key>", self.onKeyPressed)
我们将键盘事件绑定到onKeyPressed()方法。 游戏由键盘光标键控制。
try:
self.idot = Image.open("dot.png")
self.dot = ImageTk.PhotoImage(self.idot)
self.ihead = Image.open("head.png")
self.head = ImageTk.PhotoImage(self.ihead)
self.iapple = Image.open("apple.png")
self.apple = ImageTk.PhotoImage(self.iapple)
except IOError as e:
print(e)
sys.exit(1)
在这些行中,我们加载图像。 贪食蛇游戏中包含三个图像:头部,圆点和苹果。
def createObjects(self):
'''creates objects on Canvas'''
self.create_text(30, 10, text="Score: {0}".format(self.score),
tag="score", fill="white")
self.create_image(self.appleX, self.appleY, image=self.apple,
anchor=NW, tag="apple")
self.create_image(50, 50, image=self.head, anchor=NW, tag="head")
self.create_image(30, 50, image=self.dot, anchor=NW, tag="dot")
self.create_image(40, 50, image=self.dot, anchor=NW, tag="dot")
在createObjects()方法中,我们在画布上创建游戏对象。 这些是帆布项目。 它们被赋予初始的 x 和 y 坐标。 image参数提供要显示的图像。 anchor参数设置为NW; 这样,画布项目的坐标就是项目的左上角。 如果我们希望能够在根窗口的边框旁边显示图像,这很重要。 尝试删除锚点,看看会发生什么。 tag参数用于识别画布上的项目。 一个标签可用于多个画布项目。
checkAppleCollision()方法检查蛇是否击中了苹果对象。 如果是这样,我们会增加分数,添加另一个蛇形关节,并称为locateApple()。
apple = self.find_withtag("apple")
head = self.find_withtag("head")
find_withtag()方法使用其标签在画布上找到一个项目。 我们需要两个项目:蛇的头和苹果。 请注意,即使只有一个带有给定标签的项目,该方法也会返回一个元组。 苹果产品就是这种情况。 然后,可以通过以下方式访问苹果项目:apple[0]。
x1, y1, x2, y2 = self.bbox(head)
overlap = self.find_overlapping(x1, y1, x2, y2)
bbox()方法返回项目的边界框点。 find_overlapping()方法查找给定坐标的冲突项。
for ovr in overlap:
if apple[0] == ovr:
x, y = self.coords(apple)
self.create_image(x, y, image=self.dot, anchor=NW, tag="dot")
self.locateApple()
如果苹果与头部碰撞,我们将在苹果对象的坐标处创建一个新的点项目。 我们调用locateApple()方法,该方法从画布上删除旧的苹果项目,然后创建并随机放置一个新的项目。
在moveSnake()方法中,我们有游戏的关键算法。 要了解它,请看一下蛇是如何运动的。 我们控制蛇的头。 我们可以使用光标键更改其方向。 其余关节在链上向上移动一个位置。 第二关节移动到第一个关节的位置,第三关节移动到第二个关节的位置,依此类推。
z = 0
while z < len(items)-1:
c1 = self.coords(items[z])
c2 = self.coords(items[z+1])
self.move(items[z], c2[0]-c1[0], c2[1]-c1[1])
z += 1
该代码将关节向上移动。
self.move(head, self.moveX, self.moveY)
我们使用move()方法移动磁头。 按下光标键时,将设置self.moveX和self.moveY变量。
在checkCollisions()方法中,我们确定蛇是否击中了自己或撞墙之一。
x1, y1, x2, y2 = self.bbox(head)
overlap = self.find_overlapping(x1, y1, x2, y2)
for dot in dots:
for over in overlap:
if over == dot:
self.inGame = False
如果蛇用头撞到关节之一,我们就结束游戏。
if y1 > Cons.BOARD_HEIGHT - Cons.DOT_SIZE:
self.inGame = False
如果蛇击中Board的底部,我们就结束游戏。
locateApple()方法在板上随机找到一个新苹果,然后删除旧的苹果。
apple = self.find_withtag("apple")
self.delete(apple[0])
在这里,我们找到并删除了被蛇吃掉的苹果。
r = random.randint(0, Cons.MAX_RAND_POS)
我们得到一个从 0 到MAX_RAND_POS-1的随机数。
self.appleX = r * Cons.DOT_SIZE
...
self.appleY = r * Cons.DOT_SIZE
这些行设置了apple对象的 x 和 y 坐标。
在onKeyPressed()方法中,我们在游戏过程中对按下的键做出反应。
LEFT_CURSOR_KEY = "Left"
if key == LEFT_CURSOR_KEY and self.moveX <= 0:
self.moveX = -Cons.DOT_SIZE
self.moveY = 0
如果我们按左光标键,则相应地设置self.moveX和self.moveY变量。 在moveSnake()方法中使用这些变量来更改蛇对象的坐标。 还要注意,当蛇向右行驶时,我们不能立即向左转。
def onTimer(self):
'''creates a game cycle each timer event '''
self.drawScore()
self.checkCollisions()
if self.inGame:
self.checkAppleCollision()
self.moveSnake()
self.after(Cons.DELAY, self.onTimer)
else:
self.gameOver()
每DELAY ms,就会调用onTimer()方法。 如果我们参与了游戏,我们将调用三种构建游戏逻辑的方法。 否则,游戏结束。 计时器基于after()方法,该方法仅在DELAY ms 之后调用一次方法。 要重复调用计时器,我们递归调用onTimer()方法。
def drawScore(self):
'''draws score'''
score = self.find_withtag("score")
self.itemconfigure(score, text="Score: {0}".format(self.score))
drawScore()方法在板上画分数。
def gameOver(self):
'''deletes all objects and draws game over message'''
self.delete(ALL)
self.create_text(self.winfo_width() /2, self.winfo_height()/2,
text="Game Over with score {0}".format(self.score), fill="white")
如果游戏结束,我们将删除画布上的所有项目。 然后,我们将游戏的最终比分绘制在屏幕中央。

图:贪食蛇
这是用 Tkinter 创建的贪食蛇电脑游戏。
Qt Quick 教程
这是 Qt Quick 入门教程。 本教程讲授了 Qt Quick 中编程的基础知识。 本教程使用 Qt 5.5.1 编写。
Qt Quick
Qt Quick 是一种现代的用户界面技术,将声明性用户界面设计和命令性编程逻辑分开。 它是 Qt 框架内的一个应用框架。 它提供了一种构建自定义的,高度动态的,具有流畅过渡和效果的用户界面的方式,这种方式在移动设备中尤为常见。
Qt Quick 是与 Qt Widgets 分离的模块,该模块针对传统的桌面应用。 Qt Quick 基于 QML 声明性语言。
QML
QML 是一种用户界面规范和编程语言。 它允许创建流畅的动画和视觉吸引力的应用。 QML 提供了一种高度可读的,声明性的,类似于 JSON 的语法,并支持将命令性 JavaScript 表达式与动态属性绑定结合在一起。
QML 由元素层次构成。
简单的例子
我们从一个简单的例子开始。
simple.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
ApplicationWindow {
width: 300
height: 200
title: "Simple"
Text {
text: "Qt Quick"
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
font.pointSize: 24; font.bold: true
}
}
该代码创建一个带有居中文本的小窗口。
import QtQuick 2.5
import QtQuick.Controls 1.4
导入了必要的模块。 Qt Quick 模块的最新版本与 Qt 版本不同。 这些是 Qt 5.5.1 的最新模块。
ApplicationWindow {
...
}
ApplicationWindow是用于主应用窗口的 Qt Quick 控件。 用户界面元素由其类型名称指定,后跟两个大括号。
width: 300
height: 200
title: "Simple"
这是ApplicationWindow元素的三个内置属性。 它们指定窗口的宽度,高度和标题。
Text {
text: "Qt Quick"
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
font.pointSize: 24
}
Text控件显示文本; 文本是使用text属性指定的。 在其父元素ApplicationWindow元素中声明它。 我们通过parent属性引用父对象。 anchors用于在应用窗口中将Text控件居中。 最后,font属性用于设置文本的大小。 parent和font是组属性的示例。

图:简单 example
用qmlscene工具加载simple.qml文档后,我们得到了这张图片。
退出按钮
在第二个示例中,我们展示Button控件。
quit_button.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
ApplicationWindow {
width: 300
height: 200
title: "Quit button"
Button {
x: 20
y: 20
text: "Quit"
onClicked: Qt.quit()
}
}
窗口上放置了一个按钮。 单击该按钮可终止该应用。
Button {
x: 20
y: 20
text: "Quit"
onClicked: Qt.quit()
}
Button控件嵌套在ApplicationWindow元素内。 它放置在x = 20,y = 20坐标处; 坐标相对于窗口的左上角。 text属性指定按钮的标签。 onClicked()是按钮单击信号的处理器。 Qt.quick()函数终止应用。

图:退出按钮
CheckBox
CheckBox是 Qt Quick 控件,具有两种状态:开和关。 复选框通常用于表示可以启用或禁用的应用中的功能。
mycheckbox.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
ApplicationWindow {
id: rootwin
width: 300
height: 200
title: "CheckBox"
function onChecked(checked) {
if (checked) {
rootwin.title = "CheckBox"
} else {
rootwin.title = " "
}
}
CheckBox {
x: 15
y: 15
text: "Show title"
checked: true
onClicked: rootwin.onChecked(checked)
}
}
在我们的示例中,我们在窗口上放置了一个检查按钮。 复选按钮显示或隐藏窗口的标题。
id: rootwin
id是一个特殊值,用于引用 QML 文档中的元素。 id 在文档中必须唯一,并且不能将其重置为其他值,也无法查询。
function onChecked(checked) {
if (checked) {
rootwin.title = "CheckBox"
} else {
rootwin.title = " "
}
}
onChecked是一个 JavaScript 函数,用于设置或删除窗口的标题。 为此,我们使用先前创建的rootwin ID。
CheckBox {
x: 15
y: 15
text: "Show title"
checked: true
onClicked: rootwin.onChecked(checked)
}
由于标题在应用的开头是可见的,因此我们使用checked属性将CheckBox设置为选中状态。 onClicked处理器调用onChecked函数。 由于它是在根窗口的空间中定义的,因此我们再次使用rootwin id 来引用它。

图:CheckBox
滑杆
Slider是具有简单句柄的控件。 可以前后拉动此手柄,从而为特定任务选择一个值。
slider.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
ApplicationWindow {
id: rootwin
width: 300
height: 200
title: "Slider"
Row {
Slider {
id: slider
minimumValue: 0
maximumValue: 100
}
Label {
text: Math.floor(slider.value)
}
}
}
窗口上放置了Slider和Label控件。 拉动滑块,我们将更新标签。
Row {
...
}
Row是 QML 类型,其子项沿一行放置。
Slider {
id: slider
minimumValue: 0
maximumValue: 100
}
创建一个Slider控件。 我们指定其最小值和最大值。
Label {
text: Math.floor(slider.value)
}
标签的text属性绑定到滑块的value属性。 这称为属性绑定。

图:滑块
NumberAnimation
Qt Quick 中提供了几种动画类型。 其中之一是NumberAnimation。 NumberAnimation是数值变化的特殊属性动画。
numberanim.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
ApplicationWindow {
width: 400
height: 300
title: "Number animation"
Rectangle {
x: 20
y: 20
width: 100; height: 100
color: "forestgreen"
NumberAnimation on x { to: 250; duration: 1000 }
}
}
在示例中,我们使用NumberAnimation为矩形设置动画; 矩形沿 x 轴移动一秒钟。
NumberAnimation on x { to: 250; duration: 1000 }
动画将应用于Rectangle的x属性。 to:属性保存动画的结束值。 duration:属性保存动画的持续时间(以毫秒为单位)。
自定义绘图
可以在Canvas元素上执行自定义绘图。
shapes.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
ApplicationWindow {
width: 400
height: 200
title: "Shapes"
Canvas {
anchors.fill: parent
onPaint: {
var ctx = getContext("2d");
ctx.fillStyle = "lightslategray"
ctx.beginPath();
ctx.fillRect(10, 10, 80, 50);
ctx.beginPath();
ctx.fillRect(120, 10, 70, 70);
ctx.beginPath();
ctx.ellipse(230, 10, 90, 70);
ctx.fill();
ctx.beginPath();
ctx.ellipse(10, 110, 70, 70);
ctx.fill();
ctx.beginPath();
ctx.roundedRect(120, 110, 70, 70, 10, 10);
ctx.fill();
ctx.beginPath();
ctx.moveTo(230, 110);
ctx.arc(230, 110, 70, 0, Math.PI * 0.5, false);
ctx.fill();
}
}
}
在示例中,我们在画布上绘制了六个不同的形状:矩形,正方形,椭圆形,圆形,圆角矩形和弧形。
Canvas {
anchors.fill: parent
...
}
Canvas填充整个父级。
var ctx = getContext("2d");
我们使用getContext()函数获得绘图上下文。
ctx.fillStyle = "lightslategray"
形状的内部充满了光亮的色彩。
ctx.beginPath();
ctx.fillRect(10, 10, 80, 50);
beginPath()函数开始一个新路径。 fillRect()使用fillStyle绘制指定的矩形区域。

图:形状
在 C++ 中部署 Qt Quick 应用
在本节中,我们显示如何在 C++ 中部署 Qt Quick 应用。
simple.pro
QT += qml quick
TARGET = Simple
TEMPLATE = app
SOURCES += main.cpp
这是项目文件。 它在应用中包括 qml 和 quick 模块。
basic.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
ApplicationWindow {
width: 300
height: 200
title: "Simple"
Text {
text: "Qt Quick"
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
font.pointSize: 24
}
}
这是要在 C++ 应用中显示的 QML 文档。 它包含居中文本。
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickWindow>
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl("simple.qml"));
QObject *topLevel = engine.rootObjects().value(0);
QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel);
window->show();
return app.exec();
}
QQmlApplicationEngine用于加载 QML 文档。
在 PyQt5 中部署 Qt Quick 应用
在本节中,我们显示如何在 PyQt5 中部署 Qt Quick 应用。
$ sudo apt-get install python3-pyqt5
$ sudo apt-get install python3-pyqt5.qtquick
$ sudo apt-get install qtdeclarative5-qtquick2-plugin
在基于 Debian 的 Linux 上,我们可以安装上述包以使事情顺利进行。
basic.qml
import QtQuick 2.2
Rectangle {
x: 20
y: 20
width: 100
height: 100
color: "lightsteelblue"
}
这是要在 PyQt5 应用中显示的 QML 文档; 它包含一个矩形对象。
launcher.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import QUrl
from PyQt5.QtQuick import QQuickView
if __name__ == "__main__":
app = QApplication(sys.argv)
view = QQuickView()
view.setSource(QUrl('basic.qml'))
view.show()
sys.exit(app.exec_())
QQuickView类提供了一个用于显示 Qt Quick 用户界面的窗口。
这是 QtQuick 教程。 您可能也对 Qt5 教程或 PyQt5 教程感兴趣。
Java Swing 教程
这是 Java Swing 教程。 Java Swing 教程适合初学者和中级 Swing 开发者。 阅读本教程后,您将能够开发非平凡的 Java Swing 应用。 可以在作者的 Github Java-Swing-Examples 仓库中找到这些代码示例。
目录
- 简介
- 首个程序
- 菜单和工具栏
- Swing 布局管理
GroupLayout管理器- Swing 事件
- 基本的 Swing 组件
- 基本 Swing 组件 II
- Swing 对话框
- Swing 模型
- 拖放
- 绘图
- 可调整大小的组件
- 拼图
- 俄罗斯方块
Swing
Swing 是 Java 编程语言的主要 GUI 工具包。 它是 JFC(Java 基础类)的一部分,JFC 是用于为 Java 程序提供图形用户界面的 API。 它完全用 Java 编写。
电子书
高级 Java Swing 电子书涵盖了高级 Java Swing 主题。 它具有 621 页和 206 个代码示例。
还有用于 Swing 布局管理过程的电子书: Java Swing 布局管理教程。
相关教程
ZetCode 上有一些相关的教程。 JavaFX 教程涵盖了 JavaFX(现代 Java RIA 平台)。 Java SWT 教程是第三方 Java GUI 工具包的教程。 Java 2D 游戏教程和 Java 2D 教程进一步增强了您对 Java 图形编程的了解。 Java 教程讲授 Java 语言的基础知识。
Java Swing 简介
原文: http://zetcode.com/tutorials/javaswingtutorial/introduction/
这是 Swing 入门教程。 本教程的目的是使您开始使用 Java Swing 工具包。 该教程已在 Linux 上创建并测试。
关于 Swing
Swing 库是 Sun Microsystems 发行的官方 Java GUI 工具箱。 它用于使用 Java 创建图形用户界面。
Swing 工具包是:
- 平台无关
- 可定制
- 可扩展
- 可配置
- 轻巧
Swing 是一个高级 GUI 工具箱。 它具有丰富的小部件集。 从基本的小部件(例如按钮,标签,滚动条)到高级的小部件(例如树和表格)。 Swing 本身是用 Java 编写的。
Swing 是 JFC Java 基础类的一部分。 它是用于创建功能齐全的桌面应用的包的集合。 JFC 由 AWT,Swing,Accessibility,Java 2D 和拖放组成。 Swing 于 1997 年随 JDK 1.2 一起发布。 这是一个成熟的工具包。
Java 平台具有 Java2D 库,使开发者能够创建高级 2D 图形和图像。
窗口小部件工具箱基本上有两种类型:
- 轻巧的
- 重量级
一个重量级的工具箱使用 OS 的 API 绘制小部件。 例如,Borland 的 VCL 是重量级的工具包。 它取决于 WIN32 API,即内置的 Windows 应用编程接口。 在 Unix 系统上,我们有一个 GTK+ 工具箱,它建立在 X11 库的顶部。 Swing 是一个轻量级的工具包。 它绘制自己的小部件。 Qt4 工具箱也是如此。
JavaFX
2008 年,发布了新的 Java GUI 工具箱。 创建它是为了满足图形计算中的新需求,例如高级动画和多点触控支持。
JavaFX 是用于开发和交付可在各种设备上运行的富互联网应用(RIA)的软件平台。 JavaFX 是用于 Java 平台的下一代 GUI 工具包。 它与 Java SE Runtime Environment(JRE)和 Java Development Kit(JDK)的最新版本完全集成。
SWT 库
还有另一个用于 Java 编程语言的第三方 GUI 库。 它称为标准窗口小部件工具包(SWT)。 SWT 库最初由 IBM 公司开发。 现在,它是一个由 Eclipse 社区维护的开源项目。 SWT 是重量级工具箱的一个示例。 它允许基础操作系统创建用户界面。 SWT 使用 Java 本机接口来完成这项工作。 在 ZetCode 上有专门用于 SWT 的教程。
这是 Java Swing 的简介。
Windows API 中的字符串
在 C 语言中,没有字符串数据类型。 程序中的字符串字面值是字符数组。 每当我们说字符串时,都是指一个字符数组。
我们有五组处理字符串的函数; 在 C 运行时库(CRT)和 Windows API 中:
- ANSI C 标准函数
- 安全性增强的 CRT 函数
- Windows API 内核和用户函数
- Windows API Shell 轻量级工具函数
- Windows API StrSafe 函数
建议您使用安全性增强的标准函数或 Windows API 安全函数。
ANSI C 字符串函数
C 运行时(CRT)库函数的开销很小,因为它们在下面调用 Windows API 函数。 这些函数提供了可移植性,但有一些限制。 如果使用不当,可能会导致安全隐患。
这些函数失败时不会返回错误值。
ansic_functions.c
#include <windows.h>
#include <wchar.h>
#define STR_EQUAL 0
int wmain(void) {
wchar_t str1[] = L"There are 15 pines";
wprintf(L"The length of the string is %lld characters\n", wcslen(str1));
wchar_t buf[20];
wcscpy(buf, L"Wuthering");
wcscat(buf, L" heights\n");
wprintf(buf);
if (wcscmp(L"rain", L"rainy")== STR_EQUAL) {
wprintf(L"rain and rainy are equal strings\n");
} else {
wprintf(L"rain and rainy are not equal strings\n");
}
return 0;
}
在示例中,我们介绍了 CRT 库中的一些字符串函数。
wprintf(L"The length of the string is %lld characters\n", wcslen(str1));
wcslen()返回字符串中的宽字符数。
wcscpy(buf, L"Wuthering");
wcscpy()将字符串复制到字符串缓冲区。
wcscat(buf, L" heights\n");
wcscat()函数将字符串附加到字符串缓冲区。
if (wcscmp(L"rain", L"rainy")== STR_EQUAL) {
wprintf(L"rain and rainy are equal strings\n");
} else {
wprintf(L"rain and rainy are not equal strings\n");
}
wcscmp()比较两个字符串。
C:\Users\Jano\Documents\Pelles C Projects\strings\ANSICFunctions>ANSICFunctions.exe
The length of the string is 18 characters
Wuthering heights
rain and rainy are not equal strings
这是示例的输出。
安全性增强的 CRT 函数
安全性 CRT 函数为 CRT 函数增加了额外的安全性。 (它们不是标准函数,而是 MS 扩展。)这些函数验证参数,获取大小缓冲区,检查字符串是否以 NULL 终止以及提供增强的错误报告。
安全 CRT 函数的后缀为_s。
security_enhanced.c
#include <windows.h>
#include <wchar.h>
#define BUF_LEN 25
int wmain(void) {
wchar_t str1[] = L"There are 15 pines";
const int MAX_CHARS = 50;
size_t count = wcsnlen_s(str1, MAX_CHARS);
wprintf(L"The length of the string is %ld characters\n", count);
wchar_t buf[BUF_LEN] = {0};
int r = wcscpy_s(buf, BUF_LEN, L"Wuthering");
if (r != 0) {
wprintf(L"wcscpy_s() failed %ld", r);
}
r = wcscat_s(buf, BUF_LEN, L" heights\n");
if (r != 0) {
wcscat_s(L"wcscat_s() failed %ld", r);
}
wprintf_s(buf);
return 0;
}
在示例中,我们展示了四个函数:wcsnlen_s(),wcscpy_s(),wcscat_s()和wprintf_s()。
const int MAX_CHARS = 50;
size_t count = wcsnlen_s(str1, MAX_CHARS);
wcsnlen_s()计算宽字符串的长度。 该函数仅检查前MAX_CHARS个字符。
int r = wcscpy_s(buf, BUF_LEN, L"Wuthering");
使用wcscpy_s()函数,我们将L"Wuthering"字符串复制到缓冲区中。 该函数使用缓冲区中的最大字符数,如果失败,则返回错误代码。 该函数成功返回 0。
r = wcscat_s(buf, BUF_LEN, L" heights\n");
wcscat_s()是wcscat()函数的安全扩展。
wprintf_s(buf);
甚至还具有增强安全性的wprintf()函数; 它具有一些运行时约束。
C:\Users\Jano\Documents\Pelles C Projects\strings\SecurityEnhanced>SecurityEnhanced.exe
The length of the string is 18 characters
Wuthering heights
这是SecurityEnhanced.exe示例的输出。
Windows API 内核和用户字符串函数
这些函数特定于 Windows OS。 它们在User32.lib和Kernel32.lib中可用。 它们比 CRT 同类产品轻。
内核字符串函数起源于 Windows 内核的开发。 它们以l字母为前缀。
字符串长度
最常见的要求之一是弄清楚字符串的长度。 lstrlen()函数以字符为单位返回指定字符串的长度。 它不计算终止的空字符。
int WINAPI lstrlenA(LPCSTR lpString);
int WINAPI lstrlenW(LPCWSTR lpString);
ANSI 和 UNICODE 函数将字符串作为参数,并返回字符串中的字符数。
winapi_string_lenght.c
#include <windows.h>
#include <wchar.h>
int wmain(void) {
char *name = "Jane";
wchar_t *town = L"Bratislava";
wprintf(L"The length of the name string is %d\n", lstrlenA(name));
wprintf(L"The town string length is %d\n", lstrlenW(town));
return 0;
}
我们计算两个字符串的长度。 lstrlen()函数实际上是lstrlenA()或lstrlenW()的宏。 第一个用于 ANSI 字符串,第二个用于宽字符串。
wprintf(L"The town string length is %d\n", lstrlenW(town));
我们使用lstrlenW()函数打印L"Bratislava"字符串的长度。
C:\Users\Jano\Documents\Pelles C Projects\strings\WinapiStringLength>WinapiStringLength.exe
The length of the name string is 4
The town string length is 10
这是WinapiStringLength.exe程序的输出。
连接字符串
lstrcatW()函数将一个字符串附加到另一个字符串。
LPWSTR WINAPI lstrcatW(LPWSTR lpString1, LPCWSTR lpString2);
第一个参数是缓冲区,其中应包含两个字符串。 它必须足够大以包含两个字符,包括NULL终止字符。 返回值是指向缓冲区的指针。
winapi_string_concat.c
#include <windows.h>
#include <wchar.h>
int main(void) {
wchar_t *s1 = L"ZetCode, ";
wchar_t *s2 = L"tutorials ";
wchar_t *s3 = L"for ";
wchar_t *s4 = L"programmers.\n";
int len = lstrlenW(s1) + lstrlenW(s2)
+ lstrlenW(s3) + lstrlenW(s4);
wchar_t buf[len+1];
lstrcpyW(buf, s1);
lstrcatW(buf, s2);
lstrcatW(buf, s3);
lstrcatW(buf, s4);
wprintf(buf);
return 0;
}
在示例中,我们连接了四个字符串。
wchar_t *s1 = L"ZetCode, ";
wchar_t *s2 = L"tutorials ";
wchar_t *s3 = L"for ";
wchar_t *s4 = L"programmers.\n";
这些是我们要连接的字符串。
int len = lstrlenW(s1) + lstrlenW(s2)
+ lstrlenW(s3) + lstrlenW(s4);
我们使用lstrlenW()函数计算四个字符串的长度。
wchar_t buf[len+1];
我们创建一个缓冲区来保存最终的字符串。 请注意,我们加 1 来包含NULL字符。
lstrcpyW(buf, s1);
我们使用lstrcpyW()函数将第一个字符串复制到缓冲区。
lstrcatW(buf, s2);
lstrcatW(buf, s3);
lstrcatW(buf, s4);
我们用lstrcatW()函数附加其余的字符串。
C:\Users\Jano\Documents\Pelles C Projects\strings\WinapiStringConcat>WinapiStringConcat.exe
ZetCode, tutorials for programmers.
这是WinapiStringConcat.exe程序的输出。
转换字符
我们有两种将字符转换为大写或小写的方法。 CharLowerW()函数将字符串或单个字符转换为小写。 CharUpperW()函数将字符串或单个字符转换为大写。 如果操作数是字符串,则该函数将就地转换字符。 换句话说,它们已被修改。
LPWSTR WINAPI CharLowerW(LPWSTR lpsz);
LPWSTR WINAPI CharUpperW(LPWSTR lpsz);
这些函数在适当位置修改字符串,并返回指向修改后的字符串的指针。
winapi_string_case.c
#include <windows.h>
#include <wchar.h>
#pragma comment(lib, "User32.lib")
int wmain(void) {
wchar_t str[] = L"Europa";
CharLowerW(str);
wprintf(L"%ls\n", str);
CharUpperW(str);
wprintf(L"%ls\n", str);
return 0;
}
我们有一个字符串,可以将其转换为小写和大写形式。
CharLowerW(str);
wprintf(L"%ls\n", str);
我们使用CharLowerW()方法将str字符串转换为小写。 字符串被修改。
C:\winapi\examples2\strings\UpperLower>UpperLower.exe
europa
EUROPA
这是UpperLower.exe程序的输出。
比较字符串
lstrcmpW()函数比较两个字符串。 如果字符串相等,则返回 0。 比较是区分大小写的。 这意味着Cup和cup是两个不同的字符串。 lstrcmpiW()产生不区分大小写的字符串比较。 对于此函数,Cup和cup相等。
int WINAPI lstrcmpW(LPCWSTR lpString1, LPCWSTR lpString2);
int WINAPI lstrcmpiW(LPCWSTR lpString1, LPCWSTR lpString2);
这些函数采用两个字符串作为参数。 返回值指示字符串的相等性。 对于相等的字符串,返回 0 值。
winapi_string_compare.c
#include <windows.h>
#include <wchar.h>
#define STR_EQUAL 0
int wmain(void) {
wchar_t *s1 = L"Strong";
wchar_t *s2 = L"strong";
if (lstrcmpW(s1, s2) == STR_EQUAL) {
wprintf(L"%ls and %ls are equal\n", s1, s2);
} else {
wprintf(L"%ls and %ls are not equal\n", s1, s2);
}
wprintf(L"When applying case insensitive comparison:\n");
if (lstrcmpiW(s1, s2) == STR_EQUAL) {
wprintf(L"%ls and %ls are equal\n", s1, s2);
} else {
wprintf(L"%ls and %ls are not equal\n", s1, s2);
}
return 0;
}
我们有两个字符串。 我们使用区分大小写和不区分大小写的字符串比较来比较它们。
if (lstrcmpW(s1, s2) == STR_EQUAL) {
wprintf(L"%ls and %ls are equal\n", s1, s2);
} else {
wprintf(L"%ls and %ls are not equal\n", s1, s2);
}
如果lstrcmpW()函数返回定义为 0 的STR_EQUAL,则我们向控制台显示两个字符串相等。 否则,我们打印出它们不相等。
C:\Users\Jano\Documents\Pelles C Projects\strings\WinapiStringCompare>WinapiStringCompare.exe
Strong and strong are not equal
When applying case insensitive comparison:
Strong and strong are equal
WinapiStringCompare.exe程序给出以上输出。
填充缓冲区
在 C 编程中,用格式化的数据填充缓冲区至关重要。 wsprintfW()函数将格式化的数据写入指定的缓冲区。
int __cdecl wsprintfW(LPWSTR lpOut, LPCWSTR lpFmt, ... );
该函数的第一个参数是要接收格式化输出的缓冲区。 第二个是包含格式控制规范的字符串。 然后,我们有一个或多个可选参数,它们对应于格式控制规范。
winapi_string_fillbuffer.c
#include <windows.h>
#include <wchar.h>
#pragma comment(lib, "User32.lib")
int wmain(void) {
SYSTEMTIME st = {0};
wchar_t buf[128] = {0};
GetLocalTime(&st);
wsprintfW(buf, L"Today is %lu.%lu.%lu\n", st.wDay,
st.wMonth, st.wYear);
wprintf(buf);
return 0;
}
我们建立一个用当前日期填充的字符串。
wchar_t buf[128] = {0};
在这种特殊情况下,我们可以安全地假设字符串不会超过 128 个字符。
GetLocalTime(&st);
GetLocalTime()函数检索当前的本地日期和时间。
wsprintfW(buf, L"Today is %lu.%lu.%lu\n", st.wDay,
st.wMonth, st.wYear);
wsprintfW()用宽字符串填充缓冲区。 根据格式说明符将参数复制到字符串。
wprintf(buf);
缓冲区的内容将打印到控制台。
C:\Users\Jano\Documents\Pelles C Projects\strings\WinapiStringFillBuffer>WinapiStringFillBuffer.exe
Today is 11.2.2016
这是WinapiStringFillBuffer.exe示例的输出。
字符类型
字符有多种类型。 它们可以是数字,空格,字母,标点符号或控制字符。
BOOL WINAPI GetStringTypeW(DWORD dwInfoType, LPCWSTR lpSrcStr,
int cchSrc, LPWORD lpCharType);
GetStringTypeW()函数检索指定 Unicode 字符串中字符的字符类型信息。 第一个参数是指定信息类型的标志。
| 标志 | 含义 |
|---|---|
CT_CTYPE1 |
检索字符类型信息。 |
CT_CTYPE2 |
检索双向布局信息。 |
CT_CTYPE3 |
检索文本处理信息。 |
Table: Character info types
第二个参数是要检索其字符类型的 Unicode 字符串。
第三个参数是字符串的大小。 最后一个参数是指向 16 位值数组的指针。 该数组的长度必须足够大,以便为源字符串中的每个字符接收一个 16 位值。 该数组将包含一个与源字符串中每个字符相对应的单词。
GetStringTypeW()函数返回一个值,该值是类型的组合。 我们可以使用&运算符查询特定类型。
| 值 | 含义 |
|---|---|
C1_DIGIT |
小数位数 |
C1_SPACE |
空格字符 |
C1_PUNCT |
标点 |
C1_CNTRL |
控制字符 |
C1_ALPHA |
任何语言特征 |
Table: Partial list of character types
如果失败,该函数将返回 0。
winapi_string_types.c
#include <windows.h>
#include <wchar.h>
#include <stdbool.h>
int wmain(void) {
wchar_t str[] = L"7 white, 3 red roses.\n";
int alphas = 0;
int digits = 0;
int spaces = 0;
int puncts = 0;
int contrs = 0;
int size = lstrlenW(str);
WORD types[size];
ZeroMemory(types, size);
bool rv = GetStringTypeW(CT_CTYPE1, str, size, types);
if (!rv) {
wprintf(L"Could not get character types (%ld)", GetLastError());
return EXIT_FAILURE;
}
for (int i=0; i<size; i++) {
if (types[i] & C1_ALPHA) {
alphas++;
}
if (types[i] & C1_DIGIT) {
digits++;
}
if (types[i] & C1_SPACE) {
spaces++;
}
if (types[i] & C1_PUNCT) {
puncts++;
}
if (types[i] & C1_CNTRL) {
contrs++;
}
}
wprintf(L"There are %ld letter(s), %ld digit(s), "
L"%ld space(s), %ld punctuation character(s), "
L"and %ld control character(s)\n", alphas, digits,
spaces, puncts, contrs);
return 0;
}
我们有一句话。 GetStringTypeW()函数用于确定字符串的字符类型。
wchar_t str[] = L"7 white, 3 red roses.\n";
这是由各种宽字符组成的简短句子。
int alphas = 0;
int digits = 0;
int spaces = 0;
int puncts = 0;
int contrs = 0;
这些变量将用于计算字母,数字,空格,标点和控制字符。
int size = lstrlenW(str);
WORD types[size];
ZeroMemory(types, size);
我们获取字符串的大小,并创建值数组。 大小不包含NULL终止字符。 我们可以加 1 以包括它。 它将被算作控制字符。
bool rv = GetStringTypeW(CT_CTYPE1, str, size, types);
我们得到句子的字符类型。 types数组填充有字符类型值。
if (types[i] & C1_DIGIT) {
digits++;
}
如果该值包含C1_DIGIT标志,我们将增加数字计数器。
C:\Users\Jano\Documents\Pelles C Projects\strings\WinapiStringTypes>WinapiStringTypes.exe
There are 13 letter(s), 2 digit(s), 5 space(s), 2 punctuation character(s),
and 1 control character(s)
这是WinapiStringTypes.exe示例的输出。
Windows API Shell 轻量级工具函数
这些函数特定于 Windows OS。 它们在Shlwapi.lib中可用。
修剪字符串
StrTrimW()函数从字符串中删除指定的开头和结尾字符。 如果删除了任何字符,则返回true;否则返回false。 否则为假。
BOOL WINAPI StrTrimW(LPWSTR psz, LPCWSTR pszTrimChars);
第一个参数是指向要修剪的字符串的指针。 当此函数成功返回时,psz接收到修剪后的字符串。 第二个参数是一个指向字符串的指针,该字符串包含要从psz中修剪的字符。
winapi_shell_trim.c
#include <windows.h>
#include <wchar.h>
#include <stdbool.h>
#include "Shlwapi.h"
#pragma comment(lib, "Shlwapi.lib")
int wmain(void) {
wchar_t buf[] = L"23tennis74";
wchar_t trim[] = L"0123456789";
wprintf(L"Original string: %ls\n", buf);
bool r = StrTrimW(buf, trim);
if (r == true) {
wprintf(L"The StrTrim() trimmed some characters\n", buf);
} else {
wprintf(L"No characters were trimmed\n", buf);
}
wprintf(L"Trimmed string: %ls\n", buf);
return 0;
}
在该示例中,我们从字符串中删除所有数字。
wchar_t buf[] = L"23tennis74";
我们将从该字符串中删除所有数字。
wchar_t trim[] = L"0123456789";
该字符串包含所有要删除的字符。
bool r = StrTrimW(buf, trim);
使用StrTrimW()函数,我们可以修剪缓冲区中的数字。
C:\Users\Jano\Documents\Pelles C Projects\strings\ShellTrimString>ShellTrimString.exe
Original string: 23tennis74
The StrTrim() trimmed some characters
Trimmed string: tennis
这是ShellTrimString.exe示例的输出。
将字符串转换为整数
StrToIntExW()将代表十进制或十六进制数字的字符串转换为整数。 成功时该函数返回true。
BOOL WINAPI StrToIntExW(LPCWSTR pszString, DWORD dwFlags, int *piRet);
第一个参数是指向要转换的字符串的指针。 第二个参数是标志之一,用于指定应如何解析pszString以将其转换为整数。 第三个参数是指向接收转换后的字符串的int的指针。
winapi_shell_convert.c
#include <windows.h>
#include <wchar.h>
#include <stdbool.h>
#include "Shlwapi.h"
#pragma comment(lib, "Shlwapi.lib")
int wmain(void) {
wchar_t str1[] = L"512";
wchar_t str2[] = L"0xAB12";
int n = 0;
bool r = StrToIntExW(str1, STIF_DEFAULT, &n);
if (r == true) {
wprintf(L"The value is %d\n", n);
} else {
wprintf(L"The first conversion failed\n");
return 1;
}
r = StrToIntExW(str2, STIF_SUPPORT_HEX, &n);
if (r == true) {
wprintf(L"The value is %d\n", n);
} else {
wprintf(L"The second conversion failed\n");
return 1;
}
return 0;
}
在示例中,我们转换了两个字符串; 一个代表十进制值,一个代表十六进制值。
wchar_t str1[] = L"512";
wchar_t str2[] = L"0xAB12";
第一个字符串代表十进制数;第二个字符串代表十进制数。 第二个字符串表示一个十六进制数。
bool r = StrToIntExW(str1, STIF_DEFAULT, &n);
使用StrToIntExW()函数,我们将第一个字符串转换为整数。 STIF_DEFAULT标志告诉函数转换十进制值。
r = StrToIntExW(str2, STIF_SUPPORT_HEX, &n);
通过STIF_SUPPORT_HEX标志,我们告诉函数转换一个十六进制值。
C:\Users\Jano\Documents\Pelles C Projects\strings\ShellConvertString>ShellConvertString.exe
The value is 512
The value is 43794
这是ShellConvertString.exe程序的输出。
搜索字符串
StrStrW()函数查找字符串中子字符串的首次出现。 比较是区分大小写的。
LPWSTR WINAPI StrStrW(LPCWSTR pszFirst, LPCWSTR pszSrch);
第一个参数是指向要搜索的字符串的指针。 第二个参数是指向要搜索的子字符串的指针。 如果成功,该函数将返回匹配子字符串的第一个匹配项的地址,否则返回NULL。
winapi_shell_search.c
#include <windows.h>
#include <wchar.h>
#include "Shlwapi.h"
#pragma comment(lib, "Shlwapi.lib")
int wmain(void) {
wchar_t buf[] = L"Today is a rainy day.";
wchar_t *search_word = L"rainy";
int len = wcslen(search_word);
LPWSTR pr = StrStrW(buf, search_word);
if (pr == NULL) {
wprintf(L"No match\n", buf);
} else {
wprintf(L"%.*ls is found\n", len, pr);
}
return 0;
}
在代码示例中,我们在句子中搜索单词。
wchar_t buf[] = L"Today is a rainy day.";
我们从这句话中搜索一个单词。
wchar_t *search_word = L"rainy";
这是我们要搜索的词。
LPWSTR pr = StrStrW(buf, search_word);
StrStrW()函数在句子中搜索单词。 如果成功,它将返回一个指向匹配子字符串的指针。
C:\Users\Jano\Documents\Pelles C Projects\strings\ShellSearchString>ShellSearchString.exe
rainy is found
这是ShellSearchString.exe程序的输出。
Windows API StrSafe 函数
为了提高应用的安全性,发布了 StrSafe 函数。 这些函数需要目标缓冲区的大小作为输入。 保证缓冲区为空终止。 该函数返回错误代码。 这样可以进行正确的错误处理。
每个函数都有相应的字符计数Cch或字节计数Cb版本。
字符串长度
StringCchLengthW()和StringCbLengthW()函数可以确定字符和字节的字符串长度。
HRESULT StringCchLengthW(LPCWSTR psz, size_t cchMax, size_t *pcch);
HRESULT StringCbLengthW(LPCWSTR psz, size_t cbMax, size_t *pcb);
函数的第一个参数是要检查其长度的字符串。 第二个参数是psz参数中允许的最大字符数(字节)。 该值不能超过STRSAFE_MAX_CCH。 第三个参数是psz中的字符数(字节),不包括终止的空字符。
函数成功返回S_OK,失败返回STRSAFE_E_INVALID_PARAMETER。 如果psz中的值为NULL,cchMax大于STRSAFE_MAX_CCH或psz大于cchMax,则函数将失败。 SUCCEEDED和FAILED宏可用于检查函数的返回值。
safe_length.c
#include <windows.h>
#include <strsafe.h>
#include <wchar.h>
int wmain(void) {
wchar_t str[] = L"ZetCode";
size_t target_size = 0;
size_t size = sizeof(str);
HRESULT r = StringCbLengthW(str, size, &target_size);
if (SUCCEEDED(r)) {
wprintf(L"The string has %lld bytes\n", target_size);
} else {
wprintf(L"StringCbLengthW() failed\n");
return 1;
}
size = sizeof(str)/sizeof(wchar_t);
r = StringCchLengthW(str, size, &target_size);
if (SUCCEEDED(r)) {
wprintf(L"The string has %lld characters\n", target_size);
} else {
wprintf(L"StringCchLengthW() failed\n");
return 1;
}
return 0;
}
该代码示例确定给定字符串(以字符和字节为单位)的长度。
wchar_t str[] = L"ZetCode";
我们将确定此字符串的长度。
size_t target_size = 0;
函数返回时,target_size变量将填充计数值。
size_t size = sizeof(str);
使用sizeof运算符,我们获得字符数组的大小(以字节为单位)。 该值用作StringCbLengthW()函数的字符串中允许的最大字符数。
HRESULT r = StringCbLengthW(str, size, &target_size);
使用StringCbLengthW()函数,我们可以确定字符串的长度(以字节为单位)。 长度存储在target_size变量中。
if (SUCCEEDED(r)) {
wprintf(L"The string has %lld bytes\n", target_size);
} else {
wprintf(L"StringCbLengthW() failed\n");
return 1;
}
我们使用SUCCEEDED宏检查返回的值。 成功后,我们将打印字符串中的字节数。 如果出现错误,我们将显示一条错误消息。
size = sizeof(str)/sizeof(wchar_t);
在这里,我们确定字符串中允许的最大字符数。 wchar_t是一种宽字符类型; 它的大小取决于编译器。
r = StringCchLengthW(str, size, &target_size);
使用StringCchLengthW()函数,我们可以得到字符串的大小(以字符为单位)。
if (SUCCEEDED(r)) {
wprintf(L"The string has %lld characters\n", target_size);
} else {
wprintf(L"StringCchLengthW() failed\n");
return 1;
}
成功后,我们将字符串中的字符数打印到控制台。 出现错误时,我们会打印一条错误消息。
C:\Users\Jano\Documents\Pelles C Projects\strsafe\SafeLength>SafeLength.exe
The string has 14 bytes
The string has 7 characters
该字符串包含 14 个字节或 7 个字符。
读取标准输入
StringCchGetsW()从标准输入读取一行,包括换行符。
HRESULT StringCchGetsW(LPWSTR pszDest, size_t cchDest);
第一个参数是目标缓冲区,它接收复制的字符。 第二个参数是目标缓冲区的大小(以字符为单位)。
safe_gets.c
#include <windows.h>
#include <strsafe.h>
#include <wchar.h>
#define BUF_LEN 8191
int wmain(void) {
wchar_t buf[BUF_LEN] = {0};
HRESULT r = StringCchGetsW(buf, ARRAYSIZE(buf));
if (SUCCEEDED(r)) {
wprintf(L"You have entered: %ls\n", buf);
} else {
wprintf(L"StringCchGets() failed\n");
return 1;
}
return 0;
}
在示例中,我们从标准输入中读取了一行。 该行被打印回到控制台。
#define BUF_LEN 8191
根据 MSDN 文档,命令提示符下的最大输入不能超过 8191 个字符。
wchar_t buf[BUF_LEN] = {0};
我们为输入字符串创建一个缓冲区。
HRESULT r = StringCchGetsW(buf, ARRAYSIZE(buf));
StringCchGetsW()从stdin读取一行。
C:\Users\Jano\Documents\Pelles C Projects\strsafe\SafeGets>SafeGets.exe
Today is a rainy day.
You have entered: Today is a rainy day.
这是SafeGets.exe程序的示例运行。
复制字符串
StringCchCopyW()将一个字符串复制到另一个。
HRESULT StringCchCopyW(LPTSTR pszDest, size_t cchDest, LPCWSTR pszSrc);
第一个参数是目标缓冲区,它接收复制的字符串。 第二个参数是目标缓冲区的大小(以字符为单位)。 第三个参数是源字符串。
safe_copy.c
#include <windows.h>
#include <strsafe.h>
#include <wchar.h>
int wmain(void) {
wchar_t *sentence = L"Today is a rainy day.";
size_t size = wcslen(sentence) + 1;
wchar_t buf[size];
ZeroMemory(buf, size);
HRESULT r = StringCchCopyW(buf, size, sentence);
if (SUCCEEDED(r)) {
wprintf(L"%ls\n", buf);
} else {
wprintf(L"StringCchCopyW() failed\n");
return 1;
}
return 0;
}
在代码示例中,我们使用StringCchCopyW()函数复制一个字符串。
wchar_t *sentence = L"Today is a rainy day.";
这是要复制的字符串。
size_t size = wcslen(sentence) + 1;
我们用wcslen()函数确定它的长度; 换行符保留一个字符。
wchar_t buf[size];
ZeroMemory(buf, size);
我们使用ZeroMemory()函数创建一个缓冲区,并使用零。
HRESULT r = StringCchCopyW(buf, size, sentence);
使用StringCchCopyW(),我们将字符串复制到缓冲区中。 提供目标缓冲区的大小是为了确保它不会在该缓冲区的末尾写入。
C:\Users\Jano\Documents\Pelles C Projects\strsafe\SafeCopy>SafeCopy.exe
Today is a rainy day.
这是SafeCopy.exe程序的输出。
连接字符串
StringCchCatW()将一个字符串连接到另一个字符串。
HRESULT StringCchCatW(LPWSTR pszDest, size_t cchDest, LPCWSTR pszSrc);
第一个参数是目标缓冲区。 第二个参数是目标缓冲区的大小(以字符为单位)。 第三个参数是要连接到目标缓冲区末尾的源字符串。
safe_concat.c
#include <windows.h>
#include <strsafe.h>
#include <wchar.h>
#define BUF_LEN 256
int wmain(void) {
wchar_t buf[BUF_LEN] = {0};
HRESULT r = StringCchCatW(buf, BUF_LEN, L"Hello ");
if (FAILED(r)) {
wprintf(L"StringCchCatW() failed\n");
return 1;
}
r = StringCchCatW(buf, BUF_LEN, L"there");
if (FAILED(r)) {
wprintf(L"StringCchCatW() failed\n");
return 1;
}
wprintf(L"%ls\n", buf);
return 0;
}
在代码示例中,我们使用StringCchCatW()函数将两个字符串连接在一起。
HRESULT r = StringCchCatW(buf, BUF_LEN, L"Hello ");
StringCchCatW()函数将L"Hello "字符串添加到buf数组。
r = StringCchCatW(buf, BUF_LEN, L"there");
稍后,第二个字符串将添加到缓冲区。
C:\Users\Jano\Documents\Pelles C Projects\strsafe\SafeConcat>SafeConcat.exe
Hello there
这是SafeConcat.exe程序的输出。
格式化字符串
StringCchPrintfW()函数将格式化的数据写入目标缓冲区。
HRESULT StringCchPrintfW(LPWSTR pszDest, size_t cchDest, LPCWSTR pszFormat, ...);
第一个参数是目标缓冲区,它接收从pszFormat及其参数创建的格式化字符串。 第二个参数是目标缓冲区,以字符为单位。 第三个参数是格式字符串。 将以下参数插入pszFormat字符串中。
safe_format.c
#include <windows.h>
#include <strsafe.h>
#include <wchar.h>
#define BUF_LEN 256
int wmain(void) {
wchar_t *word = L"table";
int count = 6;
wchar_t buf[BUF_LEN] = {0};
wchar_t *line = L"There are %d %lss";
HRESULT r = StringCchPrintfW(buf, ARRAYSIZE(buf), line, count, word);
if (SUCCEEDED(r)) {
wprintf(L"%ls\n", buf);
} else {
wprintf(L"StringCchPrintfW() failed\n");
return 1;
}
return 0;
}
在代码示例中,我们使用StringCchPrintfW()函数创建格式化的字符串。
wchar_t *line = L"There are %d %lss";
这是格式字符串; 它具有两个格式说明符:%d和%ls。
HRESULT r = StringCchPrintfW(buf, ARRAYSIZE(buf), line, count, word);
使用StringCchPrintfW()函数,我们将两个值插入目标缓冲区。
C:\Users\Jano\Documents\Pelles C Projects\strsafe\SafeFormat>SafeFormat.exe
There are 6 tables
这是SafeFormat.exe程序的输出。
在 Windows API 教程的这一部分中,我们使用了字符串。
Java Swing 首个程序
原文: http://zetcode.com/tutorials/javaswingtutorial/firstprograms/
在本章中,我们将对第一个 Swing 程序进行编程。 我们创建了第一个简单的应用,展示了如何使用“退出”按钮终止应用,显示框架图标,显示工具提示,使用助记符以及显示标准颜色。
Java Swing 组件是 Java Swing 应用的基本构建块。 在本章中,我们将使用JFrame,JButton和JLabel组件。 JFrame是带有标题和边框的顶层窗口。 它用于组织其他组件,通常称为子组件。
JButton是用于执行动作的按钮。 JLabel是用于支付短文本字符串或图像或两者的组件。
第一个 Swing 示例
第一个示例在屏幕上显示一个基本窗口。
com/zetcode/SimpleEx.java
package com.zetcode;
import java.awt.EventQueue;
import javax.swing.JFrame;
public class SimpleEx extends JFrame {
public SimpleEx() {
initUI();
}
private void initUI() {
setTitle("Simple example");
setSize(300, 200);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new SimpleEx();
ex.setVisible(true);
});
}
}
尽管这段代码很短,但是应用窗口可以做很多事情。 可以调整大小,最大化或最小化。 随之而来的所有复杂性对应用员都是隐藏的。
import java.awt.EventQueue;
import javax.swing.JFrame;
在这里,我们导入将在代码示例中使用的 Swing 类。
public class SimpleEx extends JFrame {
SimpleEx继承自JFrame组件。 JFrame是顶级容器。 容器的基本目的是容纳应用的组件。
public SimpleEx() {
initUI();
}
好的编程习惯是不要将应用代码放入构造器中,而是将任务委托给特定的方法。
setTitle("Simple example");
在这里,我们使用setTitle()方法设置窗口的标题。
setSize(300, 200);
setSize()将窗口的大小调整为 300 像素宽和 200 像素高。
setLocationRelativeTo(null);
这条线使窗口在屏幕上居中。
setDefaultCloseOperation(EXIT_ON_CLOSE);
如果单击标题栏的“关闭”按钮,则此setDefaultCloseOperation()将关闭窗口。 默认情况下,如果我们单击按钮,则不会发生任何事情。
EventQueue.invokeLater(() -> {
var ex = new SimpleEx();
ex.setVisible(true);
});
我们创建代码示例的实例,并使它在屏幕上可见。 invokeLater()方法将应用放置在 Swing 事件队列中。 它用于确保所有 UI 更新都是并发安全的。 换句话说,这是为了防止 GUI 在某些情况下挂起。

图:简单 example
JButton示例
在下一个示例中,我们将有一个按钮。 当我们单击按钮时,应用终止。
com/zetcode/QuitButtonEx.java
package com.zetcode;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import java.awt.EventQueue;
public class QuitButtonEx extends JFrame {
public QuitButtonEx() {
initUI();
}
private void initUI() {
var quitButton = new JButton("Quit");
quitButton.addActionListener((event) -> System.exit(0));
createLayout(quitButton);
setTitle("Quit button");
setSize(300, 200);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void createLayout(JComponent... arg) {
var pane = getContentPane();
var gl = new GroupLayout(pane);
pane.setLayout(gl);
gl.setAutoCreateContainerGaps(true);
gl.setHorizontalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
);
gl.setVerticalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new QuitButtonEx();
ex.setVisible(true);
});
}
}
我们将JButton放置在窗口上,然后向该按钮添加一个动作监听器。
var quitButton = new JButton("Quit");
在这里,我们创建一个按钮组件。 此构造器将字符串标签作为参数。
quitButton.addActionListener((event) -> System.exit(0));
我们将一个动作监听器插入按钮。 该操作通过调用System.exit()方法来终止应用。
createLayout(quitButton);
子组件需要放入容器中。 我们将任务委托给createLayout()方法。
var pane = getContentPane();
var gl = new GroupLayout(pane);
pane.setLayout(gl);
JFrame的内容窗格是放置子组件的区域。 子级由专门的不可见组件(称为布局管理器)组织。 内容窗格的默认布局管理器是BorderLayout管理器。 这个管理器很简单。 在本教程中,我们使用GroupLayout管理器,它功能更强大,更灵活。
gl.setAutoCreateContainerGaps(true);
setAutoCreateContainerGaps()方法会在组件和容器边缘之间产生间隙。 空间或间隙是每个应用设计的重要组成部分。
gl.setHorizontalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
);
gl.setVerticalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
);
GroupLayout管理器独立定义每个大小的布局。 第一步,我们沿着水平轴布置组件; 在另一步骤中,我们沿垂直轴布置组件。 在两种布局中,我们都可以顺序或并行排列组件。 在水平布局中,一行组件称为顺序组,而一列组件称为并行组。 在垂直布局中,一列组件称为顺序组,一排组件称为并行组。
在我们的示例中,我们只有一个按钮,因此布局非常简单。 对于每个大小,我们将按钮组件作为参数调用addComponent()方法。 (必须为两个维度都添加每个子组件。)

图:退出按钮
JFrame图标
在下面的示例中,我们将在框架上显示一个图标。 它显示在标题栏的左侧。
com/zetcode/FrameIconEx.java
package com.zetcode;
import java.awt.EventQueue;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
public class FrameIconEx extends JFrame {
public FrameIconEx() {
initUI();
}
private void initUI() {
var webIcon = new ImageIcon("src/resources/web.png");
setIconImage(webIcon.getImage());
setTitle("Icon");
setSize(300, 200);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new FrameIconEx();
ex.setVisible(true);
});
}
}
ImageIcon用于创建图标。 web.png是一个22x22px的小图像文件。
var webIcon = new ImageIcon("src/resources/web.png");
我们从位于项目根目录中的 PNG 文件创建一个ImageIcon。
setIconImage(webIcon.getImage());
setIconImage()设置要显示为该窗口图标的图像。 getImage()返回图标的Image。

图:图标
Swing 工具提示
工具提示是内部应用帮助系统的一部分。 如果将鼠标指针悬停在设置了工具提示的对象上,则 Swing 将显示一个小窗口。
com/zetcode/TooltipEx.java
package com.zetcode;
import java.awt.EventQueue;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class TooltipEx extends JFrame {
public TooltipEx() {
initUI();
}
private void initUI() {
var btn = new JButton("Button");
btn.setToolTipText("A button component");
createLayout(btn);
setTitle("Tooltip");
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void createLayout(JComponent... arg) {
var pane = (JPanel) getContentPane();
var gl = new GroupLayout(pane);
pane.setLayout(gl);
pane.setToolTipText("Content pane");
gl.setAutoCreateContainerGaps(true);
gl.setHorizontalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
.addGap(200)
);
gl.setVerticalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
.addGap(120)
);
pack();
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new TooltipEx();
ex.setVisible(true);
});
}
}
在示例中,我们为框架和按钮设置了一个工具提示。
btn.setToolTipText("A button component");
要启用工具提示,我们调用setTooltipText()方法。
var pane = (JPanel) getContentPane();
var gl = new GroupLayout(pane);
pane.setLayout(gl);
内容窗格是JPanel组件的实例。 getContentPane()方法返回Container类型。 由于设置工具提示需要JComponent实例,因此我们将对象投射到JPanel。
pane.setToolTipText("Content pane");
为内容窗格设置了一个工具提示。
gl.setHorizontalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
.addGap(200)
);
gl.setVerticalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
.addGap(120)
);
我们将addGap()方法用于水平和垂直大小。 它在按钮的右侧和底部创建了一些空间。 (目的是增加窗口的初始大小。)
pack();
pack()方法根据其组件的大小自动调整JFrame的大小。 它也考虑了定义的空间。 我们的窗口将显示按钮和我们通过addGap()方法设置的空间。

图:工具提示
Swing 助记符
助记符是用于激活支持助记符的组件的快捷键。 例如,它们可以与标签,按钮或菜单项一起使用。
com/zetcode/MnemonicEx.java
package com.zetcode;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import java.awt.EventQueue;
import java.awt.event.KeyEvent;
public class MnemonicEx extends JFrame {
public MnemonicEx() {
initUI();
}
private void initUI() {
var btn = new JButton("Button");
btn.addActionListener((event) -> System.out.println("Button pressed"));
btn.setMnemonic(KeyEvent.VK_B);
createLayout(btn);
setTitle("Mnemonics");
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void createLayout(JComponent... arg) {
var pane = getContentPane();
var gl = new GroupLayout(pane);
pane.setLayout(gl);
gl.setAutoCreateContainerGaps(true);
gl.setHorizontalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
.addGap(200)
);
gl.setVerticalGroup(gl.createParallelGroup()
.addComponent(arg[0])
.addGap(200)
);
pack();
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new MnemonicEx();
ex.setVisible(true);
});
}
}
我们有一个带有动作监听器的按钮。 我们为此按钮设置了一个助记符。 可以使用 Alt + B 键盘快捷键激活。
btn.setMnemonic(KeyEvent.VK_B);
setMnemonic()方法为按钮设置键盘助记符。 助记键由KeyEvent类中的虚拟键代码指定。 助记符与外观的无鼠标修饰符(通常 Alt )结合在一起。
目前,有三种激活按钮的方法:单击鼠标左键, Alt + B快捷键以及空格键(按钮具有焦点)。 空格键绑定是由 Swing 自动创建的。 (在“金属”外观下,焦点由按钮标签周围的小矩形直观地表示。)
Swing 标准色
Color类定义 13 种颜色值,包括红色,绿色,蓝色和黄色。
com/zetcode/StandardColoursEx.java
package com.zetcode;
import javax.swing.GroupLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.util.ArrayList;
class MyLabel extends JLabel {
public MyLabel() {
super("", null, LEADING);
}
@Override
public boolean isOpaque() {
return true;
}
}
public class StandardColoursEx extends JFrame {
public StandardColoursEx() {
initUI();
}
private void initUI() {
Color[] stdCols = { Color.black, Color.blue, Color.cyan,
Color.darkGray, Color.gray, Color.green, Color.lightGray,
Color.magenta, Color.orange, Color.pink, Color.red,
Color.white, Color.yellow };
var labels = new ArrayList<JLabel>();
for (var col : stdCols) {
var lbl = createColouredLabel(col);
labels.add(lbl);
}
createLayout(labels.toArray(new JLabel[0]));
setTitle("Standard colours");
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private JLabel createColouredLabel(Color col) {
var lbl = new MyLabel();
lbl.setMinimumSize(new Dimension(90, 40));
lbl.setBackground(col);
return lbl;
}
private void createLayout(JLabel[] labels) {
var pane = (JPanel) getContentPane();
var gl = new GroupLayout(pane);
pane.setLayout(gl);
pane.setToolTipText("Content pane");
gl.setAutoCreateContainerGaps(true);
gl.setAutoCreateGaps(true);
gl.setHorizontalGroup(gl.createParallelGroup()
.addGroup(gl.createSequentialGroup()
.addComponent(labels[0])
.addComponent(labels[1])
.addComponent(labels[2])
.addComponent(labels[3]))
.addGroup(gl.createSequentialGroup()
.addComponent(labels[4])
.addComponent(labels[5])
.addComponent(labels[6])
.addComponent(labels[7]))
.addGroup(gl.createSequentialGroup()
.addComponent(labels[8])
.addComponent(labels[9])
.addComponent(labels[10])
.addComponent(labels[11]))
.addComponent(labels[12])
);
gl.setVerticalGroup(gl.createSequentialGroup()
.addGroup(gl.createParallelGroup()
.addComponent(labels[0])
.addComponent(labels[1])
.addComponent(labels[2])
.addComponent(labels[3]))
.addGroup(gl.createParallelGroup()
.addComponent(labels[4])
.addComponent(labels[5])
.addComponent(labels[6])
.addComponent(labels[7]))
.addGroup(gl.createParallelGroup()
.addComponent(labels[8])
.addComponent(labels[9])
.addComponent(labels[10])
.addComponent(labels[11]))
.addComponent(labels[12])
);
pack();
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new StandardColoursEx();
ex.setVisible(true);
});
}
}
该示例显示了 13 个JLabel组件; 每个标签具有不同的背景色。 JLabel通常用于显示文本; 但它也可以显示颜色。
class MyLabel extends JLabel {
public MyLabel() {
super("", null, LEADING);
}
@Override
public boolean isOpaque() {
return true;
}
}
JLabel组件是具有默认透明背景的特定组件。 为了在标签上绘图,我们重写了isOpaque()方法,该方法返回true。
Color[] stdCols = { Color.black, Color.blue, Color.cyan,
Color.darkGray, Color.gray, Color.green, Color.lightGray,
Color.magenta, Color.orange, Color.pink, Color.red,
Color.white, Color.yellow };
这里我们有一个内置的静态颜色值数组。
var labels = new ArrayList<JLabel>();
for (var col : stdCols) {
var lbl = createColouredLabel(col);
labels.add(lbl);
}
将创建JLabel组件的列表。 使用createColouredLabel()方法创建一个新标签。
public JLabel createColouredLabel(Color col) {
var lbl = new MyLabel();
lbl.setMinimumSize(new Dimension(90, 40));
lbl.setBackground(col);
return lbl;
}
createColouredLabel()方法创建一个新标签。 我们为标签设置了最小大小。 setBackground()设置组件的背景色。

图:标准颜色
Swing 鼠标移动事件
MouseMotionAdapter用于接收鼠标运动事件。
com/zetcode/MouseMoveEventEx.java
package com.zetcode;
import javax.swing.GroupLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
public class MouseMoveEventEx extends JFrame {
private JLabel coords;
public MouseMoveEventEx() {
initUI();
}
private void initUI() {
coords = new JLabel("");
createLayout(coords);
addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
super.mouseMoved(e);
int x = e.getX();
int y = e.getY();
var text = String.format("x: %d, y: %d", x, y);
coords.setText(text);
}
});
setTitle("Mouse move events");
setSize(300, 200);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void createLayout(JComponent... arg) {
var pane = getContentPane();
var gl = new GroupLayout(pane);
pane.setLayout(gl);
gl.setAutoCreateContainerGaps(true);
gl.setHorizontalGroup(gl.createParallelGroup()
.addComponent(arg[0])
.addGap(250)
);
gl.setVerticalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
.addGap(130)
);
pack();
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new MouseMoveEventEx();
ex.setVisible(true);
});
}
}
在示例中,我们在标签组件中显示鼠标指针的坐标。
addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
super.mouseMoved(e);
int x = e.getX();
int y = e.getY();
var text = String.format("x: %d, y: %d", x, y);
coords.setText(text);
}
});
我们重写了适配器的moseMoved()方法。 从MouseEvent对象中,我们获得鼠标指针的 x 和 y 坐标,构建一个字符串并将其设置为标签。

图:鼠标移动事件
在本章中,我们创建了一些简单的 Java Swing 程序。
Java Swing 中的菜单和工具栏
http://zetcode.com/tutorials/javaswingtutorial/menusandtoolbars/
在 Java Swing 教程的这一部分中,我们将使用菜单和工具栏。 在示例中,我们将创建常规菜单,子菜单,复选框菜单项,单选按钮菜单项,弹出菜单和工具栏。
菜单是位于菜单栏中的一组命令。 工具栏上的按钮带有应用中的一些常用命令。
我们将使用以下组件:
JMenuBar- 实现菜单栏。JMenu— 实现一个菜单,一个包含JMenuItems的弹出窗口,当用户在JMenuBar上选择一个项目时显示。JMenuItem- 在菜单中实现一个项目。 用户选择它来执行操作。JSeparator- 提供用于实现分隔线的通用组件。JCheckBoxMenuItem- 实现可以选择或取消选择的菜单。JRadioButtonMenuItem- 实现单选按钮菜单项,用于相互排斥的选择。ButtonGroup- 为一组按钮创建一个多重排除范围。JPopupMenu- 实现一个弹出菜单,一个弹出的小窗口并显示一系列选项。JToolBar- 实现工具栏,该工具栏对于显示常用的Actions或控件很有用。
Swing JMenuBar
我们从一个简单的菜单栏示例开始。
com/zetcode/SimpleMenuEx.java
package com.zetcode;
import java.awt.EventQueue;
import java.awt.event.KeyEvent;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
public class SimpleMenuEx extends JFrame {
public SimpleMenuEx() {
initUI();
}
private void initUI() {
createMenuBar();
setTitle("Simple menu");
setSize(350, 250);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void createMenuBar() {
var menuBar = new JMenuBar();
var exitIcon = new ImageIcon("src/resources/exit.png");
var fileMenu = new JMenu("File");
fileMenu.setMnemonic(KeyEvent.VK_F);
var eMenuItem = new JMenuItem("Exit", exitIcon);
eMenuItem.setMnemonic(KeyEvent.VK_E);
eMenuItem.setToolTipText("Exit application");
eMenuItem.addActionListener((event) -> System.exit(0));
fileMenu.add(eMenuItem);
menuBar.add(fileMenu);
setJMenuBar(menuBar);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new SimpleMenuEx();
ex.setVisible(true);
});
}
}
我们的示例将显示一个菜单项。 选择退出菜单项,我们关闭应用。
var menuBar = new JMenuBar();
使用JMenuBar创建菜单栏。
var exitIcon = new ImageIcon("src/resources/exit.png");
菜单中显示退出图标。
var fileMenu = new JMenu("File");
fileMenu.setMnemonic(KeyEvent.VK_F);
使用JMenu类创建菜单对象。 也可以通过键盘访问菜单。 要将菜单绑定到特定键,我们使用setMnemonic()方法。 在我们的情况下,可以使用 Alt + F 快捷方式打开菜单。
var eMenuItem = new JMenuItem("Exit", exitIcon);
eMenuItem.setMnemonic(KeyEvent.VK_E);
菜单对象由菜单项组成。 使用JMenuItem类创建一个菜单项。 菜单项具有其自己的助记符。 可以使用 Alt + F + E 组合键激活。
eMenuItem.setToolTipText("Exit application");
此代码行为菜单项创建工具提示。
eMenuItem.addActionListener((event) -> System.exit(0));
JMenuItem是一种特殊的按钮组件。 我们向它添加了一个动作监听器,它终止了应用。
fileMenu.add(eMenuItem);
menuBar.add(fileMenu);
菜单项被添加到菜单对象,菜单对象被插入菜单栏。
setJMenuBar(menuBar);
setJMenuBar()方法设置JFrame容器的菜单栏。

图:简单菜单
Swing 子菜单
每个菜单也可以有一个子菜单。 这样,我们可以将类似的命令分组。 例如,我们可以将用于隐藏和显示各种工具栏(例如个人栏,地址栏,状态栏或导航栏)的命令放在称为工具栏的子菜单中。 在菜单中,我们可以使用分隔符来分隔命令。 分隔符是一条简单的线。 通常的做法是使用单个分隔符将“新建”,“打开”,“保存”等命令与“打印”,“打印预览”等命令分开。 除助记符外,还可通过加速器启动菜单命令。
com/zetcode/SubmenuEx.java
package com.zetcode;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import java.awt.EventQueue;
public class SubmenuEx extends JFrame {
public SubmenuEx() {
initUI();
}
private void initUI() {
createMenuBar();
setTitle("Submenu");
setSize(360, 250);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void createMenuBar() {
var menuBar = new JMenuBar();
var iconNew = new ImageIcon("src/resources/new.png");
var iconOpen = new ImageIcon("src/resources/open.png");
var iconSave = new ImageIcon("src/resources/save.png");
var iconExit = new ImageIcon("src/resources/exit.png");
var fileMenu = new JMenu("File");
var impMenu = new JMenu("Import");
var newsMenuItem = new JMenuItem("Import newsfeed list...");
var bookmarksMenuItem = new JMenuItem("Import bookmarks...");
var importMailMenuItem = new JMenuItem("Import mail...");
impMenu.add(newsMenuItem);
impMenu.add(bookmarksMenuItem);
impMenu.add(importMailMenuItem);
var newMenuItem = new JMenuItem("New", iconNew);
var openMenuItem = new JMenuItem("Open", iconOpen);
var saveMenuItem = new JMenuItem("Save", iconSave);
var exitMenuItem = new JMenuItem("Exit", iconExit);
exitMenuItem.setToolTipText("Exit application");
exitMenuItem.addActionListener((event) -> System.exit(0));
fileMenu.add(newMenuItem);
fileMenu.add(openMenuItem);
fileMenu.add(saveMenuItem);
fileMenu.addSeparator();
fileMenu.add(impMenu);
fileMenu.addSeparator();
fileMenu.add(exitMenuItem);
menuBar.add(fileMenu);
setJMenuBar(menuBar);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new SubmenuEx();
ex.setVisible(true);
});
}
}
本示例创建一个子菜单,并使用菜单分隔符分隔菜单项组。
var impMenu = new JMenu("Import");
...
fileMenu.add(impMenu);
子菜单与其他任何普通菜单一样。 它是用相同的方式创建的。 我们只需将菜单添加到现有菜单即可。
exitMenuItem.setToolTipText("Exit application");
使用setToolTipText()方法将工具提示设置为“退出”菜单项。
var newMenuItem = new JMenuItem("New", iconNew);
此JMenuItem构造器创建带有标签和图标的菜单项。
fileMenu.addSeparator();
分隔符是一条水平线,可以在视觉上分隔菜单项。 这样,我们可以将项目分组到一些合理的位置。 使用addSeparator()方法创建分隔符。

图:子菜单
Swing 助记符和加速器
助记符和加速键是使您能够通过键盘执行命令的快捷键。 助记符导航菜单层次结构以选择特定的菜单项,而加速器则跳过菜单层次结构并直接激活菜单项。
下面的示例利用操作,这些操作是可以由需要相同功能的不同组件共享的对象。
com/zetcode/ShortcutsEx.java
package com.zetcode;
import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
public class ShortcutsEx extends JFrame {
public ShortcutsEx() {
initUI();
}
private void initUI() {
createMenuBar();
setTitle("Mnemonics and accelerators");
setSize(360, 250);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void createMenuBar() {
var menuBar = new JMenuBar();
var iconNew = new ImageIcon("src/resources/new.png");
var iconOpen = new ImageIcon("src/resources/open.png");
var iconSave = new ImageIcon("src/resources/save.png");
var iconExit = new ImageIcon("src/resources/exit.png");
var fileMenu = new JMenu("File");
fileMenu.setMnemonic(KeyEvent.VK_F);
var newMenuItem = new JMenuItem(new MenuItemAction("New", iconNew,
KeyEvent.VK_N));
var openMenuItem = new JMenuItem(new MenuItemAction("Open", iconOpen,
KeyEvent.VK_O));
var saveMenuItem = new JMenuItem(new MenuItemAction("Save", iconSave,
KeyEvent.VK_S));
var exitMenuItem = new JMenuItem("Exit", iconExit);
exitMenuItem.setMnemonic(KeyEvent.VK_E);
exitMenuItem.setToolTipText("Exit application");
exitMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W,
InputEvent.CTRL_DOWN_MASK));
exitMenuItem.addActionListener((event) -> System.exit(0));
fileMenu.add(newMenuItem);
fileMenu.add(openMenuItem);
fileMenu.add(saveMenuItem);
fileMenu.addSeparator();
fileMenu.add(exitMenuItem);
menuBar.add(fileMenu);
setJMenuBar(menuBar);
}
private class MenuItemAction extends AbstractAction {
public MenuItemAction(String text, ImageIcon icon,
Integer mnemonic) {
super(text);
putValue(SMALL_ICON, icon);
putValue(MNEMONIC_KEY, mnemonic);
}
@Override
public void actionPerformed(ActionEvent e) {
System.out.println(e.getActionCommand());
}
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new ShortcutsEx();
ex.setVisible(true);
});
}
}
该示例包含多个助记符和一个加速器。 三个菜单项共享一个动作对象。 选择这三个菜单项会使它们的操作命令打印到控制台。
var fileMenu = new JMenu("File");
fileMenu.setMnemonic(KeyEvent.VK_F);
助记符设置为“文件”菜单。 现在可以使用 Alt + F 快捷键激活菜单。
var newMenuItem = new JMenuItem(new MenuItemAction("New", iconNew,
KeyEvent.VK_N));
“新建”菜单项将操作对象作为参数。 其构造器将文本标签,图标和助记键作为参数。
exitMenuItem.setMnemonic(KeyEvent.VK_E);
“退出”菜单项不使用操作对象。 其功能是单独构建的。 我们调用setMnemonic()方法来设置助记键。 要使用助记符,该组件必须在屏幕上可见。 因此,我们必须首先激活菜单对象,使“退出”菜单项可见,然后才能激活此菜单项。 这意味着此菜单项通过 Alt + F + E 组合键激活。
exitMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W,
ActionEvent.CTRL_MASK));
加速器是直接启动菜单项的快捷键。 在我们的情况下,通过按 Ctrl + W 关闭我们的应用。 通过setAccelerator()方法设置加速器。
private class MenuItemAction extends AbstractAction {
public MenuItemAction(String text, ImageIcon icon,
Integer mnemonic) {
super(text);
putValue(SMALL_ICON, icon);
putValue(MNEMONIC_KEY, mnemonic);
}
@Override
public void actionPerformed(ActionEvent e) {
System.out.println(e.getActionCommand());
}
}
此动作类的一个实例由三个菜单项共享。 动作使用各种键来定义其功能。 putValue()方法将字符串值与指定的键关联。

图:助记符和加速器
带下划线的字符在视觉上提示助记符,加速器的快捷键显示在菜单项标签的旁边。
JCheckBoxMenuItem
JCheckBoxMenuItem是可以选择或取消选择的菜单项。 如果选中该菜单项,通常会在其旁边带有对勾标记。 如果未选择或取消选择,菜单项将显示而没有选中标记。 与常规菜单项一样,复选框菜单项可以具有与之关联的文本或图形图标,或两者都有。
com/zetcode/CheckBoxMenuItemEx.java
package com.zetcode;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ItemEvent;
import java.awt.event.KeyEvent;
import javax.swing.BorderFactory;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
public class CheckBoxMenuItemEx extends JFrame {
private JLabel statusBar;
public CheckBoxMenuItemEx() {
initUI();
}
private void initUI() {
createMenuBar();
statusBar = new JLabel("Ready");
statusBar.setBorder(BorderFactory.createEtchedBorder());
add(statusBar, BorderLayout.SOUTH);
setTitle("JCheckBoxMenuItem");
setSize(360, 250);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void createMenuBar() {
var menuBar = new JMenuBar();
var fileMenu = new JMenu("File");
fileMenu.setMnemonic(KeyEvent.VK_F);
var viewMenu = new JMenu("View");
viewMenu.setMnemonic(KeyEvent.VK_V);
var showStatusBarMenuItem = new JCheckBoxMenuItem("Show statubar");
showStatusBarMenuItem.setMnemonic(KeyEvent.VK_S);
showStatusBarMenuItem.setDisplayedMnemonicIndex(5);
showStatusBarMenuItem.setSelected(true);
showStatusBarMenuItem.addItemListener((e) -> {
if (e.getStateChange() == ItemEvent.SELECTED) {
statusBar.setVisible(true);
} else {
statusBar.setVisible(false);
}
});
viewMenu.add(showStatusBarMenuItem);
menuBar.add(fileMenu);
menuBar.add(viewMenu);
setJMenuBar(menuBar);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new CheckBoxMenuItemEx();
ex.setVisible(true);
});
}
}
本示例使用JCheckBoxMenuItem来切换状态栏的可见性。
statusBar = new JLabel("Ready");
statusBar.setBorder(BorderFactory.createEtchedBorder());
add(statusBar, BorderLayout.SOUTH);
状态栏是一个简单的JLabel组件。 我们在标签周围放置了凸起的EtchedBorder,以使其可见。
var showStatusBarMenuItem = new JCheckBoxMenuItem("Show statubar");
showStatusBarMenuItem.setMnemonic(KeyEvent.VK_S);
showStatusBarMenuItem.setDisplayedMnemonicIndex(5);
JCheckBoxMenuItem创建一个复选框菜单项。 标签上有两个字母; 因此,我们使用setDisplayedMnemonicIndex()方法来选择要强调的内容。 我们选择了第二个。
showStatusBarMenuItem.setSelected(true);
因为状态栏最初是可见的,所以我们调用JCheckBoxMenuItem的setSelected()方法来选择它。
showStatusBarMenuItem.addItemListener((e) -> {
if (e.getStateChange() == ItemEvent.SELECTED) {
statusbar.setVisible(true);
} else {
statusbar.setVisible(false);
}
});
JCheckBoxMenuItem是一种特殊的按钮组件。 它实现了ItemSelectable接口。 ItemListener可用于监听其状态变化。 根据其状态,我们显示或隐藏状态栏。

图:JCheckBoxMenuItem
JRadioButtonMenuItem
JRadioButtonMenuItem使您可以从互斥的选项列表中进行选择。 选择特定的JRadioButtonMenuItem会取消选择所有其他项目。 将JRadioButtonMenuItems放入ButtonGroup。
com/zetcode/RadioMenuItemEx.java
package com.zetcode;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JRadioButtonMenuItem;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ItemEvent;
import java.awt.event.KeyEvent;
public class RadioMenuItemEx extends JFrame {
private JLabel statusBar;
public RadioMenuItemEx() {
initUI();
}
private void initUI() {
createMenuBar();
statusBar = new JLabel("Easy");
statusBar.setBorder(BorderFactory.createEtchedBorder());
add(statusBar, BorderLayout.SOUTH);
setTitle("JRadioButtonMenuItem");
setSize(360, 250);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void createMenuBar() {
var menuBar = new JMenuBar();
var difMenu = new JMenu("Difficulty");
difMenu.setMnemonic(KeyEvent.VK_F);
var difGroup = new ButtonGroup();
var easyRMenuItem = new JRadioButtonMenuItem("Easy");
easyRMenuItem.setSelected(true);
difMenu.add(easyRMenuItem);
easyRMenuItem.addItemListener((e) -> {
if (e.getStateChange() == ItemEvent.SELECTED) {
statusBar.setText("Easy");
}
});
var mediumRMenuItem = new JRadioButtonMenuItem("Medium");
difMenu.add(mediumRMenuItem);
mediumRMenuItem.addItemListener((e) -> {
if (e.getStateChange() == ItemEvent.SELECTED) {
statusBar.setText("Medium");
}
});
var hardRMenuItem = new JRadioButtonMenuItem("Hard");
difMenu.add(hardRMenuItem);
hardRMenuItem.addItemListener((e) -> {
if (e.getStateChange() == ItemEvent.SELECTED) {
statusBar.setText("Hard");
}
});
difGroup.add(easyRMenuItem);
difGroup.add(mediumRMenuItem);
difGroup.add(hardRMenuItem);
menuBar.add(difMenu);
setJMenuBar(menuBar);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new RadioMenuItemEx();
ex.setVisible(true);
});
}
}
该示例创建一个包含三个JRadioButtonMenuItem组件的菜单。
var difGroup = new ButtonGroup();
ButtonGroup用于为一组按钮创建一个多重排除范围。
var easyRMenuItem = new JRadioButtonMenuItem("Easy");
easyRMenuItem.setSelected(true);
difMenu.add(easyRMenuItem);
创建一个新的JRadioButtonMenuItem。 用setSelected()方法选择它,并用add()方法放置在按钮组中。
easyRMenuItem.addItemListener((e) -> {
if (e.getStateChange() == ItemEvent.SELECTED) {
statusbar.setText("Easy");
}
});
ItemListener用于监听JRadioButtonMenuItem的事件。 getStateChange()确定状态更改的类型。 如果更改为ItemEvent.SELECTED,我们将在状态栏上更改状态。 (另一个状态更改是ItemEvent.DESELECTED。)

图:JRadioButtonMenuItem
右侧菜单
某些应用在右侧显示菜单。 通常,它是一个“帮助”菜单。
com/zetcode/RightMenuEx.java
package com.zetcode;
import java.awt.EventQueue;
import javax.swing.Box;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
public class RightMenuEx extends JFrame {
public RightMenuEx() {
initUI();
}
private void initUI() {
createMenuBar();
setTitle("Right menu");
setSize(300, 200);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void createMenuBar() {
var menuBar = new JMenuBar();
var fileMenu = new JMenu("File");
var viewMenu = new JMenu("View");
var toolsMenu = new JMenu("Tools");
var helpMenu = new JMenu("Help");
menuBar.add(fileMenu);
menuBar.add(viewMenu);
menuBar.add(toolsMenu);
menuBar.add(Box.createHorizontalGlue());
menuBar.add(helpMenu);
setJMenuBar(menuBar);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new RightMenuEx();
ex.setVisible(true);
});
}
}
该示例在左侧显示三个菜单,在右侧显示一个菜单。
var menubar = new JMenuBar();
var fileMenu = new JMenu("File");
var viewMenu = new JMenu("View");
var toolsMenu = new JMenu("Tools");
var helpMenu = new JMenu("Help");
将创建一个菜单栏和四个菜单对象。
menuBar.add(fileMenu);
menuBar.add(viewMenu);
menuBar.add(toolsMenu);
menuBar.add(Box.createHorizontalGlue());
menuBar.add(helpMenu);
添加三个菜单后,我们使用Box.createHorizontalGlue()方法在菜单栏上添加水平胶水。 胶水会吸收所有可用的额外空间。 这会将“帮助”菜单推到菜单栏的右侧。

图:右侧的帮助菜单
弹出菜单
菜单的另一种类型是弹出菜单。 Java Swing 具有此功能的JPopupMenu类。 它也称为上下文菜单,通常在右键单击组件时显示。 想法是仅提供与当前上下文相关的命令。 说我们有一张图片。 通过右键单击图像,我们将弹出一个窗口,其中包含用于保存,缩放或移动图像的命令。
com/zetcode/PopupMenuEx.java
package com.zetcode;
import java.awt.EventQueue;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
public class PopupMenuEx extends JFrame {
private JPopupMenu popupMenu;
public PopupMenuEx() {
initUI();
}
private void initUI() {
createPopupMenu();
setTitle("JPopupMenu");
setSize(300, 250);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private void createPopupMenu() {
popupMenu = new JPopupMenu();
var maximizeMenuItem = new JMenuItem("Maximize");
maximizeMenuItem.addActionListener((e) -> {
if (getExtendedState() != JFrame.MAXIMIZED_BOTH) {
setExtendedState(JFrame.MAXIMIZED_BOTH);
maximizeMenuItem.setEnabled(false);
}
});
popupMenu.add(maximizeMenuItem);
var quitMenuItem = new JMenuItem("Quit");
quitMenuItem.addActionListener((e) -> System.exit(0));
popupMenu.add(quitMenuItem);
addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
if (getExtendedState() != JFrame.MAXIMIZED_BOTH) {
maximizeMenuItem.setEnabled(true);
}
if (e.getButton() == MouseEvent.BUTTON3) {
popupMenu.show(e.getComponent(), e.getX(), e.getY());
}
}
});
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new PopupMenuEx();
ex.setVisible(true);
});
}
}
该示例显示了带有两个命令的弹出菜单。 第一个命令最大化窗口,第二个命令退出应用。
popupMenu = new JPopupMenu();
JPopupMenu创建一个弹出菜单。
var maximizeMenuItem = new JMenuItem("Maximize");
maximizeMenuItem.addActionListener((e) -> {
if (getExtendedState() != JFrame.MAXIMIZED_BOTH) {
setExtendedState(JFrame.MAXIMIZED_BOTH);
maximizeMenuItem.setEnabled(false);
}
});
弹出菜单由JMenuItems组成。 此项目将最大化框架。 getExtendedState()方法确定帧的状态。 可用状态为:NORMAL,ICONIFIED,MAXIMIZED_HORIZ,MAXIMIZED_VERT和MAXIMIZED_BOTH。 最大化帧后,我们将使用setEnabled()方法禁用菜单项。
popupMenu.add(quitMenuItem);
菜单项通过add()插入到弹出菜单中。
addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
if (getExtendedState() != JFrame.MAXIMIZED_BOTH) {
maximizeMenuItem.setEnabled(true);
}
if (e.getButton() == MouseEvent.BUTTON3) {
popupMenu.show(e.getComponent(), e.getX(), e.getY());
}
}
});
弹出菜单显示在我们用鼠标按钮单击的位置。 getButton()方法返回哪些鼠标按钮已更改状态。 MouseEvent.BUTTON3仅在右键单击时启用弹出菜单。 一旦窗口未最大化,我们将启用最大化菜单项。

图:JPopupMenu
工具栏
菜单将我们可以在应用中使用的命令分组。 使用工具栏可以快速访问最常用的命令。 在 Java Swing 中,JToolBar类在应用中创建一个工具栏。
com/zetcode/ToolbarEx.java
package com.zetcode;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JToolBar;
import java.awt.BorderLayout;
import java.awt.EventQueue;
public class ToolbarEx extends JFrame {
public ToolbarEx() {
initUI();
}
private void initUI() {
createMenuBar();
createToolBar();
setTitle("Simple toolbar");
setSize(300, 200);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void createMenuBar() {
var menuBar = new JMenuBar();
var fileMenu = new JMenu("File");
menuBar.add(fileMenu);
setJMenuBar(menuBar);
}
private void createToolBar() {
var toolbar = new JToolBar();
var icon = new ImageIcon("src/resources/exit2.png");
var exitButton = new JButton(icon);
toolbar.add(exitButton);
exitButton.addActionListener((e) -> System.exit(0));
add(toolbar, BorderLayout.NORTH);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new ToolbarEx();
ex.setVisible(true);
});
}
}
该示例创建一个带有一个退出按钮的工具栏。
var toolbar = new JToolBar();
使用JToolBar创建工具栏。
var exitButton = new JButton(icon);
toolbar.add(exitButton);
我们创建一个按钮并将其添加到工具栏。 插入工具栏中的按钮是常规JButton。
add(toolbar, BorderLayout.NORTH);
工具栏位于BorderLayout的北部区域。 (BorderLayout是JFrame,JWindow,JDialog,JInternalFrame和JApplet内容窗格的默认布局管理器。

图:JToolBar
Swing 工具栏
通常需要在窗口上显示多个工具栏。 以下示例显示了如何执行此操作。
com/zetcode/ToolbarsEx.java
package com.zetcode;
import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JToolBar;
import java.awt.EventQueue;
public class ToolbarsEx extends JFrame {
public ToolbarsEx() {
initUI();
}
private void initUI() {
createToolBars();
setTitle("Toolbars");
setSize(360, 250);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void createToolBars() {
var toolbar1 = new JToolBar();
var toolbar2 = new JToolBar();
var newIcon = new ImageIcon("src/resources/new2.png");
var openIcon = new ImageIcon("src/resources/open2.png");
var saveIcon = new ImageIcon("src/resources/save2.png");
var exitIcon = new ImageIcon("src/resources/exit2.png");
var newBtn = new JButton(newIcon);
var openBtn = new JButton(openIcon);
var saveBtn = new JButton(saveIcon);
toolbar1.add(newBtn);
toolbar1.add(openBtn);
toolbar1.add(saveBtn);
var exitBtn = new JButton(exitIcon);
toolbar2.add(exitBtn);
exitBtn.addActionListener((e) -> System.exit(0));
createLayout(toolbar1, toolbar2);
}
private void createLayout(JComponent... arg) {
var pane = getContentPane();
var gl = new GroupLayout(pane);
pane.setLayout(gl);
gl.setHorizontalGroup(gl.createParallelGroup()
.addComponent(arg[0], GroupLayout.DEFAULT_SIZE,
GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(arg[1], GroupLayout.DEFAULT_SIZE,
GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
gl.setVerticalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
.addComponent(arg[1])
);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new ToolbarsEx();
ex.setVisible(true);
});
}
}
窗口顶部显示两个工具栏。
var toolbar1 = new JToolBar();
var toolbar2 = new JToolBar();
使用JToolBar创建两个工具栏对象。
private void createLayout(JComponent... arg) {
var pane = getContentPane();
var gl = new GroupLayout(pane);
pane.setLayout(gl);
gl.setHorizontalGroup(gl.createParallelGroup()
.addComponent(arg[0], GroupLayout.DEFAULT_SIZE,
GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(arg[1], GroupLayout.DEFAULT_SIZE,
GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
gl.setVerticalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
.addComponent(arg[1])
);
}
GroupLayout管理器用于将工具栏放置在容器的顶部。

图:工具栏 s
在 Java Swing 教程的这一部分中,我们提到了菜单和工具栏。 我们使用了以下组件:JMenuBar,JMenu,JMenuItem,JSeparator,JCheckBoxMenuItem,JRadioButtonMenuItem,ButtonGroup,JPopupMenu和JToolBar。
Swing 布局管理
原文: http://zetcode.com/tutorials/javaswingtutorial/swinglayoutmanagement/
Java Swing 具有两种组件:容器和子组件。 容器将子项分组为合适的布局。 要创建布局,我们使用布局管理器。
ZetCode 为 Swing 布局管理过程提供了 196 页专门的电子书: Java Swing 布局管理教程
Swing 布局管理器
Swing 有大量可用的布局管理器-内置和第三方。 但是,大多数管理器都不适合现代 UI 的创建。
有三种可以正确完成工作的布局管理器:
MigLayoutGroupLayoutFormLayout
MigLayout,GroupLayout和FormLayout是强大,灵活的布局管理器,可以满足大多数布局要求。 在本教程中,我们使用GroupLayout管理器来设计用户界面。
以下布局管理器已过时:
FlowLayoutGridLayoutCardLayoutBoxLayoutGridBagLayout
这些布局管理器无法满足现代 UI 的要求。
过时的管理器遇到的问题
过时的管理器要么太简单(FlowLayout,GridLayout),要么就是不必要的复杂(GridBagLayout)。 所有这些管理器都有一个基本的设计错误:他们使用组件之间的固定间隙。 在组件之间使用刚性空间是不可移植的:一旦在不同的屏幕分辨率下运行程序,用户界面就会损坏。
过时的管理器试图通过一种称为嵌套的技术来修复其弱点。 在嵌套中,开发者在多个面板中使用几个不同的布局管理器。 尽管可以使用嵌套创建 UI,但它给代码带来了额外的不必要的复杂性。
过时的管理器
在本节中,我们将介绍过时的布局管理器。 不建议使用这些管理器。 如果我们需要维护一些遗留代码,只需花一些时间研究它们。 否则,应拒绝使用它们。
FlowLayout管理器
这是 Java Swing 工具箱中最简单的布局管理器。 它是JPanel组件的默认布局管理器。
它是如此简单,以至于不能用于任何实际布局。 许多 Java Swing 教程都介绍了该管理器,因此,初学者尝试在其项目中使用它,而没有意识到它不能用于任何严重的事情。
在计算其子大小时,流程布局使每个组件都假定其自然(首选)大小。 管理器将组件排成一排。 按顺序添加了它们。 如果它们不适合一排,则进入下一排。 可以从右向左添加组件,反之亦然。 管理器允许对齐组件。 隐式地,组件居中,并且组件之间以及组件的边缘与容器的边缘之间有 5px 的空间。
FlowLayout()
FlowLayout(int align)
FlowLayout(int align, int hgap, int vgap)
FlowLayout 管理器有三个可用的构造器。 第一个创建具有隐式值的管理器。 以 5px 水平和垂直空间居中。 其他允许指定这些参数。
FlowLayoutEx.java
package com.zetcode;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTree;
import java.awt.Dimension;
import java.awt.EventQueue;
public class FlowLayoutEx extends JFrame {
public FlowLayoutEx() {
initUI();
}
private void initUI() {
var panel = new JPanel();
var button = new JButton("button");
panel.add(button);
var tree = new JTree();
panel.add(tree);
var area = new JTextArea("text area");
area.setPreferredSize(new Dimension(100, 100));
panel.add(area);
add(panel);
pack();
setTitle("FlowLayout example");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new FlowLayoutEx();
ex.setVisible(true);
});
}
}
该示例显示了窗口中的按钮,树组件和文本区域组件。 如果我们创建一个空树组件,则组件内部会有一些默认值。
var panel = new JPanel();
JPanel组件的隐式布局管理器是FlowLayout。 我们不必手动设置。
var area = new JTextArea("text area");
area.setPreferredSize(new Dimension(100, 100));
流布局管理器为其组件设置首选大小。 因此,在我们的案例中,区域组件将为100x100px。 如果我们未设置首选大小,则组件将具有其文本的大小。 没有文本,该组件将根本不可见。 尝试在区域组件中编写或删除一些文本。 组件将相应地增长和收缩。
panel.add(area);
该组件放置在带有add()的容器内。

图:FlowLayout
GridLayout
GridLayout布局管理器将组件布置在矩形网格中。 容器分为大小相等的矩形。 每个矩形中放置一个组件。
GridLayout非常简单,不能用于任何实际布局。
GridLayoutEx.java
package com.zetcode;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.EventQueue;
import java.awt.GridLayout;
public class GridLayoutEx extends JFrame {
public GridLayoutEx() {
initUI();
}
private void initUI() {
var panel = new JPanel();
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
panel.setLayout(new GridLayout(5, 4, 5, 5));
String[] buttons = {
"Cls", "Bck", "", "Close", "7", "8", "9", "/", "4",
"5", "6", "*", "1", "2", "3", "-", "0", ".", "=", "+"
};
for (int i = 0; i < buttons.length; i++) {
if (i == 2) {
panel.add(new JLabel(buttons[i]));
} else {
panel.add(new JButton(buttons[i]));
}
}
add(panel);
setTitle("GridLayout");
setSize(350, 300);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new GridLayoutEx();
ex.setVisible(true);
});
}
}
该示例显示了一个简单计算器工具的框架。 我们在管理器中放入了 19 个按钮和一个标签。 请注意,每个按钮的大小均相同。
panel.setLayout(new GridLayout(5, 4, 5, 5));
在这里,我们为面板组件设置了网格布局管理器。 布局管理器采用四个参数。 行数,列数以及组件之间的水平和垂直间隙。

图:GridLayout
BorderLayout
BorderLayout是一个简单的布局管理器,在某些布局中可以派上用场。 它是JFrame,JWindow,JDialog,JInternalFrame和JApplet的默认布局管理器。 它有一个严重的局限性-它以像素为单位设置子元素之间的间隙,从而创建了刚性的布局。 这导致了不可移植的 UI,因此不建议使用它。
BorderLayout将空间分为五个区域:北,西,南,东和中心。 每个区域只能有一个组件。 如果需要在一个区域中放置更多组件,则必须在其中放置一个由我们选择的管理器组成的小组。 N,W,S,E 区域中的组件具有首选大小。 中间的组件占据了剩余的整个空间。
如果子组件彼此之间距离太近,则看起来不太好。 我们必须在它们之间留一些空间。 Swing 工具箱中的每个组件的边缘都可以带有边框。 要创建边框,我们可以创建EmptyBorder类的新实例,或者使用BorderFactory。
BorderEx.java
package com.zetcode;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Insets;
public class BorderLayoutEx extends JFrame {
public BorderLayoutEx() {
initUI();
}
private void initUI() {
var bottomPanel = new JPanel(new BorderLayout());
var topPanel = new JPanel();
topPanel.setBackground(Color.gray);
topPanel.setPreferredSize(new Dimension(250, 150));
bottomPanel.add(topPanel);
bottomPanel.setBorder(new EmptyBorder(new Insets(20, 20, 20, 20)));
add(bottomPanel);
pack();
setTitle("BorderLayout");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new BorderLayoutEx();
ex.setVisible(true);
});
}
}
该示例将显示一个灰色面板及其周围的边框。
var bottomPanel = new JPanel(new BorderLayout());
var topPanel = new JPanel();
我们将面板放入面板中。 底部面板具有BorderLayout管理器。
bottomPanel.add(topPanel);
在这里,我们将顶部面板放入底部面板组件中。 更准确地说,我们将其放置在BorderLayout管理器的中心区域。
bottomPanel.setBorder(new EmptyBorder(new Insets(20, 20, 20, 20)));
在这里,我们在底部面板周围创建了 20px 的边框。 边框值如下:上,左,下和右。 请注意,创建固定的嵌入(空格)不是可移植的。

图:BorderLayout
下一个示例显示BorderLayout管理器的典型用法。
BorderLayoutEx2.java
package com.zetcode;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.border.EmptyBorder;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Insets;
public class BorderLayoutEx2 extends JFrame {
public BorderLayoutEx2() {
initUI();
}
private void initUI() {
var menubar = new JMenuBar();
var fileMenu = new JMenu("File");
menubar.add(fileMenu);
setJMenuBar(menubar);
var toolbar = new JToolBar();
toolbar.setFloatable(false);
var exitIcon = new ImageIcon("src/resources/exit.png");
var exitBtn = new JButton(exitIcon);
exitBtn.setBorder(new EmptyBorder(0, 0, 0, 0));
toolbar.add(exitBtn);
add(toolbar, BorderLayout.NORTH);
var vertical = new JToolBar(JToolBar.VERTICAL);
vertical.setFloatable(false);
vertical.setMargin(new Insets(10, 5, 5, 5));
var driveIcon = new ImageIcon("src/resources/drive.png");
var compIcon = new ImageIcon("src/resources/computer.png");
var printIcon = new ImageIcon("src/resources/printer.png");
var driveBtn = new JButton(driveIcon);
driveBtn.setBorder(new EmptyBorder(3, 0, 3, 0));
var compBtn = new JButton(compIcon);
compBtn.setBorder(new EmptyBorder(3, 0, 3, 0));
var printBtn = new JButton(printIcon);
printBtn.setBorder(new EmptyBorder(3, 0, 3, 0));
vertical.add(driveBtn);
vertical.add(compBtn);
vertical.add(printBtn);
add(vertical, BorderLayout.WEST);
add(new JTextArea(), BorderLayout.CENTER);
var statusbar = new JLabel(" Statusbar");
add(statusbar, BorderLayout.SOUTH);
setSize(400, 350);
setTitle("BorderLayout");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new BorderLayoutEx2();
ex.setVisible(true);
});
}
}
该示例显示了典型的应用框架。 我们显示了一个垂直和水平工具栏,一个状态栏和一个中央组件(文本区域)。
BorderLayout是JFrame容器的默认布局管理器。 因此,我们不必显式设置它。
add(toolbar, BorderLayout.NORTH);
我们将工具栏放置在布局的北部。
var driveBtn = new JButton(driveIcon);
driveBtn.setBorder(new EmptyBorder(3, 0, 3, 0));
为了在按钮周围留一些空白,我们必须使用EmptyBorder。 这会在按钮的顶部和底部增加一些固定的空间。 当我们添加固定空间时,UI 不可移植。 3px 的空间在 1280x720 的屏幕上可能看起来不错,但在1920x1200px的屏幕上不合适。
add(vertical, BorderLayout.WEST);
我们将垂直太杆放在西边。
add(new JTextArea(), BorderLayout.CENTER);
我们将文本区域放置在中间。
add(statusbar, BorderLayout.SOUTH);
状态栏转到南部区域。

图:BorderLayout 2
CardLayout
CardLayout是一个简单的布局管理器,将每个组件都视为卡。 容器是这些卡的栈。 一次只能看到一个组件。 其余的隐藏。 最初显示容器时,默认情况下将显示添加到容器的第一个组件。 该管理器的实际用途有限。 它可用于创建向导或选项卡式窗格。
以下示例使用CardLayout管理器创建图像库。 我们使用了 Krasna Horka 城堡的四张图片(2012 年大火之前)。
CardLayoutEx.java
package com.zetcode;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.EventQueue;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class CardLayoutEx extends JFrame {
private ImageIcon horka1;
private ImageIcon horka2;
private ImageIcon horka3;
private ImageIcon horka4;
private ImageIcon previ;
private ImageIcon nexti;
private JPanel mainPanel;
private CardLayout cardLayout;
public CardLayoutEx() {
initUI();
}
private void initUI() {
mainPanel = new JPanel();
mainPanel.setBackground(new Color(50, 50, 50));
mainPanel.setBorder(
BorderFactory.createEmptyBorder(5, 5, 5, 5)
);
cardLayout = new CardLayout();
mainPanel.setLayout(cardLayout);
horka1 = new ImageIcon("src/resources/horka1.jpg");
horka2 = new ImageIcon("src/resources/horka2.jpg");
horka3 = new ImageIcon("src/resources/horka3.jpg");
horka4 = new ImageIcon("src/resources/horka4.jpg");
previ = new ImageIcon("src/resources/previous.png");
nexti = new ImageIcon("src/resources/next.png");
var label1 = new JLabel(horka1);
var label2 = new JLabel(horka2);
var label3 = new JLabel(horka3);
var label4 = new JLabel(horka4);
mainPanel.add(label1);
mainPanel.add(label2);
mainPanel.add(label3);
mainPanel.add(label4);
add(mainPanel);
var prevButton = new JButton(previ);
prevButton.addActionListener((e) -> cardLayout.previous(mainPanel));
var nextButton = new JButton(nexti);
nextButton.addActionListener((e) -> cardLayout.next(mainPanel));
var btnPanel = new JPanel();
btnPanel.setBackground(new Color(50, 50, 50));
btnPanel.add(prevButton);
btnPanel.add(nextButton);
add(btnPanel, BorderLayout.SOUTH);
pack();
setTitle("Gallery");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new CardLayoutEx();
ex.setVisible(true);
});
}
}
我们创建两个按钮来浏览图像。
mainPanel = new JPanel();
mainPanel.setBackground(new Color(50, 50, 50));
mainPanel.setBorder(
BorderFactory.createEmptyBorder(5, 5, 5, 5)
);
我们创建主面板组件。 我们将其颜色设置为深灰色。 我们将 5px 放置在面板周围,以使它的子级不太靠近窗口的边界。
cardLayout = new CardLayout();
mainPanel.setLayout(cardLayout);
CardLayout管理器已创建并设置到主面板。
mainPanel.add(label1);
mainPanel.add(label2);
mainPanel.add(label3);
mainPanel.add(label4);
显示图像的标签组件将添加到面板中。
var prevButton = new JButton(previ);
prevButton.addActionListener((e) -> cardLayout.previous(mainPanel));
单击上一个按钮,将调用管理器的previous()方法。 它会翻转到指定容器的上一张卡片。
add(mainPanel);
我们将主面板添加到框架组件边框布局的中心区域。 如果未明确指定放置组件的位置,则会将其添加到中心区域。
var btnPanel = new JPanel();
btnPanel.setBackground(new Color(50, 50, 50));
btnPanel.add(prevButton);
btnPanel.add(nextButton);
这些按钮将添加到按钮面板。
add(btnPanel, BorderLayout.SOUTH);
最后,将带有按钮的面板放入BorderLayout管理器的南部区域。

图:CardLayout
BoxLayout
BoxLayout管理器是一个简单的布局管理器,用于组织列或行中的组件。 它可以使用嵌套创建非常复杂的布局。 但是,这增加了布局创建的复杂性,并使用了额外的资源,尤其是许多其他JPanel组件。 BoxLayout仅能创建固定空间; 因此,其布局不可移植。
BoxLayout具有以下构造器:
BoxLayout(Container target, int axis)
构造器创建一个布局管理器,该管理器将沿给定轴布置组件。 与其他布局管理器不同,BoxLayout将容器实例作为构造器中的第一个参数。 第二个参数确定管理器的方向。 要创建一个水平框,我们可以使用LINE_AXIS常量。 要创建一个垂直框,我们可以使用PAGE_AXIS常量。
框布局管理器通常与Box类一起使用。 此类创建一些不可见的组件,这些组件会影响最终布局。
- 胶水
- 支撑
- 刚性区域
假设我们要在窗口的右下角放置两个按钮。
BoxLayoutButtonsEx.java
package com.zetcode;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Dimension;
import java.awt.EventQueue;
public class BoxLayoutButtonsEx extends JFrame {
public BoxLayoutButtonsEx() {
initUI();
}
private void initUI() {
var basePanel = new JPanel();
basePanel.setLayout(new BoxLayout(basePanel, BoxLayout.Y_AXIS));
add(basePanel);
basePanel.add(Box.createVerticalGlue());
var bottomPanel = new JPanel();
bottomPanel.setAlignmentX(1f);
bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.X_AXIS));
var okBtn = new JButton("OK");
var closeBtn = new JButton("Close");
bottomPanel.add(okBtn);
bottomPanel.add(Box.createRigidArea(new Dimension(5, 0)));
bottomPanel.add(closeBtn);
bottomPanel.add(Box.createRigidArea(new Dimension(15, 0)));
basePanel.add(bottomPanel);
basePanel.add(Box.createRigidArea(new Dimension(0, 15)));
setTitle("Two Buttons");
setSize(300, 150);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new BoxLayoutButtonsEx();
ex.setVisible(true);
});
}
}
下图说明了该示例。

图:两个按钮
我们创建两个面板。 基本面板具有垂直框布局。 底部面板有一个水平面板。 我们将底板插入底板。 底部面板右对齐。 窗口顶部和底部面板之间的空间是可扩展的。 这是通过垂直胶水实现的。
basePanel.setLayout(new BoxLayout(basePanel, BoxLayout.Y_AXIS));
在这里,我们使用垂直BoxLayout创建一个基础面板。
var bottomPanel = new JPanel();
bottomPanel.setAlignmentX(1f);
bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.X_AXIS));
底部面板右对齐。 这是通过setAlignmentX()方法完成的。 面板具有水平布局。
bottomPanel.add(Box.createRigidArea(new Dimension(5, 0)));
我们在按钮之间留出一些刚性空间。
basePanel.add(bottomPanel);
在这里,我们将具有水平框布局的底部面板放置到垂直基础面板上。
basePanel.add(Box.createRigidArea(new Dimension(0, 15)));
我们还在底部面板和窗口边框之间留出一些空间。

图:BoxLayout按钮示例
当使用BoxLayout管理器时,可以在组件之间设置一个刚性区域。
BoxLayoutRigidAreaEx.java
package com.zetcode;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Insets;
public class BoxLayoutRigidAreaEx extends JFrame {
public BoxLayoutRigidAreaEx() {
initUI();
}
private void initUI() {
var basePanel = new JPanel();
basePanel.setLayout(new BoxLayout(basePanel, BoxLayout.Y_AXIS));
basePanel.setBorder(new EmptyBorder(new Insets(40, 60, 40, 60)));
basePanel.add(new JButton("Button"));
basePanel.add(Box.createRigidArea(new Dimension(0, 5)));
basePanel.add(new JButton("Button"));
basePanel.add(Box.createRigidArea(new Dimension(0, 5)));
basePanel.add(new JButton("Button"));
basePanel.add(Box.createRigidArea(new Dimension(0, 5)));
basePanel.add(new JButton("Button"));
add(basePanel);
pack();
setTitle("RigidArea");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new BoxLayoutRigidAreaEx();
ex.setVisible(true);
});
}
}
在此示例中,我们显示四个按钮。 默认情况下,按钮之间没有空格。 为了在其中留出一些空间,我们增加了一些刚性区域。
basePanel.setLayout(new BoxLayout(basePanel, BoxLayout.Y_AXIS));
我们为面板使用垂直BoxLayout管理器。
basePanel.add(new JButton("Button"));
basePanel.add(Box.createRigidArea(new Dimension(0, 5)));
basePanel.add(new JButton("Button"));
我们添加按钮并使用Box.createRigidArea()在它们之间创建一个刚性区域。

图:RigidArea
每日提示
下一个示例创建“每日提示”窗口对话框。 我们结合使用各种布局管理器。
TipOfDayEx.java
package com.zetcode;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JTextPane;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.event.KeyEvent;
public class TipOfDayEx extends JDialog {
public TipOfDayEx() {
initUI();
}
private void initUI() {
var basePanel = new JPanel();
basePanel.setLayout(new BoxLayout(basePanel, BoxLayout.Y_AXIS));
add(basePanel);
var topPanel = new JPanel(new BorderLayout(0, 0));
topPanel.setMaximumSize(new Dimension(450, 0));
var hint = new JLabel("Productivity Hints");
hint.setBorder(BorderFactory.createEmptyBorder(0, 25, 0, 0));
topPanel.add(hint);
var icon = new ImageIcon("src/resources/coffee2.png");
var label = new JLabel(icon);
label.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
topPanel.add(label, BorderLayout.EAST);
var separator = new JSeparator();
separator.setForeground(Color.gray);
topPanel.add(separator, BorderLayout.SOUTH);
basePanel.add(topPanel);
var textPanel = new JPanel(new BorderLayout());
textPanel.setBorder(BorderFactory.createEmptyBorder(15, 25, 15, 25));
var pane = new JTextPane();
pane.setContentType("text/html");
var text = "<p><b>Closing windows using the mouse wheel</b></p>" +
"<p>Clicking with the mouse wheel on an editor tab closes the window. " +
"This method works also with dockable windows or Log window tabs.</p>";
pane.setText(text);
pane.setEditable(false);
textPanel.add(pane);
basePanel.add(textPanel);
var boxPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 20, 0));
var box = new JCheckBox("Show Tips at startup");
box.setMnemonic(KeyEvent.VK_S);
boxPanel.add(box);
basePanel.add(boxPanel);
var bottomPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
var tipBtn = new JButton("Next Tip");
tipBtn.setMnemonic(KeyEvent.VK_N);
var closeBtn = new JButton("Close");
closeBtn.setMnemonic(KeyEvent.VK_C);
bottomPanel.add(tipBtn);
bottomPanel.add(closeBtn);
basePanel.add(bottomPanel);
bottomPanel.setMaximumSize(new Dimension(450, 0));
setTitle("Tip of the Day");
setSize(new Dimension(450, 350));
setResizable(false);
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new TipOfDayEx();
ex.setVisible(true);
});
}
}
该示例混合使用了布局管理器。 只需将四个面板放入垂直组织的基本面板中即可。
var basePanel = new JPanel();
basePanel.setLayout(new BoxLayout(basePanel, BoxLayout.Y_AXIS));
add(basePanel);
这是最底部的面板。 它具有垂直框布局管理器。 基本面板已添加到默认的JDialog组件。 默认情况下,此组件具有边框布局管理器。
var topPanel = new JPanel(new BorderLayout(0, 0));
topPanel面板具有边框布局管理器。 我们将包含三个组成部分。 两个标签和一个分隔符。
topPanel.setMaximumSize(new Dimension(450, 0));
如果我们想要的面板不超过其组件,则必须设置其最大大小。 零值将被忽略。 管理器计算必要的高度。
var textPanel = new JPanel(new BorderLayout());
...
textPanel.add(pane);
文本窗格组件将添加到边框布局管理器的中心区域。 它会占用所有剩余空间。 正是我们想要的。
var boxPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 20, 0));
该复选框显示在boxPanel面板中。 它保持对齐。 流布局管理器的水平间隙为 20px。 其他组件的像素为 25px。 这是为什么? 这是因为流布局管理器也在组件和边缘之间放置了一些空间。
var bottomPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
...
bottomPanel.setMaximumSize(new Dimension(450, 0));
底部面板显示两个按钮。 它具有右对齐的流布局管理器。 为了在对话框的右边缘显示按钮,面板必须从头到尾水平伸展。

图:当天的提示
没有管理器
可以不使用布局管理器。 在少数情况下,我们可能不需要布局管理器。 (也许将一些图像放置在一些不规则的位置。)但是在大多数情况下,要创建真正可移植的复杂应用,我们需要布局管理器。
如果没有布局管理器,我们将使用绝对值来定位组件。
AbsoluteLayoutEx.java
package com.zetcode;
import javax.swing.JButton;
import javax.swing.JFrame;
import java.awt.EventQueue;
public class AbsoluteLayoutEx extends JFrame {
public AbsoluteLayoutEx() {
initUI();
}
private void initUI() {
setLayout(null);
var okBtn = new JButton("OK");
okBtn.setBounds(50, 50, 80, 25);
var closeBtn = new JButton("Close");
closeBtn.setBounds(150, 50, 80, 25);
add(okBtn);
add(closeBtn);
setTitle("Absolute positioning");
setSize(300, 250);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
var ex = new AbsoluteLayoutEx();
ex.setVisible(true);
});
}
}
这个简单的示例显示了两个按钮。
setLayout(null);
我们通过向setLayout()方法提供null使用绝对定位。 (JFrame组件具有默认的布局管理器BorderLayout。)
okBtn.setBounds(50, 50, 80, 25);
setBounds()方法放置“确定”按钮。 参数是 x 和 y 坐标以及组件的宽度和高度。

图:绝对布局
在本章中,我们提到了 Swing 中的布局管理。


浙公网安备 33010602011771号