-1

(W10 platform)

What I'm trying to get (I was asked to produce a target outcome, with Gimp): what I want

What I currently get:

current result

MRE:

from PyQt5.QtCore import QRect, Qt, QAbstractTableModel
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableView, QWidget, QVBoxLayout, QStyledItemDelegate, \
    QPlainTextEdit, QShortcut
import sys, types
from PyQt5.QtGui import QFont, QBrush, QColor

class HistoryTableModel( QAbstractTableModel ):
    def __init__( self ):
        super(HistoryTableModel, self).__init__()
        data = [
              [4, 9, 2],
              [1, 0, 0],
              [3, 5, 0],
        ]
        self._data = data

    def data(self, index, role):
        if role == Qt.DisplayRole:
            return self._data[index.row()][index.column()]

    def rowCount(self, index):
        return len(self._data)

    def columnCount(self, index):
        return 2
    
    def flags(self, index):
        return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
    
    def setData(self, index, value, role ):
        if role == Qt.EditRole:
            self._data[ index.row() ][ index.column() ] = value  
        return True  
    
    def headerData(self, section, orientation, role):
        if orientation == Qt.Horizontal:
            print( f'headerData, horiz: section {section}, role {role}')
        
            if role == Qt.DisplayRole:
                return ( 'Date', 'Entry' )[section]
            elif role == Qt.ForegroundRole:
                # this has the desired effect: colours the font
                result = super().headerData( section, orientation, role )
                return QBrush( QColor( 'brown' ) ) 
            elif role == Qt.BackgroundRole:
                # this seems to have no effect
                result = super().headerData( section, orientation, role )
                return QBrush( QColor( 'yellow' ) ) 

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.resize(600, 700 )
        self.centralwidget = QWidget(MainWindow)
        self.verticalLayoutWidget = QWidget(self.centralwidget)
        self.verticalLayoutWidget.setGeometry( QRect(20, 20, 300, 300))
        self.verticalLayout = QVBoxLayout(self.verticalLayoutWidget)
        self.comps = []
        self.table_view = QTableView(self.verticalLayoutWidget)
        
        # this has an effect on the table data rows, but it's the header I want to format... 
        self.table_view.setStyleSheet( 'background: palegoldenrod; gridline-color: black;' )
        
#         # ATTEMPT 1
#         # this has an inexplicable effect: most of the window outside the table is given a white background;
#         # gridlines are unchanged 
#         self.table_view.setStyleSheet( """QTableHeader.section.horizontal {
#             color: red;
#             background: blue;
#             gridline-color: green;
#         }""" )
        
#           # ATTEMPT 2
#           # this puts a small corner of blue in the top right corner of the window;
#           # gridlines are unchanged 
#         self.table_view.horizontalHeader().setStyleSheet( """
#             color: red;
#             background: blue;
#             gridline-color: green;
#         """ )
        
#           # ATTEMPT 3
#           # this puts a small line of red in the top right corner of the window;
#         self.table_view.horizontalHeader().setStyleSheet( """
#             border-bottom:3px solid red;
#         """ )
        
        self.comps.append( self.table_view )
        self.table_view.setGeometry(QRect(20, 20, 200, 200))
        self.verticalLayout.addWidget(self.table_view)
        self.table_view.setModel( HistoryTableModel() )
        MainWindow.setCentralWidget(self.centralwidget)

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

app = QApplication(sys.argv)
application = MainWindow()
application.show()
sys.exit(app.exec())

What I'm trying to do here is 1) change the grid lines in the horizontal header; 2) put a solid line of some kind between the header and the data rows of the table. Uncomment my attempts if interested.

I'm not clear whether overriding functionality to do with ItemDataRole is the way to go here: I can't see any way that this functionality actually takes care of grid lines... Or whether style sheets should do this.

It seems that it may be significant that I'm working on a W10 machine (i.e. more understandable results may possibly be obtained in Linux/OS).

Image of attempt1: failed attempt 1

Image of attempt2: failed attempt 2

Image of attempt3: failed attempt 3

mike rodent
  • 14,126
  • 11
  • 103
  • 157
  • Your question is unclear, generally visual requirements are better explained with visual elements such as images, so considering the above it would be great if you put an image of what you get and what you want to obtain. – eyllanesc Nov 09 '20 at 18:49
  • On the other hand, I recommend that you avoid placing irrelevant information such as the hours you invested in trying to solve your problem since they do not contribute anything and only show your frustration, do you want us to get frustrated too? – eyllanesc Nov 09 '20 at 18:50
  • 1) We can all get frustrated, and seeing a post where the OP complains to me discourages me since I think that the OP's frustration will prevent healthy communication, so I always ask the OP not to show his feelings in the post but to just focus on the problem itself, 2) it's great if you show images of your failed attempts but it would be cooler if you **post an image of what you want to get**. – eyllanesc Nov 09 '20 at 19:04
  • It seems that I do not explain correctly: I would recommend that you take a screenshot of your GUI and use some image editing program(like gimp, photoshop) that allows you to show the GUI you want to obtain, so your request would be very clear. – eyllanesc Nov 09 '20 at 19:08
  • Furthermore. 1) As already suggested, you should **not** try to edit the files created with pyuic, nor try to mimic their behavior (as you're probably doing); those approaches are *highly* discouraged, as they might lead to lots of confusion and misconceptions. 2) Carefully read and study the documentation about [style sheet syntax](https://doc.qt.io/qt-5/stylesheet-syntax.html), their [reference page](https://doc.qt.io/qt-5/stylesheet-reference.html) (especially the parts about the pseudo-states and sub-controls), and analyze their [examples](https://doc.qt.io/qt-5/stylesheet-examples.html). – musicamante Nov 09 '20 at 19:34
  • @musicamante Thanks, I'll study those links. I haven't done any editing of pyuic files and I haven't tried to mimic anything except attempts by other people which I found online to solve this problem of formatting the header of QTreeView (all unsuccessful, on my OS anyway). I'm mainly just looking for an answer to a problem which shouldn't be that difficult: clearly control of grid lines display is possible in the main body of the table. Do you know how to do the same in the header by any chance? – mike rodent Nov 09 '20 at 19:44
  • @musicamente I've now read that style sheet syntax page and the reference page and had a look at the the examples. There is only one reference to gridlines, which I had already in fact applied to the main body of my table, as you can see from the MRE. Secondly, I can't see how pseudo states (as in conventional CSS) applies to the solution of this problem. Perhaps you could be more explicit if you can see a solution to the problem posed? – mike rodent Nov 09 '20 at 19:54
  • @mikerodent the `class Ui_MainWindow(object):` is taken from a pyuic generated file; when implementing an UI by code you should not use that approach. Grid lines are applied to the table contents, the header is a QHeaderView class and in its case, the important selector is `::section`. – musicamante Nov 09 '20 at 20:08
  • Oh, I see... yes, I just copy-pasted from a pyuic-generated file, quite true. What's wrong with doing that? Secondly, I just tried adjusting the stylesheet for `self.table_view` to try to select `QHeaderView`: "QHeaderView.section {..." and also "QHeaderView.section.horizontal {...". Neither of these worked. – mike rodent Nov 09 '20 at 20:15
  • About the pyuic issue: those files are created only as a convenience way for python to use `ui` files, their structure can lead to some confusion about objects/class/instance relation and parentship unless you **really** know how they work (and if you know how they work, you know you shouldn't edit them). Also, if you start to edit them to create programs you'll most certainly find yourself in a real mess as soon as you realize you have to modify the UI: merging the new pyuic generated file with the existing code will cause painful and very long headaches which could be avoided just by -> – musicamante Nov 09 '20 at 20:58
  • -> using them just as they are intended to be used: imported scripts that are not going to be manually modified and can be easily regenerated whenever you need with almost any issue. Read more about how to correctly deal with those files in the official guidelines about [using Designer](https://www.riverbankcomputing.com/static/Docs/PyQt5/designer.html). – musicamante Nov 09 '20 at 20:59
  • I think I may have some awareness of the issue: copy-pasting is one thing, but editing such a file would be bonkers. I've found this technique to be a godsend: `tree_view.add_row = types.MethodType( add_row, tree_view )` ... i.e. retrospectively engineering a created object to add methods to it. By the way, in the JavaFX equivalent to designer (SceneBuilder) you can import your own subclasses into the designer. At the current time I haven't got the faintest idea whether there's something similar in pyqt5 designer... – mike rodent Nov 09 '20 at 23:27

1 Answers1

1

What you're trying to style is the table header (a QHeaderView), not the table.

So, you should apply the stylesheet to it, and use the correct class::selector:state syntax.

table.horizontalHeader().setStyleSheet('''
    QHeaderView {
        /* set the bottom border of the header, in order to set a single 
           border you must declare a generic border first or set all other 
           borders */
        border: none;
        border-bottom: 2px solid green;
    }

    QHeaderView::section:horizontal {
        /* set the right border (as before, the overall border must be 
           declared), and a minimum height, otherwise it will default to 
           the minimum required height based on the contents (font and 
           decoration sizes) */
        border: none;
        border-right: 1px solid red;
        min-height: 28px;
    }
''')

Note that since in the example I've set the stylesheet to the horizontal header, the :horizontal selector can be omitted (but the ::section is required).

Also consider that you could still set the stylesheet above to the table: its syntax will be correctly applied since I've correctly used the class selector. The problem is that, since we need to show a border for the whole header, it has to be applied to the class without selectors, which will obviously cause both headers to show a bottom border, and that's because in this case :horizontal won't work properly (AFAIK).

musicamante
  • 41,230
  • 6
  • 33
  • 58
  • Thanks very much. For one thing, I didn't appreciate, as a PyQt5 newb, that you apply C++ style separators in the CSS specifications! (i.e. "::" and ":" - I was assuming it was going to be translated as "." for Python). When I run this I do also find with this solution that the green line extends to the right of the table and joins the side of the window. Just for fun I tried including a `gridline-color` directive in your CSS: no result. Interesting. Puzzling. – mike rodent Nov 09 '20 at 20:26
  • 1) That has absolutely nothing to do with C++ (even if it was, rember that PyQt is a python *binding* to a C++ library, which knows nothing nor should care about you using python), that's plain **standard** CSS syntax for pseudo selectors. 2) if you want to set borders only in sections and not to the whole header, just move the `border-bottom` to the appropriate `::section:horizontal` definition 3) QHeaderView *does *not** have grid lines. Please, take your time, carefully read, study and experiment with the syntax/reference docs already suggested to you, everything is clearly explained there. – musicamante Nov 09 '20 at 20:39
  • Thanks for the clarifications: in fact I've never used "::" for pseudo elements in CSS, and (it seems) double colons are optional, hence some of my confusion. I shall do that careful reading you recommend, and endeavour to understand how `QHeaderView` works... Thanks again. – mike rodent Nov 09 '20 at 20:45
  • Double colons might be optional, but they don't cost that much. While the style sheet syntax is somehow "liberal", let's try to keep conventions and standards, as a future Qt release might create issues if you're not using the correct syntax. – musicamante Nov 09 '20 at 20:49
  • Yes, of course. I meant they are to all intents and purposes "optional" in conventional uses of CSS, but as you say, they don't cost much, so now I know about them I shall also use them there! – mike rodent Nov 09 '20 at 20:52