1. 程式人生 > 實用技巧 >《QT Creator快速入門》第五章:應用程式主視窗

《QT Creator快速入門》第五章:應用程式主視窗

1、選單欄

新增選單:

QMainWindow中的選單欄是一個QMenuBar物件,可以通過設計模式下來給選單欄新增選單,選單項也可以設定加速鍵,通過在給選單文字新增(&加速鍵),而且子選單上如果設定的加速鍵其實就快捷鍵,不用再使用alt鍵,直接輸入對應鍵即為點選對應的選單項。如下所示的檔案選單項的加速鍵為alt + f,選單項新建的快捷鍵為n。

QMainWindow設計模式下還有一個動作編輯器和訊號槽編輯器,如下所示,動作編輯器裡每一行對應一個選單項,雙擊動作編輯器裡一行來設定對應選單項的顯示文字、物件名稱、提示文字、圖示、快捷鍵等。訊號槽編輯器裡可以新增選單項的一些訊號槽方法,比如選單項點選訊號triggered,也可以在動作編輯器裡右鍵-轉到槽來自動生成選單項的訊號槽方法。

新增資原始檔:

上面在動作編輯器裡設定選單的圖示是在當前專案的資原始檔裡尋找圖示檔案,新增圖片資原始檔的方法:右鍵專案新增QT資原始檔,名稱可以設定為myResource,路徑選擇當前專案所在,點選確定後會在選擇的路徑下生成qrc檔案,將圖片檔案放到qrc檔案所在目錄或子目錄下,在Qt Creator中開啟qrc文件(右擊qrc檔案選擇open in editor),點選新增按鈕選擇新增字首,編輯字首名如“/myImage”,再點選新增按鈕選擇新增檔案,選擇要新增的圖片檔案,最後ctrl+s儲存即可。在專案中使用圖片資源的時候使用路徑":/myImage/imageFolder/imageName.png"即可。

使用程式碼來新增選單:

在選單的動作編輯器裡有個Checkable選項,這是用來設定選單項點選後顯示一個對勾標誌的,在程式碼中我們可以將選單項新增到一個QActionGroup動作組中,使只有一個選單項點選後顯示對勾:

也可以向選單欄新增一個其它型別的部件,通過繼承QWidgetAction,然後重寫createWidget方法,在其中返回要新增的部件,如下程式碼實現了一個包含一個標籤和一個行編輯器的選單項:

QWidget* MyAction::createWidget(QWidget* parent)
{
    if(parent->inherits("
QMenu")) //父部件不是選單直接返回 return nullptr; QSplitter* splitter = new QSplitter(parent); QLabel* label = new QLabel; label->setText(QString::fromUtf8(u8"輸入文字")); splitter->addWidget(label); QLineEdit* lineEidt = new QLineEdit; splitter->addWidget(lineEidt); return splitter; }

QObject::inherits(const char* className)方法用來測試當前物件是否是className型別物件或其派生類物件。

2、工具欄

QToolBar是工具欄類,在前面程式碼裡可以看到往工具欄上新增項也是使用addAction()方法新增一個QAction。如果想要往工具欄上新增Button等型別的部件的話可以使用addWidget()方法。通過工具欄的屬性可以對其進行設定:toolButtonStyle設定圖示和文字顯示(預設只顯示圖示)、movable設定狀態列是否可以移動、allowedArea設定允許停靠的位置、floatable設定是否可以懸浮。

3、狀態列

QStatusBar提供了一個水平條,用來顯示狀態資訊,QMainWindow下預設有一個狀態列。狀態資訊分為3類,臨時資訊(超過一段時間後消失,出現在狀態列左邊)、正常資訊(如當前行號、頁數,在狀態列左邊顯示,可能會被臨時資訊掩蓋)、永久資訊(如版本資訊,在狀態列右邊顯示),呼叫showMessage方法來顯示臨時資訊,一般使用addWidget方法新增一個QLabel到狀態列上顯示正常資訊,使用addPermanentWidget方法來新增一個QLabel來顯示永久資訊。

QMainWindow狀態列的最右端有一個QSizeGrip部件,它用來調整視窗的大小,可以使用狀態列的setSizeGripEnabled()方法來禁用它。

4、富文字編輯器

QTextEdit是一個富文字編輯器,QTextBrowser是其只讀版本,QPlaintTextEdit是普通的純文字編輯器。如下是對QTextEdit進行的操作和效果:

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

    ui->textEdit->setText("test"); //設定文字後游標預設沒有變化還是在首位
    QTextCursor cursor = ui->textEdit->textCursor(); //獲取游標
    cursor.movePosition(QTextCursor::End); //移動游標到末尾
    ui->textEdit->setTextCursor(cursor); //應用到當前編輯器
}

上面程式碼在使用setText設定編輯器文字後游標還是在首位,也可以直接使用游標的insertText()方法來插入文字,這樣游標會直接改變:

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

    QTextCursor cursr = ui->textEdit->textCursor();
    cursr.insertText("test");
}

Qt對富文字的處理分為編輯操作和只讀操作,編輯操作使用基於游標(QTextCursor)的一些介面函式,文件(QTextDocument)的讀取使用只讀的分層次介面函式。富文字文件中包含框架(QTextFrame)、文字塊(QTextBlock)、表格(QTextTable)、列表(QTextList)這幾種元素,每種元素的格式使用對應的format類來表示,如框架格式QTextFrameFormat、文字塊格式QTextBlockFormat,一個空文件包含了一個根框架,這個根框架又包含了一個空的文字塊,在根框架中還可以再新增文字塊、子框架等。QTextEdit預設包含了一個QTextCursor游標物件和一個QTextDocument文件物件。如下圖所示:

如下是對富文字編輯器重新設定了框架的格式,執行程式可以發現只能在紅色邊框的中進行輸入:

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


    QTextDocument* document = ui->textEdit->document(); //獲取富文字編輯器的文件物件
    QTextFrame* rootFrame = document->rootFrame(); //獲取文件的根框架

    QTextFrameFormat format; //建立框架格式
    format.setBorderBrush(Qt::red); //邊界顏色
    format.setBorder(4); //邊界寬度
    //format.setWidth(100); //高度
    rootFrame->setFrameFormat(format); //設定根框架格式
}

下面是在上面程式碼的基礎上,使用游標物件,在根框架中再添加了一個子框架:

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


    QTextDocument* document = ui->textEdit->document(); //獲取富文字編輯器的文件物件
    QTextFrame* rootFrame = document->rootFrame(); //獲取文件的根框架

    QTextFrameFormat format; //建立框架格式
    format.setBorderBrush(Qt::red); //邊界顏色
    format.setBorder(4); //邊界寬度
    //format.setWidth(100); //高度
    rootFrame->setFrameFormat(format); //設定根框架格式

    QTextFrameFormat frameFormat;
    frameFormat.setBackground(Qt::lightGray); //背景顏色
    frameFormat.setMargin(10); //邊距
    frameFormat.setPadding(5); //填襯(邊框和文字的間距)
    frameFormat.setBorder(2);
    frameFormat.setBorderStyle(QTextFrameFormat::BorderStyle_Dotted); //邊框樣式為虛線
    QTextCursor cursor = ui->textEdit->textCursor(); //獲取游標
    cursor.insertFrame(frameFormat); //在游標處插入子框架
}

下面的函式中獲取了上面程式中的富文字編輯器的文件的根框架,然後遍歷了根框架中所有子框架和文字塊:

void MainWindow::on_pushButton_2_clicked()
{

    QTextDocument* document = ui->textEdit->document(); //獲取富文字編輯器的文件物件
    QTextFrame* frame = document->rootFrame(); //獲取文件的根框架
    for(QTextFrame::iterator it = frame->begin(); !it.atEnd(); ++it) //遍歷根框架的文件
    {
        QTextFrame* childFrame = it.currentFrame(); //子框架
        QTextBlock childBlock = it.currentBlock(); //文字塊
        qDebug() << childBlock.text();
    }
}
View Code

上面的方法中只獲得了文件根框架中的子框架和文字塊,而子框架的文字塊卻無法遍歷到,下面的方法獲得了文件的所有文字塊:

void MainWindow::on_pushButton_clicked()
{
    QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));
    QTextDocument* document = ui->textEdit->document(); //獲取富文字編輯器的文件物件

    QTextBlock block = document->firstBlock(); //獲取文件的第一個文字塊
    for(int i = 0; i < document->blockCount(); ++i)
    {
        qDebug() << tr("文字塊%1: 首行行號為%2, 長度為%3, 內容為").arg(i).arg(block.firstLineNumber()).arg(block.length())
                 << block.text();
        block = block.next(); //獲取下一個文字塊
    }
}

文字塊QTextBlock可以看做一個段落,但不能使用回車換行,因為一個回車換行就表示建立一個新的文字塊。QTextBlock提供了只讀介面,文字塊的格式由QTextBlockFormat類來處理,包括對齊方式、文字塊四周的邊白、縮排等,文字內容的格式由QTextCharFormat類來設定,包括字型大小、加粗、下劃線等。

下面的方法是編輯文字塊及其內容的格式,比如水平居中顯示、字型、字型顏色、下劃線等:

void MainWindow::on_pushButton_clicked()
{
    QTextCursor cursor = ui->textEdit->textCursor();

    QTextBlockFormat blockFormat; //文字塊格式
    blockFormat.setAlignment(Qt::AlignCenter); //水平居中
    cursor.insertBlock(blockFormat); //使用文字塊格式

    QTextCharFormat charFormat; //字元格式
    charFormat.setBackground(Qt::lightGray); //字型背景色
    charFormat.setForeground(Qt::blue); //字型顏色
    charFormat.setFont(QFont(QString::fromUtf8(u8"宋體"), 12, QFont::Bold, true)); //宋體、12號、加粗、傾斜
    charFormat.setFontUnderline(true); //下劃線
    cursor.setCharFormat(charFormat); //使用字元格式

    cursor.insertText(QString::fromUtf8(u8"測試文字"));
}

插入表格QTextTable:

void MainWindow::on_pushButton_clicked()
{
    QTextCursor cursor = ui->textEdit->textCursor();

    QTextTableFormat format; //表格格式
    format.setCellSpacing(2); //表格外白邊
    format.setCellPadding(10); //表格內白邊
    cursor.insertTable(2, 2, format); //插入兩行兩列的表格
}
View Code

表格QTextTable、列表QTextList也支援使用QTextFrame::iterator來遍歷它們。表格的cellAt()方法可以獲得指定的單元格(單元格對應的類是QTextTableCell,其對應格式為QTextTableCellFormat),其它的還有插入列、插入行、合併單元格、拆分單元格等方法。列表提供了獲取列表項個數count()、item()獲得指定項的文字塊等方法。

插入列表QTextList:

void MainWindow::on_pushButton_2_clicked()
{
    QTextListFormat format; //列表格式
    format.setStyle(QTextListFormat::ListDecimal); //使用數字編號
    ui->textEdit->textCursor().insertList(format); //插入列表
}
View Code

插入圖片:

void MainWindow::on_pushButton_3_clicked()
{
    QTextImageFormat format; //圖片格式
    format.setName("logo.png"); //指定圖片
    format.setHeight(20.0); //設定圖片高度
    ui->textEdit->textCursor().insertImage(format); //插入圖片
}
View Code

QTextEdit的find()方法可以進行文字的查詢,它返回其所在的行和列,更多的查詢功能可以使用QTextDocument類find()方法。

QTextEdit配合QSyntaxHighlighter可以實現語法高亮,通過重寫QSyntaxHighlighter的highlightBlock方法,再將QTextDocument類物件作為QSyntaxHighlighter物件的父部件即可。如下是對"char"單詞進行高亮顯示的方法:

void MySyntaxHighlighter::highlightBlock(const QString& text)
{
    QTextCharFormat format; //高亮顯示文字格式
    format.setFontWeight(QFont::Bold); //字型加粗
    format.setForeground(Qt::green); //綠色字型

    QString pattern = "\\bchar\\b"; //匹配"char"單詞
    QRegExp expression(pattern); //建立正則表示式
    int idx = text.indexOf(expression); //開始匹配字串
    while(idx >= 0) //匹配成功
    {
        int len = expression.matchedLength(); //要匹配字串的長度
        setFormat(idx, len, format); //對要匹配的字串設定高亮顯示格式
        idx = text.indexOf(expression, idx + len); //繼續匹配
    }
}
View Code

可以在QLabel或者QTextEdit新增文字時使用HTML標籤或CSS屬性,具體可在幫助中參考Supported HTML Subset關鍵字。QCompleter類用來實現編輯自動補全,可以參考Qt的示例程式Custom Completer。關於富文字編輯器其它用法可以參考Rich Text分類下的幾個程式。

5、檔案拖放

Qt提供了檔案拖放機制,可以在幫助中檢視Drag and Drop關鍵字來了解。當檔案拖動時拖動資料會被儲存為MIME型別,在Qt中使用QMimeData類來表示MIME型別資料,QMimeData中提供了幾個函式來處理常見的MIME資料,如下所示:

下面是實現拖動一個txt檔案到QMainWindow上的QTextEdit中,然後在編輯器中顯示檔案內容的示例:

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

    setAcceptDrops(true); //接收拖動放下事件
}

//拖動進入事件
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
    if(event->mimeData()->hasUrls()) //是否包含URL
        event->acceptProposedAction(); //接收動作
    else
        event->ignore(); //忽略事件
}

//放下事件
void MainWindow::dropEvent(QDropEvent *event)
{
    const QMimeData* mimeData = event->mimeData(); //獲取MIME資料
    if(mimeData->hasUrls()) //如果包含URL
    {
        QList<QUrl> urlList = mimeData->urls(); //獲取URL列表
        QString fileName = urlList.at(0).toLocalFile(); //獲取拖入的第一個檔案的本地路徑
        if(!fileName.isEmpty())
        {
            QFile file(fileName);
            if(!file.open(QIODevice::ReadOnly)) return;
            QTextStream in(&file);
            ui->textEdit->setText(in.readAll()); //將檔案內容讀入編輯器
        }
    }
}
View Code

當呼叫close()方法關閉視窗實際上會向widget傳送一個關閉事件QCloseEvent,預設視窗將會隱藏而不是被釋放,除非對視窗設定了Qt::WA_DeleteOnClose屬性。

QDrag可以用來完成資料的轉移,比如下面的程式就是實現拖動QMainWindow上的一個圖片Label後移動該圖片Label的效果:

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

    setAcceptDrops(true); //設定視窗可以接收拖入
    //新增圖片顯示部件
    QLabel* label = new QLabel(this);
    QPixmap pix("logo.png");
    label->setPixmap(pix);
    label->resize(pix.size());
    label->move(100, 100);
    label->setAttribute(Qt::WA_DeleteOnClose); //當視窗關閉時銷燬圖片
}

void MainWindow::mousePressEvent(QMouseEvent* event) //滑鼠按下事件
{
    //獲取圖片
    QWidget* widget = childAt(event->pos()); //獲得滑鼠所在的部件
    QLabel* child = static_cast<QLabel*>(widget); //將滑鼠所在的部件強轉為QLabel
    if(!child->inherits("QLabel")) //如果部件是否是QLabel型別
        return;
    QPixmap pixmap = *child->pixmap(); //獲取Label中的圖片

    //自定義MIME型別,將MIME型別資料放入到QMimeData中
    QByteArray itemData; //建立位元組陣列
    QDataStream dataStream(&itemData, QIODevice::WriteOnly); //建立位元組陣列的資料流
    dataStream << pixmap << QPoint(event->pos() - child->pos()); //將圖片、位置資訊儲存到位元組陣列中
    QMimeData* mimeData = new QMimeData; //建立QMimeData來存放要移動的資料
    mimeData->setData("myImage/png", itemData); //將位元組陣列放入到QMimeData中

    //將QMimeData資料放入到QDrag中
    QDrag* drag = new QDrag(this); //建立QDrag,它用來移動資料
    drag->setMimeData(mimeData); //設定MIME資料
    drag->setPixmap(pixmap); //設定在拖動過程中顯示圖片
    drag->setHotSpot(event->pos() - child->pos()); //拖動時滑鼠指標的位置不變

    //拖動時給原圖片新增陰影
    QPixmap tempPixmap = pixmap;
    QPainter painter; //建立QPainter來繪製圖片
    painter.begin(&tempPixmap);
    painter.fillRect(pixmap.rect(), QColor(127, 127, 127, 127)); //新增一層透明的淡黑色
    painter.end();
    child->setPixmap(tempPixmap);

    //使用QDrag的exec()方法來執行拖放操作,exec()可以設定支援的放下動作和預設的放下動作,該方法不會影響主事件迴圈造成阻塞
    if(drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction) == Qt::MoveAction) //設定拖放可以是移動或複製操作,預設是複製操作
    { //移動操作
        child->close();
    }
    else
    { //複製操作
        child->show(); //拖放完成後顯示label
        child->setPixmap(pixmap); //不再顯示陰影效果
    }
}

void MainWindow::dragEnterEvent(QDragEnterEvent *event) //拖動進入事件
{
    if(event->mimeData()->hasFormat("myImage/png")) //如果有我們定義的MIME型別資料,則執行移動操作
    {
        event->setDropAction(Qt::MoveAction);
        event->accept();
    }
    else {
        event->ignore();
    }
}

void MainWindow::dragMoveEvent(QDragMoveEvent *event) //拖動事件
{
    if(event->mimeData()->hasFormat("myImage/png")) //如果有我們定義的MIME型別資料,則執行移動操作
    {
        event->setDropAction(Qt::MoveAction);
        event->accept();
    }
    else {
        event->ignore();
    }
}

void MainWindow::dropEvent(QDropEvent *event) //放下事件
{
    if(event->mimeData()->hasFormat("myImage/png"))
    {
        QByteArray itemData = event->mimeData()->data("myImage/png"); //獲得拖放資料到位元組陣列

        //將位元組陣列中資料讀入QPixmap和QPoint物件
        QDataStream dataStream(&itemData, QIODevice::ReadOnly);
        QPixmap pixmap;
        QPoint offset;
        dataStream >> pixmap >> offset;

        //新建Label,新增圖片後顯示
        QLabel* newLabel = new QLabel(this);
        newLabel->setPixmap(pixmap);
        newLabel->resize(pixmap.size());
        newLabel->move(event->pos() - offset);
        newLabel->show();
        newLabel->setAttribute(Qt::WA_DeleteOnClose);

        event->setDropAction(Qt::MoveAction);
        event->accept();
    }
    else {
        event->ignore();
    }
}
View Code

如果想要實現拖動複製圖片Label效果的話,將dragEnterEvent、dragMoveEvent、dropEvent方法中event->setDropAction()引數設定為Qt::CopyAction,或者將setDropActio()和accept()替換成event->acceptProposedAction(),因為上面QDrag的exec方法設定了拖放預設動作是複製。

6、QPrinter

下面程式碼通過QPrinter列印器物件實現了儲存文字編輯器textEdit上內容到pdf檔案的功能:

void MainWindow::on_pushButton_clicked()
{
    //顯示儲存檔案對話方塊並獲得使用者的選擇
    QString fileName = QFileDialog::getSaveFileName(this, QString::fromUtf8(u8"儲存檔案對話方塊"), QString(), "*.pdf");
    if(fileName.isEmpty())
        return;
    if(QFileInfo(fileName).suffix().isEmpty())
        fileName.append(".pdf");

    //將textEdit中內容儲存為pdf檔案
    QPrinter printer;
    printer.setOutputFormat(QPrinter::PdfFormat);
    printer.setOutputFileName(fileName);
    ui->textEdit->print(&printer);
}
View Code