Issue
I am trying to set the forbidden cursor to a dynamically enabled/disabled line edit. But it does not seem to work at all.
from PySide6.QtCore import Qt
from PySide6.QtGui import QCursor
def toggle_line_edit(self, switch_type: SwitchType):
match switch_type:
case SwitchType.One:
self.ui.line_edit.setCursor(QCursor(Qt.ForbiddenCursor))
self.ui.line_edit.setDisabled(True)
case SwitchType.Two:
self.ui.line_edit.setCursor(QCursor(Qt.IBeamCursor))
self.ui.line_edit.setDisabled(False)
The reason I need this behaviour is because there is a kind of expert mode that unlocks certain fields when activated. This is activated via a radio button group in the UI.
My goal would be that when the expert mode is deactivated, all inputs that require expert mode are "deactivated" in a way and that you are told with the cursor as well as with a tool tip that you can only change these fields when you activate the expert mode.
Is there something I miss?
Solution
This is not just about QLineEdit: any disabled widget will not show its custom cursor whenever it is disabled.
That's for a valid reason, at least from a generic point of view: a disabled widget does not provide any user interaction (keyboard and mouse) so there is no point in showing a different cursor that could make the user believe that the widget is, in fact, enabled.
Note that, while you could make the QLineEdit read only, that wouldn't prevent some level of interaction: for instance, it would still provide text selection with the mouse and even with the keyboard (after it has been clicked or reached using Tab).
A possible alternative would be to completely prevent any interaction; the focus capability can be cleared by setting the policy to NoFocus
, while mouse interaction requires ignoring all mouse events. For simplicity, it's better to use a subclass:
class FakeDisabledLineEdit(QLineEdit):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setCursor(Qt.ForbiddenCursor)
self.setFocusPolicy(Qt.NoFocus)
def event(self, event):
if isinstance(event, QMouseEvent):
return True
return super().event(event)
Note: you seem to be using a UI created from Designer, so, in order to use such a subclass in your UI you will need widget promotion. Consider doing some research on the subject, starting from this post of mine.
The only problem might be that the widget would still appear as enabled, so a possible (dirty) workaround could be to temporarily set the WA_Disabled
flag in the paint event. This will make Qt believe that the widget is disabled, and let the style draw its contents accordingly:
class FakeDisabledLineEdit(QLineEdit):
# ... as above
def paintEvent(self, event):
enabled = self.isEnabled()
if enabled:
self.setAttribute(Qt.WA_Disabled, True)
super().paintEvent(event)
if enabled:
self.setAttribute(Qt.WA_Disabled, False)
Edit based on comments
Since you want to show a visible "hint" that the field can, in fact, become enabled depending on other elements of the UI, there is another possibility.
You can make the line edit as read only, and use the placeholderText
property to show that hint; disabling the focus as explained above will prevent any editing until the line edit becomes "enabled".
A basic implementation could just override the setReadOnly()
and do everything required in there, after calling the base implementation:
class FakeDisabledLineEdit(QLineEdit):
def setReadOnly(self, readOnly):
super().setReadOnly(readOnly)
if readOnly:
self.setCursor(Qt.ForbiddenCursor)
self.setFocusPolicy(Qt.NoFocus)
self.clear()
self.setPlaceholderText('Click the checkbox to enable')
else:
self.setCursor(Qt.IBeamCursor)
self.setFocusPolicy(Qt.StrongFocus)
self.setPlaceholderText('')
That could become problematic whenever you do have a placeholder text for that field, and even if you want to restore the text.
That would require a more comprehensive approach, but it's still feasible. Here is an example that shows the required behavior while allowing both the "normal" placeholder text and restoring the previously set text:
class FakeDisabledLineEdit(QLineEdit):
_text = ''
_readOnlyText = ''
_placeholderText = ''
def __init__(self, *args, **kwargs):
if 'readOnlyText' in kwargs:
self._readOnlyText = kwargs.pop('readOnlyText')
super().__init__(*args, **kwargs)
self._text = self.text()
self._placeholderText = self.placeholderText()
if self.isReadOnly() and self._readOnlyText:
self.setReadOnly(True)
@Property(str)
def readOnlyText(self):
return self._readOnlyText
@readOnlyText.setter
def readOnlyText(self, text):
if self._readOnlyText != text:
self._readOnlyText = text
if self.isReadOnly():
super().setPlaceholderText(text or self._placeholderText)
def setReadOnlyText(self, text):
self.readOnlyText = text
def setPlaceholderText(self, text):
self._placeholderText = text
if self.isReadOnly():
text = self._readOnlyText
super().setPlaceholderText(text)
def setReadOnly(self, readOnly):
super().setReadOnly(readOnly)
if readOnly:
with QSignalBlocker(self):
self.setCursor(Qt.ForbiddenCursor)
self.setFocusPolicy(Qt.NoFocus)
self._text = self.text()
self.clear()
placeholderText = self._readOnlyText
else:
self.setCursor(Qt.IBeamCursor)
self.setFocusPolicy(Qt.StrongFocus)
self.setText(self._text)
placeholderText = self._placeholderText
super().setPlaceholderText(placeholderText)
def setText(self, text):
if self._text == text:
return
self._text = text
if self.isReadOnly():
with QSignalBlocker(self): # avoid textChanged signal emit
super().setText(self._readOnlyText)
else:
super().setText(text)
And here is a test that shows how the above works:
app = QApplication([])
win = QWidget()
placeholderEdit = QLineEdit('Placeholder text if editable',
placeholderText='Set placeholder')
readOnlyEdit = QLineEdit('Click the checkbox to enable',
placeholderText='Read only text')
check = QCheckBox('Enable field')
field = FakeDisabledLineEdit(
readOnly=True, placeholderText=placeholderEdit.text(),
readOnlyText=readOnlyEdit.text())
layout = QVBoxLayout(win)
layout.addWidget(placeholderEdit)
layout.addWidget(readOnlyEdit)
layout.addWidget(check)
layout.addWidget(QFrame(frameShape=QFrame.HLine))
layout.addWidget(QLabel('Custom field:'))
layout.addWidget(field)
placeholderEdit.textChanged.connect(field.setPlaceholderText)
readOnlyEdit.textChanged.connect(field.setReadOnlyText)
check.toggled.connect(lambda checked: field.setReadOnly(not checked))
win.show()
app.exec()
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.