Python3: GUI later cannot display output properly? - python-3.x

I'm designing a GUI application that converts between celsius and fahrenheit. For now, there're primarily two problems that I'm not able to tackle:
1) When I enter an integer that needs to be converted based on the given conversion formula, the Label from tkinter cannot display the output properly. In fact, it shows something like this:
<conversionModel.Conversion object at 0x1057b11d0>
which made it really difficult for debug to a beginner like me.
2) There's a quitButton, thought which we can destroy() the GUI application. The problem is that when I close the GUI by clicking the red cross of the window, the Shell says:
_tkinter.TclError: can't invoke "destroy" command: application has been destroyed
I checked answers to other questions regarding the same problem, it turned out that it was because this GUI application was destroyed before closing. I had no idea how to address this particular problem.
Below are three pieces of code written in Model/View/Controller form:
The Model in conversionModel.py:
class Conversion:
"""
class Conversion is the Model for a celsius-fahrenheit conversion
application. It converts celsius into fahrenheit and fahrenheit into
celsius.
"""
def toCelsius(self, temp):
return (5 / 9) * (temp - 32)
def toFahrenheit(self, temp):
return ((9 / 5) * temp) + 32
The View in conversionView.py:
import tkinter
class MyFrame(tkinter.Frame):
def __init__(self, controller):
tkinter.Frame.__init__(self)
self.pack()
self.controller = controller
self.tempEntry = tkinter.Entry()
self.tempEntry.insert(0, "0")
self.tempEntry.pack({"side": "left"})
self.celsiusButton = tkinter.Button(self)
self.celsiusButton["text"] = "Celsius"
self.celsiusButton["command"] = self.controller.buttonToC
self.celsiusButton.pack({"side": "left"})
self.fahrenheitButton = tkinter.Button(self)
self.fahrenheitButton["text"] = "Fahrenheit"
self.fahrenheitButton["command"] = self.controller.buttonToF
self.fahrenheitButton.pack({"side": "left"})
self.labelForOutput = tkinter.Label(self)
self.labelForOutput["text"] = 0
self.labelForOutput.pack ({"side": "left"})
self.quitButton = tkinter.Button(self)
self.quitButton["text"] = "Quit"
self.quitButton["command"] = self.quit
self.quitButton.pack({"side": "left"})
The Controller in controller.py:
import tkinter
import conversionView
import conversionModel
class Controller:
def __init__(self):
root = tkinter.Tk()
self.model = conversionModel.Conversion()
self.view = conversionView.MyFrame(self)
self.value = float(self.view.tempEntry.get())
self.view.mainloop()
root.destroy()
def buttonToC(self):
self.model.toCelsius(self.value)
self.view.labelForOutput["text"] = str(self.model) + " °C"
def buttonToF(self):
self.model.toFahrenheit(self.value)
self.view.labelForOutput["text"] = str(self.model) + " °F"
if __name__ == "__main__":
c = Controller()

For #1, you need to read the tempEntry control each time you do a conversion, and capture the result of the conversion for printing. As it is now you only read the tempEntry control on __init__, and str(self.model) just prints out the name of the model object. This should work:
def buttonToC(self):
fahr = float(self.view.tempEntry.get())
temp = self.model.toCelsius(fahr)
self.view.labelForOutput["text"] = str(temp) + " °C"
def buttonToF(self):
celsius = float(self.view.tempEntry.get())
temp = self.model.toFahrenheit(celsius)
self.view.labelForOutput["text"] = str(temp) + " °F"
For #2, I'm not familiar enough with Tk yet to know why the Quit button works correctly but the red X destroys the windows before you get around to calling root.destroy, but this should work around it:
self.view.mainloop()
try:
root.destroy()
except tkinter.TclError:
pass
The Quit button needs the destroy, but the X button doesn't and throws an exception. This code just ignores the exception in the X button case.

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 to thread with tkinter in python3 using queue?

I'm trying to thread definitions in tkinter using queue in specifically python3
I've had similar code in python2 work great using a similar method without queue but in python3 from what i've read tkinter doesn't allow for multithreading with gui. I found some examples that uses Queue process. They outline i'm suppose to create an Queue object, a new thread with access to that queue and check for input in the main thread
#!/usr/bin/python3
from tkinter import *
import time
import threading
import queue
import subprocess
def temp_sensor(queue_list):
warning = 0
while True:
var = "cat /sys/class/thermal/thermal_zone*/temp"
temp_control = subprocess.check_output([var], shell=True)
temp_length = len(temp_control)
temp_control = temp_control[35:]
temp_control = temp_control[:-4]
temp_control = int(temp_control)
degree_sign= u'\N{DEGREE SIGN}'
displayed_temp = "Tempature: " + str(temp_control) + degree_sign + "C"
if temp_control > 79:
warning = warning + 1
if warning == 3:
print ("Warning Core Tempature HOT!")
warning = 0
if temp_control > 90:
time.sleep(3)
print ("Warning EXTREMLY to HOT!!!")
queue_list.put(displayed_temp)
time.sleep(1)
class Gui(object):
def __init__(self, queue_list):
self.queue_list = queue_list
self.root = Tk()
self.root.geometry("485x100+750+475")
main_tempature_status = StringVar(self.root)
Ts = Entry(self.root, textvariable=main_tempature_status)
Ts.pack()
Ts.place(x=331, y=70, width=160, height=25)
Ts.config(state=DISABLED, disabledforeground="Black")
self.root.after(1000, self.read_queue)
def read_queue(self):
try:
temp = self.queue.get_nowait()
self.main_tempature_status.set(temp)
except queue_list.Empty:
pass
self.root.after(1000, self.read_queue)
if __name__ == "__main__":
queue_list = queue.Queue()
gui = Gui(queue_list)
t1 = threading.Thread(target=temp_sensor, args=(queue_list,))
t1.start()
gui.root.mainloop()
My desired result is to run a some of these definitions to do various tasks and display their variables in the tkinter entry using python3.
when i run my code it gives me the variable from the queue but it won't post to the GUI. please forgive my less then pythonic code.
Change you Gui class to this:
class Gui(object):
def __init__(self, queue_list):
self.queue_list = queue_list
self.root = Tk()
self.root.geometry("485x100+750+475")
self.main_tempature_status = StringVar(self.root)
self.Ts = Entry(self.root, textvariable=self.main_tempature_status)
self.Ts.pack()
self.Ts.place(x=331, y=70, width=160, height=25)
self.Ts.config(state=DISABLED, disabledforeground="Black")
self.root.after(1000, self.read_queue)
def read_queue(self):
try:
temp = self.queue_list.get_nowait()
self.Ts.config(state=NORMAL)
self.main_tempature_status.set(temp)
self.Ts.config(state=DISABLED)
except queue.Empty:
pass
self.root.after(1000, self.read_queue)
Explanation:
variable main_temperature_status is used in function read_queue as class variable, but not defined as class variable.
You cannot show the change in value of Entry widget if it is always disabled, so enabling it before value change in read_queue.

Trying to build Risk style game in Tkinter

I have been trying to build my skills in Python and am trying to create a risk style game.
I am not far in to it at the moment as I am trying to get to grips with classes and Tkinter.
My first trial is to create a series of buttons to take the place of the different countries. I then want these buttons to update the amount of armies on the country when they are clicked.
So far I have been able to get the map to generate from the class I have created and the buttons are clickable. When a button is clicked it updates the amount of armies but always for the last button.
How do I get it so that the button I click updates and not the last one?
Have I gone about this in entirely the wrong way?
from tkinter import *
import random
class territory:
def __init__ (self, country, player = "1", current_armies = 0, x=0, y=0):
self.country = country
self.current_armies = current_armies
self.player = player
self.y = y
self.x = x
def get_armies(self):
print(self.country + " has " + str( self.current_armies)+ " armies.")
def add_armies (self, armies):
self.current_armies += armies
def roll_dice (self, dice=1):
rolls = []
for i in range(0, dice):
rolls.append(random.randint(1,6))
rolls.sort()
rolls.reverse()
print (self.country + " has rolled " + str(rolls))
return rolls
def owner(self):
print (self.country + " is owned by " + self.player)
def get_country(self):
print(country)
def button (self):
Button(window, text = territories[0].current_armies, width = 10, command = click1(territories, 0)).grid(row=y,column=x)
window = Tk()
def create_territories():
countries = ["UK", "GER", "SPA", "RUS"]
terr_pos = [[1,0],[2,0],[1,5],[4,1]]
sta_arm = [1,1,1,1]
terr = []
player = "1"
for i in range(len(countries)):
terr.append(territory(countries[i],player, sta_arm [i] , terr_pos[i][0],terr_pos[i][1]))
if player == "1":
player = "2"
else:
player = "1"
return terr
def click1(territory, i):
territory[i].current_armies += 1
build_board(territory)
def build_board(territories):
for i in range(0,4):
Button(window, text = territories[i].country+"\n"+str(territories[i].current_armies), width = 10, command = lambda: click1(territories, i)).grid(row=territories[i].y,column=territories[i].x)
territories = create_territories()
window.title ("Domination")
create_territories()
build_board(territories)
window.mainloop()
In your def button(self):... you are always referencing territories[0]:
Button(window, text=territories[0].current_armies,... command=click1(territories, 0)...
As such, you are always using the first territory as your reference, so you ought to initialize each territory with its index in territories[] so you can pass that into your Button constructor.
On your question of "entirely the wrong way," I'd personally send that question over to CodeReview, since that's more of their domain (we fix broken code, they address smelly code), though there is significant overlap. We do prefer one question per question, however, and "is this whole thing wrong?" is a little broad for StackOverflow.

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