How to loop this code? - python-3.x

I have trying to loop this but fail everytime.
It´s the def create_widgets I try to loop. So I got a GUI that show a red button/box as soon something goes offline.
This is the code I tried to use.
from tkinter import *
class Application(Frame):
""" GUI """
def __init__(self, master):
""" Initialize the Frame"""
Frame.__init__(self,master)
self.grid()
self.create_widgets()
def create_widgets(self):
"""Create button. """
import os
#Router
self.button1 = Button(self)
self.button1["text"] = "Router"
self.button1["fg"] = "white"
self.button1.grid(padx=0,pady=5)
#Ping
hostname = "10.18.18.1"
response = os.system("ping -n 1 " + hostname)
#response
if response == 0:
self.button1["bg"] = "green"
else:
self.button1["bg"] = "red"
root = Tk()
root.title("monitor")
root.geometry("500x500")
app = Application(root)
root.mainloop()

You can attach it onto Tk's event loop using Tk's after method.
def if_offline(self):
#Ping
hostname = "10.18.18.1"
response = os.system("ping -n 1 " + hostname)
#response
if response == 0:
self.button1["bg"] = "green"
else:
self.button1["bg"] = "red"
Then, this line goes anywhere between app = Application(root) and root.mainloop():
root.after(0, app.if_offline)
after attaches a process onto the Tk event loop. The first argument is how often the process should be repeated in milliseconds, and the second is the function object to be executed. Since the time we have specified is 0, it will constantly check and constantly update the button's background color. If that churns your CPU, or you don't want to be pinging that much, you can change the repeat time to something larger.
The function object passed in should be just that: a function object. It has the same rules as the command argument in a Button constructor.
If you need to pass in arguments to the function, use a lambda like so:
root.after(0, lambda: function(argument))
This works because the lambda function returns a function object, which when executed will run function(argument).
Source

Related

Separating Tkinters GUI and control of the application

1) What is my goal:
I’m creating an application that should read data every 60s from ModBusServer, append those data to Graphs and then when the app is closed save the data to excel file.
Site note:
The process of reading data from ModBusServer and appending them to graphs should start after a start button is pressed.
And end after stop button is pressed OR when ModBusServer sends a request to stop.
2) What I have so far:
I created the GUI without any major problems as a class “GUI_komora”.
Everything there works just fine.
3) What is the problem:
But now I’m lost on how to approach the “read data every 60 seconds”, and overall how to control the application.
I did some research on threading but still I’m confused how to implement this to my application.
I learned how to make functions run simultaneously in this tutorial.
And also how to call a function every few seconds using this question.
But none of them helped me to learn how to control the overall flow of the application.
If you could redirect me somewhere or tell me about a better approach I would be really glad.
Some of my code:
from tkinter import *
from GUI_komora import GUI
root = Tk()
my_gui = GUI(root) #my GUI class instance
#main loop
root.mainloop()
"""
How do I achieve something like this???
whenToEnd = False
while whenToEnd:
if step == "Inicialzation":
#inicializace the app
if step == "ReadData":
#read data every 60 seconds and append them to graphs
if step == "EndApp"
#save data to excel file and exit app
whenToEnd = True
"""
Here is an example of a loop that takes a decision (every 60 sec in your case) and pushes the outcome of the decision to tkinter GUI: https://github.com/shorisrip/PixelescoPy/blob/master/base.py
Parts:
main thread - starts tkinter window
control thread - reads some data and decides what to show in GUI
GUI class - has a method "add_image" which takes input an image and displays on GUI.(add_data_to_graph maybe in your case). This method is called everytime by the control thread.
Snippets:
def worker(guiObj, thread_dict):
# read some data and make decision here
time.sleep(60)
data_to_show = <outcome of the decision>
while some_logic:
pictureObj = Picture(chosen, timer)
pictureObj.set_control_thread_obj(thread_dict["control_thread"])
guiObj.set_picture_obj(pictureObj)
pictureObj.display(guiObj)
# do post display tasks
guiObj.quit_window()
# Keep GUI on main thread and everything else on thread
guiObj = GuiWindow()
thread_list = []
thread_dict = {}
thread_for_image_control = threading.Thread(target=worker, args=(guiObj,
thread_dict))
thread_dict["control_thread"] = thread_for_image_control
thread_list.append(thread_for_image_control)
thread_for_image_control.start()
guiObj.run_window_on_loop()
# thread_for_image_control.join()
Code for Picture class:
class Picture:
def __init__(self, path, timer):
self.path = path
self.timer = timer
self.control_thread_obj = None
def set_control_thread_obj(self, thread_obj):
self.control_thread_obj = thread_obj
def display(self, guiObj):
image_path = self.path
guiObj.add_image(image_path)
time.sleep(self.timer)
Code for GUI class
class GuiWindow():
def __init__(self):
self.picture_obj = None
self.root = Tk()
self.image_label = None
self.image = None
self.folder_path = None
self.timer = None
self.root.protocol("WM_DELETE_WINDOW", self.exit_button)
def add_image(self, image_path):
resized_img = self.resize(image_path)
image_obj = ImageTk.PhotoImage(resized_img)
image_label = Label(self.root, image=image_obj,
height=resized_img.height,
width=resized_img.width)
self.image = image_obj # DO NOT REMOVE - Garbage collector error
if self.image_label is not None:
self.remove_image()
image_label.grid(row=0, column=0, columnspan=3)
self.image_label = image_label
Here based on my control loop thread I am changing image (in your case graph data) of the tkinter GUI.
Does this help?

How do I make sure all GUI input variables can be accessed by certain classes and function calls?

When I provide an address /and or location to the entry bar and I press the "Get forecast" button the script fails at line 22. I think the error is raised because the str(address.get()) cant find the address variable, probably because it doesn't technically exist during that point of run time (I'm not able to log the error due to the structure of that function).
My question is; How do I make sure that my "get_hourly_forecast" function is able to access the address entry variable?
I have tried instantiating the address variable in various locations, e.g in the MainWeatherHub class, as well as in the MyWeatherApp class and then passing it as an argument to the MainWeatherHub in line 79, neither variation has worked. The current code shows the former variation.
import urllib, json, requests
from tkinter import *
from tkinter import ttk
def get_hourly_forecast(*args):
## params *args:
#A location argument
#Returns:
# A list of temps in Farenheit for the next 156 hours
API_KEY = 'removing my API key for security purposes'
try:
print('here') # The code makes it to here
curr_address = str(address.get()) # Code seems to fail here (not sure how to have the error print)
print('here')
geocode_url = "https://maps.googleapis.com/maps/api/geocode/json?address={}&key={}".format(cur_address, API_KEY)
response = requests.get(geocode_url)
response_dict = response.json()['results']
location = response_dict[0]['geometry']['location']
lat = location['lat']
lng = location['lng']
local_url_request = 'https://api.weather.gov/points/lat={}lng={}'.format(lat, lng)
response_one = requests.get(local_url_request)
json_dict_one = response_one.json()
local_props = json_dict_one['properties']
local_forecast_request = local_props['forecastHourly']
resposne_two = requests.get(local_forecast_request)
json_dict_two = resposne_two.json()
local_forecast_properites = json_dict_two['properties']
hourly_updates = local_forecast_properites['periods']
out = []
for i in hourly_updates:
for key, value in i.items():
if key == "temperature":
out.append(value)
current_weather.set(out[0])
except:
print("Not working.")
#############################################################
class MyWeatherApp:
"""
MyWeatherApp is the primary Frame for this GUI application
"""
def __init__(self, master):
super(MyWeatherApp, self).__init__()
self.master = master
# Create the main window Frame
master_style = ttk.Style()
master_style.configure('Master.TFrame')
self.master.title("My Weather")
self.master.geometry("500x500")
MWA = ttk.Frame(self.master, style='Master.TFrame')
MWA.place(relheight=1.0, relwidth=1.0)
# Run other widgets within this class
MainWeatherHub(MWA)
#############################################################
class MainWeatherHub(MyWeatherApp):
"""
The MainWeatherHub (MWH) is the top panel of the app
"""
def __init__(self, mainwindow):
super(MyWeatherApp, self).__init__()
self.mainwindow = mainwindow
# Create a Frame for the MainWeatherHub
MWH_style = ttk.Style()
MWH_style.configure('MWH.TFrame')
MWH = ttk.Frame(self.mainwindow, style='MWH.TFrame', relief='sunken')
MWH.place(relheight=0.33, relwidth=0.95, relx=0.025, rely=0.025)
# Create an entry widget to take a location
# and store that as a loction variable.
address = StringVar()
loc_entry = ttk.Entry(MWH, textvariable=address)
loc_entry.place(relheight=0.30, relwidth=.95, relx=0.025, rely=0.05)
# Get weather button finds weather for the users location
current_weather = StringVar()
get_weather_button = ttk.Button(loc_entry, text="Get Forecast", command=get_hourly_forecast)
get_weather_button.place(relheight=0.85,relwidth=0.2, relx=0.79, rely=0.075)
#Display weather in the Message widget
weath_display = Message(MWH, textvariable=current_weather)
weath_display.place(relwidth=0.95, relheight=0.55, relx=0.025, rely=0.375)
root = Tk()
my_gui = MyWeatherApp(root)
root.mainloop()
If this script works properly, it should return the current temperature in degrees Fahrenheit of the location that was provided in the entry bar.
You should send it as parameter
def get_hourly_forecast(cur_address):
geocode_url = "...".format(cur_address, API_KEY)
And then assing to button function which runs get_hourly_forecast with string
class MainWeatherHub(MyWeatherApp):
def __init__(self, mainwindow):
self.address = StringVar() # use self.
ttk.Button(loc_entry, text="Get Forecast", command=run_it)
def run_it(self):
get_hourly_forecast(self.address.get())
or using lambda
class MainWeatherHub(MyWeatherApp):
def __init__(self, mainwindow):
ttk.Button(loc_entry, text="Get Forecast", command=lambda:get_hourly_forecast(address.get()))
EDIT:
I see you use current_weather (StringVar from MainWeatherHub) in get_hourly_forecast to set value current_weather.set(out[0]).
You could send current_weather to get_hourly_forecast as parameter
def get_hourly_forecast(cur_address, current_weather):
geocode_url = "...".format(cur_address, API_KEY)
current_weather.set(out[0])
and
class MainWeatherHub(MyWeatherApp):
def __init__(self, mainwindow):
self.address = StringVar() # use self.
self.current_weather = StringVar() # use self.
ttk.Button(loc_entry, text="Get Forecast", command=run_it)
def run_it(self):
get_hourly_forecast(self.address.get(), self.current_weather)
but it could be better to return value from get_hourly_forecast
def get_hourly_forecast(cur_address):
geocode_url = "...".format(cur_address, API_KEY)
return out[0]
and get it in run_it
def run_it(self):
result = get_hourly_forecast(self.address.get())
if result is not None:
self.current_weather.set(result)
This way get_hourly_forecast doesn't work with StringVar and you can use it in other program which doesn't use StringVar.

TkInter: how to get actual text modification?

I want to know where and what was changed by user in tkinter's Text widget.
I've found how to get that text was somehow modified by using <<Modified>>event but I can't get actual changes:
from tkinter import *
def reset_modified():
global resetting_modified
resetting_modified = True
text.tk.call(text._w, 'edit', 'modified', 0)
resetting_modified = False
def on_change(ev=None):
if resetting_modified: return
print ("Text now:\n%s" % text.get("1.0", END))
if False: # ????
print ("Deleted [deleted substring] from row %d col %d")
if False: # ????
print ("Inserted [inserted substring] at row %d col %d")
reset_modified()
resetting_modified = False
root = Tk()
text = Text(root)
text.insert(END, "Hello\nworld")
text.pack()
text.bind("<<Modified>>", on_change)
reset_modified()
root.mainloop()
For example, if I select 'ello' part from "hello\nworld" in Text widget then I press 'E', then I want to see
"Deleted [ello] from row 0 col 1" followed by "Inserted [E] at row 0 col 1"
is it possible to get such changes (or at least their coordinates) or I have basically to diff text on each keystroke if I want to detect changes run time?
Catching the low level inserts and deletes performed by the underlying tcl/tk code is the only good way to do what you want. You can use something like WidgetRedirector or you can do your own solution if you want more control.
Writing your own proxy command to catch all internal commands is quite simple, and takes just a few lines of code. Here's an example of a custom Text widget that prints out every internal command as it happens:
from __future__ import print_function
import Tkinter as tk
class CustomText(tk.Text):
def __init__(self, *args, **kwargs):
"""A text widget that report on internal widget commands"""
tk.Text.__init__(self, *args, **kwargs)
self._orig = self._w + "_orig"
self.tk.call("rename", self._w, self._orig)
self.tk.createcommand(self._w, self.proxy)
def proxy(self, command, *args):
# this lets' tkinter handle the command as usual
cmd = (self._orig, command) + args
result = self.tk.call(cmd)
# here we just echo the command and result
print(command, args, "=>", result)
# Note: returning the result of the original command
# is critically important!
return result
if __name__ == "__main__":
root = tk.Tk()
CustomText(root).pack(fill="both", expand=True)
root.mainloop()
After digging around, I've found that idlelib has WidgetRedirector which can redirect on inserted/deleted events:
from tkinter import *
from idlelib.WidgetRedirector import WidgetRedirector
def on_insert(*args):
print ("INS:", text.index(args[0]))
old_insert(*args)
def on_delete(*args):
print ("DEL:", list(map(text.index, args)))
old_delete(*args)
root = Tk()
text = Text(root)
text.insert(END, "Hello\nworld")
text.pack()
redir = WidgetRedirector(text)
old_insert=redir.register("insert", on_insert)
old_delete=redir.register("delete", on_delete)
root.mainloop()
Though it seems hacky. Is there a more natural way?

Multithreading in Tkinter

I am new to Python and Tkinter but I was wondering if I could do Multithreading in Tkinter.
I have a Restaurant Simulation program and whenever it accepts an order, this is what should happen:
1) Timer that counts down and shows how many seconds are left before the order is done
2) While the Timer is counting, I want to create another instance of the Restaurant Simulation so that it could accept another order.
This is what I have tried:
from Tkinter import *
class food():
def __init__(self):
self.inventory = []
def cookOrder(self,type):
if type is 'AA': #Cook Barf
self.inventory[0]+=1
class Restaurant():
def Callback(self,root):
if tkMessageBox.askokcancel("Quit", "Do you really wish to quit?"):
root.destroy()
def MainMenu(self):
self.Main = Tk()
self.Main.grid()
Title = Label(self.Main,text = "Welcome to Resto!",font=("Bauhaus 93",20))
Title.grid(columnspan=6)
RestoMenu = Button(self.Main,text = "Order Food",command = lambda:self.Restaurant_Menu('AA'),font=("High Tower Text",12))
RestoMenu.grid(row=2,column=0)
self.Main.mainloop()
def Restaurant_Menu(self,type):
self.Main.destroy()
self.setCookTime(type)
def setCookTime(self,type):
self.MainMenu() #This is not working as I planned it to be
self.cookTimer = Tk()
self.timeLabel = Label(text="")
self.timeLabel.pack()
if type is "AA":
self.Tick(10,type)
self.cookTimer.mainloop()
self.cookTimer.wm_protocol ("WM_DELETE_WINDOW", self.Callback(self.cookTimer)) #Getting TCL Error
def Tick(self,time,type):
time -=1
if time ==-1:
food.cookOrder(type)
self.cook.destroy()
else:
self.timeLabel.configure(text="%d" %time)
self.cookTimer.after(1000, lambda: self.Tick(time,type))
Food = food()
Resto = Restaurant()
Resto.MainMenu()
Now, the problem with the code is, it is not creating another instance of the Restaurant while the countdown is going on. Any help?
Plus, how do you ask the user for the confirmation of exiting program when he clicks the exit button while the timer is ticking, using WM_DELETE_WINDOW?

Making a basic user interface with tkinter-python

I've just been trying to practice some code by making a simple dice game
and using tkinter for the user interface of the starting menu for the game
For the starting menu, I'm trying to just see how it will come out if I used the code below,
BUT before making the Button widget and the Label Widgets, the commands come up first.
How would I fix this up?
thanks in advance
import tkinter as tk
from main import main
from written import showInstructions, showCredits
from generate_no import generate_no
class DiceGameUI(tk.Frame):
def __init__(self, master = None):
tk.Frame.__init__(self, master)
self.grid()
self.createWidgets()
def createWidgets(self):
self.titleLabel = tk.Label(self, fg = "red") #The Title of the Game
self.titleLabel["text"] = "Dice Game"
self.startButton = tk.Button(self) #Start Button
self.startButton["text"] = "Roll On!"
self.startButton["command"] = main() <<----- This plays out first before
self.startButton.grid() making any widgets
self.instrButton = tk.Button(self) #Instructions Button
self.instrButton["text"] = "Instructions"
self.instrButton["command"] = showInstructions()
self.instrButton.grid()
self.credits = tk.Button(self) #Credits Button
self.credits["text"] = "Credits"
self.credits["command"] = showCredits()
self.credits.grid()
root = tk.Tk() #Run code using tkinter
app = DiceGameUI(master = root)
app.mainloop()
'
You have to assign only name of function without () and arguments
self.startButton["command"] = main
If you use () than you run that function and result is assigned to command. It is good to create dynamicly function for command.
If you will need assign function which require arguments you have to use lambda function.
self.startButton["command"] = lambda:main()
self.startButton["command"] = lambda:main("abc", 123)
a = "abc"
b = 123
self.startButton["command"] = lambda arg1=a,arg2=b:main(arg1,arg2)
self.startButton["command"] = lambda title=a,count=b:main(title,count)
# this may not work - especially if a or b changes value (for example in loop)
self.startButton["command"] = lambda:main(a, b)
example how to use function name in own code
def plus(a, b):
return a + b
def minus(a, b):
return a - b
def result(a, b, func_name):
return func_name(a,b)
print result(10, 7, plus) # 17
print result(10, 7, minus) # 3

Resources