How do you make a constantly refreshing canvas in Python? - python

I am currently trying to code a basic smartmirror for my coding II class in high school with Python. One thing I'm trying to do is create a welcome text that updates based on what button you press. I am reading the string for the canvas' text from a .txt document that changes depending what user I select. Is there any way to get this to automatically refresh when I change the document's text?
The code I am using to display the message is:
text2 = Canvas(tk, width=500, height=100)
welcometext = text2.create_text(200, 50, text=string, font=('Helvetica', 20, 'italic'))
text2.pack(side=TOP, anchor=Center)

The basic way to do this is have a loop. And every cycle of the loop you should check for user input and redraw. Make sure you break everything up into functions so your loop is clean. Something like this -
canvas = Canvas(tk, width=500, height=500)
text = ''
while(True) {
getUserInput(text)
draw(canvas, text)
}
You can use python's built in input function to get the user input in your getUserInput function.
As an aside, this is a naive approach, because the loop will wait for user input every time before redrawing. The proper way to do this would be to use threading. You could have a thread to capture user input while your main loop does other things. This can get complicated really fast though, due to data synchronization issues. I'd just stick with the naive approach for now.
Also, the variable text2 is misleading. Always try to name your variables to be exactly what they are. In this case, it's a Canvas object, so call it canvas.

Related

Tkinter control scale and entry field with each other

I have a scale and an input field which both control the same variable to give the user choice of which one they'd like to use. I've coded it a bit like this:
def scale_has_moved(value):
entry_field.delete(0, END)
entry_field.insert(0, str(float(value)))
# Other functions I want the code to do
def entry_field_has_been_written(*args):
value = float( entry_field.get() )
scale.set(value)
This works, when I move the scale the entry_field gets written in and vice versa, and the other functions I want the code to do all happen. The obvious problem is the functions call each other in a loop, so moving the scale calls scale_has_moved() which calls the additional functions within and writes in the entry field, then because the entry field has been written in entry_field_has_been_written() gets called which in turn calls scale_has_moved() again, it doesn't go in an endless loop but it does everything at least twice everytime which affects performance.
Any clue how I'd fix this? Thank you
If you use the same variable for both widgets, they will automatically stay in sync. You don't need your two functions at all. The following code illustrates the technique.
import tkinter as tk
root = tk.Tk()
var = tk.IntVar(value=0)
scale = tk.Scale(root, variable=var, orient="horizontal")
entry = tk.Entry(root, textvariable=var)
scale.pack(side="top", fill="x")
entry.pack(side="top", fill="x")
root.mainloop()

Tkinter python scrollbar on selection

I'm trying to make a basic python interface for demo purposes and I really want to keep it simple. I have to use two scrollbars side by side. In scrollbarNR1 there are some user numbers and the other one has to be refreshed whenever someone clicks between the users. I tried to solve it with a recursive function by refreshing every 20 milliseconds my scrollbarNR2 but that just doesn't work because it clears my selection. My question is, how can I tell if my scrollbar was clicked and refresh the data just then?
My code is:
import tkinter
F1 = tkinter.Frame()
userScroll = tkinter.Scrollbar(F1)
userList = tkinter.Listbox(F1)
userScroll.pack(side=tkinter.RIGHT, fill=tkinter.Y)
userList.pack(side=tkinter.LEFT, fill=tkinter.Y)
userScroll['command'] = userList.yview
userList['yscrollcommand'] = userScroll.set
//here comes the insertion loop with the datas - irrelevant atm
def userSelect():
selectedUser = userList.get('active')
print(selectedUser)
userScroll.after(20, userSelect)

Python: Having trouble using .bind() on anything not involving the mouse

I can make a basic python application like so:
from tkinter import *
block = None
def moveUp(event):
field.move(block,0,-50)
root = Tk()
field = Canvas(root, width = 300, height = 300, bg = 'light blue')
field.pack()
block = field.create_rectangle(100,100,110,110)
field.bind('<Button-1>',moveUp)
mainloop()
and it will behave just like you would expect. It creates a square on a Canvas and moves that square up 50 pixels every time you click in the Canvas.
However, When I replace
field.bind('<Button-1>',moveUp)
to, for example,
field.bind('<Return>',moveUp)
the square does not move, no matter how many times I press the Enter key. This problem persists for any kind of keyboard input (e.g <space>, etc), but any input involving the mouse is fine.
Any input at all is appreciated. Thanks!
The field does not have focus, and therefore does not capture the keypress. One option is simply to make the binding more general:
field.bind('<Return>',moveUp)
to
root.bind('<Return>',moveUp)
Another option is to set the focus to the field:
field.bind('<Return>',moveUp)
field.focus_set()
Not entirely sure what's the reason, but it seems to work if you use bind_all instead of bind.
field.bind_all('<Return>',moveUp)
My guess is that using the keyboard, the canvas does not have focus and so does no register the event. Using bind_all, the event is registered when any widget of the application has focus.
See here for some information on levels of binding.

How can I display text AND variables in a tkinter text widget

I asked a question yesterday explaining a basic calculator I was making. So far, I haven't been able to get text to display AT ALL in a text widget. This is what I'm using:
text = Tk()
ans_text = Text(text, width=40, height=10)
ans_text.pack
ans_text.insert("1.0", 0, 'test')
mainloop()
That's just something from effbot with modified variables. I haven't bothered to create any classes or define functions, except for the mathematical functions used in this "calculator." I don't exactly see the need to.
So how can I get text to display? I've just seen stuff about making a canvas and I don't really understand that. I just went some letters and numbers to show up :P
The text (a zero) is being inserted, but the widget isn't visible which is why you aren't seeing it. You are forgetting the () when trying to pack the widget. Change the pack statement to look like this:
ans_text.pack()
With that modification your code will insert a zero as the first character of the text widget and apply the tag text to that character.
Whenever something doesn't appear the way you expect, a really good first thing to try is to temporarily give the widget a distinctive color. It will then become obvious whether the widget isn't working the way you want (eg: text isn't being inserted), or it's working the way you want but it's not visible on the screen.

Python tkinter .pack/.pack_forget memory issue

I've been teaching myself Python for a few months now and have proceed into learning some GUI techniques.
I wrote this simple script based off a pack_remove example I found within a book. My script simply displays local and UTC time every second. Granted the only difference is the hour, I would still like to redisplay every second.
The script works, yet my RAM is consistently increasing with every time display. I start out with around 4mb then after 2 hours or so the script uses 25mb. This makes some sense to me, but I was curious if there was a way display new times every second, but reduce the memory usage of such a simple clock display.
Or am I using an inefficient technique to re-display data in a GUI at a high frequency?
Here is my code:
from tkinter import *
import time
class TimeDisplay(Frame):
def __init__(self,msecs = 1000):
Frame.__init__(self)
self.msecs = msecs
self.pack()
utc_time = Label(self, text='')
utc_time.pack()
cst_time = Label(self, text='')
cst_time.pack()
self.utc_time = utc_time
self.cst_time = cst_time
self.repeater()
def repeater(self):
self.utc_time.pack_forget()
self.cst_time.pack_forget()
self.utc_time = Label(self, text= 'UTC: ' + time.strftime('%Y/%m/%d %H:%M:%S',time.gmtime()))
self.utc_time.pack()
self.utc_time.config(bg='navy',fg='white')
self.cst_time = Label(self, text= 'CST: ' + time.strftime('%Y/%m/%d %H:%M:%S',time.localtime()))
self.cst_time.pack()
self.cst_time.config(bg='navy',fg='white')
self.after(self.msecs, self.repeater)
if __name__ == '__main__': TimeDisplay(msecs=1000).mainloop()
Thanks in advance
pack_forget doesn't destroy anything, it just makes it non-visible. This is a GUI version of a memory leak -- you keep creating objects without ever destroying them.
So, the first lesson to learn is that you should destroy a widget when you are done with it.
The more important lesson to learn is that you don't have to keep destroying and recreating the same widget over and over. You can change the text that is displayed with the configure method. For example:
self.utc_time.configure(text="...")
This will make your program not use any extra memory, and even use (imperceptibly) less CPU.
To actually free widget's memory you should also call it's .destroy() method. This prevents memory leaking in your case.
However a more efficient way to implement the stuff is to associate string variable with Label widget like this:
v = StringVar()
Label(master, textvariable=v).pack()
v.set("New Text!")
see http://effbot.org/tkinterbook/label.htm for reference

Categories

Resources