如何将所有窗口小部件设置复制到PyQt5中的另一个窗口?

时间:2020-07-16 12:20:42

标签: python pyqt5

以下代码使用QtDesigner中的.ui。如果您使用REPLACE_LISTVIEW_CLASS = False运行它,则将获得“原始”布局-因此,如果您单击该按钮,则会得到以下内容:

original.png

但是,一旦您单击列表视图中的空白区域以清除所有选择,我就不喜欢最后选择的项目周围的虚线选择线。

因此,我从Clear selection when clicking on blank area of Item View开始实现该类,并且由于我想保留.ui,因此必须通过替换小部件来更改__init__中的列表视图类。原则上可行,如果使用REPLACE_LISTVIEW_CLASS = True运行代码,则可以看到:

class-replaced.png

...但是,从上面的屏幕截图可以清楚地看到-在Qt设计器中的列表视图小部件上设置的所有设置现在都丢失了:不仅是字体,还包括ExtendedSelection设置(小部件都​​还原为单选)。

所以我的问题是-在删除原始窗口小部件之前,是否有一种简单的方法将所有设置从原始窗口小部件复制到子类窗口小部件?

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>

1 个答案:

答案 0 :(得分:1)

“复制”窗口小部件的“设置”非常困难,因为每个窗口小部件都具有非常特定的属性,但是在任何情况下,由于您无需“替换”窗口小部件,因此完全没有必要。

使用升级的小部件

最好的解决方案是使用promoted widget:这个概念是您在设计器中创建GUI,添加与要子类化的类相同的小部件,然后升级它;这将导致Qt使用您在ui中设置但应用于子类的所有属性。

  • 在Designer中,右键单击已添加的QListView,然后选择“升级为...”
  • 确保“基类名称”是正确的(应该自动选择)
  • 在“升级的类名”字段中输入“ MyListView”(子类的名称)
  • 在“头文件”中,输入定义子类的文件的名称,不带扩展名(在您的示例中为test);考虑到如果文件位于子文件夹中,则应使用点分隔符:如果子类位于“ promoted”文件夹中的“ mylistview”文件中,则需要编写promoted.mylistview
  • 单击“添加”以将新促销添加到已知的促销小部件列表中,单击“促销”并再次保存文件

现在,您的用户界面将使用MyListView子类,而不是默认的QListView(显然,您不再需要整个REPLACE_LISTVIEW_CLASS块)。

使用事件过滤器

对于像您这样的简单情况,您只需要对键盘/鼠标事件做出反应,可以在小部件上安装event filter并做出相应的反应:

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)

猴子修补

一个更简单的选择(应谨慎使用)是“装饰”小部件方法:

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)

请注意,只能在调用覆盖方法的之前进行Qt对象的猴子修补:例如,您不能在之后进行猴子mousePressEvent 的猴子修补已经发生了鼠标按下操作,这是因为Qt绑定使用函数缓存:如果已经为该实例调用了基本实现,则从那时起将始终使用该实现。另一方面,仅当函数已经被覆盖时,才可以再次覆盖该方法。