Qt入門(19)——自定義視窗部件
我們介紹可以畫自己的第一個自定義視窗部件。我們也加入了一個有用的鍵盤介面。
我們新增了一個槽:setRange()。
void setRange( int minVal, int maxVal );
現在我們新增了設定LCDRange範圍的可能性。直到現在,它就可以被設定為0~99。
在建構函式中有一個變化。
setRange()設定了LCDRange中滑塊的範圍。因為我們已經把QLCDNumber設定為只顯示兩位數字了,我們想通過限制minVal和maxVal為0~99來避免QLCDNumber的溢位。(我們可以允許最小值為-9,但是我們沒有那樣做。)如果引數是非法的,我們使用Qt的qWarning()函式來向使用者發出警告並立即返回。qWarning()是一個像printf一樣的函式,預設情況下它的輸出傳送到stderr。如果你想改變的話,你可以使用::qInstallMsgHandler()函式安裝自己的處理函式。
CanonField是一個知道如何顯示自己的新的自定義視窗部件。
目前,CanonField只包含一個角度值,我們使用了LCDRange中同樣的方式。
這是我們在QWidget中遇到的許多事件處理器中的第二個。只要一個視窗部件需要重新整理它自己(比如,畫視窗部件表面),這個虛擬函式就會被Qt呼叫。
我們又一次使用和前一章中的LCDRange同樣的方式。
建構函式把角度值初始化為45度並且給這個視窗部件設定了一個自定義調色盤。
這個調色盤只是說明背景色,並選擇了其它合適的顏色。(對於這個視窗部件,只有背景色和文字顏色是要用到的。)
這個函式設定角度值。我們選擇了一個5~70的合法範圍,並根據這個範圍來調節給定的degrees的值。當新的角度值超過了範圍,我們選擇了不使用警告。
如果新的角度值和舊的一樣,我們立即返回。這隻對當角度值真的發生變化時,發射angleChanged()訊號有重要意義。
然後我們設定新的角度值並重新畫我們的視窗部件。QWidget::repaint()函式清空視窗部件(通常用背景色來充滿)並向視窗部件發出一個繪畫事件。這樣的結構就是呼叫視窗部件的繪畫事件函式一次。
最後,我們發射angleChanged()訊號來告訴外面的世界,角度值發生了變化。emit關鍵字只是Qt中的關鍵字,而不是標準C++的語法。實際上,它只是一個巨集。
這是我們第一次試圖寫一個繪畫事件處理程式。這個事件引數包含一個繪畫事件的描述。QPaintEvent包含一個必須被重新整理的視窗部件的區域。現在,我們比較懶惰,並且只是畫每一件事。
我們的程式碼在一個固定位置顯示視窗部件的角度值。首先我們建立一個含有一些文字和角度值的QString,然後我們建立一個操作這個視窗部件的QPainter並使用它來畫這個字串。我們一會兒會回到QPainter,它可以做很多事。
#include "cannon.h"我們包含了我們的新類:
這一次我們在頂層視窗部件中只使用了一個LCDRange和一個CanonField。
LCDRange *angle = new LCDRange( this, "angle" );
在建構函式中,我們建立並設定了我們的LCDRange。
angle->setRange( 5, 70 );
我們設定LCDRange能夠接受的範圍是5~70度。
我們建立了我們的CannonField。
這裡我們把LCDRange的valueChanged()訊號和CannonField的setAngle()槽連線起來了。只要使用者操作LCDRange,就會重新整理CannonField的角度值。我們也把它反過來連線了,這樣CannonField中角度的變化就可以重新整理LCDRange的值。在我們的例子中,我們從來沒有直接改變CannonField的角度,但是通過我們的最後一個connect()我們就可以確保沒有任何變化可以改變這兩個值之間的同步關係。
這說明了元件程式設計和正確封裝的能力。
注意只有當角度確實發生變化時,才發射angleChanged()是多麼的重要。如果LCDRange和CanonField都省略了這個檢查,這個程式就會因為第一次數值變化而進入到一個無限迴圈當中。
QGridLayout *grid = new QGridLayout( this, 2, 2, 10 );
//2×2,10畫素的邊界
到現在為止,我們沒有因為幾何管理把QVBox和QGrid視窗部件整合到一起。現在,無論如何,我們需要對我們的佈局加一些控制,所以我們使用了更加強大的QGridLayout類。QGridLayout不是一個視窗部件,它是一個可以管理任何視窗部件作為子物件的不同的類。
就像註釋中所說的,我們建立了一個以10畫素為邊界的2*2的陣列。(QGridLayout的建構函式有一點神祕,所以最好在這裡加入一些註釋。)
grid->addWidget( quit, 0, 0 );
我們在網格的左上的單元格中加入一個Quit按鈕:0,0。
grid->addWidget( angle, 1, 0, Qt::AlignTop );
我們把angle這個LCDRange放到左下的單元格,在單元格內向上對齊。(這只是QGridLayout所允許的一種對齊方式,而QGrid不允許。)
grid->addWidget( cannonField, 1, 1 );
我們把CannonField物件放到右下的單元格。(右上的單元格是空的。)
grid->setColStretch( 1, 10 );
我們告訴QGridLayout右邊的列(列1)是可拉伸的。因為左邊的列不是(它的拉伸因數是0,這是預設值),QGridLayout就會在MyWidget被重新定義大小的時候試圖讓左面的視窗部件大小不變,而重新定義CannonField的大小。
angle->setValue( 60 );
我們設定了一個初始角度值。注意這將會引發從LCDRange到CannonField的連線。
angle->setFocus();
我們剛才做的是設定angle獲得鍵盤焦點,這樣預設情況下鍵盤輸入會到達LCDRange視窗部件。
LCDRange沒有包含任何keyPressEvent(),所以這看起來不太可能有用。無論如何,它的建構函式中有了新的一行:
setFocusProxy( slider );
LCDRange設定滑塊作為它的焦點代理。這就是說當程式或者使用者想要給LCDRange一個鍵盤焦點,滑塊就會就會注意到它。QSlider有一個相當好的鍵盤介面,所以就會出現我們給LCDRange新增的這一行。
鍵盤現在可以做一些事了——方向鍵、Home、End、PageUp和PageDown都可以作一些事情。
當滑塊被操作,CannonFiled會顯示新的角度值。如果重新定義大小,CannonField會得到儘可能多的空間。
我們新增了一個槽:setRange()。
void setRange( int minVal, int maxVal );
現在我們新增了設定LCDRange範圍的可能性。直到現在,它就可以被設定為0~99。
在建構函式中有一個變化。
void LCDRange::setRange( int minVal, int maxVal )
{
if ( minVal < 0 || maxVal > 99 || minVal > maxVal ) {
qWarning( "LCDRange::setRange(%d,%d)\n"
"\tRange must be 0..99\n"
"\tand minVal must not be greater than maxVal",
minVal, maxVal );
return;
}
slider->setRange( minVal, maxVal );
}
setRange()設定了LCDRange中滑塊的範圍。因為我們已經把QLCDNumber設定為只顯示兩位數字了,我們想通過限制minVal和maxVal為0~99來避免QLCDNumber的溢位。(我們可以允許最小值為-9,但是我們沒有那樣做。)如果引數是非法的,我們使用Qt的qWarning()函式來向使用者發出警告並立即返回。qWarning()是一個像printf一樣的函式,預設情況下它的輸出傳送到stderr。如果你想改變的話,你可以使用::qInstallMsgHandler()函式安裝自己的處理函式。
CanonField是一個知道如何顯示自己的新的自定義視窗部件。
class CannonField : public QWidget
{
Q_OBJECT
public:
CannonField( QWidget *parent=0, const char *name=0 );
CanonField繼承了QWidget,我們使用了LCDRange中同樣的方式。
int angle() const { return ang; }
QSizePolicy sizePolicy() const;
public slots:
void setAngle( int degrees );
signals:
void angleChanged( int );
目前,CanonField只包含一個角度值,我們使用了LCDRange中同樣的方式。
protected:
void paintEvent( QPaintEvent * );
這是我們在QWidget中遇到的許多事件處理器中的第二個。只要一個視窗部件需要重新整理它自己(比如,畫視窗部件表面),這個虛擬函式就會被Qt呼叫。
CannonField::CannonField( QWidget *parent, const char *name )
: QWidget( parent, name )
{
我們又一次使用和前一章中的LCDRange同樣的方式。
ang = 45;
setPalette( QPalette( QColor( 250, 250, 200) ) );
}
建構函式把角度值初始化為45度並且給這個視窗部件設定了一個自定義調色盤。
這個調色盤只是說明背景色,並選擇了其它合適的顏色。(對於這個視窗部件,只有背景色和文字顏色是要用到的。)
void CannonField::setAngle( int degrees )
{
if ( degrees < 5 )
degrees = 5;
if ( degrees > 70 )
degrees = 70;
if ( ang == degrees )
return;
ang = degrees;
repaint();
emit angleChanged( ang );
}
這個函式設定角度值。我們選擇了一個5~70的合法範圍,並根據這個範圍來調節給定的degrees的值。當新的角度值超過了範圍,我們選擇了不使用警告。
如果新的角度值和舊的一樣,我們立即返回。這隻對當角度值真的發生變化時,發射angleChanged()訊號有重要意義。
然後我們設定新的角度值並重新畫我們的視窗部件。QWidget::repaint()函式清空視窗部件(通常用背景色來充滿)並向視窗部件發出一個繪畫事件。這樣的結構就是呼叫視窗部件的繪畫事件函式一次。
最後,我們發射angleChanged()訊號來告訴外面的世界,角度值發生了變化。emit關鍵字只是Qt中的關鍵字,而不是標準C++的語法。實際上,它只是一個巨集。
void CannonField::paintEvent( QPaintEvent * )
{
QString s = "Angle = " + QString::number( ang );
QPainter p( this );
p.drawText( 200, 200, s );
}
這是我們第一次試圖寫一個繪畫事件處理程式。這個事件引數包含一個繪畫事件的描述。QPaintEvent包含一個必須被重新整理的視窗部件的區域。現在,我們比較懶惰,並且只是畫每一件事。
我們的程式碼在一個固定位置顯示視窗部件的角度值。首先我們建立一個含有一些文字和角度值的QString,然後我們建立一個操作這個視窗部件的QPainter並使用它來畫這個字串。我們一會兒會回到QPainter,它可以做很多事。
#include "cannon.h"我們包含了我們的新類:
class MyWidget: public QWidget
{
public:
MyWidget( QWidget *parent=0, const char *name=0 );
};
這一次我們在頂層視窗部件中只使用了一個LCDRange和一個CanonField。
LCDRange *angle = new LCDRange( this, "angle" );
在建構函式中,我們建立並設定了我們的LCDRange。
angle->setRange( 5, 70 );
我們設定LCDRange能夠接受的範圍是5~70度。
CannonField *cannonField
= new CannonField( this, "cannonField" );
我們建立了我們的CannonField。
connect( angle, SIGNAL(valueChanged(int)),
cannonField, SLOT(setAngle(int)) );
connect( cannonField, SIGNAL(angleChanged(int)),
angle, SLOT(setValue(int)) );
這裡我們把LCDRange的valueChanged()訊號和CannonField的setAngle()槽連線起來了。只要使用者操作LCDRange,就會重新整理CannonField的角度值。我們也把它反過來連線了,這樣CannonField中角度的變化就可以重新整理LCDRange的值。在我們的例子中,我們從來沒有直接改變CannonField的角度,但是通過我們的最後一個connect()我們就可以確保沒有任何變化可以改變這兩個值之間的同步關係。
這說明了元件程式設計和正確封裝的能力。
注意只有當角度確實發生變化時,才發射angleChanged()是多麼的重要。如果LCDRange和CanonField都省略了這個檢查,這個程式就會因為第一次數值變化而進入到一個無限迴圈當中。
QGridLayout *grid = new QGridLayout( this, 2, 2, 10 );
//2×2,10畫素的邊界
到現在為止,我們沒有因為幾何管理把QVBox和QGrid視窗部件整合到一起。現在,無論如何,我們需要對我們的佈局加一些控制,所以我們使用了更加強大的QGridLayout類。QGridLayout不是一個視窗部件,它是一個可以管理任何視窗部件作為子物件的不同的類。
就像註釋中所說的,我們建立了一個以10畫素為邊界的2*2的陣列。(QGridLayout的建構函式有一點神祕,所以最好在這裡加入一些註釋。)
grid->addWidget( quit, 0, 0 );
我們在網格的左上的單元格中加入一個Quit按鈕:0,0。
grid->addWidget( angle, 1, 0, Qt::AlignTop );
我們把angle這個LCDRange放到左下的單元格,在單元格內向上對齊。(這只是QGridLayout所允許的一種對齊方式,而QGrid不允許。)
grid->addWidget( cannonField, 1, 1 );
我們把CannonField物件放到右下的單元格。(右上的單元格是空的。)
grid->setColStretch( 1, 10 );
我們告訴QGridLayout右邊的列(列1)是可拉伸的。因為左邊的列不是(它的拉伸因數是0,這是預設值),QGridLayout就會在MyWidget被重新定義大小的時候試圖讓左面的視窗部件大小不變,而重新定義CannonField的大小。
angle->setValue( 60 );
我們設定了一個初始角度值。注意這將會引發從LCDRange到CannonField的連線。
angle->setFocus();
我們剛才做的是設定angle獲得鍵盤焦點,這樣預設情況下鍵盤輸入會到達LCDRange視窗部件。
LCDRange沒有包含任何keyPressEvent(),所以這看起來不太可能有用。無論如何,它的建構函式中有了新的一行:
setFocusProxy( slider );
LCDRange設定滑塊作為它的焦點代理。這就是說當程式或者使用者想要給LCDRange一個鍵盤焦點,滑塊就會就會注意到它。QSlider有一個相當好的鍵盤介面,所以就會出現我們給LCDRange新增的這一行。
鍵盤現在可以做一些事了——方向鍵、Home、End、PageUp和PageDown都可以作一些事情。
當滑塊被操作,CannonFiled會顯示新的角度值。如果重新定義大小,CannonField會得到儘可能多的空間。
相關文章
- Qt入門(14)——父視窗部件和子視窗部件QT
- Qt入門(15)——使用視窗部件QT
- Qt入門(16)——組裝視窗部件QT
- 34.qt quick-Popup彈出視窗自定義QTUI
- QT中 視窗部件的 背景圖片 的設定QT
- Qt 視窗強制禁用系統陰影(自定義選單)QT
- Qt視窗居中QT
- QT視窗類QT
- 自定義Toast及視窗透明處理AST
- Qt實現畫板部件並和自定義button按鈕結合例項QT
- Tkinter (20) 頂層視窗部件 Toplevel
- WPF 自定義MessageBox 彈窗提示 彈窗載入
- Windows API視窗程式設計 - 自定義按鈕WindowsAPI程式設計
- avalonia自定義彈窗
- 自定義 alert 彈窗
- qt 視窗預設居中QT
- qt視窗居中顯示QT
- Qt TCP (小型聊天視窗)QTTCP
- Qt Charts 自定義樣式QT
- Go Qt5 建立空白視窗、視窗居中及關閉視窗事件GoQT事件
- Windows API視窗程式設計 - 完善自定義按鈕WindowsAPI程式設計
- QT入門QT
- 自定義版本更新彈窗
- 自定義view————廣告彈窗View
- Qt 佈局管理 - 停靠視窗QT
- QT自定義精美換膚介面QT
- Android 自定義 View 之入門篇AndroidView
- webpack 快速入門 系列 - 自定義 wepack 上Web
- QT快速入門QT
- Qt入門(11)——Qt外掛QT
- Qt 設定視窗居中顯示QT
- Qt5:視窗居中顯示QT
- Qt視窗螢幕居中顯示QT
- uniapp 自定義彈窗元件APP元件
- SQL輕鬆入門(5):視窗函式SQL函式
- AutoCAD快速入門(二):圖形視窗
- 【QT】QT如何讓視窗放置在螢幕正中間QT
- Qt實現自定義控制元件QT控制元件