Python Qt bindings: setCosmetic() and sceneRect(),

2019-07-05 09:12发布

问题:

With the following simple example (which works well with either PySide or PyQt4):

import sys
import random
import numpy
from PySide import QtGui, QtCore

class Window(QtGui.QWidget):

    def __init__(self):
        super(Window, self).__init__()

        self.resize(600, 400)
        self.view = QtGui.QGraphicsView()
        self.scene = QtGui.QGraphicsScene()
        self.view.setScene(self.scene)
        self.setWindowTitle('Example')

        # Layout
        layout = QtGui.QGridLayout()
        layout.addWidget(self.view, 0, 0)
        self.setLayout(layout)

        # Styles
        self.pen = QtGui.QPen(QtCore.Qt.black, 0, QtCore.Qt.SolidLine)
        self.brush = QtGui.QBrush(QtGui.QColor(255, 255, 255, 0))

    def addLine(self, x0, y0, x1, y1):
        line = QtCore.QLineF(x0, -y0, x1, -y1)
        pen = QtGui.QPen(QtGui.QColor(0, 0, 255, 255), 0, QtCore.Qt.SolidLine)
        l = self.scene.addLine(line, pen)

    def addRect(self, left, top, width, height):
        rect = QtCore.QRectF(left, -top, width, abs(height))
        r = self.scene.addRect(rect, self.pen, self.brush)

    def fit(self):
        self.view.fitInView(self.scene.sceneRect())

    def resizeEvent(self, event = None):
        self.fit()


if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    window = Window()
    window.show()
    window.addLine(-1, -1, 2, 2)
    window.addLine(0, 1, 1, 0)
    window.addRect(0, 1, 1, 1)
    window.fit()
    sys.exit(app.exec_())

I am able to draw one rectangle and two blue lines crossing it. Notice how, using a QtGui.QPen with width 0 makes the blue lines have a constant width no matters the size of the square/lines nor the size of the window:

That is because a zero width pen is cosmetic by default. According to the Qt documentation:

Cosmetics pens are used to draw strokes that have a constant width regarless of any transformations applied to the QPainter they are used with. Drawing a shape with a cosmetic pen ensures that its outline will have the same thickness at different scale factors.

A non-zero width pen can be also set as cosmetic, using the method setCosmetic(True). So, setting the pen width in the example's addLine() method to 2 and setting the pen as cosmetic:

    def addLine(self, x0, y0, x1, y1):
        line = QtCore.QLineF(x0, -y0, x1, -y1)
        pen = QtGui.QPen(QtGui.QColor(0, 0, 255, 255), 2, QtCore.Qt.SolidLine)
        pen.setCosmetic(True)
        l = self.scene.addLine(line, pen)

Gives the following output:

As you can see, the margins are huge, and I really would expect the crossing line to start and end at the lower-left and upper-right corners of the window, with no margins.

It seems that those margins were added because the scene.sceneRect() was modified as if the line was not cosmetic. See for example this case, in which the width is set to 2 as well, but the pen is not set as cosmetic:

    def addLine(self, x0, y0, x1, y1):
        line = QtCore.QLineF(x0, -y0, x1, -y1)
        pen = QtGui.QPen(QtGui.QColor(0, 0, 255, 255), 2, QtCore.Qt.SolidLine)
        l = self.scene.addLine(line, pen)

Why is this happening? Shouldn't the extra margins be added only when isCosmetic() == False? If this behavior is intentional, could someone explain the reason?

Also, is there a way to avoid it? Something "clean", different from manually changing the boundings of the line before adding it to the scene (or different from substracting the extra margins later from the scene). Perhaps there is a configuration parameter or another way of adding the line to the scene?

EDIT

Setting the cap style to "flat" results in smaller margins, although the problem is still there:

    def addLine(self, x0, y0, x1, y1):
        line = QtCore.QLineF(x0, -y0, x1, -y1)
        pen = QtGui.QPen(QtGui.QColor(0, 0, 255, 255), 2, QtCore.Qt.SolidLine)
        pen.setCosmetic(True)
        pen.setCapStyle(QtCore.Qt.FlatCap)
        l = self.scene.addLine(line, pen)

And once again, we can see how the margins are the same as if we used a non-cosmetic pen:

回答1:

This isn't quite the answer but I thought it might help:

I did this in C++ but it's easy enough to translate. In your QGraphicsView, set the scrollbar policies:

view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

Then in your call to fitInView, add the following flag:

view->fitInView(scene->sceneRect(), Qt::KeepAspectRatioByExpanding);