I want to make GUI like below with PyQt5, but I can't find an example to help me.
I searched for "change layout on qwidget" and "tab pane with no title bar" and "card layout" without luck. How can I make this with PyQt5?
You have to use a QStackedLayout (or a QStackedWidget) that changes pages when the buttons are pressed. And the first page should have the buttons. I have also implemented the back() method that returns to the initial page, that slot must be invoked when the Change button is pressed:
from functools import partial
from PyQt5 import QtCore, QtWidgets
class CardWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(CardWidget, self).__init__(parent)
self._layout = QtWidgets.QStackedLayout(self)
button_widget = QtWidgets.QWidget()
self.btn_lay = QtWidgets.QFormLayout(button_widget)
self._layout.addWidget(button_widget)
def add_widget(self, text, widget):
self._layout.addWidget(widget)
btn = QtWidgets.QPushButton(text)
self.btn_lay.addRow(btn)
btn.clicked.connect(partial(self._layout.setCurrentWidget, widget))
#QtCore.pyqtSlot()
def back(self):
self._layout.setCurrentIndex(0)
class Widget(QtWidgets.QWidget):
backSignal = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.le1 = QtWidgets.QLineEdit()
self.le2 = QtWidgets.QLineEdit()
button = QtWidgets.QPushButton("Change")
button.clicked.connect(self.backSignal)
flay = QtWidgets.QFormLayout()
flay.addRow("Value 1:", self.le1)
flay.addRow("Value 2:", self.le2)
lay = QtWidgets.QVBoxLayout(self)
lay.addLayout(flay)
lay.addWidget(button)
def create_label():
label = QtWidgets.QLabel(
"Some Other Components",
alignment=QtCore.Qt.AlignCenter
)
label.setStyleSheet("background-color:blue;")
return label
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
c = CardWidget()
for i in range(3):
w = Widget()
w.backSignal.connect(c.back)
c.add_widget("Want to Change value {}".format(i+1), w)
p = QtWidgets.QWidget()
lay = QtWidgets.QGridLayout(p)
lay.addWidget(create_label(), 0, 0, 1, 2)
lay.addWidget(c, 1, 0)
lay.addWidget(create_label(), 1, 1)
lay.setColumnStretch(0, 1)
lay.setColumnStretch(1, 1)
lay.setRowStretch(0, 1)
lay.setRowStretch(1, 1)
p.resize(640, 480)
p.show()
sys.exit(app.exec_())
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
There is this QLineEdit with setText is set to a predefined value and there is a QDialog with QLabel in it which is supposed to show whatever is in the QLineEdit. The code below shows the situation.
import sys
import os
import datetime
from PySide2.QtWidgets import *
from PySide2 import *
now = datetime.datetime.now()
now_str = now.strftime("%H.%M.%S,%d/%m/%y")
default_text = (str("Sugar_" + now_str))
class MainWindow(QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.resize(600, 500)
btn = QPushButton("show dialog")
mw_layout = QVBoxLayout()
mw_layout.addWidget(btn)
self.setLayout(mw_layout)
btn.clicked.connect(show_d)
class dialog(QDialog):
def __init__(self):
super(dialog, self).__init__()
self.resize(400, 350)
title = QLineEdit()
title.setText(default_text)
show_title = QPushButton("Show title")
cancel = QPushButton("Cancel")
d_layout = QVBoxLayout()
d_layout.addWidget(title)
d_layout.addWidget(show_title)
d_layout.addWidget(cancel)
self.setLayout(d_layout)
t = title.text()
title_dialog = QDialog()
label = QLabel()
label.setText("The title is " + title.text())
ok = QPushButton("OK!")
t_layout = QVBoxLayout()
t_layout.addWidget(label)
t_layout.addWidget(ok)
title_dialog.setLayout(t_layout)
def show_t():
title_dialog.exec_()
title_dialog.setModal(True)
def close_t():
title_dialog.accept()
show_title.clicked.connect(show_t)
ok.clicked.connect(close_t)
cancel.clicked.connect(self.close_d)
def close_d(self):
self.reject()
def show_d():
d = dialog()
d.exec_()
d.setModal(True)
if __name__ == '__main__':
app = QApplication(sys.argv)
MainWindow = MainWindow()
MainWindow.show()
sys.exit(app.exec_())
But this doesn't work like I expected it to. The QLabel text just show the default text even when the text in QLineEdit is changed.
The console also shows the following error;
qt.xkb.compose: failed to create compose table.
I think there is something's obviously wrong but I can't seem to find what.
Any help appreciated.
You must update the text before a certain event, for example an instant before displaying the dialog:
class dialog(QDialog):
def __init__(self):
super(dialog, self).__init__()
self.resize(400, 350)
self.title_lineedit = QLineEdit(default_text)
show_title = QPushButton("Show title")
cancel = QPushButton("Cancel")
d_layout = QVBoxLayout(self)
d_layout.addWidget(self.title_lineedit)
d_layout.addWidget(show_title)
d_layout.addWidget(cancel)
self.title_dialog = QDialog()
self._title_label = QLabel()
ok = QPushButton("OK!")
t_layout = QVBoxLayout(self.title_dialog)
t_layout.addWidget(self._title_label)
t_layout.addWidget(ok)
show_title.clicked.connect(self.on_clicked)
ok.clicked.connect(self.title_dialog.reject)
cancel.clicked.connect(self.reject)
self.update_label()
def update_label(self):
self._title_label.setText("The title is " + self.title_lineedit.text())
def on_clicked(self):
self.update_label()
self.title_dialog.exec_()
I'm creating a method to hide and show the close button of a tab. I found a way to hide it. However, I don't know how to do it in reverse.
This is my existing code for hiding the close button. Using the same lines of codes, how can I show the close button of the tab?
def disable_close_button(self):
self.ui.tab_widget.tabBar().setTabButton(self.current_index(), QTabBar.RightSide, None)
def enable_close_button(self):
pass
Thanks in advance!
You are not hiding the button, you are eliminating it. So in my solution I get the button and then I hide it or show it as needed.
import sys
from PyQt5 import QtCore, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
show_button = QtWidgets.QPushButton(
text="show",
clicked=self.enable_close_button
)
hide_button = QtWidgets.QPushButton(
text="hide",
clicked=self.disable_close_button
)
self.tab_widget = QtWidgets.QTabWidget(tabsClosable=True)
for i in range(4):
label = QtWidgets.QLabel(
text="label {}".format(i),
alignment=QtCore.Qt.AlignCenter
)
self.tab_widget.addTab(label , "tab-{}".format(i))
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(show_button)
lay.addWidget(hide_button)
lay.addWidget(self.tab_widget)
#QtCore.pyqtSlot()
def enable_close_button(self):
ix = self.tab_widget.currentIndex()
button = self.tab_widget.tabBar().tabButton(ix, QtWidgets.QTabBar.RightSide)
if button is not None:
button.show()
#QtCore.pyqtSlot()
def disable_close_button(self):
ix = self.tab_widget.currentIndex()
button = self.tab_widget.tabBar().tabButton(ix, QtWidgets.QTabBar.RightSide)
if button is not None:
button.hide()
if __name__ == '__main__':
app = QtWidgets.QApplication.instance()
if app is None:
app = QtWidgets.QApplication(sys.argv)
app.setStyle("fusion")
w = Widget()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
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 tried to make some simple local chatting app for practice. I make a button to send a message every time i push the button the message displayed but the scrollarea become more narrow and not expanded the scrollarea. Am I missing something or added something i shouldn't in my code? how can i fix this?
Here is my code:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Bubble(QLabel):
def __init__(self,text):
super(Bubble,self).__init__(text)
self.setContentsMargins(5,5,5,5)
def paintEvent(self, e):
p = QPainter(self)
p.setRenderHint(QPainter.Antialiasing,True)
p.drawRoundedRect(0,0,self.width()-1,self.height()-1,5,5)
super(Bubble,self).paintEvent(e)
class MyWidget(QWidget):
def __init__(self,text,left=True):
super(MyWidget,self).__init__()
hbox = QHBoxLayout()
label = Bubble(text)
if not left:
hbox.addSpacerItem(QSpacerItem(1,1,QSizePolicy.Expanding,QSizePolicy.Preferred))
hbox.addWidget(label)
if left:
hbox.addSpacerItem(QSpacerItem(1,1,QSizePolicy.Expanding,QSizePolicy.Preferred))
hbox.setContentsMargins(0,0,0,0)
self.setLayout(hbox)
self.setContentsMargins(0,0,0,0)
class Chatting(QWidget):
def __init__(self, parent=None):
super(Chatting, self).__init__(parent)
self.vbox = QVBoxLayout()
for _ in range(20):
self.vbox.addWidget(MyWidget("Left side"))
widget = QWidget()
widget.setLayout(self.vbox)
scroll = QScrollArea()
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll.setWidgetResizable(False)
scroll.setWidget(widget)
#Scroll Area Layer add
vLayout = QVBoxLayout(self)
vLayout.addWidget(scroll)
send = QPushButton('')
send.setIcon(QIcon('images/send.png'))
send.setStyleSheet("background-color:#d00001; width: 44px")
send.setIconSize(QSize(84,20))
send.clicked.connect(self.send_messages)
vLayout.addWidget(send)
self.setLayout(vLayout)
def send_messages(self):
self.vbox.addWidget(MyWidget('testing'))
The problem is simple you have to enable the widgetResizable property to True since that property allows the QScrollArea to calculate the size of the content.
scroll.setWidgetResizable(True)
On the other hand I have taken the time to improve your code and I show it below:
from PyQt4 import QtGui, QtCore
class Bubble(QtGui.QLabel):
def __init__(self, text):
super(Bubble,self).__init__(text)
self.setContentsMargins(5, 5, 5, 5)
def paintEvent(self, e):
p = QtGui.QPainter(self)
p.setRenderHint(QtGui.QPainter.Antialiasing, True)
p.drawRoundedRect(self.rect().adjusted(0, 0, -1, -1), 5, 5)
super(Bubble, self).paintEvent(e)
class MyWidget(QtGui.QWidget):
def __init__(self, text, left=True):
super(MyWidget,self).__init__()
lay = QtGui.QVBoxLayout(self)
lay.setContentsMargins(0, 0, 0, 0)
self.setContentsMargins(0, 0, 0, 0)
label = Bubble(text)
lay.addWidget(label, alignment= QtCore.Qt.AlignLeft if left else QtCore.Qt.AlignRight)
class Chatting(QtGui.QWidget):
def __init__(self, parent=None):
super(Chatting, self).__init__(parent)
widget = QtGui.QWidget()
self.vbox = QtGui.QVBoxLayout(widget)
self.vbox.addStretch()
self.scroll = QtGui.QScrollArea(widgetResizable=True)
self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.scroll.setWidget(widget)
#Scroll Area Layer add
send = QtGui.QPushButton(icon= QtGui.QIcon('images/send.png'))
send.setStyleSheet("background-color:#d00001; width: 44px")
send.setIconSize(QtCore.QSize(84,20))
send.clicked.connect(self.on_clicked)
vLayout = QtGui.QVBoxLayout(self)
vLayout.addWidget(self.scroll)
vLayout.addWidget(send)
def send_message(self, text, direction=True):
widget = MyWidget(text, direction)
self.vbox.insertWidget(self.vbox.count()-1, widget)
scroll_bar = self.scroll.verticalScrollBar()
# move to end
QtCore.QTimer.singleShot(10, lambda: scroll_bar.setValue(scroll_bar.maximum()))
#QtCore.pyqtSlot()
def on_clicked(self):
self.send_message("testing")
if __name__ == '__main__':
import sys
import random
app = QtGui.QApplication(sys.argv)
w = Chatting()
def test():
for _ in range(8):
w.send_message("testing", random.choice((True, False)))
QtCore.QTimer.singleShot(1000, test)
w.resize(240, 480)
w.show()
sys.exit(app.exec_())