I have a table view, in which each cell has a custom text color. When the row is selected, the color is changed to the default highlightedText value (white). How can I keep the defined ForegroundRole assigned colour, when the row is selected too? Thanks for your help.
You can probably use a custom delegate to achieve this.
Try this with hints from here.
from PyQt4 import QtGui, QtCore
import sys
class CustomSelectColorDelegate(QtGui.QStyledItemDelegate):
def __init__(self, parent = None):
super(CustomSelectColorDelegate, self).__init__(parent)
def paint(self, painter, option, index):
painter.save()
# set background color
if option.state & QtGui.QStyle.State_Selected:
painter.setBrush(QtGui.QBrush(QtCore.Qt.white))
else:
painter.setBrush(QtGui.QBrush(QtCore.Qt.red))
painter.drawRect(option.rect)
# set text color
if option.state & QtGui.QStyle.State_Selected:
painter.setPen(QtGui.QPen(QtCore.Qt.red))
else:
painter.setPen(QtGui.QPen(QtCore.Qt.white))
value = index.data(QtCore.Qt.DisplayRole)
if value.isValid():
text = value.toString()
painter.drawText(option.rect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignCenter, text)
painter.restore()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mw = QtGui.QWidget()
tableViewer0 = QtGui.QTableWidget()
newitem1 = QtGui.QTableWidgetItem('this is standard text')
newitem2 = QtGui.QTableWidgetItem('this is custom text')
tableViewer0.setColumnCount(2)
tableViewer0.insertRow(0)
tableViewer0.setItem(0, 0, newitem1)
tableViewer0.setItem(0, 1, newitem2)
customSelectColorDelegate = CustomSelectColorDelegate()
tableViewer0.setItemDelegateForColumn(1, customSelectColorDelegate)
vbox = QtGui.QVBoxLayout()
vbox.addWidget(tableViewer0)
mw.setLayout(vbox)
mw.show()
sys.exit(app.exec_())
Related
I did a custom QFileDialog in other to add a label and a check box to it. It works the way I want but the problem is that when you modify this class the style changes since we aren't calling the static method. I'm doing this in a larger project and this change in style isn't aesthetically pleasing to the user, compared to how the whole app looks. Is there a way to style it? I'm sure the style changes due to this options=QtWidgets.QFileDialog.DontUseNativeDialog, but if I take it out, the code won't work. Below is a sample of the code
import sys
from PyQt5 import QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
button = QtWidgets.QPushButton("Open")
button.clicked.connect(self.OpenFile)
button.setStyleSheet('background-color: rgba(53, 53, 53,50)')
save_button = QtWidgets.QPushButton("Save")
# save_button.clicked.connect(self.savingFile)
save_button.setStyleSheet('background-color: rgba(53, 53, 53,50)')
hlayout = QtWidgets.QHBoxLayout()
hlayout.addWidget(button)
hlayout.addWidget(save_button)
lay = QtWidgets.QVBoxLayout(self)
lay.addLayout(hlayout)
def OpenFile(self):
dialog = QtWidgets.QFileDialog(
self,
"Open File",
"",
"Image Files (*.dcm *.DCM *.tif *.tiff *.TIF "
"*.TIFF *.oct *.OCT);;All Files (*)",
supportedSchemes=["file"],
options=QtWidgets.QFileDialog.DontUseNativeDialog,
)
checkBox = QtWidgets.QCheckBox()
labelWidget = QtWidgets.QLabel()
labelWidget.setText("Repeated Frame Averaging")
dialog.layout().addWidget(labelWidget)
dialog.layout().addWidget(checkBox)
dialog.setOption(QtWidgets.QFileDialog.DontUseNativeDialog, True)
if dialog.exec_() == QtWidgets.QDialog.Accepted:
filename = dialog.selectedFiles()[0]
cbSelection = checkBox.isChecked()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
I have QPixmaps inside cells of QTreeView using QStandardItemModel. I would like to be able to scale QPixmap when column is scaled (matching colum/cell width while keeping aspect ratio).
I can use scaled() method on QPixmap, but then I would have to figure out a trigger signal on column width change. Is there any simple and straightforward method ?
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.resize(400, 300)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.treeView = QtWidgets.QTreeView(self.centralwidget)
self.verticalLayout.addWidget(self.treeView)
MainWindow.setCentralWidget(self.centralwidget)
self.my_tree_model = MYTreeModel()
self.treeView.setModel(self.my_tree_model)
MainWindow.setCentralWidget(self.centralwidget)
class MYTreeModel(QtGui.QStandardItemModel):
def __init__(self):
super().__init__()
columns = ['data', 'thumbnail', 'data', 'data']
for row_id in range(3):
qrow_data = []
for col_id, column in enumerate(columns):
content = {}
if column == 'thumbnail':
image = QtGui.QImage('monkey.png')
item = QtGui.QStandardItem()
pxmap = QtGui.QPixmap.fromImage(image).scaled(50,50, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
item.setData(QtCore.QVariant(pxmap), QtCore.Qt.DecorationRole)
qrow_data.append(item)
else:
key = "data{0:s}{1:s}".format(str(row_id), str(col_id))
content[key] = QtGui.QStandardItem('my_data')
qrow_data.append(content[key])
self.appendRow(qrow_data)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication([])
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
One possible solution is to use a delegate:
class ThumbnailDelegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
option.icon = QtGui.QIcon()
option.features &= ~QtWidgets.QStyleOptionViewItem.HasDecoration
def paint(self, painter, option, index):
super().paint(painter, option, index)
mode = QtGui.QIcon.Normal
if not (option.state & QtWidgets.QStyle.State_Enabled):
mode = QtGui.QIcon.Disabled
elif option.state & QtWidgets.QStyle.State_Selected:
mode = QtGui.QIcon.Selected
state = (
QtGui.QIcon.On
if option.state & QtWidgets.QStyle.State_Open
else QtGui.QIcon.Off
)
pixmap = index.data(QtCore.Qt.DecorationRole).scaled(
option.rect.size(),
QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation,
)
icon = QtGui.QIcon(pixmap)
pixmap = icon.pixmap(pixmap.size(), mode, state)
painter.drawPixmap(option.rect, pixmap)
def sizeHint(self, option, index):
return index.data(QtCore.Qt.DecorationRole).size()
delegate = ThumbnailDelegate(self.treeView)
self.treeView.setItemDelegateForColumn(1, delegate)
Example images:
Below is my example code:
from PyQt5 import QtCore, QtGui, QtWidgets, QtSql
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtSql import *
from PyQt5 import uic
import sys
import sqlite3
class UI(QMainWindow):
def __init__(self):
super(UI, self).__init__()
uic.loadUi("tableview.ui", self)
self.show()
db = QSqlDatabase.addDatabase('QSQLITE')
db.setDatabaseName('book.db')
db.open()
self.model = QtSql.QSqlTableModel(self)
self.model.setTable("card")
self.model.select()
self.tableView.setModel(self.model)
#self.view_msa()
self.pushButton.clicked.connect(self.edit_items)
def edit_view(self):
self.editWindow = QtWidgets.QWidget()
self.editWindow.resize(300, 300)
self.editWindow.setWindowTitle("Edit Window")
self.editWindow.setWindowModality(Qt.ApplicationModal)
self.update = QtWidgets.QPushButton(self.editWindow)
self.update.setGeometry(QtCore.QRect(60, 150, 75, 23))
self.update.setObjectName("update")
self.widget = QtWidgets.QWidget(self.editWindow)
self.widget.setGeometry(QtCore.QRect(60, 60, 201, 74))
self.widget.setObjectName("widget")
self.formLayout = QtWidgets.QFormLayout(self.widget)
self.formLayout.setContentsMargins(0, 0, 0, 0)
self.formLayout.setObjectName("formLayout")
self.label = QtWidgets.QLabel(self.widget)
self.label.setObjectName("label")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label)
self.name_line = QtWidgets.QLineEdit(self.widget)
self.name_line.setObjectName("name_line")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.name_line)
self.label_2 = QtWidgets.QLabel(self.widget)
self.label_2.setObjectName("label_2")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_2)
self.age_line = QtWidgets.QLineEdit(self.widget)
self.age_line.setObjectName("age_line")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.age_line)
self.label_3 = QtWidgets.QLabel(self.widget)
self.label_3.setObjectName("label_3")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_3)
self.address_line = QtWidgets.QLineEdit(self.widget)
self.address_line.setObjectName("address_line")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.address_line)
self.update.setText("Update")
self.label.setText("Name")
self.label_2.setText("Age")
self.label_3.setText("Address")
self.name_line.setReadOnly(True)
self.editWindow.show()
def edit_items(self):
index = (self.tableView.selectionModel().currentIndex())
id_us = (self.tableView.model().data(index))
print(str(id_us))
self.edit_view()
app = QApplication(sys.argv)
window = UI()
app.exec_()
I am using: self.model = QtSql.QSqlTableModel, Qtableview, 3 Lineedit input widgets and 2 QPushbuttons. My SQLite3 database have 3 columns that are " name, age(int values) and gender "
My Question is:
How is it possible to do that if i select a row from tableview that 3 items should be copy into their respective lineedit input widgets as name, age and address. The name Editline is read-only, Then again if the update button is pressed, the QSqlTableModel should be updated with the reference of name lineedit(the name lineedit is read-only).
You need to access the individual indexes for each column you're interested into. This can be done by calling index.sibling or via a simple model.index(row, column) as soon as you know the current row.
Then, you can use a QDialog, which is better than a QWidget with the window modality set, as you can use its exec_() method to "block" the function until the dialog is closed.
In the following example I've connected the update button to the dialog.accept() slot, in order to update the data only when the button is clicked, otherwise if the dialog is cancelled (by pressing Esc or closing it) no change is applied.
As you can see, I didn't set any instance attribute (as you did with self.editWindow, etc), as their reference is only important within the function scope, and since you're going to recreate the dialog each time there's no use in setting them as attributes.
class UI(QMainWindow):
def __init__(self):
# ...
self.pushButton.clicked.connect(self.edit_items)
def edit_items(self):
if not self.model.rowCount():
return
index = self.tableView.currentIndex()
if index.isValid():
row = index.row()
else:
row = 0
dialog = QtWidgets.QDialog()
dialog.setWindowTitle("Edit Window")
layout = QtWidgets.QVBoxLayout(dialog)
formLayout = QtWidgets.QFormLayout()
layout.addLayout(formLayout)
name_line = QtWidgets.QLineEdit(self.model.index(row, 0).data())
formLayout.addRow('Name', name_line)
name_line.setReadOnly(True)
age_edit = QtWidgets.QSpinBox()
formLayout.addRow('Age', age_edit)
age_edit.setFocus()
age_edit.setValue(self.model.index(row, 1).data())
genders = 'M', 'F'
gender_combo = QtWidgets.QComboBox()
formLayout.addRow('Gender', gender_combo)
gender_combo.addItems(genders)
gender = self.model.index(row, 2).data()
if gender and gender.upper() in genders:
gender_combo.setCurrentIndex(genders.index(gender.upper()))
else:
gender_combo.setCurrentIndex(-1)
updateButton = QtWidgets.QPushButton('Update')
layout.addWidget(updateButton)
updateButton.clicked.connect(dialog.accept)
if not dialog.exec_():
return
self.model.setData(self.model.index(row, 1), age_edit.value(),
QtCore.Qt.EditRole)
if gender_combo.currentIndex() >= 0:
self.model.setData(self.model.index(row, 2),
gender_combo.currentText(), QtCore.Qt.EditRole)
# submit all changes to the database
self.model.submitAll()
Note: avoid overwriting existing class attributes, as you did with self.update.
I have a program that dynamically creates tabs with buttons on them, when the user clicks button, I want it to give me the button_id (number that corresponds to the tab index).
I understand that you can do something like tabwidget.currentIndex() to get index of tab being used, but I don't want that as I will eventually have a method that iterates through the number of tabs and access each button without selecting the tabs as shown below.
for i in range(1,self.tabWidget.count()):
self.tabWidget.widget(i).stagematch.click()
For example:
If user clicks 'Clear Text' button on 'Tab 2' then I want it to give me the number 2 back.
How can I accomplish this without using the currentIndex() method for the tabs
Test code:
import sys
from PyQt5 import QtCore, QtWidgets
class TabPage(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
group = QtWidgets.QGroupBox('Monty Python')
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(group)
grid = QtWidgets.QGridLayout(group)
testbutton = QtWidgets.QPushButton('Clear Text')
grid.addWidget(testbutton, 2, 2)
testbutton.clicked.connect(self.tab_match)
#testbutton.clicked.connect(self.button_id)
def button_id(self):
sender = self.sender()
print(sender.text()) # Gives text of button, i'd like a number that corresponds to the tab# that called it
def tab_match(self,button_id):
#Do something with button ID here
pass
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.tabs = QtWidgets.QTabWidget()
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.tabs)
button = QtWidgets.QToolButton()
button.setToolTip('Add New Tab')
button.clicked.connect(self.addNewTab)
button.setIcon(self.style().standardIcon(
QtWidgets.QStyle.SP_DialogYesButton))
self.tabs.setCornerWidget(button, QtCore.Qt.TopRightCorner)
self.addNewTab()
def addNewTab(self):
text = 'Tab %d' % (self.tabs.count() + 1)
self.tabs.addTab(TabPage(self.tabs), text)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 100, 300, 200)
window.show()
sys.exit(app.exec_())
Try it:
import sys
from PyQt5 import QtCore, QtWidgets
class TabPage(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.parent = parent # +
self.button_id = 0 # +
group = QtWidgets.QGroupBox('Monty Python')
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(group)
grid = QtWidgets.QGridLayout(group)
testbutton = QtWidgets.QPushButton('Clear Text')
grid.addWidget(testbutton, 2, 2)
testbutton.clicked.connect(self.tab_match)
self.parent.currentChanged.connect(self.qtabwidget_currentchanged) # +
def tab_match(self):
#Do something with button ID here
print("\ndef tab_match: button_id-> {}".format(self.button_id)) # +
#QtCore.pyqtSlot(int)
def qtabwidget_currentchanged(self, index): # +
self.button_id = index
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.tabs = QtWidgets.QTabWidget()
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.tabs)
button = QtWidgets.QToolButton()
button.setToolTip('Add New Tab')
button.clicked.connect(self.addNewTab)
button.setIcon(self.style().standardIcon(
QtWidgets.QStyle.SP_DialogYesButton))
self.tabs.setCornerWidget(button, QtCore.Qt.TopRightCorner)
self.button_id = 0
self.addNewTab()
def addNewTab(self):
text = 'Tab %d' % (self.tabs.count() + 1)
self.tabs.addTab(TabPage(self.tabs), text)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 100, 300, 200)
window.show()
sys.exit(app.exec_())
With the sample code below (heavily influenced from here) the right-click context menu is not really aligned properly.
As can be seen in the screenshot, the resulting menu is above the mouse cursor quite a bit. I would expect the menu's top left corner to be exactly aligned with the mouse pointer.
Is there any way to adjust for this?
import re
import operator
import os
import sys
import sqlite3
import cookies
from PyQt4.QtCore import *
from PyQt4.QtGui import *
def main():
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
self.tabledata = [('apple', 'red', 'small'),
('apple', 'red', 'medium'),
('apple', 'green', 'small'),
('banana', 'yellow', 'large')]
self.header = ['fruit', 'color', 'size']
# create table
self.createTable()
# layout
layout = QVBoxLayout()
layout.addWidget(self.tv)
self.setLayout(layout)
def popup(self, pos):
for i in self.tv.selectionModel().selection().indexes():
print i.row(), i.column()
menu = QMenu()
quitAction = menu.addAction("Quit")
action = menu.exec_(self.mapToGlobal(pos))
if action == quitAction:
qApp.quit()
def createTable(self):
# create the view
self.tv = QTableView()
self.tv.setStyleSheet("gridline-color: rgb(191, 191, 191)")
self.tv.setContextMenuPolicy(Qt.CustomContextMenu)
self.tv.customContextMenuRequested.connect(self.popup)
# set the table model
tm = MyTableModel(self.tabledata, self.header, self)
self.tv.setModel(tm)
# set the minimum size
self.tv.setMinimumSize(400, 300)
# hide grid
self.tv.setShowGrid(True)
# set the font
font = QFont("Calibri (Body)", 12)
self.tv.setFont(font)
# hide vertical header
vh = self.tv.verticalHeader()
vh.setVisible(False)
# set horizontal header properties
hh = self.tv.horizontalHeader()
hh.setStretchLastSection(True)
# set column width to fit contents
self.tv.resizeColumnsToContents()
# set row height
nrows = len(self.tabledata)
for row in xrange(nrows):
self.tv.setRowHeight(row, 18)
# enable sorting
self.tv.setSortingEnabled(True)
return self.tv
class MyTableModel(QAbstractTableModel):
def __init__(self, datain, headerdata, parent=None, *args):
""" datain: a list of lists
headerdata: a list of strings
"""
QAbstractTableModel.__init__(self, parent, *args)
self.arraydata = datain
self.headerdata = headerdata
def rowCount(self, parent):
return len(self.arraydata)
def columnCount(self, parent):
return len(self.arraydata[0])
def data(self, index, role):
if not index.isValid():
return QVariant()
elif role != Qt.DisplayRole:
return QVariant()
return QVariant(self.arraydata[index.row()][index.column()])
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return QVariant(self.headerdata[col])
return QVariant()
def sort(self, Ncol, order):
"""Sort table by given column number.
"""
self.emit(SIGNAL("layoutAboutToBeChanged()"))
self.arraydata = sorted(self.arraydata, key=operator.itemgetter(Ncol))
if order == Qt.DescendingOrder:
self.arraydata.reverse()
self.emit(SIGNAL("layoutChanged()"))
if __name__ == "__main__":
main()
the position is in viewport coordinate, so if you are using
self.tableView.setContextMenuPolicy(Qt.CustomContextMenu)
so you don't have event passed to popup, you can do the following
action = menu.exec_(self.tableView.viewport().mapToGlobal(pos))
instead.
This was a bit tricky, but following the subclassing example in this wiki example and replacing
15 action = menu.exec_(self.mapToGlobal(event.pos()))
with
15 action = menu.exec_(event.globalPos())
will make the popup menu's top left corner match the mouse click exactly.
This will work for maximized/minified windows.
Menu will be generated at right-bottom position of mouse.
menu.exec_(self.mapToGlobal(self.mapFromGlobal(QtGui.QCursor.pos())))