Reputation: 247
I want to select a column on a right click and afterwards a context menu has to provide a delete entry for deleting the selected column.
With the transportet QPoint arg the mouse position is sent to the slot. But i need the clicked column.
How to fix it?
Upvotes: 1
Views: 4880
Reputation: 247
Ok - based on your solution (thanks a lot) - i tried a view. Mostly it does what i want. If i click on the right mouse button on a cell in the table body the cell should be selected and a context menu should be shown - ok - works. If a row or column head is under the mouse cursor the whole row (column) should be selected - works.
BUT the selection remains and that shouldn't be so!
Here's my code for the view:
import logging
import sys
from PyQt5.QtWidgets import \
QApplication, QWidget, QVBoxLayout, QMenu, \
QTableWidget, QTableWidgetItem, QTableWidgetSelectionRange
from PyQt5.QtCore import \
QPoint, Qt
class View(QWidget):
def __init__(self, app, parent=None):
super().__init__(parent)
self.app = app
# gui elements
self.layout = None
self.tbl = None
self.tbl_selection = None
self.tbl_item = None
self.tbl_item_pos = [-1, -1]
def _setup_layout(self):
logging.debug("<View._setup_layout>")
self.setMinimumHeight(600)
self.setMinimumWidth(800)
self.layout = QVBoxLayout()
self.setLayout(self.layout)
def _setup_table(self):
logging.debug("<View._setup_table()>")
if self.app.data:
data = self.app.data
rows, cols = len(data), len(data[0])
self.tbl = tbl = QTableWidget(rows, cols)
tbl.setHorizontalHeaderLabels(data.pop(0))
for r, ds in enumerate(data):
for c, f in enumerate(ds):
item = QTableWidgetItem(f)
tbl.setItem(r, c, item)
def _setup_widgets(self):
logging.debug("<View._setup_widgets()>")
if self.app is not None:
self._setup_table()
else:
self.tbl = QTableWidget()
tbl = self.tbl
tbl.verticalHeader().setContextMenuPolicy(
Qt.CustomContextMenu)
tbl.horizontalHeader().setContextMenuPolicy(
Qt.CustomContextMenu)
self.layout.addWidget(self.tbl)
def _connect_evt_with_handler(self):
logging.debug("<View._connect_evt_with_handler()>")
tbl = self.tbl
tbl.setContextMenuPolicy(Qt.CustomContextMenu)
tbl.customContextMenuRequested.connect(self.on_tbl_rightclick)
# event for click on row heads
tbl.verticalHeader().customContextMenuRequested.connect(
self.on_tbl_rhead_rightclick)
# event for click on col heads
tbl.horizontalHeader().customContextMenuRequested.connect(
self.on_tbl_chead_rightclick)
# protected
def _get_clicked_item(self, qpoint: QPoint) -> bool:
logging.debug("<View._get_clicked_item(qpoint={})>".format(qpoint))
self.tbl_item = item = self.tbl.itemAt(qpoint)
self.tbl_item_pos = [item.row(), item.column()]
return self.tbl_item is not None
def _set_selection(self):
logging.debug("<View._set_selection()>")
r, c = self.tbl_item_pos
logging.debug("... row={}, col={}".format(r, c))
if r >= 0 and c >= 0:
self.tbl_selection = QTableWidgetSelectionRange(
r, c, r, c)
elif c < 0:
# row head
self.tbl_selection = QTableWidgetSelectionRange(
r, 0, r, self.tbl.columnCount() - 1)
elif r < 0:
# col head
self.tbl_selection = QTableWidgetSelectionRange(
0, c, self.tbl.rowCount() - 1, c)
self.tbl.setRangeSelected(self.tbl_selection, True)
def _build_ctx_mnu(self, qpoint: QPoint):
logging.debug("<View._build_ctx_mnu(qpoint={})>".format(qpoint))
r, c = self.tbl_item_pos
logging.debug("... row={}, col={}".format(r, c))
# build menu
action_del_col = None
action_del_row = None
menu = QMenu()
if c < 0:
# row head
action_del_row = menu.addAction("Delete row")
elif r < 0:
# col head
action_del_col = menu.addAction("Delete column")
else:
# table body
action_del_col = menu.addAction("Delete column")
action_del_row = menu.addAction("Delete row")
action = menu.exec_(self.tbl.viewport().mapToGlobal(qpoint))
if action is not None and action == action_del_col:
logging.debug("... action: {}".format(action_del_col.text()))
self.tbl.removeColumn(c)
elif action is not None and action == action_del_row:
logging.debug("... action: {}".format(action_del_row.text()))
self.tbl.removeRow(r)
# UI
def setup(self):
logging.debug("<View.setup()>")
self._setup_layout()
self._setup_widgets()
self._connect_evt_with_handler()
# events
def on_tbl_rightclick(self, qpoint: QPoint):
logging.debug("<View.on_tbl_rightclick(qpoint={})>".format(qpoint))
if self._get_clicked_item(qpoint):
self._set_selection()
self._build_ctx_mnu(qpoint)
def on_tbl_rhead_rightclick(self, qpoint: QPoint):
logging.debug(
"<View.on_tbl_rhead_rightclick(qpoint={})>".format(qpoint))
if self._get_clicked_item(qpoint):
self.tbl_item_pos[1] = -1
self._set_selection()
self._build_ctx_mnu(qpoint)
def on_tbl_chead_rightclick(self, qpoint: QPoint):
logging.debug(
"<View.on_tbl_chead_rightclick(qpoint={})>".format(qpoint))
if self._get_clicked_item(qpoint):
self.tbl_item_pos[0] = -1
self._set_selection()
self._build_ctx_mnu(qpoint)
And here some date - once more:
data = [f.strip().split(",") for f in """\
id,first_name,last_name,email,gender,ip_address
1,Hillard,Tasseler,[email protected],Male,104.153.237.243
2,Tyrus,Oley,[email protected],Male,163.73.24.45
3,Kora,Covil,[email protected],Female,158.44.166.87
4,Phineas,McEntee,[email protected],Male,71.82.246.45
5,Dottie,Spraging,[email protected],Female,226.138.241.22
6,Andria,Ivatts,[email protected],Female,57.5.76.78
7,Missy,Featherstone,[email protected],Female,9.56.215.203
8,Anstice,Sargant,[email protected],Female,36.115.185.109
9,Teresita,Trounce,[email protected],Female,240.228.133.166
10,Sib,Thomke,[email protected],Female,129.191.2.7
11,Amery,Dallander,[email protected],Male,4.115.194.100
12,Rourke,Rowswell,[email protected],Male,48.111.190.66
13,Cloe,Benns,[email protected],Female,142.48.24.44
14,Enos,Fery,[email protected],Male,59.19.200.235
15,Russell,Capelen,[email protected],Male,38.205.20.141""".split()]
And for testing: the app object and the main function:
class App:
def __init__(self, data=()):
self.view = View(app=self)
self.data = []
if isinstance(data, (list, tuple)) and len(data) > 0:
self.set(data)
def set(self, data):
logging.debug("<App.set(data)>")
self.data = data
def setup(self):
logging.debug("<App.setup()>")
self.view.setup()
def show(self):
logging.debug("<App.show()>")
self.view.show()
def main():
logging.basicConfig(level=logging.DEBUG)
qapp = QApplication(sys.argv)
app = App(data=data)
app.setup()
app.show()
sys.exit(qapp.exec_())
main()
Upvotes: 1
Reputation: 243897
To obtain the column you must use the pressed item that is obtained using the itemAt() method.
It is selected using the setRangeSelected method by passing it QTableWidgetSelectionRange, which has the column as data.
Then you create the QMenu and the exec_() method you pass the position of the signal but you must convert it to global using mapToGlobal from the viewport.
Then you delete the column using the removeColumn() method.
from PyQt5 import QtCore, QtGui, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.table_widget = QtWidgets.QTableWidget(20, 10)
for i in range(self.table_widget.rowCount()):
for j in range(self.table_widget.columnCount()):
it = QtWidgets.QTableWidgetItem("{}-{}".format(i, j))
self.table_widget.setItem(i, j, it)
vlay = QtWidgets.QVBoxLayout(self)
vlay.addWidget(self.table_widget)
self.table_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.table_widget.customContextMenuRequested.connect(self.on_customContextMenuRequested)
@QtCore.pyqtSlot(QtCore.QPoint)
def on_customContextMenuRequested(self, pos):
it = self.table_widget.itemAt(pos)
if it is None: return
c = it.column()
item_range = QtWidgets.QTableWidgetSelectionRange(0, c, self.table_widget.rowCount()-1 , c)
self.table_widget.setRangeSelected(item_range, True)
menu = QtWidgets.QMenu()
delete_column_action = menu.addAction("Delete column")
action = menu.exec_(self.table_widget.viewport().mapToGlobal(pos))
if action == delete_column_action:
self.table_widget.removeColumn(c)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
Upvotes: 2