I have a tab widget that contains table widgets for each tab, and I also have a dock widget with options. One of the options is the column count, and I want to change it as soon as the column count spin box value changes. But when switching to a different tab, I'd like the spin box value (and all other options) to reset / switch to that specific tab's settings.
My question is how to best do this and still have the options as a dock widget. I could store all settings as variables for each tab widget and then change the value each time a new tab is opened, I guess, but maybe there is a better solution.
from PyQt5 import QtWidgets, QtCore
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent = None):
super(MainWindow, self).__init__()
self.__setup__()
def __setup__(self):
self.resize(400, 400)
tabWidget = TabWidget(self)
self.setCentralWidget(tabWidget)
options = Options(self)
optionsDock = QtWidgets.QDockWidget()
optionsDock.setWidget(options)
optionsDock.setWindowTitle("Options")
self.addDockWidget(QtCore.Qt.TopDockWidgetArea, optionsDock)
options.spinBox_columns.valueChanged.connect(lambda: tabWidget.tabWidget.currentWidget().
setColumnCount(options.spinBox_columns.value()))
class Options(QtWidgets.QWidget):
def __init__(self, parent):
super(Options, self).__init__(parent)
self.__setup__()
def __setup__(self):
self.spinBox_columns = QtWidgets.QSpinBox()
self.spinBox_columns.setValue(1)
self.spinBox_columns.setMinimum(1)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.spinBox_columns)
self.setLayout(layout)
class TabWidget(QtWidgets.QWidget):
def __init__(self, parent):
super(TabWidget, self).__init__(parent)
self.__setup__()
def __setup__(self):
self.tabWidget = QtWidgets.QTabWidget()
for i in range(3):
widget = QtWidgets.QTableWidget()
widget.setColumnCount(1)
widget.setRowCount(3)
self.tabWidget.addTab(widget, "Column " + str(i))
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.tabWidget)
self.setLayout(layout)
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
You have to connect the currentChanged signal provided by the index of the tab, then use the widget() method to obtain the index associated with that index, then access its QTabWidget and obtain the number of columns using it to place the value to the QSpinBox.
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.__setup__()
def __setup__(self):
self.resize(400, 400)
tabWidget = TabWidget(self)
self.setCentralWidget(tabWidget)
options = Options(self)
optionsDock = QtWidgets.QDockWidget()
optionsDock.setWidget(options)
optionsDock.setWindowTitle("Options")
self.addDockWidget(QtCore.Qt.TopDockWidgetArea, optionsDock)
tabWidget.tabWidget.currentChanged.connect(lambda index: options.spinBox_columns.
setValue(tabWidget.tabWidget.widget(index).columnCount()))
options.spinBox_columns.valueChanged.connect(lambda value: tabWidget.tabWidget.currentWidget().
setColumnCount(value))
Related
The code below creates a number of tabs (from "howmuchtab"s value) in a QTabWidget when clicking pushbutton. Each tab contains a QTextBrowser.
I want to refresh the tabs each time the PushButton is clicked.
Problem is: when clicking "Create Tabs" for the second time (and subsequents...), the QTextBrowser is not created into the first tab (others tabs are ok).
from PyQt5 import QtWidgets, QtCore
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.button = QtWidgets.QPushButton('Create Tabs', self)
self.button.move(100, 70)
self.report_window = ReportWindow()
self.button.clicked.connect(self.analyze_clicked)
def analyze_clicked(self):
self.report_window.fill_reports()
self.report_window.show()
class ReportWindow(QtWidgets.QWidget):
def __init__(self):
super(ReportWindow, self).__init__()
self.layout = QtWidgets.QVBoxLayout(self)
self.tab = QtWidgets.QTabWidget()
self.layout.addWidget(self.tab)
self.setLayout(self.layout)
def fill_reports(self):
howmuchtabs = 5
if self.tab.count() != 0:
self.tab.clear()
for i in range(howmuchtabs):
self.create_tabs(self.tab, i + 1)
self.tb.append(f"Textbrowser in Tab #{i + 1}")
def create_tabs(self, obj, num):
tabtoadd = QtWidgets.QWidget()
obj.addTab(tabtoadd, str(num))
self.tb = QtWidgets.QTextBrowser(tabtoadd)
self.tb.setFrameShape(QtWidgets.QFrame.Box)
self.tb.setGeometry(QtCore.QRect(10, 10, 150, 30))
self.tb.setObjectName(str(num))
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Pictures:
First click
Second click, QTextBrowser is not created into first tab
self.tb what does QTextBrowser refer to? that is, when creating n tabs there will be n QTextBrowser, which of them is self.tb? Well, who knows (only the last one) so to avoid that kind of problem you just have to create a class that has that attribute:
class PageWidget(QtWidgets.QWidget):
def __init__(self, num, parent=None):
super().__init__(parent)
self.tb = QtWidgets.QTextBrowser(self)
self.tb.setFrameShape(QtWidgets.QFrame.Box)
self.tb.setGeometry(QtCore.QRect(10, 10, 150, 30))
self.tb.setObjectName(str(num))
class ReportWindow(QtWidgets.QWidget):
def __init__(self):
super(ReportWindow, self).__init__()
self.layout = QtWidgets.QVBoxLayout(self)
self.tab = QtWidgets.QTabWidget()
self.layout.addWidget(self.tab)
self.setLayout(self.layout)
def fill_reports(self):
howmuchtabs = 5
if self.tab.count() != 0:
self.tab.clear()
for i in range(howmuchtabs):
tabtoadd = self.create_tabs(self.tab, i + 1)
tabtoadd.tb.append(f"Textbrowser in Tab #{i + 1}")
def create_tabs(self, obj, num):
tabtoadd = PageWidget(num)
obj.addTab(tabtoadd, str(num))
return tabtoadd
I found some code on here that shows an example of how you can get the window to resize when the widget is hidden, and it works for me. Here is the code:
from PyQt4 import QtCore, QtGui
import sys
class MainWindow(QtGui.QWidget):
def __init__(self):
self.app = QtGui.QApplication(sys.argv)
super(MainWindow, self).__init__()
self.button = QtGui.QPushButton('Show/Hide')
self.button.setCheckable(True)
self.frame = QtGui.QFrame()
self.frame.setFixedHeight(100)
self.layout = layout = QtGui.QVBoxLayout()
layout2 = QtGui.QVBoxLayout()
self.setLayout(layout)
self.frame.setLayout(layout2)
layout.addWidget(self.button)
layout.addWidget(self.frame)
layout.addStretch(1)
layout2.addWidget(QtGui.QLabel('Yoyoyo'))
self.button.toggled.connect(self.clickAction)
def startup(self):
self.show()
sys.exit(self.app.exec_())
def clickAction(self):
checked = self.button.isChecked()
if checked:
self.frame.show()
else:
self.frame.hide()
QtCore.QTimer.singleShot(0, self.resizeMe)
def resizeMe(self):
self.resize(self.minimumSizeHint())
if __name__ == "__main__":
myApp = MainWindow()
myApp.startup()
I then tried to modify this to match my existing code by separating the mainWindow class and the widget class. Here is the code that does that.
from PySide import QtGui,QtCore
import sys
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.w = testW(self)
self.setCentralWidget(self.w)
self.show()
class testW(QtGui.QWidget):
def __init__(self,parent):
super(testW,self).__init__()
self.parent = parent
self.button = QtGui.QPushButton('Show/Hide')
self.button.setCheckable(True)
self.button.setChecked(True);
self.frame = QtGui.QFrame()
self.frame.setFixedHeight(100)
self.layout = layout = QtGui.QVBoxLayout()
layout2 = QtGui.QVBoxLayout()
self.setLayout(layout)
self.frame.setLayout(layout2)
layout.addWidget(self.button)
layout.addWidget(self.frame)
layout.addStretch(1)
layout2.addWidget(QtGui.QLabel('Yoyoyo'))
self.button.toggled.connect(self.clickAction)
def clickAction(self):
checked = self.button.isChecked()
if checked:
self.frame.show()
else:
self.frame.hide()
QtCore.QTimer.singleShot(0, self.resizeMe)
def resizeMe(self):
self.resize(self.minimumSizeHint())
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myApp = MainWindow()
sys.exit(app.exec_())
#time.sleep(1)
Running the first code does what I want it to. After I hide the widget, the window resizes to the correct size. The second implementation of the code does not shrink and expand the window when I hide and show the widget. Is this because the MainWindow is in a separate class?
Use size policies for your widgets. For your example you can change UI creation code as follows:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.w = testW(self)
self.w.setSizePolicy(
QtWidgets.QSizePolicy.MinimumExpanding,
QtWidgets.QSizePolicy.MinimumExpanding
)
self.setCentralWidget(self.w)
self.show()
Please note new setSizePolicy call which say Qt layout engine how to change the size of your widget according to its content.
Unfortunately QMainWindow does not respect sizeHint automatically, but it is calculated properly, so you can adjustSize manually:
def clickAction(self):
checked = self.button.isChecked()
if checked:
self.frame.show()
else:
self.frame.hide()
QtCore.QTimer.singleShot(0, self.parent.adjustSize)
You do not need to resize your widget itself, because it will be resized according to the policy. Even sizeHint will be calculated automatically so you need only to call adjustSize of QMainWindow.
PS: I used PySide2 instead of PySide so the imports are different a little bit:
from PySide2 import QtWidgets, QtCore
is there a way to get a context menu on a tables column head.
Find nothing about that in PyQt5's tuts.
the table's context menu is simple but the column heads don't affect.
# dlg is a QDialog object
self.tbl = QtWidgets.QTableWidget(dlg)
self.tbl.setContextMenuPolicy( Qt.CustomContextMenu )
You have to use the QHeaderView of the QTableWidget:
from PyQt5 import QtCore, QtWidgets
class Dialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
self.tbl = QtWidgets.QTableWidget(10, 10, self)
for w in (self.tbl.horizontalHeader(), self.tbl.verticalHeader(), self.tbl):
w.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
w.customContextMenuRequested.connect(self.on_customContextMenuRequested)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.tbl)
#QtCore.pyqtSlot(QtCore.QPoint)
def on_customContextMenuRequested(self, pos):
widget = self.sender()
if isinstance(widget, QtWidgets.QAbstractItemView):
widget = widget.viewport()
menu = QtWidgets.QMenu()
menu.addAction("Foo Action")
menu.exec_(widget.mapToGlobal(pos))
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Dialog()
w.show()
sys.exit(app.exec_())
Update:
class Dialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
self.tbl = QtWidgets.QTableWidget(10, 10, self)
self.tbl.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.tbl.customContextMenuRequested.connect(self.on_customContextMenuRequested_tw)
self.tbl.verticalHeader().setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.tbl.verticalHeader().customContextMenuRequested.connect(self.on_customContextMenuRequested_vh)
self.tbl.horizontalHeader().setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.tbl.horizontalHeader().customContextMenuRequested.connect(self.on_customContextMenuRequested_hh)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.tbl)
#QtCore.pyqtSlot(QtCore.QPoint)
def on_customContextMenuRequested_tw(self, pos):
menu = QtWidgets.QMenu()
menu.addAction("Foo Action TW")
menu.exec_(self.tbl.viewport().mapToGlobal(pos))
#QtCore.pyqtSlot(QtCore.QPoint)
def on_customContextMenuRequested_vh(self, pos):
menu = QtWidgets.QMenu()
menu.addAction("Foo Action VH")
menu.exec_(self.tbl.verticalHeader().mapToGlobal(pos))
#QtCore.pyqtSlot(QtCore.QPoint)
def on_customContextMenuRequested_hh(self, pos):
menu = QtWidgets.QMenu()
menu.addAction("Foo Action HH")
menu.exec_(self.tbl.horizontalHeader().mapToGlobal(pos))
You need to set the context menu policy on the header itself (if I've understood correctly), so...
self.tbl = QtWidgets.QTableWidget(dlg)
self.tbl.horizontalHeader().setContextMenuPolicy(Qt.CustomContextMenu)
and connect to the `QHeaderView::customContextMenuRequested signal...
self.tbl.horizontalHeader().customContextMenuRequested.connect(self.handle_context_menu_request)
I have a pushbutton to add a tab in a QTabWidget. But when I change the button's size, it's not in the corner anymore. So how can I bring it to the corner like in the first picture?
Not change size:
Change size:
Here is my code:
from PyQt4 import QtGui, QtCore
class PlaylistTable(QtGui.QWidget):
def __init__(self):
super(PlaylistTable, self).__init__()
self.playlistTable = QtGui.QTableWidget(self)
self.playlistTable.setFrameShape(QtGui.QFrame.NoFrame)
self.playlistTable.setFrameShadow(QtGui.QFrame.Sunken)
self.playlistTable.setLineWidth(0)
self.playlistTable.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.playlistTable.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
self.playlistTable.setHorizontalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
self.playlistTable.setShowGrid(True)
self.playlistTable.setGridStyle(QtCore.Qt.SolidLine)
self.playlistTable.setWordWrap(True)
self.playlistTable.setCornerButtonEnabled(True)
self.playlistTable.verticalHeader().setVisible(False)
class CustomTabWidget(QtGui.QTabWidget):
"""Tab Widget that that can have new tabs easily added to it."""
def __init__(self, parent=None):
super(CustomTabWidget, self).__init__(parent)
# QtGui.QTabWidget.__init__(self, parent)
# Tab Bar
self.tab = QtGui.QTabBar()
self.setTabBar(self.tab)
# Properties
self.setMovable(True)
self.setTabsClosable(True)
self.plusButton = QtGui.QPushButton("+")
self.plusButton.setFixedSize(QtCore.QSize(22, 22))
self.setCornerWidget(self.plusButton)
# Signals
self.connect(self.plusButton, QtCore.SIGNAL('clicked()'), self.addTab)
# self.tab.plusClicked.connect(self.addTab)
self.tab.tabMoved.connect(self.tab.moveTab)
self.tabCloseRequested.connect(self.removeTab)
def addTab(self):
string = QtCore.QString.fromUtf8("Playlist")
tab = PlaylistTable()
super(CustomTabWidget, self).addTab(tab, string)
class AppDemo(QtGui.QMainWindow):
def __init__(self):
super(AppDemo, self).__init__()
self.centralwidget = QtGui.QWidget(self)
self.horizontalLayout = QtGui.QHBoxLayout(self.centralwidget)
self.horizontalLayout.setContentsMargins(0, -1, 0, -1)
self.playlist_manager = CustomTabWidget(self.centralwidget)
self.horizontalLayout.addWidget(self.playlist_manager)
self.playlist_manager.addTab()
self.setCentralWidget(self.centralwidget)
self.show()
# end class AppDemo
def main():
import sys
app = QtGui.QApplication(sys.argv)
w = AppDemo()
w.setWindowTitle('AppDemo')
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I'm guessing the reason is this, from QTabWidget Documentation:
The geometry of the widget is determined
based on the widget's sizeHint() and the style().
If you print the sizeHint() of the QPushButton you will see that width never goes below a certain value.
I've found an alternative is to use a QToolButton which can do everything (even more) a QPushButton does.
self.plusButton = QtGui.QToolButton(self)
self.plusButton.setText("+")
I need to add a new tab, but I am having problems. I want to add new tabs in the main window, and keep the methods of the class Editor(). I can do this without having to create the class Editor() but need it to be so. Sorry for my English.
This is my code:
from PyQt4 import QtGui
from PyQt4 import QtCore
class Main(QtGui.QMainWindow):
def __init__(self):
super(Main, self).__init__()
self.initUi()
def initUi(self):
self.setWindowTitle("Editor")
self.resize(640, 480)
self.edit = Editor()
newAc = QtGui.QAction('New', self)
newAc.setShortcut('Ctrl+N')
newAc.triggered.connect(self.new_)
menu = self.menuBar()
filemenu = menu.addMenu('&File')
filemenu.addAction(newAc)
self.tab = QtGui.QTabWidget(self)
self.setCentralWidget(self.tab)
class Editor(QtGui.QTextEdit):
def __init__(self, parent=None):
super(Editor, self).__init__(parent)
def new_(self):
tab = QtGui.QTextEdit(self.tab)
self.tab.addTab(tab, 'Untitled')
def main():
import sys
app = QtGui.QApplication(sys.argv)
w = Main()
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
If you want to have the same text on both (or more) tabs you can use the same Editor class, but if not, you need instantiate an Editor object for each tab.
Also your code have some problems:
1- You are handling the tabs inside Editor objects. Instead, you must handle tabs at Main level.
2- The "default" tab you're adding when you create the Main object do not have any related QTextEdit change:
self.tab = QtGui.QTabWidget(self)
self.setCentralWidget(self.tab) # <---- tab without QTextEdit
add this:
self.tab = QtGui.QTabWidget(self)
self.editor = Editor(self.tab) # editor receives self.tab widget as parent.
self.setCentralWidget(self.tab)
also you will need define Editor class before Main.
3- Main object don't have any method called new_, Editor does. So the line:
newAc.triggered.connect(self.new_)
it's wrong.
So your code might look like:
from PyQt4 import QtGui
from PyQt4 import QtCore
class Editor(QtGui.QTextEdit):
def __init__(self, parent=None):
super(Editor, self).__init__(parent)
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.initUi()
def initUi(self):
self.setWindowTitle("Editor")
self.resize(640, 480)
newAc = QtGui.QAction('New', self)
newAc.setShortcut('Ctrl+N')
newAc.triggered.connect(self.new_)
menu = self.menuBar()
filemenu = menu.addMenu('&File')
filemenu.addAction(newAc)
self.tab = QtGui.QTabWidget(self)
self.setCentralWidget(self.tab)
self.tab.addTab(Editor(), "New Text")
def new_(self):
self.tab.addTab(Editor(), "New text")
def main():
import sys
app = QtGui.QApplication(sys.argv)
w = Main()
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()