在創建項目時,使用的基類Base Class
為QWidget
1. 使用圖形化界面的方式實現“Hello World”
- 雙擊文件:
widget.ui
,進入designer
模式:
-
在“控件盒子”的“Display Widgets”中找到“Label”,并拖放到白板中
-
雙擊剛剛拖放到
Label
,輸入"Hello World"
-
最后直接運行即可:
此時我們回過頭來看widget.ui
和編譯生成的ui_widget.h
文件,都發生了變化:
// widget.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"><class>Widget</class><widget class="QWidget" name="Widget"><property name="geometry"><rect><x>0</x><y>0</y><width>800</width><height>600</height></rect></property><property name="windowTitle"><string>Widget</string></property><widget class="QLabel" name="label"><property name="geometry"><rect><x>230</x><y>110</y><width>81</width><height>51</height></rect></property><property name="text"><string>"Hello World"</string></property></widget></widget><resources/><connections/>
</ui>// ui_widget.h
/********************************************************************************
** Form generated from reading UI file 'widget.ui'
**
** Created by: Qt User Interface Compiler version 6.7.3
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/#ifndef UI_WIDGET_H
#define UI_WIDGET_H#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QLabel>
#include <QtWidgets/QWidget>QT_BEGIN_NAMESPACEclass Ui_Widget
{
public:QLabel *label;void setupUi(QWidget *Widget){if (Widget->objectName().isEmpty())Widget->setObjectName("Widget");Widget->resize(800, 600);label = new QLabel(Widget);label->setObjectName("label");label->setGeometry(QRect(230, 110, 81, 51));retranslateUi(Widget);QMetaObject::connectSlotsByName(Widget);} // setupUivoid retranslateUi(QWidget *Widget){Widget->setWindowTitle(QCoreApplication::translate("Widget", "Widget", nullptr));label->setText(QCoreApplication::translate("Widget", "\"Hello World\"", nullptr));} // retranslateUi};namespace Ui {class Widget: public Ui_Widget {};
} // namespace UiQT_END_NAMESPACE#endif // UI_WIDGET_H
2. 用代碼的方式實現“Hello World”
一般來說,在實現圖形化界面時,一般在繼承類的構造函數中進行編寫,如這里的widget.h
實現如下:
#include "widget.h"
#include "./ui_widget.h"
#include <QLabel>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);QLabel* label = new QLabel(this);label->setText("\"Hello World\"");
}Widget::~Widget()
{delete ui;
}
-
Qt中每個類都有其同名的頭文件,要使用類
QLabel
,就需要包含頭文件<QLabel>
-
在定義
QLabel
對象時,一般建議在堆上創建,同時建議將這個對象掛到對象樹
上,其父節點就是this
-
方法
label->setText
的形參實際上是類型QString
- Qt的誕生時間比C++標準形成的時間要早,因此,對于一些常見的數據結構,Qt自己造了一些輪子
- 例如:
QString
、QVector
、QList
、QMap
等 - C++標準庫中的容器也可以很方便的和Qt中的容器類相互轉換
最后運行結果如圖:
內存泄露問題
可以注意到,為了使用QLable
對象,我們只用了new
語句:QLabel* label = new QLabel(this);
我們在后面沒有delete
掉,不就內存泄露了嗎?
這里可以告訴大家結論:
- 上述代碼在Qt中,不會產生內存泄露, 及在合適的時候 **,**?
label
會被自動釋放 - 而原因就是,??
label
??對象被掛到對象樹上了
對象樹
引入對象樹的主要目的,就是將諸如QLable
這樣的空間進行統一管理,這樣就可以統一在合適的時候進行釋放
- 所謂合適的時候,就是圖形界面窗口關閉/銷毀的時候
- 而如果不適用對象樹,就可能導致一些資源的提前釋放,具體到圖形化界面中就可能導致一些控件元素不會正常顯示
- 在上面的代碼中,將
QLabel
對象開辟在堆空間上,就是為了將其生命周期交給對象樹進行管理
而如果開辟在棧上,就可能引發問題:
#include "widget.h"
#include "./ui_widget.h"
#include <QLabel>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);// QLabel* label = new QLabel(this);// label->setText("\"Hello World\"");QLabel label(this);label.setText("\"Hello World\"");
}Widget::~Widget()
{delete ui;
}
運行:
發現預期的"Hello World"
不見了
為了驗證如果一個對象被掛到對象樹上,會被自動清理,我么可以新建一個類myQLabel
,其繼承于QLabel
- 新建文件
-
選擇C++中的
C++ class
-
填寫自定義類的信息
代碼如下:
// myQLabel.h
#ifndef MYQLABEL_H
#define MYQLABEL_H#include <QLabel>class myQLabel : public QLabel
{
public:myQLabel(QWidget* parent);~myQLabel();
};#endif // MYQLABEL_H// myQlabel.cpp
#include "myqlabel.h"#include <iostream>myQLabel::myQLabel(QWidget* parent): QLabel(parent) {}myQLabel::~myQLabel()
{std::cout << "MyQLabel 被釋放" << std::endl;
}// widget.cpp
#include "widget.h"
#include "./ui_widget.h"
// #include <QLabel>
#include "myqlabel.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);myQLabel* label = new myQLabel(this);label->setText("\"Hello World\"");
}Widget::~Widget()
{delete ui;
}
可以看到,在關閉圖形化窗口時,會輸出:
亂碼問題
從上面的輸出我們可以看到,本來應該被打印的漢字“被釋放”,輸出竟然是亂碼,這是為什么?
首先要說明結論:亂碼之所以出現,其最主要的原因就是編碼方式不一致
- 源代碼文件的編碼格式
- 終端(控制臺)的顯示編碼格式
- 字符串本身的編碼處理方式
接下來,我們同樣用該清楚,一個漢字到底占幾個字節?
- 對于這個問題,我們首先要區分漢字是采用哪種編碼方式進行編碼的——GBK,UTF-8
- 如果采用GBK方式編碼,那一個漢字就占2個字節
- 如果采用UTF-8方式編碼,那一個漢字就占2~4個字節,一般為3字節
我們先來查看輸出漢字的文件myqlable.cpp
是采用哪種方式進行編碼:
- 可以看到,編碼方式為UTF-8
- 而如果顯示為
ANSI
,那么編碼方式就是GBK
既然myqlabel.cpp
的編碼方式為utf8,但輸出到控制臺卻出現亂碼,說明終端控制臺的編碼方式就不是UTF-8
qDebug
為了解決這一問題,我們不是用C++標準的標準輸出std::cout
,而采用Qt提供的QDebug
類:
#include "myqlabel.h"#include <QDebug>myQLabel::myQLabel(QWidget* parent): QLabel(parent) {}myQLabel::~myQLabel()
{qDebug() << "MyQLabel 被釋放";
}
QDebug
這個類重載了移位運算符<<
,不直接使用類QDebug
;而qdebug
是一個宏,封裝了QDebug
對象,我們可以像使用std::cout
一樣來使用它- 使用
qDebug()
還有另一個好處,我們可以使用開關來對日志輸出進行關閉, 防止日志信息對用戶的干擾
3. 使用輸入框實現 “Hello World”
3.1 使用圖形化界面的方式
-
雙擊文件
widget.ui
,進入designer
模式后找到控件:Line Edit
-
將其拖放到白板中,雙擊控件,輸入
"Hello World"
-
保存并運行即可
3.2 使用代碼的方式
和QLabel
的操作方式一樣,將widget.cpp
文件修改如下:
#include "widget.h"
#include "./ui_widget.h"#include <QLineEdit>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);QLineEdit* line_edit = new QLineEdit(this);line_edit->setText("\"Hello World\"");
}Widget::~Widget()
{delete ui;
}
運行:
4. 使用按鈕的方式實現“Hello World”
4.1 使用圖形化界面的方式
- 雙擊文件
widget.ui
,進入designer
模式后找到控件:Push Button
-
將其拖放到白板中,雙擊控件,輸入
"Hello World"
同時可以看到,在
QObject
中出現了objectName
和對應的值:-
在
Qt Designer
創建一個控件的時候,此時會給這個控件分配一個objectName
屬性,這個屬性的值要求在界面中是唯一的。我么也可以自己指定這個值 -
當
CMake
在預處理.ui
文件的時候,就會根據objectName
來生成同名的類。例如上面的控件pushButton
被生成的類名就是myPushButton
-
我們可以在生成的
ui_widget.h
中看到這樣的代碼:
-
-
進入編輯界面,修改
widget.cpp
如下:
// widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void clickHandler();private:Ui::Widget *ui;
};
#endif // WIDGET_H//widget.cpp
#include "widget.h"
#include "./ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);connect(ui->myPushButton, &QPushButton::clicked, this, &Widget::clickHandler);
}Widget::~Widget()
{delete ui;
}// 當控件內容為“Hello World”,點擊按鈕后,就會變為“Hello Qt”
// 當空間內容為“Hello Qt”,點擊按鈕后,就會變為“Hello World“
void Widget::clickHandler()
{if (ui->myPushButton->text() == "\"Hello World\"") {ui->myPushButton->setText("\"Hello Qt\"");} else {ui->myPushButton->setText("\"Hello World\"");}
}
上述出現的connect
是類QObject
提供的靜態函數,其功能是連接“信號”和“槽”,其4個參數的作用分別為:
4.2 使用代碼的方式
代碼如下:
// widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QPushButton>QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void clickHandler();private:Ui::Widget *ui;QPushButton* myPushButton_ = nullptr;
};
#endif // WIDGET_H// widget.cpp
#include "widget.h"
#include "./ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget), myPushButton_(new QPushButton(this))
{ui->setupUi(this);myPushButton_->setText("\"Hello World\"");connect(myPushButton_, &QPushButton::clicked, this, &Widget::clickHandler);
}Widget::~Widget()
{delete ui;
}void Widget::clickHandler()
{if (myPushButton_->text() == "\"Hello World\"") {myPushButton_->setText("\"Hello Qt\"");} else {myPushButton_->setText("\"Hello World\"");}
}
5. 圖形化方式開發和代碼方式開發
實際開發中,圖形化方式開發界面和以純代碼方式代碼界面都是很常用的方法
- 如果界面內容是比較固定的,此時就會使用圖形化界面來構造
- 如果界面內容經常要動態變化,就需要以代碼的方式進行開發
- 此外,這兩種方式也可以配合使用
6. Qt 坐標系
Qt的坐標系是 平面直角坐標系,其X軸從左往右增長,Y軸從上往下增長
坐標系的原點就是屏幕的左上角/窗口的左上角
給Qt的控件指定位置,就需要設置坐標,對于這個控件來說,其原點是相對于父窗口/父控件的
而如果要指定控件或窗口的位置,就需要用到對應的move
成員函數:
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget), myPushButton_(new QPushButton(this))
{ui->setupUi(this);myPushButton_->setText("\"Hello World\"");myPushButton_->move(200, 300);this->move(100, 200);
}
- move的第一個參數為水平移動距離,單位為像素px
- move的第二個參數為豎直移動距離,單位為像素px
運行如圖: