on scrolling how to redraw as little as possible

I’ve been looking at sample apps, and don’t see this illustrated. I want to have a very long canvas (e.g. 50,000 pixels), with the window only showing a small part at any one time. When the window scrolls (or there is some other redraw request), I don’t want to draw everything – only the bits showing.

All the examples I found redraw everything on a Paint event.

Here are the stages in which I would like to proceed:

  1. in the Paint event handler, how do I find out what part of the window is exposed and redraw that.

  2. Further, for scrolling, how can I find the part of the window which has scrolled in and only update that.

  3. Further, if scrolling is happening faster than drawing, throw out all Paint events except the last, so I don’t redraw a view which is already obsolete.

  4. Finally, how do I draw invisibly, the suddenly paste the drawing in so it does not flicker? For my example, this might not be necessary, but I have more intense things in mind.

Answers to any appreciated.

Mark Fanty

P.S. I’m including my 115 line sample program derived from examples on the web. It has one scrollable drawing window which shows a wide graph. If done right, scrolling should be very fast. Right now, it redraws everything and is slow.

“”" sample3
“”"
import sys
import wx
import random

=================================

== code to make a random graph ==

···

#==================================

class Node(object):
def init(self, frame,index):
self.frame = frame
self.index = index
self.arcsIn = []
self.arcsOut = []

def addArc(n1, n2):
n1.arcsOut.append(n2)
n2.arcsIn.append(n1)

class Graph(object):
def init(self, numFrames = 1000, nodesPerFrame = 10, arcsPerNode = 3):
# make nodes
self.frames = [None]*numFrames
for f in xrange(0,numFrames):
nodelist = []
for i in xrange(0,nodesPerFrame):
nodelist.append(Node(f,i))
self.frames[f] = nodelist

# make arcs
for f in xrange(0,numFrames-1):
  for node in self.frames[f]:
    next = random.sample(self.frames[f+1], arcsPerNode)
    for x in next:

      addArc(node,x)

======================

== code for the GUI ==

#=======================

Size of the drawing page, in pixels.

PAGE_WIDTH = 52000
PAGE_HEIGHT = 500

#-----------------------------------------------------------

class DrawingFrame(wx.Frame):
“”" A frame to hold a drawing “”"

def __init__(self, parent, id, title):
    """ Standard constructor.
    """
    wx.Frame.__init__(self, parent, id, title,
                      style = wx.DEFAULT_FRAME_STYLE | wx.WANTS_CHARS |
                             wx.NO_FULL_REPAINT_ON_RESIZE)

    self.graph = Graph()

    # topPanel can hold some controls later
    self.topPanel = wx.Panel(self, -1, style=wx.SIMPLE_BORDER)

    # drawPanel is the canvas
    self.drawPanel = wx.ScrolledWindow(self.topPanel, -1,
                                      style=wx.SUNKEN_BORDER)
    self.drawPanel.SetBackgroundColour(wx.WHITE)

    self.drawPanel.EnableScrolling(True, True)
    self.drawPanel.SetScrollbars(20, 20, PAGE_WIDTH / 20, PAGE_HEIGHT / 20)

    self.drawPanel.Bind(wx.EVT_PAINT, self.onPaintEvent)
    # Position everything in the window.

    topSizer = wx.BoxSizer(wx.VERTICAL)
    topSizer.Add(self.drawPanel, 1, wx.EXPAND)

    self.topPanel.SetAutoLayout(True)
    self.topPanel.SetSizer(topSizer)

def onPaintEvent(self, event):
    """ Not good.  This redraws everything.
    """
    dc = wx.PaintDC(self.drawPanel)
    self.drawPanel.PrepareDC(dc)
    dc.BeginDrawing()
    for f in self.graph.frames:
      for n in f:
        xb = (n.frame+1)*50
        yb = (n.index+1)*50
        dc.DrawRectangle(xb-5, yb-5, 10, 10)
        for a in n.arcsOut:
          dc.DrawLine(xb,yb, (a.frame+1)*50, (a.index+1)*50)
    dc.EndDrawing()

class GraphApp(wx.App):
“”" The main application object.
“”"
def OnInit(self):
“”" Initialise the application.
“”"
frame = DrawingFrame(None, -1, “Untitled”)
frame.Centre()
frame.Show(True)

    return True

#--------------------------------------------------------------------

def main():
“”" Start up
“”"
global _app

_app = GraphApp(0)
_app.MainLoop()

if name == “main”:
main()

Hi Mark,
Have you considered using a panel and a scrollbar control by themselves, instead of using a scrolling panel?

You can then handle all the events from the scrollbar, and simply calculate the region that needs to be painted in the panel, based on the scrollbar position, min & max settings etc.

Others may have better advice for you, but this is how I would have handled scroll-based calculations in Windows using another programming language that does not deserve naming here.

···

--
Les Ferguson
Software developer
Waitakere, NZ

Mark Fanty wrote:

I've been looking at sample apps, and don't see this illustrated. I want to have a very long canvas (e.g. 50,000 pixels), with the window only showing a small part at any one time. When the window scrolls (or there is some other redraw request), I don't want to draw everything -- only the bits showing.
All the examples I found redraw everything on a Paint event.
Here are the stages in which I would like to proceed:
1. in the Paint event handler, how do I find out what part of the window is exposed and redraw that.
2. Further, for scrolling, how can I find the part of the window which has scrolled in and only update that.
3. Further, if scrolling is happening faster than drawing, throw out all Paint events except the last, so I don't redraw a view which is already obsolete.
4. Finally, how do I draw invisibly, the suddenly paste the drawing in so it does not flicker? For my example, this might not be necessary, but I have more intense things in mind.
Answers to any appreciated.
Mark Fanty