[Tkinter-discuss] Entry widget inactive after messagebox.askquestion

John McCabe john at mccabe.org.uk
Fri Jan 8 09:42:24 EST 2021


Hi,

I've raised this as a bug report at https://bugs.python.org/issue42867, 
(based on the same issue having been reported in 2010, but closed with 
no real resolution in 2014), but apparently they're busy so, in the 
meantime...

I've built an application using tkinter (see below). I'm fairly new to 
tkinter, despite having substantial software experience (33 years), so 
the layout might be a bit odd/wrong, and the example obviously doesn't 
follow PEP-8 guidelines, however...

Basically this application (which is a bit more than minimal, but should 
be easy to follow) is designed to manage pairs of text values as JSON, 
saving/loading from a file. When it starts, the user is asked if they 
want to read in an existing file of data; if they select NO, a fairly 
empty frame with 3 buttons showing "+", "Save" and "Quit" is displayed.

At this point, if "+" is pressed, a new row with two labels and two 
Entry widgets is added to the frame.

However (and this is the problem which appears to be identical to that 
reported in Issue #9673 - at the Python Issue Tracker, in 2010), it is 
not possible to set focus into either of the Entry widgets on Windows 
10; there is no problem doing this on Ubuntu 16.04 (although I've only 
got Python 3.5 on there)

If the "Save" button is then pressed, a message box pops up telling the 
use that no changes have been saved. Once OK has been pressed on that, 
it becomes possible to set focus into the Entry widgets.

One of the problems with Issue #9673 was that no 'minimal' example was 
provided showing this behaviour, and the very minimal example given in 
https://bugs.python.org/issue9673#msg218765 doesn't exhibit the problem, 
hence the example below being a bit more than minimal, while still not 
being particularly complicated.

The behaviour described here is identical on Windows 10 on Python 3.6, 
3.8 and 3.9. If anyone can tell me whether there is an issue with how 
I'm going about this, or a way round this problem while still retaining 
the same functionality, I'd appreciate your help.

====
#!/usr/bin/python3

import os
import tkinter as tk
from tkinter import filedialog, messagebox
import json

class Application(tk.Frame):

     def __init__(self, master):
         super().__init__(master)
         self.master = master
         self.grid()
         self.originalJson = {}
         self.inFileName = ""
         self.leftRightEntries = []
         self.fileDlgOpts = { "initialdir"       : os.getcwd(),
                              "initialfile"      : "file.json",
                              "filetypes"        : (("JSON File", 
"*.json"), ("All Files","*.*")),
                              "defaultextension" : '.json',
                              "title"            : "Select A File" }
         self.createWidgets()

     def openInFile(self):
         fileName = ""
         reuse = tk.messagebox.askquestion("Use An Existing File", "Do 
you want to load and use an existing file?")
         if reuse == "yes":
             fileName = tk.filedialog.askopenfilename(**self.fileDlgOpts)
             if fileName is not "":
                 try:
                     with open(fileName, 'r') as json_file:
                         self.originalJson = json.load(json_file)
                         json_file.close()
                 except Exception:
                     tk.messagebox.showerror("Use An Existing File", 
"File could not be loaded; continuing without one.")
                     fileName = ""
             else:
                 tk.messagebox.showwarning("Use An Existing File", "No 
existing file specified; continuing without one.")

         return fileName

     def createWidgets(self):
         self.inFileName = self.openInFile()

         # We add the buttons to some huge numbered row because we might 
want to insert more
         # rows, and the layout manager will collapse everything in 
between. Also we
         # add these first because of the way the tab order is set up
         self.addBtn = tk.Button(self.master, text = "+", command = 
self.addNew)
         self.addBtn.grid(row = 100, column = 0, sticky = tk.W)

         # Save button; pretty self-explanatory
         self.saveBtn = tk.Button(self.master, text = "Save", command = 
self.save)
         self.saveBtn.grid(row = 100, column = 2, sticky = tk.W)

         # Quit button; pretty self-explanatory
         self.quitBtn = tk.Button(self.master, text = "QUIT", fg = "red", 
command = self.quit)
         self.quitBtn.grid(row = 100, column = 3, sticky = tk.E)

         # If there is original json, work through each key and put the 
fields on the display
         rowNum = 0
         for leftText in sorted(self.originalJson.keys()):
             self.insertRow(rowNum, leftText);
             rowNum = rowNum + 1

         self.nextEmptyRow = rowNum

         self.redoPadding()

     def redoPadding(self):
         for child in self.master.winfo_children():
             child.grid_configure(padx = 5, pady = 5)

     def focusNextWidget(self, event):
         event.widget.tk_focusNext().focus()
         return("break")

     def insertRow(self, rowNum, initialLeft = None):
         tk.Label(self.master, height = 1, text = "Left: ").grid(row = 
rowNum, column = 0, sticky = tk.W)
         leftBox = tk.Entry(self.master, width = 20)
         leftBox.grid(row = rowNum, column = 1, sticky = tk.W)
         leftBox.bind("<Tab>", self.focusNextWidget)
         if initialLeft is not None:
             leftBox.insert(tk.END, initialLeft)
         tk.Label(self.master, height = 1, text = "Right: ").grid(row = 
rowNum, column = 2, sticky = tk.W)
         rightBox = tk.Entry(self.master, width = 20)
         rightBox.grid(row = rowNum, column = 3, sticky = tk.W)
         rightBox.bind("<Tab>", self.focusNextWidget)
         if initialLeft is not None:
             rightBox.insert(tk.END, initialLeft)
         self.leftRightEntries.append((leftBox, rightBox))
         leftBox.focus_set()

     def addNew(self):
         # Add a new row before the button
         self.insertRow(self.nextEmptyRow)
         self.nextEmptyRow = self.nextEmptyRow + 1
         self.redoPadding()

     def getCurrent(self):
         # Work through the rows and check stuff
         current = {}
         for (leftEntry, rightEntry) in self.leftRightEntries:
             leftText = leftEntry.get()
             rightText = rightEntry.get()
             if leftText == "" and rightText == "":
                 pass
             elif leftText == "":
                 print("No leftText specified for rightText 
[{}]".format(rightText))
             elif rightText == "":
                 print("No rightText specified for leftText 
[{}]".format(leftText))
             else:
                 print("lefText: {}, rightText: {}".format(leftText, 
rightText))
                 current[leftText] = rightText
         return current

     def save(self):
         # Get the current values, and then dump the new json to a file, 
if it's changed!
         finalResult = self.getCurrent()
         if finalResult != self.originalJson:
             if self.inFileName == "":
                 self.inFileName = 
tk.filedialog.asksaveasfilename(**self.fileDlgOpts)

             if self.inFileName != "":
                 with open(self.inFileName, 'w') as json_file:
                     json.dump(finalResult, json_file, indent = 4)
                     self.originalJson = finalResult
                 tk.messagebox.showinfo("Save Data", "Data saved to 
{}".format(self.inFileName))
             else:
                 tk.messagebox.showwarning("Save Data", "Data has not 
been saved; no file name was supplied!")
         else:
             tk.messagebox.showwarning("Save Data", "Data has not been 
saved; there are no changes")

     def quit(self):
         # Deal with quitting when the file's been modified, check 
original vs current JSON
         reallyQuit = True
         finalResult = self.getCurrent()
         if finalResult != self.originalJson:
             answer = tk.messagebox.askquestion("Quit", "Data has 
changed; do you really want to quit?", icon = "warning")
             if answer != "yes":
                 reallyQuit = False

         if reallyQuit:
             self.master.destroy()

if __name__ == "__main__":
     root = tk.Tk()
     root.title("Inactive Entry Example")
     app = Application(root)
     root.protocol("WM_DELETE_WINDOW", app.quit)
     app.mainloop()
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.python.org/pipermail/tkinter-discuss/attachments/20210108/0450786f/attachment.html>


More information about the Tkinter-discuss mailing list