Question

I've run into problems while trying to create a program for my frequent biking. I want the program to draw a red line across the designated course when the start button of the stopwatch is pressed. However, I can't seem to access the canvas from the class stopwatch and I need help doing that. The code is below.

from Tkinter import *
import random
import time
from itertools import product


class App():

    def __init__(self,master):
        menubar = Menu(master)
        filemenu = Menu(menubar,tearoff=0)
        filemenu.add_command(label="Quit",command=root.destroy)
        filemenu.add_command(label="Interval",command=self.Interval)
        coursemenu = Menu(menubar,tearoff=0)
        coursemenu.add_command(label="New Random Course",command=self.regenerateTerrain)
        menubar.add_cascade(label="File",menu=filemenu)
        menubar.add_cascade(label="Course",menu=coursemenu)
        master.config(menu=menubar)        
        self.statusbar = Frame(master)
        self.statusbar.pack(side=BOTTOM, fill=X)
        self.infobar = Frame(master)
        self.infobar.pack(side=TOP,fill=X)
        self.course = Label(self.infobar, text="Welcome!")
        self.course.pack(side=LEFT)
        self.action = Label(self.infobar, text="")
        self.action.pack(side=RIGHT,fill=X)

        #Stopwatch

        self.stop = StopWatch(self.infobar)

        #Stopwatch buttons
        self.button = Button(self.infobar, text="Start", command=self.stop.Start)
        self.button2 = Button(self.infobar, text="Stop", command=self.stop.Stop)
        self.button3 = Button(self.infobar, text="Reset", command=self.stop.Reset)
        self.button4 = Button(self.infobar, text="Quit", command=root.quit)

        self.button.pack(side=LEFT)
        self.button2.pack(side=LEFT)
        self.button3.pack(side=LEFT)
        self.button4.pack(side=LEFT)
        #Constants for program        
        #distance is in miles
        #height is in feet
        self.totalDistance = 25
        self.heightInterval = 10
        self.canvasHeight = 300
        self.canvasWidth = 500

        self.c = Canvas(root,width=self.canvasWidth,height=self.canvasHeight,background="white")
        self.c.pack(side=TOP,fill=BOTH,expand=YES)
        #Call regenerate an initial time, so that terrain gets generated on
        #initial creation
        self.regenerateTerrain()

    def buildTerrain(self,distance=25,topBound=0,
                     bottomBound=300,width=500):
        options=['up','down','flat','flat']
        y = (bottomBound-topBound)/2
        map = [y]
        changer =0
        for i in xrange(distance*10):
            direction = random.choice(options)
            options.pop()
            if direction=='up' and y>10:
                options.append('up')
                map.append(map[len(map)-1]-self.heightInterval)
                changer=-self.heightInterval
            elif direction=='down' and y<bottomBound-10:
                options.append('down')
                map.append(map[len(map)-1]+self.heightInterval)
                changer=self.heightInterval
            else:
                options.append('flat')
                map.append(map[len(map)-1])
                changer=0
            y+=changer
        return map

    def regenerateTerrain(self,distance=25,topBound=0,bottomBound=300,width=500):
        self.c.delete(ALL)
        x = 0
        y = (bottomBound+topBound)/2
        self.build = self.buildTerrain()
        for i in xrange(1,len(self.build)-1):
            self.c.create_line(x,y,x+(self.canvasWidth/(self.totalDistance*10)),self.build[i],fill="black")
            x+=(self.canvasWidth/(self.totalDistance*10))
            y=self.build[i]
        self.c.create_oval(0,self.build[0]-1,4,self.build[0]-5,fill="red")

    def Interval(self):
        top = Toplevel()
        top.title("Interval Mode")
        a = Frame(top)
        b = Frame(top)
        c = Frame(top)
        entLabelLow = Label(a, text="# of minutes at low interval: ")
        entLabelHigh = Label(b, text="# of minutes at high interval: ")
        entLabelTotal = Label(c, text="Total Number of Minutes: ")

        entWidgeTotal = Entry(c, width=5)
        entWidgeLow = Entry(a, width=5)
        entWidgeHigh = Entry(b, width=5)

        entLabelTotal.pack(side=LEFT)
        entWidgeTotal.pack(side=LEFT)
        entLabelLow.pack(side=LEFT)
        entWidgeLow.pack(side=LEFT)
        entLabelHigh.pack(side=LEFT)
        entWidgeHigh.pack(side=LEFT)

        a.pack(side=TOP)
        b.pack(side=TOP)
        c.pack(side=TOP)

        self.linesDist = 0
        self.minutes = 0.0 
        self.timeatHL = 0
        self.timeatLL = 0
        self.currentPos = 0


        def drawGraph():
            if entWidgeLow.get().strip() == "" or entWidgeHigh.get().strip() == "":
                print"Enter a value please"
                pass
                top.destroy()
            elif int(entWidgeLow.get().strip()) not in range(1,11) or int(entWidgeHigh.get().strip()) not in range(1,11):
                print"Please enter a number between 1 and 10"
                pass
                top.destroy()

            else: #Get the values
                self.LLength = int(entWidgeLow.get().strip())
                self.HLength = int(entWidgeHigh.get().strip())
                self.TLength = int(entWidgeTotal.get().strip())
                top.destroy()
            while self.linesDist < self.canvasWidth - 50: #Create the vertical lines
                self.c.create_line(10,195,10,205,fill="red")
                self.linesDist += 50
                self.intervalLength = self.TLength / 10.0 
                self.minutes += float(self.intervalLength)
                self.c.create_line((self.linesDist, 0, self.linesDist, 300), fill="gray")
                self.c.create_text(self.linesDist, 290, text=str(self.minutes))
            #Now to draw the graph
            while self.currentPos < 500:
                self.c.create_line(self.currentPos, 200, (((500/self.TLength)*self.LLength)+self.currentPos), 200)
                self.currentPos += (float(self.LLength)/self.TLength) * 500
                self.c.create_line(self.currentPos, 200, self.currentPos, 100)

                self.c.create_line(self.currentPos, 100, (((500/self.TLength)*self.HLength)+self.currentPos), 100) 
                self.currentPos += (float(self.HLength)/self.TLength) * 500
                self.c.create_line(self.currentPos, 100, self.currentPos, 200)
            self.stop.Start()


        self.submit = Button(top, text="Submit", command = drawGraph)
        self.submit.pack(side=BOTTOM)

        self.c.delete(ALL)






class StopWatch(Frame):

    def __init__(self, parent=App, **kw):
        """Creates the watch widget"""
        Frame.__init__(self, parent, kw)  
        self._start = 0.0
        self._elapsed = 0.0
        self._running = 0
        self.timestr = StringVar()
        self.parent=parent
        self.makeWidgets()

    def makeWidgets(self):
        """Make the label"""
        #It doesn't know waht the parent of this label is
        l = Label(self.parent, textvariable=self.timestr)
        self._setTime(self._elapsed)
        l.pack(fill=X, expand=NO, padx=2, pady=2)
    def _update(self):
        """Update the label with the correct time"""
        self._elapsed=time.time() - self._start
        self._setTime(self._elapsed)
        self._timer = self.after(50, self._update)
        return self._elapsed



    def _setTime(self, elap):
        """Set time string"""
        minutes = int(elap/60)
        seconds = int(elap - minutes*60.0)
        hundreths = int(((elap - minutes*60.0 - seconds)*100))
        self.timestr.set("%02d:%02d:%02d" % (minutes, seconds, hundreths))
    def Start(self):
        if not self._running:
            self._start = time.time() - self._elapsed
            self._update()
            self._running = 1
            return self._running

    def Stop(self):
        """To stop it, DUH"""
        if self._running:
            self.after_cancel(self._timer)
            self._elapsed = time.time() - self._start
            self._setTime(self._elapsed)
            self._running = 0
    def Reset(self):
        """Think about it"""
        if self._running:
            self.Stop()
        self._start = time.time()
        self._elapsed = 0.0
        self._setTime(self._elapsed)

root=Tk()
root.title("Bike Computer")
myapp=App(root)
root.mainloop()

I want to put the create_line statement in the _update definition but I can't access the canvas.

Was it helpful?

Solution

class App(object):

    def __init__(self):
        self.c = Canvas(root, ...)
        self.stop = StopWatch(self.infobar, self.c)

class StopWatch(object):

    def __init__(self, infobar, canvas):
        self.canvas = canvas

    def draw_line_on_canvas(self):
        self.canvas.create_line(...)

As Bryan Oakley said, you'll need to pass a reference to the canvas, to the StopWatch. We do this the same way as the "infobar". We make a few modifications to the StopWatch's initaliser to accomidate the canvas reference. Then we just bind it to StopWatch instance, as you would do normally. Now you have access to the canvas, and can call its methods and access it's attributes.

OTHER TIPS

Just pass a reference to the canvas to the constructor of the StopWatch class. Or, create a controller class that knows about the canvas and the stopwatch, then the stopwatch will ask the controller to ask the canvas to draw the line.

[edit] How would you do that? First, make the canvas, then make the stopwatch:

self.c = Canvas(root,...)
...
self.stop = StopWatch(self.infobar, self.c)

Then, use that within the stopwatch:

class StopWatch(Frame):
    def __init__(parent=App, canvas):
        Frame.__init__(self, parent)
        self.canvas = canvas
        ...
    def drawGraph():
        ...
        self.canvas.create_line(...)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top