0

I want to be able to make a function constantly run in a tkinter window.
For example, the code,

>>> import tkinter
>>> Window=tkinter.Tk()
>>> ThisCouldBeAnyObject=tkinter.Label(Window,text="Aligned right.",bg="#0000FF")
>>> thisCouldBeAnyObject=tkinter.Label(Window,text="Aligned right.",bg="#0000FF")
>>> def Function():
    ThisCouldBeAnyObject.place(x=Window.winfo_width()-128,y=24)
    ThisCouldBeAnyObject.config(text="To small!" if Window.winfo_width()<128 else "Window size: "+str(Window.winfo_width()))
    thisCouldBeAnyObject.place(x=(Window.winfo_width()-128)//128*128,y=64)

creates a window, 2 labels and function that positions the labels according to the width of the window. (See edit history for more simple version of example code.)

My understanding is that mainloop causes tkinter to loop through a piece of code that checks if buttons are clicked, mouse motion is detected, etc. depending on the elements in the window. Could I make it so that in the mainloop, my function keeps getting called without the need for these triggers, so that (in this example) the label moves as the user resizes the window?


So far, I've worked out that the code,

>>> Window.bind("<Configure>",func=lambda x:Function())

seems to work for the example, but it would be helpful to know how to avoid having to limit the execution of the function to when the window is configured.

I don't want to use grid, or pack because I feel that I could do more with place and it will be good practice ready for other languages (that could be used for games development).


This is my first question, so thank you for any advice on asking questions.


Yes. I know that this might not be exactly how your supposed to use block quotes, but it was the only style of formatting I could find that provides the visual queue that a piece of text is a comment that you don't need to read to answer the question.

Community
  • 1
  • 1
Programmer S
  • 429
  • 7
  • 21
  • `place` may seem to give you more control, but that comes at a price. Tkinter is exceptionally good at making scaleable, responsive UIs. With `place` you're going to have to do a lot of that work yourself. It is true that `place` might give you good practice for other languages, since very few languages have anything as powerful and simple as `grid` and `pack`. – Bryan Oakley Apr 12 '18 at 20:40
  • 1
    It's not clear what you're really asking about. You can make functions that run constantly, and you can make functions that are called in response to the window changing size, and you can also configure tkinter so that you don't need to do anything when the window resizes. Are you asking about how to keep a label centered, or are you asking about how to run any general-purpose function either constantly (ie: it has its own infinite loop) or periodically (eg: every X seconds)? – Bryan Oakley Apr 12 '18 at 20:42
  • Sorry if I was vague. Tkinter has a function called mainloop, which I am guessing starts a loop that runs multiple times each second. Can I add my own functions to the mainloop? (like how in gamemaker (gml), there is the step event which will run multiple times each second, according to the room speed.). – Programmer S Apr 12 '18 at 21:01
  • @SuperS you can actually manage your own mainloop function but you wont need it. You can also use `bind()` to update the label while the window is being updated. We would need a more complete example code to work with. Please provide [Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) – Mike - SMT Apr 12 '18 at 21:12
  • 1
    I'm still not clear what you're asking about. Is your goal to learn how to do layout (which is related to the use of pack, place, or grid) or is your goal to learn how to run a function "constantly"? – Bryan Oakley Apr 12 '18 at 21:26
  • You don't need to have `Function()` run constantly if all it does is call the `place` (or another) layout manager. If you call the right one and pass it the right arguments, doing so will keep the `Label` (or whatever) widget centered. However for that to happen, the `mainloop()` will have to be running which means it must have been called somewhere. `tkinter` has an "event-driven" architecture, which means that it's main loop constantly watches the various input devices and calls the appropriate handler depending on which one has received some new data. Grasping this concept is paramount. – martineau Apr 12 '18 at 21:43
  • Thank you for your feedback. I have edited my question to make it more specific. I'm a bit disappointed that my question hasn't been voted up yet, but I think I got some good practice at asking. – Programmer S Apr 14 '18 at 21:03

2 Answers2

3

Your question is a bit unclear, but that might be due to your unfamiliarity with tkinter concepts.

GUI programming is all about servicing events in a queue. As things happen (button clicks, window resizes, etc), events are added to the queue. They are picked up by the event loop (in tkinter, this is mainloop()), and any functions that are bound to each event are executed as the events are processed. For example, when you resize a window, multiple events are added to the queue. If you want to run a function when the window resizes, you can bind a function to the appropriate event.

Tkinter also gives you the ability to add events to the queue. You can add actual events (eg: clicks and keypresses), but you can also specify functions to be run after a specific amount of time.

For example, if you want to run a function every 10ms, you could define a function like this:

def run_periodically(func):
    func()
    root.after(10, run_periodically, func)

When you call that function once, it will call the function passed to it, and then run itself again after 10 milliseconds. For example, to run foo every 10 ms you would call run_periodically(foo) once, and it would then run every 10 ms until you stop it.

If your goal is to adjust the location of a widget when the window is resized, you don't need to run your own function continuously. Instead, you can bind to the <Configure> event, which is one of the events that gets added to the queue when the window is resized. The function that is bound to the event can then look at the size of the window and do the appropriate action.

Most of the time, it is far more efficient and easier to use grid and pack, which provide options that tell tkinter how to react when the window changes size, than it is to use place. For example, widgets can stay centered, or they can expand or contract as necessary.

If you are using place, it has some features to do similar things (for example, it supports relative placement and relative widths and heights), but in many cases it requires more work to use place than it does to use grid or pack. It may seem easy at first, but once you have a complex GUI, even a slight change to one widget such as changing a font or the thickness of a border can force you to make changes to every other widget.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
0

You are probably will need use pack() or grid() to get the results you are looking for. I would not use place() all that much except for some specific needs that you likely wont run into early on in your programming.

If you take the time to review grid() and pack() you will find they can do almost anything you need them to do. I had the same idea about place() when I first started learning Python/Tkinter and I have not really used it for the past year in my programs. It just doesn't come up as something I need.

Take a look at the below code. I chose to use grid() to place my widget and we can use columnconfigure() and rowconfigure() to add a weight to the row and column the widget is placed in. This allows for expansion of the widget placed there.

Per your question in the comments the weight is used to decide how much if any the column and/or row will expand with the window. A default weight of zero will not expand with the window and a value of 1 will evenly expand. You can also set it to more or less than 1 for variable of expansion.

Here is a post on the topic of weight that already has a good answer: What does 'weight' do in tkinter?

import tkinter as tk


root=tk.Tk()
root.columnconfigure(0, weight=1) # allows widgets placed on row zero to expand left and right.
root.rowconfigure(0, weight=1) # allows widgets placed on column zero to expand up and down.

tk.Label(root, text="This label resizes with window", background="grey").grid(row=0, column=0, sticky="nsew")

root.mainloop()

If you really want to work with place() then you might want to try using a bind() and a function to update the placement. We can bind() the event that handles the resizing of the root window to the function that updates the placement of the label.

Take a look at the below code:

import tkinter as tk


root=tk.Tk()

lbl = tk.Label(root, text="This label resizes with window", background="grey")
lbl.place(x=10, y=10, width=100, height=20)

def update_label(event):
    w,h = event.width, event.height
    lbl.place(x=10, y=10, width=w, height=h)

root.bind('<Configure>', update_label)

root.mainloop()
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
Mike - SMT
  • 14,784
  • 4
  • 35
  • 79
  • I'm not new to programing and want to do more technical things, which place would seem to be better for. Also, I don't understand what weight does. I was asking for instructions to perform a technique, not for a bug fix, and therefore would like you to explain exactly how your code works. – Programmer S Apr 12 '18 at 21:13
  • @SuperS I have updated my answer to include more on `weight` as well as a link to a good Q/A on `weight`. – Mike - SMT Apr 12 '18 at 21:18