简体   繁体   中英

How to copy all widget settings to another in PyQt5?

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:

原创.png

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 :

类替换.png

... 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.

Use a promoted 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.

  • In Designer right click on the QListView you've added and select "Promote to..."
  • Ensure that the "Base class name" is the correct one (it should be automatically selected)
  • Type "MyListView" (the name of the subclass) in the "Promoted class name" field
  • In the "Header file" type the name of the file the subclass is defined, without the extension (in your example, 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
  • Click "Add" to add the new promotion to the list of known promoted widget, click "Promote" and save the file again

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).

Use an event filter

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)

Monkey patching

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM