Creating multiple tabs inside of a preset layout and keeping data within them.

I am attempting to generate output information in a tabbed layout using PyQT5. In the case that I have multiple devices, I would expect multiple tabs to show up in the output with information corresponding to their respective device.

For some reason, I am getting a single tab, and a single button. Basically like the information is getting written over.
The issue is that I do not know how to dynamically add new tabs and hold on to all of the information inside them properly upon each iteration. I will not know the number of devices or device parameters ahead of time, so I can not set this information in code. It must be dynamic.

Here is my current code:
` # $description: Testing
# $show-in-menu

    import pya
    import re, sys
    import os

        # Create tab widget within main layout
        self.tab_widget = pya.QTabWidget()
        self.scroll_layout.addWidget( self.tab_widget )
        tab = []
        tab_layout = []

        data_for_tabs = dict()
        data_for_tabs["Device1"] = dict()
        data_for_tabs["Device1"]["Parameter1"] = dict()
        data_for_tabs["Device1"]["Parameter1"]["x"] = 1.2
        data_for_tabs["Device1"]["Parameter1"]["y"] = 1.5
        data_for_tabs["Device1"]["Parameter2"] = dict()
        data_for_tabs["Device1"]["Parameter2"]["x"] = 1.4
        data_for_tabs["Device1"]["Parameter2"]["y"] = 1.3

        data_for_tabs["Device2"] = dict()
        data_for_tabs["Device2"]["Parameter1"] = dict()
        data_for_tabs["Device2"]["Parameter1"]["x"] = 2.2
        data_for_tabs["Device2"]["Parameter1"]["y"] = 2.5
        data_for_tabs["Device2"]["Parameter2"] = dict()
        data_for_tabs["Device2"]["Parameter2"]["x"] = 2.4
        data_for_tabs["Device2"]["Parameter2"]["y"] = 2.3

        # First generate tabs
        tabNumber = 0
        for key in data_for_tabs.keys():

            # Create tab
            tab.append( pya.QWidget() )
            tab_layout.append( pya.QVBoxLayout() )
            tab[tabNumber].setLayout( tab_layout[tabNumber] )
            self.tab_widget.addTab( tab[tabNumber], key )
            self.tab = tab[tabNumber]
            self.tab_layout = tab_layout[tabNumber]

            self.runExtraction( data_for_tabs[key], self.tab, self.tab_layout )
            tabNumber += 1

      def runExtraction( self, data, tab, tab_layout ):
        row = 0
        for key, value in data.items():
            outGroupBox = pya.QGroupBox( key )
            self.out_layout = pya.QHBoxLayout()
            for pkey, pval in value.items():
                txtlbl = pya.QLabel( tab )
                txtlbl.setText(f"{pkey} = ")
                txt = pya.QLineEdit( tab )
                txt.setText(f"{pval}")
                txt.setReadOnly( True )

                self.out_layout.addWidget( txtlbl )
                self.out_layout.addWidget( txt )

            self.button = pya.QPushButton()
            self.button.text = f"{key}"
            self.out_layout.addWidget( self.button )

            # Reformat
            outGroupBox.setLayout( self.out_layout )
            outGroupBox.update()
            setattr( self, "outGroupBox" + str(row), outGroupBox )
            tab_layout.addWidget( getattr( self, "outGroupBox" + str(row) ) )
            row += 1

        # Add stretch so everything sits at the top
        tab_layout.addStretch()

`

Please feel free to leave comments or provide any help. My guess is that I will need to create some sort of class for the tabbed layout, but I am not sure.

Comments

  • edited July 2023

    Hi ManzAutomations,

    here's an example of tab that holds random amound of data dynamically

    The concept is to create a page widget that can be used repeatly, so later we can feed parameters into page and add to tab widget using loops.

    import pya
    import random
    
    # generate random device count and data
    tabDataArray = []
    for i in range(random.randint(2,8)):
        tabData = {
            "name"     : ("device%d" % i),
            "channel"  : {"width" : random.randint(1,5),  "length" :  random.randint(1,5)},
            "location" : {"x"     : random.randint(2,20), "y"      : random.randint(2,20)},
        }
        tabDataArray.append(tabData) 
    
    class MultiTabWidget(pya.QWidget):
        def __init__(self, tabDataArray = [], parent = None):
            super(MultiTabWidget, self).__init__()
            self.tabWgt = pya.QTabWidget()
            self.layout = pya.QVBoxLayout()
            self.layout.addWidget(self.tabWgt)
            self.setLayout(self.layout)
    
            # parse tab data array and feed into page widget
            # add page widget into tab dynamically
            for tabData in tabDataArray:
                tabPgWgt = TabPageWidget(
                        name   = tabData["name"],
                        x      = tabData["location"]["x"],
                        y      = tabData["location"]["y"],
                        w      = tabData["channel"]["width"],
                        l      = tabData["channel"]["width"], 
                        parent = self
                    )
    
                self.tabWgt.addTab (tabPgWgt, tabData["name"])
    
    #page display widget for parameters
    class TabPageWidget(pya.QWidget):
        def __init__(self, name = "Unspecified", x = 0, y = 0, w = 0, l = 0, parent = None):
            super(TabPageWidget, self).__init__(parent) 
            self.nameLE = pya.QLineEdit(str(name))
            self.locXLE = pya.QLineEdit(str(x))
            self.locYLE = pya.QLineEdit(str(x))
            self.chnWLE = pya.QLineEdit(str(w))
            self.chnLLE = pya.QLineEdit(str(l))
            self.layout = pya.QGridLayout()
    
            self.layout.addWidget(pya.QLabel("name"),          0,0,1,1)
            self.layout.addWidget(pya.QLabel("location x"),    1,0,1,1)
            self.layout.addWidget(pya.QLabel("location y"),    2,0,1,1)
            self.layout.addWidget(pya.QLabel("channel width"), 3,0,1,1)
            self.layout.addWidget(pya.QLabel("channel length"),4,0,1,1)
    
            self.layout.addWidget(self.nameLE,                 0,1,1,1)
            self.layout.addWidget(self.locXLE,                 1,1,1,1)
            self.layout.addWidget(self.locYLE,                 2,1,1,1)
            self.layout.addWidget(self.chnWLE,                 3,1,1,1)
            self.layout.addWidget(self.chnLLE,                 4,1,1,1)
            self.setLayout(self.layout) 
    
    w =  MultiTabWidget(tabDataArray)       
    w.show()
    
  • @ManzAutomations Your code does not execute, so I can't debug the problem.

    In general you need to properly set the widget's parents in order to establish the Qt ownership. This is prevent Python from destroying the widgets once the variables go out of scope. @RawrRanger's code does the right thing by using the correct parent from the tab widget ("parent" argument).

    Here is another basic code to generate two tabs. Please note the comments:


    # Create Tab 1 def create_tab1(on): # Note: it is important to use "on" as the parent as # "addTab" does not make "on" the owner of tab1. tab1 = pya.QWidget(on) on.addTab(tab1, "Tab 1") tab1_layout = pya.QGridLayout() tab1.setLayout(tab1_layout) tab1_label = pya.QLabel("Tab1 Label", tab1) tab1_layout.addWidget(tab1_label, 0, 0) tab1_line_edit = pya.QLineEdit(tab1) tab1_layout.addWidget(tab1_line_edit, 0, 1) tab1_layout.setRowStretch(1, 1) # Create Tab 2 def create_tab2(on): # Note: it is important to use "on" as the parent as # "addTab" does not make "on" the owner of tab1. tab2 = pya.QWidget(on) on.addTab(tab2, "Tab 2") tab2_layout = pya.QGridLayout() tab2.setLayout(tab2_layout) tab2_label = pya.QLabel("Tab2 Label", tab2) tab2_layout.addWidget(tab2_label, 0, 0) tab2_check_box = pya.QCheckBox(tab2) tab2_check_box.text = "Check Box on Tab" tab2_layout.addWidget(tab2_check_box, 0, 1) tab2_layout.setRowStretch(1, 1) # Main code: create a dialog and populate the tabs dialog = pya.QDialog() layout = pya.QVBoxLayout() dialog.setLayout(layout) tab_widget = pya.QTabWidget(dialog) layout.addWidget(tab_widget) create_tab1(tab_widget) create_tab2(tab_widget) # Shows the dialog dialog.exec_()

    Matthias

  • @Matthias , I tried to give the full code, but it would not let me given there were too many characters. I will try again...
    '
    # $description: Testing
    # $show-in-menu

    from datetime import datetime
    
    import pya
    
    
    class MyApp(pya.QDialog):
        def __init__(self, parent):
            super(MyApp, self).__init__(parent)
    
            # Change title
            self.windowTitle = 'Automated Parameter Extraction'
            self.resize(1250, 500)
    
            # Set application font
            self.font = pya.QFont("Arial", 10)
            self.setFont(self.font)
    
            # Create a vertical layout for the main widget
            self.layout = pya.QGridLayout()
            self.setLayout(self.layout)
    
            # Create the first panel
            self.first_panel = pya.QWidget()
            self.first_layout = pya.QVBoxLayout()
            self.first_panel.setLayout(self.first_layout)
    
            # Create and connect the combo box to switch between pages
            self.deviceCombo = pya.QComboBox()
            self.deviceCombo.addItems(["Multiple Devices (Config File)"])
            self.deviceCombo.activated = self.switchPage
            self.first_layout.addWidget(self.deviceCombo)
    
            # Create the inputs related to a single device
            self.createPages()
            self.first_layout.addWidget(self.deviceGroupBox)
    
            # Add first panel to the main layout
            self.layout.addWidget(self.first_panel, 0, 0, 1, 1)
    
            # Create the second panel (scroll layout)
            self.second_panel = pya.QWidget()
            self.second_layout = pya.QVBoxLayout()
            self.second_panel.setLayout(self.second_layout)
    
            # Create a scroll area
            self.scroll_area = pya.QScrollArea()
            self.scroll_widget = pya.QWidget()
            self.scroll_layout = pya.QVBoxLayout()
            self.scroll_widget.setLayout(self.scroll_layout)
            self.scroll_area.setWidgetResizable(True)
            self.scroll_area.setWidget(self.scroll_widget)
    
            # Add scroll area to the second layout
            self.second_layout.addWidget(self.scroll_area)
    
            # Analysis button
            self.ok = pya.QPushButton("Run Extraction")
            self.ok.clicked = self.runPE
            self.second_layout.addWidget(self.ok)
    
            # Add second panel to the main layout
            self.layout.addWidget(self.second_panel, 0, 1, 1, 1)
    
        def createPages(self):
    
            # Initialize
            self.deviceGroupBox = pya.QGroupBox('Extraction Information')
            self.stackedLayout = pya.QStackedLayout()
            self.deviceGroupBox.setLayout(self.stackedLayout)
    
            self.page1 = pya.QWidget()
            self.page1_layout = pya.QGridLayout()
    
            self.edit1_pane = pya.QWidget()
            self.edit1_layout = pya.QVBoxLayout()
            self.edit1_pane.setLayout(self.edit1_layout)
    
            self.button1_pane = pya.QWidget()
            self.button1_layout = pya.QVBoxLayout()
            self.button1_pane.setLayout(self.button1_layout)
    
            # Create output location edit field
            self.outputLocation = pya.QLineEdit(self)
            self.button1 = pya.QPushButton()
            self.edit1_layout.addWidget(self.outputLocation)
            self.button1_layout.addWidget(self.button1)
    
            # Create top cell choice edit field
            self.topCell = pya.QLineEdit(self)
            self.button2 = pya.QPushButton()
            self.edit1_layout.addWidget(self.topCell)
            self.button1_layout.addWidget(self.button2)
    
            # Create unit cell choice edit field
            self.unitCell = pya.QLineEdit(self)
            self.button3 = pya.QPushButton()
            self.edit1_layout.addWidget(self.unitCell)
            self.button1_layout.addWidget(self.button3)
    
            # Create edge cell choice edit field
            self.edgeCell = pya.QLineEdit(self)
            self.button4 = pya.QPushButton()
            self.edit1_layout.addWidget(self.edgeCell)
            self.button1_layout.addWidget(self.button4)
    
            # Reformat
            self.page1_layout.addWidget(self.edit1_pane, 0, 0, 1, 1)
            self.page1_layout.addWidget(self.button1_pane, 0, 1, 1, 1)
    
            self.page1.setLayout(self.page1_layout)
            self.stackedLayout.addWidget(self.page1)
    
            # Initialize
            self.page2 = pya.QWidget()
            self.page2_layout = pya.QGridLayout()
    
            self.edit2_pane = pya.QWidget()
            self.edit2_layout = pya.QVBoxLayout()
            self.edit2_pane.setLayout(self.edit2_layout)
    
            self.button2_pane = pya.QWidget()
            self.button2_layout = pya.QVBoxLayout()
            self.button2_pane.setLayout(self.button2_layout)
    
            # Create input files location edit field
            self.inFiles = pya.QLineEdit(self)
            self.button5 = pya.QPushButton()
            self.edit2_layout.addWidget(self.inFiles)
            self.button2_layout.addWidget(self.button5)
    
            # Create config file location edit field
            self.configFile = pya.QLineEdit(self)
            self.button6 = pya.QPushButton()
            self.edit2_layout.addWidget(self.configFile)
            self.button2_layout.addWidget(self.button6)
    
            # Reformat
            self.page2_layout.addWidget(self.edit2_pane, 0, 0, 1, 1)
            self.page2_layout.addWidget(self.button2_pane, 0, 1, 1, 1)
    
            self.page2.setLayout(self.page2_layout)
            self.stackedLayout.addWidget(self.page2)
    
        def switchPage(self):
            self.stackedLayout.setCurrentIndex(self.deviceCombo.currentIndex)
    
        def runPE(self):
            self.multipleDevice()
    
        def multipleDevice(self):
    
            # Create tab widget within croll layout
            self.tab_widget = pya.QTabWidget()
            self.scroll_layout.addWidget(self.tab_widget)
            tab = []
            tab_layout = []
    
            data_for_tabs = dict()
            data_for_tabs["Device1"] = dict()
            data_for_tabs["Device1"]["Parameter1"] = dict()
            data_for_tabs["Device1"]["Parameter1"]["x"] = 1.2
            data_for_tabs["Device1"]["Parameter1"]["y"] = 1.5
            data_for_tabs["Device1"]["Parameter2"] = dict()
            data_for_tabs["Device1"]["Parameter2"]["x"] = 1.4
            data_for_tabs["Device1"]["Parameter2"]["y"] = 1.3
    
            data_for_tabs["Device2"] = dict()
            data_for_tabs["Device2"]["Parameter1"] = dict()
            data_for_tabs["Device2"]["Parameter1"]["x"] = 2.2
            data_for_tabs["Device2"]["Parameter1"]["y"] = 2.5
            data_for_tabs["Device2"]["Parameter2"] = dict()
            data_for_tabs["Device2"]["Parameter2"]["x"] = 2.4
            data_for_tabs["Device2"]["Parameter2"]["y"] = 2.3
    
            # First generate tabs
            tabNumber = 0
            for key in data_for_tabs.keys():
                # Create tab
                tab.append(pya.QWidget())
                tab_layout.append(pya.QVBoxLayout())
                tab[tabNumber].setLayout(tab_layout[tabNumber])
                self.tab_widget.addTab(tab[tabNumber], key)
                self.tab = tab[tabNumber]
                self.tab_layout = tab_layout[tabNumber]
    
                self.runExtraction(data_for_tabs[key], self.tab, self.tab_layout)
                tabNumber += 1
    
        def runExtraction(self, data, tab, tab_layout):
            row = 0
            for key, value in data.items():
                outGroupBox = pya.QGroupBox(key)
                self.out_layout = pya.QHBoxLayout()
                for pkey, pval in value.items():
                    txtlbl = pya.QLabel(tab)
                    txtlbl.setText(f"{pkey} = ")
                    txt = pya.QLineEdit(tab)
                    txt.setText(f"{pval}")
                    txt.setReadOnly(True)
    
                    self.out_layout.addWidget(txtlbl)
                    self.out_layout.addWidget(txt)
    
                self.button = pya.QPushButton()
                self.button.text = f"{key}"
                self.out_layout.addWidget(self.button)
    
                # Reformat
                outGroupBox.setLayout(self.out_layout)
                outGroupBox.update()
                setattr(self, "outGroupBox" + str(row), outGroupBox)
                tab_layout.addWidget(getattr(self, "outGroupBox" + str(row)))
                row += 1
    
            # Add stretch so everything sits at the top
            tab_layout.addStretch()
    
    
    # Create the app instance and run the event loop
    peDialog = MyApp(pya.Application.instance().main_window())
    peDialog.show()
    

    '

  • @Matthias for context, I will not know ahead of time how many tabs I will need. So I want the code to be dynamic such that it can be 2 tabs or 15 if the user chooses.

    I have tried using setattr and getattr to attach numbers to the tabs, but for some reason, that is causing me some issues with the tabs even showing up on the application.

    I am also curious if the issue is my version? Maybe if the version of KLayout/PyQT5 is too old, that may be the issue. At this point, I have no idea.
  • Hi ManzAutomations

    I slightly change the UI part of you code into a widget to hold dynamyc data.

    The example is as below, the parameter input part is generated randomly to simulate a dynamic data set.


    from datetime import datetime import random import pya class MyApp(pya.QDialog): def __init__(self, parent): super(MyApp, self).__init__(parent) # Change title self.windowTitle = 'Automated Parameter Extraction' self.resize(1250, 500) # Set application font self.font = pya.QFont("Arial", 10) self.setFont(self.font) # Create a vertical layout for the main widget self.layout = pya.QGridLayout() self.setLayout(self.layout) # Create the first panel self.first_panel = pya.QWidget() self.first_layout = pya.QVBoxLayout() self.first_panel.setLayout(self.first_layout) # Create and connect the combo box to switch between pages self.deviceCombo = pya.QComboBox() self.deviceCombo.addItems(["Multiple Devices (Config File)"]) self.deviceCombo.activated = self.switchPage self.first_layout.addWidget(self.deviceCombo) # Create the inputs related to a single device self.createPages() self.first_layout.addWidget(self.deviceGroupBox) # Add first panel to the main layout self.layout.addWidget(self.first_panel, 0, 0, 1, 1) # Create the second panel (scroll layout) self.second_panel = pya.QWidget() self.second_layout = pya.QVBoxLayout() self.second_panel.setLayout(self.second_layout) # Create a scroll area self.scroll_area = pya.QScrollArea() self.scroll_widget = pya.QWidget() self.scroll_layout = pya.QVBoxLayout() self.scroll_widget.setLayout(self.scroll_layout) self.scroll_area.setWidgetResizable(True) self.scroll_area.setWidget(self.scroll_widget) # Add scroll area to the second layout self.second_layout.addWidget(self.scroll_area) # Analysis button self.ok = pya.QPushButton("Run Extraction") self.ok.clicked = self.runPE self.second_layout.addWidget(self.ok) # Add second panel to the main layout self.layout.addWidget(self.second_panel, 0, 1, 1, 1) def createPages(self): # Initialize self.deviceGroupBox = pya.QGroupBox('Extraction Information') self.stackedLayout = pya.QStackedLayout() self.deviceGroupBox.setLayout(self.stackedLayout) self.page1 = pya.QWidget() self.page1_layout = pya.QGridLayout() self.edit1_pane = pya.QWidget() self.edit1_layout = pya.QVBoxLayout() self.edit1_pane.setLayout(self.edit1_layout) self.button1_pane = pya.QWidget() self.button1_layout = pya.QVBoxLayout() self.button1_pane.setLayout(self.button1_layout) # Create output location edit field self.outputLocation = pya.QLineEdit(self) self.button1 = pya.QPushButton() self.edit1_layout.addWidget(self.outputLocation) self.button1_layout.addWidget(self.button1) # Create top cell choice edit field self.topCell = pya.QLineEdit(self) self.button2 = pya.QPushButton() self.edit1_layout.addWidget(self.topCell) self.button1_layout.addWidget(self.button2) # Create unit cell choice edit field self.unitCell = pya.QLineEdit(self) self.button3 = pya.QPushButton() self.edit1_layout.addWidget(self.unitCell) self.button1_layout.addWidget(self.button3) # Create edge cell choice edit field self.edgeCell = pya.QLineEdit(self) self.button4 = pya.QPushButton() self.edit1_layout.addWidget(self.edgeCell) self.button1_layout.addWidget(self.button4) # Reformat self.page1_layout.addWidget(self.edit1_pane, 0, 0, 1, 1) self.page1_layout.addWidget(self.button1_pane, 0, 1, 1, 1) self.page1.setLayout(self.page1_layout) self.stackedLayout.addWidget(self.page1) # Initialize self.page2 = pya.QWidget() self.page2_layout = pya.QGridLayout() self.edit2_pane = pya.QWidget() self.edit2_layout = pya.QVBoxLayout() self.edit2_pane.setLayout(self.edit2_layout) self.button2_pane = pya.QWidget() self.button2_layout = pya.QVBoxLayout() self.button2_pane.setLayout(self.button2_layout) # Create input files location edit field self.inFiles = pya.QLineEdit(self) self.button5 = pya.QPushButton() self.edit2_layout.addWidget(self.inFiles) self.button2_layout.addWidget(self.button5) # Create config file location edit field self.configFile = pya.QLineEdit(self) self.button6 = pya.QPushButton() self.edit2_layout.addWidget(self.configFile) self.button2_layout.addWidget(self.button6) # Reformat self.page2_layout.addWidget(self.edit2_pane, 0, 0, 1, 1) self.page2_layout.addWidget(self.button2_pane, 0, 1, 1, 1) self.page2.setLayout(self.page2_layout) self.stackedLayout.addWidget(self.page2) def switchPage(self): self.stackedLayout.setCurrentIndex(self.deviceCombo.currentIndex) def runPE(self): self.multipleDevice() def multipleDevice(self): # Create tab widget within croll layout self.tab_widget = pya.QTabWidget() self.scroll_layout.addWidget(self.tab_widget) # generates random data, with random device count, random amount of parameter and randon amount of sub item data_for_tabs = {} for i in range(random.randint(2,10)): dName = "Device%d" % (i+1) data_for_tabs[dName] = {} for j in range(random.randint(2,5)): pName = "Parameter%d" % (j+1) data_for_tabs[dName][pName] = {} for k in range(random.randint(2,5)): iName = random.choice(string.ascii_letters) data_for_tabs[dName][pName][iName] = random.randint(10,20)/10 # add TabPage widget on demand for deviceName in data_for_tabs: tData = data_for_tabs[deviceName] self.tab_widget.addTab (TabPageWidget(tData, self), deviceName) class TabPageWidget(pya.QWidget): def __init__(self, tData, parent = None): super(TabPageWidget, self).__init__(parent) self.layout = pya.QVBoxLayout() for key in tData: pData = tData[key] self.layout.addWidget(ParameterWidget(key, pData, self)) self.layout.addStretch() self.setLayout(self.layout) class ParameterWidget(pya.QWidget): def __init__(self, pName, pData, parent = None): super(ParameterWidget, self).__init__(parent) self.groupBox = pya.QGroupBox(pName) self.gbLayout = pya.QGridLayout() self.layout = pya.QVBoxLayout() for row, itemName in enumerate(pData): itemLabel = pya.QLabel(itemName + "=", self) itemEdit = pya.QLineEdit(str(pData[itemName]), self) itemEdit.setReadOnly(True) itemLabel.setFixedWidth(35) self.gbLayout.addWidget(itemLabel, row, 0, 1, 1) self.gbLayout.addWidget(itemEdit, row, 1, 1, 1) self.groupBox.setLayout(self.gbLayout) self.layout.addWidget(self.groupBox) self.setLayout(self.layout) # Create the app instance and run the event loop peDialog = MyApp(pya.Application.instance().main_window()) peDialog.show()
  • Hi ManzAutomations

    As to why the tabs is not showing correctly, this is due to the widget or tab is not set to bind with a parent widget.
    System will automatically remove the unbinded widget to free up memory and results in tabs missing.

    for instance, we would like to add TabPageWidget to UI with binding, the way to do this is:
    Method-1

    #declear  to self.* parameter, this sets TabPageWidget's parent to "self" widget
    #suitable to fix UI layout, since the number of the widget is known and can be predefined with parameter
    self.tpw = TabPageWidget(tData)
    self.tab_widget.addTab (self.tpw, deviceName)
    

    Method-2

    #using parent parameter to pass "self" to widget
    #which is more suitable for generate dynamic UI
    self.tab_widget.addTab (TabPageWidget(tData, self), deviceName)
    

    Class declaration
    The parent parameter will be binded using "init(parent)" , make sure your custom widget did pass this parameter to it's "super" class.

    class TabPageWidget(pya.QWidget):
        def __init__(self, tData, parent = None):
            super(TabPageWidget, self).__init__(parent)
    
  • @RawrRanger That fixed my issue! Thank you so much!

Sign In or Register to comment.