[Tutor] tkinter code executes before function returned

Alan Gauld alan.gauld at yahoo.co.uk
Sun Apr 15 09:24:34 EDT 2018


On 15/04/18 03:57, Chris Roy-Smith wrote:

> The code does not wait till the function returns a value, 

OK, I've had a closet look now and can confirm the
problem lies in your code structure. Its not event based.
You need to understand event-driven programming better.

In event driven code you initialise your program
(including the GUI) and then wait for events - usually from
the user. When you receive an event you handle that event
- and only that specific event - before returning to the
wait state until the user makes the next move.

The problem with your code is that you build a basic GUI
then wait. But when the user presses a button you create
a second set of GUI elements and then *without waiting
for the user to do anything* try to process the data in
the new elements. That is why you get nothing back,
the user hasn't done anything yet.

The other, related, problem is that event handlers do
NOT return values. You can't assign the value of an
event callback method to a variable you have to use
a global variable inside the event function. (Note: This
is one reason GUIs are often built using objects because
you can assign to an window object's attribute rather
than a global variable, which makes for slightly
cleaner code, easier to maintain.)

So what to do?

You need to break your data processing code out to a
separate function(*) then call that only when you know there
is data to be processed. That wont happen until you get
the submit action from the user. So you need to call
the data processing in your final callback - ReturnCount
in your case.

(*)Technically, you could just move the code into ReturnCount
but that makes the callback code overly complex, better to
keep reading and displaying as distinct operations.

So the flow of action should be:

initialise GUI and wait.
on clock
-> create extended GUI and wait
on click
-> create input dialog and wait
on click
-> read data and
   if valid
	-> call data processing function
           delete input dialog
           wait
   else
	-> reset fields and/or generate error message
           wait

Note how the end of each step waits for the user to
initiate the next action? That's the event driven bit.
How do we wait? We do nothing, the GUI mainloop
handles all that for us.

> #!/usr/bin/python3
> from tkinter import *
> import os
> from reportlab.lib.units import cm
> from reportlab.lib.pagesizes import A4
> from reportlab.pdfgen import canvas
> from reportlab.lib.utils import ImageReader
> 
> def printSign():
>      global gc, packages, rows
>      myCanvas = canvas.Canvas("Signs.pdf", pagesize=A4)
>      width, height = A4 #keep for
>      myCanvas.rotate(90)
>      myCanvas.setFillColorRGB(0,0,0)
>      myCanvas.setFont("Helvetica-Bold", 400)
>      TopMargin=-20
>      LeftMargin=1
>      Width=14
>      Height=19
>      VertPos=-15
>      Bottom=-1
>      a=[" " for i in range(rows)]
>      i=0
>      sign=0

All the stuff below here should go in a function
called something like display??? where ??? is whatever
your data represents.


>      for line in packages:
>          signcount=getcount(line[1])
>          print('line 27 ###   required sign count for {} is {} 
> ###'.format(line[1], str(signcount)))
>          for x in range(signcount):
>              #draw rectangle
>              myCanvas.rect(LeftMargin*cm+Width*sign*cm, TopMargin*cm, 
> Width*cm, Height*cm, stroke=0, fill=1)
> myCanvas.drawCentredString((LeftMargin+(0.5*Width))*cm+(Width*sign)*cm, 
> VertPos*cm, line[0])
>              if sign==1:
>                  myCanvas.showPage()
>                  sign=0
>                  myCanvas.rotate(90)
>                  i+=1
>              else:
>                  sign+=1
>                  i+=1
> 
>      myCanvas.showPage()
>      myCanvas.save()
>      if os.name == "posix":
>          os.popen("evince %s" % ("Signs.pdf"))
>      if os.name == "nt":
>          os.startfile('Signs.pdf')
> 



> def getcount(SignText):
>      global gc,e
>      gc=Toplevel(master)
>      MsgText='How many copies of {} do you want to print?'.format(SignText)
>      Label(gc, text=MsgText).grid(row=0, column=0, sticky=(W,E))
>      e = Entry(gc)
>      e.grid(row=0, column=1)
>      Button(gc, text='Okay', command=ReturnCount).grid(row=1, column=0, 
> sticky=(W,E))
>      Button(gc, text='Cancel', command=gc.destroy).grid(row=1, column=1, 
> sticky=(W,E))
> 
> def ReturnCount():
>      global gc,e
>      b0=e.get()
>      if b0 == None:
>          b0=0

call display??? here

>      gc.destroy()
>      print('line 64 ###   The required number of signs is {} 
> ###'.format(b0))
>      return b0

You need to make b0 (can't you think of a more informative name?
count say?)

Then you can access b0 from your display??? code

> 
> master = Tk()
> master.title("Testing")
> packages = [[0,'D','drill'],[1,'J','Jointer'],[2,'B','Bandsaw']]
> rows = 3
> b2 = Button(master, text="Print Signs", command=printSign).grid(row=4, 
> column=0, sticky=(W,E), padx=5, pady=5)
> b3 = Button(master, text="Quit", command=master.destroy).grid(row=4, 
> column=3, sticky=(W,E))
> 
> master.mainloop()

Finally, I still think you would be better creating all
of the initial GUI window at the beginning (ie in the
code just above this paragraph) And then your printSign()
code will be much smaller, basically just a call to getcount()
In fact you can probably eliminate all of printSign and
make getcount the direct callback function of Button b2.
(Please use better names in future it makes it much
easier to read and comment on your code! Including for
yourself in 6 months if you have to modify it!)

For more on event driven programming principles read
the event-driven programming and GUO topics in my web
tutorial.

HTH
-- 
Alan G
Author of the Learn to Program web site
http://www.alan-g.me.uk/
http://www.amazon.com/author/alan_gauld
Follow my photo-blog on Flickr at:
http://www.flickr.com/photos/alangauldphotos




More information about the Tutor mailing list