I'm writing a custom TableModel in PyQt5, inheriting from QtCore.QAbstractTableModel. I want my Table to have one column with CheckBoxes only, no text, and one column with a PushButton in each row.
I tried to return a QPushButton object in the data method for the Qt.Display role, but apparently this is not possible.
So my question is: Can I implement in the Model itself that it returns certain widgets for certain cells? In my opinion, that is the job of the model, but how do I achieve this?
My second question would be how I have to assign the slots so that I know from which of the buttons (from which row) the action occurred?
Explanation:
Can I implement in the Model itself that it returns certain widgets for certain cells? In my opinion, that is the job of the model, but how do I achieve this?
It could be that the model provides the widgets but it is not common. In general, the model provides the information that will be displayed through a delegate, and is the delegate that can provide widgets. There is also the alternative of setting widgets on the unlinked view of the model using the setIndexWidget method.
Considering the first case, the solution is to implement a delegate.
how I have to assign the slots so that I know from which of the buttons (from which row) the action occurred?
In general the widgets that are used as editors so you must update the model, and then the model can notify other elements through the dataChanged signal as for example if the user wrote a text or changed the status of a checkbox, but in the case of the clicked not notify a permanent change but temporary. Considering this, the delegate could present a signal.
Solution:
from PyQt5 import QtCore, QtGui, QtWidgets
class PushButtonDelegate(QtWidgets.QStyledItemDelegate):
clicked = QtCore.pyqtSignal(QtCore.QModelIndex)
def paint(self, painter, option, index):
if (
isinstance(self.parent(), QtWidgets.QAbstractItemView)
and self.parent().model() is index.model()
):
self.parent().openPersistentEditor(index)
def createEditor(self, parent, option, index):
button = QtWidgets.QPushButton(parent)
button.clicked.connect(lambda *args, ix=index: self.clicked.emit(ix))
return button
def setEditorData(self, editor, index):
editor.setText(index.data(QtCore.Qt.DisplayRole))
def setModelData(self, editor, model, index):
pass
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QTableView()
model = QtGui.QStandardItemModel(0, 2)
for i in range(10):
it1 = QtGui.QStandardItem()
it1.setData(QtCore.Qt.Checked, QtCore.Qt.CheckStateRole)
it1.setFlags(it1.flags() | QtCore.Qt.ItemIsUserCheckable)
it2 = QtGui.QStandardItem()
it2.setData("text-{}".format(i), QtCore.Qt.DisplayRole)
model.appendRow([it1, it2])
# pass the view as parent
delegate = PushButtonDelegate(w)
w.setItemDelegateForColumn(1, delegate)
def on_clicked(index):
print(index.data())
delegate.clicked.connect(on_clicked)
w.setModel(model)
w.show()
sys.exit(app.exec_())
Related
I have a QLineEdit widget and I want to use the double click event on it. How can I do that?
def __init__(self):
#... other codes
self.title = QLineEdit()
self.title.returnPressed.connect(self.lockTitle)
#like this -> 'self.title.doubleClicked.connect(self.unlockTitle)'
#... other codes
def lockTitle(self):
self.title.setDisabled(True)
def unlockTitle(self):
self.title.setDisabled(False)
A possible solution is to create a custom QLineEdit by creating a new signal that is emitted in the mouseDoubleClickEvent method, but the problem in your case is that the QLineEdit is disabled and that method is not invoked so instead of using that method you should use the event method:
class LineEdit(QLineEdit):
doubleClicked = pyqtSignal()
def event(self, event):
if event.type() == QEvent.Type.MouseButtonDblClick:
self.doubleClicked.emit()
return super().event(event)
self.title = LineEdit()
self.title.returnPressed.connect(self.lockTitle)
self.title.doubleClicked.connect(self.unlockTitle)
Following the examples outlined in "Create simple GUI" I have tried to create Custom Widget, but nothing seems to by shown. It seems to be the simplest widget that I can imagine but still something is missing and I have no idea what.
from PyQt5.QtWidgets import *
import sys
class customWidget(QWidget):
def __init__(self, *args, **kwargs):
super(customWidget, self).__init__(*args, **kwargs)
layout = QHBoxLayout()
label = QLabel("Que chinga")
layout.addWidget(label)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("Esta locura")
label = customWidget()
self.setCentralWidget(label)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
Do any of the following in the __init__ of customWidget (they are the conceptually the same thing):
add self.setLayout(layout) after creating the layout;
change layout = QHBoxLayout() to layout = QHBoxLayout(self);
The reason is that you're not setting the layout for the widget, so the QLabel has no parent and won't be shown on its own.
In practice, the customWidget instance could know anything about those objects, so there's no reason for which it should show them.
I also suggest you to:
read about classes and instances: while it's not directly related to this issue, it has lots of aspects in common, since the parenthood relation of Qt objects (such as QWidget subclasses) is closely related to the OOP aspects of instances.
always use capitalized names for classes (CustomWidget, not customWidget), as lower cased names should only be used for variable and function names.
I'm tired about looking for the methods to deal with widgets within python class (Python 2.7 & PyQt4) who load file.ui (GUI QTDesigner)
CODE
form_class = uic.loadUiType("MyPythonProgram.ui")[0]
class MyWindowClass(QtGui.QMainWindow, form_class):
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self, parent)
self.setupUi(self)
self.btn_buscar.clicked.connect(self.buscar)
def addingResultsToQListView(self):
for item in SomeList:
self.listView.addItem(item) ###It's not correct, but cannot find the right one
def onListItemClicked():
getItem = listView.currentItem().text() ###It's not correct, but cannot find the right one
def buscar(self):
getEditText = self.textEdit.toPlainText()
### Don't know how to do this function. I want to get the edittext to search on some website and retrieve the results into a list. then the list will be added to QlistView (just found C++ methods, not for python)
#Finally
getEditText = '' ###After click on 'btn_buscar', want to clear this field
app = QtGui.QApplication(sys.argv)
MyWindow = MyWindowClass(None)
MyWindow.show()
app.exec_()
It could be helper to get some DOC, or some help about making python apps hybrid (Android if its possible), keeping .ui and .py layers separately as I'm trying to show you.
This is my .ui for more information:
QUESTION'
How could I bind python functions with elements on .ui? I was trying too much methods but didn't find the right one. Need to know how to deal with QlistView and Qedittext... Thanks
This is the way I do it: separate the classes between construction (loading) of the UI and changing its content.
form_class = uic.loadUiType("MyPythonProgram.ui")[0]
class MyWindowClass(QtGui.QMainWindow, form_class):
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self, parent)
self.setupUi(self)
class myGui:
def __init__(self):
self.gui = MyWindowClass() # that's the trick!
# self.addindResultsToQListView() # doesn't work, because I don't have your list items
self.gui.btn_buscar.clicked.connect(self.buscar)
self.editText = None
def show(self):
self.gui.show()
def addingResultsToQListView(self):
for item in SomeList: # you need to specify this `list` before this works!
self.gui.listView.addItems(item)
def buscar(self):
self.editText = self.gui.textEdit.text()
self.gui.textEdit.setText("")
app = QtGui.QApplication(sys.argv)
MyWindow = MyGui()
MyWindow.show()
app.exec_()
The trick is to reference the MyWindowClass, which is the constructor of your gui and hence the GUI itself, as an object within the class that controls the content of your GUI (myGui).
You call myGui on toplevel which then calls MyWindowClass as the object self.gui. From then on, whenever you want to address something in your GUI you name it self.gui. and add the QObject.
I also tried to understand what you want to do for the pushBotton. The content of your TextEdit (in PyQt they are called lineEdit btw) is stored in the variable self.editText which is initialized as None. Afterwards, the lineEdit is cleared from the user content.
Using PyQt4 + QT4.8.4, I'd like to drop (external) textual content to a widget defined as a PyQt4 plugin in QtDesigner
I use two python classes :
widgetlabelplugin.py inherited from QPyDesignerCustomWidgetPlugin
widgetlabel.py inherited from QLabel
Overriding the dropEvent in (widgetlabel.py), I'm able to retrieve "external textual content" and to set _model property.
I do the following steps :
Launch designer by previously setting PYQTDESIGNERPATH to the
.py path
Create a dialog without button
Drop a PyGMT/ WiddgetLabel on the dialog
Drop a "textual content" (from notepad) to the widgetlabel label
-> at this step, the label is updated on the dialog but not on the properties browser on the right
Save Dialog from Qt designer tool bar
-> ui file doesn't contain any "textual content" neither for QLabel/text, nor WidgetLabel/model
In Qt designer, If I select dialog background and reselect WidgetLabel, properties are updated in the browser, they are still not saved if I save the ui !
python class : widgetlabelplugin.py
# A demonstration custom widget plugin for PROJECT Qt Designer.
from PyQt4 import QtGui, QtDesigner
from widgetlabel import WidgetLabel
# This class implements the interface expected by Qt Designer to access the
# custom widget. See the description of the QDesignerCustomWidgetInterface
# class for full details.
class WidgetLabelPlugin(QtDesigner.QPyDesignerCustomWidgetPlugin):
# Initialise the instance.
def __init__(self, parent=None):
super(WidgetLabelPlugin, self).__init__(parent)
self._initialized = False
# Initialise the custom widget for use with the specified formEditor
# interface.
def initialize(self, formEditor):
if self._initialized:
return
self._initialized = True
# Return True if the custom widget has been intialised.
def isInitialized(self):
return self._initialized
# Return a new instance of the custom widget with the given parent.
def createWidget(self, parent):
return WidgetLabel(parent)
# Return the name of the class that implements the custom widget.
def name(self):
return "WidgetLabel"
# Return the name of the group to which the custom widget belongs. A new
# group will be created if it doesn't already exist.
def group(self):
return "PyGMT"
# Return the icon used to represent the custom widget in Designer's widget
# box.
def icon(self):
return QtGui.QIcon(":/designer/frame.png")
# Return a short description of the custom widget used by Designer in a
# tool tip.
def toolTip(self):
return "Satis demonstration widget"
# Return a full description of the custom widget used by Designer in
# "What's This?" help for the widget.
def whatsThis(self):
return "WidgetLabel is a demonstration custom widget written in Python " \
"using PyQt."
# Return True if the custom widget acts as a container for other widgets.
def isContainer(self):
return False
# Return the name of the module containing the class that implements the
# custom widget. It may include a module path.
def includeFile(self):
return "WidgetLabel"
python class : widgetlabel.py
#############################################################################
##
## This file is part of the examples of PROJECT Qt Designer.
##
#############################################################################
from PyQt4 import QtCore, QtGui
# This is the class that implements the custom widget for PROJECT.
class WidgetLabel(QtGui.QLabel):
#model changed signal
modelChanged = QtCore.pyqtSignal(QtCore.QString)
# Initialise the instance.
def __init__(self, parent=None):
super(WidgetLabel, self).__init__(parent)
self.setAcceptDrops(True)
self.setText("Label")
# Initialise the model property.
self._model = None
###########################################################################
# DRAG & DROP PART #
###########################################################################
def dragEnterEvent(self, e):
if e.mimeData().hasFormat("text/plain"):
e.setDropAction(QtCore.Qt.CopyAction)
e.accept()
else:
e.ignore()
def dragMoveEvent(self, event):
if event.mimeData().hasFormat("text/plain"):
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
else:
event.ignore
def dropEvent(self, e):
e.acceptProposedAction()
bStream = e.mimeData().retrieveData("text/plain",
QtCore.QVariant.ByteArray)
self.setModel(QtCore.QString(str(bStream.toByteArray())))
# The getter for the zoom property.
def getModel(self):
return self._model
# The setter for the model property. We also make define this as a Qt slot
# which can be connected to Qt signals in Qt Designer.
#QtCore.pyqtSlot(QtCore.QString)
def setModel(self, model):
# print "new model", model
# Set QLabel text
self.setText(model)
# Don't do anything if nothing has changed.
if self._model == model:
return
# Remember the new model level.
self._model = model
# Emit the Qt signal to say that the model level has changed.
self.modelChanged.emit(model)
# The resetter for the model property.
def resetModel(self):
self.setModel(None)
# Define the model property. Changing the value of this in Qt Designer's
# property editor causes the model to change dynamically.
model = QtCore.pyqtProperty(QtCore.QString, getModel, setModel, resetModel)
# Display the custom widget if the script is being run directly from the
# command line.
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
demo = WidgetLabel()
demo.show()
sys.exit(app.exec_())
Since it appears there is no "dedicated" Abstract Model "designed" purposely for QTreeView (for QListView there is QAbstractListModel and for QTableView there is QAbstractTableModel) and since I need to be able to display the headers I opted to use Table's Abstract model: QAbstractTableModel with 'QTreeView'. The code runs fine but if a plus signed is clicked it crashes immidiately. Shouldn't be QAbstractTableModel used with 'QTreeView'? What Abstract model to use?
import os,sys
from PyQt4 import QtCore, QtGui
app=QtGui.QApplication(sys.argv)
elements={'Animals':{1:'Bison',2:'Panther',3:'Elephant'},'Birds':{1:'Duck',2:'Hawk',3:'Pigeon'},'Fish':{1:'Shark',2:'Salmon',3:'Piranha'}}
class Model(QtCore.QAbstractTableModel):
def __init__(self):
QtCore.QAbstractListModel.__init__(self)
self.items=[]
self.modelDict={}
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.items)
def columnCount(self, index=QtCore.QModelIndex()):
return 3
def data(self, index, role):
if not index.isValid() or not (0<=index.row()<len(self.items)): return QtCore.QVariant()
if role==QtCore.Qt.DisplayRole: return QtCore.QVariant(self.items[index.row()])
def buildItems(self):
totalItems=self.rowCount()
for key in self.modelDict:
self.beginInsertRows(QtCore.QModelIndex(), totalItems+1, 0)
self.items.append(key)
self.endInsertRows()
class TreeView(QtGui.QTreeView):
def __init__(self):
super(TreeView, self).__init__()
self.model= Model()
self.model.modelDict=elements
self.model.buildItems()
self.setModel(self.model)
self.show()
window=TreeView()
sys.exit(app.exec_())
You cannot use QAbstractTableModel properly with a QTreeView, as that class is only for QTableView. You must instead inherit QAbstractItemModel, (which is what both QAbstractTableModel and QAbstractListModel inherit), and implement index(), parent(), rowCount(), columnCount(), and data(), as described in the subclassing section of Qt's fine manual. For a QTreeView, parent() in specific is very important to pay attention to, as it tells the QTreeView if an item is at the top level or if it's a child of another item in the tree.
I believe the primary motivation for not having a QAbstractTreeModel class in Qt is because you would need to override all of these methods to create a properly expressive tree model, already.