1. 程式人生 > 實用技巧 >《QT Creator快速入門》第三章:視窗部件(1)

《QT Creator快速入門》第三章:視窗部件(1)

1、QWidget

QWidget類是所有介面物件的基類,它是基礎視窗部件,如下所示:

QWidget類的建構函式:QWidget(QWidget * parent = 0, Qt::WindowFlags f = 0);其中parent指定父視窗(預設為0,QWidget為一個視窗,非0的話QWidget是一個子部件),WindowFlags指定視窗型別和標誌,例如:QT::Widget預設型別(如果沒有父視窗的話為獨立視窗型別,有父視窗的話為子部件型別),QT::Window視窗型別,Qt::Dialog對話方塊型別,Qt::WindowStaysOnTopHint始終處於頂層,Qt::FramelessWindowHint無標題欄和邊框樣式,Qt::SplashScreen顯示效果與FramelessWindowHint相同,但工作列中無該部件圖示,一般與WindowStaysOnTopHint配合用於歡迎介面,Qt::WindowMaximized最大化,Qt::WindowMinMaxButtonsHint具有最小最大化按鈕。我們也可以在物件定義後呼叫QWidget::setWindowFlags來設定視窗型別和標誌。

setWindowState()可以用來設定視窗的狀態,如最小化,Qt::WindowMinimized,最大化Qt::WindowMaximized,全屏顯示Qt::WindowFullScreen。這裡需要注意的一點,如果部件是無邊框的則最大化會全屏顯示,我們可以通過QDesktopWidget::availableGeometry()函式獲得不含工作列的桌面大小,然後來設定部件最大化。

在QT的示例程式中有一個Window Flags程式演示了所有的視窗型別和標誌,可以在Qt Creator的歡迎模式中找到該示例。

還有兩點需要注意,如果一個父視窗部件我們沒有指定大小,那麼他的大小會由子部件大小決定。最後在釋放物件的時候不必單獨釋放子部件,在釋放父視窗部件的時候會自動釋放其子部件(銷燬一個QObject時其子物件也會被自動銷燬,析構順序為先執行父物件的析構方法,再執行子物件的析構方法)。

下面是一個示例和效果:

//#include <QApplication>
//#include <QLabel>
//#include <QWidget>
#include <QtGui> //QApplication和GUI類標頭檔案都包含在QtGui

int main(int argc, char**argv)
{
    QApplication app(argc, argv);

    //下面的widget和label都沒有父視窗,關閉任何一個部件後另一個不受影響,仍然顯示
    QWidget* pWidget = new QWidget;
    pWidget
->setWindowTitle(QString::fromUtf8("我是Widget")); //一般視窗都有邊框和標題欄,可以通過QWidget建構函式的第二個引數進行設定 pWidget->show(); //沒有設定位置的話預設居中顯示,沒有設定大小的話,如果當前有子部件會根據子部件設定大小,當前沒有子部件的話預設設定一個大小 QLabel label; label.setWindowTitle(QString::fromUtf8("我是Label")); label.move(600,500); label.resize(180, 30); label.show(); QDialog dlg; dlg.setWindowTitle(QString::fromUtf8("我是Dialog")); dlg.resize(200, 150); dlg.show(); QLabel* pSubLabel = new QLabel(pWidget, Qt::FramelessWindowHint); pSubLabel->setText(QString::fromUtf8("我是子label")); pSubLabel->move(0, 0); pSubLabel->resize(100, 30); pSubLabel->show(); int ret = app.exec(); delete pWidget; //銷燬父視窗的時候自動銷燬子部件,所以不用再delete label2 return ret; }
View Code

2、佈局、位置資訊函式

因為視窗可能會帶有框架,所以視窗的方法一類是包含框架的(x()、y()、pos()、frameGeometry()、move()),一類是不包含框架的(geometry()、width()、height()、rect()、size()),如下圖所示:

QWidget* pt = new QWidget;

//設定位置
pt->move(100, 200);
//獲得位置
QPoint p = pt->pos();
int x = pt->x();
int w = pt->width();

//設定大小
pt->resize(100, 200);
pt->setFixedSize(100, 200);
//獲得大小
QSize s = pt->size();

//設定位置和大小
pt->setGeometry(10, 20, 100, 200);
//獲得位置和大小
QRect r = pt->geometry(); //r的x、y是相對於父視窗的座標
r = pt->rect(); //r的x、y始終為0
x = pt->geometry().x();
w = pt->geometry().width();

3、模態對話方塊

#include "dialog.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Dialog w;
    w.setWindowTitle(QString::fromUtf8("main"));

    /*****建立模態對話方塊的三種方法*****/
    /*****關閉對話方塊可以使用done(int)或者使用accept()、reject()來隱藏對話方塊*****/

    //呼叫對話方塊的exec,程式會一直等待exec方法返回才繼續執行,所以w會在dlg關閉後才顯示
    QDialog dlg(&w);
    dlg.exec();

    //呼叫對話方塊的setModal後再呼叫show,此時程式不會阻塞,w也會顯示,但只有關閉dlg後才能操作w
    QDialog* dialog = new QDialog(&w);
    dialog->setModal(true);
    dialog->show();

    //呼叫對話方塊的setWindowModality後再呼叫show,setWindowModality與setModal類似,當它可以設定阻塞視窗的型別:
    //Qt::ApplicationModal為阻塞所有視窗,相當於setModal(true)
    //Qt::WindowModal為阻塞父視窗、子視窗
    //Qt::NonModal不阻塞視窗,相當於一個非模態對話方塊
    QDialog* pDlg = new QDialog(&w);
    pDlg->setWindowModality(Qt::ApplicationModal);
    pDlg->show();

     w.show();

    return a.exec();
}
View Code

4、初識訊號與槽

訊號和槽用於兩個物件之間的通訊,例如使用者單擊了關閉按鈕則希望執行視窗的close()方法來關閉視窗,只有QObject和其子類能夠使用訊號槽機制。使用回撥方法也可以來實現兩個物件之間的通訊,但是回撥方法只能是全域性方法或類的靜態成員方法,想要訪問物件成員的話還需要向回撥傳入物件地址,實現比較麻煩。

使用訊號和槽必須在類的宣告中新增Q_OBJECT巨集,對於我們自定義的類需要注意這點, 推薦的做法是我們自定義的類中宣告都加上Q_OBJECT,如:

  class MyClass
  {
    Q_OBJECT
  public:
    ......
  };

一個訊號可以關聯多個槽(槽執行順序是隨機的),多個訊號也可以關聯到一個槽上。

新建一個Qt Gui專案,基類選擇QWidget,然後在設計模式下新增一個Push Button,我們想要對按鈕的點選事件進行處理的話就需要新增訊號和槽的處理,訊號即按鈕點選時候產生,然後執行槽來處理,即槽其實為一個執行函式。有四種方法來實現訊號和槽:

①、

1)、在QWidget派生類的標頭檔案和原始檔中新增槽的宣告和定義:

  public slots:
    void showChildDialog();

  void MyWidget::showChildDialog()
  {
  ......
  }

2)、在QWidget派生類的建構函式中呼叫connect()將按鈕的單擊訊號和槽進行關聯,connect()的四個引數分別為傳送訊號的物件(指標)、傳送的訊號名(使用SIGNAL()巨集獲得)、接收訊號的物件(指標,如果是當前物件即this的話該引數可以省略)、要執行的槽名(使用SLOT()巨集獲得):

connect(ui->showChildButton, SIGNAL(clicked(bool)), this, SLOT(showChildDialog(bool)));

訊號與槽機制與回撥方法相比另外不同的一點是可以指定槽方法的同步/非同步執行,通過connect()方法的最後一個帶預設引數的連線型別引數,以下為connect的連線型別引數的取值,預設為Qt::AutoConnection:

設定部件物件名可以通過Qt設計器中的屬性一欄的ObjectName,還可以呼叫setObjectName()函式來實現。

訊號不僅可以connect關聯到槽方法上,還可以可以關聯訊號到另一個訊號上:connect(myButton, SIGNAL(clicked()), this, SIGNAL(buttonClicked()));

②、在設計介面的選單欄上選擇"編輯訊號/槽"模式,拖動按鈕然後在當前主視窗上釋放(想要呼叫哪個widget的槽方法就在哪個widget上釋放),選擇按鈕的clicked訊號,選擇對應widget的槽方法,如果槽方法我們還沒有定義的話點選下方法的 “編輯” 新增槽方法名稱後還需再在widget類中新增這個槽方法的定義。在選擇訊號和槽的對話方塊上有一個“顯示從QWidget繼承的訊號和槽”的選項,如下圖所示,勾選它後可以看到多了一些訊號和槽方法,我們可以直接選擇QWidget中一些成員槽函式來直接作為槽方法,這樣就不用自己再定義槽方法。

  

下面是QWidget和Qdialog中一些成員槽函式的說明:

  QWidget::close(); //關閉部件
  QWidget::show();  //顯示部件
  QDialog::accept(); //對於執行exec()產生的模態對話方塊隱藏,即使對話方塊的exec()函式返回,且返回值為QDialog::Accepted
  QDialog::reject(); //對於執行exec()產生的模態對話方塊隱藏,即使對話方塊的exec()函式返回,且返回值為QDialog::Rejected

③、上面這種將訊號和槽進行關聯的方法是手動關聯,還有一種簡單的 “自動關聯” 的方式,只需將槽的函式名設定為on + 傳送訊號的物件名 + 訊號名,而不再需要connect()函式,eg: void on_showChildButton_clicked();

使用這種方式的話必須要在ui->setupUi(this)語句之前來定義部件(因為在setupUi方法中呼叫了conncetSlotsByName方法來支援訊號和槽的自動關聯),並且設定部件的objectName,如下所示:

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    QPushButton* pushBtn = new QPushButton(this);
    pushBtn->setObjectName("myPushBtn");

    ui->setupUi(this);
}
View Code

如果是對於自定義的訊號,則只能使用手動關聯的方法。

 ④、在設計介面右擊Button->轉到槽->選擇clicked()訊號->點選確定。與第二種方法相比,這種方法自動選擇了Button的父視窗為執行槽的物件,然後在父視窗類中生成了槽方法而不用我們自己再定義槽方法。這種方法
實際上就是替我們實現了第③種方法中所說的對訊號和槽的“自動關聯”方式。
自動生成的槽方法是按照"on_物件名_訊號名"來生成的,比如void on_pushButton_clicked(),我們可以修改部件的objectName物件名來定製自動生成的槽方法的名稱。

⑤、第五種方法是針對自定義訊號執行槽方法的實現,一般使用它來實現兩個物件之間的通訊。如以下實現當Dialog中的Button點選時,向Widget傳送訊號來執行指定的槽:

  首先,使用訊號和槽必須在類的宣告中新增Q_OBJECT巨集,然後在Dialog中使用 signals 來宣告自定義的訊號(訊號只需要進行宣告,不用實現),signals類似於protected,即只有當前類和子類能夠發射該訊號:
//mydialog.h

class myDialog : public QDialog
{
    Q_OBJECT

        ......

        signals:
    void dlgReturn(int);//宣告自定義的訊號
};
    然後,在Dialog上按鈕點選訊號的槽裡使用 emit 來發送自定義的訊號:
//mydialog.cpp

void myDialog::on_pushButton_clicked()
{
    int iValue = 100;
    emit dlgReturn(iValue);//傳送自定義的訊號,訊號中的引數可以多於槽中的引數
    close();
}

  然後,在Widget上新增自定義訊號的槽的宣告和實現:
 //mywidget.h
class myWidget : public QWidget
{
    Q_OBJECT
        ......

        private slots:
    void receiveValue(int value); //自定義訊號的槽的宣告
};


//mywidget.cpp
void myWidget::receiveValue(int value)//自定義訊號的槽的定義
{
    qDebug() << "get value: " << value; //qDebug()可以嚮應用程式輸出欄輸出各種格式,如int、string、QObject(輸出物件名)、QObjectList等。
}

  最後,在Widget的建構函式中利用connect()實現自定義訊號和槽的關聯:
 //mywidget.h

#include "mydialog.h"

myWidget::myWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    myDialog* dlg = new myDialog(this);
    connect(dlg, SIGNAL(dlgReturn(int)), this, SLOT(receiveValue(int)));//實現自定義訊號和槽的關聯
    dlg->show();
}

在槽方法中可以通過QObject::sender()方法來獲得傳送訊號的部件指標,如下所示:
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QObject::connect(ui->pushButton_1, SIGNAL(clicked()), SLOT(on_pushButton_clicked()));
    QObject::connect(ui->pushButton_2, SIGNAL(clicked()), SLOT(on_pushButton_clicked()));
    QObject::connect(ui->pushButton_3, SIGNAL(clicked()), SLOT(on_pushButton_clicked()));
}

void MainWindow::on_pushButton_clicked()
{
    QObject* obj = sender(); //獲得傳送訊號的部件

    if(obj == ui->pushButton_1)
    {
        //Todo
    }
    else if(obj == ui->pushButton_2)
    {
        //Todo
    }
    else if(obj == ui->pushButton_3)
    {
        //Todo
    }
}

使用QSignalMapper訊號對映器可以為訊號新增int、QString、QObject*或QWidget*型別的引數:

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QSignalMapper* signalMapper = new QSignalMapper(this); //建立訊號對映器

    connect(ui->pushButton_1, SIGNAL(clicked()), signalMapper, SLOT(map())); //繫結按鈕點選訊號到訊號對映器上
    signalMapper->setMapping(ui->pushButton_1, ui->pushButton_1->text()); //設定訊號引數
    connect(ui->pushButton_2, SIGNAL(clicked()), signalMapper, SLOT(map()));
    signalMapper->setMapping(ui->pushButton_2, ui->pushButton_2->text());
    connect(ui->pushButton_3, SIGNAL(clicked()), signalMapper, SLOT(map()));
    signalMapper->setMapping(ui->pushButton_3, ui->pushButton_3->text());

    connect(signalMapper, SIGNAL(mapped(QString)), SLOT(on_pushButton_clicked(QString))); //繫結訊號對映器的mapped訊號到當前部件
}

void MainWindow::on_pushButton_clicked(QString strParam) //帶QString型別引數
{
    QObject* obj = sender();

    if(obj == ui->pushButton_1)
    {
       //Todo
    }
    else if(obj == ui->pushButton_2)
    {
       //Todo
    }
    else if(obj == ui->pushButton_3)
    {
       //Todo
    }
}

通過以上例子可以看到,使用QSignalMapper的好處是可以給clicked()訊號新增一個引數,有時候這樣很方便,比如下面直接將引數設定為textEdit部件的顯示文字,而不用再在槽方法中進行設定:

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QSignalMapper* signalMapper = new QSignalMapper(this); //建立訊號對映器

    connect(ui->pushButton_1, SIGNAL(clicked()), signalMapper, SLOT(map())); //繫結按鈕點選訊號到訊號對映器上
    signalMapper->setMapping(ui->pushButton_1, ui->pushButton_1->text()); //設定訊號引數
    connect(ui->pushButton_2, SIGNAL(clicked()), signalMapper, SLOT(map()));
    signalMapper->setMapping(ui->pushButton_2, ui->pushButton_2->text());
    connect(ui->pushButton_3, SIGNAL(clicked()), signalMapper, SLOT(map()));
    signalMapper->setMapping(ui->pushButton_3, ui->pushButton_3->text());

    //繫結訊號對映器的mapped訊號到textEdit部件上的設定編輯器文字的槽方法
    connect(signalMapper, SIGNAL(mapped(QString)), ui->textEdit, SLOT(setText(QString)));
}