011-小项目-动态矩阵密码键盘

auther: abinng date: 2026-03-26 10:01 createDate:2026-03-26 10:01

在银行等私密输入密码的地方,其键盘的排列并不是顺序的123456789,而是九个位置顺序打乱

功能描述

矩阵键盘分为数字键(0~9),功能按键(删除和确认)

输入 6 位数字模拟密码,每次输入 6 位后,键盘数字按键随机变换坐标,功能按键坐标固定

点击确定后,显示清零后数字按键随机变换坐标

矩阵键盘按键尺寸固定,每个按键长宽为 80px * 80px,LCD 显示区长宽为 240px * 80px

创建项目

本次使用 CLion ,如果还没安装:CLion 安装

进入 CLion -> New Project -> (左侧)Qt Widget Executable

修改项目路径

选择 Qt CMake prefix path 其实就是 CMakeLists.txt 中的 set(CMAKE_PREFIX_PATH "D:/09_SW/_Dev/Qt/6.5.3/mingw_64")

Language standard 选择 C++14 即可

最后点击右下角 Create

创建后,默认创建的 CMakeLists.txt 会有点问题

官方不太建议用这三行来开启 MOC, RCC, UIC

1
2
3
set(CMAKE_AUTOMOC ON)  
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)

我们把它换成

1
qt_standard_project_setup()
点击展开
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
cmake_minimum_required(VERSION 4.1)  
project(ex15_keyPad)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_PREFIX_PATH "D:/09_SW/_Dev/Qt/6.5.3/mingw_64")
find_package(Qt6 COMPONENTS Core Gui Widgets REQUIRED)
qt_standard_project_setup()

add_executable(ex15_keyPad main.cpp
key_pad.cpp
key_pad.h
key_pad.ui)
target_link_libraries(ex15_keyPad Qt6::Core Qt6::Gui Qt6::Widgets)


if (WIN32 AND NOT DEFINED CMAKE_TOOLCHAIN_FILE)
set(DEBUG_SUFFIX)
if (MSVC AND CMAKE_BUILD_TYPE MATCHES "Debug")
set(DEBUG_SUFFIX "d")
endif ()
set(QT_INSTALL_PATH "${CMAKE_PREFIX_PATH}")
if (NOT EXISTS "${QT_INSTALL_PATH}/bin")
set(QT_INSTALL_PATH "${QT_INSTALL_PATH}/..")
if (NOT EXISTS "${QT_INSTALL_PATH}/bin")
set(QT_INSTALL_PATH "${QT_INSTALL_PATH}/..")
endif ()
endif ()
if (EXISTS "${QT_INSTALL_PATH}/plugins/platforms/qwindows${DEBUG_SUFFIX}.dll")
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
"$<TARGET_FILE_DIR:${PROJECT_NAME}>/plugins/platforms/")
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${QT_INSTALL_PATH}/plugins/platforms/qwindows${DEBUG_SUFFIX}.dll"
"$<TARGET_FILE_DIR:${PROJECT_NAME}>/plugins/platforms/")
endif ()
foreach (QT_LIB Core Gui Widgets)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${QT_INSTALL_PATH}/bin/Qt6${QT_LIB}${DEBUG_SUFFIX}.dll"
"$<TARGET_FILE_DIR:${PROJECT_NAME}>")
endforeach (QT_LIB)
endif ()

默认创建的这个 main.cpp 也没啥用

接下来创建UI文件

此时会创建三个文件:key_pad.ui, key_pad.h, key_pad.cpp

初步设置界面

先用鼠标拖动的方式大致规划一下界面

双击 key_pad.ui 会自动进入 Qt Designer

我们先规定 QWidget ,垂直布局,长宽和最大/最小长宽均为 240 x 400

这样就保证了用户拖不动大小,界面不会乱

整体大致分为两块,一个 LCD Number 用于显示,一个 Widget 作为容器

将两个组件改一下名字,lcdShow, padWidget

计算器设为无边框,将 KeyPad 的 Layout 中,几个边距(Margin)和内部间隔(Spacing)调为 0

将 lcdShow 和 padWidget 所在的布局,比例调为 1, 4

保存一下,回到 CLion,构建一下,没报错就是正常的

补全 main.cpp 测试一下

点击展开
1
2
3
4
5
6
7
8
9
#include "key_pad.h"
#include "cmake-build-debug/ex15_keyPad_autogen/include/ui_key_pad.h"

int main(int argc, char *argv[]) {
QApplication a(argc, argv);
KeyPad win;
win.show();
return QApplication::exec();
}

先运行看看效果

写代码

key_pad.h

点击展开
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//
// Created by abinng on 2026/3/26.
//

#ifndef EX15_KEYPAD_KEY_PAD_H
#define EX15_KEYPAD_KEY_PAD_H

#include <QWidget>
#include <QGridLayout>
#include <QString>

QT_BEGIN_NAMESPACE

namespace Ui {
class KeyPad;
}

struct KeyInfo {
QString text;
int value;
};

QT_END_NAMESPACE

class KeyPad : public QWidget {
Q_OBJECT

public:
explicit KeyPad(QWidget *parent = nullptr);

~KeyPad() override;
public slots:
void lcdShow() const;

private:
void fixed_keypad() const;
void random_keypad() const;
Ui::KeyPad *ui;
QGridLayout *pad_layout;
};


#endif //EX15_KEYPAD_KEY_PAD_H

key_pad.cpp

点击展开
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//
// Created by abinng on 2026/3/26.
//

// You may need to build the project (run Qt uic code generator) to get "ui_key_pad.h" resolved

#include "key_pad.h"
#include "ui_key_pad.h"
#include <QPushButton>
#include <QRandomGenerator>
#include <QDateTime>

// static QList<QString> nums = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "删除", "0", "确定"};

static QList<KeyInfo> pads = {
{"1", 1}, {"2", 2}, {"3", 3}, {"4", 4}, {"5", 5},
{"6", 6}, {"7", 7}, {"8", 8}, {"9", 9},{"删除", 11},
{"0", 0}, {"确定", 12}
};

KeyPad::KeyPad(QWidget *parent) : QWidget(parent), ui(new Ui::KeyPad) {
ui->setupUi(this);
pad_layout = new QGridLayout(ui->padWidget);
pad_layout->setContentsMargins(0,0,0,0);
pad_layout->setSpacing(0);
fixed_keypad();
}

void KeyPad::fixed_keypad() const {
for (int x = 0; x < 4; ++x) {
for (int y = 0; y < 3; ++y) {
int pos = x * 3 + y;
auto btn = new QPushButton(pads[pos].text, ui->padWidget);
btn->setProperty("value", pads[pos].value);
btn->setMinimumSize(80, 80); // 设置最小尺寸
btn->setMaximumSize(80, 80); // 设置最大尺寸
pad_layout->addWidget(btn, x, y, 1, 1);
connect(btn, &QPushButton::clicked, this, &KeyPad::lcdShow);
}
}
}

void KeyPad::random_keypad() const {
QRandomGenerator random(QDateTime::currentSecsSinceEpoch());
QList<int> indexes;
for (int i = 0; i < pads.size(); ++i) {
if (pads[i].value < 10) {
indexes.append(i);
}
}
std::shuffle(indexes.begin(), indexes.end(), random);

QPushButton *btn;
int pos_index = 0;
for (int x = 0; x < 4; ++x) {
for (int y = 0; y < 3; ++y) {
int pos = x * 3 + y;
if (pos == 9 || pos == 11) {
btn = new QPushButton(pads[pos].text, ui->padWidget);
btn->setProperty("value", pads[pos].value);
} else {
btn = new QPushButton(pads[indexes[pos_index]].text, ui->padWidget);
btn->setProperty("value", pads[indexes[pos_index]].value);
++pos_index;
}
btn->setMinimumSize(80, 80); // 设置最小尺寸
btn->setMaximumSize(80, 80); // 设置最大尺寸
pad_layout->addWidget(btn, x, y, 1, 1);
connect(btn, &QPushButton::clicked, this, &KeyPad::lcdShow);
}
}
}

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

void KeyPad::lcdShow() const {
auto btn = qobject_cast<QPushButton *>(sender());
if (btn == nullptr) {
return ;
}
int lcd_value = ui->lcdShow->intValue();
int v = btn->property("value").toInt();
if (v == 11) {
lcd_value /= 10;
ui->lcdShow->display(lcd_value);
} else if (v == 12) {
ui->lcdShow->display(0);
random_keypad();
} else {
lcd_value *= 10;
lcd_value += v;
ui->lcdShow->display(lcd_value);
}
}

main.cpp

点击展开
1
2
3
4
5
6
7
8
9
10
11
#include <QApplication>  

#include "key_pad.h"
#include "cmake-build-debug/ex15_keyPad_autogen/include/ui_key_pad.h"

int main(int argc, char *argv[]) {
QApplication a(argc, argv);
KeyPad win;
win.show();
return QApplication::exec();
}

效果:

这不是最终版,因为目前的样式还是 Qt 默认的样式,我们要用 QSS 进行渲染一下按键

QSS 按键渲染

接下来我们需要加入 QSS按键渲染,结果如下图:

其实就是加上 CSS 语法的字符串,设置为 Qt 组件的 StyleSheet,调用 setStyleSheet 函数

key_pad.cpp

点击展开
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//
// Created by abinng on 2026/3/26.
//

// You may need to build the project (run Qt uic code generator) to get "ui_key_pad.h" resolved

#include "key_pad.h"
#include "ui_key_pad.h"
#include <QPushButton>
#include <QRandomGenerator>
#include <QDateTime>

// static QList<QString> nums = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "删除", "0", "确定"};

static QList<KeyInfo> pads = {
{"1", 1}, {"2", 2}, {"3", 3}, {"4", 4}, {"5", 5},
{"6", 6}, {"7", 7}, {"8", 8}, {"9", 9},{"删除", 11},
{"0", 0}, {"确定", 12}
};

+ static QString lcdStyle = R"(
+ background-color: #282726;
+ color: #ebe9eb;
+ font-size: 55px;
+ padding: 0px 5px 0px 5px;
+ text-align: right;
+ margin-bottom: 0px;
+ )";

KeyPad::KeyPad(QWidget *parent) : QWidget(parent), ui(new Ui::KeyPad) {
ui->setupUi(this);
+ ui->lcdShow->setStyleSheet(lcdStyle);
pad_layout = new QGridLayout(ui->padWidget);
pad_layout->setContentsMargins(0,0,0,0);
pad_layout->setSpacing(0);
random_keypad();
}

void KeyPad::fixed_keypad() const {
for (int x = 0; x < 4; ++x) {
for (int y = 0; y < 3; ++y) {
int pos = x * 3 + y;
auto btn = new QPushButton(pads[pos].text, ui->padWidget);
btn->setProperty("value", pads[pos].value);
btn->setMinimumSize(80, 80); // 设置最小尺寸
btn->setMaximumSize(80, 80); // 设置最大尺寸
pad_layout->addWidget(btn, x, y, 1, 1);
connect(btn, &QPushButton::clicked, this, &KeyPad::lcdShow);
}
}
}

void KeyPad::random_keypad() const {
QRandomGenerator random(QDateTime::currentSecsSinceEpoch());
QList<int> indexes;
for (int i = 0; i < pads.size(); ++i) {
if (pads[i].value < 10) {
indexes.append(i);
}
}
std::shuffle(indexes.begin(), indexes.end(), random);

QPushButton *btn;
int pos_index = 0;
for (int x = 0; x < 4; ++x) {
for (int y = 0; y < 3; ++y) {
int pos = x * 3 + y;
if (pos == 9 || pos == 11) {
btn = new QPushButton(pads[pos].text, ui->padWidget);
btn->setProperty("value", pads[pos].value);
+ btn->setStyleSheet(R"(
+ QPushButton {
+ background-color: #f99f2b;
+ font-size: 25px;
+ color: #ebe9eb; border-radius: 0px; border: 1px solid rgb(44,44,46);
+ }
+ QPushButton:pressed {
+ background-color: #c67e20
+ })");
} else {
btn = new QPushButton(pads[indexes[pos_index]].text, ui->padWidget);
btn->setProperty("value", pads[indexes[pos_index]].value);
+ btn->setStyleSheet(R"(
+ QPushButton {
+ background-color: #5e5d5c;
+ font-size: 22px;
+ color: #ebe9eb; border-radius: 0px; border: 1px solid rgb(44,44,46);
+ }
+ QPushButton:pressed {
+ background-color: #9f9e9e
+ })");
++pos_index;
}
btn->setMinimumSize(80, 80); // 设置最小尺寸
btn->setMaximumSize(80, 80); // 设置最大尺寸
pad_layout->addWidget(btn, x, y, 1, 1);
connect(btn, &QPushButton::clicked, this, &KeyPad::lcdShow);
}
}
}

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

void KeyPad::lcdShow() const {
auto btn = qobject_cast<QPushButton *>(sender());
if (btn == nullptr) {
return ;
}
int lcd_value = ui->lcdShow->intValue();
int v = btn->property("value").toInt();
if (v == 11) {
lcd_value /= 10;
ui->lcdShow->display(lcd_value);
} else if (v == 12) {
ui->lcdShow->display(0);
random_keypad();
} else {
if (lcd_value <= 9999) {
lcd_value *= 10;
lcd_value += v;
ui->lcdShow->display(lcd_value);
}
}
}

这么一来就好了

项目内存管理改进与主逻辑实现

每次点击确定按键,调用 random_keypad() 时,都会循环用 new 操作创建按钮

此时父对象会产生大量子对象,内存占用率高

我们全局创建一个按钮列表,在构造函数中进行 new 初始化分配空间

点击展开
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
KeyPad::KeyPad(QWidget *parent) : QWidget(parent), ui(new Ui::KeyPad) {
ui->setupUi(this);
ui->lcdShow->setStyleSheet(lcdStyle);
pad_layout = new QGridLayout(ui->padWidget);
pad_layout->setContentsMargins(0,0,0,0);
pad_layout->setSpacing(0);

QPushButton *btn;
for (int i = 0; i < pads.size(); ++i) {
btn = new QPushButton(pads[i].text, ui->padWidget);
btn->setProperty("value", pads[i].value);
btn->setMinimumSize(80, 80); // 设置最小尺寸
btn->setMaximumSize(80, 80); // 设置最大尺寸
if (i == 9 || i == 11) {
btn->setStyleSheet(R"(
QPushButton {
background-color: #f99f2b;
font-size: 25px;
color: #ebe9eb; border-radius: 0px; border: 1px solid rgb(44,44,46);
}
QPushButton:pressed {
background-color: #c67e20
})");
} else {
btn->setStyleSheet(R"(
QPushButton {
background-color: #5e5d5c;
font-size: 22px;
color: #ebe9eb; border-radius: 0px; border: 1px solid rgb(44,44,46);
}
QPushButton:pressed {
background-color: #9f9e9e
})");
}
connect(btn, &QPushButton::clicked, this, &KeyPad::lcdShow);
btns.append(btn);
}
random_keypad();
}

之后 random_keypad() 中只要改变 btn 的指向并添加进布局就好

lcdShow() 中,需要优化为,六位数字时,打乱键盘,多于6位数字时,清空lcd并显示刚刚点击的数字

点击展开
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void KeyPad::lcdShow() const {
static int cnt = 0;
auto btn = qobject_cast<QPushButton *>(sender());
if (btn == nullptr) {
return ;
}
int lcd_value = ui->lcdShow->intValue();
int v = btn->property("value").toInt();
if (v == 11) {
if (cnt <= 0) {
return ;
}
lcd_value /= 10;
ui->lcdShow->display(lcd_value);
--cnt;
} else if (v == 12) {
ui->lcdShow->display(0);
random_keypad();
cnt = 0;
} else {
++cnt;
if (cnt > 6) {
ui->lcdShow->display(v);
cnt = 1;
return ;
}
if (cnt == 6) {
random_keypad();
}
lcd_value *= 10;
lcd_value += v;
ui->lcdShow->display(lcd_value);
}
}

这里的 lcd 屏幕显示 6 位数字是 lcdNumber 的一个属性,可以在 Qt Designer 中调:

总代码

key_pad.h key_pad.cpp main.cpp

点击展开
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//
// Created by abinng on 2026/3/26.
//

#ifndef EX15_KEYPAD_KEY_PAD_H
#define EX15_KEYPAD_KEY_PAD_H

#include <QWidget>
#include <QGridLayout>
#include <QString>

QT_BEGIN_NAMESPACE

namespace Ui {
class KeyPad;
}

struct KeyInfo {
QString text;
int value;
};

QT_END_NAMESPACE

class KeyPad : public QWidget {
Q_OBJECT

public:
explicit KeyPad(QWidget *parent = nullptr);

~KeyPad() override;
public slots:
void lcdShow() const;

private:
void fixed_keypad() const;
void random_keypad() const;
Ui::KeyPad *ui;
QGridLayout *pad_layout;
};

#endif //EX15_KEYPAD_KEY_PAD_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//
// Created by abinng on 2026/3/26.
//

#ifndef EX15_KEYPAD_KEY_PAD_H
#define EX15_KEYPAD_KEY_PAD_H

#include <QWidget>
#include <QGridLayout>
#include <QString>

QT_BEGIN_NAMESPACE

namespace Ui {
class KeyPad;
}

struct KeyInfo {
QString text;
int value;
};

QT_END_NAMESPACE

class KeyPad : public QWidget {
Q_OBJECT

public:
explicit KeyPad(QWidget *parent = nullptr);

~KeyPad() override;
public slots:
void lcdShow() const;

private:
void fixed_keypad() const;
void random_keypad() const;
Ui::KeyPad *ui;
QGridLayout *pad_layout;
};


#endif //EX15_KEYPAD_KEY_PAD_H
1
2
3
4
5
6
7
8
9
10
11
#include <QApplication>

#include "key_pad.h"
#include "cmake-build-debug/ex15_keyPad_autogen/include/ui_key_pad.h"

int main(int argc, char *argv[]) {
QApplication a(argc, argv);
KeyPad win;
win.show();
return QApplication::exec();
}

个人实现

我自己复现后又给按键加上了图标,但是遇到了点问题,就是不显示图标

原因在 CMakeLists.txt 中,不知道什么原因,明明加上了 qt_standard_project_setup() ,还是编译成功但没显示图片

刚开始一直不知道是这个原因,检查了好久。。。

后来加上下面这个就可以了

1
set(CMAKE_AUTORCC ON)

代码:

点击展开
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#ifndef EX15_MYKEYPAD_KEY_PAD_H
#define EX15_MYKEYPAD_KEY_PAD_H

#include <QWidget>
#include <QGridLayout>
#include <QLcdNumber>
#include <QList>
#include <QPushButton>
#include <QDebug>

QT_BEGIN_NAMESPACE

namespace Ui {
class KeyPad;
}

QT_END_NAMESPACE

struct KeyInfo {
QString text;
int value;
QString imagePath;
};

enum KeyRole {
Num0 = 0, Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9,
DeleteBtn = 11,
ConfirmBtn = 12
};

enum PadMode {
QssMode,
ImageMode
};

class KeyPad : public QWidget {
Q_OBJECT
public:
explicit KeyPad(QWidget *parent = nullptr, PadMode mode = PadMode::QssMode);

~KeyPad() override;
public slots:
void lcdChange();
private:
void init_keypad();
void random_keypad();
void loadStyleSheet();
QLCDNumber *lcdShow;
QWidget *padWidget;
QGridLayout *padLayout;
QList<QPushButton *> m_btns;
QString m_currentInput;
PadMode m_mode;
};

#endif // !EX15_MYKEYPAD_KEY_PAD_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#include "key_pad.h"
#include <QBoxLayout>
#include <QDateTime>
#include <QRandomGenerator>
#include <QFile>
#include <QLatin1StringView>

#define QSS_STYLE_IMAGE ":/styles/style_image.qss"
#define QSS_STYLE_QSS ":/styles/style_qss.qss"

namespace {
const QList<KeyInfo> pads = {
{"1", 1, ":/images/1.png"}, {"2", 2, ":/images/2.png"}, {"3", 3, ":/images/3.png"},
{"4", 4, ":/images/4.png"}, {"5", 5, ":/images/5.png"}, {"6", 6, ":/images/6.png"},
{"7", 7, ":/images/7.png"}, {"8", 8, ":/images/8.png"}, {"9", 9, ":/images/9.png"},
{"⌫", 11, ":/images/del.png"}, {"0", 0, ":/images/0.png"}, {"✓", 12, ":/images/ok.png"}
};
} // namespace

KeyPad::KeyPad(QWidget *parent, PadMode mode) : QWidget(parent), m_mode(mode)
{
// 主窗口
this->setGeometry(50, 100, 240, 400);

// 主布局
auto main_layout = new QVBoxLayout(this);
main_layout->setSpacing(0);
main_layout->setContentsMargins(0, 0, 0, 0);

// 上方 lcd 显示屏
lcdShow = new QLCDNumber(this);
lcdShow->setDigitCount(6);
lcdShow->display("");
lcdShow->setSegmentStyle(QLCDNumber::Flat);
// 显示屏的外容器
auto lcdContainer = new QWidget(this);
lcdContainer->setMinimumSize(240, 80);
lcdContainer->setMaximumSize(240, 80);
// 显示器容器的布局
auto containerLayout = new QVBoxLayout(lcdContainer);
if (m_mode == PadMode::ImageMode) {
// 图片模式
containerLayout->setContentsMargins(15, 15, 15, 5);
} else {
// QSS 模式
containerLayout->setContentsMargins(0, 0, 0, 0);
}
containerLayout->addWidget(lcdShow);

// 下方键盘区
padWidget = new QWidget(this);
padLayout = new QGridLayout(padWidget); // 键盘区网格布局
padLayout->setContentsMargins(0, 0, 0, 0);
padLayout->setSpacing(0);

init_keypad();
loadStyleSheet();
random_keypad();

main_layout->addWidget(lcdContainer);
main_layout->addWidget(padWidget);
main_layout->setStretch(0, 1);
main_layout->setStretch(1, 4);
}

KeyPad::~KeyPad() {}

void KeyPad::loadStyleSheet()
{
QString stylePath = (m_mode == PadMode::ImageMode) ? QSS_STYLE_IMAGE : QSS_STYLE_QSS;
QFile file(stylePath);
if (file.open(QFile::ReadOnly)) {
this->setStyleSheet(QLatin1String(file.readAll()));
file.close();
}
}

void KeyPad::init_keypad()
{
QPushButton *btn;

for (int x = 0; x < 4; ++x) {
for (int y = 0; y < 3; ++y) {
int pos = x * 3 + y;
btn = new QPushButton(padWidget);
btn->setProperty("value", pads[pos].value);
btn->setMinimumSize(80, 80);
btn->setMaximumSize(80, 80);

if (m_mode == 1) {
// 图标模式
btn->setIcon(QIcon(pads[pos].imagePath));
btn->setIconSize(QSize(50, 50));
} else {
// 纯 QSS , 不带图片
btn->setText(pads[pos].text);
if (pads[pos].value >= DeleteBtn) { // 删除/确认 按键
btn->setProperty("btnType", "func");
} else {
btn->setProperty("btnType", "num");
}
}
// 连接信号和槽
connect(btn, &QPushButton::clicked, this, &KeyPad::lcdChange);
m_btns.append(btn);
}
}
}

void KeyPad::random_keypad()
{
// 初始化随机数熵池
auto random = QRandomGenerator::global();
// 随机下标容器
QList<int> indexes;
for (int i = 0; i < pads.size(); ++i) {
if (pads[i].value <= KeyRole::Num9) { // 仅打乱数字键
indexes.append(i);
}
}
// 打乱一下
std::shuffle(indexes.begin(), indexes.end(), *random);

int pos_indes = 0;
for (int x = 0; x < 4; ++x) {
for (int y = 0; y < 3; ++y) {
int pos = x * 3 + y;
QPushButton *btn;
if (pads[pos].value >= KeyRole::DeleteBtn) {
btn = m_btns[pos];
} else {
btn = m_btns[indexes[pos_indes]];
++pos_indes;
}
padLayout->addWidget(btn, x, y, 1, 1);
}
}
}

void KeyPad::lcdChange()
{
// 看看是哪个发的信号
auto btn = qobject_cast<QPushButton *>(sender());
if (btn == nullptr) {
return;
}
int v = btn->property("value").toInt();
if (v == KeyRole::DeleteBtn) { // 删除
if (m_currentInput.isEmpty()) {
return;
}
m_currentInput.chop(1);
lcdShow->display(m_currentInput.isEmpty() ? "" : m_currentInput);
} else if (v == KeyRole::ConfirmBtn) { // 确认:清空一下 lcd 显示
m_currentInput.clear();
lcdShow->display("");
} else {
if (m_currentInput.size() >= 6) {
m_currentInput.clear();
}

m_currentInput.append(QString::number(v));
lcdShow->display(m_currentInput);

if (m_currentInput.size() == 6) { // 正好等于 6 时,随机一下键盘
random_keypad();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
#include <QApplication>
#include <QWidget>
#include "key_pad.h"

int main(int argc, char *argv[]) {
QApplication app(argc, argv);
KeyPad win(nullptr, PadMode::QssMode);

win.show();

return QApplication::exec();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<RCC>
<qresource prefix="/img">
<file>images/num1.png</file>
<file>images/num2.png</file>
<file>images/num3.png</file>
<file>images/num4.png</file>
<file>images/num5.png</file>
<file>images/num6.png</file>
<file>images/num7.png</file>
<file>images/num8.png</file>
<file>images/num9.png</file>
<file>images/delete.png</file>
<file>images/num0.png</file>
<file>images/ok.png</file>
</qresource>
</RCC>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
cmake_minimum_required(VERSION 3.16)
project(ex15_myKeyPad CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_PREFIX_PATH "D:/09_SW/_Dev/Qt/6.5.3/mingw_64")

# target_compile_definitions(ex15_myKeyPad PRIVATE QT_NO_DEBUG_OUTPUT)

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)

find_package(Qt6 REQUIRED COMPONENTS Core Widgets)

qt_standard_project_setup()

qt_add_executable(ex15_myKeyPad
main.cpp
key_pad.cpp
key_pad.h
res.qrc
)

target_link_libraries(ex15_myKeyPad PRIVATE Qt6::Core Qt6::Widgets)

# 设置目标属性
set_target_properties(ex15_myKeyPad PROPERTIES
WIN32_EXECUTABLE OFF # Windows 下:作为图形界面程序运行,隐藏黑色的 CMD 控制台窗口
MACOSX_BUNDLE ON # macOS 下:打包成标准的 .app 应用程序包
)

样式:

点击展开
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/* 主窗口背景 */
KeyPad {
background-color: #282726;
}

/* LCD 显示屏 */
QLCDNumber {
background-color: #1e1e1e;
color: #ebe9eb;
border: none;
border-bottom: 3px solid #f99f2b;
}

/* 功能键 (确认/删除) */
QPushButton[btnType="func"] {
background-color: #f99f2b;
font-family: 'Segoe UI', Arial;
font-size: 32px;
font-weight: bold;
color: #ffffff;
border: 1px solid rgb(44,44,46);
border-radius: 4px;
margin: 1px;
}
QPushButton[btnType="func"]:hover { background-color: #ffb04f; }
QPushButton[btnType="func"]:pressed { background-color: #c67e20; }

/* 数字键 */
QPushButton[btnType="num"] {
background-color: #5e5d5c;
font-family: 'Segoe UI', Arial;
font-size: 28px;
font-weight: bold;
color: #ebe9eb;
border: 1px solid rgb(44,44,46);
border-radius: 4px;
margin: 1px;
}
QPushButton[btnType="num"]:hover { background-color: #6e6d6c; }
QPushButton[btnType="num"]:pressed { background-color: #9f9e9e; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* 主窗口背景 */
KeyPad {
background-color: #f4f7f6;
}

/* LCD 显示屏 */
QLCDNumber {
background-color: #2c2c2e;
color: #ffffff;
border: none;
border-radius: 12px;
}

/* 图片模式下的所有按钮 */
QPushButton {
background: transparent;
border: none;
}
QPushButton:pressed {
background-color: rgba(0, 0, 0, 30); /* 浅灰底色下,按下用半透明黑色更好看 */
border-radius: 10px;
}

可选按键装饰

提供了两种按键装饰:图标和纯QSS

且分离了QSS代码到静态文件,通过文件读取来适配QSS样式,方便后续新增皮肤

style 下,style_qss.qssstyle_image.qss

1
2
3
4
5
6
7
8
9
10
11
12
#define QSS_STYLE_IMAGE ":/styles/style_image.qss"
#define QSS_STYLE_QSS ":/styles/style_qss.qss"

void KeyPad::loadStyleSheet()
{
QString stylePath = (m_mode == PadMode::ImageMode) ? QSS_STYLE_IMAGE : QSS_STYLE_QSS;
QFile file(stylePath);
if (file.open(QFile::ReadOnly)) {
this->setStyleSheet(QLatin1String(file.readAll()));
file.close();
}
}

代码结构优化

key_pad.cpp 中,静态的字符串定义在匿名命名空间中

通过枚举明确值的实际意义

构造函数新增默认形参,保证可选样式

已发布 Github: abinng/Qt-Modern-Keypad: 个人学习Qt时的做的一个小Demo