I have a QTreeView and a QListView. Both of these use the QStandardItemModel as models. The QTreeView should not accept drops. The QListView should accept drops.
If an item is dragged from the QTreeView and dropped onto an item in the QListView, then a copy of the item dragged from the QTreeView should replace the item in the QListView that it is dropped onto. If an item is dragged from the QTreeView and dropped at the top of the QListView, or between existing items in the QListView, or at the bottom of the QListView, then it should be copied to there.
If an item is dragged from the QListView and dropped onto an item in the QListView, then it should be moved to replace the item in the QListView that it is dropped onto. If an item is dragged from the QListView and dropped at the top of the QListView, or between existing items in the QListView, or at the bottom of the QListView, then it should be moved to there.
The items in the QTreeView should not be editable. Once they are copied to the QListView they should become editable in the QListView.
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
def decode_byte_array(byte_array):
role_value_dict_list = []
data_stream = QDataStream(byte_array, QIODevice.ReadOnly)
while not data_stream.atEnd():
row = data_stream.readInt32()
column = data_stream.readInt32()
count = data_stream.readInt32()
role_value_dict = {}
for i in range(count):
role = data_stream.readInt32()
value = QVariant()
data_stream >> value
role_value_dict[Qt.ItemDataRole(role)] = value
role_value_dict_list.append(role_value_dict)
return role_value_dict_list
class MyListModel(QStandardItemModel):
def dropMimeData(self, data, action, row, column, parent):
if data.hasFormat('application/x-qabstractitemmodeldatalist'):
byte_array = QByteArray(data.data("application/x-qabstractitemmodeldatalist"))
role_value_dict_list = decode_byte_array(byte_array)
item_list = []
for role_value_dict in role_value_dict_list:
item = QStandardItem()
for role, value in role_value_dict.items():
item.setData(value, role)
item.setEditable(True)
item_list.append(item)
parent_item = self.itemFromIndex(parent)
if row == -1 and column == -1:
if parent_item == None:
# Drop is after last row.
for item in item_list:
self.appendRow(item)
else:
# Drop is on row.
self.setItem(parent.row(), parent.column(), item_list[0])
row = parent.row() + 1
for item in item_list[1:]:
self.insertRow(row, item)
row = row + 1
elif row >= 0 and column >= 0:
# Drop is before first row or between rows.
for item in item_list:
self.insertRow(row, item)
row = row + 1
else:
return False
else:
return False
return True
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
centralWidget = QWidget()
layout = QHBoxLayout()
tree_view_model = QStandardItemModel()
parent_item = tree_view_model.invisibleRootItem()
item_1 = QStandardItem('item 1')
item_1.setDragEnabled(False)
item_1.setEditable(False)
item_2 = QStandardItem('item 2')
item_2.setEditable(False)
item_3 = QStandardItem('item 3')
item_3.setEditable(False)
item_1.appendRow(item_2)
item_1.appendRow(item_3)
parent_item.appendRow(item_1)
tree_view = QTreeView()
tree_view.setModel(tree_view_model)
tree_view.setHeaderHidden(True)
tree_view.header().setSectionResizeMode(QHeaderView.ResizeToContents)
tree_view.setDragEnabled(True)
list_view_model = MyListModel()
list_view = QListView()
list_view.setModel(list_view_model)
list_view.setDragEnabled(True)
list_view.setAcceptDrops(True)
list_view.setDefaultDropAction(Qt.MoveAction)
list_view.setDragDropOverwriteMode(False)
list_view.setSelectionBehavior(QListView.SelectRows)
list_view.setSelectionMode(QListView.SingleSelection)
layout.addWidget(tree_view)
layout.addWidget(list_view)
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
app = QApplication([])
f = QFont('Courier')
f.setPointSize(16)
app.setFont(f)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Related
I tried to insert some QRadioButton inside some cell of a QTableWidget. The situation is similar to the one of this post. In particular, the solution by #eyllanesc, with PySide2 is the following
import sys
from PySide2.QtWidgets import QApplication, QTableWidget, QTableWidgetItem, \
QButtonGroup, QRadioButton
app = QApplication(sys.argv)
searchView = QTableWidget(0, 4)
colsNames = ['A', 'B', 'C']
searchView.setHorizontalHeaderLabels(['DIR'] + colsNames)
dirNames = {'A': ['/tmp', '/tmp/dir1'], 'B': ['/tmp/dir2'],
'C': ['/tmp/dir3']}
rowCount = sum(len(v) for (name, v) in dirNames.items())
searchView.setRowCount(rowCount)
index = 0
for letter, paths in dirNames.items():
for path in paths:
it = QTableWidgetItem(path)
searchView.setItem(index, 0, it)
group = QButtonGroup(searchView)
for i, name in enumerate(colsNames):
button = QRadioButton()
group.addButton(button)
searchView.setCellWidget(index, i + 1, button)
if name == letter:
button.setChecked(True)
index += 1
searchView.show()
sys.exit(app.exec_())
When resizing the columns or rows, I notice a weird behavior: while I'm pressing the mouse button and resizing the column or the raw, the QRadioButtons remain still at their places, causing some clashes; then, when I finally release the mouse button, every QRadioButton come to its place. Is there a way to avoid that aka to make the QRadioButtons move as well during the resizing process?
Since my previous solution generates other problems then in this solution I will show another alternative:
Implement the logic of exclusion through a delegate using the Qt :: CheckStateRole.
A QProxyStyle can be used for painting
import sys
from PySide2.QtCore import Qt, QEvent
from PySide2.QtWidgets import (
QApplication,
QTableWidget,
QTableWidgetItem,
QStyledItemDelegate,
QStyle,
QStyleOptionViewItem,
QProxyStyle,
)
class RadioButtonDelegate(QStyledItemDelegate):
def editorEvent(self, event, model, option, index):
flags = model.flags(index)
if (
not (flags & Qt.ItemIsUserCheckable)
or not (option.state & QStyle.State_Enabled)
or not (flags & Qt.ItemIsEnabled)
):
return False
state = index.data(Qt.CheckStateRole)
if state is None:
return False
widget = option.widget
style = widget.style() if widget is not None else QApplication.style()
# make sure that we have the right event type
if (
(event.type() == QEvent.MouseButtonRelease)
or (event.type() == QEvent.MouseButtonDblClick)
or (event.type() == QEvent.MouseButtonPress)
):
viewOpt = QStyleOptionViewItem(option)
self.initStyleOption(viewOpt, index)
checkRect = style.subElementRect(
QStyle.SE_ItemViewItemCheckIndicator, viewOpt, widget
)
me = event
if me.button() != Qt.LeftButton or not checkRect.contains(me.pos()):
return False
if (event.type() == QEvent.MouseButtonPress) or (
event.type() == QEvent.MouseButtonDblClick
):
return True
else:
return False
if state != Qt.Checked:
for c in range(model.columnCount()):
if c not in (0, index.column()):
ix = model.index(index.row(), c)
model.setData(ix, Qt.Unchecked, Qt.CheckStateRole)
return model.setData(index, Qt.Checked, Qt.CheckStateRole)
return False
class RadioStyle(QProxyStyle):
def drawPrimitive(self, element, option, painter, widget=None):
if element == QStyle.PE_IndicatorItemViewItemCheck:
element = QStyle.PE_IndicatorRadioButton
super().drawPrimitive(element, option, painter, widget)
app = QApplication(sys.argv)
searchView = QTableWidget(0, 4)
style = RadioStyle(searchView.style())
searchView.setStyle(style)
delegate = RadioButtonDelegate(searchView)
searchView.setItemDelegate(delegate)
colsNames = ["A", "B", "C"]
searchView.setHorizontalHeaderLabels(["DIR"] + colsNames)
dirNames = {"A": ["/tmp", "/tmp/dir1"], "B": ["/tmp/dir2"], "C": ["/tmp/dir3"]}
rowCount = sum(len(v) for (name, v) in dirNames.items())
searchView.setRowCount(rowCount)
index = 0
for letter, paths in dirNames.items():
for path in paths:
it = QTableWidgetItem(path)
searchView.setItem(index, 0, it)
for i, name in enumerate(colsNames):
it = QTableWidgetItem()
searchView.setItem(index, i + 1, it)
it.setCheckState(Qt.Checked if name == letter else Qt.Unchecked)
index += 1
searchView.show()
sys.exit(app.exec_())
I had this problem and managed to fix it with a fairly straightforward solution.
Create a connection from your QTableWidget's QHeader's sectionResized signal to a custom method (_refresh_row_size in my case).
self.table.horizontalHeader().sectionResized.connect(self._refresh_row_size)
self.table.verticalHeader().sectionResized.connect(self._refresh_row_size)
I only wanted the first row and first column to get resized, as that is where I have inserted a QCheckbox. This is what I used:
def _refresh_row_size(self, logicalIndex, oldSize, newSize):
self.table.rowResized(0, oldSize, newSize)
self.table.columnResized(0, oldSize, newSize)
return
For extra context, this is the QTableItem I added to the QTable:
# Create checkbox widget
self.widget = QtWidgets.QWidget()
self.checkbox = QtWidgets.QCheckBox(self.widget)
self.layout = QtWidgets.QHBoxLayout(self.widget)
self.layout.addWidget(self.checkbox)
self.layout.setAlignment(QtCore.Qt.AlignCenter)
self.layout.setContentsMargins(0, 0, 0, 0)
# Add new row to table
row_position = self.table.rowCount()
self.table.insertRow(row_position)
# Create new table item, add item, add widget
item = QtWidgets.QTableWidgetItem()
self.table.setItem(0, 0, item)
self.table.setCellWidget(0, 0, self.widget)
I have a QListWidget with custom widgets. I am seeing an issue where if I do the following:
add two items
Remove the second item
Add another item
then the first item's widget contents disappears until I either resize the window, or add a third item.
import sys
from PySide.QtGui import *
from PySide.QtCore import *
class StagingWidget(QGroupBox):
def __init__(self,parent=None):
#QWidget.__init__(self,parent)
super(StagingWidget,self).__init__()
self.itemWidgets = list()
self.count = 1
self.createUi()
def createUi(self):
self.widget_layout=QVBoxLayout(self)
self.list_widget=QListWidget()
self.setFixedWidth(450)
self.setFixedHeight(600)
self.list_widget.setStyleSheet("QListWidget::item:selected{background:lightblue}")
self.widget_layout.addWidget(self.list_widget)
self.buttonHLayout = QHBoxLayout()
self.add = QPushButton("Add Item")
self.add.clicked.connect(self.addListItem)
self.buttonHLayout.addWidget(self.add)
self.widget_layout.addLayout(self.buttonHLayout)
def addListItem(self):
itemN = QListWidgetItem()
widget = QWidget()
Button1 = QPushButton(str(self.count))
Button2 = QPushButton("Remove")
Button2.clicked.connect(lambda item=itemN:self.removeJob(item))
widgetLayout = QHBoxLayout()
widgetLayout.addWidget(Button1)
widgetLayout.addWidget(Button2)
widget.setLayout(widgetLayout)
itemN.setSizeHint(widget.sizeHint())
self.itemWidgets.append(widget)
self.list_widget.addItem(itemN)
self.list_widget.setItemWidget(itemN, self.itemWidgets[-1])
self.count = self.count + 1
def removeJob(self,item):
print("Removing Job")
row = self.list_widget.indexFromItem(item).row()
self.list_widget.takeItem(row)
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = StagingWidget()
widget.show()
sys.exit(app.exec_())
Example:
Add 1 item:
Add another item:
Remove item 2:
Add another item(You can see widget of item 1 disappear):
Add another item(Widget of item 1 reappears):
This happens every single time I do the above steps.
I have two tables. I want to get the selected value in table1 and put it in table2.
For example, if you select 1, table2
I want the whole value of row 1 to be entered and the next row 5 to be added to the row 5.
In conclusion, I would like to make table1 show the selected row value in table2.
I do not know exactly how to load the selected table1 value, but I think it would be better to append one value to QStandardItemModel in def table1_DoubleClicked (self): using self.serch.table.setModel in table2. How can I do it?
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.resize(500, 500)
self.Table1()
self.Table2()
self.Layout()
def Table1(self):
self.select_guorpbox = QGroupBox()
self.select_guorpbox.setTitle("Article 1")
self.columncount = 10
self.rowcount = 10
self.select_table_model = QStandardItemModel(self.rowcount,self.columncount)
for i in range(self.rowcount):
for j in range(self.columncount):
table = QStandardItem('test [{},{}]'.format(i,j))
self.select_table_model.setItem(i,j,table)
table.setTextAlignment(Qt.AlignCenter)
self.TextFilter = QSortFilterProxyModel()
self.TextFilter.setSourceModel(self.select_table_model)
self.TextFilter.setFilterKeyColumn(2)
self.SerchLineEdit = QLineEdit()
self.SerchLineEdit.textChanged.connect(self.TextFilter.setFilterRegExp)
self.select_table = QTableView()
self.select_table.setModel(self.TextFilter)
self.select_table.setColumnWidth(1, 150)
self.select_table.setColumnWidth(2, 300)
self.select_table.setEditTriggers(QTableView.NoEditTriggers)
self.select_table.setSelectionBehavior(QTableView.SelectRows)
self.select_table.setContextMenuPolicy(Qt.CustomContextMenu)
self.select_table.doubleClicked.connect(self.table1_DoubleClicked)
self.select_table.customContextMenuRequested.connect(self.table1_CustomContextMenu)
# column auto sort
# self.select_table.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
# self.select_table.resizeColumnsToContents()
v = QVBoxLayout()
v.addWidget(self.select_table)
self.select_guorpbox.setLayout(v)
def Table2(self):
self.serch_groupbox = QGroupBox()
self.serch_groupbox.setTitle("Article 2")
lable = QLabel("~")
lable.setFixedWidth(10)
lable.setAlignment(Qt.AlignCenter)
insertbutton = QPushButton("insert")
self.startdate = QDateEdit()
self.startdate.setDate(QDate.currentDate())
self.startdate.setFixedWidth(150)
self.startdate.setCalendarPopup(True)
self.enddate = QDateEdit()
self.enddate.setDate(QDate.currentDate())
self.enddate.setFixedWidth(150)
self.enddate.setCalendarPopup(True)
self.article_serch_button = QPushButton("ARTICL SERTCH")
self.article_serch_button.setFixedWidth(250)
self.serch_table = QTableView()
h1 = QHBoxLayout()
h1.addWidget(insertbutton)
h1.addWidget(self.startdate)
h1.addWidget(lable)
h1.addWidget(self.enddate)
h1.addWidget(self.article_serch_button)
h2 = QHBoxLayout()
h2.addWidget(self.serch_table)
v = QVBoxLayout()
v.addLayout(h1)
v.addLayout(h2)
self.serch_groupbox.setLayout(v)
def table1_DoubleClicked(self):
self.k =QItemSelectionModel().Select
def table1_CustomContextMenu(self, position):
menu = QMenu()
menu.addAction("Add")
menu.exec_(self.select_table.mapToGlobal(position))
print("?")
def Layout(self):
self.vbox = QVBoxLayout()
self.vbox.addWidget(self.SerchLineEdit)
self.vbox.addWidget(self.select_guorpbox)
self.vbox.addWidget(self.serch_groupbox)
self.setLayout(self.vbox)
if __name__ == "__main__":
app = QApplication(sys.argv)
fream = MainWindow()
fream.show()
app.exec_()
You could try the following, it copies the selected row from one table to the other:
def table1_DoubleClicked(self, index):
rows = []
row = []
for column_index in range(self.columncount):
cell_idx = self.select_table.model().index(index.row(), column_index)
row.append(self.select_table.model().data(cell_idx))
rows.append(row)
search_table_model = QStandardItemModel(len(rows), self.columncount)
for i in range(len(rows)):
for j in range(self.columncount):
search_table_model.setItem(i, j, QStandardItem(rows[i][j]))
self.serch_table.setModel(search_table_model)
The code below creates a single QTableView and QPushButton. When the button is clicked I would like to toggle the current selection (inverse it): what used to be selected is now deselected and what used to be deselected is selected.
Finally I would like to remove (delete) the rows that are now selected leaving only those that are deselected.
Question: How to achieve it?
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
app = QApplication([])
class Dialog(QDialog):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
self.setLayout(QVBoxLayout())
self.view = QTableView(self)
self.view.setSelectionBehavior(QTableWidget.SelectRows)
self.view.setSortingEnabled(True)
self.view.sortByColumn(0, Qt.DescendingOrder)
self.view.setModel(QStandardItemModel(4, 4))
for each in [(row, col, QStandardItem('item %s_%s' % (row, col))) for row in range(4) for col in range(4)]:
self.view.model().setItem(*each)
self.layout().addWidget(self.view)
btn1 = QPushButton('Invert selection then remove what selected')
btn1.clicked.connect(self.invertSelectionRemoveSelected)
self.layout().addWidget(btn1)
self.resize(500, 250)
self.show()
def invertSelectionRemoveSelected(self):
print 'invertSelectionRemoveSelected'
dialog = Dialog()
app.exec_()
You have to iterate to get the QModelIndex associated with each cell, and use the QItemSelection to invert the selection of each cell.
def invertSelectionRemoveSelected(self):
model = self.view.model()
for i in range(model.rowCount()):
for j in range(model.columnCount()):
ix = model.index(i, j)
self.view.selectionModel().select(ix, QItemSelectionModel.Toggle)
# delete rows
for ix in reversed(self.view.selectionModel().selectedRows()):
model.removeRow(ix.row())
Another Solution:
From your request I understand that you want to eliminate the unselected rows, and deselect all the others afterwards. So the next solution does it directly.
def invertSelectionRemoveSelected(self):
model = self.view.model()
rows_selected =[ix.row() for ix in self.view.selectionModel().selectedRows()]
[model.removeRow(i) for i in reversed(range(model.rowCount())) if i not in rows_selected]
self.view.clearSelection()
Note: #eyllanesc's answer is shorter, here
Before deleting selected lines we should know the indexes of them. As you may guess, deleting an item changes others indexes.
def invertSelectionRemoveSelected(self):
#from #eyllanesc's answer, inverse selected items
model = self.view.model()
for i in range(model.rowCount()):
for j in range(model.columnCount()):
ix = model.index(i, j)
self.view.selectionModel().select(ix, QItemSelectionModel.Toggle)
#delete selected items
index_list = []
for model_index in self.view.selectionModel().selectedRows():
index = QPersistentModelIndex(model_index)
index_list.append(index)
for index in index_list:
model.removeRow(index.row())
what I want to do is to change the color of a QTableWidget item, when I hover with the mouse over the item of my QTableWidget.
Firstly, the table widget needs to have mouse-tracking switched on to get the hover events.
Secondly, we need to find some signals that tell us when the mouse enters and leaves the table cells, so that the background colours can be changed at the right times.
The QTableWidget class has the cellEntered / itemEntered signals, but there is nothing for when the mouse leaves a cell. So, we will need to create some custom signals to do that.
The TableWidget class in the demo script below sets up the necessary cellExited / itemExited signals, and then shows how everything can be hooked up to change the item background when hovering with the mouse:
from PyQt4 import QtGui, QtCore
class TableWidget(QtGui.QTableWidget):
cellExited = QtCore.pyqtSignal(int, int)
itemExited = QtCore.pyqtSignal(QtGui.QTableWidgetItem)
def __init__(self, rows, columns, parent=None):
QtGui.QTableWidget.__init__(self, rows, columns, parent)
self._last_index = QtCore.QPersistentModelIndex()
self.viewport().installEventFilter(self)
def eventFilter(self, widget, event):
if widget is self.viewport():
index = self._last_index
if event.type() == QtCore.QEvent.MouseMove:
index = self.indexAt(event.pos())
elif event.type() == QtCore.QEvent.Leave:
index = QtCore.QModelIndex()
if index != self._last_index:
row = self._last_index.row()
column = self._last_index.column()
item = self.item(row, column)
if item is not None:
self.itemExited.emit(item)
self.cellExited.emit(row, column)
self._last_index = QtCore.QPersistentModelIndex(index)
return QtGui.QTableWidget.eventFilter(self, widget, event)
class Window(QtGui.QWidget):
def __init__(self, rows, columns):
QtGui.QWidget.__init__(self)
self.table = TableWidget(rows, columns, self)
for column in range(columns):
for row in range(rows):
item = QtGui.QTableWidgetItem('Text%d' % row)
self.table.setItem(row, column, item)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.table)
self.table.setMouseTracking(True)
self.table.itemEntered.connect(self.handleItemEntered)
self.table.itemExited.connect(self.handleItemExited)
def handleItemEntered(self, item):
item.setBackground(QtGui.QColor('moccasin'))
def handleItemExited(self, item):
item.setBackground(QtGui.QTableWidgetItem().background())
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window(6, 3)
window.setGeometry(500, 300, 350, 250)
window.show()
sys.exit(app.exec_())
You can achieve your goal pretty easily using the proper signals as proved by the following simple code:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class TableViewer(QMainWindow):
def __init__(self, parent=None):
super(TableViewer, self).__init__(parent)
self.table = QTableWidget(3, 3)
for row in range (0,3):
for column in range(0,3):
item = QTableWidgetItem("This is cell {} {}".format(row+1, column+1))
self.table.setItem(row, column, item)
self.setCentralWidget(self.table)
self.table.setMouseTracking(True)
self.current_hover = [0, 0]
self.table.cellEntered.connect(self.cellHover)
def cellHover(self, row, column):
item = self.table.item(row, column)
old_item = self.table.item(self.current_hover[0], self.current_hover[1])
if self.current_hover != [row,column]:
old_item.setBackground(QBrush(QColor('white')))
item.setBackground(QBrush(QColor('yellow')))
self.current_hover = [row, column]
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
tv = TableViewer()
tv.show()
sys.exit(app.exec_())
You may be interested in other signals too, especially itemEntered. However, if you want total control over the editing and display of items then using delegates (via the QTableWidget.setItemDelegate method) is strongly recommended.
UPDATE:
sorry, I had forgotten the second part of the problem i.e. what happens when the mouse exits a cell. Even then the problem can be solved easily without using events. See the updated code, please.
There are no events based on QTableWidgetItem, but you can do this:
reimplement the mouseMoveEvent() of QTableWidget, you can get the mouse position;
use itemAt() method to get the item under your mouse cursor;
customize your item;
This may simalute what you want.
I know this is old but wanted to update a couple of parts to it as I came across this page looking for a similar solution. This has a couple of parts to it, one is similar to the above but avoids the NoneType error if the cell is empty. Additionally, it will change the color of the highlighted cell, but also update a tooltip for the cell to display the contents of the cell in a tooltip. Nice if you have cells with runoffs 123...
Sure it could be cleaned up a bit, but works for PyQt5. Cheers!
def cellHover(self, row, column):
item = self.My_Table1.item(row, column)
old_item = self.My_Table1.item(self.current_hover[0], self.current_hover[1])
if item is not None:
if self.current_hover != [row,column]:
text = item.text()
if text is not None:
self.My_Table1.setToolTip(text)
item.setBackground(QBrush(QColor('#bbd9f7')))
old_item.setBackground(QBrush(QColor('white')))
self.current_hover = [row, column]