I have a QTableView with QAbstractTableModel. I want to change row backgroundcolor when I click one cell. I know at least two methods that can change row backgroundcolor when clicking one cell. One is use delegate, and another is use setData method in QAbstractTable. But I have got none of them,,,oops. Here I tried using setData method in QAbstractTable to just change the selected cell backgroundcolor, but failed! Could you pls help me to correct my code in order to change a whole row color not just a cell. Anyway, changing cell color is not even ok! Much thanks! Code below
import sys
import typing
import numpy as np
import pandas as pd
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QMainWindow, \
QWidget, QTableView, QVBoxLayout
from PyQt5.QtCore import QAbstractTableModel, Qt, QModelIndex
class MyTableModel(QAbstractTableModel):
def __init__(self, data:pd.DataFrame):
super().__init__()
self._data = data
def data(self, index: QModelIndex, role: int = ...) -> typing.Any:
if role==Qt.DisplayRole:
value = str(self._data.iloc[index.row()][index.column()])
return value
def setData(self, index: QModelIndex, value: typing.Any, role: int = ...) -> bool:
if not index.isValid():
return False
else:
if role==Qt.BackgroundColorRole:
self.dataChanged.emit(index, index, [role])
return True
def rowCount(self, parent: QModelIndex = ...) -> int:
return self._data.shape[0]
def columnCount(self, parent: QModelIndex = ...) -> int:
return self._data.shape[1]
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.myTable = QTableView()
df = self.get_DataFrame_Data()
self.model = MyTableModel(df)
self.myTable.setModel(self.model)
self.myTable.clicked.connect(self.change_row_bgcolor)
hlayout = QVBoxLayout()
hlayout.addWidget(self.myTable)
dummy_widget = QWidget()
dummy_widget.setLayout(hlayout)
self.setCentralWidget(dummy_widget)
self.setFixedSize(600, 600)
def get_DataFrame_Data(self):
ndarray = np.random.randint(10, 50, (7, 3))
df = pd.DataFrame(data=ndarray, columns=['col1','col2','col3'])
return df
def change_row_bgcolor(self, index):
self.model.setData(index,Qt.red,Qt.BackgroundColorRole)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
Solved! Two ways to changed QTableView Row background color when user mouse clicking.
Use QStyledItemDelegate.
Subclass QStyledItemDelegate. You should set a class property (etc tableview's currentindex) which can be reset value from outside the class, by this, the delegate's default loop will compare the tableview's currentindex.Code:
class TableDelegate(QStyledItemDelegate):
select_index = None
def paint(self, painter: QtGui.QPainter, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex) -> None:
# option.state
row = index.row()
column = index.column()
select_row = self.select_index.row()
# self.initStyleOption(option,index)
if row == select_row:
# option.font.setItalic(True)
option.font.setStyle(QFont.StyleOblique)
bg = QColor(135, 206, 255)
painter.fillRect(option.rect, bg)
# painter.eraseRect(option.rect)
QStyledItemDelegate.paint(self, painter, option, index)
Use QAbstractTableModel.Also, you should set a class property, than the method data()'s default loop will compare with the class property(tableview's current index). and set backgroud color.Code:
class MyTableModel(QAbstractTableModel):
def __init__(self, data:pd.DataFrame):
super().__init__()
self._data = data
self.color_enabled = False
self.color_back = Qt.magenta
self.target_row = -1
def data(self, index: QModelIndex, role: int) -> typing.Any:
if role==Qt.DisplayRole:
# print(index.row())
value = str(self._data.iloc[index.row()][index.column()])
return value
if role == Qt.BackgroundRole and index.row()==self.target_row \
and self.color_enabled==True:
return QBrush(self.color_back)
And,,! there is another special problem that should emphasized here. When user click one cell, there is a default backgroud which I see in my computer is blue. If you want to whole row background color is same when clicking, you should do this after creating a QTableView:
self.myTable.setStyleSheet("QTableView::item:selected{"
"background:rgb(135, 206, 255)}")
This means,you set the selected cell bgcolor by QSS, and then, either when you use QAbstractTableModel' data() method or pain() method in QStyledItemDelege, you should set the same color. Then everything is ok!
Related
Im using PyQt5 to develop an MVC application using sqlite DB.
I have a custom model inheriting from QAbstractTableModel. For the view im using in QTableView.
In the custom model i have added function setdata() and flags() needed to make the table cells in the view editable.
But when i edit the cells in the QtableView ,they do not persist in the sqlite DB.
When creating the view i use setModel() function to link the view and model.
What i want is a view with an editable table which on edit persists in the view and updates the sqlite database as well.
eg If i edit first row for result1 column from 1.23 to 1.234,althought the View now shows 1.234, the sqlite DB shows 1.23 instead of 1.234 :-(
#musicamante Below is my code so far
from PyQt5 import QtCore, QtGui,QtWidgets
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
)
import sys, sqlite3
import pandas as pd
from PyQt5.QtCore import Qt
class TestUI():
def __init__(self):
self.db = sqlite3.connect("test.db")
data = pd.read_sql_query("SELECT * FROM Reports",self.db) # where Reports is the table name, which i have already created beforehand
self.model = TableModel(data)
def createView(self, title):
view = QtWidgets.QTableView(self.centralwidget)
view.setGeometry(QtCore.QRect(60, 130, 541, 301))
view.resizeColumnsToContents()
view.setObjectName("view")
view.setModel(self.model)
view.setWindowTitle(title)
return view
def setupUi(self, MainWindow):
MainWindow.resize(800, 480)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.createView("TableView")
MainWindow.setCentralWidget(self.centralwidget)
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
self.initializeModel()
def initializeModel(self):
self.setHeaderData(2, QtCore.Qt.Horizontal, "serialNo")
self.setHeaderData(3, QtCore.Qt.Horizontal, "timestamp")
self.setHeaderData(4, QtCore.Qt.Horizontal, "result1")
def data(self, index, role):
if index.isValid():
if role == Qt.DisplayRole or role == Qt.EditRole:
value = self._data.iloc[index.row(), index.column()]
return str(value)
if role == Qt.ForegroundRole or role == Qt.EditRole:
value = self._data.iloc[index.row(), index.column()]
if (
(isinstance(value, int) or isinstance(value, float))
and value > 0
):
return QtGui.QColor('red')
def rowCount(self, index):
return self._data.shape[0]
def columnCount(self, index):
return self._data.shape[1]
def headerData(self, section, orientation, role):
# section is the index of the column/row.
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return str(self._data.columns[section])
if orientation == Qt.Vertical:
return str(self._data.index[section])
def setData(self, index, value, role):
if role == Qt.EditRole:
self._data.iloc[index.row(), index.column()] = value
return True
def flags(self, index):
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
if __name__ == "__main__":
app = QApplication(sys.argv)
MainWindow = QMainWindow()
ui = TestUI()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
I want to cancel pan function from other button. So far, my understanding is that when I want to pan&zoom image, I will click 'Pan' button. If I would like to do other function, e.g. 'Mark' function (in my case), I have to click 'Pan' Button again, then click whatever button I want to do.
I have searched for solving this and found something like 'release_pan', 'button_release_event', but I don't understand how to implement them correctly.
To be clear, I want to cancel pan function from 'Mark' button, and here is my code.
import sys
import time
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Cursor
from matplotlib.backends.qt_compat import QtCore, QtWidgets
if QtCore.qVersion() >= "5.":
from matplotlib.backends.backend_qt5agg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
else:
from matplotlib.backends.backend_qt4agg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.coor = [0,0] #temporary user selection
self.cid = None
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
layout = QtWidgets.QVBoxLayout(self._main)
butt = QtWidgets.QHBoxLayout()
self.static_canvas = FigureCanvas(Figure(figsize=(5, 5), dpi=100))
self.addToolBar = NavigationToolbar(self.static_canvas, self)
self.addToolBar.hide()
self.home = QtWidgets.QPushButton('Home')
self.pan = QtWidgets.QPushButton('Pan')
self.mark = QtWidgets.QPushButton('Mark')
butt.addWidget(self.home)
butt.addWidget(self.pan)
butt.addWidget(self.mark)
layout.addLayout(butt)
layout.addWidget(self.static_canvas)
self._static_ax = self.static_canvas.figure.subplots()
self.tar = plt.imread(r'my_image.tif').copy()
self._static_ax.imshow(self.tar)
# Set cursor
self.cursor = Cursor(self._static_ax, horizOn=True, vertOn=True, useblit=True,
color = 'r', linewidth = 1)
#trigger zone
self.home.clicked.connect(self.Home)
self.pan.clicked.connect(self.Pan)
self.mark.clicked.connect(self.Mark)
def coor_onclick(self, event):
"""
This function will get coordination from click and plot it on canvas
"""
#check out-figure click
if event.xdata == None or event.ydata == None:
pass
else:
self.coor[0] = int(event.xdata)
self.coor[1] = int(event.ydata)
# print(self.coor)
#show line marking on canvas
tar = self.tar.copy()
#NOTE:: self.coor = [x,y] = [col, row]
# x = self.coor[0]
# y = self.coor[1]
#marking line
for r in range(tar.shape[1]):
for c in range(tar.shape[0]):
tar[self.coor[1], c] = [255, 0, 0]
tar[r, self.coor[0]] = [255, 0, 0]
#set final mark on canvas
self._static_ax.clear()
self._static_ax.imshow(tar)
self._static_ax.axis('off')
# Set cursor
self.cursor = Cursor(self._static_ax, horizOn=True, vertOn=True, useblit=True,
color = 'r', linewidth = 1)
self.static_canvas.draw()
def Home(self):
self.cid = self.static_canvas.mpl_connect('button_press_event', self.coor_onclick)
self.addToolBar.home()
def Pan(self):
if self.cid is None:
pass
else:
#disconnect to self.coor_onclick
self.static_canvas.mpl_disconnect(self.cid)
self.addToolBar.pan()
def Mark(self):
self.cid = self.static_canvas.mpl_connect('button_press_event', self.coor_onclick)
if __name__ == "__main__":
# Check whether there is already a running QApplication (e.g., if running
# from an IDE).
qapp = QtWidgets.QApplication.instance()
if not qapp:
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
app.activateWindow()
app.raise_()
qapp.exec_()
I have modified from matplotlib documentation.
Check the current mode of NavigationToolbar and if the mode is "PAN", set the mode off by calling pan() again (which will uncheck the action (check out the source code for more details.)).
FYI:
You can check the current mode of the NavigationToolbar by using NavigationToolbar.mode.name, currently there are two modes: "ZOOM" and "PAN".
In your code, change function Mark like this:
def Mark(self):
# if the current mode is Pan, set the mode off by unchecking it.
if self.nav_toolbar.mode.name == "PAN":
self.nav_toolbar.pan()
self.cid = self.static_canvas.mpl_connect(
'button_press_event', self.coor_onclick)
I have a simple QWidget that contains a frame and two labels. I want to use eventFilter to change QFrame background color on label hover. Can someone please check the below code and tell me why I can't change the QFrame background and if it is the correct way for doing it?
import sys
from PyQt5.QtWidgets import QWidget, QHBoxLayout, \
QGraphicsDropShadowEffect, QPushButton, QApplication, QComboBox, QFrame, QLabel
from PyQt5 import QtCore
class MainWindow(QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.layout = QHBoxLayout(self)
self.frame = QFrame(self)
self.setObjectName("frame")
self.frame_lay = QHBoxLayout()
self.one_label = QLabel(self.frame)
self.one_label.setText("one")
self.one_label.setObjectName("one")
self.two_label = QLabel(self.frame)
self.two_label.setText("two")
self.two_label.setObjectName("two")
self.one_label.installEventFilter(self)
self.two_label.installEventFilter(self)
self.frame_lay.addWidget(self.one_label)
self.frame_lay.addWidget(self.two_label)
self.frame.setStyleSheet("""QFrame{background-color: red;}""")
self.frame.setLayout(self.frame_lay)
self.frame_lay.setContentsMargins(0, 0, 0, 0)
self.layout.addWidget(self.frame)
self.setLayout(self.layout)
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.Enter:
if type(obj) == QLabel:
if obj.objectName() in ["one", "two"]:
print(obj.objectName())
self.frame.setStyleSheet("""QFrame#frame{background-color: blue;}""")
return super(QWidget, self).eventFilter(obj, event)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Should the installEventFilter be applied to QWidget or QFrame? The labels are contained within the QFrame.
Thanks
You set the frame object name for the "MainWindow", but in the event filter you used the object name for a QFrame class.
Just set the object name for the frame instead:
self.frame.setObjectName("frame")
Note that QLabel inherits from QFrame, so, using QFrame{background-color: red;} technically applies the background for both the parent frame and any child label.
In case you want to be more specific, you either use the object name as you did in the event filter, or use the .ClassName selector, which applies the sheet only to the class and not its subclasses (note the full stop character before QFrame):
self.frame.setStyleSheet(""".QFrame{background-color: red;}""")
I am not able to emit dataChanged signal from my model. I am under python 3.5.2, PyQt5.9.1.
I tried at least 4 different syntaxes, none of this works for me: different veiws of this model are only updated when I click on them...
#pyqtSlot()
def setData(self, index: QModelIndex, Any, role=None):
if role == QtCore.Qt.EditRole:
row = index.row()
color = QtGui.QColor(Any)
if color.isValid():
self._datas[row] = color
# self.dataChanged.emit(index,index) # doesn't work because PyQt5 changed signature
# self.dataChanged.emit(index, index, []) # doesn't update other views of the same model
# self.dataChanged.emit(index,index,[QtCore.Qt.EditRole,]) # neither
# self.data_changed.emit(index,index) # class method is 'data_changed = pyqtSignal(QModelIndex,QModelIndex)', doesn't work
return True
return False
This question How to emit dataChanged in PyQt5 is marked as solved, however, I am not able to reproduce
EDIT:
A Verifiable exemple, with several views of the same model. I am expecting all views to be updated, whenever I change the color
EDIT_2 solved... Just a typo... This exemple works as expected
from PyQt5 import QtGui, QtCore
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QModelIndex, pyqtSignal,QAbstractListModel, pyqtSlot
import sys
class listModel(QAbstractListModel):
def __init__(self, colors=None):
super(QAbstractListModel, self).__init__()
self._datas = colors
def data(self, index: QModelIndex, role=None):
row = index.row()
value = self._datas[row]
if role == QtCore.Qt.DisplayRole:
return value.name()
elif role == QtCore.Qt.DecorationRole:
pixmap = QtGui.QPixmap(12,12)
pixmap.fill(value)
icon = QtGui.QPixmap(pixmap)
return icon
elif role == QtCore.Qt.ToolTipRole:
return "Hex code: " + self._datas[row].name()
def rowCount(self, parent=None, *args, **kwargs):
return len(self._datas)
def headerData(self, p_int, Qt_Orientation, role=None):
if role == QtCore.Qt.DisplayRole:
if Qt_Orientation == QtCore.Qt.Horizontal:
return "Palette"
else:
return "Color {a}".format(a=p_int)
def flags(self, QModelIndex: QModelIndex):
# check state editable or not?
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
#pyqtSlot()
def setData(self, QModelIndex, Any, role=None):
if role == QtCore.Qt.EditRole:
row = QModelIndex.row()
color = QtGui.QColor(Any)
if color.isValid():
self._datas[row] = color
self.dataChanged.emit(QModelIndex, QModelIndex, [])
return True
return False
if __name__ == '__main__':
app = QApplication(sys.argv)
red = QtGui.QColor(255,0,0)
green = QtGui.QColor(0, 255, 0)
blue = QtGui.QColor(0, 0, 255)
colors = [red,green,blue]
model = listModel(colors)
listView = QtWidgets.QListView()
listView.setModel(model)
listView.setWindowTitle('list')
listView.show()
treeV = QtWidgets.QTreeView()
treeV.setModel(model)
treeV.setWindowTitle('tree')
treeV.show()
tableV = QtWidgets.QTableView()
tableV.setModel(model)
tableV.setWindowTitle('table')
tableV.show()
sys.exit(app.exec_())
self.dataChanged.emit(index, index, [QtCore.Qt.EditRole]) is correct. There must be a bug elsewhere in your code.
In following code, when the button is clicked, I insert the plots into the tabs of an auinotebook in another frame.
for example, When I have multiple plots in the plt window, I can drag a notebook tab into the bottom (that results in displaying two plots). Later on when I delete the bottom tab, and try to go into other plots, I see a flicker like the closed tab is still there.
I guess the issue is with my on_nb_tab_close. Because, without that I was not able to notice any such problem.
I appreciate help. Code samples will be very useful. (wxpython version 2.812)
import wx
import wx.lib.agw.aui as aui
import matplotlib as mpl
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as mplCanvas
def create_plotter(self):
try:
self.plotter.Show()
except AttributeError:
self.plotter =PlotFrame(self, 500, 500)
self.plotter.Show()
return self.plotter
class PlotFrame(wx.Frame):
def __init__(self, parent, height, width):
wx.Frame.__init__(self, None, size=(height,width), title="plts")
self.parent=parent
self.nb = aui.AuiNotebook(self)
self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.on_nb_tab_close, self.nb)
def AddPlotTab(self,name="plot"):
page = Plot(self.nb)
self.nb.AddPage(page,name)
return page
def on_nb_tab_close(self, evt):
print "tab close fired"
s=self.nb.GetSelection()
v=self.nb.RemovePage(s)
if not self.nb.GetPageCount():
self.on_Close(evt)
evt.Veto()
class Plot(wx.Panel):
def __init__(self, parent, id = -1, dpi = None, **kwargs):
wx.Panel.__init__(self, parent, id=id, **kwargs)
self.figure = mpl.figure.Figure(dpi=dpi)
self.canvas = mplCanvas(self, -1, self.figure) # wxPanel object containing plot
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.canvas,1,wx.EXPAND)
self.SetSizer(sizer)
class MainFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title="Plotting test", size=(300, 300))
self.btn1 = wx.Button(self, -1, "Print 1")
self.Bind(wx.EVT_BUTTON, self.OnBtn1, self.btn1)
def OnBtn1(self, evt):
plotter=create_plotter(self)
page1 = plotter.AddPlotTab("case 1: first_plot")
page1.figure.gca().plot(range(10),range(10),'+')
page1.figure.gca().plot(range(10),range(10),'-',color='red')
page1.figure.canvas.draw()
if __name__ == '__main__':
APP = wx.App(False)
FRAME = MainFrame(None)
FRAME.Show()
APP.MainLoop()