How to set mutually exclusive QRadioButton in QTableWidget per row - pyqt

If I add radio button this way, all radio buttons are mutually exclusive
so any idea how to set per row?
...
for y in range(2):
for x in range(3):
checkbox = QRadioButton()
table.setCellWidget(y, x, checkbox)
...

The QButtonGroup class provides a container to organize groups of button widgets.
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class TableExample(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.table = QTableWidget()
self.table.setRowCount(3)
self.table.setColumnCount(3)
for y in range(3):
button_group = QButtonGroup(self) # <---
button_group.setExclusive(True) # <---
for x in range(3):
checkbox = QRadioButton()
button_group.addButton(checkbox) # <---
self.table.setCellWidget(y, x, checkbox)
layout = QVBoxLayout(self)
layout.addWidget(self.table)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = TableExample()
w.setWindowTitle('qradiobutton-in-qtablewidget')
w.setWindowIcon(QIcon('im.png'))
w.resize(400, 150)
w.show()
sys.exit(app.exec())

Related

How to select row on cursor move?

I want to imitate list walking with arrow keys, I mean to select row when cursor goes up or down with arrow keys, like it happens when I click right-mouse-button on cell in this code, but keyPressEvent does not fires when I use arrows keys.
import sys
from PySide6 import QtCore
from PySide6.QtGui import QKeyEvent
from PySide6.QtWidgets import QMainWindow, QApplication, QTableWidgetItem, QTableWidget, QVBoxLayout, QWidget
from loguru import logger
class TableWindow(QMainWindow):
def __init__(self, data, parent=None):
super(TableWindow, self).__init__(parent)
self.table_widget = QTableWidget()
self.table_widget.cellClicked.connect(self.clicked)
self.populate_cells(data)
layout = QVBoxLayout()
layout.addWidget(self.table_widget)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
def keyPressEvent(self, event) -> None:
logger.info(f"{event.key()=}")
def clicked(self, row, col):
logger.info(f"{row}, {col}")
self.table_widget.selectRow(row)
def populate_cells(self, data):
self.table_widget.setRowCount(len(data))
self.table_widget.setColumnCount(len(data[0]))
for row in range(len(data)):
for col in range(len(data[0])):
value = data[row][col]
self.table_widget.setItem(row, col, QTableWidgetItem(value))
if __name__ == "__main__":
data_sample = [
["customer1", "address1"],
["customer2", "address2"]
]
app = QApplication(sys.argv)
window = TableWindow(data_sample)
window.show()
sys.exit(app.exec())
I think the issue is that you are listening for keyPressEvents on the main window when you want to be listening for them on the QTableWidget.
What you can do is subclass QTableWidget and reimplement the keyPressEvent method.
For Example:
class TableWidget(QTableWidget):
def keyPressEvent(self, event):
logger.info(f"{event.key()=}")
super().keyPressEvent(event)
class TableWindow(QMainWindow):
def __init__(self, data, parent=None):
super(TableWindow, self).__init__(parent)
self.table_widget = TableWidget()
self.table_widget.cellClicked.connect(self.clicked)
self.populate_cells(data)
layout = QVBoxLayout()
layout.addWidget(self.table_widget)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
def clicked(self, row, col):
logger.info(f"{row}, {col}")
self.table_widget.selectRow(row)
def populate_cells(self, data):
self.table_widget.setRowCount(len(data))
self.table_widget.setColumnCount(len(data[0]))
for row in range(len(data)):
for col in range(len(data[0])):
value = data[row][col]
self.table_widget.setItem(row, col, QTableWidgetItem(value))
if __name__ == "__main__":
data_sample = [
["customer1", "address1"],
["customer2", "address2"]
]
app = QApplication(sys.argv)
window = TableWindow(data_sample)
window.show()
sys.exit(app.exec())

How to make a layout with only aboslute positioning in pyqt

I want to make a layout where the user is able to move the widget inside freely with no constraints of rows or column. So I'm using absolute positioning. I'm able to make it happen in a window with no layout but I don't know what to use as a layout , problem is in the commented line in the code.
from PyQt5.QtWidgets import QWidget, QLabel, QApplication,QVBoxLayout,QHBoxLayout
from PyQt5.QtCore import Qt, QMimeData
from PyQt5.QtGui import QDrag
class DragLabel(QLabel):
def mouseMoveEvent(self, e):
if e.buttons() == Qt.LeftButton:
drag = QDrag(self)
mime = QMimeData()
drag.setMimeData(mime)
drag.exec_(Qt.MoveAction)
class Window(QWidget):
def __init__(self):
super().__init__()
self.setAcceptDrops(True)
self.label = DragLabel('first', self)
self.label2 = DragLabel('second', self)
self.label3 = DragLabel('third', self)
self.label.move(50, 50)
self.label2.move(100, 100)
self.label3.move(150, 150)
# self.main_frame=QHBoxLayout()
# self.classic_frame=QVBoxLayout()
#what should I use here to make a frame working with absolute position only
# self.absolute_frame = ???
# self.absolute_frame.addWidget(self.label)
# self.absolute_frame.addWidget(self.label2)
# self.absolute_frame.addWidget(self.label3)
# self.main_frame.addLayout(self.classic_frame)
# self.main_frame.addLayout(self.absolute_frame)
# self.setLayout(self.main_frame)
def dragEnterEvent(self, e):
widget = e.source()
if isinstance(widget,DragLabel):
e.accept()
def dropEvent(self, e):
pos = e.pos()
widget_dep = e.source()
if isinstance(widget_dep,DragLabel):
widget_dep_name = widget_dep.text()
print(widget_dep_name)
widget_dep.move(pos.x(),pos.y())
if __name__ == '__main__':
app = QApplication([])
w = Window()
w.show()
app.exec_()

How to add scrollable regions with Drag & drop widgets with PyQt?

Please add a scrollable region to this code so that when I run the code, I can scroll down through the window and see the last items in the window and replace them.
As this is a drag and drop code I could not add scroll to it.
Moreover, during the drag and drop process, the scroll should work and does not interrupt the drag and drop process.
from PyQt5.QtWidgets import QApplication, QScrollArea, QHBoxLayout, QWidget, QLabel, QMainWindow, QVBoxLayout
from PyQt5.QtCore import Qt, QMimeData, pyqtSignal
from PyQt5.QtGui import QDrag, QPixmap
class DragItem(QLabel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setContentsMargins(25, 5, 25, 5)
self.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.setStyleSheet("border: 1px solid black;")
# Store data separately from display label, but use label for default.
self.data = self.text()
def set_data(self, data):
self.data = data
def mouseMoveEvent(self, e):
if e.buttons() == Qt.LeftButton:
drag = QDrag(self)
mime = QMimeData()
drag.setMimeData(mime)
pixmap = QPixmap(self.size())
self.render(pixmap)
drag.setPixmap(pixmap)
drag.exec_(Qt.MoveAction)
class DragWidget(QWidget):
"""
Generic list sorting handler.
"""
orderChanged = pyqtSignal(list)
def __init__(self, *args, orientation=Qt.Orientation.Horizontal, **kwargs):
super().__init__()
self.setAcceptDrops(True)
# Store the orientation for drag checks later.
self.orientation = orientation
if self.orientation == Qt.Orientation.Vertical:
self.blayout = QVBoxLayout()
else:
self.blayout = QHBoxLayout()
self.setLayout(self.blayout)
def dragEnterEvent(self, e):
e.accept()
def dropEvent(self, e):
pos = e.pos()
widget = e.source()
for n in range(self.blayout.count()):
# Get the widget at each index in turn.
w = self.blayout.itemAt(n).widget()
if self.orientation == Qt.Orientation.Vertical:
# Drag drop vertically.
drop_here = pos.y() < w.y() + w.size().height() // 2
else:
# Drag drop horizontally.
drop_here = pos.x() < w.x() + w.size().width() // 2
if drop_here:
# We didn't drag past this widget.
# insert to the left of it.
self.blayout.insertWidget(n-1, widget)
self.orderChanged.emit(self.get_item_data())
break
e.accept()
def add_item(self, item):
self.blayout.addWidget(item)
def get_item_data(self):
data = []
for n in range(self.blayout.count()):
# Get the widget at each index in turn.
w = self.blayout.itemAt(n).widget()
data.append(w.data)
return data
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.drag = DragWidget(orientation=Qt.Orientation.Vertical)
self.scroll = QScrollArea() ###########
self.blayout = QHBoxLayout()
self.widget = QWidget()
for n, l in enumerate(['Art', 'Boo', 'EOA', 'Hel', \
'Hyg', 'Lei', 'Lei','Lei','Lei','Lei','Lei','Lei','Lei','Lei','Lei','Lei','Lei', 'Med',\
'Nut','Nut','Nut','Rel','Rel','Rel','Sle','SLN','Spo','Spo','Spo','Spo','Spo','Thi','Thi',\
'Wor','Wor','Wor','Wor','Wor','Wor','Wor','Wor','Wor','Wor','Wor','Wor','Wor','Wor']):
item = DragItem(l)
item.set_data(n) # Store the data.
self.drag.add_item(item)
def myFun():
print('hi', self.drag.get_item_data())
# Print out the changed order.
# self.drag.orderChanged.connect(print)
# add by me
self.drag.orderChanged.connect(myFun)
#Scroll Area Properties
self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.scroll.setWidgetResizable(True)
self.scroll.setWidget(self.widget)
container = QWidget()
layout = QVBoxLayout()
layout.addStretch(1)
layout.addWidget(self.drag)
layout.addStretch(1)
container.setLayout(layout)
self.setCentralWidget(container)
# self.setCentralWidget(self.scroll)
app = QApplication([])
w = MainWindow()
w.show()
app.exec_()

Drag Drop List in Tkinter

from tkinter import *
import tkinter as tk
root = tk.Tk()
def add_many_songs():
# Loop thru to fill list box
for song in range(11):
playlist_box.insert(END, song)
playlist_box =tk.Listbox(root,bg="black", fg="green", width=60, selectbackground="green", selectforeground='black',font = 20)
playlist_box.grid(row=0, column=0)
add_many_songs()
class DragDropListbox(tk.Listbox):
""" A Tkinter listbox with drag'n'drop reordering of entries. """
def __init__(self, master, **kw):
kw['selectmode'] = tk.SINGLE
tk.Listbox.__init__(self, master, kw)
self.bind('<Button-1>', self.setCurrent)
self.bind('<B1-Motion>', self.shiftSelection)
self.curIndex = None
def setCurrent(self, event):
self.curIndex = self.nearest(event.y)
def shiftSelection(self, event):
i = self.nearest(event.y)
if i < self.curIndex:
x = self.get(i)
self.delete(i)
self.insert(i+1, x)
self.curIndex = i
elif i > self.curIndex:
x = self.get(i)
self.delete(i)
self.insert(i-1, x)
self.curIndex = i
##I found this code that does drag and drop features within tkinter list. I got it to work with the example code. However, I am not able to get it to work within the attached code. I am still learning Python.
You should use the class DragDropListbox instead of tk.Listbox when creating playlist_box:
import tkinter as tk
def add_many_songs():
# Loop thru to fill list box
for song in range(11):
playlist_box.insert(tk.END, song)
class DragDropListbox(tk.Listbox):
""" A Tkinter listbox with drag'n'drop reordering of entries. """
def __init__(self, master, **kw):
kw['selectmode'] = tk.SINGLE
tk.Listbox.__init__(self, master, kw)
self.bind('<Button-1>', self.setCurrent)
self.bind('<B1-Motion>', self.shiftSelection)
self.curIndex = None
def setCurrent(self, event):
self.curIndex = self.nearest(event.y)
def shiftSelection(self, event):
i = self.nearest(event.y)
if i < self.curIndex:
x = self.get(i)
self.delete(i)
self.insert(i+1, x)
self.curIndex = i
elif i > self.curIndex:
x = self.get(i)
self.delete(i)
self.insert(i-1, x)
self.curIndex = i
root = tk.Tk()
playlist_box = DragDropListbox(root,bg="black", fg="green", width=60, selectbackground="green", selectforeground='black',font = 20)
playlist_box.grid(row=0, column=0)
add_many_songs()
root.mainloop()
Note that it is not recommended to import tkinter like below:
from tkinter import *
import tkinter as tk
Just use import tkinter as tk.

How can I make PyQt QSplitter visible?

Can I define the width/height for a QSplitter object? Can I make it visible with a proper arrow?
In my C++ code, I use QSplitter::setHandleWidth()
Adjusting the height would probably require adjusting the height of the container itself.
Here's how you can overwrite the QSplitter class to make the splitters to have some icon to let user know they can resize the widgets, See the image for output.
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
class Handle(QWidget):
def paintEvent(self, e=None):
painter = QPainter(self)
painter.setPen(Qt.NoPen)
painter.setBrush(Qt.Dense6Pattern)
painter.drawRect(self.rect())
class customSplitter(QSplitter):
def addWidget(self, wdg):
super().addWidget(wdg)
self.width = self.handleWidth()
l_handle = Handle()
l_handle.setMaximumSize(self.width*2, self.width*10)
layout = QHBoxLayout(self.handle(self.count()-1))
layout.setSpacing(0)
layout.setContentsMargins(0,0,0,0)
layout.addWidget(l_handle)
class Window(QMainWindow):
def setUI(self, MainWindow):
self.splt_v = customSplitter(Qt.Vertical)
self.splt_v.setHandleWidth(8)
self.splt_v.addWidget(QGroupBox("Box 1"))
self.splt_v.addWidget(QGroupBox("Box 2"))
self.splt_v.addWidget(QGroupBox("Box 3"))
self.wdg = QWidget()
self.v_lt = QVBoxLayout(self.wdg)
self.v_lt.addWidget(self.splt_v)
self.spl_h = customSplitter()
self.spl_h.addWidget(self.wdg)
self.spl_h.addWidget(QGroupBox("Box 4"))
self.spl_h.addWidget(QGroupBox("Box 5"))
self.h_lt = QHBoxLayout()
self.h_lt.addWidget(self.spl_h)
self.w = QWidget()
self.w.setLayout(self.h_lt)
self.w.setGeometry(0,0,1280,720)
self.w.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
MainWindow = QMainWindow()
ui = Window()
ui.setUI(MainWindow)
sys.exit(app.exec_())

Resources