1

This is a similar question so I think I'm in the right forum. I have a tkinter app that counts 'steps' for something. After about 2 billion +1 additions (close to the size of a maximum 32-bit int), the count displayed in an Entry widget stops being user accessible -- can't focus the cursor in the displayed value and can't select some or all the characters of the value. If I restart the app and preset ('Update') the count to 1999999999 before starting, it still take 2 billion steps or more for this problem to manifest. Below is a stripped down app that shows this problem on 64-bit Windows 10 and on a Raspberry Pi 4. (Original script was replaced. See comments. 17Jan2021)

from tkinter import *  #python3

start = False
count = 0

def startCount():
    global start
    start = True
    countUp()

def stopCount():
    global start
    start = False
    eVal.set(count)

def get_eVal():
    global count
    count = int(eVal.get())

def countUp():
    global count, start
    count += 1
    if count % 1000 == 0:
        eVal.set(count)
    if start:
        on.after_idle(countUp)

root = Tk()
eVal = StringVar()
frame = Frame(root)
on = Button(frame, text='Start', command=startCount)
off = Button(frame, text='Stop', command=stopCount)
updt = Button(frame, text='Update', command=get_eVal)
entry = Entry(frame, textvariable=eVal, width=20, justify=RIGHT)
on.pack(side=LEFT)
off.pack(side=LEFT, padx=(8,4), pady=6)
updt.pack(side=LEFT, padx=(4,8))
entry.pack(side=LEFT)
frame.pack()
mainloop()

So far in my bigger app, when this problem happens, ALL Entry widgets become inaccessible to the user. Is this a known tkinter problem? Are there any easy fixes?

  • I can't get your example to run, I get an error that indicates eVal is undefined. Probably since, eVal is defined in main but is not globally available to the functions startCount, stopCount,get_eVal etc?, – itprorh66 Jan 13 '21 at 21:44
  • I don't get that problem when I run this script, but by all means, try moving the eVal = StringVal() statement up by the quit = False statement and add global as needed. What python3/system do you use? BTW, I think the scope of if __name__ == '__main__': is global. – Scott Barnes Jan 14 '21 at 22:46
  • No, on second thought, don't move the eVal = StringVal() statement before root = TK(). I'm sorry I don't know what your problem is. – Scott Barnes Jan 15 '21 at 00:29
  • Have you tried removing your `while` loop at the end? Using your own loop and calling `update` is not nearly as efficient as letting `mainloop` do its job, and may actually contribute to the problem. It's just not how tkinter is designed to be used. – Bryan Oakley Jan 15 '21 at 00:53
  • I've edited the script to avoid .update, however this version is susceptible to going "Not Responsive' if I click in the Entry's display. In the absence of any answer, I'll take it the answer is "No, this is not a known tkinter problem". mainloop() in particular doesn't seem to be an easy fix. – Scott Barnes Jan 15 '21 at 18:42
  • Sorry, that's "Not Responding" on Windows. – Scott Barnes Jan 15 '21 at 18:50

2 Answers2

1

I know I shouldn't answer my own question but I think I now have answers that may be important to others seeing this problem. I did quite a bit of research both here and elsewhere before asking. If I had found any answers, I wouldn't have bothered the community. The question was simply to confirm if anyone else had seen this problem. I didn't receive any 'yes' or 'no' comments or answers. Instead, the question was down counted. How does one show research made before asking a question? I think the question was very clear. And the answer is certainly important to me.

I also asked if there were any easy fixes - workarounds one might use to overcome the problem. Well here goes. On Windows the following script still shows anomalous display behavior, but at least the script still works, allows copying the Entry value during counting, and doesn't delay the work (countUp) any more than it has to.

from tkinter import *  #python3

start = False
count = 0

def startCount():
    global start
    start = True
    countUp()

def stopCount():
    global start
    start = False
    eVal.set(count)

def get_eVal():
    global count
    count = int(eVal.get())

def countUp():
    global count, start
    count += 1
    if count % 1000 == 0:
        eVal.set(count)
    if start:
        root.update_idletasks()
        root.after(0, countUp)

root = Tk()
eVal = StringVar()
frame = Frame(root)
on = Button(frame, text='Start', command=startCount)
off = Button(frame, text='Stop', command=stopCount)
updt = Button(frame, text='Update', command=get_eVal)
entry = Entry(frame, textvariable=eVal, width=20, justify=RIGHT)
on.pack(side=LEFT)
off.pack(side=LEFT, padx=(8,4), pady=6)
updt.pack(side=LEFT, padx=(4,8))
entry.pack(side=LEFT)
frame.pack()
root.mainloop()

The anomolous behavior is that your desktop can display two blinking cursors (sorry the vertical bars aren't blinking in this image) and selections in the Entry persist even when the app doesn't have focus. BUT, the script still functions.

two cursors active

I am testing this second script now which dares to use .update. I will comment later on whether it is a functioning workaround.

from tkinter import *   # python3

start = False
count = 0
quit = False

def startCount():
    global start
    start = True

def stopCount():
    global start
    start = False
    eVal.set(count)

def get_eVal():
    global count
    count = int(eVal.get())

def countUp():
    global count
    count += 1
    if count % 1000 == 0:
        eVal.set(count)

root = Tk()
eVal = StringVar()
frame = Frame(root)
on = Button(frame, text='Start', command=startCount)
off = Button(frame, text='Stop', command=stopCount)
updt = Button(frame, text='Update', command=get_eVal)
entry = Entry(frame, textvariable=eVal, width=20, justify=RIGHT)
on.pack(side=LEFT)
off.pack(side=LEFT, padx=(8,4), pady=6)
updt.pack(side=LEFT, padx=(4,8))
entry.pack(side=LEFT)
frame.pack()
while quit == False:
    if start:
        countUp()
    try:
        root.update_idletasks()
        root.update()
    except:
        break
  • If you wanted to share your research, then you should have ticked the 'Answer your own question – share your knowledge, Q&A-style' , and then answered it. And also your question was _"Are there any easy fixes?"_ and not type of _"The question was simply to confirm if anyone else had seen this problem."_. – Delrius Euphoria Jan 17 '21 at 07:17
  • What stackoverflow instruction manual are you reading from? I'd like to know all the customs and requirements. Yes, I did type two questions. The one you focus on and "Is this a known tkinter problem?" A straight answer would be Yes or No. My intent was to get a confirmation either way. Still hasn't happened. But thanks. – Scott Barnes Jan 17 '21 at 11:32
  • This is not from any _instruction manual_, while asking question on stackoverflow at the bottom most, there will be a checkbutton to toggle if this was a Q&A style post. I was just pointing out that _"I know I shouldn't answer my own question"_ was wrong. You can answer your own question if you come up with the answer after posting the question. Nonetheless I think I misunderstood your answer to say that you knew the answer while posting the question itself. Apologies, for misunderstanding. – Delrius Euphoria Jan 17 '21 at 12:28
  • If you want to read through asking a good question, [this](https://stackoverflow.com/help/how-to-ask) might help – Delrius Euphoria Jan 17 '21 at 12:32
  • 1
    The second script is NOT a workaround. It turns out to show the same problem brought up originally. It's probably too late but I wish to apologize to any members I have offended, especially those who have provided great code and project-saving answers such as at https://stackoverflow.com/questions/29158220/tkinter-understanding-mainloop – Scott Barnes Jan 17 '21 at 13:31
0

I'm guessing the problem is that you're using after_idle in a way that makes it impossible for mainloop to properly service events. In effect, the idle loop never gets empty because for each idle event you take off, you add one back on. And because tkinter is busy processing "idle" events, it has difficulty processing other events such as button and mouse clicks.

There should be at least a millisecond or more delay between each count in order to give the tkinter event processing system a chance to process other events besides your idle event.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Thanks, but I think that extends minimum testing time, let alone work done in 1 ms, to 23 days. I couldn't copy Entry values when this problem happened using .update my old way, but at least the script still performed. When I get some time I'm definitely going to try all sorts of variations. – Scott Barnes Jan 16 '21 at 02:30