In the following Python 2.7, PyQt4 example, I generate 2 QTableWidgets. Table1 has no ItemDelegate and the table2 has HTMLDelegate.
Selected background color works if the table has focus, but when the table loses focus, the blue selection turns gray on table2. I want table2 selection to work like table1 when focus is lost.
How can I maintain blue selection appearance regardless of focus when using itemdelegate?
import sip
sip.setapi('QVariant', 2)
from PyQt4 import QtCore, QtGui
import random
from html import escape
words = [
"Hello",
"world",
"Stack",
"Overflow",
"Hello world",
]
class HTMLDelegate(QtGui.QStyledItemDelegate):
def __init__(self, parent=None):
super(HTMLDelegate, self).__init__(parent)
self.doc = QtGui.QTextDocument(self)
def paint(self, painter, option, index):
col = index.column()
row = index.row()
painter.save()
options = QtGui.QStyleOptionViewItem(option)
self.initStyleOption(options, index)
text = index.data()
self.doc.setHtml(text)
options.text = ""
style = (
QtGui.QApplication.style()
)
style.drawControl(QtGui.QStyle.CE_ItemViewItem, options, painter)
ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
if option.state & QtGui.QStyle.State_Selected:
ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(QtGui.QPalette.Active, QtGui.QPalette.HighlightedText), )
else:
ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(QtGui.QPalette.Active, QtGui.QPalette.Text), )
textRect = (options.rect)
constant = 4
margin = (option.rect.height() - options.fontMetrics.height()) // 2
margin = margin - constant
textRect.setTop(textRect.top() + margin)
painter.translate(textRect.topLeft())
painter.setClipRect(textRect.translated(-textRect.topLeft()))
self.doc.documentLayout().draw(painter, ctx)
painter.restore()
def sizeHint(self, option, index):
return QSize(self.doc.idealWidth(), self.doc.size().height())
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
hlay = QtGui.QHBoxLayout()
lay = QtGui.QVBoxLayout(self)
self.table1 = QtGui.QTableWidget(4, 2)
self.table2 = QtGui.QTableWidget(4, 2)
lay.addLayout(hlay)
lay.addWidget(self.table1)
lay.addWidget(self.table2)
# define itemdelegate for table1, but not for table2
self.table2.setItemDelegate(HTMLDelegate(self.table2))
# fill table1
for i in range(self.table1.rowCount()):
for j in range(self.table1.columnCount()):
it = QtGui.QTableWidgetItem(random.choice(words))
self.table1.setItem(i, j, it)
# fill table2
for i in range(self.table2.rowCount()):
for j in range(self.table2.columnCount()):
it = QtGui.QTableWidgetItem(random.choice(words))
self.table2.setItem(i, j, it)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
app.setStyle("Plastique") # set style
stylesheet = """
QPushButton:hover, QComboBox:hover
{
background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #cbc9c5, stop: 1 #b9b7b5);
border: 2px solid #78879b;
border-radius: 3px;
}
QTableWidget::item:selected
{
background: #0078d7;
color: white;
}
QTableWidget
{
font: 9pt "Segoe UI";
}
QHeaderView
{
font: 9pt "Segoe UI";
}
"""
app.setStyleSheet(stylesheet)
myapp = Widget()
myapp.show()
rc = app.exec_()
myapp.close()
sys.exit(rc)
You're using QStyleOptionViewItem, which in Qt4 is a very basic class, while what you'll need is QStyleOptionViewItemV4, which implements a lot of useful things, including decorations (as in item icon) and their positioning, the item position according to other items, and, most importantly, the widget the delegate is used in and its full QStyle painting capabilities.
The style.drawControl method, like most of QStyle methods, also has a widget argument; it is normally ignored, but is very important in this kind of situations, expecially when there are stylesheets at play.
I suggest you to use the option's class of the paint() method as a reference to create your own option, which will automatically use the latest QStyleOption available for view item, and will also make a possible future transition to Qt5 much easier.
Keep in mind that, according to the documentation (which is somehow obscure about that), the widget property is available only from version 3 of QStyleOptionViewItem, but according to my tests the correct background painting will fail anyway with that version. If, for any reason, you're stuck with a very old version of Qt which doesn't provide QStyleOptionViewItemV4, I'm afraid that the only option you'll have is to keep the background color reference somewhere (there's no way to access to stylesheet colors from code, and it doesn't match the QTableView Highlight palette role) and manually paint the background by yourself.
def paint(self, painter, option, index):
#...
newOption = option.__class__(option)
self.initStyleOption(newOption, index)
#...
style = newOption.widget.style() if newOption.widget else QtGui.QApplication.style()
style.drawControl(QtGui.QStyle.CE_ItemViewItem, newOption, painter, newOption.widget)
# ...
PS: I'd suggest you to not use too similar names for objects: I actually lost sometime finding the source of the problem because I often got confused between "option" and "options": that's why I changed it, which is much better for readability and debugging purposes too.
Related
I am trying to override the paintEvent() of QMenu to make it have rounded corners.
The context menu should look something like this.
Here is the code I have tried But nothing appears:
from PyQt5 import QtWidgets, QtGui, QtCore
import sys
class Example(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Context menu')
self.show()
def contextMenuEvent(self, event):
cmenu = AddContextMenu(self)
newAct = cmenu.addAction("New")
openAct = cmenu.addAction("Open")
quitAct = cmenu.addAction("Quit")
action = cmenu.exec_(self.mapToGlobal(event.pos()))
class AddContextMenu(QtWidgets.QMenu):
def __init__(self, *args, **kwargs):
super(AddContextMenu, self).__init__()
self.painter = QtGui.QPainter(self)
self.setMinimumSize(150, 200)
self.pen = QtGui.QPen(QtCore.Qt.red)
#self.setStyleSheet('color:white; background:gray; border-radius:4px; border:2px solid white;')
def paintEvent(self, event) -> None:
self.pen.setWidth(2)
self.painter.setPen(self.pen)
self.painter.setBrush(QtGui.QBrush(QtCore.Qt.blue))
self.painter.drawRoundedRect(10, 10, 100, 100, 4.0, 4.0)
self.update()
#self.repaint()
#super(AddContextMenu, self).paintEvent(event)
def main():
app = QtWidgets.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Note: setting a style sheet doesn't work for me:
this is what I get when using the style sheet It isn't completely rounded.
This is the paintEvent after #musicamante suggestion(This is just for him/her to check)
def paintEvent(self, event) -> None:
painter = QtGui.QPainter(self)
#self.pen.setColor(QtCore.Qt.white)
#painter.setFont(QtGui.QFont("times", 22))
#painter.setPen(self.pen)
#painter.drawText(QtCore.QPointF(0, 0), 'Hello')
self.pen.setColor(QtCore.Qt.red)
painter.setPen(self.pen)
painter.setBrush(QtCore.Qt.gray)
painter.drawRoundedRect(self.rect(), 20.0, 20.0)
and in the init()
self.pen = QtGui.QPen(QtCore.Qt.red)
self.pen.setWidth(2)
I cannot comment on the paintEvent functionality, but it is possible to implement rounded corners using style-sheets. Some qmenu attributes have to be modified in order to disable the default rectangle in the background, which gave you the unwanted result.
Here is a modified version of your Example using style-sheets + custom flags (no frame + transparent background):
from PyQt5 import QtWidgets, QtCore
import sys
class Example(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Context menu')
self.show()
def contextMenuEvent(self, event):
cmenu = QtWidgets.QMenu()
# disable default frame and background
cmenu.setWindowFlags(QtCore.Qt.FramelessWindowHint)
cmenu.setAttribute(QtCore.Qt.WA_TranslucentBackground)
# set stylesheet, add some padding to avoid overlap of selection with rounded corner
cmenu.setStyleSheet("""
QMenu{
background-color: rgb(255, 255, 255);
border-radius: 20px;
}
QMenu::item {
background-color: transparent;
padding:3px 20px;
margin:5px 10px;
}
QMenu::item:selected { background-color: gray; }
""")
newAct = cmenu.addAction("New")
openAct = cmenu.addAction("Open")
quitAct = cmenu.addAction("Quit")
action = cmenu.exec_(self.mapToGlobal(event.pos()))
def main():
app = QtWidgets.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Setting the border radius in the stylesheet for a top level widget (a widget that has its own "window") is not enough.
While the solution proposed by Christian Karcher is fine, two important considerations are required:
The system must support compositing; while this is true for most modern OSes, at least on Linux there is the possibility that even an up-to-date system does not support it by choice (I disabled on my computer); if that's the case, setting the WA_TranslucentBackground attribute will not work.
The FramelessWindowHint should not be set on Linux, as it may lead to problems with the window manager, so it should be set only after ensuring that the OS requires it (Windows).
In light of that, using setMask() is the correct fix whenever compositing is not supported, and this has to happen within the resizeEvent(). Do note that masking is bitmap based, and antialiasing is not supported, so rounded borders are sometimes a bit ugly depending on the border radius.
Also, since you want custom colors, using stylesheets is mandatory, as custom painting of a QMenu is really hard to achieve.
class AddContextMenu(QtWidgets.QMenu):
def __init__(self, *args, **kwargs):
super(AddContextMenu, self).__init__()
self.setMinimumSize(150, 200)
self.radius = 4
self.setStyleSheet('''
QMenu {{
background: blue;
border: 2px solid red;
border-radius: {radius}px;
}}
QMenu::item {{
color: white;
}}
QMenu::item:selected {{
color: red;
}}
'''.format(radius=self.radius))
def resizeEvent(self, event):
path = QtGui.QPainterPath()
# the rectangle must be translated and adjusted by 1 pixel in order to
# correctly map the rounded shape
rect = QtCore.QRectF(self.rect()).adjusted(.5, .5, -1.5, -1.5)
path.addRoundedRect(rect, self.radius, self.radius)
# QRegion is bitmap based, so the returned QPolygonF (which uses float
# values must be transformed to an integer based QPolygon
region = QtGui.QRegion(path.toFillPolygon(QtGui.QTransform()).toPolygon())
self.setMask(region)
Some side notes about your paintEvent implementation, not necessary in this specific case for the above reason, but still important (some points are related to portions of code that have been commented, but the fact that you tried them makes worth mentioning those aspects):
The QPainter used for a widget must never be instanciated outside a paintEvent(): creating the instance in the __init__ as you did is a serious error and might even lead to crash. The painter can only be created when the paintEvent is received, and shall never be reused. This clearly makes useless to set it as an instance attribute (self.painter), since there's no actual reason to access it after the paint event.
If the pen width is always the same, then just set it in the constructor (self.pen = QtGui.QPen(QtCore.Qt.red, 2)), continuously setting it in the paintEvent is useless.
QPen and QBrush can directly accept Qt global colors, so there's no need to create a QBrush instance as the painter will automatically (internally and fastly) set it: self.painter.setBrush(QtCore.Qt.blue).
self.update() should never be called within a paintEvent (and not even self.repaint() should). The result in undefined and possibly dangerous.
If you do some manual painting with a QPainter and then call the super paintEvent, the result is most likely that everything painted before will be hidden; as a general rule, the base implementation should be called first, then any other custom painting should happen after (in this case it obviously won't work, as you'll be painting a filled rounded rect, making the menu items invisible).
I have implemented round corners menu using QListWidget and QWidget. You can download the code in https://github.com/zhiyiYo/PyQt-Fluent-Widgets/blob/master/examples/menu/demo.py.
My intention is to make a Hover effect for multiple frames at a time. For Example, Below is my code, I have three frames. Two inner frames and one outer frame. If the mouse enters where ever, that is, either in an outer frame or in the inner frames, I need a hover effect for three frames, at a time.
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class Dynamic_Widgets(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Dynamic Widget")
self.lbl = QLabel("this is text")
self.btn = QPushButton("Button")
self.lbl_1 = QLabel("-----------")
self.lbl_r = QLabel("My text ")
self.btn_r = QPushButton("Button888888")
self.lbl_1r = QLabel(('\u2261' * 60))
my_font_1 = QFont("Arial", 10, QFont.Bold)
my_font_1.setLetterSpacing(QFont.AbsoluteSpacing, -6)
self.lbl_1r.setFont(my_font_1)
self.vbox = QHBoxLayout()
self.frame = QFrame()
self.frame_right = QFrame()
self.frame_all = QFrame()
self.frame.setObjectName("ob_frame")
self.frame_right.setObjectName("ob_frame_right")
self.frame_all.setObjectName("ob_frame_all")
self.frame.setProperty("type", "2")
self.frame_right.setProperty("type", "2")
self.frame_all.setProperty("type", "2")
self.framebox = QVBoxLayout(self.frame)
self.framebox_right = QVBoxLayout(self.frame_right)
self.framebox_all = QHBoxLayout(self.frame_all)
self.framebox_all.setContentsMargins(0, 0, 0, 0)
self.framebox_all.setSpacing(0)
self.framebox_all.addWidget(self.frame)
self.framebox_all.addWidget(self.frame_right)
self.vbox.addWidget(self.frame_all)
self.setLayout(self.vbox)
self.frame.setFixedSize(100, 100)
self.frame_right.setFixedSize(100, 100)
self.frame_all.setFixedSize(220, 120)
self.frame.setStyleSheet(f"QFrame#ob_frame{{background-color: lightgreen;}}")
self.frame_right.setStyleSheet(f"QFrame#ob_frame_right{{background-color: lightgreen;}}")
self.frame_all.setStyleSheet(f"QFrame#ob_frame_all{{background-color: green;}}")
#self.frame_all.setStyleSheet('QFrame:hover { background: yellow; }')
self.framebox.addWidget(self.lbl)
self.framebox.addWidget(self.btn)
self.framebox.addWidget(self.lbl_1)
self.framebox_right.addWidget(self.lbl_r)
self.framebox_right.addWidget(self.btn_r)
self.framebox_right.addWidget(self.lbl_1r)
def main():
app = QApplication(sys.argv)
ex = Dynamic_Widgets()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Setting individual stylesheets can be useful for very specific and relatively static styling, but, in general, it's usually better to have a centralized QSS or, at least, different QSS set for common parents.
In normal situations, a single Class:hover selector would suffice, but if the children have properties that need to be overridden by a change in the parent, it is necessary to install an event filter on the parent.
Considering that the event filter will match Enter and Leave events, the hover selector doesn't make a lot of sense anymore: we can create a "default" style sheet stored as instance attribute and set/override it depending on those events:
class Dynamic_Widgets(QWidget):
def __init__(self):
# ...
self.frame_all_qss = '''
QFrame#ob_frame_all {
background-color: green;
}
QFrame#ob_frame {
background-color: lightgreen;
}
QFrame#ob_frame_right {
background-color: lightgreen;
}
'''
self.frame_all.setStyleSheet(self.frame_all_qss)
self.frame_all.installEventFilter(self)
def eventFilter(self, obj, event):
if obj == self.frame_all:
if event.type() == event.Enter:
res = obj.event(event)
self.frame_all.setStyleSheet(
'QFrame#ob_frame_all { background: yellow; }')
return res
elif event.type() == event.Leave:
res = obj.event(event)
self.frame_all.setStyleSheet(self.frame_all_qss)
return res
return super().eventFilter(obj, event)
I've found a way to do css based cell styling in a TableView based on contents in the cell. The following code shows an example:
#!/usr/bin/python3
from PyQt5 import QtWidgets, QtGui, QtCore
class_values = ["zero", "one", "two"]
class Cell(QtWidgets.QWidget):
def initFromItem(self, item):
self.setProperty('dataClass', class_values[int(item.text())])
class TDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, *a):
super(TDelegate, self).__init__(*a)
self.cell = Cell(self.parent())
def paint(self, painter, option, index):
item = index.model().itemFromIndex(index)
self.cell.initFromItem(item)
self.initStyleOption(option, index)
style = option.widget.style() if option.widget else QtWidgets.QApplication.style()
style.unpolish(self.cell)
style.polish(self.cell)
style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, option, painter, self.cell)
class TTableModel(QtGui.QStandardItemModel):
def __init__(self, parent=None):
super(TTableModel, self).__init__(parent)
for i in range(5):
self.appendRow([QtGui.QStandardItem(str((x+i) % 3)) for x in range(5)])
class TTableView(QtWidgets.QTableView):
def __init__(self, parent=None):
super(TTableView, self).__init__(parent)
self.setItemDelegate(TDelegate(self))
class Main(QtWidgets.QMainWindow):
def __init__(self):
super(Main, self).__init__()
self.table = TTableView(self)
self.model = TTableModel(self)
self.table.setModel(self.model)
self.setCentralWidget(self.table)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
app.setStyleSheet("""
Cell[dataClass=zero]::item { background-color: gray; }
Cell[dataClass=one]::item { background-color: green; font-style: italic }
Cell[dataClass=two]::item { font-weight: bold }
""")
mainWin = Main()
mainWin.show()
sys.exit(app.exec_())
This generates a table like this:
TableView with per cell styling
The problem is that while the colours work, the font styling has no effect. What am I doing wrong? How could I improve my code? And how does it work? For example, why does the CSS selector have to include the ::item. All answers gratefully received. But please bear in mind that the need for CSS based styling is essential to the project.
This is due to a bug in qt (v5.9.5) that ignores all font styling information when creating a CE_ItemViewItem (see QStyleSheetStyle::drawControl). Cheating by creating something else like a CE_ToolBoxTabLabel (which does correct handling of fonts in drawControl) does get you font formatting, but gets you on the colour because the rendering uses the button face palette, not the one specified in the option (or associated CSS). So you can have one or the other but not both. I know of no workaround.
As to how this works. In QStyleSheetStyle::drawControl for a CE_ItemViewItem the CSS for the subrole of ::item is looked up and if present, applied to a copy of the option (but not the font styling), and then the Item is drawn based on the updated option and its updated palette. Unfortunately there is no way to break into this code since there is no way to apply stylesheets from PyQt (since QStyleSheet is not part of the public API of Qt).
I have a bunch of widgets in a layout, and the layout is the child of a QFrame. This allows me to create a border around this layout. Now when any of the children receive focus, I would like to change the border color of the QFrame to indicate to the user that is where the focus currently is. How best to do this without subclassing the focuInEvent/focusOutEvent of every child with callbacks to the stylesheet of their parent widget (the QFrame)? When testing to focusInEvent of the QFrame I can never get it to trigger. Is there some sort of child focus event or something?
I think I came up with a pretty good solution for this after trying a few things out and learning a ton more about eventFilter's. Basically I found that you need to install an event filter in the parent and catch all focus events of the children. It's easier to show an example, this is a bit more complicated then it perhaps needs to be but it illustrates some important points:
import os
import sys
from PyQt4 import QtGui, QtCore
class BasePanel(QtGui.QWidget):
"""This is more or less abstract, subclass it for 'panels' in the main UI"""
def __init__(self, parent=None):
super(BasePanel, self).__init__(parent)
self.frame_layout = QtGui.QVBoxLayout()
self.frame_layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.frame_layout)
self.frame = QtGui.QFrame()
self.frame.setObjectName("base_frame")
self.frame.setFrameStyle(QtGui.QFrame.Box | QtGui.QFrame.Plain)
self.frame.setLineWidth(1)
self.frame_layout.addWidget(self.frame)
self.base_layout = QtGui.QVBoxLayout()
self.frame.setLayout(self.base_layout)
self.focus_in_color = "rgb(50, 255, 150)"
self.focus_out_color = "rgb(100, 100, 100)"
self.frame.setStyleSheet("#base_frame {border: 1px solid %s}" % self.focus_out_color)
self.installEventFilter(self) # this will catch focus events
self.install_filters()
def eventFilter(self, object, event):
if event.type() == QtCore.QEvent.FocusIn:
self.frame.setStyleSheet("#base_frame {border: 1px solid %s}" % self.focus_in_color)
elif event.type() == QtCore.QEvent.FocusOut:
self.frame.setStyleSheet("#base_frame {border: 1px solid %s}" % self.focus_out_color)
return False # passes this event to the child, i.e. does not block it from the child widgets
def install_filters(self):
# this will install the focus in/out event filter in all children of the panel
for widget in self.findChildren(QtGui.QWidget):
widget.installEventFilter(self)
class LeftPanel(BasePanel):
def __init__(self, parent=None):
super(LeftPanel, self).__init__(parent)
title = QtGui.QLabel("Left Panel")
title.setAlignment(QtCore.Qt.AlignCenter)
self.base_layout.addWidget(title)
edit = QtGui.QLineEdit()
self.base_layout.addWidget(edit)
class RightPanel(BasePanel):
def __init__(self, parent=None):
super(RightPanel, self).__init__(parent)
title = QtGui.QLabel("Right Panel")
title.setAlignment(QtCore.Qt.AlignCenter)
self.base_layout.addWidget(title)
edit = QtGui.QLineEdit()
self.base_layout.addWidget(edit)
class MainApp(QtGui.QMainWindow):
def __init__(self):
super(MainApp, self).__init__()
main_layout = QtGui.QHBoxLayout()
central_widget = QtGui.QWidget()
central_widget.setLayout(main_layout)
self.setCentralWidget(central_widget)
left_panel = LeftPanel()
main_layout.addWidget(left_panel)
right_panel = RightPanel()
main_layout.addWidget(right_panel)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
ex = MainApp()
ex.show()
sys.exit(app.exec_())
The only other answer (by Spencer) is using a sledgehammer to crack a nut. Unleash the power of CSS. Here's an extract from some code where I get the focused Qwidget to have a khaki background and the selected item (if applicable, e.g. in QTreeView) to have a dark kharki background. NB check out about 100 very useful colour names to use in a PyQt5 CSS context.
NB in what follows self is the QMainWindow object.
for widget in self.findChildren(QtWidgets.QWidget):
# exclude certain types from getting fancy CSS
if isinstance(widget, QtWidgets.QMenuBar) or isinstance(widget, QtWidgets.QScrollBar):
continue
widget.setStyleSheet("""
QWidget {background: azure;} # this is actually an off-white: see list above
QWidget::focus {background: khaki;} # background turns khaki only on focus!
# ... so obviously you can add some change to the border here too if you want
QWidget::item::focus {background: darkkhaki;}
""")
# NB this next stuff is not relevant to the "how to get a nice focus colouring" question,
# but just to illustrate some of the power and flexibility of CSS
self.ui.menubar.setStyleSheet('QWidget {border-bottom: 1px solid black;}')
# bolding and colour for an isolated element: note that you don't
# have to stipulate "QLabel {...}" unless it makes sense.
self.get_details_panel().ui.breadcrumbs_label.setStyleSheet('font-weight: bold; color: slategrey')
self.get_details_panel().setFrameStyle(QtWidgets.QFrame.Box)
# with this we identify the specific object to stop the style propagating to descendant objects
self.get_details_panel().setStyleSheet('QFrame#"details panel"{border-top: 1px solid black; }')
... NB in the last case the object ("details panel", a QFrame subclass) has been given an object name, which you can then use in CSS (i.e. in CSS terminology, its "id"):
self.setObjectName('details panel')
In the following code, I would like to use thicker rule separator between the column 2 and 3 for example. How can I achieve this ?
#! /usr/bin/env python2.7
# -*- coding: utf-8 -*-
import sys
from PySide import QtCore, QtGui
class MainWindow(QtGui.QWidget):
def __init__(
self,
parent = None
):
super(MainWindow, self).__init__(parent)
# General grid
self.table = QtGui.QTableWidget(self)
self.nbrow, self.nbcol = 9, 9
self.table.setRowCount(self.nbrow)
self.table.setColumnCount(self.nbcol)
# Each cell has dimension 50 pixels x 50 pixels
for row in range(0, self.nbrow):
self.table.setRowHeight(row, 50)
for col in range(0, self.nbcol):
self.table.setColumnWidth(col, 50)
# Each cell contains one single QTableWidgetItem
for row in range(0, self.nbrow):
for col in range(0, self.nbcol):
item = QtGui.QTableWidgetItem()
item.setTextAlignment(
QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter
)
self.table.setItem(row, col, item)
# Header formatting
font = QtGui.QFont()
font.setFamily(u"DejaVu Sans")
font.setPointSize(12)
self.table.horizontalHeader().setFont(font)
self.table.verticalHeader().setFont(font)
# Font used
font = QtGui.QFont()
font.setFamily(u"DejaVu Sans")
font.setPointSize(20)
self.table.setFont(font)
# Global Size
self.resize(60*9, 60*9 + 20)
# Layout of the table
layout = QtGui.QGridLayout()
layout.addWidget(self.table, 0, 0)
self.setLayout(layout)
# Set the focus in the first cell
self.table.setFocus()
self.table.setCurrentCell(0, 0)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
fen = MainWindow()
fen.show()
sys.exit(app.exec_())
You can use stylesheets to change the style of the cell borders. See examples:
http://www.qtcentre.org/threads/27195-Border-around-selected-cell-in-qtablewidget
Set QTableWidget cell's borders to 0px
This is not a complete or ideal solution, but it's somewhat relevant and might be helpful to making progress. I found an example of changing a whole cell's border in Qt and translated it to PyQt.
First, create a delegate and override its paint method to do the customization you want.
class TableBorderDelegate(QtGui.QItemDelegate):
"""Delegate for customizing cell borders"""
def paint(self, painter, option, index):
"""Overrides paint to put a red border around cells in column 3"""
if index.column() == 3: # index.row() is also a thing
painter.setPen(QtGui.QColor(255, 0, 0))
painter.drawRect(option.rect)
QtGui.QItemDelegate.paint(self, painter, option, index)
# End of class TableBorderDelegate
And then in your table
delegate = TableBorderDelegate()
self.table.setItemDelegate(delegate)
Where self.table is an instance of QtGui.QTableWidget()
The Qt solution came from https://stackoverflow.com/a/7264248/6605826 .
I found a pyqt example of delegate usage in https://github.com/baoboa/pyqt5/blob/master/examples/itemviews/spinboxdelegate.py An unrelated pyqt delegate