2

我正在尝试使用另一个类的按钮来模仿选项卡按钮的行为。

这样,当我单击“订单”或“历史记录”按钮时,就会选择相应的选项卡。

ATM 代码似乎可以在正确打印时更新 currentIndex 属性(订单为 0,历史记录为 1)。

然而,这种变化似乎并没有反映在窗口中,我是否认为这是一个“更新小部件”问题?如果是这样,我该如何“刷新”它?如果不是,我是否缺少一些基本概念?

我已经尽可能地精简了代码......

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

Application = QApplication(sys.argv)

class Window(QWidget):
    def __init__(self):
        super().__init__()

        # Layout #
        Layout = QVBoxLayout()
        Layout.addLayout(LayoutTabButtons())
        Layout.addWidget(Tabs())
        self.setLayout(Layout)
        self.show()
        
class Tabs(QTabWidget):
    def __init__(self):
        super().__init__()

        # Children #
        self.addTab(QLabel('This Is The Orders Page'),'Orders')
        self.addTab(QLabel('This Is The History Page'),'History')

        # Connections #
        InstanceButtonOrders = ButtonOrders(self)
        InstanceButtonHistory = ButtonHistory(self)

    def ChangeIndex(self, Input):
        self.setCurrentIndex(Input)
        print(self.currentIndex()) # <--- Prints Correctly

class LayoutTabButtons(QHBoxLayout):
    def __init__(self): 
        super().__init__() 

        # Children #
        self.addWidget(ButtonOrders(Tabs()))
        self.addWidget(ButtonHistory(Tabs()))

class ButtonOrders(QPushButton):
    def __init__(self, Tabs): 
        super().__init__()  

        # Content #
        self.setText('Orders') 

        # Connections #
        self.clicked.connect(lambda: self.SetIndex(Tabs)) 

    def SetIndex(self,Tabs):
        Tabs.ChangeIndex(0)

class ButtonHistory(QPushButton):
    def __init__(self, Tabs): 
        super().__init__() 

        # Content #
        self.setText('History')

        # Connections #
        self.clicked.connect(lambda: self.SetIndex(Tabs)) 

    def SetIndex(self,Tabs):
        Tabs.ChangeIndex(1)

InstanceWindow = Window()

sys.exit(Application.exec_())

顺便说一句,我的第一个直觉是浏览 PyQt5 模块文件,以便了解默认行为是如何完成的,但由于 Qt 是用 C++ 编写的,所以这里不是一个选项。

4

3 回答 3

2

您正在创建 3 个实例Tabs,因此每个clicked()信号都连接到其“自己的”选项卡实例。

构造Window函数直接创建其中一个,另外两个由LayoutTabButtons

class Window(QWidget):
    def __init__(self):
        super().__init__()

        # Layout #
        Layout = QVBoxLayout()
        Layout.addLayout(LayoutTabButtons()) # <-- 2 here
        Layout.addWidget(Tabs()) # <-- 1 here
        self.setLayout(Layout)
        self.show()

另外两个从未显示,因为它们只是连接到一个按钮而没有其他任何东西。

class LayoutTabButtons(QHBoxLayout):
    def __init__(self): 
        super().__init__() 

        # Children #
        self.addWidget(ButtonOrders(Tabs()))  # <-- Here
        self.addWidget(ButtonHistory(Tabs())) # <-- Here

要使您的代码正常工作,您必须Tabs在父级内部保留一个实例Window并将其传递给子级。

Application = QApplication(sys.argv)

class Window(QWidget):
    def __init__(self):
        super().__init__()

        self.tabs = Tabs() # <-- The local instance!
        # Layout #
        Layout = QVBoxLayout()
        Layout.addLayout(LayoutTabButtons(self.tabs)) # <-- Passed to the children
        Layout.addWidget(self.tabs) # <-- Added to the layout
        self.setLayout(Layout)
        self.show()

class Tabs(QTabWidget):
    def __init__(self):
        super().__init__()

        # Children #
        self.addTab(QLabel('This Is The Orders Page'),'Orders')
        self.addTab(QLabel('This Is The History Page'),'History')

        # Connections #
        # Now the class has local instances of the buttons
        self.InstanceButtonOrders = ButtonOrders(self)   # <-- local instance
        self.InstanceButtonHistory = ButtonHistory(self) # <-- local instance

    def ChangeIndex(self, Input):
        self.setCurrentIndex(Input)
        print(self.currentIndex()) # <-- Prints Correctly

class LayoutTabButtons(QHBoxLayout):
    def __init__(self, tabs):
        super().__init__()

        # Children #
        # Now the layout gets the buttons in Tabs
        self.addWidget(tabs.InstanceButtonOrders)
        self.addWidget(tabs.InstanceButtonHistory)

class ButtonOrders(QPushButton):
    def __init__(self, tabs):
        super().__init__()

        self.tabs = tabs # <-- These are the tabs in Window
        # Content #
        self.setText('Orders')

        # Connections #
        self.clicked.connect(self.SetIndex) # <-- The lambda was superfluos

    def SetIndex(self):
        self.tabs.ChangeIndex(0)

class ButtonHistory(QPushButton):
    def __init__(self, tabs):
        super().__init__()

        self.tabs = tabs # These are the tabs in Window
        # Content #
        self.setText('History')

        # Connections #
        self.clicked.connect(self.SetIndex) # <-- The lambda was superfluos

    def SetIndex(self):
        self.tabs.ChangeIndex(1)

InstanceWindow = Window()

sys.exit(Application.exec_())

然而,虽然通过简单的修复你的代码可以工作,但它存在严重的设计问题。
按钮不应该依赖于选项卡,他们甚至不应该知道它们。
事实上,您应该使用普通的 QPushButtons,并且唯一知道它们的对象应该是 Tabs 的实例,它将处理选项卡和按钮之间的交互。


这是一个小的重构,试图尽可能接近 OP 的想法,同时避免不必要的类

Application = QApplication(sys.argv)

class TabWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.tabs = Tabs()
        # Layout #
        Layout = QVBoxLayout()
        Layout.addWidget(self.tabs)
        self.setLayout(Layout)
        self.show()

    def AddTab(self, Widget, Name, Button=None):
        self.tabs.AddTab(Widget, Name, Button)

class Tabs(QWidget):
    def __init__(self):
        super().__init__()

        self.buttons = []
        self.tabs = QTabWidget()

        # Buttons bar layout #
        self.barLayout = QHBoxLayout()

        # Window layout #
        Layout = QVBoxLayout()
        Layout.addLayout(self.barLayout)
        Layout.addWidget(self.tabs)
        self.setLayout(Layout)

    # Sets up a new tab with its button
    # and connects the click to ChangeIndex()
    def AddTab(self, Widget, Name, Button=None):
        self.tabs.addTab(Widget, Name)

        # Uses QPushButton by default, but can be replaced
        # by providing any instance of QAbstractButton
        if Button is None:
            Button = QPushButton(Name)
        index = len(self.buttons)
        Button.clicked.connect(lambda: self.ChangeIndex(index))

        self.buttons.append(Button)
        self.barLayout.addWidget(Button)

    def ChangeIndex(self, Input):
        self.tabs.setCurrentIndex(Input)
        print(self.tabs.currentIndex()) # <-- Prints Correctly

InstanceWindow = TabWindow()
# Tabs #
InstanceWindow.AddTab(QLabel('This Is The Orders Page'), 'Orders')
InstanceWindow.AddTab(QLabel('This Is The History Page'), 'History')

sys.exit(Application.exec_())
于 2021-03-21T02:09:56.683 回答
1

主要问题是您认为执行“X()”将始终创建相同的类对象,这是错误的(除非您实现了单例)。此外,这个想法是创建具有有限范围并通过信号共享信息的对象,就好像它是一个只有输入和输出的黑匣子,里面的内容无关紧要。考虑到上述情况,一个可能的解决方案是:

from enum import IntEnum
import sys

from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QPushButton,
    QTabWidget,
    QVBoxLayout,
    QWidget,
)


class Page(IntEnum):
    OrderPage = 0
    HistoryPage = 1


class Button(QPushButton):
    pageChanged = pyqtSignal(Page)

    def __init__(self, text, page, parent=None):
        super().__init__(parent)

        self._page = page

        self.setText(text)
        self.clicked.connect(self.handle_clicked)

    @property
    def page(self):
        return self._page

    def handle_clicked(self):
        self.pageChanged.emit(self.page)


class ButtonsContainer(QWidget):
    pageChanged = pyqtSignal(Page)

    def __init__(self, parent=None):
        super().__init__(parent)
        lay = QHBoxLayout(self)

    def add_button(self, text, page):
        button = Button(text, page)
        button.pageChanged.connect(self.pageChanged)
        self.layout().addWidget(button)


class Tabs(QTabWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.addTab(QLabel("This Is The Orders Page"), "Orders")
        self.addTab(QLabel("This Is The History Page"), "History")

    def change_page(self, page):
        self.setCurrentIndex(page)


class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        button_container = ButtonsContainer()
        button_container.add_button("Orders", Page.OrderPage)
        button_container.add_button("History", Page.HistoryPage)

        tabs = Tabs()

        button_container.pageChanged.connect(tabs.change_page)

        layout = QVBoxLayout(self)
        layout.addWidget(button_container)
        layout.addWidget(tabs)


def main():

    app = QApplication(sys.argv)
    view = Window()
    view.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

例如,在我之前的代码中,每个按钮都与每个页面相关联,当它被点击时,它会将 ButtonsContainer 类接收到的页面发送给 QTabWidget。因此,您可以在不更改任何内容的情况下为更多页面添加更多按钮。

于 2021-03-21T02:18:25.443 回答
0

我最终选择了这种使用 QStackedWidget 并将所有布局放在主窗口内的方法。

import sys

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

Application = QApplication(sys.argv)

class Window(QWidget):
    def __init__(self):
        super().__init__()
            
        # PageStack #
        self.PageStack = QStackedWidget()
        self.PageStack.addWidget(QLabel('This Is The Orders Page'))
        self.PageStack.addWidget(QLabel('This Is The History Page'))

        # OrdersButton #
        OrdersButton = QPushButton()
        OrdersButton.setText('Orders')
        OrdersButton.clicked.connect(lambda: self.PageStackIndex(0))
         
        # HistoryButton #
        HistoryButton = QPushButton()
        HistoryButton.setText('History')
        HistoryButton.clicked.connect(lambda: self.PageStackIndex(1))

        # PageButtonsLayout #
        PageButtonsLayout = QHBoxLayout()
        PageButtonsLayout.setAlignment(Qt.AlignLeft)
        PageButtonsLayout.addWidget(OrdersButton)
        PageButtonsLayout.addWidget(HistoryButton)

        # MenuBarLayout #
        MenuBarLayout = QHBoxLayout()
        MenuBarLayout.addLayout(PageButtonsLayout)

        # WindowLayout #  
        WindowLayout = QVBoxLayout()
        WindowLayout.addLayout(MenuBarLayout)
        WindowLayout.addWidget(self.PageStack)
        self.setLayout(WindowLayout)

        # Window #
        self.setWindowTitle('Window')
        self.show()

    def PageStackIndex(self,Index):
        self.PageStack.setCurrentIndex(Index)
        
WindowInstance = Window()
sys.exit(Application.exec_())

通过这种方式,您仍然可以将 Window 之外的任何小部件移动到它们自己的类中,并使用信号/插槽访问它们。这将允许最大程度的模块化。

于 2021-03-23T12:43:39.407 回答