1. 程式人生 > >三種方法為QLineEdit添加清除內容按鈕

三種方法為QLineEdit添加清除內容按鈕

ride his ptr 構造 最好的 成員 sta ofo cursor

很多時候我們會發現輸入的一長串內容不得不全部刪除重新輸入,這時比起一直按著退格鍵不放一個清除內容按鈕更受歡迎。

今天我將介紹三種為QLineEdit添加清除內容按鈕的方法,其中兩種方法有較強的功能針對性,另一種方法則是通用的,不僅可以用來實現清除輸入內容,還可以擴展出其他功能。

本文索引

  • 方法1:setClearButtonEnabled顯示清除按鈕
  • 方法2:使用QAction實現清除按鈕
  • 方法3:自定義QLineEdit為其添加按鈕
  • 最終的顯示效果

方法1:setClearButtonEnabled顯示清除按鈕

這是Qt5.2之後提供的方法,當使用了setClearButtonEnabled(true);

之後會在 QLineEdit的右側顯示一個圖標為QStyle::SP_DialogResetButto的QAction,點擊後會清除輸入內容:

// 方案1
auto edit1 = new QLineEdit;
edit1->setClearButtonEnabled(true);

效果:

技術分享圖片

看到右邊那個圖標,如果是Qt自帶的話會是一個類似掃把的圖形,如果使用了系統主題那麽會有些許差異,點擊它,輸入內容就會全部清除。

方法2:使用QAction實現清除按鈕

如前所述,setClearButtonEnabled其實只是讓實現存在的QAction顯示出來而已,所以我們也可以自己實現這一過程。

要實現這一功能,需要Qt5.2之後提供的addAction

方法。它負責把一個QAction添加到edit的指定位置。

不過要註意的是,這個QAction只能顯示出圖標,文字內容的顯示不出的。

// 方案2
auto clearAction = new QAction;
clearAction->setIcon(QApplication::style()->standardIcon(QStyle::SP_DialogResetButton));
auto edit2 = new QLineEdit;
// QLineEdit::TrailingPosition表示將action放置在右邊
edit2->addAction(clearAction, QLineEdit::TrailingPosition);
QObject::connect(clearAction,
      &QAction::triggered,
      edit2,
      [edit2]{ edit2->setText(""); });

因為我們知道lineedit默認使用的清除按鈕的圖標,也知道如何清除輸入,所以可以自己實現這一過程。

這是效果,與方法1時幾乎沒什麽區別:

技術分享圖片

不過方法二的威力不止於此,基於我們可以使用自己的QAction,那麽就可以定制一些操作,比如使用我們自己的圖標:

clearAction->setIcon(QIcon(":/clear.png"));

技術分享圖片

這種方法相比前一種略顯復雜,然而卻提供了更好的擴展性。

接下來要介紹的最後一種方法更加的靈活,你不僅可以顯示自定義圖標,還可以顯示自定義文字,當然作為代價它比第二種方法要復雜不少。

方法3:自定義QLineEdit為其添加按鈕


這種方法對Qt的版本沒有什麽要求,所以它也足夠通用。

想要在QLineEdit上添加一個widget一點也不復雜,首先我們要弄清以下幾個原理:

  • qt的widget和layout是可以堆疊的,之前在實現半透明遮罩中有提過
  • 你可以為QLineEdit設置layout,如你所料layout會堆疊在edit的輸入框上
  • edit的layout會只使用控件的最小尺寸,這樣不會導致將整個輸入框遮蓋掉
  • edit的可輸入區域是可以設置的,你可以合理的設置輸入區的大小避免文字進入layout之下被遮蓋

所以如果我們想為QLineEdit或是其派生類添加一個widget比如QPushButton,那麽需要如下幾部:

  1. 創建你需要的widget以及一個布局管理器
  2. 添加拉伸因子和widget至布局管理器,拉伸因子可以不添加,只要設置好布局管理器的排列方向即可
  3. 設置布局管理器裏組件的排列方向並把布局管理器添加到QLineEdit
  4. 獲取你添加的widget的寬度,然後在加上合適的邊框距離,將QLineEdit的輸入區域限制在合理的大小

說起來簡單做起來難,我們邊看代碼邊講解。

我們先看類的定義,ButtonEdit是一個帶有按鈕的QLineEdit:

#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QString>
#include <QIcon>

class ButtonEdit: public QLineEdit {
    Q_OBJECT
public:
    explicit ButtonEdit(const QString &btnText, QWidget *parent = nullptr);
    explicit ButtonEdit(const QIcon &icon, QWidget *parent = nullptr);
    ~ButtonEdit() override = default;

private:
    // 設置文本按鈕或圖標按鈕的大小和外觀
    void setTextButton();
    void setIconButton();
    // 將按鈕添加到edit
    void addButton();

    QPushButton *button;

Q_SIGNALS:
    void buttonClicked(bool);
};

// 按鈕和輸入內容的邊距
constexpr int buttonMargin = 3;

我們的類可以從一個string或者icon構建,當edit的按鈕被點擊那麽我們就發出buttonClicked信號。

也許你會覺得對於按鈕的設置分成兩類沒什麽必要。事實不然,圖形應用的開發有很多麻煩事,而其中比較頭疼的要數如何讓控件保持一個恰到好處的尺寸,而對於圖標的處理和文本是不一樣的,所以有分開的必要。當然,如果你不介意文字或者圖標只顯示一半或者突出到編輯框的話也可以跳過這一步。

下面我們來看下類成員的實現,構造函數沒什麽亮點,無非構造button,然後交由其他成員去處理:

ButtonEdit::ButtonEdit(const QString &btnText, QWidget *parent)
 : QLineEdit(parent)
{
    button = new QPushButton(btnText);
    setTextButton();
    addButton();
}

ButtonEdit::ButtonEdit(const QIcon &icon, QWidget *parent)
 : QLineEdit(parent)
{
    button = new QPushButton;
    button->setIcon(icon);
    setIconButton();
    addButton();
}

接著是addButton,在這裏我們先把button添加進layout,隨後又設置了輸入區域的大小避免輸入內容被遮住:

void ButtonEdit::addButton() {
    connect(button,
            &QPushButton::clicked,
            this,
            &ButtonEdit::buttonClicked);
    // 按鈕已經是edit的一部分了,不應該再能被單獨聚焦,否則可能導致誤觸
    button->setFocusPolicy(Qt::NoFocus);
    // 設置鼠標,否則點擊按鈕時仍然會顯示輸入內容是的鼠標圖標
    button->setCursor(Qt::ArrowCursor);

    auto btnLayout = new QHBoxLayout;
    btnLayout->addStretch();
    btnLayout->addWidget(button);
    // 設置組件右對齊,按鈕會顯示在edit的右側
    btnLayout->setAlignment(Qt::AlignRight);
    btnLayout->setContentsMargins(0, 0, 0, 0);
    setLayout(btnLayout);
    // 設置輸入區域的範圍,從edit的最左到按鈕的最左(包含了按鈕設置的buttonMargin)
    setTextMargins(0, 0, button->width(), 0);
}

下面就是如何設置button的大小和樣式了,大小我們設置和圖標/文本的大小一樣大,然後兩邊加上buttonMargin

對於圖標按鈕我們還要設置按鈕背景平時不可見,畢竟圖標周圍有個buttonMargin寬度的框不太好看:

// 幫助函數,設置按鈕的width,大小策略為fixed,不可放大或縮小
static void setButtonSize(QPushButton *button, int width) {
    auto policy = button->sizePolicy();
    policy.setHorizontalPolicy(QSizePolicy::Fixed);
    button->setSizePolicy(policy);
    // 固定寬度,加上邊距
    button->setFixedWidth(width + buttonMargin*2);
}

void ButtonEdit::setTextButton() {
    if (!button) {
        return;
    }

    // 獲得當前字體下文本內容的像素寬度
    auto width = QWidget::fontMetrics().width(button->text());
    setButtonSize(button, width);
}

void ButtonEdit::setIconButton() {
    if (!button) {
        return;
    }

    // 獲取圖標的width簡單得多
    auto width = button->iconSize().width();
    setButtonSize(button, width);
    // 設置背景和邊框在非點擊時不可見
    button->setFlat(true);
}

現在工作完成了,不管我們添加什麽樣的圖標還是多長的文本,按鈕都可以保證有一個合適的大小,輸入內容也不會被按鈕遮住。

現在我們看下使用:

// 方案3
// 使用文本按鈕
auto edit3_1 = new ButtonEdit("clear");
QObject::connect(edit3_1,
                &ButtonEdit::buttonClicked,
                edit3_1,
                [edit3_1]{ edit3_1->setText(""); });
// 使用圖標按鈕
auto edit3_2 = new ButtonEdit(QApplication::style()->standardIcon(QStyle::SP_DialogResetButton));
QObject::connect(edit3_2,
                &ButtonEdit::buttonClicked,
                edit3_2,
                [edit3_2]{ edit3_2->setText(""); });

效果如下:

技術分享圖片

這種方案是最復雜的,但也是最靈活的,我們可以定制button的外觀,通過buttonClicked信號我們可以定制按鈕按下後的行為。所以我在上一節才說這是擴展性最好的方法。

不過方案二和三都有一個顯著的缺點,即使輸入框中沒有內容按鈕或QAction也會一直顯示,有些時候這不是我們需要的行為。解決辦法也很簡單,合理利用QLineEdit的信號加上QWidget::hideQAction::setVisible就能實現按鈕的隱藏,這一功能的實現就當做練習吧。

最終的顯示效果

現在我們將三種方法合並顯示在一起,以便大家看到各個方案帶來的顯示效果:

#include <QLineEdit>
#include <QApplication>
#include <QWidget>
#include <QAction>
#include <QObject>
#include <QIcon>
#include <QFormLayout>
#include <QStyle>

#include "ButtonEdit"

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

    // 方案1
    auto edit1 = new QLineEdit;
    edit1->setClearButtonEnabled(true);

    // 方案2
    auto clearAction = new QAction;
    clearAction->setIcon(QIcon(":/clear.png"));
    auto edit2 = new QLineEdit;
    edit2->addAction(clearAction, QLineEdit::TrailingPosition);
    QObject::connect(clearAction,
            &QAction::triggered,
            edit2,
            [edit2]{ edit2->setText(""); });

    // 方案3
    // 使用文本按鈕
    auto edit3_1 = new ButtonEdit("clear");
    QObject::connect(edit3_1,
                     &ButtonEdit::buttonClicked,
                     edit3_1,
                     [edit3_1]{ edit3_1->setText(""); });
    // 使用圖標按鈕
    auto edit3_2 = new ButtonEdit(QApplication::style()->standardIcon(QStyle::SP_DialogResetButton));
    QObject::connect(edit3_2,
                     &ButtonEdit::buttonClicked,
                     edit3_2,
                     [edit3_2]{ edit3_2->setText(""); });

    auto win = new QWidget;
    auto layout = new QFormLayout;
    layout->addRow("方案1:", edit1);
    layout->addRow("方案2:", edit2);
    layout->addRow("方案3_1:", edit3_1);
    layout->addRow("方案3_2:", edit3_2);
    win->setLayout(layout);

    win->show();

    return app.exec();
}

當無輸入內容時:

技術分享圖片

當有輸入內容時:

技術分享圖片

這樣三種方法都介紹完了,選用哪種需要自己決定。

當然最後兩種方案不僅僅能用來做清除內容按鈕,只要加入一點點想象力還有更高級的功能可以用它們來實現。

三種方法為QLineEdit添加清除內容按鈕