Let's say that I have this tkinter script:
import tkinter as tk
def callback():
new_root = tk.Tk()
new_root.mainloop()
print("Done")
root = tk.Tk()
button = tk.Button(root, text="Click me", command=callback)
button.pack()
root.mainloop()
From my understanding of tkinter, when I press the button, a new window and tcl interpreter should be created. While running callback, the main window (root) shouldn't be updated so it should be unresponsive. new_root.mainloop() is a while True loop that runs until the second window is closed. Therefore, when I press the button it should create a new window, call .mainloop() on it and that should make the main window unresponsive. The problem is that that doesn't happen. The main window is responsive even though code execution is stuck inside new_root.mainloop().
Also closing the second window doesn't print "Done" until the rest of the tkinter windows are closed. Why does that happen?
I looked at the source code for tkinter and _tkinter but I couldn't find anything useful. I have Python 3.7.9, tcl 8.6
Though you're running a subsidiary event loop (really don't do that!) it still shares the same registry of event handlers as the outer loop, so events coming in are handled in the inner loop just as in the outer one. (There's a common piece of low-level event handling code that reaches deep into the OS to do the event processing efficiently. That code, the notifier, is stuff that very few people should ever touch; it's tricky because it merges some really weird and disparate event sources while also working around a bunch of strange bugs on some platforms.) The event_loop method returns once all windows are deleted. It literally calls the low level event processing engine (the API call is Tcl_DoOneEvent()) with appropriate flags, and does that in a while loop (until the number of existing windows drops below 1; that's exactly what it is waiting for). This is why you probably shouldn't count on it terminating and absolutely shouldn't nest it in a GUI callback.
Related
Consider below example:
import tkinter as tk
root = tk.Tk()
root.title("root")
other_window = tk.Tk()
other_window.title("other_window")
root.mainloop()
and also see below example that creates instances of Tk back-to-back instead of at once, so there's exactly one instance of Tk at any given time:
import tkinter as tk
def create_window(window_to_be_closed=None):
if window_to_be_closed:
window_to_be_closed.destroy()
window = tk.Tk()
tk.Button(window, text="Quit", command=lambda arg=window : create_window(arg)).pack()
window.mainloop()
create_window()
Why is it considered bad to have multiple instances of Tk?
Is the second snippet considered a bit better, or does it suffer from
the same conditions the first code does?
Why is it considered bad to have multiple instances of Tk?
Tkinter is just a python wrapper around an embedded Tcl interpreter that imports the Tk library. When you create a root window, you create an instance of a Tcl interpreter.
Each Tcl interpreter is an isolated sandbox. An object in one sandbox cannot interact with objects in another. The most common manifestation of that is that a StringVar created in one interpreter is not visible in another. The same goes for widgets -- you can't create widgets in one interpreter that has as a parent widget in another interpreter. Images are a third case: images created in one cannot be used in another.
From a technical standpoint, there's no reason why you can't have two instances of Tk at the same time. The recommendation against it is because there's rarely an actual need to have two or more distinct Tcl interpreters, and it causes problems that are hard for beginners to grasp.
Is the second snippet considered a bit better, or does it suffer from the same conditions the first code does?
It's impossible to say whether the second example in the question is better or not without knowing what you're trying to achieve. It probably is not any better since, again, there's rarely ever a time when you actually need two instances.
The best solution 99.9% of the time is to create exactly one instance of Tk that you use for the life of your program. If you need a second or subsequent window, create instances of Toplevel. Quite simply, that is how tkinter and the underlying Tcl/Tk interpreter was designed to be used.
I disagree with the tkinter community discouraging the use of multiple tk.Tk windows. You can have multiple tk.Tk windows. Using multiple instances of tk.Tk is the only way to create windows that are truly independent of each other. The only mistake most people make when creating multiple tk.Tk windows is that they forget to pass in master=... when creating PhotoImages/StringVars/IntVars/...
For example look at this code:
import tkinter as tk
root = tk.Tk()
root2 = tk.Tk()
variable = tk.StringVar() # Add `master=root2` to fix the problem
entry = tk.Entry(root2, textvariable=variable)
entry.bind("<Return>", lambda e: print(repr(variable.get())))
entry.pack()
root.mainloop()
The code above doesn't work. If you add master=root2 to the tk.StringVar(), then it will work perfectly fine. This is because tkinter stores the first instance of tk.Tk() in tk._default_root. Then if you don't pass in master=..., it will assume that you wanted the window in tk._default_root.
Another thing people get wrong is how many times should .mainloop() be called. It handles events from all tk.Tk windows that are alive so you only need one .mainloop().
For folks who disagree, I'd be interested in an example of where an actual problem is caused by the multiple tk.Tk windows.
The best reference I've found so far is the Application Windows section of the tkinterbook:
In the simple examples we’ve used this far, there’s only one window on the screen; the root window. This is automatically created when you call the Tk constructor
and
If you need to create additional windows, you can use the Toplevel widget. It simply creates a new window on the screen, a window that looks and behaves pretty much like the original root window
My take on it is that a Tk instance creates a Toplevel widget, plus things like the mainloop, of which there should be only one.
Tk() initializes the hidden tcl interpreter so that the code can run, as Tkinter is just a wrapper around tcl/tk. It also automatically creates a new window. Toplevel() just creates a new window, and wont work if Tk() hasn't been instantiated, as it requires the tcl interpreter that Tk() initializes. You cannot create any Tkinter widgets without instantiating Tk(), and Toplevel is merely a widget. In the question, you use Tk() to create a second window. You should instead create another file, because initializing the tcl interpreter multiple times can get confusing, as #Bryan Oakley explains so well. Then you should do:
from os import startfile
startfile(nameOfTheOtherFile)
, because, as Toplevel() is just a widget, it closes when the Tk() window is closed. Having the other window in a separate file makes it less confusing.
When I write
b.windows.last.use do
b.link.click
end
Once after the click, WATIR automatically switch back to first window, now I can start operating on first window, but I don't know how to close this child window and start operating on first window. I could do it in selenium but I don't know how to do it in WATIR.
What I have tried
require 'watir'
driver = Selenium::WebDriver.for :firefox
b = Watir::Browser.new driver
b.goto("https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_win_open")
b.iframe(id: 'iframeResult').element(xpath: "//button[contains(text(),'Try it')]").click
firstWindow=b.windows.first
b.windows.last.use do
b.element(text: 'LEARN HTML').click
end #end of this block automatically switch back to first window.
b.windows.last.close #Closing the child window
#b.iframe(id: 'iframeResult').element(xpath: "//button[contains(text(),'Try it')]").click
#This above line is not working so made a switch in the following line
firstWindow.use do #but it's not working as expected too.
b.iframe(id: 'iframeResult').element(xpath: "//button[contains(text(),'Try it')]").click
end
WATIR allows me to operate on the parent window through automatic switch but if I close the child window after this automatic switch, I don't have any way to connect back to the parent window even though automatic switch was done or connection to the parent window lost.
Ah, looks like a performance enhancement we made resulted in a bug collision with Firefox - https://github.com/mozilla/geckodriver/issues/610
First, I personally avoid using blocks when working with windows, so my code would look like this:
b.window(url: 'https://www.w3schools.com/').use
# do things
b.window.close
b.original_window.use
The problem is that because the element that opened the second window was inside an iframe, when Watir switches back to the first window, Firefox keeps the browsing context in the iframe instead of restoring it to the top level context. Watir used to always reset it with every element lookup, but those were extra wire calls, so we cached the context and now only make the call to switch to the top when necessary.
The temporary workaround until Mozilla fixes that bug is to do:
b.wd.switch_to.default_content
I'm in the process of making a python GUI calculator. I've been coding for no more than 3-4 week so my knowledge is limited. Anyway i want to make a pop up window that takes input from the user(Enter number, press a button to save that number in a variable).
That should be done twice(in order to add, subtract,... 2 numbers). Then i'll make another pop-up window saying: "The result is:(result)"
I know how to make an entry widget so my question is how do i make a button to save the user's input to a variable?
I highly recommend using a module called tkinter for new coders wanting to learn GUI programming in python. A complete tutorial can be found here:
http://zetcode.com/gui/tkinter/
However, creating a calculator with tkinter is pretty easy. Before you start you should think of what type of calculator you want to make, one with buttons or user input. Since you are a beginner let's do the user input method.
First if you cannot import tkinter without striking and error, head over to command prompt and write:
pip install tkinter
First things first we need to create the popup window:
from tkinter import *
window = Tk()
window.mainloop()
Now we need to create and Entry widget:
from tkinter import *
window = Tk()
User_input = Entry()
User_input.pack()
window.mainloop()
Now you will get an Entry where you will write your math problem.
Many people get confused at this stage because when they call the .get() function it doesn't work. This is because .get() makes a string. So in order to get an int you use
user_problem = int(User_input.get())
Then you use the int (numbers) the user wrote and solve them.
When using the button method assign a command callback to each button.
So, a few months back I made a small GUI for handling NPCs in a roleplaying campaign I was running. I haven't touched in since then, except that now I need it! Tomorrow, in fact...
I have a few odd error... Loading the GUI seems to work fine, but when I start to press buttons the troubles start. It seemed, at first, that it the script was very slow, which it shouldn't be, calling a two line dice function on a button press. I accidentally figured out that when I hover the mouse over the "close/minimize window" buttons (not in the GUI, but in the OS), the button would update with the result of the button press.
The same thing happens with a listbox I have: choosing an item may or may not select the item straight away (but hovering over the close/minimize updates it), and the results of the selection may or may not show. The results is in fact weirder: selecting a listbox item is supposed to get info from the selected item and print it in another frame. Even if the selection itself is fine without hovering, the printed text is somehow "clipped", showing only an area seeming to cover an arbitrarily sized square of text... Remedied by hovering, of course. The rest of the GUI have the exact same problems.
I have no clue what is going on here. The script was written on another computer, but that was also a Mac running the same OSX version (Mavericks), and it was a MUCH slower computer. This script shouldn't need any sort of advanced specs, though! I'm guessing it's something wrong with migrating to the new computer and the various version of different software? I'll paste the script down below, in case that'll help somehow.
Any help would be greatly appreciated, especially if it comes before the next epic campaign of Superheroes starts tomorrow afternoon! =P
[UPDATE]:
It was some time ago, but I still would like to have this problem solved. I've reduced my script to just a simple button, and the problem persists: clicking the button, even though there is no function or anything associated with it, only results in the frozen "button-clicked"-colour (i.e. light blue on OSX Yosemite), and I have to hover my mouse pointer over the close/minimize/etc. buttons in the top left corner to make it go back to "idle-button"-colour (i.e. grey).
#!/usr/bin/python
import tkinter as tk
root = tk.Tk()
test = tk.Button(root, text='test')
test.pack()
root.mainloop()
So, the problem obviously isn't with any of my "downstream" scripting, but something with the module or my way of calling it. Calling the script for the Terminal doesn't give me any error messages, and the problem is still there. Any ideas? It would be really, really good to get to the bottom of this problem!
I had the same problem when using Tk 8.5.13 on Mac OS X Sierra (10.12.3) with Python and IDLE v3.6.0.
Upgrading to TCL/Tk 8.5.18.0 as recommended on the Python Software Foundation page https://www.python.org/download/mac/tcltk/#activetcl-8-5-18-0 seemed to do the trick. This was the recommended version for my edition of the OS.
The interface I was building starting responding as I would expect, i.e. straight away when one of the controls was used. The only reservation I have so far is that normal buttons don't seem to have any sort of animation now, although the buttons do actually work.
-S.
I have an application that sends the focus to other windows but those windows then don't automatically display themselves in the foreground, i.e. on top of all the other windows. Where can I configure the preferences of my window manager so that this is the default behaviour?
In particular I'm using the Ctrl-0 and Ctrl-Shft-0 shortcuts in the MATLAB IDE to move between the command window and the editor window and although the focus seems to be transferred the new window doesn't automatically redraw itself in the foreground.
Not sure of a key binding off hand that does it, but if you alt-click on a window (which allows you to drag a window) it should come to the front.
As codeDr suggests, MATLAB is also kind of bad about repainting its windows. If you draw to a figure while code is executing, the figure does not update unless you execute drawnow or have some similar pause in the execution to allow the GUI to repaint. Since we're talking about MATLAB, the figure command will also cause the indicated figure to come to the front (in fact, it's harder to get it to not come to the front). So you could do figure(gcf) to bring the current figure to the front, or save the figure number with h = figure; and then later do figure(h). Incidentally, if you want to switch current figures without switching focus, set(0, 'CurrentFigure', h) should set h to the current figure.
Your window manager (probably Metacity?) implements focus-stealing prevention so that rogue apps don't pop up windows that would disturb your typing. Matlab needs to raise its window, and give it the input focus with the correct timestamp. If this is being done from a KeyPress event handler, the timestamp for setting the input focus would be the timestamp from the KeyPress event (i.e. the timestamp of the user-generated event that caused a window to be raised/focused).
To politely give the input focus to a window, google for _NET_ACTIVE_WINDOW.
Usually when the window doesn't repaint, it means that the application's main application loop isn't running to refresh the window. Could it be that Matlab is doing some computation or disk activity when you are switching between windows?