'PyQt LineEdit QAction change icon on checked/unchecked
I need to add a button to QLineEdit, set it checkable and change icon according to checked/unchecked state. I do this that way:
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(os.path.join("Images", "L.png")), QtGui.QIcon.Normal, QtGui.QIcon.On)
icon.addPixmap(QtGui.QPixmap(os.path.join("Images", "Home.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
searchActionBttn = QtWidgets.QAction("None", self.searchIn)
searchActionBttn.triggered.connect(lambda: print(searchActionBttn.isChecked()))
searchActionBttn.setCheckable(True)
searchActionBttn.setIcon(icon)
self.searchIn.addAction(searchActionBttn, QtWidgets.QLineEdit.LeadingPosition)
But icon didn't change when I click on it.
Solution 1:[1]
The problem is caused because the QToolButton associated with the QAction has its custom paintEvent method so it does not take into account the state of the checkbox
// https://code.qt.io/cgit/qt/qtbase.git/tree/src/widgets/widgets/qlineedit_p.cpp#n354
void QLineEditIconButton::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QWindow *window = qt_widget_private(this)->windowHandle(QWidgetPrivate::WindowHandleMode::Closest);
QIcon::Mode state = QIcon::Disabled;
if (isEnabled())
state = isDown() ? QIcon::Active : QIcon::Normal;
const QLineEditPrivate *lep = lineEditPrivate();
const int iconWidth = lep ? lep->sideWidgetParameters().iconSize : 16;
const QSize iconSize(iconWidth, iconWidth);
const QPixmap iconPixmap = icon().pixmap(window, iconSize, state, QIcon::Off);
QRect pixmapRect = QRect(QPoint(0, 0), iconSize);
pixmapRect.moveCenter(rect().center());
painter.setOpacity(m_opacity);
painter.drawPixmap(pixmapRect, iconPixmap);
}
(emphasis mine)
As you can see, the state only changes while the button is pressed, so the option of using a QAction should be discarded.
Another option is to set the QToolButton directly through a layout
import os
from functools import partial
from PyQt5 import QtCore, QtGui, QtWidgets
class LineEdit(QtWidgets.QLineEdit):
@property
def _internal_layout(self):
if not hasattr(self, "_internal_layout_"):
self._internal_layout_ = QtWidgets.QHBoxLayout(self)
self._internal_layout_.addStretch()
self._internal_layout_.setContentsMargins(2, 2, 2, 2)
return self._internal_layout_
def add_button(self, button):
self._internal_layout.insertWidget(self._internal_layout.count() - 2, button)
QtCore.QTimer.singleShot(0, partial(self._fix_cursor_position, button))
button.setFocusProxy(self)
def _fix_cursor_position(self, button):
self.setTextMargins(button.geometry().right(), 0, 0, 0)
app = QtWidgets.QApplication([])
icon = QtGui.QIcon()
icon.addPixmap(
QtGui.QPixmap(os.path.join("Images", "L.png")), QtGui.QIcon.Normal, QtGui.QIcon.On
)
icon.addPixmap(
QtGui.QPixmap(os.path.join("Images", "Home.png")),
QtGui.QIcon.Normal,
QtGui.QIcon.Off,
)
button = QtWidgets.QToolButton()
button.setStyleSheet("border: none")
button.setCheckable(True)
button.setIcon(icon)
searchIn = LineEdit()
searchIn.add_button(button)
searchIn.show()
app.exec_()
Solution 2:[2]
You can have 2 actions and use removeAction/addAction to toggle between them
icon_on = QtGui.QIcon()
icon_on.addPixmap(QtGui.QPixmap(os.path.join("Images", "L.png")))
searchActionBttn_on = QtWidgets.QAction("None", self.searchIn)
searchActionBttn.setIcon(icon_on)
icon_off = QtGui.QIcon()
icon_off.addPixmap(QtGui.QPixmap(os.path.join("Images", "Home.png")))
searchActionBttn_off = QtWidgets.QAction("None", self.searchIn)
searchActionBttn.setIcon(icon_off)
self.searchIn.addAction(searchActionBttn, QtWidgets.QLineEdit.LeadingPosition)
def toggle(to_remove, to_add):
self.searchIn.addRemove(to_remove)
self.searchIn.addAction(to_add, QtWidgets.QLineEdit.LeadingPosition)
searchActionBttn_on.triggered.connect(lambda: toggle(searchActionBttn_on, searchActionBttn_off)
searchActionBttn_off.triggered.connect(lambda: toggle(searchActionBttn_off, searchActionBttn_on)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | eyllanesc |
| Solution 2 | Nico_Ool |
