How to make a QComboBox group mutually exclusive? - python

Once an item is selected on a combo, it should get removed from the others, unless it's "No use".
I made three options of QComboBox, and each of those contains the same items.
The explanation is this:
QCombobox Tug 1 has total 4 items ('No Use', '207HR', '306DR', 'Jupiter')
QCombobox Tug 2 has total 4 items ('No Use', '207HR', '306DR', 'Jupiter')
QCombobox Tug 3 has total 4 items ('No Use', '207HR', '306DR', 'Jupiter')
The default value of those Qcombobox is 'No Use'.
How can I remove the selected value of QComboBox Tug 1 from QComboBox Tug 2?
The point is that 'No Use' shall not be removed; only an item from among '207HR', '306DR', and 'Jupiter'.
The Code i made is below:
class Ship_Use_Tug_Input_Program(QWidget):
def __init__(self, master):
super().__init__()
self.initUI()
def initUI(self):
tug1_cb = QComboBox(self)
jeju_tug = ['No use','207HR (2,500HP)', '306DR (3,600HP)', 'Jupiter (3,600HP)']
tug1_cb.addItems(jeju_tug)
tug2_cb = QComboBox(self)
tug2_cb.addItems(jeju_tug)
tug3_cb = QComboBox(self)
tug3_cb.addItems(jeju_tug)
self.setGeometry(100,100,1000,500)
self.setWindowTitle('Ship_Use_Tug_Input_Program')
self.show()
app = QApplication(sys.argv)
exc = Ship_Use_Tug_Input_Program(master=Ship_Use_Tug_Input_Program)
app.exec_()
The explanation photo is below:

The view-widget of the combo-box can be used to hide the rows, and the item-data can be used to keep track of which combo-box is showing which row. A slot connected to the activated signal can then update the items whenever one of the current-items change.
Below is a complete demo script that implements that. The ExclusiveComboGroup class can be used with any group of combo-boxes. To use it, just create an instance and then add all your combo-boxes using its addCombo method.
Demo Script:
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class ExclusiveComboGroup(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._combos = []
self._role = Qt.UserRole + 500
def addCombo(self, combo):
combo.activated.connect(
lambda: self.handleActivated(combo))
self._combos.append(combo)
def handleActivated(self, target):
index = target.currentIndex()
groupid = id(target)
for combo in self._combos:
if combo is target:
continue
previous = combo.findData(groupid, self._role)
if previous >= 0:
combo.view().setRowHidden(previous, False)
combo.setItemData(previous, None, self._role)
if index > 0:
combo.setItemData(index, groupid, self._role)
combo.view().setRowHidden(index, True)
class Window(QWidget):
def __init__(self):
super().__init__()
self.group = QGroupBox('Selected Tug')
layout = QVBoxLayout(self)
layout.addWidget(self.group)
layout = QFormLayout(self.group)
layout.setVerticalSpacing(15)
layout.setHorizontalSpacing(50)
jeju_tug = [
'No use',
'207HR (2,500HP)',
'306DR (3,600HP)',
'Jupiter (3,600HP)',
]
# create a combo-group
self.tugs = ExclusiveComboGroup(self)
for index in range(3):
combo = QComboBox(self)
combo.addItems(jeju_tug)
layout.addRow(f'Tug {index + 1}', combo)
# add the combo-box
self.tugs.addCombo(combo)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.setWindowTitle('Demo')
window.setGeometry(800, 200, 100, 50)
window.show()
sys.exit(app.exec_())
PS: here is how to use it in your own example:
def initUI(self):
tug1_cb = QComboBox(self)
jeju_tug = ['No use','207HR (2,500HP)', '306DR (3,600HP)', 'Jupiter (3,600HP)']
tug1_cb.addItems(jeju_tug)
tug2_cb = QComboBox(self)
tug2_cb.addItems(jeju_tug)
tug3_cb = QComboBox(self)
tug3_cb.addItems(jeju_tug)
# copy the ExclusiveComboGroup class into
# your code and then add this section
tugs = ExclusiveComboGroup(self)
tugs.addCombo(tug1_cb)
tugs.addCombo(tug2_cb)
tugs.addCombo(tug3_cb)

Related

PyQt6 Implementing copy-paste functionality across tabs in same window

I am trying to create a tableWidget in PyQt that has copy-paste functionality and the ability to create new tabs. My table is loaded with a initialized sheet but gives the user the ability to create new tabs with new qTableWidgets which I handled using a for loop to create and initialize the widget everytime a new tab is created in my add_sheet() function.
I wanted to also add functionality for copying and pasting inside each tab and across the tabs. Now when I added the key press event function to do this, I kept getting errors when trying to copy and paste in new tabs as out of index. I tried to fix this by keeping a pointer of which tab the selected indexes come from but this only allows me to edit on the first new tab created. The initial spreadsheet crashes when trying to do operations and the other tabs just do not work. It also does not copy and paste universally amongst the tabs.
I feel I have made my handling too complicated and I have a flaw or am missing something in my design pattern.
How can I properly implement my copy-paste function to work amongst all dynamically tabs with their own QTableWidget instances including the initial QTableWidget created both locally and universally?
import sys
from PyQt6.QtWidgets import (QApplication, QMainWindow,
QTableWidget, QTableWidgetItem,QVBoxLayout,QTabWidget,QWidget,QToolButton,QToolBar)
from PyQt6.QtCore import Qt
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.tab_widget = MyTabWidget(self)
self.setCentralWidget(self.tab_widget)
self.initializeUI()
def initializeUI(self):
"""Set up the application's GUI."""
self.setMinimumSize(1200, 500)
self.setWindowTitle("Spreadsheet - QTableWidget Example")
# Used for copy and paste actions
self.item_text = None
self.setUpMainWindow()
self.show()
def setUpMainWindow(self):
"""Create and arrange widgets in the main window."""
# Set initial row and column values
main_spreadsheet_widget.setRowCount(10)
main_spreadsheet_widget.setColumnCount(10)
class MyTabWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
self.layout = QVBoxLayout(self)
# Initialize tab screen
self.sheets = QTabWidget()
self.main_sheet = QWidget()
self.sheets.resize(300, 200)
self.extra_sheets_tracker = list()
self.tab_index = []
self.copied_cells_list = []
self.paste_index = []
# Add sheets
self.sheets.addTab(self.main_sheet, "Main Sheet")
#self.sheets.addTab(self.tab3, "Geeks")
# Create first tab
self.main_sheet.layout = QVBoxLayout(self)
self.main_sheet.layout.addWidget(main_spreadsheet_widget)
self.main_sheet.setLayout(self.main_sheet.layout)
self.tabButton = QToolButton(self)
self.tabButton.setText('+')
font = self.tabButton.font()
font.setBold(True)
self.tabButton.setFont(font)
self.sheets.setCornerWidget(self.tabButton)
self.tabButton.clicked.connect(self.add_sheet)
# Add sheets to widget
self.layout.addWidget(self.sheets)
self.setLayout(self.layout)
def add_sheet(self):
self.sheet = QWidget()
self.main_tab_sheet_widget = QTableWidget()
self.extra_sheets_tracker.append(self.main_tab_sheet_widget)
self.main_tab_sheet_widget.setRowCount(10)
self.main_tab_sheet_widget.setColumnCount(10)
self.sheet.layout = QVBoxLayout(self)
self.sheet.layout.addWidget(self.main_tab_sheet_widget)
self.sheet.setLayout(self.sheet.layout)
self.sheets.addTab(self.main_tab_sheet_widget, "Sheet" + str(len(self.extra_sheets_tracker)))
def keyPressEvent(self, event):
super().keyPressEvent(event)
if event.key() == Qt.Key.Key_C and (event.modifiers() & Qt.KeyboardModifier.ControlModifier):
self.tab_index = []
for i in self.extra_sheets_tracker:
if i.selectedIndexes() is not None:
self.tab_index.append(self.extra_sheets_tracker.index(i))
self.copied_cells = sorted(self.extra_sheets_tracker[self.tab_index[0]].selectedIndexes())
self.copied_cells_list.append(self.copied_cells)
self.copied_cells = None
elif event.key() == Qt.Key.Key_V and (event.modifiers() & Qt.KeyboardModifier.ControlModifier):
self.paste_index = []
for i in self.extra_sheets_tracker:
self.paste_index.append(self.extra_sheets_tracker.index(i))
r = self.extra_sheets_tracker[self.paste_index[0]].currentRow() - self.copied_cells_list[0][0].row()
c = self.extra_sheets_tracker[self.paste_index[0]].currentColumn() - self.copied_cells_list[0][0].column()
for cell in self.copied_cells_list[0]:
self.extra_sheets_tracker[self.paste_index[0]].setItem(cell.row() + r, cell.column() + c, QTableWidgetItem(cell.data()))
if __name__ == "__main__":
app = QApplication(sys.argv)
main_spreadsheet_widget = QTableWidget()
window = MainWindow()
window.show()
sys.exit(app.exec())
I have managed to do the following:
1- I treated the main sheet as if it was "just another sheet" and made sure it follows the same relative routine. This included removing unnecessary functions like setUpMainWindow and introducing static values like MAX_ROWS and MAX_COLS for maximum rows and columns respectively. This also entailed renaming extra_sheets_tracker to become sheets_tracker since it will hold all of the sheets.
2- I have created two strategies for pasting: 1st-cell for selecting the first cell in a table, or 1-to-1 for selecting the exact count of cells at the pasting side. This meant, also, checking for the dimensions beforehand and raising the exception Unmatching pasting size if the sizes did not match.
3- I removed self.tab_index and self.copied_cells_list since they are not used. copy_list is used globally and it is not a bad idea to stay in self. However paste_list is not used anywhere else than the paste routine and I removed it from self. You need to look into your self variables, you seem to overuse them.
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QTableWidget, QTableWidgetItem, QVBoxLayout, QTabWidget, QWidget, QToolButton, QToolBar
from PyQt6.QtCore import Qt
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.tab_widget = MyTabWidget(self)
self.setCentralWidget(self.tab_widget)
self.initializeUI()
def initializeUI(self):
"""Set up the application's GUI."""
self.setMinimumSize(1200, 500)
self.setWindowTitle("Spreadsheet - QTableWidget Example")
# Used for copy and paste actions
self.item_text = None
self.show()
class MyTabWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
self.layout = QVBoxLayout(self)
# Initialize tab screen
self.MAX_ROWS = 10
self.MAX_COLS = 10
self.STRATEGY = {0:'1st-cell', 1:'1-to-1'}
self.sheets = QTabWidget()
self.main_sheet = QWidget()
self.sheets.resize(300, 200)
self.sheets_tracker = list()
self.copy_list = []
#self.copied_cells_list = []
#self.paste_list = []
# Add a sheet and create first tab
self.main_sheet.layout = QVBoxLayout(self)
# Setup main window
self.add_sheet()
tabButton = QToolButton(self)
tabButton.setText('+')
font = tabButton.font()
font.setBold(True)
tabButton.setFont(font)
tabButton.clicked.connect(self.add_sheet)
self.sheets.setCornerWidget(tabButton)
# Add sheets to widget
self.layout.addWidget(self.sheets)
self.setLayout(self.layout)
def add_sheet(self):
sheets_count = len(self.sheets_tracker)
if sheets_count < 1:
label = "Main sheet"
else:
label = f"Sheet{sheets_count}"
self.main_tab_sheet_widget = QTableWidget()
self.main_tab_sheet_widget.setRowCount(self.MAX_ROWS)
self.main_tab_sheet_widget.setColumnCount(self.MAX_COLS)
self.sheets_tracker.append(self.main_tab_sheet_widget)
self.sheet = QWidget()
self.sheet.layout = QVBoxLayout(self)
self.sheet.layout.addWidget(self.main_tab_sheet_widget)
self.sheet.setLayout(self.sheet.layout)
self.sheets.addTab(self.main_tab_sheet_widget, label)
def keyPressEvent(self, event):
super().keyPressEvent(event)
if event.key() == Qt.Key.Key_C and (event.modifiers() & Qt.KeyboardModifier.ControlModifier):
self.copy_list = sorted([(e.row(), e.column(), e.data()) for e in self.sheets_tracker[self.sheets.currentIndex()].selectedIndexes()])
if len(self.copy_list) != 0:
# recalculate the indicies to absolute values
least_row = self.copy_list[0][0]
least_col = sorted(self.copy_list, key=lambda tup: tup[1])[0][1]
self.copy_list = [(e[0]-least_row, e[1]-least_col, e[2]) for e in self.copy_list]
print(self.copy_list)
elif event.key() == Qt.Key.Key_V and (event.modifiers() & Qt.KeyboardModifier.ControlModifier):
paste_list = sorted([(element.row(), element.column(), element.data()) for element in self.sheets_tracker[self.sheets.currentIndex()].selectedIndexes()])
if len(paste_list) != 0:
if len(paste_list) == 1:
# The given paste position is the first cell only
current_r, current_c, _ = paste_list[-1]
last_r, last_c, _ = self.copy_list[-1]
if last_r + current_r - 1 < self.MAX_ROWS and last_c + current_c - 1 < self.MAX_COLS:
strategy = self.STRATEGY[0]
else:
raise Exception('Unmatching pasting size')
elif len(self.copy_list) == len(paste_list):
# You can paste in one-to-one correspondence
strategy = self.STRATEGY[1]
else:
raise Exception('Unmatching pasting size')
if strategy == self.STRATEGY[0]:
r, c, _ = paste_list[0]
for index, e in enumerate(self.copy_list):
_, _, d = self.copy_list[index]
d = '' if d is None else d
print(f"Pasting at index [{self.sheets.currentIndex()}] cell ({e[0]+r}, {e[1]+c})")
textCell = QTableWidgetItem(); textCell.setText(f'{d}')
self.sheets_tracker[self.sheets.currentIndex()].setItem(e[0]+r, e[1]+c, textCell)
if strategy == self.STRATEGY[1]:
for index, e in enumerate(paste_list):
_, _, d = self.copy_list[index]
d = '' if d is None else d
print(f"Pasting at index [{self.sheets.currentIndex()}] cell ({e[0]}, {e[1]})")
textCell = QTableWidgetItem(); textCell.setText(f'{d}')
self.sheets_tracker[self.sheets.currentIndex()].setItem(e[0], e[1], textCell)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())

PyQt5 removing button

In the list_widget I have added a add button I also want to add a remove button which asks which item you wants to remove and remove the chosen item. I was trying it to do but I didn't had any idea to do so .Also, please explain the solution I am a beginner with pyqt5 or I'd like to say absolute beginner.
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication,QMainWindow,
QListWidget, QListWidgetItem
import sys
class MyWindow(QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.x = 200
self.y = 200
self.width = 500
self.length = 500
self.setGeometry(self.x, self.y, self.width,
self.length)
self.setWindowTitle("Stock managment")
self.iniTUI()
def iniTUI(self):
# buttons
self.b1 = QtWidgets.QPushButton(self)
self.b1.setText("+")
self.b1.move(450, 100)
self.b1.resize(50, 25)
self.b1.clicked.connect(self.take_inputs)
# This is the button I want to define.
self.btn_minus = QtWidgets.QPushButton(self)
self.btn_minus.setText("-")
self.btn_minus.move(0, 100)
self.btn_minus.resize(50, 25)
# list
self.list_widget = QListWidget(self)
self.list_widget.setGeometry(120, 100, 250, 300)
self.item1 = QListWidgetItem("A")
self.item2 = QListWidgetItem("B")
self.item3 = QListWidgetItem("C")
self.list_widget.addItem(self.item1)
self.list_widget.addItem(self.item2)
self.list_widget.addItem(self.item3)
self.list_widget.setCurrentItem(self.item2)
def take_inputs(self):
self.name, self.done1 =
QtWidgets.QInputDialog.getText(
self, 'Add Item to List', 'Enter The Item you want
in
the list:')
self.roll, self.done2 = QtWidgets.QInputDialog.getInt(
self, f'Quantity of {str(self.name)}', f'Enter
Quantity of {str(self.name)}:')
if self.done1 and self.done2:
self.item4 = QListWidgetItem(f"{str(self.name)}
Quantity{self.roll}")
self.list_widget.addItem(self.item4)
self.list_widget.setCurrentItem(self.item4)
def clicked(self):
self.label.setText("You clicked the button")
self.update()
def update(self):
self.label.adjustSize()
def clicked():
print("meow")
def window():
apk = QApplication(sys.argv)
win = MyWindow()
win.show()
sys.exit(apk.exec_())
window()
The core issue here is the lack of separation of the view and the data. This makes it very hard to reason about how to work with graphical elements. You will almost certainly want to follow the Model View Controller design paradigm https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
which offers a systematic way to handle this separation.
Once you do so, it immediately becomes very straight forward how to proceed with the question: You essentially just have a list, and you either want to add a thing to this list, or remove one based on a selection.
I include an example here which happens to use the built-in classes QStringListModel and QListView in Qt5, but it is simple to write your own more specialized widgets and models. They all just use a simple signal to emit to the view that it needs to refresh the active information.
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
class StuffViewer(QMainWindow):
def __init__(self, model):
super().__init__()
self.setWindowTitle("Stock managment")
# 1: Use layouts.
hbox = QHBoxLayout()
widget = QWidget()
widget.setLayout(hbox)
self.setCentralWidget(widget)
# 2: Don't needlessly store things in "self"
vbox = QVBoxLayout()
add = QPushButton("+")
add.clicked.connect(self.add_new_stuff)
vbox.addWidget(add)
sub = QPushButton("-")
sub.clicked.connect(self.remove_selected_stuff)
vbox.addWidget(sub)
vbox.addStretch(1)
hbox.addLayout(vbox)
# 3: Separate the view of the data from the data itself. Use Model-View-Controller design to achieve this.
self.model = model
self.stuffview = QListView()
self.stuffview.setModel(self.model)
hbox.addWidget(self.stuffview)
def add_new_stuff(self):
new_stuff, success = QInputDialog.getText(self, 'Add stuff', 'Enter new stuff you want')
if success:
self.stuff.setStringList(self.stuff.stringList() + [new_stuff])
def remove_selected_stuff(self):
index = self.stuffview.currentIndex()
all_stuff = self.stuff.stringList()
del all_stuff[index.column()]
self.stuff.setStringList(all_stuff)
def window():
apk = QApplication(sys.argv)
# Data is clearly separated:
# 4: Never enumerate variables! Use lists!
stuff = QStringListModel(["Foo", "Bar", "Baz"])
# The graphical components is just how you interface with the data with the user!
win = StuffViewer(stuff)
win.show()
sys.exit(apk.exec_())
window()

QComboBox show whole row of QTreeView

I want to use QtreeView to organize the data shown by a QComboBox. As you can see in my example, creating the box and setting up data works so far.
But my problem is, that the combobox itself only shows the first argument and not the whole line. what I want to have is, that there is shown the whole row, not only the first item of the row.
Is this maybe related to the fact, that each cell is selectable? Do I have to prohibit to select items at the end of the tree branch?
How can I achieve this while adding the elements to the QtreeView-data?
minimal example:
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
data = [['a','b','c'],['d','e','f'],['g','h','i']]
class MainWindow(QMainWindow):
dispatcher = 0
def __init__(self):
super().__init__()
# buil UI
self.init_ui()
def init_ui(self):
# layout
self.box_window = QVBoxLayout()
# content
model = QStandardItemModel(len(data),len(data[0]))
row = 0
for r in data:
col = 0
for item in r:
model.setData(model.index(row, col), item)
col += 1
row += 1
tree_view = QTreeView()
tree_view.setHeaderHidden(True)
tree_view.setRootIsDecorated(False)
tree_view.setAlternatingRowColors(True)
combobox = QComboBox()
combobox.setMinimumSize(250,50)
combobox.setView(tree_view)
combobox.setModel(model)
self.box_window.addWidget(combobox)
self.box_window.addStretch()
# build central widget and select it
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.centralWidget().setLayout(self.box_window)
# show window
self.setGeometry(50,50,1024,768)
self.setWindowTitle("Test")
self.show()
def main():
app = QApplication(sys.argv)
main_window = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
A possible solution is to concatenate the texts in the row and set as the text to be painted:
class ComboBox(QComboBox):
def paintEvent(self, event):
painter = QStylePainter(self)
painter.setPen(self.palette().color(QPalette.Text))
# draw the combobox frame, focusrect and selected etc.
opt = QStyleOptionComboBox()
self.initStyleOption(opt)
values = []
for c in range(self.model().columnCount()):
index = self.model().index(self.currentIndex(), c, self.rootModelIndex())
values.append(index.data())
opt.currentText = " ".join(values)
painter.drawComplexControl(QStyle.CC_ComboBox, opt)
# draw the icon and text
painter.drawControl(QStyle.CE_ComboBoxLabel, opt)

Create different context menus on several QTableViews

I have two QTableViews inside a QMainWindow and I want to create a context menu on one of them and later another context menu on the other one.
Creating the context menu and define actions works so far. But the context menu pops up everywhere inside the whole application. I don't know how to limit it to only one specific table. I think it has to do with contextMenuEvent() which is a member of QMainWindow, but I don't know how to change this part. Trying to create a custom class that inherits QTableView didn't work, because I'm not sure where to start.
Here's what I tried:
The populate_table_1() and populate_table_2() methods are only for filling some data into the tables. The get_selected_item_TV1() method gets the necessary data from a row of table_1. The delete_file() method is an example for what I want do when calling the delete action from the contextMenuEvent() method. This code works so far, but I want the context menu to popup only if I right-click on a row of table_1 and that it don't appear at all when right-clicking elsewhere.
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
data_1 = ["file_name", "file_infos"]
data_2 = ["other_stuff_1", "other_stuff_2"]
class Ui_MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("MyApp")
self.resize(450, 280)
centralwidget = QWidget(self)
#]===================================================================[#
# table_1
table_1 = QTableView(
centralwidget,
selectionBehavior=QAbstractItemView.SelectRows,
editTriggers=QAbstractItemView.NoEditTriggers
)
# table_1 models
self.modelTV1 = QStandardItemModel(0, 2, centralwidget)
self.modelTV1.setHorizontalHeaderLabels(["column 1", "column 2"])
table_1.setModel(self.modelTV1)
self.selectionModelTV1 = table_1.selectionModel()
#]===================================================================[#
# table_2
table_2 = QTableView(
centralwidget,
selectionBehavior=QAbstractItemView.SelectRows,
editTriggers=QAbstractItemView.NoEditTriggers,
)
# table_2 models
self.modelTV2 = QStandardItemModel(0, 2, centralwidget)
self.modelTV2.setHorizontalHeaderLabels(["column 1", "column 2"])
table_2.setModel(self.modelTV2)
self.selectionModelTV2 = table_2.selectionModel()
v_Layout1 = QVBoxLayout()
v_Layout1.addWidget(table_1)
v_Layout1.addWidget(table_2)
gridLayout = QGridLayout(centralwidget)
gridLayout.addLayout(v_Layout1, 0, 0, 1, 1)
self.setCentralWidget(centralwidget)
def populate_table_1(self):
self.modelTV1.setRowCount(0)
for item in data_1:
self.modelTV1.insertRow(0)
for i, text in enumerate(data_1):
self.modelTV1.setItem(0, i, QStandardItem(text))
def populate_table_2(self):
self.modelTV2.setRowCount(0)
for item in data_2:
self.modelTV2.insertRow(0)
for i, text in enumerate(data_2):
self.modelTV2.setItem(0, i, QStandardItem(text))
def contextMenuEvent(self, event):
self.contextMenu = QMenu(self)
deleteAction = QAction("Delete", self)
self.contextMenu.addAction(deleteAction)
deleteAction.triggered.connect(lambda: self.delete_file(event))
self.contextMenu.popup(QCursor.pos())
def get_selected_item_TV1(self):
# get the row's text from the first column in table_1
listed_items = self.selectionModelTV1.selectedRows()
for index in listed_items:
selected_item = index.data()
return f"table_1 - row_{index.row()} - {selected_item}"
def delete_file(self, event):
item = self.get_selected_item_TV1()
print(f"Deleting: {item}")
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
mainUI = Ui_MainWindow()
mainUI.populate_table_1()
mainUI.populate_table_2()
mainUI.show()
sys.exit(app.exec_())
There are many alternatives:
Detect that when the mouse is pressed it is in a certain area, for example the first QTableView. In this case the widget must be accessible in the contextMenuEvent() so you must change table_1 to self.table_1 and then use underMouse():
def contextMenuEvent(self, event):
if self.table_1.underMouse():
self.contextMenu = QMenu(self)
deleteAction = QAction("Delete", self)
self.contextMenu.addAction(deleteAction)
deleteAction.triggered.connect(lambda: self.delete_file(event))
self.contextMenu.popup(QCursor.pos())
Implement the contextMenuEvent method for each QTableView:
class TableView(QTableView):
def contextMenuEvent(self, event):
self.contextMenu = QMenu(self)
deleteAction = QAction("Delete", self)
self.contextMenu.addAction(deleteAction)
deleteAction.triggered.connect(lambda: self.delete_file(event))
self.contextMenu.popup(QCursor.pos())
def get_selected_item_TV1(self):
# get the row's text from the first column in table_1
listed_items = self.selectionModel().selectedRows()
for index in listed_items:
selected_item = index.data()
return f"table_1 - row_{index.row()} - {selected_item}"
def delete_file(self, event):
item = self.get_selected_item_TV1()
print(f"Deleting: {item}")
and then you must change table_1 = QTableView(... for table_1 = TableView(...
Another alternative is to use the customContextMenuRequested signal, for this you must enable the Qt::CustomContextMenu flag:
table_1 = QTableView(
centralwidget,
selectionBehavior=QAbstractItemView.SelectRows,
editTriggers=QAbstractItemView.NoEditTriggers,
contextMenuPolicy=Qt.CustomContextMenu
)
table_1.customContextMenuRequested.connect(self.on_customContextMenuRequested)
def on_customContextMenuRequested(self):
self.contextMenu = QMenu(self)
deleteAction = QAction("Delete", self)
self.contextMenu.addAction(deleteAction)
deleteAction.triggered.connect(lambda: self.delete_file())
self.contextMenu.popup(QCursor.pos())
def delete_file(self):
item = self.get_selected_item_TV1()
print(f"Deleting: {item}")

PYQT Accessing and changing dynamically added controls

I am a beginner with GUI's and PYQT. What I am trying to do is dynamically set up a grid of QComboBox's and QLineEdit's. From the QComboBox you can select a choice and from that choice, it will fill in the corresponding QLineEdit with some numbers. The problem I'm having is creating the link between the first QComboBox and the first QLineEdit box. I could make a function for each row but I would like to know a better way. I will post some sample code. Thank you for any help or advice that you might have.
import sys
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class window(QMainWindow):
def __init__(self):
super(window, self).__init__()
self.setGeometry(50, 50, 700, 600)
self.home()
def home(self):
Test1Choices = ['Test1:','Choice1', 'Choice2', 'Choice3', 'Choice4','Choice5', 'Choice6', 'Choice7', 'Choice8', 'Choice9']
Test2Choices= ['Test2:','1','2','3','4','5','6','7','8','9','10','11','12','13','14','15']
for i in range(0,10):
Choice1ComboBox = QComboBox(self)
Choice1ComboBox.addItems(Test1Choices)
Choice1ComboBox.resize(150,25)
Choice1ComboBox.move(30,(150+(i*35)))
Choice1ComboBox.setCurrentIndex(2)
Choice2ComboBox = QComboBox(self)
Choice2ComboBox.setObjectName("Choice2ComboBox"+str(i))
Choice2ComboBox.addItems(Test2Choices)
Choice2ComboBox.resize(75,25)
Choice2ComboBox.move(200,(150+(i*35)))
Choice2ComboBox.setCurrentIndex(2)
Choice2ComboBox.activated[str].connect(self.doSomething)
numTextBox = QLineEdit(self)
numTextBox.setObjectName("numBox"+str(i))
numTextBox.move(325,(150+(i*35)))
numTextBox.resize(35,25)
result1TextBox = QLineEdit(self)
result1TextBox.setObjectName("result1Box"+str(i))
result1TextBox.move(400,(150+(i*35)))
result1TextBox.resize(100,25)
result1TextBox.setEnabled(0)
result2TextBox = QLineEdit(self)
result2TextBox.setObjectName("result2Box"+str(i))
result2TextBox.move(525,(150+(i*35)))
result2TextBox.resize(100,25)
result2TextBox.setEnabled(0)
self.show()
def doSomething(self):
numbers=['result1','result2','result3','result4','result5','result6','result7','result8','result9','result10','result11','result12','result13','result14','result15']
def run():
app = QApplication(sys.argv)
Gui = window()
sys.exit(app.exec_())
run()
To summarize I would like to bring in the index of the selected QComboBox. Then use that index number to reference the answer that is in the "numbers" array. Then print that result in the QLineEdit that is in the same row
We use sender() to get the object that emits the signal, then we look for the name of that object with setObjectName(), and we search the index, then we get the other objects with findChildren(), for example the output will be the union of the selected texts.
add name to Choice1ComboBox:
Choice1ComboBox.setObjectName("Choice1ComboBox"+str(i))
doSomething function:
def doSomething(self, _):
sender = self.sender()
l = sender.objectName().split("Choice1ComboBox")
if len(l) > 1:
number = l[1]
else:
number = sender.objectName().split("Choice2ComboBox")[1]
combo1 = self.findChildren(QComboBox, "Choice1ComboBox"+number)[0]
combo2 = self.findChildren(QComboBox, "Choice2ComboBox"+number)[0]
obj = self.findChildren(QLineEdit, "numBox"+number)[0]
obj.setText(combo1.currentText() + " " + combo2.currentText())
Complete code:
import sys
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class window(QMainWindow):
def __init__(self):
super(window, self).__init__()
self.setGeometry(50, 50, 700, 600)
self.home()
def home(self):
Test1Choices = ['Test1:','Choice1', 'Choice2', 'Choice3', 'Choice4','Choice5', 'Choice6', 'Choice7', 'Choice8', 'Choice9']
Test2Choices= ['Test2:','1','2','3','4','5','6','7','8','9','10','11','12','13','14','15']
for i in range(0,10):
Choice1ComboBox = QComboBox(self)
Choice1ComboBox.setObjectName("Choice1ComboBox"+str(i))
Choice1ComboBox.addItems(Test1Choices)
Choice1ComboBox.resize(150,25)
Choice1ComboBox.move(30,(150+(i*35)))
Choice1ComboBox.setCurrentIndex(2)
Choice1ComboBox.activated[str].connect(self.doSomething)
Choice2ComboBox = QComboBox(self)
Choice2ComboBox.setObjectName("Choice2ComboBox"+str(i))
Choice2ComboBox.addItems(Test2Choices)
Choice2ComboBox.resize(75,25)
Choice2ComboBox.move(200,(150+(i*35)))
Choice2ComboBox.setCurrentIndex(2)
Choice2ComboBox.activated[str].connect(self.doSomething)
numTextBox = QLineEdit(self)
numTextBox.setObjectName("numBox"+str(i))
numTextBox.move(325,(150+(i*35)))
numTextBox.resize(35,25)
result1TextBox = QLineEdit(self)
result1TextBox.setObjectName("result1Box"+str(i))
result1TextBox.move(400,(150+(i*35)))
result1TextBox.resize(100,25)
result1TextBox.setEnabled(0)
result2TextBox = QLineEdit(self)
result2TextBox.setObjectName("result2Box"+str(i))
result2TextBox.move(525,(150+(i*35)))
result2TextBox.resize(100,25)
result2TextBox.setEnabled(0)
self.show()
def doSomething(self, _):
sender = self.sender()
l = sender.objectName().split("Choice1ComboBox")
if len(l) > 1:
number = l[1]
else:
number = sender.objectName().split("Choice2ComboBox")[1]
combo1 = self.findChildren(QComboBox, "Choice1ComboBox"+number)[0]
combo2 = self.findChildren(QComboBox, "Choice2ComboBox"+number)[0]
obj = self.findChildren(QLineEdit, "numBox"+number)[0]
obj.setText(combo1.currentText() + " " + combo2.currentText())
def run():
app = QApplication(sys.argv)
Gui = window()
sys.exit(app.exec_())
run()

Categories