(Last updated: October 10, 2017)

Intro

QPen objects in Qt have a method called setDashPattern which theoretically lets you have a dashed line with varying patterns, and it often works reasonably well. Unfortunately, if this is used inside a QGraphicsScene/QGraphicsView in which scrolling happens, there's no real way to make them look decent while scrolling.

Problem 1: Distortion While Scrolling

DashPattern distortion

The first and most obvious problem is that if you just add something with a dashPattern QPen into the scene without doing anything else, as you'd do with any other QGraphics*Item, you'll end up seeing distortion as you scroll. Basically, the newly-drawn area while scrolling is going to end up copying inappropriate pixels, and you could end up with no line at all, a solid line, or more likely just a mess of seemingly-random pixels.

The PyQt script which generates the above behavior is: dashedline-move-distort.py

I've reported this as QTBUG-63322, so we'll see if anything comes of it.

Problem 2: Non-scrolling Lines

Non-scrolling DashPatterns

There is one way to fix the first problem, but it introduces this second problem instead. Basically, you need to call the QGraphicsView's Viewport's update() function whenever a scroll event is encountered. In my PyQt script, in the QMainWindow class, I set up the following signal handler:

self.view.horizontalScrollBar().valueChanged.connect(self.update_rect)

And then I define the following function:

def update_rect(self, value):
    self.view.viewport().update()

It's a bit ridiculous to have to do that kind of manual update, but it at least prevents complete garbage from showing in the View.

However, if you have lines with dashPatterns set which are long enough, you'll instead end up with the really stupid-looking scroll behavior as seen above, where the dashPattern'd line doesn't appear to be scrolling at all. It basically just seems as though the dashPattern gets drawn relative to the edge of the visible portion of the Scene, rather than the beginning of the line.

I've put this bug in as QTBUG-63386, and you can grab the PyQt script demonstrating it here: dashedline-move-update.py

A "Solution"

In the end, for my own purposes, I ended up just eschewing the use of dashPattern altogether and made do with adding a whole bunch of solid QGraphicsLineItem objects in the pattern I wanted. My needs for the app where I found all this out were pretty simple; I only wanted patterns with equal amounts of line and space, so I was able to get away with a simple implementation like this:

def draw_dashed_line(x1, y1, x2, y2, dash_pixels, pen, parent=None, scene=None):

    x_len = (x2 - x1)
    y_len = (y2 - y1)
    line_len = math.sqrt(x_len**2 + y_len**2)
    num_segments = line_len/dash_pixels
    if num_segments == 0:
        return

    x_delta = x_len/num_segments
    y_delta = y_len/num_segments
    num_segments_int = math.ceil(num_segments)
    cur_x = x1
    cur_y = y1
    for i in range(num_segments_int):
        if i == num_segments_int - 1:
            new_x = x2
            new_y = y2
        else:
            new_x = cur_x + x_delta
            new_y = cur_y + y_delta
        if (i % 2) == 0:
            line = QtWidgets.QGraphicsLineItem(cur_x, cur_y, new_x, new_y, parent)
            line.setPen(pen)
            if scene:
                scene.addItem(line)
        cur_x = new_x
        cur_y = new_y

Far from the most elegant solution, certainly, but it's got the benefit of Actually Working, and not demonstrating either of the bugs mentioned above. The "fixed" PyQt script I have which uses my draw_dashed_line function is here: dashedline-move-manual.py

I'll definitely try to keep this page updated if I see that either of those bugs has been addressed.

Changelog

October 10, 2017
  • Initial Post