PyQt5: Add button to each item of a QListWidget - python

In my app I'm displaying a list using the QListwidget (I can't use other widget for this task), but I was wondering if it is possible to add to each item of the list a button since I'd like to create a delete item button. Basically I'd need something like this:
So I'd need to know how to create this button for each item in the list and a way to know which button is pressed.
This is my code right now:
import sys
from PyQt5.QtWidgets import (
QApplication,
QHBoxLayout,
QVBoxLayout,
QGroupBox,
QListWidget,
QWidget,
)
class Window(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("List Item with button")
self.centralwidgetHorizontalLayout = QHBoxLayout(self)
self.Frame = QGroupBox()
self.FrameHorizontalLayout = QHBoxLayout(self.Frame)
self.ListWidget = QListWidget(self.Frame)
self.ListWidget.setSpacing(11)
self.ListWidget.setStyleSheet(
"QListWidget { background: palette(window); border: none;}"
"QListWidget::item {"
"border-style: solid;"
"border-width:1px;"
"border-color: black;"
"margin-right: 30px;"
"}"
"QListWidget::item:hover {"
"border-color: green;"
"}")
self.FrameHorizontalLayout.addWidget(self.ListWidget)
self.centralwidgetHorizontalLayout.addWidget(self.Frame)
for i in range(13):
self.ListWidget.addItem(f"Line {i+1}")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.show()
window.showMaximized()
sys.exit(app.exec_())
Is there a way to accomplish this?

Just change the for code and the rest remains the same.
This is an example, should be what you want.
Typesetting solves it yourself.
class Window(QWidget):
def __init__(self):
...
for i in range(13):
item = QListWidgetItem()
item_widget = QWidget()
line_text = QLabel("I love PyQt!")
line_push_button = QPushButton("Push Me")
line_push_button.setObjectName(str(i))
line_push_button.clicked.connect(self.clicked)
item_layout = QHBoxLayout()
item_layout.addWidget(line_text)
item_layout.addWidget(line_push_button)
item_widget.setLayout(item_layout)
item.setSizeHint(item_widget.sizeHint())
self.ListWidget.addItem(item)
self.ListWidget.setItemWidget(item, item_widget)
...
def clicked(self):
sender = self.sender()
push_button = self.findChild(QPushButton, sender.objectName())
print(f'click: {push_button.objectName()}')

Related

How to transfer items from QListWidget based in one window to another QListWidget based in another window?

That being said, I would like to be able to transfer my items from QListWidget based in one window to another.
The code below allows me to transfer one item at a time,
but I am struggling to think of a way to transfer multiple items
at one time.
(Code below is from an example I found and have altered.)
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QDialog,
QWidget,
QVBoxLayout,
QLineEdit,
QLabel,
QPushButton,
QListWidget,
QAbstractItemView
)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.user_input = QListWidget()
self.user_input.addItem("2")
self.user_input.addItem("3")
self.populate()
self.show()
self.user_input.setSelectionMode(QAbstractItemView.ExtendedSelection)
def populate(self):
widgets = [QLabel("Insert a number"), self.user_input]
centralWidget = self.group_widgets(widgets)
self.setCentralWidget(centralWidget)
def group_widgets(self, widgets):
parentWidget = QWidget()
layout = QVBoxLayout()
for widget in widgets: layout.addWidget(widget)
parentWidget.setLayout(layout)
return parentWidget
def when_input(self, function):
#self.user_input.textChanged.connect(function)
self.user_input.itemClicked.connect(self.printItemText)
self.user_input.itemClicked.connect(function)
def printItemText(self):
items = self.user_input.selectedItems()
x = []
for i in range(len(items)):
x.append(str(self.user_input.selectedItems()[i].text()))
print (x)
class Dialog(QDialog):
def __init__(self):
super().__init__()
self.user_input = QListWidget()
self.relay_sum = None # function to relay result of addition
self.populate()
self.show()
def populate(self):
widgets = self.get_widgets()
layout = self.get_layout(widgets)
self.setLayout(layout)
def get_widgets(self):
widgets = [
QLabel("Inserted number"),
self.user_input,
]
return widgets
def get_layout(self, widgets):
layout = QVBoxLayout()
for widget in widgets: layout.addWidget(widget)
return layout
def main():
app = QApplication([])
mainWindow = MainWindow()
dialog = Dialog()
mainWindow.when_input(lambda text: dialog.user_input.addItem(str(text.text())))
app.exec_()
if __name__ == "__main__":
main()
When an item is clicked it is also selected so just use the last feature.
One possible solution is to create a new token that transports the cloned items from the selected items, and then add it to the other QListWidget.
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import (
QAbstractItemView,
QApplication,
QDialog,
QLabel,
QListWidget,
QMainWindow,
QVBoxLayout,
QWidget,
)
class MainWindow(QMainWindow):
custom_signal = pyqtSignal(list)
def __init__(self):
super().__init__()
self.user_input = QListWidget(selectionMode=QAbstractItemView.ExtendedSelection)
self.user_input.addItem("2")
self.user_input.addItem("3")
self.populate()
self.user_input.itemSelectionChanged.connect(self.handle_selection_changed)
def populate(self):
widgets = [QLabel("Insert a number"), self.user_input]
centralWidget = self.group_widgets(widgets)
self.setCentralWidget(centralWidget)
def group_widgets(self, widgets):
parentWidget = QWidget()
layout = QVBoxLayout(parentWidget)
for widget in widgets:
layout.addWidget(widget)
return parentWidget
def handle_selection_changed(self):
items = []
for item in self.user_input.selectedItems():
items.append(item.clone())
self.custom_signal.emit(items)
class Dialog(QDialog):
def __init__(self):
super().__init__()
self.user_input = QListWidget()
self.populate()
def populate(self):
widgets = self.get_widgets()
layout = self.get_layout(widgets)
self.setLayout(layout)
def get_widgets(self):
widgets = [
QLabel("Inserted number"),
self.user_input,
]
return widgets
def get_layout(self, widgets):
layout = QVBoxLayout()
for widget in widgets:
layout.addWidget(widget)
return layout
def add_items(self, items):
for item in items:
self.user_input.addItem(item)
def main():
app = QApplication([])
mainWindow = MainWindow()
mainWindow.show()
dialog = Dialog()
dialog.show()
mainWindow.custom_signal.connect(dialog.add_items)
app.exec_()
if __name__ == "__main__":
main()

Aligning popup widget in PyQt5

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_())

Get color from variable number of buttons PyQt

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()

PyQt changing specific button

I've been looking through answers for a while but couldn't find a solution to my problem. I recently started playing around with PyQt and trying to code out couple of ideas, one of which was to have a couple of buttons with some text on them and when a button is clicked, its text would change color. Here is my code:
import sys
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QLabel, QMainWindow,
QHBoxLayout, QGroupBox, QDialog, QVBoxLayout, QGridLayout, QMessageBox)
from PyQt5.QtCore import pyqtSlot, Qt
class App(QDialog):
def __init__(self):
super().__init__()
self.title = "Battleship"
self.left = 50
self.top = 50
self.width = 750
self.height = 500
self.initUI()
def initUI(self):
self.setGeometry(self.left, self.top, self.width, self.height)
self.setWindowTitle(self.title)
self.setFixedSize(self.width, self.height)
stylesheet = """QPushButton {background: #EEE; color: #11}
!QPushButton {background: #555; color: #EEE}"""
self.setStyleSheet(stylesheet)
self.grid()
layout = QVBoxLayout()
layout.addWidget(self.box)
self.setLayout(layout)
self.show()
def grid(self):
self.box = QGroupBox()
self.layout = QGridLayout()
self.box.setLayout(self.layout)
self.button1 = QPushButton('X', self)
self.button2 = QPushButton('X', self)
self.button3 = QPushButton('X', self)
self.button1.clicked.connect(self.pick)
self.button2.clicked.connect(self.pick)
self.button3.clicked.connect(self.pick)
self.layout.addWidget(self.button1)
self.layout.addWidget(self.button2)
self.layout.addWidget(self.button3)
def pick(self):
# turn 'X' from black to red ONLY on the clicked button
# while leaving the others untouched
pass
#---------------------------------------------------------------
if __name__ == '__main__':
app = QApplication(sys.argv)
gui = App()
sys.exit(app.exec_())
What does the 'pick' function have to look like in order to do this? To clarify: I don't want to make three individual functions, one for each button (as it would be very impractical for a larger number of buttons), but a single function which would work with any button.
the sender() method returns us the object that emits the signal, in your case the button will be pressed:
def pick(self):
# turn 'X' from black to red ONLY on the clicked button
# while leaving the others untouched
btn = self.sender()
btn.setStyleSheet("color: red")

Creating a pop-up window from custom pushbutton python

I am trying to create a pop-up window that gets popped-up from pressing on a QPushButton. However, I have a separate QPushButton class that I would like to use. I can't seem to get it working. Anything I am doing wrong?
#import ... statements
import sys
# from ... import ... statements
from PyQt5.QtWidgets import (QMainWindow, QApplication, QPushButton, QGridLayout, QWidget, QHBoxLayout, QLabel,
QVBoxLayout)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont, QFontDatabase, QColor, QPalette, QMovie
from skimage import transform, io
# Create main window of the widget
class MainWindow(QWidget):
def __init__(self):
super().__init__()
#Set a title inside the widget
self.titleLabel = QLabel()
titleText = "some title text"
self.titleLabel.setText(titleText)
# Give the label some flair
self.titleLabel.setFixedWidth(1000)
self.titleLabel.setAlignment(Qt.AlignCenter)
QFontDatabase.addApplicationFont(link_to_custom_font)
font = QFont()
font.setFamily("custom_font_name")
font.setPixelSize(50)
self.titleLabel.setFont(font)
# Set first button - The "Yes" Button
self.btn1 = myButtonOne("Yes")
#Initialize GUI
self.layoutGUI()
self.initUI()
def initUI(self):
self.fromleft = 200
self.fromtop = 100
self.w = 1000
self.h = 500
self.setGeometry(self.fromleft, self.fromtop, self.w, self.h)
def layoutGUI(self):
hbox = QHBoxLayout()
hbox.setSpacing(20)
hbox.addWidget(self.btn1)
vbox = QVBoxLayout()
vbox.addWidget(self.titleLabel)
vbox.addLayout(hbox)
self.setLayout(vbox)
class myButtonOne(QPushButton):
def __init__(self, parent=None):
super(myButtonOne, self).__init__(parent)
# Set maximum border size
imSize = io.imread(imagePath)
imHeight = imSize.shape[1]
imWidth = imSize.shape[0]
# Set first button - The "Yes" Button
yesImage = someImagePath
self.setStyleSheet("background-image: url(" + yesImage + ");"
"background-repeat: none;"
"background-position: center;"
"border: none")
self.setFixedSize(imWidth, imHeight)
self.clicked.connect(self.buttonOnePushed)
def buttonOnePushed(self):
textView().show()
def enterEvent(self, event):
newImage = someOtherImagePath
self.setStyleSheet("background-image: url("+newImage+");"
"background-repeat: none;"
"background-position: center;"
"border: none")
def leaveEvent(self, event):
newImage = someImagePath
self.setStyleSheet("background-image: url("+newImage+");"
"background-repeat: none;"
"background-position: center;"
"border: none")
class textView(QWidget):
def __init(self):
textView.__init__()
theText = QLabel()
#define sizes
self.height = 550
self.width = 250
# Open QWidget
self.initUI()
# Set the text for the QLabel
someText = "Some Text for the label"
theText.setText(someText)
def initUI(self):
self.show()
# Start GUI
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
So, I am trying to keep the QPushButton classes separate, so that I can customize them. I would like to keep it like that, especially to keep it clean and readable.
First off - please read: How to create a Minimal, Complete, and Verifiable example. I had a lot of work minimizing your code, which wasted a good amount of my time.
Nonetheless, here is a minimal working code, with your own button class:
import sys
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QLabel, QVBoxLayout
class MainWindow(QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.titleLabel = QLabel( "some label text" )
self.btn1 = myButtonOne( "button text" )
hbox = QVBoxLayout() # one vertical box seemed enough
hbox.addWidget( self.titleLabel )
hbox.addWidget( self.btn1 )
self.setLayout( hbox )
class myButtonOne(QPushButton):
def __init__(self, text, parent=None):
super(myButtonOne, self).__init__(text, parent)
self.clicked.connect(self.buttonOnePushed)
# add your customizations here
def buttonOnePushed (self) :
self.t = textView()
self.t.show()
class textView(QWidget):
def __init__(self):
super(textView, self).__init__()
self.theText = QLabel('test', self )
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
What have you done wrong in your code?
using textView().show() creates a local version of you textView-class and show()'s it:
def buttonOnePushed(self):
textView().show()
But, show() means that the code continues, where the end of your code comes, which results in cleaning up the locals. End - it's just shown for a microsecond.
def buttonOnePushed (self) :
self.t = textView()
self.t.show()
The code above stores the var as instance-attribute of the button, which is not cleaned up.
Furthermore you misspelled the init in your textView-class:
"__init" should be __init__ - else it is not called when using the constructor:
class textView(QWidget):
def __init(self):
textView.__init__()
Finally, you wanted to called show() twice:
in your textView-init you call initUI() which is calling show()
you calling show manually with textView().show()
Hope this helps! I did not include your personal style adjustments for readability.

Categories

Resources