I'm trying to make a GUI for my program and faced a problem trying to access text from a clicked QPushButton.
The problem is, when I use for-loop to iterate through a list of buttons button.clicked.connect(function) processes every click as a click on the last item (at least, I think so).
Here's sample code
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QPushButton, QHBoxLayout
import sys
def main():
app = QApplication(sys.argv)
# main window
win = QMainWindow()
win.setGeometry(100, 100, 460, 80)
win.setWindowTitle("My great app")
# layout
box = QWidget(win)
box.setGeometry(0, 0, 460, 80)
layout = QHBoxLayout(win)
box.setLayout(layout)
btns = []
# creating 10 buttons
for i in range(10):
btn = QPushButton(str(i+1), box)
btns.append(btn)
layout.addWidget(btns[i], i)
# accessing buttons text
for btn in btns:
btn.clicked.connect(lambda: print(btn.text()))
win.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
If you run the program and click on any button it will print "10" every time.
You should read here https://doc.qt.io/qt-5/qmainwindow.html#details and here https://doc.qt.io/qt-5/qabstractbutton.html#clicked
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, \
QPushButton, QHBoxLayout
def main():
app = QApplication(sys.argv)
# main window
win = QMainWindow()
win.setGeometry(100, 100, 460, 80)
win.setWindowTitle("My great app")
# layout
box = QWidget() #(win)
box.setGeometry(0, 0, 460, 80)
layout = QHBoxLayout(box) #(win)
# box.setLayout(layout) # ---
win.setCentralWidget(box) # +++
btns = []
# creating 10 buttons
for i in range(10):
btn = QPushButton(str(i+1)) #, box)
btns.append(btn)
layout.addWidget(btns[i], i)
# accessing buttons text
# btn.clicked.connect(lambda: print(btn.text())) # ---
btn.clicked.connect(lambda ch, text=btn.text(): print(text)) # +++
win.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Related
it's a simple application. Just one label and three buttons. But the layout is making me trouble.
I want to have the childlayout for the label and the childlayout for the buttons to have the same height. But I can't get it to work. Neither with BoxLayouts nor with GridLayouts. I tried to addStretch, so both layouts have the same stretchfactor, and various different stuff. I'm sure the answer is easy but I can't figure it out.
I also tried it in QtDesigner, so I could inspect and compare the codeparts, but:
I designed this
and got this when running the code, again the buttons stick to the bottom and dont have the same height as the label
However, here's my code:
(I know that the Grid Layout is not necessary here, it was just some sort of trying to solve the problem. Anyways I don't think that the Grid Layout is the problem, as I had the same problems with BoxLayouts or just the label without a layout.)
import sys
from PyQt5 import QtCore
from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout, QLabel, QVBoxLayout, QPushButton, QGroupBox, \
QGridLayout, QMainWindow, QSizePolicy
class Window(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(100, 50, 400, 300)
self.setWindowTitle("Fretboard Note Quiz")
self.layout()
self.show()
def layout(self):
glay = QGridLayout()
self.setLayout(glay)
hbox1 = QHBoxLayout() #upper layout for label
hbox2 = QHBoxLayout() #bottom layout for buttons
btn1 = QPushButton("1")
btn2 = QPushButton("2")
btn3 = QPushButton("3")
#adding the buttons to the bottom layout
hbox2.addWidget(btn1)
hbox2.addWidget(btn2)
hbox2.addWidget(btn3)
label = QLabel("Text")
hbox1.addWidget(label)
glay.addLayout(hbox1, 0, 1)
glay.addLayout(hbox2, 1, 1)
glay.setRowStretch(0, 1)
glay.setRowStretch(1, 1)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Window()
sys.exit(app.exec_())
In this case, the simplest thing is to use a QWidget as a container since by default they are stretched in the same way as the QLabel, so they will try to occupy the same space.
The problem with your initial code is that the stretch factors do not apply to layouts but to widgets.
Finally layout() is a method so do not hide it using it for other things, the names of the functions should describe the action of the method.
import sys
from PyQt5.QtWidgets import (
QApplication,
QHBoxLayout,
QLabel,
QPushButton,
QVBoxLayout,
QWidget,
)
class Window(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(100, 50, 400, 300)
self.setWindowTitle("Fretboard Note Quiz")
self.build_layout()
def build_layout(self):
btn1 = QPushButton("1")
btn2 = QPushButton("2")
btn3 = QPushButton("3")
label = QLabel("Text")
button_container = QWidget()
hlay = QHBoxLayout(button_container)
hlay.addWidget(btn1)
hlay.addWidget(btn2)
hlay.addWidget(btn3)
vlay = QVBoxLayout(self)
vlay.addWidget(label)
vlay.addWidget(button_container)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())
Maybe you could set the minimunHeight of your buttons like:
ui.pushButton.setMinimumHeight(100)
I have a few qdockwidgets in one qmainwindow. When I undock a window and I switch to another program the undocked widget disappears. How can I make the undocked QDockWidget visible or stay visible when I switch programs?
So, I've found out that the following line
super(MainProgram, self).__init__(parent,Qt.WindowStaysOnTopHint)
Makes sure that the whole program stays on top. However the undocked widget still disappears. Even when I put the following line in the qdockwidget code:
self.Risk_Monitor.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
Surely it's possible to have floating widgets stay on top as well right?
The following doesn't seem to work as well:
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class Dockdemo(QMainWindow):
def __init__(self, parent=None):
super(Dockdemo, self).__init__(parent,Qt.WindowStaysOnTopHint)
self.setWindowTitle("Dock demo")
self.setCentralWidget(QTextEdit())
items = QDockWidget("Stay on Top!", self, flags=Qt.Window)
#items = QDockWidget("Dockable", self, flags=Qt.WindowStaysOnTopHint) # flags=Qt.Window
# items.setGeometry(650, 130, 300, 200)
items.show() # +++
listWidget = QListWidget()
listWidget.addItem("item1")
listWidget.addItem("item2")
listWidget.addItem("item3")
items.setWidget(listWidget)
items.setFloating(False)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Dockdemo()
ex.show()
sys.exit(app.exec_())
*Keep widget on top in Qt
Solved:
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class Dockdemo(QMainWindow):
def __init__(self, parent=None):
super(Dockdemo, self).__init__(parent,Qt.WindowStaysOnTopHint)
self.setWindowTitle("Dock demo")
self.setCentralWidget(QTextEdit())
items = QDockWidget("Dockable", self) # flags=Qt.Window
#items.setWindowFlags(Qt.WindowStaysOnTopHint)
# items.setGeometry(650, 130, 300, 200)
items.setWindowFlags(
Qt.Window |
Qt.CustomizeWindowHint |
Qt.WindowTitleHint |
Qt.WindowCloseButtonHint |
Qt.WindowStaysOnTopHint
)
items.show()
# items.raise_()
listWidget = QListWidget()
listWidget.addItem("item1")
listWidget.addItem("item2")
listWidget.addItem("item3")
items.setWidget(listWidget)
items.setFloating(False)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Dockdemo()
ex.show()
sys.exit(app.exec_())
I've seen a number of replies on SO regarding this matter but not specifically to QMenu and QToolButton. Would appreciate some pointers on aligning the popup widget to the right side of the button. Here's a basic code I'm working off..
import sys
from PyQt5.QtWidgets import *
class test(QWidget):
def __init__(self):
super().__init__()
self.resize(200, 100)
layout = QHBoxLayout(self)
label = QLabel('Testing QToolButton Popup')
toolbutton = QToolButton()
toolbutton.setPopupMode(QToolButton.InstantPopup)
widget = QWidget()
widgetLayout = QHBoxLayout(widget)
widgetLabel = QLabel('Popup Text')
widgetSpinbox = QSpinBox()
widgetLayout.addWidget(widgetLabel)
widgetLayout.addWidget(widgetSpinbox)
widgetAction = QWidgetAction(toolbutton)
widgetAction.setDefaultWidget(widget)
widgetMenu = QMenu(toolbutton)
widgetMenu.addAction(widgetAction)
toolbutton.setMenu(widgetMenu)
layout.addWidget(label)
layout.addWidget(toolbutton)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = test()
win.show()
sys.exit(app.exec_())
The outcome looks like this:
The Qt developer thought the default position was correct, so if you want to modify the alignment you must move the QMenu as I show below:
import sys
from PyQt5.QtCore import QPoint
from PyQt5.QtWidgets import (
QApplication,
QHBoxLayout,
QLabel,
QMenu,
QSpinBox,
QToolButton,
QWidgetAction,
QWidget,
)
class Menu(QMenu):
def showEvent(self, event):
if self.isVisible():
button = self.parentWidget()
if button is not None:
pos = button.mapToGlobal(button.rect().bottomRight())
self.move(pos - self.rect().topRight())
super().showEvent(event)
class Test(QWidget):
def __init__(self):
super().__init__()
self.resize(200, 100)
layout = QHBoxLayout(self)
label = QLabel("Testing QToolButton Popup")
toolbutton = QToolButton(popupMode=QToolButton.InstantPopup)
widgetLabel = QLabel("Popup Text")
widgetSpinbox = QSpinBox()
widget = QWidget()
widgetLayout = QHBoxLayout(widget)
widgetLayout.addWidget(widgetLabel)
widgetLayout.addWidget(widgetSpinbox)
widgetAction = QWidgetAction(toolbutton)
widgetAction.setDefaultWidget(widget)
widgetMenu = Menu(toolbutton)
widgetMenu.addAction(widgetAction)
toolbutton.setMenu(widgetMenu)
layout.addWidget(label)
layout.addWidget(toolbutton)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Test()
win.show()
sys.exit(app.exec_())
I'm trying to get the player's names and the color that they choose for a next step in the project. Getting the names is easy enough but the colors is a bit of a pain.
I don't know how to get the selected color and apply it the button using the QColorDialog. The ultimate goal is to get a structure with player name linked to the color they chose.
Here is what i have:
from PyQt5.QtWidgets import (
QLineEdit,
QWidget,
QApplication,
QLabel,
QMainWindow,
QGridLayout,
QColorDialog,
QPushButton,
QVBoxLayout,
QHBoxLayout,
)
import sys
class NamesPlayers(QMainWindow):
""" name screen, ask the names of the players """
def __init__(self, nb_players):
super().__init__()
self.layout_widget = QWidget()
self.player_names = []
self.player_colors = []
main_layout = QVBoxLayout()
names_layout = QGridLayout()
button_layout = QHBoxLayout()
button_list = []
for i in range(nb_players):
label = QLabel("Name :")
player_name = QLineEdit()
color_button = QPushButton("Color")
color_button.setStyleSheet("background-color: white")
names_layout.addWidget(label, i, 0)
names_layout.addWidget(player_name, i, 1)
names_layout.addWidget(color_button, i, 2)
button_list.append(color_button)
self.player_names.append(player_name)
self.player_colors.append(color_button.styleSheet())
self.confirm_button = QPushButton("Confirm")
button_layout.addWidget(self.confirm_button)
main_layout.addLayout(names_layout)
main_layout.addLayout(button_layout)
for button in button_list:
button.clicked.connect(self.open_colordialog)
self.layout_widget.setLayout(main_layout)
self.setCentralWidget(self.layout_widget)
def open_colordialog(self):
color_dialog = QColorDialog()
color_dialog.exec_()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = NamesPlayers(4)
window.show()
app.exec_()
My main issue i guess is the fact that the number of buttons for the color can vary and when I click on those buttons, the address for the QColorDialog is always the same which is not what I want.
Any help is appreciated
One possible solution is to use the sender() method to get the button pressed:
import sys
from PyQt5 import QtCore, QtWidgets
class NamesPlayers(QtWidgets.QMainWindow):
""" name screen, ask the names of the players """
def __init__(self, nb_players, parent=None):
super().__init__(parent)
names_layout = QtWidgets.QGridLayout()
for i in range(nb_players):
label = QtWidgets.QLabel("Name :")
player_name = QtWidgets.QLineEdit()
color_button = QtWidgets.QPushButton("Color")
color_button.setStyleSheet("background-color: white")
color_button.clicked.connect(self.open_colordialog)
names_layout.addWidget(label, i, 0)
names_layout.addWidget(player_name, i, 1)
names_layout.addWidget(color_button, i, 2)
self.confirm_button = QtWidgets.QPushButton("Confirm")
central_widget = QtWidgets.QWidget()
main_layout = QtWidgets.QVBoxLayout(central_widget)
button_layout = QtWidgets.QHBoxLayout()
button_layout.addWidget(self.confirm_button)
main_layout.addLayout(names_layout)
main_layout.addLayout(button_layout)
self.setCentralWidget(central_widget)
#QtCore.pyqtSlot()
def open_colordialog(self):
button = self.sender()
color_dialog = QtWidgets.QColorDialog()
if color_dialog.exec_() == QtWidgets.QColorDialog.Accepted:
button.setStyleSheet(
"background-color: {}".format(color_dialog.selectedColor().name())
)
button.clearFocus()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = NamesPlayers(4)
window.show()
sys.exit(app.exec_())
Another possible solution is to send the button pressed (with the methods indicated in this answer):
from functools import partial
# ...
for i in range(nb_players):
# ...
color_button = QtWidgets.QPushButton("Color")
color_button.setStyleSheet("background-color: white")
color_button.clicked.connect(partial(self.open_colordialog, color_button))
# or
# color_button.clicked.connect(lambda *args, btn=color_button: self.open_colordialog(btn))
def open_colordialog(self, button):
color_dialog = QtWidgets.QColorDialog()
if color_dialog.exec_() == QtWidgets.QColorDialog.Accepted:
button.setStyleSheet(
"background-color: {}".format(color_dialog.selectedColor().name())
)
button.clearFocus()
I'm trying to put a QProgressBar below a QPushButton and align them on the center of a QVBoxLayout, but for some reason the button stays left aligned when the progress bar is present and center aligned if it is not.
I tried setting the alignment of all parent widgets and layouts to Qt.AlignCenter, but the progress bar keeps making the button go to the left.
connect_box = QVBoxLayout()
connect_box.setAlignment(Qt.AlignCenter)
connect_button = QPushButton('Connect')
connect_button.setFixedSize(120, 30)
connect_progress = QProgressBar()
connect_progress.setRange(0, 10000)
connect_progress.setValue(0)
connect_box.addWidget(connect_button)
connect_box.addWidget(connect_progress)
connect_box.setContentsMargins(0, 20, 0, 0)
I expect the button to stay center aligned when the progress bar is added.
Try it:
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class MyWidget(QWidget):
def __init__(self):
super().__init__()
connect_button = QPushButton('Connect')
connect_button.setFixedSize(120, 30)
connect_progress = QProgressBar()
connect_progress.setRange(0, 10000)
connect_progress.setValue(0)
connect_box = QVBoxLayout(self)
connect_box.setAlignment(Qt.AlignCenter)
connect_box.addWidget(connect_button, alignment=Qt.AlignCenter) # < ----
connect_box.addWidget(connect_progress)
connect_box.setContentsMargins(0, 20, 0, 0)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())