Qt QComboBox之setEditable和currentTextChanged及其原始碼分析

師從名劍山發表於2022-04-05

Qt QComboBox之setEditable和currentTextChanged以及其原始碼分析

前言

最近做了一個QComboBox裡有選項,然後選中選項之後就會自動觸發條件搜尋。然後我發現,在我初始化comboBox時,由於訊號連線的原因會觸發這個currentTextChanged訊號。程式碼大致如下:

connect(ui->comboBox, &QComboBox::currentTextChanged,
        this,         &CountryType::slot_pageSearch);


void Country::setComboBox()
{
    QStringList content;
    int maxLen = 0;
    QFont font;
    font.setFamily("Microsoft YaHei");
    font.setPixelSize(16);
    QFontMetrics fontMetrics(font);

    QString command = jointQueryComboBoxTextCommand();
    QList<QStringList> texts = m_oracle->runSelectCommand(command);
    foreach (QStringList text, texts) {
        QString item = text.value(0)+"-"+text.value(1);
        content.push_back(item);
        // 計算最大寬度
        maxLen = maxLen > fontMetrics.boundingRect(item).width() ?
                    maxLen :
                    fontMetrics.boundingRect(item).width();
    }

    // comboBox的寬度為 文字的最大寬度 + 下拉箭頭的寬度 + 文字兩邊的間距
    ui->comboBox->setMinimumWidth(maxLen + 38 + 8);
    ui->comboBox->clear();
    // 填充一個空選項作為篩選所有
    ui->comboBox->addItem("");
    ui->comboBox->addItems(content);
}

void Country::search()
{
    setComboBox();
}

問題的出現

在我每一次對頁面進行切換的時候,我發現這個search都會觸發這個slot_pageSearch槽函式,然後執行條件搜尋。
但是我今天突發奇想,我是不是應該讓使用者能夠手動的輸入這個條件呢,於是我setEditable(true);,將編輯開啟了。
也就是:

ui->comboBox->setEditable(true);

在設定了這個之後,我驚奇的發現,並沒有像之前一樣會觸發slot_pageSearch這個槽函式。

問題分析

因為我只修改了ui->comboBox->setEditable(true);,所以我肯定,問題就是發生在這個地方,於是我在網上搜尋與這個問題有關聯的答案。

最後,我還是在QT的官方文件中對於currentText這個部分的介紹中,找到了問題的原因。
在這裡插入圖片描述大概意思就是說,當你將QComboBox設定成可編輯的狀態時(setEditable(true)),currentText就是當前的框內顯示的文字。當不為可編輯的狀態時,currentText就是當前的選項或者是一個空的字串。

所以我猜想,設定成不可編輯狀態時,由於我進行了一個條目的新增,所以就將當前的選項改變了。

currentTextChanged訊號觸發

於是我在正常的流程下,新增了一些列印語句,用於證實我的猜想。

    void Country::setComboBox() {
        ...
        // 填充一個空選項作為篩選所有

        qDebug() << "1";
        ui->comboBox->addItem("");
        qDebug() << "2";
        ui->comboBox->addItems(content);
        qDebug() << "3";
        ...
    }

    void CountryType::slot_pageSearch()
    {
        ...
        qDebug() << "111";
        ...
    }

輸出的結果為:

    1
    111
    2
    3

這也就表明了,我是在setItem之後,就會觸發槽函式。但是具體為啥是這樣的,為啥addItems不會觸發currentTextChanged呢?
所以我帶著問題,決定去原始碼裡找答案

原始碼分析

// 程式碼呼叫結構
1. QComboBox::addItem(int , const QIcon &, const QString &, const QVariant &)
----> QStandardItem::setData(const QVariant &, int )
	  ----> QStandardItemModelPrivate::itemChanged(QStandardItem *, const QVector<int> &)
			----> signal: QStandardItemModel::dataChanged(QModelIndex,QModelIndex) slot: QComboBox::_q_dataChanged(QModelIndex,QModelIndex)
				  ----> if (lineEdit) lineEdit->setText(); else emit currentTextChanged(QString);
  
2. QComboxBox::addItems(QStringList)
----> QComboxBox::insertItems(int, QStringList)
	  ----> QStandardItem::insertRows(int, QList<QStandardItem*>)
			----> QStandardItemPrivate::insertRows(int, QList<QStandardItem*>)
				  ----> rowsAboutToBeInserted(QStandardItem *, int , int)
						----> QAbstractItemModel::beginInsertRows(const QModelIndex &, int , int )
							  ----> signal: rowsAboutToBeInserted(const QModelIndex &, int , int ) slot: 
							  ----> QAbstractItemModelPrivate::rowsAboutToBeInserted(const QModelIndex &, int , int )
				  ----> QStandardItemModelPrivate::rowsInserted(QStandardItem *, int , int )
						----> QAbstractItemModel::endInsertRows()
							  ----> void QAbstractItemModelPrivate::rowsInserted(const QModelIndex &, int , int )
							  ----> signal: QAbstractItemModel::rowsInserted(QModelIndex,int,int) slot: QComboBox::_q_rowsInserted(QModelIndex,int,int)
  1. 首先,我從最簡單的來入手——addItem
    請新增圖片描述
    在上面這張圖裡可以知道,addItem呼叫的是insertItem這個函式,這個是用來插入條目的一個函式;

然後就是insertItem這個函式,我們可以看到,這個函式會根據你的是不是原始的QStandardItemModel,是的話,就會去設定資料;
請新增圖片描述
這裡有兩個分支,

  • setData
    在這裡插入圖片描述
    在這裡插入圖片描述
    隨著函式的呼叫過程,訊號dataChanged被髮射了,同時,在qcombobox.cpp中有對這個訊號的連線,
    在這裡插入圖片描述在這裡插入圖片描述
    我們進到這個_q_dataChanged()函式裡面,
    在這裡插入圖片描述
    這裡有一段程式碼:
 if (currentIndex.row() >= topLeft.row() && currentIndex.row() <= bottomRight.row()) {
        const QString text = q->itemText(currentIndex.row());
        if (lineEdit) {
            lineEdit->setText(text);
            updateLineEditGeometry();
        } else {
            emit q->currentTextChanged(text);
        }
        q->update();
#ifndef QT_NO_ACCESSIBILITY
        QAccessibleValueChangeEvent event(q, text);
        QAccessible::updateAccessibility(&event);
#endif
    }

在這裡,我們就找到了我們的目標currentTextChanged這個訊號。但是發射這個訊號的前提條件是:

  • currentIndex.row() >= topLeft.row() && currentIndex.row() <= bottomRight.row()也就是說,當前的下標的值需要在範圍內
  • 當前的狀態必須是不可編輯狀態才會發射訊號

所以這裡就是設定成可編輯狀態後,不會觸發訊號的原因;

  • insertRow
    在這裡插入圖片描述在這裡插入圖片描述
    在這裡插入圖片描述
    現在關鍵的函式要來了,這個函式bool QStandardItemPrivate::insertRows,在待會addItems這個函式分析時也會用到。
    在這裡插入圖片描述
    在這裡插入圖片描述
    在這裡發射了這個rowInserted()訊號,這個訊號,又在QComboBox中進行了槽函式的連線
    在這裡插入圖片描述
    在這裡插入圖片描述
    所以在這個函式裡面,如果是插入的第一個條目,就會把當前的下表設定成0,這時候就會觸發另外一個訊號currentIndexChanged
    在這裡插入圖片描述
    至此,我們就能明白,為什麼addItem會觸發currentTextChanged的訊號。同時,如果設定成可編輯狀態,又是為何不會觸發currentTextChanged
  1. 其次,我們從第二個函式,也就是addItems
    在這裡插入圖片描述
    在這裡插入圖片描述
    到這裡,就能發現,這個部分呼叫的還是這個bool QStandardItemPrivate::insertRows,同樣根據條件判斷,currentIndex = 0而其他兩個分別為1和新增條目的數量,很顯然不符合要求。
    所以這也就是為什麼addItems不會觸發currentIndexChange的原因。
    至此,根據原始碼的分析,所有發生的事情,都能夠正常的解釋通了。

相關文章