如何在一个 Main 中包含两个小部件 window

How to have two widgets in one Main window

我整个早上都在努力解决这个问题。 所以我有一个 PyQt Main Window,我想在其中显示两个小部件。 在第一个小部件中列出了文章(目前有效)。 当我点击它们直到现在 QMessageBox 正在打开,但我想要那个 第二个小部件正在打开,我可以在其中阅读 RSS Feed。 但这是行不通的。请参阅下面的代码:

class ArticleWidgets(QWidget):
    def __init__(self, *args):
        super().__init__(*args)

        self.setGeometry(610, 610, 600, 600)

        self.initUi()

    def initUi(self):
        self.box = QHBoxLayout(self)

    def show(self, feed=None):

        self.title = QLabel()
        self.summary = QLabel()
        self.link = QLabel()

        if feed:
            self.title.setText(feed[0])
            self.summary.setText(feed[1])
            self.link.setText(feed[2])

        self.box.addWidget(self.title)
        self.box.addWidget(self.summary)
        self.box.addWidget(self.link)

        self.setLayout(self.box)


class TitleWidgets(QWidget):
    def __init__(self, *args):
        super().__init__(*args)

        self.setGeometry(10, 10, 600, 600)

        self.initUi()

    def initUi(self):
        vbox = QHBoxLayout(self)

        self.titleList = QListWidget()
        self.titleList.itemDoubleClicked.connect(self.onClicked)
        self.titleList.setGeometry(0, 0, 400, 400)
        self.news = ANFFeed()
        for item in self.news.all_feeds:
            self.titleList.addItem(item[0])
        vbox.addWidget(self.titleList)

    def onClicked(self, item):
        feeds = self.news.all_feeds
        id = 0
        for elem in range(len(feeds)):
            if feeds[elem][0] == item.text():
                id = elem

        summary = feeds[id][1] + '\n\n'
        link = feeds[id][2]

        if feeds and id:
            #ANFApp(self).show_articles(feeds[id])
            show = ANFApp()
            show.show_articles(feed=feeds[id])

        QMessageBox.information(self, 'Details', summary + link)


class ANFApp(QMainWindow):
    def __init__(self, *args):
        super().__init__(*args)

        self.setWindowState(Qt.WindowMaximized)
        self.setWindowIcon(QIcon('anf.png'))
        self.setAutoFillBackground(True)

        self.anfInit()

        self.show()

    def anfInit(self):
        self.setWindowTitle('ANF RSS Reader')

        TitleWidgets(self)
        #article_box = ArticleWidgets(self)

        exitBtn = QPushButton(self)
        exitBtn.setGeometry(600, 600, 100, 50)
        exitBtn.setText('Exit')
        exitBtn.setStyleSheet("background-color: red")
        exitBtn.clicked.connect(self.exit)

    def show_articles(self, feed=None):
        present = ArticleWidgets()
        present.show(feed)

    def exit(self):
        QCoreApplication.instance().quit()

问题

在我看来,您构建 GUI 的方式很混乱,并且可能会导致错误。我建议使用 Layouts 以获得更有条理的 GUI。

另一个问题是每个小部件都是独立的 class 所以如果你想通过 Main Window 连接一个小部件中的动作以在另一个小部件中做某事,你必须使用Signals.

编辑: 另一个建议,关闭函数使用其他名称而不是 exit 并尝试使用 self.close() 而不是 QCoreApplication.instance().quit()

解决方案

我制作了这个 GUI,试图模仿你想做的事情:

import sys
from PyQt5 import QtGui, QtCore

class MyWindow(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        ## Generate the structure parts of the MainWindow
        self.central_widget = QtGui.QWidget() # A QWidget to work as Central Widget
        self.layout1 = QtGui.QVBoxLayout() # Vertical Layout
        self.layout2 = QtGui.QHBoxLayout() # Horizontal Layout
        self.widget_one = WidgetOne()
        self.widget_two = WidgetTwo()
        self.exitBtn = QtGui.QPushButton('Exit')
        ## Build the structure
         # Insert a QWidget as a central widget for the MainWindow    
        self.setCentralWidget(self.central_widget)  
         # Add a principal layout for the widgets/layouts you want to add
        self.central_widget.setLayout(self.layout1)
         # Add widgets/layuts, as many as you want, remember they are in a Vertical
         # layout: they will be added one below of the other
        self.layout1.addLayout(self.layout2)
        self.layout1.addWidget(self.exitBtn)
         # Here we add the widgets to the horizontal layout: one next to the other
        self.layout2.addWidget(self.widget_one)
        self.layout2.addWidget(self.widget_two)
        ## Connect the signal
        self.widget_one.TitleClicked.connect(self.dob_click)

    def dob_click(self, feed):
        ## Change the properties of the elements in the second widget
        self.widget_two.title.setText('Title : '+feed[0])
        self.widget_two.summary.setText('Summary : '+feed[1])

## Build your widgets same as the Main Window, with the excepton that here you don't
## need a central widget, because it is already a widget.
class WidgetOne(QtGui.QWidget):
    TitleClicked = QtCore.pyqtSignal([list]) # Signal Created
    def __init__(self):
        QtGui.QWidget.__init__(self)
        ## 
        self.layout = QtGui.QVBoxLayout()  # Vertical Layout
        self.setLayout(self.layout)
        self.titleList = QtGui.QListWidget()
        self.label = QtGui.QLabel('Here is my list:')
        self.layout.addWidget(self.label)
        self.layout.addWidget(self.titleList)
        
        self.titleList.addItem(QtGui.QListWidgetItem('Title 1'))
        self.titleList.addItem(QtGui.QListWidgetItem('Title 2'))
        self.titleList.itemDoubleClicked.connect(self.onClicked)

    def onClicked(self, item):
        ## Just test parameters and signal emited
        self.TitleClicked.emit([item.text(), item.text()+item.text()]) 

class WidgetTwo(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        self.layout = QtGui.QVBoxLayout()
        self.setLayout(self.layout)
        self.title = QtGui.QLabel('Title : ---')
        self.summary = QtGui.QLabel('Summary : ---')
        self.link = QtGui.QLabel('Link : ---')
        self.layout.addWidget(self.title)
        self.layout.addWidget(self.summary)
        self.layout.addWidget(self.link)

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    window = MyWindow()
    window.show()
    sys.exit(app.exec_())

在代码中,有一些注释可以帮助您理解我为什么要构建一个有组织的 GUI。还有一个 Signal 用于将 itemDoubleClicked 的操作从第一个小部件连接到第二个小部件的示例。下面是 MainWindow 的样子:

仅从结果来看还不是很清楚布局是如何工作的,所以我画了一点画以更好地理解:

蓝色框为竖排(QVBoxLayout),红色框为横排(QHBoxLayout)。在蓝色布局内,有红色布局(上)和退出按钮(下);在红色布局中,位于 widget_1(左)和 widget_2(右)。

其他解决方案

“更简单”的解决方案是在 MainWindow 中构建小部件,而不是创建单独的 classes。有了这个,您将避免使用信号,但代码会变得更加混乱,因为所有代码都将被压缩在一个 class.

使用 Pyqtgraph 的 Docks 和 QTextBrowser 的解决方案

这是一个试图重现您的草图的代码。我使用了 Pyqtgraph 模块(此处的文档:Pyqtgraph's Documentation and Pyqtgraph's Web Page),因为从我的角度来看,它的 Dock 小部件更易于使用和实施。

在尝试此代码之前,您必须安装 pyqtgraph 模块:

import sys
from PyQt5 import QtGui, QtCore
from pyqtgraph.dockarea import *

class DockArea(DockArea):
    ## This is to prevent the Dock from being resized to te point of disappear
    def makeContainer(self, typ):
        new = super(DockArea, self).makeContainer(typ)
        new.setChildrenCollapsible(False)
        return new

class MyApp(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        central_widget = QtGui.QWidget()
        layout = QtGui.QVBoxLayout()
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)
    
        label = QtGui.QLabel('This is a label, The widgets will be below')
        label.setMaximumHeight(15)
        ## The DockArea as its name says, is the are where we place the Docks
        dock_area = DockArea(self)
        ## Create the Docks and change some esthetic of them
        self.dock1 = Dock('Widget 1', size=(300, 500))
        self.dock2 = Dock('Widget 2', size=(400, 500))
        self.dock1.hideTitleBar()
        self.dock2.hideTitleBar()
        self.dock1.nStyle = """
        Dock > QWidget {
            border: 0px solid #000;
            border-radius: 0px;
        }"""
        self.dock2.nStyle = """
        Dock > QWidget {
            border: 0px solid #000;
            border-radius: 0px;
        }"""
        self.button = QtGui.QPushButton('Exit')
        self.widget_one = WidgetOne()
        self.widget_two = WidgetTwo()
        ## Place the Docks inside the DockArea
        dock_area.addDock(self.dock1)
        dock_area.addDock(self.dock2, 'right', self.dock1)
        ## The statment above means that dock2 will be placed at the right of dock 1
        layout.addWidget(label)
        layout.addWidget(dock_area)
        layout.addWidget(self.button)
        ## Add the Widgets inside each dock
        self.dock1.addWidget(self.widget_one)
        self.dock2.addWidget(self.widget_two)
        ## This is for set the initial size and posotion of the main window
        self.setGeometry(100, 100, 600, 400)
        ## Connect the actions to functions, there is a default function called close()
        self.widget_one.TitleClicked.connect(self.dob_click)
        self.button.clicked.connect(self.close)
        
    def dob_click(self, feed):
        self.widget_two.text_box.clear()
        ## May look messy but wat i am doing is somethin like this:
        ## 'Title : ' + feed[0]  + '\n\n' + 'Summary : ' + feed[1]
        self.widget_two.text_box.setText(
            'Title : ' + feed[0]\
            + '\n\n' +\
            'Summary : ' + feed[1]
        )
        
class WidgetOne(QtGui.QWidget):
    ## This signal is created to pass a "list" when it (the signal) is emited
    TitleClicked = QtCore.pyqtSignal([list])
    def __init__(self):
        QtGui.QWidget.__init__(self)
        self.layout = QtGui.QVBoxLayout()
        self.setLayout(self.layout)
        self.titleList = QtGui.QListWidget()
        self.label = QtGui.QLabel('Here is my list:')
        self.layout.addWidget(self.label)
        self.layout.addWidget(self.titleList)
        
        self.titleList.addItem(QtGui.QListWidgetItem('Title 1'))
        self.titleList.addItem(QtGui.QListWidgetItem('Title 2'))
        self.titleList.itemDoubleClicked.connect(self.onClicked)

    def onClicked(self, item):
        ## Just test values
        title = item.text()
        summary = "Here you will put the summary of {}. ".format(title)*50
        ## Pass the values as a list in the signal. You can pass as much values
        ## as you want, remember that all of them have to be inside one list
        self.TitleClicked.emit([title, summary]) 

class WidgetTwo(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        self.layout = QtGui.QVBoxLayout()
        self.setLayout(self.layout)
        self.label2 = QtGui.QLabel('Here we show results?:')
        self.text_box = QtGui.QTextBrowser()
        
        self.layout.addWidget(self.label2)
        self.layout.addWidget(self.text_box)

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    window = MyApp()
    window.show()
    sys.exit(app.exec_())

同样,代码中有注释可以帮助您理解我所做的事情。 外观如下:

如果您在两个小部件之间移动鼠标,您会看到鼠标图标会发生变化,这样您就可以重新调整 运行 两个小部件的大小.

最后的话

这是另一种方法,比我之前的回答更“互动”也更美观。正如您所说,使用 QSplitter 也可以。