The below code uses.ui from QtDesigner. If you run it with REPLACE_LISTVIEW_CLASS = False
, you get the "original" layout - so if you click the button, you get something like this:
However, I did not like the dotted selection line around the last selected item, once you click on an empty area in the list view to clear any selection.
So, I implemented the class from Clear selection when clicking on blank area of Item View - and since I want to keep the.ui, I have to change the list view class in the __init__
by replacing the widget. That works in principle, and can be seen if you run the code with REPLACE_LISTVIEW_CLASS = True
:
... however, as it is clear from the above screenshot - all the settings that were set on the list view widget in Qt designer are now lost: not just the font, but also the ExtendedSelection setting (the widget reverts to SingleSelection).
So my question is - is there an easy way to copy all of the settings from the original widget to the subclassed widget, before the original widget is deleted?
test.py
:
import sys
from PyQt5 import QtCore, QtWidgets, QtGui, uic
from PyQt5.QtCore import pyqtSlot
REPLACE_LISTVIEW_CLASS = True # False # True
class MyListView(QtWidgets.QListView): # https://stackoverflow.com/q/8516163
def keyPressEvent(self, event):
if (event.key() == QtCore.Qt.Key_Escape and
event.modifiers() == QtCore.Qt.NoModifier):
self.selectionModel().clear()
else:
super(MyListView, self).keyPressEvent(event)
def mousePressEvent(self, event):
if not self.indexAt(event.pos()).isValid():
self.selectionModel().clear()
super(MyListView, self).mousePressEvent(event)
class MyMainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
uic.loadUi('test.ui', self)
self.listview_model = QtGui.QStandardItemModel()
self.listView.setModel(self.listview_model)
self.pushButton.clicked.connect(self.on_pushButton_click)
if REPLACE_LISTVIEW_CLASS:
# to change widget class - replace the widget:
list_view_real = MyListView()
listView_parent_layout = self.listView.parent().layout()
listView_parent_layout.replaceWidget(self.listView, list_view_real)
self.listView.deleteLater() # must delete the old manually, else it still shows after replaceWidget!
# nice - reassignment like this works, but all old styling is lost!
self.listView = list_view_real
self.listView.setModel(self.listview_model)
self.show()
@pyqtSlot()
def on_pushButton_click(self):
self.listview_model.appendRow( QtGui.QStandardItem("Lorem ipsum") )
self.listview_model.appendRow( QtGui.QStandardItem("dolor sit amet,") )
self.listview_model.appendRow( QtGui.QStandardItem("consectetur ") )
self.listview_model.appendRow( QtGui.QStandardItem("adipiscing elit, ...") )
def main():
app = QtWidgets.QApplication(sys.argv)
window = MyMainWindow()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
test.ui
:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QListView" name="listView">
<property name="font">
<font>
<family>Palatino Linotype</family>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Hello!</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
"Copying" a widget's "settings" is very difficult, as every widget has very specific properties, but, in any case, it's completely unnecessary, as you don't need to "replace" a widget.
The best solution is to use a promoted widget : the concept is that you create your GUI in designer, add a widget that has the same class you're going to subclass, and promote it; this will result in Qt using all the properties you've set in the ui, but applied for the subclass.
test
); consider that if the file is in a subfolder, you should use dot separators: if the subclass is in the file "mylistview" in the "promoted" folder, you'll need to write promoted.mylistview
Now your UI will be using the MyListView subclass instead of the default QListView (obviously, you don't need the whole REPLACE_LISTVIEW_CLASS
block anymore).
For simple situations like yours, where you only need to react to keyboard/mouse events, you can install an event filter on the widget, and react accordingly:
class MyMainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
uic.loadUi('test.ui', self)
# ...
self.listView.installEventFilter(self)
def eventFilter(self, source, event):
if source == self.listView:
if (event.type() == QtCore.QEvent.KeyPress and
event.key() == QtCore.Qt.Key_Escape and
event.modifiers() == QtCore.Qt.NoModifier):
self.listView.selectionModel().clear()
elif (event.type() == QtCore.QEvent.MouseButtonPress and
not self.listView.indexAt(event.pos()).isValid()):
self.listView.selectionModel().clear()
return super(MyMainWindow, self).eventFilter(source, event)
An even simpler alternative (that should be used with care) is to "monkey patch" the widget methods:
class MyMainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
uic.loadUi('test.ui', self)
# ...
self.listView.keyPressEvent = self.listKeyPress
self.listView.mousePressEvent = self.listMousePress
def listKeyPress(self, event):
if (event.key() == QtCore.Qt.Key_Escape and
event.modifiers() == QtCore.Qt.NoModifier):
self.listView.selectionModel().clear()
else:
QtWidgets.QListView.keyPressEvent(self.listView, event)
def listMousePress(self, event):
if not self.listView.indexAt(event.pos()).isValid():
self.listView.selectionModel().clear()
QtWidgets.QListView.mousePressEvent(self.listView, event)
Note that monkey patching of Qt objects can only be done before the overridden methods have been called: for example, you cannot monkey patch mousePressEvent
after a mouse press has already been occurred, and that's because Qt bindings use function caching: if a base implementation has already been called for the instance, it will always be used from that moment on; on the other hand, the method can be overridden again only if the function has already been overwritten before.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.