I have a problem with the creation of multiple tabs in table and a delete button. This button should delete the rows in the current table. My problem is that it only deletes rows in the last created table if I create more than one new tab. And I can't name the tables due to the fact that I don't know how many tabs I need.
import sys
from PyQt4 import QtGui, QtCore
class Fenster(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.resize(300, 300)
addButton = QtGui.QPushButton(u"Add Tab")
self.connect(addButton, QtCore.SIGNAL("clicked()"), self.addTab)
layout = QtGui.QVBoxLayout()
layout.addWidget(addButton)
self.tab_widget = QtGui.QTabWidget()
self.tab_widget.updatesEnabled()
widget = QtGui.QWidget()
self.tab_widget.addTab(widget, "Tab 1")
widget.setLayout(layout)
self.setCentralWidget(self.tab_widget)
self.show()
def addTab(self):
contents = QtGui.QWidget()
delButton = QtGui.QPushButton(u"Del Row")
self.connect(delButton, QtCore.SIGNAL("clicked()"), self.delRow)
self.table = QtGui.QTableWidget(5, 2)
layout = QtGui.QVBoxLayout()
layout.addWidget(self.table)
layout.addWidget(delButton)
self.tab_widget.addTab(contents, "New Tab")
contents.setLayout(layout)
def delRow(self):
self.table.setRowCount(0)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Fenster()
window.show()
sys.exit(app.exec_())
The problem is that self.table always points to the last table widget you created. The delRow method needs to know which table to delete from, so it needs a reference to that table. I would suggest the following code. Here, your slot takes an argument that points to the table you want to delete from.
def addTab(self):
contents = QtGui.QWidget()
table = QtGui.QTableWidget(5, 2)
delButton = QtGui.QPushButton(u"Del Row")
delButton.clicked.connect(lambda: self.delRow(table))
layout = QtGui.QVBoxLayout()
layout.addWidget(table)
layout.addWidget(delButton)
self.tab_widget.addTab(contents, "New Tab")
contents.setLayout(layout)
def delRow(self, table):
table.setRowCount(0)
A couple of things about this code:
I've used the new style signal/slot method to connect the clicked signal to a slot (it is more pythonic)
Because the signal expects to connect to a slot that takes no arguments, I've wrapped your delRow(table) method using lambda. If you haven't come across lambda before, it is basically short hand for writing a one line function. You can read up about it in the Python docs.
You could track which tab is active via currentChanged() signal. Then in signal handler you set self.table.
Related
This question already has answers here:
PyQT Navigation between layouts
(2 answers)
Closed 1 year ago.
How to get back my MainWindow ?
From my MainWindow. If I Press either the "Open Left Box" button or "Open Right Box" button, it's worked and at the same time, If I press the "Back" Button from Left Box, nothing will happen. How to obtain the main window? (Simply, I want to know how to set Layouts and remove layouts in setcentral Widgets)
import sys,os
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class Class_MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Main Window")
self.initUI()
def initUI(self):
self.widgets()
self.layouts()
def widgets(self):
self.Masterbtn = QPushButton("Master")
self.transbtn = QPushButton("tanscation")
self.reportbtn = QPushButton("Reports")
self.masterlbl = QLabel("Master Label")
self.translbl = QLabel("transcation label")
self.reportlbl = QLabel("Report Label")
self.leftboxbtn = QPushButton("Open Left Box")
self.leftboxbtn.clicked.connect(self.leftboxopn)
self.rightboxbtn = QPushButton("Open Right Box")
self.rightboxbtn.clicked.connect(self.rightboxopn)
self.backbtn =QPushButton("Back")
self.backbtn.clicked.connect(self.mainwindow)
def layouts(self):
self.mainbox = QVBoxLayout()
self.mainbox.addWidget(self.leftboxbtn)
self.mainbox.addWidget(self.rightboxbtn)
self.leftbox = QVBoxLayout()
self.leftbox.addWidget(self.Masterbtn)
self.leftbox.addWidget(self.transbtn)
self.leftbox.addWidget(self.reportbtn)
self.leftbox.addWidget(self.backbtn)
self.rightbox = QVBoxLayout()
self.rightbox.addWidget(self.masterlbl)
self.rightbox.addWidget(self.translbl)
self.rightbox.addWidget(self.reportlbl)
# self.rightbox.addWidget(self.backbtn)
widget = QWidget()
widget.setLayout(self.mainbox)
self.setCentralWidget(widget)
def leftboxopn(self):
self.setWindowTitle("Left Box ")
widget = QWidget()
widget.setLayout(self.leftbox)
self.setCentralWidget(widget)
def rightboxopn(self):
self.setWindowTitle("Right Box")
widget = QWidget()
widget.setLayout(self.rightbox)
self.setCentralWidget(widget)
def mainwindow(self):
self.setWindowTitle("Main Window")
widget = QWidget()
widget.setLayout(self.mainbox)
self.setCentralWidget(widget)
def main():
app = QApplication(sys.argv)
mainwindow = Class_MainWindow()
mainwindow.show()
sys.exit(app.exec_())
if __name__ =="__main__":
main()
You cannot "get back" anything, because everytime you use setCentralWidget() the existing widget gets deleted, as the documentation explains:
Note: QMainWindow takes ownership of the widget pointer and deletes it at the appropriate time.
When you call setCentralWidget() on another widget, the previous is completely deleted, including all its children. When a Qt object is deleted, all its child objects gets deleted along with it, and the result is that the self.mainbox you created in the beginning doesn't exist any more (the python object exists, but remember that PyQt objects are only a reference to the actual Qt objects: a Qt object can be deleted if Qt requires it, even if the python reference still exists).
In substance (and, in any case), you should not replace the central widget everytime, but use a paged widget like QStackedWidget as the central widget instead, and then switch to the other interfaces using its functions: setCurrentIndex() or setCurrentWidget().
In order to properly use it, all child widgets must be added to a QWidget container, which will then be added as individual "pages" to QStackedWidget:
class Class_MainWindow(QMainWindow):
# ...
def layouts(self):
self.mainContainer = QWidget()
self.mainbox = QVBoxLayout(self.mainContainer)
self.mainbox.addWidget(self.leftboxbtn)
self.mainbox.addWidget(self.rightboxbtn)
self.leftContainer = QWidget()
self.leftbox = QVBoxLayout(self.leftContainer)
self.leftbox.addWidget(self.Masterbtn)
self.leftbox.addWidget(self.transbtn)
self.leftbox.addWidget(self.reportbtn)
self.leftbox.addWidget(self.backbtn)
self.rightContainer = QWidget()
self.rightbox = QVBoxLayout(self.rightContainer)
self.rightbox.addWidget(self.masterlbl)
self.rightbox.addWidget(self.translbl)
self.rightbox.addWidget(self.reportlbl)
# self.rightbox.addWidget(self.backbtn)
self.stackedWidget = QStackedWidget()
self.setCentralWidget(self.stackedWidget)
self.stackedWidget.addWidget(self.mainContainer)
self.stackedWidget.addWidget(self.leftContainer)
self.stackedWidget.addWidget(self.rightContainer)
def leftboxopn(self):
self.setWindowTitle("Left Box ")
self.stackedWidget.setCurrentWidget(self.leftContainer)
def rightboxopn(self):
self.setWindowTitle("Right Box")
self.stackedWidget.setCurrentWidget(self.rightContainer)
def mainwindow(self):
self.setWindowTitle("Main Window")
self.stackedWidget.setCurrentWidget(self.mainContainer)
Edits: Phrasing and included a minimum reproducible example
I have a question about how object names work in PyQt5 / how OOP works in PyQt5:
Background
I'm working on a project which includes a QTabWidget. This QTabWidget adds a new tab each time a '+' tab is clicked:
Before click:
After click:
When I add the new tab as just a blank widget using insertNewRegionTab(), everything works properly; however, when I instead use a custom class that is a derived class of QtWidgets.QWidget (customTab), the program crashes. I created this class so that each tab would have the same formatting / internal relationships between objects, and that I could just add as many as I needed with the '+' button.
I think the crashing may be due to name conflicts, e.g. a label called "label1" being created in two tabs simultaneously.
Question
The problem is that I don't see how name conflicts could be an issue if each customTab is it's own object. I'm wondering if any one knows why I can't implement this customTab class in the desired way.
Proposed Solution
My thoughts on a solution is to just define class counters and then increment every time a new tab is added so they all have unique names, but this is a bit of work so I wanted to make sure that this is actually what the problem is before I implement it.
Code
N.B. - The two original tabs using this code will not have names. The one on the left, 'Test 1', has a combo box in it and is in the customTab class. The one on the right, '+', adds a new tab, but a normal QWidget instead of customTab object
from PyQt5 import QtCore, QtGui, QtWidgets
class topLevelWindow(QtWidgets.QMainWindow):
def __init__(self):
# Function that is used to add tabs
def insertNewRegionTab(self):
if self.mainTabWidgetCount == 1 + self.mainTabWidget.currentIndex():
# Attempted method 1:
tab = QtWidgets.QWidget() # Want this to be a customTab not a QWidget
self.mainTabWidget.insertTab(self.mainTabWidget.currentIndex(),
tab, "Tab " + str(self.mainTabWidgetCount))
# Attempted method 2:
# self.tabInstaces.append(customTab(self.mainTabWidget))
self.mainTabWidgetCount += 1
super().__init__()
# Initialization of main window
self.setObjectName("MainWindow")
self.resize(500, 300)
self.centralwidget = QtWidgets.QWidget(self)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout_5 = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout_5.setObjectName("gridLayout_5")
# Create the main tab widget and set properties
self.mainTabWidget = QtWidgets.QTabWidget(self.centralwidget)
self.mainTabWidget.setObjectName("mainTabWidget")
self.mainTabWidget.setCurrentIndex(0)
self.gridLayout_5.addWidget(self.mainTabWidget, 1, 1, 1, 1)
self.setCentralWidget(self.centralwidget)
# Insert a tab (of the custom, pre-formatted tab class) into this tab widget
self.tabInstances = [customTab(self.mainTabWidget)]
self.mainTabWidgetCount = 2
# Add the tab which creates other tabs to the tan widget
self.addRegionTab = QtWidgets.QWidget()
self.addRegionTab.setObjectName("addRegionTab")
self.mainTabWidget.addTab(self.addRegionTab, "")
# Add functionality: When '+' tab is selected, add a tab
self.mainTabWidget.currentChanged.connect(lambda: insertNewRegionTab(self))
# Show window
self.show()
class customTab(QtWidgets.QWidget):
def __init__(self, parent=None):
# Initializes the object itself and renames it. Add vertical layout to it
super(customTab, self).__init__()
self.setObjectName("tabInstances")
self.verticalLayout = QtWidgets.QVBoxLayout(self)
self.verticalLayout.setObjectName("verticalLayout")
self.comboBox1 = QtWidgets.QComboBox(self)
self.comboBox1.setObjectName("comboBox1")
self.comboBox1.addItem("")
self.comboBox1.addItem("")
# Add self to parent
parent.addTab(self, "")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = topLevelWindow()
sys.exit(app.exec_())
I do not understand the logic of OP since it asks about objectName() that has nothing to do with the problem, the objectName is only a name that allows us to identify elements and nothing else.
In my answer I show how to create the button that when pressing must add tabs:
from PyQt5 import QtCore, QtGui, QtWidgets
class TabWidget(QtWidgets.QTabWidget):
plusClicked = QtCore.pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.tabBar().installEventFilter(self)
self.add_button = QtWidgets.QToolButton(self, text="+")
self.add_button.clicked.connect(self.plusClicked)
def eventFilter(self, obj, event):
if obj is self.tabBar() and event.type() == QtCore.QEvent.Resize:
r = self.tabBar().geometry()
h = r.height()
self.add_button.setFixedSize((h - 1) * QtCore.QSize(1, 1))
self.add_button.move(r.right(), 0)
return super().eventFilter(obj, event)
class topLevelWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
# Initialization of main window
self.setObjectName("MainWindow")
self.resize(500, 300)
self.centralwidget = QtWidgets.QWidget(self)
self.setCentralWidget(self.centralwidget)
lay = QtWidgets.QGridLayout(self.centralwidget)
# Create the main tab widget and set properties
self.mainTabWidget = TabWidget()
self.mainTabWidget.setObjectName("mainTabWidget")
self.mainTabWidget.setCurrentIndex(0)
lay.addWidget(self.mainTabWidget, 1, 1, 1, 1)
self.mainTabWidget.addTab(CustomWidget(), "Tab1")
self.mainTabWidget.plusClicked.connect(self.add_clicked)
def add_clicked(self):
index = self.mainTabWidget.count() + 1
self.mainTabWidget.addTab(CustomWidget(), "Tab {}".format(index))
class CustomWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
# Initializes the object itself and renames it. Add vertical layout to it
super(CustomWidget, self).__init__(parent)
self.comboBox1 = QtWidgets.QComboBox()
self.comboBox1.addItems(list("abcdef"))
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.comboBox1)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = topLevelWindow()
w.show()
sys.exit(app.exec_())
how to place objects within the confines of a QFrame as i can't get my head around it. I've read the documentation on https://doc.qt.io/qtforpython/PySide2/QtWidgets/QFrame.html but its just not sinking in for me. I've also looked at various code snippets but nothing seems to do what i want.
When i try to call methods of either a QPushButton or QFrame there seems to be no options for either to interact with each other.
from PySide2.QtWidgets import *
import sys
class ButtonTest(QWidget):
def __init__(self):
QWidget.__init__(self)
self.button1 = QPushButton("Button 1")
self.button2 = QPushButton("Button 2")
self.myframe = QFrame()
self.myframe.setFrameShape(QFrame.StyledPanel)
self.myframe.setFrameShadow(QFrame.Plain)
self.myframe.setLineWidth(3)
self.buttonlayout = QVBoxLayout(self.myframe)
self.buttonlayout.addWidget(self.button1)
self.buttonlayout.addWidget(self.button2)
self.setLayout(self.buttonlayout)
app = QApplication(sys.argv)
mainwindow = ButtonTest()
mainwindow.show()
sys.exit(app.exec_())
They pass in the QFrame as an argument when constructing the layout. This compiles fine, but the frame is nowhere to be seen.
The problem is simple: A layout can only be established in a widget, to better understand you have to know that:
lay = QXLayout(foowidet)
equals:
lay = QXLayout()
foowidget.setLayout(lay)
In your code you first pointed out that buttonlayout handles myframe's child widgets(self.buttonlayout = QVBoxLayout(self.myframe)) but then you have set it to handle window's children(self.addWidget(self.myframe).
The solution is to establish the QFrame through a layout:
class ButtonTest(QWidget):
def __init__(self):
super(ButtonTest, self).__init__()
self.button1 = QPushButton("Button 1")
self.button2 = QPushButton("Button 2")
self.myframe = QFrame()
self.myframe.setFrameShape(QFrame.StyledPanel)
self.myframe.setFrameShadow(QFrame.Plain)
self.myframe.setLineWidth(3)
buttonlayout = QVBoxLayout(self.myframe)
buttonlayout.addWidget(self.button1)
buttonlayout.addWidget(self.button2)
lay = QVBoxLayout(self)
lay.addWidget(self.myframe)
With a row of QListWidgets I wonder if there is a simple way to label each so the user would be able to say which List is which.
Here is a dialog's screenshot a code posted below results.
I avoid using any widgets outside of QListWidgets(). Ideal solution would be solved utilizing QListWidgets itself. It would be great if there is a way to place a text line-label similar to those available for QGroupBox with .setTitle('myString'). At very least an ability to place a label as a first list item would be sufficient too...
from PyQt4 import QtGui, QtCore
class MyApp(object):
def __init__(self):
super(MyApp, self).__init__()
app = QtGui.QApplication(sys.argv)
self.mainWidget = QtGui.QWidget()
self.mainLayout = QtGui.QVBoxLayout()
self.mainWidget.setLayout(self.mainLayout)
self.groupbox = QtGui.QGroupBox()
self.groupbox.setTitle('My Groupbox')
self.layout = QtGui.QVBoxLayout()
self.groupbox.setLayout(self.layout)
self.listGroupbox = QtGui.QGroupBox()
self.listLayout = QtGui.QHBoxLayout()
self.listGroupbox.setLayout(self.listLayout)
self.listA=QtGui.QListWidget()
self.listB=QtGui.QListWidget()
self.listLayout.addWidget(self.listA)
self.listLayout.addWidget(self.listB)
self.layout.addWidget(self.listGroupbox)
self.okButton = QtGui.QPushButton('OK')
self.okButton.clicked.connect(self.OK)
self.layout.addWidget(self.okButton)
self.mainLayout.addWidget(self.groupbox)
self.mainWidget.show()
sys.exit(app.exec_())
def OK(self):
print 'Ok'
if __name__ == '__main__':
MyApp()
Unfortunately there's no (Qt-native) way to label a QListView (on which QListWidget is based). If your really don't want additional widgets, I would instead use a single-column QTableWidget, and put the list title in the column header. QTableWidget and QListWidget work pretty similarly, so this probably won't break too much of your existing code.
An example based on yours:
class MyApp(object):
def __init__(self):
# snipped
self.listA = self.prepareTableWidget('List A')
self.listB = self.prepareTableWidget('List B')
# snipped
def prepareTableWidget(self, name):
table = QtGui.QTableWidget()
table.setColumnCount(1)
table.setHorizontalHeaderLabels([name])
table.horizontalHeader().setResizeMode(QtGui.QHeaderView.Stretch)
return table
# snipped
Here is my attempt to achieve it without abandoning QListWidget()... utilizing layout's .insertLayout() method to attach QLabel without losing GUI space usually taken by QGroupBox()...
from PyQt4 import QtGui, QtCore
class MyApp(object):
def __init__(self):
super(MyApp, self).__init__()
app = QtGui.QApplication(sys.argv)
self.mainWidget = QtGui.QWidget()
self.mainLayout = QtGui.QVBoxLayout()
self.mainWidget.setLayout(self.mainLayout)
self.groupbox = QtGui.QGroupBox()
self.groupbox.setTitle('My Groupbox')
self.layout = QtGui.QVBoxLayout()
self.groupbox.setLayout(self.layout)
self.listGroupbox = QtGui.QGroupBox()
self.listLayout = QtGui.QHBoxLayout()
self.listGroupbox.setLayout(self.listLayout)
self.listA=QtGui.QListWidget()
self.listB=QtGui.QListWidget()
self.subLayoutA=QtGui.QVBoxLayout()
self.listLayout.insertLayout(0,self.subLayoutA)
self.subLayoutA.addWidget(QtGui.QLabel('Label A') )
self.subLayoutA.addWidget(self.listA)
self.subLayoutB=QtGui.QVBoxLayout()
self.listLayout.insertLayout(1,self.subLayoutB)
self.subLayoutB.addWidget(QtGui.QLabel('Label B') )
self.subLayoutB.addWidget(self.listB)
self.layout.addWidget(self.listGroupbox)
self.okButton = QtGui.QPushButton('OK')
self.okButton.clicked.connect(self.OK)
self.layout.addWidget(self.okButton)
self.mainLayout.addWidget(self.groupbox)
self.mainWidget.show()
sys.exit(app.exec_())
def OK(self):
print 'Ok'
if __name__ == '__main__':
MyApp()
I have QTabWidget, and layout with some widgets and sub layouts that I want to show in this QTabWidget tabs. what I want to do is to add this layout to the first tab (as default), and if the user moves to the next tab, I want to show the exact same layout, and to add some widgets near it.
this is the layout that I am talking about:
self.right_tab_layout = QVBoxLayout()
self.right_tab_widget = QWidget()
self.right_tab_title_label = QLabel("Select full files path:")
self.simoderev_layout = QHBoxLayout()
self.simoderev_widget = QWidget()
self.simoderev_checkbox = QCheckBox("use simoderev as base: ")
self.simoderev_combobox = QComboBox()
self.paths_label = QLabel("paths:")
self.right_tab_widget.setLayout(self.right_tab_layout)
self.simoderev_widget.setLayout(self.simoderev_layout)
self.simoderev_widget.setMaximumWidth(250)
self.simoderev_layout.addWidget(self.simoderev_checkbox)
self.simoderev_layout.addWidget(self.simoderev_combobox)
self.simoderev_layout.setContentsMargins(0,0,0,0)
self.right_tab_layout.addWidget(self.right_tab_title_label)
self.right_tab_layout.addWidget(self.simoderev_widget)
self.right_tab_layout.addWidget(self.paths_label)
is there any way to do this ?
If you just want the widgets in the tabs to look the same, you should create a custom Widget class and put an instance of that class in each tab.
Your custom widget could look like:
class CustomWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(CustomWidget, self).__init__(parent)
layout = QtGui.QVBoxLayout(self)
self.title_label = QtGui.QLabel("Select full files path:")
self.simoderev_widget = QtGui.QWidget()
simoderev_layout = QtGui.QHBoxLayout(self.simoderev_widget)
self.simoderev_checkbox = QtGui.QCheckBox("use simoderev as base: ")
self.simoderev_combobox = QtGui.QComboBox()
self.simoderev_widget.setMaximumWidth(250)
simoderev_layout.addWidget(self.simoderev_checkbox)
simoderev_layout.addWidget(self.simoderev_combobox)
simoderev_layout.setContentsMargins(0,0,0,0)
self.paths_label = QtGui.QLabel("paths:")
layout.addWidget(self.title_label)
layout.addWidget(self.simoderev_widget)
layout.addWidget(self.paths_label)
If you want them to be the same, here's a hacky solution. You should connect the currentChanged signal of your tabwidget to a slot that will move your custom widget from one tab to the other.
class MyTabWidget(QtGui.QTabWidget):
def __init__(self, parent = None):
super(MyTabWidget, self).__init__(parent)
self.subwidget = CustomWidget(self)
self.left_tab_widget = QtGui.QWidget()
self.leftLayout = QtGui.QVBoxLayout(self.left_tab_widget)
self.leftLayout.addWidget(self.subwidget)
self.right_tab_widget = QtGui.QWidget(self)
self.rightLayout = QtGui.QVBoxLayout(self.right_tab_widget)
label = QtGui.QLabel("Some additional data", self.right_tab_widget)
self.rightLayout.addWidget(label)
self.addTab(self.left_tab_widget, "Left Tab")
self.addTab(self.right_tab_widget, "Right Tab")
self.currentChanged.connect(self.onCurrentChanged)
def onCurrentChanged(self, index):
if index == 0:
self.leftLayout.addWidget(self.subwidget)
else:
self.rightLayout.addWidget(self.subwidget)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
widget = MyTabWidget()
widget.show()
app.exec_()