目錄
一,Label
1.1 主要屬性
1.2 文本格式
1.3 設置圖片
1.4 其它常用屬性
1.5?設置伙伴
二,LCD Number
2.1 主要屬性
2.2 實現倒計時
?2.3?兩個問題
三,ProgressBar
3.1 主要屬性
3.2 進度條按時間增長
3.3 改變樣式
3.4?一個問題
四,Calendar Widget
4.1 主要屬性
4.2 獲取選中的日期?
一,Label
1.1 主要屬性
QLabel 可以用來顯示文本或圖片,主要屬性如下:
屬性 | 說明 |
---|---|
text | QLabel 中的文本 |
textFormat | 文本的格式:
|
pixmap | Qlabel 內部包含的圖片 |
scaledContents | 設為 true表示圖片自動拉伸填充滿 QLabel,false就不會 |
alignment | 對齊方式,可設置水平和垂直方向如何對齊 |
wordWrap | 設為 true 內部文本會自動換行,false 則不會 QLabel 不會提供滾動條,別的控件才有,比如 QtextEdit(多行編輯框) |
indent | 設置文本縮進,水平和垂直方向都生效 |
margin | 內部文本和邊框之間的邊距 |
openExternalLinks | 是否允許打開一個外部的鏈接(QLabel 文本內容包含 URL 時涉及) |
buddy | 給 QLabel 關聯?個"伙伴",這樣點擊QLabel時就能激活對應的伙伴 例如伙伴如果是?個QCheckBox,那么該QCheckBox就會被選中 |
?下面我們來搞幾個具體的例子,演示下上表格的部分屬性
1.2 文本格式
先創建三個label用于展示不同的格式,然后可以在構造函數里設置不同文本格式:
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);ui->label->setTextFormat(Qt::PlainText);ui->label->setText("這是一段純?本");ui->label_2->setTextFormat(Qt::RichText);ui->label_2->setText("<b> 這是一段富?本 </b>"); //b標簽表示加粗ui->label_3->setTextFormat(Qt::MarkdownText);ui->label_3->setText("## 這是一段 markdown ?本"); // ##表示二級標題
}
?
1.3 設置圖片
首先用 qrc 準備一張圖片,然后就可以對 Label 設置圖片:
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//先把整個 Label 鋪滿窗口,然后讓 Label 的左上角設置到窗口的左上角ui->label->setGeometry(0, 0, this->width(), this->height());QPixmap pixmap(":/deepseek.png");ui->label->setPixmap(pixmap);ui->label->setScaledContents(true); //有時候圖片尺寸會小于窗口,那么這個就可以讓圖片拉伸
}
但是上面我們對 Label 尺寸的設置是“一次性”的,就上面的程序而言,只要我們擴大或縮小窗口大小,里面的 Label控件大小是不會變的,所以下面我們讓 Label 的大小隨著窗口大小實時發生改變
原理:
- 用戶的絕大部分操作,會對應一些事件
- 但 Qt 中,除了信號,還有一個“事件”也用來表示用戶的操作
- 鼠標拖拽窗口大小時,會讓 Qt 觸發一個 resize 事件(resizeEvent),而且我們改變窗口尺寸的過程中,會觸發一系列的 resize事件
- 此時就可以借助 resizeEvent 來完成功能,重寫父類 QWidget 的 resizeEvent虛函數,而且在鼠標拖動窗口尺寸過程中,會持續調用這個虛函數進而調用子類的函數
先在頭文件里聲明函數:
然后實現該函數即可:
//此處的形參 event 包含了觸發 resize 事件時,窗口尺寸的數值
void Widget::resizeEvent(QResizeEvent *event)
{//qDebug() << event->size();ui->label->setGeometry(0, 0, event->size().width(), event->size().height());
}
?效果如下:
1.4 其它常用屬性
先創建幾個帶邊框的 Label,如下圖:
?然后在構造函數中,給這幾個 label 設置不同的屬性:
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{//①設置對齊方式ui->setupUi(this);ui->label->setText("這是一段文本");ui->label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); //設置文本對齊方式,參數也是枚舉,這兩個表示垂直和水平居中,就是顯示在框框中間//水平方向有靠左、中、右三種,垂直方向有靠上、中、下三種,一般用兩個組合使用//②設置自動換行ui->label_2->setText("Tcp全稱“傳輸控制協議(Transmission Control Protocol)”,是當今互聯網使用最廣泛的傳輸層協議,因為它基于通信時保證可靠性,并且對于高效傳輸也有一定策略,是目前應用層底層使用的非常常見的網絡協議");ui->label_2->setWordWrap(true); //表示開啟自動換行//③設置縮進ui->label_3->setText("這是另一段文本");ui->label_3->setIndent(50); //表示在顯示在程序上時給字體前面加上多少像素的空白//如果是label_2那樣的長文本的話,會給所有的行都添加縮進//④設置邊距ui->label_4->setText("Tcp的三次握手是驗證雙方通信信道的最小次數,能夠快速建立連接;并且奇數次握手,可以確保一般情況下握手失敗的連接成本是嫁接在客戶端的,能保證服務器本身的穩定性");ui->label_4->setWordWrap(true); //開啟自動換行ui->label_4->setMargin(50); //設置邊距,表示文本內容的上下左右四個方向都要留出部分像素的空白
}
效果如下:
1.5?設置伙伴
這個和 HTML 前端中一樣的,有時候按鈕旁邊會有一些字,但是為了方便用戶點擊,一般用戶直接點擊按鈕的旁邊的文字也可以選中按鈕,所以這個設置伙伴就是將文本和按鈕“綁定”
先創建兩個單選框和兩個文本框:
關于 QLabel 快捷鍵:
- Qt 中 Label 的快捷鍵是在文本中使用符號 ‘ & ’ 跟上一個字符來表示快捷鍵
- 比如 &A ,然后需要通過鍵盤上的 Alt + a 才能觸發快捷鍵
- 綁定了伙伴關系之后,就可以通過快捷鍵選中對應的單選或復選按鈕了
- 但是這個快捷鍵沒有我們前面QPushButton的功能那么強大,所以了解即可
然后就是通過代碼將下面的文本框添加“伙伴”,如下代碼:
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//設置 label 和 radioButton 的伙伴關系ui->label->setBuddy(ui->radioButton);ui->label_2->setBuddy(ui->radioButton_2);
}
然后我們就可以通過 Alt + A 或 Alt + B 的快捷鍵方式來選中按鈕了:
二,LCD Number
2.1 主要屬性
QLCDNumber 是一個專門用來顯示數字的控件,類似于“老式計算器”的效果,主要屬性如下:
屬性 | 說明 |
---|---|
intValue | 顯示的數字值(int) |
value | 顯示的數字值(double)
|
digitCount | 顯示幾位數字 |
mode | 數字顯示形式
只有十進制才能顯示小數點后的內容 |
segmentStyle | 設置顯示風格
|
smallDecimalPoint | 設置比正常大小還小的小數點 |
下面通過例子來演示上述效果:
2.2 實現倒計時
我們先使用 QLCDNumber 顯示一個初始的數值,比如4,然后程序啟動后,每過一秒數字就 -1,直到 0 就結束
先創建一個LCD,如下:
然后我們現在的關鍵點就是要實現“每秒鐘 -1” 這個效果,也就是周期性的執行某個邏輯
關于“定時器”功能
- C++ 標準庫里沒有提供定時器的相關接口(Boost庫里面有)
- 但是Qt 中是封裝了對應的定時器的,涉及到的類叫做 QTimer,創建出來的對象會產生 timeout 這樣的信號
- 我們可以通過 start 方法來開啟定時器,并在參數中設定觸發 timeout 信號的周期?
- 然后結合 connect ,把這個 timeout 信號綁定到需要的槽函數中,就可以執行邏輯,修改 LCDNumber 中的數字了
我們先在頭文件添加 定時器對象和槽函數的聲明:
然后就是倒計時的邏輯了:
#include "widget.h"
#include "ui_widget.h"
#include<QTimer>
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);ui->lcdNumber->display(4); //設置一個初始值timer = new QTimer(this);connect(timer, &QTimer::timeout, this, &Widget::handle);//將 QTimer 的 timeout 信號和我們自己創建的槽函數進行關聯timer->start(1000); //啟動定時器,單位ms,1000表示每隔一秒觸發一次信號
}Widget::~Widget()
{delete ui;
}void Widget::handle()
{int value = ui->lcdNumber->intValue();if(value <= 0){timer->stop();return;}ui->lcdNumber->display(value - 1);
}
效果如下:
2.3?兩個問題
問題一:如果我不用計時器,直接在 Widget 的構造函數里,通過 “循環 + sleep(1)” 的方式是否可以呢
例如下列代碼:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{ui->setupUi(this);int value = ui->lcdNumber->intValue();while (true) {std::this_thread::sleep_for(std::chrono::seconds(1));if (value <= 0) break;ui->lcdNumber->display(value - 1);}
}
這個方法直接叉掉,在構造函數里搞循環,會導致構造函數無法退出,此時界面就無法顯示任何控件
問題二:那么我是否可以另外創建一個線程,在新線程里完成 循環 + sleep(1) 可以嗎?
如下代碼:
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//創建線程t,并添加lamdba線程函數std::thread t([this]() {int value = this->ui->lcdNumber->intValue();while (true) {std::this_thread::sleep_for(std::chrono::seconds(1));if (value <= 0) break;this->ui->lcdNumber->display(value - 1);}});
}
這個也是不行的,原因如下:
- Qt 的界面只能由主線程去負責維護和更新的,并且 Qt 為了保證修改頁面過程中不受影響,禁止了其他線程直接修改頁面
- 因為這種情況如果不加鎖,不維護好線程安全,就可能有多個線程同時在修改頁面,很容易導致頁面顯示混亂,這是無法容忍的
- 所以Qt 為了主界面線程安全,直接要求所有對界面的修改操作,必須在主線程中完成
- 槽函數就是由主線程調用的,所以可以修改
簡單來說,界面就相當于共享資源,每個線程訪問簽都必須先加鎖,但是 Qt 規定只能由主線程一個線程去修改?
解決辦法也有,就是創建線程后,線程只負責每秒發 timeout 信號給槽函數再去修改,但這屬于多此一舉, 所以暫時不考慮?
三,ProgressBar
3.1 主要屬性
QProgressBar 控件表示一個進度條,就是我們安裝程序時界面顯示的那個條,主要屬性如下:
屬性 | 說明 |
---|---|
minimum | 進度條最小值 |
maximum | 進度條最大值 |
value | 京都條當前值 |
alignment | 文本在進度條中的對齊方式
|
textVisible | 進度條數字是否可見 |
orientation | 進度條的方向是水平還是垂直 |
invertAppearance | 是否朝反方向增長進度 |
textDirection | 文本的朝向 |
format | 展示的數字格式
|
3.2 進度條按時間增長
我們之前是寫過一個命令行版本的進度條的,可以參考:Linux實現簡單進度條-CSDN博客
先創建一個進度條,如下圖:
我們的期望是每隔100毫秒進度條就增加1,所以我們仍然可以通過定時器來實現周期行為
代碼如下:
#include "widget.h"
#include "ui_widget.h"
#include<QTimer>
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);timer = new QTimer(this);connect(timer, &QTimer::timeout, this, &Widget::handle);timer->start(20); //啟動計時器
}Widget::~Widget()
{delete ui;
}void Widget::handle()
{int value = ui->progressBar->value(); //獲取進度條當前數值if(value >= 100){timer->stop();return;}ui->progressBar->setValue(value + 1);
}
步驟和前面的倒計時很像,都是計時器發信號然后觸發槽函數然后修改,效果如下:
?在實際開發中,進度條的取值,往往是根據當前任務的實際進度來進行設置的
3.3 改變樣式
QProGressBar 也是繼承自 QWidget 的,所以也可以使用 styleSheet等方式來改變進度條的顏色等外表
假如我們要把上面的進度條顏色改為“紅色”,如下:
?可以添加下列樣式:
QProgressBar::chunk { background-color: red; }
上面兩個冒號是“選擇器”,對于什么是選擇器,可以參考:前端學習(2)—— CSS詳解與使用-CSDN博客?
?效果如下:
但是我們發現,我們的 100% 的數字跑到了左上角,這個忙猜是 Qt 的bug,畢竟我們沒有改變其他的任何地方,所以我們只能先使用 alignment 來手動調整下,如下圖:
效果如下:
3.4?一個問題
問題:我們上面的 Widget.h 頭文件,添加QTimer* timer; 聲明時,明明沒有包含 對應頭文件,為什么不會提示“QTimer 找不到定義”之類的呢?
如下圖:
原因如下:
- 在 Qt 中,有一個特殊的頭文件,這個頭文件里包含了 Qt 中所有類的“前置聲明”,這個頭文件一般不會直接接觸到,但是包含其他的 Qt 的頭文件,都會間接包含到這個文件
- 比如上面的 Widget 類的前面已經提供了 QTimer 類的聲明的話,此時就可以直接在 Widget 中使用 QTimer 的指針/引用類型的成員(這個是 C++ 中的特殊技巧,Qt 就把它充分發揮了)
追加問題:Qt 為什么要使用上面的技巧呢?
解答:
- C/C++ 的代碼,編譯速度在其他語言的橫向對比中,其實是非常慢的,這個 #include 頭文件有很大關系
- 因為C/C++代碼在編譯期間,是直接把頭文件展開,然后替換掉原來包含頭文件的位置,相當于復制拷貝,而一個頭文件會間接包含其他頭文件,然后其他頭文件也會包含其他頭文件,這樣一下來,就會造成很多不必要的頭文件展開
- 因此,盡可能減少 include 頭文件的個數,就可以有效減少編譯事件,Qt 就使用 class 前置聲明的方式,盡量減少頭文件的包含
- 但是實際開發中,該包含就該包含,也可以引入更好的硬件資源來更高效的編譯,一些互聯網大腸,都有專門的“編譯集群”(分布式編譯),專門用來編譯
四,Calendar Widget
4.1 主要屬性
QCalendarWidget 表示一個“日歷”,主要屬性如下:
屬性 | 說明 |
---|---|
selectDate | 當前選中的日期 |
minimumDate | 最小日期 |
maximumDate | 最大日期 |
firstDayOfWeek | 每周的第一天是周幾(日歷的第一列) |
gridVisible | 是否顯示表格的邊框 |
selectionMode | 是否允許選擇日期 |
navigationBarVisible | 日歷上方標題是否顯示 |
horizontalHeaderFormat | 日歷上方標題顯示的日期格式 |
verticalHeaderFormat | 日歷第一列顯示的內容格式 |
dateEditEnabled | 是否允許日期被編輯 |
也提供了一些信號,如下:
信號 | 說明 |
---|---|
selectionChanged(const QDate&) | 當選中的日期發生改變時發出 |
activated(const QDate&) | 當雙擊一個有效的日期或按下回車鍵時發出,形參是一個QDate類型,保存了選中的日期 |
currentPageChanged(int, int) | 當年份月份改變時發出,形參表示改變后的新年份個月份 |
4.2 獲取選中的日期
先創建一個 label 和一個日歷,然后右鍵轉到槽,選擇 selectionChange():
編寫如下槽函數:
void Widget::on_calendarWidget_selectionChanged()
{QDate date = ui->calendarWidget->selectedDate();QString ret = "選擇的日期是:";ret += date.toString();ui->label->setText(ret);
}
效果如下: