16

Let's consider this little snippet:

import sys
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5 import QtGui
from PyQt5.QtWidgets import QMenu
from PyQt5.QtGui import QKeySequence
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QCursor


def create_action(parent, text, slot=None,
                  shortcut=None, shortcuts=None, shortcut_context=None,
                  icon=None, tooltip=None,
                  checkable=False, checked=False):
    action = QtWidgets.QAction(text, parent)

    if icon is not None:
        action.setIcon(QIcon(':/%s.png' % icon))
    if shortcut is not None:
        action.setShortcut(shortcut)
    if shortcuts is not None:
        action.setShortcuts(shortcuts)
    if shortcut_context is not None:
        action.setShortcutContext(shortcut_context)
    if tooltip is not None:
        action.setToolTip(tooltip)
        action.setStatusTip(tooltip)
    if checkable:
        action.setCheckable(True)
    if checked:
        action.setChecked(True)
    if slot is not None:
        action.triggered.connect(slot)

    return action


class Settings():

    WIDTH = 20
    HEIGHT = 15
    NUM_BLOCKS_X = 10
    NUM_BLOCKS_Y = 14


class QS(QtWidgets.QGraphicsScene):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        width = Settings.NUM_BLOCKS_X * Settings.WIDTH
        height = Settings.NUM_BLOCKS_Y * Settings.HEIGHT
        self.setSceneRect(0, 0, width, height)
        self.setItemIndexMethod(QtWidgets.QGraphicsScene.NoIndex)


class QV(QtWidgets.QGraphicsView):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.view_menu = QMenu(self)
        self.create_actions()

    def create_actions(self):
        act = create_action(self.view_menu, "Zoom in",
                            slot=self.on_zoom_in,
                            shortcut=QKeySequence("+"), shortcut_context=Qt.WidgetShortcut)
        self.view_menu.addAction(act)

        act = create_action(self.view_menu, "Zoom out",
                            slot=self.on_zoom_out,
                            shortcut=QKeySequence("-"), shortcut_context=Qt.WidgetShortcut)
        self.view_menu.addAction(act)
        self.addActions(self.view_menu.actions())

    def on_zoom_in(self):
        if not self.scene():
            return

        self.scale(1.5, 1.5)

    def on_zoom_out(self):
        if not self.scene():
            return

        self.scale(1.0 / 1.5, 1.0 / 1.5)

    def drawBackground(self, painter, rect):
        gr = rect.toRect()
        start_x = gr.left() + Settings.WIDTH - (gr.left() % Settings.WIDTH)
        start_y = gr.top() + Settings.HEIGHT - (gr.top() % Settings.HEIGHT)
        painter.save()
        painter.setPen(QtGui.QColor(60, 70, 80).lighter(90))
        painter.setOpacity(0.7)

        for x in range(start_x, gr.right(), Settings.WIDTH):
            painter.drawLine(x, gr.top(), x, gr.bottom())

        for y in range(start_y, gr.bottom(), Settings.HEIGHT):
            painter.drawLine(gr.left(), y, gr.right(), y)

        painter.restore()

        super().drawBackground(painter, rect)


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    a = QS()
    b = QV()
    b.setScene(a)
    # b.resize(800,600)
    b.show()
    sys.exit(app.exec_())

If we run it we can see the number block of grids is ok, as specified it's 8x10:

enter image description here

Now, let's say i set NUM_BLOCKS_X=3 and NUM_BLOCKS_Y=2, the output will be this one:

enter image description here

That's wrong! I definitely don't want that, I'd like the QGraphicsView to be shrinked properly to the grid's settings I've specified.

1st Question: How can i achieve that?

Another thing I'd like to know is, let's consider the posted snippet where the grid is 10x8 and then let's resize the QGraphicsWidget to 800x600, the output will be:

enter image description here

But I'd like to know how i can draw only the QGraphicsScene region. Right now I'm using rect in drawBackground.

So my 2nd question is: How can I draw the grid only inside QGraphicsScene's region?

One of the main problems appears when I zoom out, in that case I'd like to see only the size of the QGraphicsScene, because I'll be adding items only on that region, let's call it "drawable" region. As you can see, right now it's drawing grid lines on "non-drawable" region and that's really confusing

BPL
  • 9,632
  • 9
  • 59
  • 117
  • Is there a reason you are drawing in the view rather than on the scene? It doesn't seem like the current scheme is utilising any of the benefits of the view/scene architecture. You may as well be drawing on the background of a generic `QWidget` (although you'd have the same problems you see now). Is it OK if answers draw the grid in the scene? – three_pineapples Sep 25 '16 at 01:09
  • I'm a bit confused as to what you actually want (or maybe you don't fully understand how PyQt works). In any case, using the QGraphicsView as a top-level widget (and thus a Window) muddies the waters a lot. In a real application, you might be using the view alongside other widgets inside a Window and maybe constrain it with a rigid layout. Or, much better, if you want the widget to have fixed dimensions, you contrain the dimensions of the widget directly. If you leave it unconstrained, of course it will resize if you resize the window, especially when it's a top-level widget. – blubberdiblub Jan 20 '17 at 09:17

2 Answers2

9

I would draw the grid in the Scene like this:

class QS(QtWidgets.QGraphicsScene):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        width = Settings.NUM_BLOCKS_X * Settings.WIDTH
        height = Settings.NUM_BLOCKS_Y * Settings.HEIGHT
        self.setSceneRect(0, 0, width, height)
        self.setItemIndexMethod(QtWidgets.QGraphicsScene.NoIndex)

        for x in range(0,Settings.NUM_BLOCKS_X+1):
            xc = x * Settings.WIDTH
            self.addLine(xc,0,xc,height)

        for y in range(0,Settings.NUM_BLOCKS_Y+1):
            yc = y * Settings.HEIGHT
            self.addLine(0,yc,width,yc)

EDIT: Additional visibility/opacity functionality:

class QS(QtWidgets.QGraphicsScene):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.lines = []

        self.draw_grid()
        self.set_opacity(0.3)
        #self.set_visible(False)
        #self.delete_grid()

    def draw_grid(self):
        width = Settings.NUM_BLOCKS_X * Settings.WIDTH
        height = Settings.NUM_BLOCKS_Y * Settings.HEIGHT
        self.setSceneRect(0, 0, width, height)
        self.setItemIndexMethod(QtWidgets.QGraphicsScene.NoIndex)

        pen = QPen(QColor(255,0,100), 1, Qt.SolidLine)

        for x in range(0,Settings.NUM_BLOCKS_X+1):
            xc = x * Settings.WIDTH
            self.lines.append(self.addLine(xc,0,xc,height,pen))

        for y in range(0,Settings.NUM_BLOCKS_Y+1):
            yc = y * Settings.HEIGHT
            self.lines.append(self.addLine(0,yc,width,yc,pen))

    def set_visible(self,visible=True):
        for line in self.lines:
            line.setVisible(visible)

    def delete_grid(self):
        for line in self.lines:
            self.removeItem(line)
        del self.lines[:]

    def set_opacity(self,opacity):
        for line in self.lines:
            line.setOpacity(opacity)

You need to add import: from PyQt5.QtGui import QPen, QColor

EDIT2: Painting a rectangle in the scene:

def draw_insert_at_marker(self):
    w = Settings.WIDTH * 3
    h = Settings.HEIGHT

    r = QRectF(7 * Settings.WIDTH, 7 * Settings.HEIGHT, w, h)
    gradient = QLinearGradient(r.topLeft(), r.bottomRight())
    gradient.setColorAt(1, QColor(255, 255, 255, 0))
    gradient.setColorAt(0, QColor(255, 255, 255, 127))
    rect = self.addRect(r, Qt.white, gradient)
snow
  • 438
  • 3
  • 7
  • added some functions in the edited answer to do this. – snow Jan 20 '17 at 13:58
  • By cursor item you mean the horizontal three squares? What is that exactly? – snow Jan 20 '17 at 15:52
  • Instead of painting in the drawBackground of the graphicsscene add a rectangle: [addRect](http://doc.qt.io/qt-5/qgraphicsscene.html#addRect-1) You can than also set its zvalue etc. – snow Jan 20 '17 at 16:04
  • I updated the answer - see EDIT2. When you execute this after the grid drawing you dont need to set any zvalues. – snow Jan 20 '17 at 16:19
-3

You can obtain an automatically resizing grid from the library:

grid = QtGui.QGridLayout ()

http://zetcode.com/gui/pyqt4/layoutmanagement/

You can set the border color. It actually serves as an alignment tool for widgets, but maybe it's possible to adapt it to your needs.

rainer
  • 3,295
  • 5
  • 34
  • 50
  • This does not answer the question. A `QGridLayout` is not a paint-device, so you cannot draw a border or anything else on it. – ekhumoro Oct 24 '16 at 18:34