提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
最近开发一个需求需要在QLineEdit上增加回显功能,这个回显要集成在QLineEdit里面,而且要好看。一开始我想到的是创建一个QWidget同时集成QLineEdit和QToolButton来实现联动,看了QLineEdit源代码之后直到其实不用这么麻烦。
思路就是QLineEdit本身自带一个Clear功能,就是一键清除,其实它提前预留了一些扩展,可以方便我们增加自己的定制功能,而且不用担心UI适配问题。
一、QAction方案
简单来说Clear功能也是QAction方案,只不过这个功能集成在QLineEdit内部,你只能操作有限的几个方法,不能过多的定制行为。我们的回显就是QAction方案,总体来说可以实现一定的定制化。
我也不太理解像回显这么重要的功能为什么框架没有直接提供,所幸官方预留了接口,定制化也比较简单。
二、部分代码
因为我的代码引用了另一个框架,所以原封不动复制进来你们也跑不起来,这里我贴出来我的代码,虽然它不能跑起来,但是你应该看的明白原理。
总体思路就是创建一个QAction,然后定义信号连接,将Action加到QLineEdit的尾部。
cpp
_pIsEchoButtonEnable = isEcho;
if (isEcho) {
_echoAction = new QAction(XIcon::getElaIcon(ElaIconType::EyeSlash, XThemeColor(eTheme->getThemeMode(), BasicText)), "", this);
_echoAction->setCheckable(true);
Q_Q(XLineEdit);
connect(_echoAction, &QAction::triggered, this, [=](bool checked) {
if (checked) {
_echoAction->setIcon(XIcon::getElaIcon(ElaIconType::EyeSlash, XThemeColor(eTheme->getThemeMode(), PrimaryNormal)));
q->setEchoMode(QLineEdit::Normal);
} else {
_echoAction->setIcon(XIcon::getElaIcon(ElaIconType::EyeSlash, XThemeColor(eTheme->getThemeMode(), BasicText)));
q->setEchoMode(QLineEdit::Password);
}
});
// 加到尾部
q->addAction(_echoAction, QLineEdit::TrailingPosition);
三、QLineEdit源代码
我们去框架源代码看看,先看看他是怎么设置清除按钮逻辑的。QLineEdit的源代码在D:\Work\Qt\5.15.2\Src\qtbase\src\widgets\widgets\qlineedit.cpp
,把我的安装路径替换成你的。
cpp
void QLineEdit::setClearButtonEnabled(bool enable)
{
#if QT_CONFIG(action)
Q_D(QLineEdit);
if (enable == isClearButtonEnabled())
return;
if (enable) {
QAction *clearAction = new QAction(d->clearButtonIcon(), QString(), this);
clearAction->setEnabled(!isReadOnly());
clearAction->setObjectName(QLatin1String(clearButtonActionNameC));
int flags = QLineEditPrivate::SideWidgetClearButton | QLineEditPrivate::SideWidgetFadeInWithText;
auto widgetAction = d->addAction(clearAction, nullptr, QLineEdit::TrailingPosition, flags);
widgetAction->setVisible(!text().isEmpty());
} else {
QAction *clearAction = findChild<QAction *>(QLatin1String(clearButtonActionNameC));
Q_ASSERT(clearAction);
d->removeAction(clearAction);
delete clearAction;
}
#else
Q_UNUSED(enable);
#endif // QT_CONFIG(action)
}
看到了嘛,创建了Action;如果是移除就找到已经创建的Action然后移除,因为默认情况下这个函数是可以随时调用的。
重点是下面这一句,进入了private代码
cpp
auto widgetAction = d->addAction(clearAction, nullptr, QLineEdit::TrailingPosition, flags);
private代码在D:\Work\Qt\5.15.2\Src\qtbase\src\widgets\widgets\qlineedit_p.cpp
cpp
QWidget *QLineEditPrivate::addAction(QAction *newAction, QAction *before, QLineEdit::ActionPosition position, int flags)
{
Q_Q(QLineEdit);
if (!newAction)
return nullptr;
if (!hasSideWidgets()) { // initial setup.
QObject::connect(q, SIGNAL(textChanged(QString)), q, SLOT(_q_textChanged(QString)));
lastTextSize = q->text().size();
}
QWidget *w = nullptr;
// Store flags about QWidgetAction here since removeAction() may be called from ~QAction,
// in which a qobject_cast<> no longer works.
#if QT_CONFIG(action)
if (QWidgetAction *widgetAction = qobject_cast<QWidgetAction *>(newAction)) {
if ((w = widgetAction->requestWidget(q)))
flags |= SideWidgetCreatedByWidgetAction;
}
#endif
if (!w) {
#if QT_CONFIG(toolbutton)
QLineEditIconButton *toolButton = new QLineEditIconButton(q);
toolButton->setIcon(newAction->icon());
toolButton->setOpacity(lastTextSize > 0 || !(flags & SideWidgetFadeInWithText) ? 1 : 0);
if (flags & SideWidgetClearButton) {
QObject::connect(toolButton, SIGNAL(clicked()), q, SLOT(_q_clearButtonClicked()));
#if QT_CONFIG(animation)
// The clear button is handled only by this widget. The button should be really
// shown/hidden in order to calculate size hints correctly.
toolButton->setHideWithText(true);
#endif
}
toolButton->setDefaultAction(newAction);
w = toolButton;
#else
return nullptr;
#endif
}
// QTBUG-59957: clear button should be the leftmost action.
if (!before && !(flags & SideWidgetClearButton) && position == QLineEdit::TrailingPosition) {
for (const SideWidgetEntry &e : trailingSideWidgets) {
if (e.flags & SideWidgetClearButton) {
before = e.action;
break;
}
}
}
// If there is a 'before' action, it takes preference
// There's a bug in GHS compiler that causes internal error on the following code.
// The affected GHS compiler versions are 2016.5.4 and 2017.1. GHS internal reference
// to track the progress of this issue is TOOLS-26637.
// This temporary workaround allows to compile with GHS toolchain and should be
// removed when GHS provides a patch to fix the compiler issue.
#if defined(Q_CC_GHS)
const SideWidgetLocation loc = {position, -1};
const auto location = before ? findSideWidget(before) : loc;
#else
const auto location = before ? findSideWidget(before) : SideWidgetLocation{position, -1};
#endif
SideWidgetEntryList &list = location.position == QLineEdit::TrailingPosition ? trailingSideWidgets : leadingSideWidgets;
list.insert(location.isValid() ? list.begin() + location.index : list.end(),
SideWidgetEntry(w, newAction, flags));
positionSideWidgets();
w->show();
return w;
}
这个代码比较长,就不一句句解读了,重点
在这一句:
cpp
SideWidgetEntryList &list = location.position == QLineEdit::TrailingPosition ? trailingSideWidgets : leadingSideWidgets;
其实就是两个位置QLineEdit::TrailingPosition
和QLineEdit::LeadingPosition
,很好理解就是头部
和尾部
。就是说你可以在头部和尾部添加Action,它其实头尾各有一个列表,理论上可以添加很多,但是我们用不了那么多。
Action的信号绑定可以在外面绑定,这样的话可以完全的自定义新Action行为。
总结
1、列表是有序的,后加入的Action总是在前加入的Action的后面
2、这种方式创建的行为会自动适配UI,不会和文字重叠,这一点很好,省去了适配的繁琐