I want to use CTRL+C to copy data from QTableWidget. To do this I have sublclassed QTableWidget and overridden the keyPressEvent() method, which works well. However this looses useful keyPressEvent() functionality such as using direction keys to scroll through the table.
Is there a way to inherent the original method will implementing some additional functionality?
class SubQTableWidget(QtWidgets.QTableWidget):
def __init__(self, parent=None):
QtWidgets.QTableWidget.__init__(self, parent)
def keyPressEvent(self, event):
# inheret original keyPressEvent() functionality?
if (event.type() == QtCore.QEvent.KeyPress and
event.matches(QtGui.QKeySequence.Copy)):
self.copy_selection()
To override a method you have to understand if it conflicts with the new functionality. In this case the keyPressEvent method of QTableWidget does not conflict with the CTRL+C shorcut since they do nothing by default with that key so to avoid losing the previous functionality then you must call the parent's super method:
def keyPressEvent(self, event):
super(SubQTableWidget, self).keyPressEvent(event)
if event.matches(QtGui.QKeySequence.Copy)):
self.copy_selection()
If you want to handle CTRL+C in a simple way then you can use a QShortcut so there is no need to override the keyPressEvent method:
class SubQTableWidget(QtWidgets.QTableWidget):
def __init__(self, parent=None):
super(SubQTableWidget, self).__init__(parent)
QtWidgets.QShortcut(
QtGui.QKeySequence(QtGui.QKeySequence.Copy),
self,
activated=self.copy_selection,
context=QtCore.Qt.WidgetShortcut
)
Related
I have a QTreeView with a QStyledItemDelegate inside of it. When a certain action occurs to the delegate, its size is supposed to change. However I haven't figured out how to get the QTreeView's rows to resize in response to the delegate's editor size changing. I tried QTreeView.updateGeometry and QTreeView.repaint and a couple other things but it doesn't seem to work. Could someone point me in the right direction?
Here's a minimal reproduction (note: The code is hacky in a few places, it's just meant to be a demonstration of the problem, not a demonstration of good MVC).
Steps:
Run the code below
Press either "Add a label" button
Note that the height of the row in the QTreeView does not change no matter how many times either button is clicked.
from PySide2 import QtCore, QtWidgets
_VALUE = 100
class _Clicker(QtWidgets.QWidget):
clicked = QtCore.Signal()
def __init__(self, parent=None):
super(_Clicker, self).__init__(parent=parent)
self.setLayout(QtWidgets.QVBoxLayout())
self._button = QtWidgets.QPushButton("Add a label")
self.layout().addWidget(self._button)
self._button.clicked.connect(self._add_label)
self._button.clicked.connect(self.clicked.emit)
def _add_label(self):
global _VALUE
_VALUE += 10
self.layout().addWidget(QtWidgets.QLabel("Add a label"))
self.updateGeometry() # Note: I didn't expect this to work but added it regardless
class _Delegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
widget = _Clicker(parent=parent)
viewer = self.parent()
widget.clicked.connect(viewer.updateGeometries) # Note: I expected this to work
return widget
def paint(self, painter, option, index):
super(_Delegate, self).paint(painter, option, index)
viewer = self.parent()
if not viewer.isPersistentEditorOpen(index):
viewer.openPersistentEditor(index)
def setEditorData(self, editor, index):
pass
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
def sizeHint(self, option, index):
hint = index.data(QtCore.Qt.SizeHintRole)
if hint:
return hint
return super(_Delegate, self).sizeHint(option, index)
class _Model(QtCore.QAbstractItemModel):
def __init__(self, parent=None):
super(_Model, self).__init__(parent=parent)
self._labels = ["foo", "bar"]
def columnCount(self, parent=QtCore.QModelIndex()):
return 1
def data(self, index, role):
if role == QtCore.Qt.SizeHintRole:
return QtCore.QSize(200, _VALUE)
if role != QtCore.Qt.DisplayRole:
return None
return self._labels[index.row()]
def index(self, row, column, parent=QtCore.QModelIndex()):
child = self._labels[row]
return self.createIndex(row, column, child)
def parent(self, index):
return QtCore.QModelIndex()
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return len(self._labels)
application = QtWidgets.QApplication([])
view = QtWidgets.QTreeView()
view.setModel(_Model())
view.setItemDelegate(_Delegate(parent=view))
view.show()
application.exec_()
How do I get a single row in a QTreeView, which has a persistent editor applied already to it, to tell Qt to resize in response to some change in the editor?
Note: One possible solution would be to close the persistent editor and re-open it to force Qt to redraw the editor widget. This would be generally very slow and not work in my specific situation. Keeping the same persistent editor is important.
As the documentation about updateGeometries() explains, it:
Updates the geometry of the child widgets of the view.
This is used to update the widgets (editors, scroll bars, headers, etc) based on the current view state. It doesn't consider the editor size hints, so that call or the attempt to update the size hint is useless (and, it should go without saying, using global for this is wrong).
In order to properly notify the view that a specific index has updated its size hint, you must use the delegate's sizeHintChanged signal, which should also be emitted when the editor is created in order to ensure that the view makes enough room for it; note that this is normally not required for standard editors (as, being they temporary, they should not try to change the layout of the view), but for persistent editors that are potentially big, it may be necessary.
Other notes:
calling updateGeometry() on the widget is pointless in this case, as adding a widget to a layout automatically results in a LayoutRequest event (which is what updateGeometry() does, among other things);
as explained in createEditor(), "the view's background will shine through unless the editor paints its own background (e.g., with setAutoFillBackground())";
the SizeHintRole of the model should always return a size important for the model (if any), not based on the editor; it's the delegate responsibility to do that, and the model should never be influenced by any of its views;
opening a persistent editor in a paint event is wrong; only drawing related aspects should ever happen in a paint function, most importantly because they are called very often (even hundreds of times per second for item views) so they should be as fast as possible, but also because doing anything that might affect a change in geometry will cause (at least) a recursive call;
signals can be "chained" without using emit: self._button.clicked.connect(self.clicked) would have sufficed;
Considering all the above, there are two possibilities. The problem is that there is no direct correlation between the editor widget and the index it's referred to, so we need to find a way to emit sizeHintChanged with its correct index when the editor is updated.
This can only be done by creating a reference of the index for the editor, but it's important that we use a QPersistentModelIndex for that, as the indexes might change while a persistent editor is opened (for example, when sorting or filtering), and the index provided in the arguments of delegate functions is not able to track these changes.
Emit a custom signal
In this case, we only use a custom signal that is emitted whenever we know that the layout is changed, and we create a local function in createEditor that will eventually emit the sizeHintChanged signal by "reconstructing" the valid index:
class _Clicker(QtWidgets.QWidget):
sizeHintChanged = QtCore.Signal()
def __init__(self, parent=None):
super().__init__(parent)
self.setAutoFillBackground(True)
layout = QtWidgets.QVBoxLayout(self)
self._button = QtWidgets.QPushButton("Add a label")
layout.addWidget(self._button)
self._button.clicked.connect(self._add_label)
def _add_label(self):
self.layout().addWidget(QtWidgets.QLabel("Add a label"))
self.sizeHintChanged.emit()
class _Delegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
widget = _Clicker(parent)
persistent = QtCore.QPersistentModelIndex(index)
def emitSizeHintChanged():
index = persistent.model().index(
persistent.row(), persistent.column(),
persistent.parent())
self.sizeHintChanged.emit(index)
widget.sizeHintChanged.connect(emitSizeHintChanged)
self.sizeHintChanged.emit(index)
return widget
# no other functions implemented here
Use the delegate's event filter
We can create a reference for the persistent index in the editor, and then emit the sizeHintChanged signal in the event filter of the delegate whenever a LayoutRequest event is received from the editor:
class _Clicker(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setAutoFillBackground(True)
layout = QtWidgets.QVBoxLayout(self)
self._button = QtWidgets.QPushButton("Add a label")
layout.addWidget(self._button)
self._button.clicked.connect(self._add_label)
def _add_label(self):
self.layout().addWidget(QtWidgets.QLabel("Add a label"))
class _Delegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
widget = _Clicker(parent)
widget.index = QtCore.QPersistentModelIndex(index)
return widget
def eventFilter(self, editor, event):
if event.type() == event.LayoutRequest:
persistent = editor.index
index = persistent.model().index(
persistent.row(), persistent.column(),
persistent.parent())
self.sizeHintChanged.emit(index)
return super().eventFilter(editor, event)
Finally, you should obviously remove the SizeHintRole return in data(), and in order to open all persistent editors you could do something like this:
def openEditors(view, parent=None):
model = view.model()
if parent is None:
parent = QtCore.QModelIndex()
for row in range(model.rowCount(parent)):
for column in range(model.columnCount(parent)):
index = model.index(row, column, parent)
view.openPersistentEditor(index)
if model.rowCount(index):
openEditors(view, index)
# ...
openEditors(view)
I had a similar problem when I was adding new widgets to a QFrame, because the dumb thing was not updating the value of its sizeHint( ) after adding each new widget. It seems that QWidgets (including QFrames) only update their sizeHint( ) when the children widgets are "visible". Somehow, in some occassions Qt sets new children to "not visible" when they are added, don't ask me why. You can see if a widget is visible by calling isVisible( ), and change its visibility status with setVisible(...). I solved my problem by telling to the QFrame that they new child widgets were intended to be visible, by calling setVisible( True ) with each of each child after adding them to the QFrame. Some Qt fundamentalists may say that this is a blasphemous hack that breaks the fabric of space time or something and that I should be burnt at the stake, but I don't care, it works, and it works very well in a quite complex GUI that I have built.
I am working on a project involving PyQt5, and I am struggling with managing inheritance between widgets.
I have one QWidget screen that inherits off QtWidgets.QWidget and another class which is generated by QtDesigner. It reads something like this:
class a(QtWidgets.QWidget, Ui_a):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
self.setupUi(self)
<some attributes>
<some functions
Here, I inherit off Ui_a, a separate class stored in a generated file, and I can call setupUi (a method of Ui_a) fine.
I now want to create another class b, which also is a QWidget that needs to be displayed. This class b requires the use of some of the functions and attributes from class a. I can easily just copy paste the required stuff in but that is bad practice so I am looking for a more neat solution. If I do the code:
class b(QtWidgets.QWidget, Ui_b, a):
def __init__(self, parent=None):
QtWidgets.QWidget.__init(self, parent)
self.setupUi(self)
This then crashes with an error message saying that it cannot create a consistent method resolution order.
My first question is - I know I need to call the init method of class a since a's attributes are created there, but I don't know how.
My second question is - How do I fix this MRO error and succeed in creating the new class b which can use a's attributes and functions?
You are trying to mix in the parent classes before the derived class. There is no need to, just inherit directly from a and the new Ui_b and nothing else:
class b(a, Ui_b):
# *no* __init__ needed either
a already pulls in QtWidgets.QWidget.
Now, Ui_a and Ui_b may not play well together. You may have to invoke both Ui_a.setupUi() and Ui_b.setupUi(). You can do so explicitly:
class b(a, Ui_b):
def __init__(self, parent=None):
super().__init__(parent)
# Ui_b.setupUi would have shadowed Ui_a.setupUi, so
# call the latter explicitly
Ui_a.setupUi(self, self) # unbound
It may be that Ui_a and Ui_b can't be mixed at all. In that case you should just pull out all the methods you want to re-use into a separate base class and have both a and b inherit from that:
class SharedStuff:
# ...
class a(QtWidgets.QWidget, SharedStuff, Ui_a):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
self.setupUi(self)
class b(QtWidgets.QWidget, SharedStuff, Ui_b):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
self.setupUi(self)
Your question is slightly unclear, but I am going to assume that Ui_a and Ui_b aren't both classes generated from Qt Designer files - because there is no sane way of inheriting from two such classes.
I'm guessing your current classes are equivalent to this:
class A(QtWidgets.QWidget, Ui_FromQtDesigner):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
self.setupUi(self)
def methodA(self):
print('A:', self.windowTitle())
class B(QtWidgets.QWidget):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
def methodB(self):
print('B:', self.windowTitle())
and you want a class C which will be a QWidget subclass that gets both the Qt Designer UI elements and the custom methods from classes A and B.
Now, Qt uses single inheritance almost exclusively. Inheriting from two QWidget classes is usually an error, unless the classes share a common set of base-classes (and even then, there are a number of limitations that still make it a questionable design choice - for more details, see this blog post).
In your case, it would be better to use a simple mixin class. It is only really necessary for class A to inherit from QWidget, so the set of classes can be defined like this:
class WidgetA(QtWidgets.QWidget, Ui_FromQtDesigner):
def __init__(self, parent=None):
super(WidgetA, self).__init__(parent)
self.setupUi(self)
def methodA(self):
print('A:', self.windowTitle())
class WidgetMixinB(object):
def methodB(self):
print('B:', self.windowTitle())
class WidgetC(WidgetA, WidgetMixinB):
pass
And if you also want to use WidgetMixinB as a widget in its own right, you can create another class for it like this:
class WidgetB(QtWidgets.QWidget, WidgetMixinB):
pass
Im making a QTableWidget in Pyqt and ran into a bit of an annoying hiccup.
I need to use widgets in my table for its functionality, so im using setCellWidget to add them to the table. However, widgets dont have the same methods available as QTableWidgetItem's do (especially regarding selection in the table).
Im wondering if its possible to do something subclassing both items, so i can have the methods of both, and how i woulda dd that to the table.
Something like:
class TableItem(QtGui.QTableWidgetItem, QtGui.QWidget):
def __init__(self, parent=None):
super(TableItem, self).__init__(parent)
self.check = QtGui.QCheckBox()
self.label = QtGui.QLabel('Some Text')
self.h_box = QtGui.QHBoxLayout()
self.h_box.addWidget(self.check)
self.h_box.addWidget(self.label)
and then somehow add that to my table as a TableWidgetItem so it displays widgets and also has selection methods available.
Any ideas here?
For reference:
setCellWidget: http://pyqt.sourceforge.net/Docs/PyQt4/qtablewidget.html#setCellWidget
QWidget: (easy to find, i cant post more than 2 links)
-Which doesnt have the nice methods for a table
QTableWidgetItem: http://pyqt.sourceforge.net/Docs/PyQt4/qtablewidgetitem.html#type
with isSelected and setSelected (Methods not avialble from a widget used in setCellWidget.
To return the widget in a cell you can use table.cellWidget(row, column) and then use your widgets methods on that. But beacuse setSelected and isSelected arent methods of a widget, you cant check for selection. I was hoping to subclass the two together to allow for both
--Basically I need to know how to get my class to 'return' the proper type when i call it to add to the table with setItem
I am not sure what you want to do but you could "inject" a method like:
class TableWidgetItem(QtGui.QTableWidgetItem):
def __init__(self, parent=None):
QtGui.QTableWidgetItem.__init__(self)
def doSomething(self):
print "doing something in TableWidgetItem"
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self)
tableWidgetItem = TableWidgetItem()
widget = Widget()
def widgetFunction(self):
tableWidgetItem.doSomething()
# as an instance method
settatr(widget, "widgetFunction", MethodType(widgetFunction, widget, type(widget)))
# or as a class method
settatr(widget, "widgetFunction", widgetFunction)
Then you can:
>>>widget.widgetFunction()
doing something in TableWidgetItem
(not tested)
First off this question is based around PySide, but it is a general question of inheritance for class attributes.
So I have an inheritance problem. I basically want to inherit from 2 PySide GUI classes. Multiple inheritance had major conflicts and gave errors. Basically, I made a custom widget and want to make that same widget into a dock widget (floating window).
One way that I found easy to implement was to override the getattr method to redirect attribute calls, like below.
class DockWidget(QtGui.QDockWidget):
def __init__(self):
super().__init__()
self.test = Test()
# Container is a custom widget containing the Test widget and a toolbar.
self.setWidget(self.test.getCustomContainer())
def __getattr__(self, name):
"""Call a property's method when the given attribute name is not found.
Note: Gives full access to the test attribute.
Args:
name (str): Method or property name.
"""
if hasattr(self.test, name):
return self.test.__getattribute__(name)
# end __getattr
# end class DockWidget
class Test(QtGui.QWidget):
def doSomething(self, msg):
print(msg)
# end doSomething
# end Test
widg = DockWidget()
widg.doSomething("Test")
I would like to know if this is considered really bad, and if there is a better way.
Since DockWidget and Test both inherit QWidget, you can use a mixin. This would allow you to do things like reimplement virtual methods, which would not be possible using __getattr__.
class WidgetMixin(object):
def doSomething(self, msg):
print(msg)
def closeEvent(self, event):
print(self.__class__.__name__)
class Test(WidgetMixin, QtGui.QWidget):
def __init__(self):
super().__init__()
class DockWidget(WidgetMixin, QtGui.QDockWidget):
def __init__(self):
super().__init__()
QLineEdit has a textEdited signal which is emitted whenever the text is changed by user interaction, but not when the text is changed programatically. However, QDateTimeEdit has only a general dateTimeChanged signal that does not distinguish between these two types of changes. Since my app depends on knowing if the field was edited by the user or not, I'm looking for ways to implement it.
My (currently working) strategy was to create an eventFilter to the edit field, intercept key press and mouse wheel events and use them to determine if the field was modified by the user (storing this info in an object), and finally connecting the dateTimeChanged signal to a function that decides if the change was by the user or done programatically. Here are the relevant parts of the code (python):
class UserFilter(QObject):
def __init__(self, parent):
QObject.__init__(self, parent)
self.parent = parent
def eventFilter(self, object, event):
if event.type() == QEvent.KeyPress or event.type() == QEvent.Wheel:
self.parent.edited = True
else:
pass
return False
class DockThumb(QWidget):
def __init__(self, parent):
QWidget.__init__(self, parent)
self.parent = parent
self.edited = False
self.dateedit = QDateTimeEdit(self)
self.userfilter = UserFilter(self)
self.dateedit.installEventFilter(self.userfilter)
...
self.connect(self.dateedit,
SIGNAL('dateTimeChanged(QDateTime)'),
self.edited_or_not)
def edited_or_not(self):
if self.edited:
# User interacted! Go for it.
self.parent.runtimer()
# self.edited returns to False once data is saved.
else:
# User did not edited. Wait.
pass
Is there a more objective way of doing it? I tried subclasssing QDateTimeEdit, but failed to deal with events... Expected user interactions are direct typing, up/down arrow keys to spin through dates and copy/pasting the whole string.
The idiomatic Qt way of achieving this is indeed subclassing QDateTimeEdit and adding the functionality you require. I understand you tried it and "failed to deal with events", but that's a separate issue, and perhaps you should describe those problems - since they should be solvable.
Since I'm not entirely sure about what you are trying to do, I would agree with Eli Bendersky. Short of that, if you know when you will be programatically changing the QDateTimeEdit, set some flag that you can check in the slot handler that will indicate a programatic change is occurring and clear it when you are done.