How to adjust Label in tkinter? - python-3.x

I am making a program that uses this equation:
16*(falling_speed)^2 = height
This basically takes the time you are falling and uses it to determine how high you are falling from.
I know how to use the equation, but my question is this, how to adjust the label to 1 second or 2 seconds?
I tried to make them seperate labels, but that didn't work either.
Here is my code:
from tkinter import *
from time import *
print("""This is an app that basically you time the amount of time someone takes to fall from a cliff, then we will
use an equation to tell you how high the cliff is.
This is a recreation of the app Mark Rober created, by The way""")
window = Tk()
window.title("falling app")
window.geometry("700x700")
window.configure(bg = "sky blue")
"""We will use time import for this"""
timer = Label(window, text = "0:00", font = ("verdana", 60))
timer.place(relx = 0.4, rely = 0.35, anchor = "nw")
def start():
mins = 0
seconds = 0
while seconds != 60:
sleep(1.00)
seconds+=1
if seconds == 60:
mins = mins+1
seconds = 0
This line: timer = Label(window, text = "0:00", font = ("verdana", 60))
is what makes the text. Is there a way to change text after you have created it?
Thanks in advance!!!

You can use timer["text"] = "some_text", or timer.config(text="some_text").
All widgets have the configure method which you can find good reference here.

Related

How to make button repeatedly use it's command when clicked python tkinter

How do I make a button use it's command whenever it is active? For example, if you hold a repeating button for 1 second, it should have executed it's command about 250 to 1000 times already right? But with a regular button, it just executes it one time, until you release the mouse and click it again. How can I make a button like that? I currently have this with moving,
def up(player):
x = 0
y = -10
c.move(player.rect,x,y)
def down(player):
x = 0
y = 10
c.move(player.rect,x,y)
def left(player):
x = -10
y = 0
c.move(player.rect,x,y)
def right(player):
x = 10
y = 0
c.move(player.rect,x,y)
#functions are in a class called player
p = player(sx1=950,sy1=540,sx2=975,sy2=565)
lup = lambda : tk.after(50,p.up)
upbtn = Button(tk,width=3,height=2,text="↑",command=lup)
upbtn.pack()
upbtn.place(x=960,y=700)
mainloop()
I searched on the web, but it didn't show any results consistent with my problem. Thanks in advance!
Use option repeatdelay and repeatinterval of tkinter.Button.
repeatdelay, the number of milliseconds to wait before starting to repeat.
repeatinterval, the number of milliseconds to be used between repeats
Simple demo code,
from tkinter import *
def on_click():
global count
count += 1
print(f'click {count}')
root = Tk()
count = 0
button = Button(root, text='Fire', repeatdelay=100, repeatinterval=100, command=on_click)
button.pack()
root.mainloop()

How to efficiently use Schedule Module in tkinter without getting GUI Freeze

I am new to python and tkinter and I made very basic program which check IP address ping or reachability in a given time range. I used Schedule module to schedule ping but actually after clicking Start Task, the GUI freeze while the code keeps running in the background. Probably the while loop is causing the freeze and I failed to solve this issue even after reviewing all stackoverflow mentioned solutions since no one addressed how to flawlessly use Schedule module in tkinter.
I wonder if there is a work-around to implement Schedule module without using while loop or with while loop without causing the GUI to freeze.
Thank you very much.
I made this fast sample for my current case.
Using Python 3.8.5
Target OS: Windows10 pro
Tested on VS Code
import tkinter as tk
import tkinter.font as tkFont
from pythonping import ping
from tkinter import *
from win10toast import ToastNotifier
import schedule
class App:
def __init__(self, root):
root.title("Ping Check")
width=600
height=400
screenwidth = root.winfo_screenwidth()
screenheight = root.winfo_screenheight()
alignstr = '%dx%d+%d+%d' % (width, height, (screenwidth - width) / 2, (screenheight - height) / 2)
root.geometry(alignstr)
root.resizable(width=False, height=False)
self.ip_address = StringVar()
self.seconds = IntVar()
self.n = ToastNotifier()
IP_Address=tk.Entry(root)
IP_Address["borderwidth"] = "1px"
ft = tkFont.Font(family='Times',size=10)
IP_Address["font"] = ft
IP_Address["fg"] = "#333333"
IP_Address["justify"] = "center"
IP_Address["textvariable"] = self.ip_address
IP_Address.place(x=250,y=70,width=270,height=32)
ip_address_label=tk.Label(root)
ft = tkFont.Font(family='Times',size=10)
ip_address_label["font"] = ft
ip_address_label["fg"] = "#333333"
ip_address_label["justify"] = "center"
ip_address_label["text"] = "Enter IP Address"
ip_address_label.place(x=60,y=70,width=139,height=30)
seconds_label=tk.Label(root)
ft = tkFont.Font(family='Times',size=10)
seconds_label["font"] = ft
seconds_label["fg"] = "#333333"
seconds_label["justify"] = "center"
seconds_label["text"] = "Enter Seconds"
seconds_label.place(x=50,y=170,width=143,height=30)
Seconds=tk.Entry(root)
Seconds["borderwidth"] = "1px"
ft = tkFont.Font(family='Times',size=10)
Seconds["font"] = ft
Seconds["fg"] = "#333333"
Seconds["justify"] = "center"
Seconds["textvariable"] = self.seconds
Seconds.place(x=250,y=170,width=272,height=30)
start_button=tk.Button(root)
start_button["bg"] = "#efefef"
ft = tkFont.Font(family='Times',size=10)
start_button["font"] = ft
start_button["fg"] = "#000000"
start_button["justify"] = "center"
start_button["text"] = "Start Task"
start_button.place(x=100,y=310,width=178,height=30)
start_button["command"] = self.Start_Task
stop_button=tk.Button(root)
stop_button["bg"] = "#efefef"
ft = tkFont.Font(family='Times',size=10)
stop_button["font"] = ft
stop_button["fg"] = "#000000"
stop_button["justify"] = "center"
stop_button["text"] = "Stop Task"
stop_button.place(x=330,y=310,width=172,height=30)
stop_button["command"] = self.Stop_Task
def ping_ip(self):
l = list(ping(self.ip_address.get()))
if not str(l[0]).startswith('Reply'):
self.n.show_toast("Warning!", "Unreachable IP Address, Error Ping Message: Request timed out!")
else:
self.n.show_toast("Successful reply!")
def Start_Task(self):
schedule.every(self.seconds.get()).seconds.do(self.ping_ip)
while True:
schedule.run_pending()
def Stop_Task(self):
schedule.cancel_job(self.Start_Task)
if __name__ == "__main__":
root = tk.Tk()
app = App(root)
root.mainloop()
GUI toolkits like tkinter are event-driven. To work properly, the
mainloop must continuously be able to process keyboard and mouse events.
When it is not processing events, it will start scheduled idle tasks.
So tkinter programs work quite differently from normal Python scripts.
A callback is called in response to activating a control (like clicking on
a button).
An idle task is started by the system after a specified number of milliseconds
when the system is not busy processing events. You can schedule idle tasks
with the Tk.after() method.
Basically, the callbacks and idle tasks are your program.
But they are run from within the main loop.
So whatever you do in a call-back, it should not take too long. Otherwise the GUI becomes unresponsive.
So using while True in a call-back is not a good idea.
In a Python 3 tkinter program, when you want to perform a long-running task especially if it involves disk access or network activity, you should probably do that in a second thread. (In Python 3 tkinter is mostly thread-safe.)
Edit 1:
Instead of using pythonping and schedule, use subprocess.Popen to run the ping program asyncronously:
import subprocess as sp
# class App:
# et cetera...
def query_task(self):
if self.process:
if self.process.returncode:
# The ping has finished
if self.process.returncode != 0:
self.n.show_toast("Warning:", f"ping returned {self.process.returncode}")
else: # ping has finished successfully.
# self.process.stdout and self.process.stderr contain the output of the ping process...
pass
# So a new task can be started.
self.process = None
else:
# check again after 0,5 seconds.
self.after(0.5, self.query_task);
# By convention, method names should be lower case.
def start_task(self):
if self.process is None:
self.process = sp.Popen(
["ping", self.ip_address.get()], stdout=sp.Pipe, stderr=sp.Pipe
)
# Check after 0.5 seconds if the task has finished.
self.after(0.5, self.query_task);

How to get screenshot and change DPI on the clipboard?

Under Win7 I would like to get the content of a window on the clipboard and set/adjust the DPI setting on the clipboard and copy it to a final application.
The MCVE below is not yet working as desired.
There is an issue:
sometimes it can happen that apparently the window is not yet set to foreground and the ImageGrab.grab(bbox) gets the wrong content. Waiting for some time (2-5 sec) helps, but is not very practical. How to avoid or workaround this?
Here is the code:
from io import BytesIO
from PIL import Image,ImageGrab
import win32gui, win32clipboard
import time
def get_screenshot(window_name, dpi):
hwnd = win32gui.FindWindow(None, window_name)
if hwnd != 0:
win32gui.SetForegroundWindow(hwnd)
time.sleep(2) ### sometimes window is not yet in foreground. delay/timing problem???
bbox = win32gui.GetWindowRect(hwnd)
screenshot = ImageGrab.grab(bbox)
width, height = screenshot.size
lmargin = 9
tmargin = 70
rmargin = 9
bmargin = 36
screenshot = screenshot.crop(box = (lmargin,tmargin,width-rmargin,height-bmargin))
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
output = BytesIO()
screenshot.convert("RGB").save(output, "BMP", dpi=(dpi,dpi))
data = output.getvalue()[14:]
output.close()
win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
win32clipboard.CloseClipboard()
print("Screenshot taken...")
else:
print("No window found named:", window_name)
window_name = "Gnuplot (window id : 0)"
get_screenshot(window_name,200)
Edit:
also this attempt to improve still gets sometimes the wrong content. Maybe somebody can explain why?
win32gui.SetForegroundWindow(hwnd)
for i in range(1000):
print(i)
time.sleep(0.01)
if win32gui.GetForegroundWindow() == hwnd:
break
bbox = win32gui.GetWindowRect(hwnd)
Addition:
That's what I (typically) get when I remove the line with the delay time time.sleep(2).
Left: desired content, right: received content. How can I get a reliable capture of content the desired window? What's wrong with the code? The larger I set the delay time the higher the probability that I get the desired content. But I don't want to wait several seconds to be sure. How can I check whether the system is ready for a screenshot?
As discussed you can use the approach as discussed in below
Python Screenshot of inactive window PrintWindow + win32gui
import win32gui
import win32ui
from ctypes import windll
import Image
hwnd = win32gui.FindWindow(None, 'Calculator')
# Change the line below depending on whether you want the whole window
# or just the client area.
#left, top, right, bot = win32gui.GetClientRect(hwnd)
left, top, right, bot = win32gui.GetWindowRect(hwnd)
w = right - left
h = bot - top
hwndDC = win32gui.GetWindowDC(hwnd)
mfcDC = win32ui.CreateDCFromHandle(hwndDC)
saveDC = mfcDC.CreateCompatibleDC()
saveBitMap = win32ui.CreateBitmap()
saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)
saveDC.SelectObject(saveBitMap)
# Change the line below depending on whether you want the whole window
# or just the client area.
#result = windll.user32.PrintWindow(hwnd, saveDC.GetSafeHdc(), 1)
result = windll.user32.PrintWindow(hwnd, saveDC.GetSafeHdc(), 0)
print result
bmpinfo = saveBitMap.GetInfo()
bmpstr = saveBitMap.GetBitmapBits(True)
im = Image.frombuffer(
'RGB',
(bmpinfo['bmWidth'], bmpinfo['bmHeight']),
bmpstr, 'raw', 'BGRX', 0, 1)
win32gui.DeleteObject(saveBitMap.GetHandle())
saveDC.DeleteDC()
mfcDC.DeleteDC()
win32gui.ReleaseDC(hwnd, hwndDC)
if result == 1:
#PrintWindow Succeeded
im.save("test.png")
Thanks to #Tarun Lalwani pointing to
this answer, I finally have a code which is working for me for the time being. However, it seems to me quite lengthy with a lot of different modules. Maybe it still can be simplified. Suggestions are welcome.
Code:
### get the content of a window and crop it
import win32gui, win32ui, win32clipboard
from io import BytesIO
from ctypes import windll
from PIL import Image
# user input
window_name = 'Gnuplot (window id : 0)'
margins = [8,63,8,31] # left, top, right, bottom
dpi = 96
hwnd = win32gui.FindWindow(None, window_name)
left, top, right, bottom = win32gui.GetWindowRect(hwnd)
width = right - left
height = bottom - top
crop_box = (margins[0],margins[1],width-margins[2],height-margins[3])
hwndDC = win32gui.GetWindowDC(hwnd)
mfcDC = win32ui.CreateDCFromHandle(hwndDC)
saveDC = mfcDC.CreateCompatibleDC()
saveBitMap = win32ui.CreateBitmap()
saveBitMap.CreateCompatibleBitmap(mfcDC, width, height)
saveDC.SelectObject(saveBitMap)
result = windll.user32.PrintWindow(hwnd, saveDC.GetSafeHdc(), 0)
bmpinfo = saveBitMap.GetInfo()
bmpstr = saveBitMap.GetBitmapBits(True)
im = Image.frombuffer( 'RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']),
bmpstr, 'raw', 'BGRX', 0, 1).crop(crop_box)
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
output = BytesIO()
im.convert("RGB").save(output, "BMP", dpi=(dpi,dpi))
data = output.getvalue()[14:]
output.close()
win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
win32clipboard.CloseClipboard()
win32gui.DeleteObject(saveBitMap.GetHandle())
saveDC.DeleteDC()
mfcDC.DeleteDC()
win32gui.ReleaseDC(hwnd, hwndDC)
print('"'+window_name+'"', "is now on the clipboard with", dpi, "dpi.")
### end of code

Use ipython widgets to manipulate plots, that are constantly updated from within an infinite loop

Within an ipython notebook, using Jupyter I try to do a calculation, thats running for an extended period of time in a while loop. I use pyplot to show the current status of my calculation and I would like to be able to communicate with the calculation and the plot via ipywidgets. For example this could be a stop button for the calculation, or a slider that adjusts the update rate of the plot.
Below I show a minimal example of a sin function, that is constantly plotted with different phase shifts. The start button starts the calculation, the stop button stops it and the slider should adjust the speed at which the whole thing is updated. The textbox shows the current phase shift.
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import *
import IPython.display as display
import _thread
import time
%matplotlib nbagg
# set up widgets
buttons = widgets.ToggleButtons(
options=['Stop', 'Start'],
description='',
disabled=False,
)
text = widgets.Text(
value='0',
placeholder='',
description='Shift [rad]',
)
speed_slider = widgets.IntSlider(
description = "Speed",
value = 100,
min = 10,
max = 500,
step = 10,
)
container = widgets.HBox(children = [buttons, speed_slider, text])
display.display(container)
# functions
def mySin(x, x0):
return np.sin(x-x0)
def run_app(x, dx0):
x0 = np.remainder(np.float(text.value), 2*np.pi)
while buttons.value == buttons.options[1]:
x0 = np.remainder(x0 + dx0, 2*np.pi)
line.set_ydata(mySin(x, x0))
text.value = str(x0)
time.sleep(speed_slider.value/1000)
fig.canvas.draw()
# setup plot
N = 1000
x = np.linspace(0, 10*np.pi, N)
dx0 = 0.05*2*np.pi
fig, ax = plt.subplots(ncols = 1, nrows = 1, figsize = (8,3))
line = ax.plot(x, mySin(x, np.float(text.value)))[0]
# act on button change
def buttons_on_changed(val):
if buttons.value == buttons.options[1]:
_thread.start_new_thread(run_app, (x, dx0))
buttons.observe(buttons_on_changed)
I try to run the function "run_app" in a new thread in order to make the interaction possible. I'm aware that _thread is deprecated, but I would like to know first, wether this is the right way to go. The example above works more or less, but the execution stops after a couple of seconds or when I do something else in the notebook (scrolling, clicking, ...). So my questions are the following:
Is the Thread closing or just losing priority and why is it doing that?
Is that a good approach at all, or can that be achieved in an easier way. Unfortunately, I haven't found anything browsing the web.
Thanks a lot,
Frank

How to create GUI objects one by one with Tkinter

I like to create an object per second and make the show up one by one. However, the code below wait for 3 seconds and show them all at the same time.
from tkinter import *
import time
def create():
for i in range(3):
r4=Radiobutton(root, text="Option 1"+str(i), value=1)
r4.pack( anchor = W )
time.sleep(1)
root = Tk()
create()
root.mainloop()
Your code, as is, creates a one object per second as you desired it, but this objects need to be shown, and they're shown when code flow reaches the mainloop. Hence, for observer, it looks like there're no objects at all after one second.
Of course, you can use sleep and update, but beware - sleeping leads to unresponsive window, so it's OK option (to be honest - not OK at all), if your application isn't drawn and you're outside of mainloop, but if it's not - prepare for a "frozen" window, because GUI can redraw himself only in mainloop (an event loop, you can reach it with update as well) and sleep blocks this behaviour.
But there's a good alternative, the after method, take a look on it!
And there's a snippet, so you can see the difference:
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
import time
def create_with_sleep():
for _ in range(3):
tk.Radiobutton(frame_for_sleep, text="Sleep Option").pack(anchor='w')
time.sleep(int(time_entry.get()))
root.update()
def create_with_after(times=3):
if times != 0:
tk.Radiobutton(frame_for_after, text="After Option").pack(anchor='w')
times -= 1
root.after(int(time_entry.get()) * 1000, lambda: create_with_after(times))
root = tk.Tk()
test_yard_frame = tk.Frame(root)
frame_for_sleep = tk.Frame(test_yard_frame)
frame_for_after = tk.Frame(test_yard_frame)
test_yard_frame.pack()
frame_for_sleep.pack(side='left')
frame_for_after.pack(side='left')
button_frame = tk.Frame(root)
button_for_sleep = tk.Button(button_frame, text='Create 3 radiobuttons with sleep+update', command=create_with_sleep)
button_for_after = tk.Button(button_frame, text='Create 3 radiobuttons with after', command=create_with_after)
button_frame.pack()
button_for_sleep.pack(side='left')
button_for_after.pack(side='left')
time_label = tk.Label(root, text='Time delay in seconds:')
time_label.pack(fill='x')
time_entry = tk.Entry(root)
time_entry.insert(0, 1)
time_entry.pack(fill='x')
root.mainloop()
With 1 seconds delay there's no much difference, but you can try to increase delay to understand why after option is preferable in general.
You can use update to call a refresh on your objects.
In use, you would have to add the line root.update() in your for loop.

Resources