Reputation:
I'm trying to mimic the tab button behaviour with buttons from another class.
So that when I click the Orders or History button the corresponding tab is selected.
ATM the code I have seems to work in so far as updating the currentIndex attribute as it prints correctly (0 for Orders and 1 for History).
However this change does not seem to reflect in the window, am I right in thinking this is an 'update widget' problem? If so how do I 'refresh' it? If not am I missing some fundamental concept?
I've slimmed the code as much as I could...
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_())
As an aside my 1st instinct was to go through the PyQt5 module files in order to get a clue as to how the default behaviour is done, but as Qt is written in C++ that wasn't an option here.
Upvotes: 2
Views: 1320
Reputation: 1247
You're creating 3 instances of Tabs
, therefore each clicked()
signal is connected to its "own" instance of Tabs.
The Window
constructor creates one of them directly and the other two are created by 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()
The other two, which are never shown, because they're just connected to one button and nothing else.
class LayoutTabButtons(QHBoxLayout):
def __init__(self):
super().__init__()
# Children #
self.addWidget(ButtonOrders(Tabs())) # <-- Here
self.addWidget(ButtonHistory(Tabs())) # <-- Here
To make your code work you have to keep an instance of Tabs
inside the parent Window
and pass it to the children.
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_())
However, while with a simple fix your code works, it has serious design issues.
The buttons should not depend on the tabs, they shouldn't even know about them.
In fact you should use plain QPushButtons and the only object aware of them should be the instance of Tabs, that would handle the interaction between tabs and buttons.
Here's a little refactoring that tries to stay close as possible to the OP idea while avoiding unnecessary classes
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_())
Upvotes: 1
Reputation: 243897
The main problem is that you are thinking that doing "X()" will always create the same class object and that is false (unless you have implemented a singleton). In addition, the idea is to create objects that have a limited scope and that share information through signals as if it were a black box where there are only inputs and outputs and what is inside does not matter. Considering the above, a possible solution is:
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()
For example, in my previous code, each button is associated with each page, and when it is clicked, it sends the page that is received by the ButtonsContainer class, and the latter transmits it to the QTabWidget. So you could add more buttons for more pages without changing anything.
Upvotes: 1