Maintain Selection of QComboBox under a QStyledItemDelegate - python

Below is an example of a custom delegated QComboBox. When I make a selection, click out (or otherwise lose focus with the QComboBox), and then TAB back in (gain focus), I lose my original selection. For example, using the below code if I choose "Item 2", click out, then TAB back in, the selection will go back to "Item 1."
How can I maintain the selection?
I am assuming this issue occurs because I am using addItem() in TheEditor QComboBox class every time it is initialized except I am not too sure how I should be approaching this method. Should I instead be initalizing TheEditor in the EditDelegate __ init __ class so that it is only initialized once and not every time it is focused? How might I do that properly?
import sys
from PySide import QtCore, QtGui, QtSql
class EditDelegate(QtGui.QStyledItemDelegate):
def __init__(self, parent=None):
super(EditDelegate, self).__init__(parent)
def createEditor(self, parent, option, index):
editor = TheEditor(parent)
return editor
class TheEditor(QtGui.QComboBox):
def __init__(self, parent=None):
super(TheEditor, self).__init__(parent)
self.addItem("Item 1")
self.addItem("Item 2")
self.addItem("Item 3")
self.setEditable(True)
class TheTable(QtGui.QTableWidget):
def __init__(self, columns, parent=None):
super(TheTable, self).__init__(parent)
self.setItemDelegate(EditDelegate())
self.setEditTriggers(QtGui.QAbstractItemView.AllEditTriggers)
self.setColumnCount(1)
self.setRowCount(1)
self.setHorizontalHeaderLabels(["QCombo"])
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setCentralWidget(TheTable(self))
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
frame = MainWindow()
frame.show()
app.exec_()
Note: PySide v1.2.0

As QT Introduction to (Model/View Programming) says
Note that we do not need to keep a pointer to the editor widget because the view takes responsibility for destroying it when it is no longer needed.
editor is temporary object. But you can try catching selection from old editor and pass it to new editor like this:
class EditDelegate(QtGui.QStyledItemDelegate):
editorsLastIndex=None
def __init__(self, parent=None):
super(EditDelegate, self).__init__(parent)
def createEditor(self, parent, option, index):
editor = TheEditor(parent)
if self.editorsLastIndex != None:
editor.setCurrentIndex(self.editorsLastIndex)
editor.currentIndexChanged.connect(self.editorIndexChanged)
return editor
def editorIndexChanged(self, index):
self.editorsLastIndex = index

Related

The signal and slot within custom class including PyQt QWidget not working

I have some trouble customizing a class including a QPushButton and QLabel, and I just want to set the QPushButton checkable and define a slot to its toggled signal, in addition, the custom class inherents QObject.
The code is shown below,
import sys
from PyQt5 import QtGui
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import pyqtSlot, QObject
class CustomButton(QPushButton):
def __init__(self, label='', parent=None):
super().__init__(label, parent)
self.setCheckable(True)
self.toggled.connect(self.update)
def update(self, state):
if state:
self.setStyleSheet('background-color: green')
else:
self.setStyleSheet('background-color: red')
class ButtonCtrl(QObject):
def __init__(self, parent=None, label=''):
super().__init__()
if isinstance(parent, QLayout):
col = QVBoxLayout()
parent.addLayout(col)
else:
col = QVBoxLayout(parent)
self.text = ['ON', 'OFF']
self.label = QLabel(label)
self.button = QPushButton('ON')
# self.button = CustomButton('ON')
col.addWidget(self.label)
col.addWidget(self.button)
self.button.setCheckable(True)
self.button.setChecked(True)
self.button.toggled.connect(self.update)
self.update(True)
self.label.setFont(QFont('Microsoft YaHei', 14))
self.button.setFont(QFont('Microsoft YaHei', 12, True))
self.button.toggle()
# #pyqtSlot(bool)
def update(self, state):
if state:
self.button.setText(self.text[0])
self.button.setStyleSheet('background-color: green')
else:
self.button.setText(self.text[-1])
self.button.setStyleSheet('background-color: red')
class Window(QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
# set the layout
layout = QVBoxLayout(self)
but = ButtonCtrl(layout, "Test")
#self.but = ButtonCtrl(layout, "Test")
btn = CustomButton()
layout.addWidget(btn)
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle('Fusion')
main = Window()
main.show()
sys.exit(app.exec_())
I have customized two buttons, named CustomButton(QPushButton) and ButtonCtrl(QObject), and I have tested the slot in the main window, however the background update slot works for CustomButton(QPushbutton) and does not work for ButtonCtrl(QObject), the slot function is not even invoked.
However, if I change the button member of ButtonCtrl(QObject) from QPushButton into my CustomButton(QPushButton), the it will work well in the main window. Furthermore, if the but in main window becomes a member of the main window class by setting self.but=ButtonCtrl(layout, "Test"), it will work as well.
I didn't find direct answer to it in Qt documentation which explains that
Signals are emitted by an object when its internal state has changed in some way that might be interesting to the object's client or owner. Signals are public access functions and can be emitted from anywhere, but we recommend to only emit them from the class that defines the signal and its subclasses.
I am not sure if the lifetime of the but causing this effect, hope to get an answer, thank you.
The problem is simple: The ButtonCtrl class object has a local scope so it will be destroyed, and why doesn't the same happen with the CustomButton class object? Well, because the ownership of a QWidget has its parent, and the parent of that button is the window, instead the ButtonCtrl object does not have it. In this case the solution is to extend the life cycle of the variable, and in the case of a QObject there are several options:
make the class member variable,
Place it in a container with a longer life cycle, or
establish a QObject parent with a longer life cycle.
Using the third alternative is just to change to:
class ButtonCtrl(QObject):
def __init__(self, parent=None, label=''):
super().__init__(parent)
# ...
The first option is the one commented on in your code:
self.but = ButtonCtrl(layout, "Test")
and the second is similar:
self.container = list()
but = ButtonCtrl(layout, "Test")
self.container.append(but)

How to know which qwidget in a ui form got the focus in pyqt

I am new to PyQt. I designed a form in QtDeveloper which have three controls. One push button, one combo box and one line edit. The name of the line edit widget in my ui form is myLineEdit. I want to know which Qwidget got focus (QLineEdit or QComboBox). I implement the code obtained from internet. When the code run, a separate line edit is created and it works fine. But I want to give the focusInEvent to myLineEdit widget created in the .ui form. My code is given. Please help.
class MyLineEdit(QtGui.QLineEdit):
def __init__(self, parent=None):
super(MyLineEdit, self).__init__(parent)
def focusInEvent(self, event):
print 'focus in event'
self.clear()
QLineEdit.focusInEvent(self, QFocusEvent(QEvent.FocusIn))
class MainWindow(QtGui.QMainWindow,Ui_MainWindow):
def __init__(self, parent = None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.myLineEdit = MyLineEdit(self)
You must implement the eventFilter method and enable this property to the widgets that are needed with:
{your widget}.installEventFilter(self)
The eventFilter method has as information the object and type of event.
Example
import sys
from PyQt5 import uic
from PyQt5.QtCore import QEvent
from PyQt5.QtWidgets import QApplication, QWidget
uiFile = "widget.ui" # Enter file here.
Ui_Widget, _ = uic.loadUiType(uiFile)
class Widget(QWidget, Ui_Widget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent=parent)
self.setupUi(self)
self.lineEdit.installEventFilter(self)
self.pushButton.installEventFilter(self)
self.comboBox.installEventFilter(self)
def eventFilter(self, obj, event):
if event.type() == QEvent.FocusIn:
if obj == self.lineEdit:
print("lineedit")
elif obj == self.pushButton:
print("pushbutton")
elif obj == self.comboBox:
print("combobox")
return super(Widget, self).eventFilter(obj, event)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
Ouput:
lineedit
pushbutton
combobox
pushbutton
lineedit

PySide make QDialog appear in main window

I've created an app which has an main window and the possibility to open an dialog (question, error and so on). I'm not using QMessageBox.warning() or QMessageBox.question() and so on because I wanted to customize the dialogs a bit.
But every time I open a new Dialog, in the Windows task bar (I'm working on Windows 10) a new 'tab' is opened, which is a little bit annoying.
My code (shortened):
from PySide import QtCore, QtGui
import sys
class MessageBox:
def __init__(self, title, message):
msg = QtGui.QMessageBox()
flags = QtCore.Qt.Dialog
flags |= QtCore.Qt.CustomizeWindowHint
flags |= QtCore.Qt.WindowTitleHint
msg.setWindowFlags(flags)
msg.setWindowTitle(title)
msg.setText(message)
msg.exec_()
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.show()
MessageBox("Title", "My message here")
if __name__ == "__main__":
app = QtGui.QApplication([])
window = MainWindow()
sys.exit(app.exec_())
Note: Normally, the dialog is called from an menu or button.
Question: How can I make the dialog appear in the main window without creating a new 'task bar tab'?
The solution was quite simple: Passing an reference of QMainWindow to the constructor of QDialog will do the job, e.g:
class MessageBox(QtGui.QDialog):
def __init__(self, parent, title, message, icon="info"):
super(MessageBox, self).__init__(parent)
...
and then calling the dialog from an class that inherits from QMainWindow:
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
#connect button with function, e.g.:
mybutton.clicked.connect(self.open_dialog)
def open_dialog(self):
MessageBox(self)
Maybe this helps anyone!
If you set the parent of the QDialog to the window, it will only show as one item on the task bar. This is generally the first argument to QMessageBox.
class MessageBox:
def __init__(self, parent, title, message):
msg = QtGui.QMessageBox(parent)
Also, if you really want to create a custom dialog, you might as well just subclass from QDialog.

Pyside - Select all text when QLineEdit gets focus

I am new to Qt/PySide. I want QLineEdit to select all text in it when it gets focus. After getting focus and selecting all text, it should select all text only after focus is lost and gained again. It should not select all text when I change cursor position after QLineEdit gains focus. How do I do that?
Update: My current code improved as suggested by Ashwani Kumar. I still can't get it to work though:
import sys
from PySide.QtGui import QLineEdit, QApplication, QVBoxLayout, QWidget
class MyLineEdit(QLineEdit):
def __init__(self, parent=None):
super(MyLineEdit, self).__init__(parent)
def focusInEvent(self, e):
self.selectAll()
app = QApplication(sys.argv)
top = QWidget()
layout = QVBoxLayout()
layout.addWidget(MyLineEdit())
layout.addWidget(MyLineEdit())
top.setLayout(layout)
top.show()
app.exec_()
With focusInEvent, when you click the widget, it gets executed, but since you click, it removes the selected text.
To overcome this, we must use the mousePressEvent, this can be done two ways:
import sys
from PySide.QtGui import QLineEdit, QApplication, QVBoxLayout, QWidget
class MyLineEdit(QLineEdit):
def __init__(self, parent=None):
super(MyLineEdit, self).__init__(parent)
def mousePressEvent(self, e):
self.selectAll()
app = QApplication(sys.argv)
top = QWidget()
layout = QVBoxLayout()
layout.addWidget(MyLineEdit())
layout.addWidget(MyLineEdit())
top.setLayout(layout)
top.show()
app.exec_()
Or you can do it by simply overriding the base QLineEdit class:
txt_demo = QtGui.QLineEdit()
txt_demo.mousePressEvent = lambda _ : txt_demo.selectAll()
However, since we are modifying the mousePressEvent, whenever you try to click text, it will always select all first.
For future visitors, I am posting code that is working for me. As I am a newbie I am not sure if the code contains any malpractices. If it does feel free to comment and I'll update my code/answer. Code:
import sys
from PySide.QtGui import QLineEdit, QApplication, QVBoxLayout, QWidget
class LineEdit(QLineEdit):
def __init__(self, parent=None):
super(LineEdit, self).__init__(parent)
self.readyToEdit = True
def mousePressEvent(self, e, Parent=None):
super(LineEdit, self).mousePressEvent(e) #required to deselect on 2e click
if self.readyToEdit:
self.selectAll()
self.readyToEdit = False
def focusOutEvent(self, e):
super(LineEdit, self).focusOutEvent(e) #required to remove cursor on focusOut
self.deselect()
self.readyToEdit = True
app = QApplication(sys.argv)
top = QWidget()
layout = QVBoxLayout()
layout.addWidget(LineEdit())
layout.addWidget(LineEdit())
top.setLayout(layout)
top.show()
app.exec_()
You have to subclass the QLineEdit and then use the new class instead of QLineEdit.
e.g: -
class MyLineEdit(QtGui.QLineEdit):
def __init__(self, parent=None)
super(MyLineEdit, self).__init__(parent)
def focusInEvent(self, e):
self.selectAll()
lineedit = MyLineEdit()
QTimer solution as seen on QtCentre:
import types
from PyQt4 import QtCore
def bind(func, to):
"Bind function to instance, unbind if needed"
return types.MethodType(func.__func__ if hasattr(func, "__self__") else func, to)
...
self.txtSrc.focusInEvent = bind(lambda w, e: QtCore.QTimer.singleShot(0, w.selectAll), self.txtSrc)
Also the provided solution doesn't require to subclass QLineEdit.
These answers don't really provide the sort of standard ergonomics you'd probably want (and users might expect). For example, if you single-click once on a QLE which is not currently all-selected, and then single-click again, typically you'd want the first click to select-all, and the second click to allow you to place the cursor in the specific spot you have chosen.
This can be achieved simply by doing this:
def mousePressEvent(self, event):
already_select_all = self.text() == self.selectedText()
super().mousePressEvent(event)
if not already_select_all:
self.selectAll()
The question in fact asks about gaining focus, not specifically by mouse-clicking, and indeed, if you are a keyboardist or generally musophobic you'll probably also want the whole text to be selected any time the QLE gains focus, e.g. by tabbing or by use of a QLabel "buddy" mnemonic. This seems to do the job:
class MyLineEdit(QtWidgets.QLineEdit):
def __init__(self, *args):
super().__init__(*args)
self.focus_in_reason = None
def focusInEvent(self, event):
super().focusInEvent(event)
self.selectAll()
self.focus_in_reason = event.reason()
def mousePressEvent(self, event):
super().mousePressEvent(event)
if self.focus_in_reason == QtCore.Qt.MouseFocusReason:
self.selectAll()
self.focus_in_reason = None

Passing parameter from main window to pop-up Qdialog window

I have Qdialog with that I open from main window:
Dialog = myDialog(self)
Here is the code from new opened Dialog:
class myDialog(QtGui.QDialog, Ui_DialogCalibration):
def __init__(self, parent=None):
super(myDialog, self).__init__(parent)
self.setupUi(self)
How can pass parameter(lint) from main window to this new window, something like
Dialog = myDialog(self, listInformation)
and then in myDialog class use that list
class myDialog(QtGui.QDialog, Ui_DialogCalibration):
def __init__(self, parent=None, listInfo):
super(myDialog, self).__init__(parent)
self.listInfo = listInfo
self.setupUi(self)
Then, when you go to create the myDialog, you can add the list as a parameter. When you need to use it inside of your myDialog, you would access it as self.listInfo.
EDIT: To further expand on the comments:
If you have def __init__(self, parent=None, listInfo=None), you would call it as Dialog = myDialog(parent=self, listInfo=listInfo). If you had it as def __init__(self, parent, listInfo) you would do Dialog = myDialog(self, listInfo). Hopefully you see the pattern here.

Categories

Resources