I was developing a simple clock app which includes the features of: clock, timer, stopwatch and potentially world clock with live conversions - just a project to get me back into programming. To challenge myself, I wanted to make it under an object oriented approach of which I set a main object with methods.
What I am struggling with is with the sidebar functionality, I already have it and it looks to my liking, but I am struggling to reference it with the current frame of what will be appearing "in the red box" to switch it with the other menus of the other technology in my app. Yes, I save it under a frame and try to use the switch_frame function, however, I am in questioning of how to reference each instance of the object's frame.
Image of the running tkinter app with the sidebar and the frame of which changes menus highlighted in red
PS: I have the class as a ttkbootstrap object in order to have a better looking app.
"""
Goofy agh clock app to get me back into programming.
To make it harder, I limited it to only Object-Oriented programming and also uses ttkboostrap to at least make it
remotely look good.
Features:
Clock, timer (with included sounds), stop watch.
"""
from PIL import Image, ImageTk
import tkinter as tk
import ttkbootstrap as ttb
from ttkbootstrap.constants import *
from datetime import *
class ClockApp:
def __init__(self):
self.root = ttb.Window(themename="darkly")
self.root.title("Clock")
self.root.geometry("500x500")
self.root.resizable(False, False)
self.root.iconphoto(False, ImageTk.PhotoImage(file="clock_icon.png"))
self.side_panel = ttb.Frame(self.root, width=75, height=500, bootstyle="info")
self.side_panel.grid(rowspan=4, column=0)
clock_image = Image.open("clock_icon.png")
resized_clock = clock_image.resize((50, 50))
timer_image = Image.open("timer_icon.png")
resized_timer = timer_image.resize((50, 50))
used_clock_image = ImageTk.PhotoImage(resized_clock)
used_timer_image = ImageTk.PhotoImage(resized_timer)
self.clock_button = ttb.Button(self.root, image=used_clock_image, bootstyle=INFO)
self.clock_button.image = used_clock_image
self.clock_button.grid(row=0, column=0)
self.timer_button = ttb.Button(self.root, image=used_timer_image, bootstyle=INFO)
self.timer_button.image = used_timer_image
self.timer_button.grid(row=1, column=0)
def update_time(self):
new_time = datetime.now()
new_string_time = new_time.strftime("%H : %M : %S")
time_label.config(text=new_string_time)
self.root.after(1000, self.update_time)
def switch_frame(self, current_frame, new_frame):
print("Button has been pressed")
def side_buttons_manager(self, button):
pass
if __name__ == '__main__':
clock = ClockApp()
now_time = datetime.now()
string_time = now_time.strftime("%H : %M : %S")
time_frame = ttb.Frame(clock.root)
time_frame.grid(row=1, column=1)
time_label = ttb.Label(time_frame, text=string_time,
font=("Arial Greek", 32, "bold"), bootstyle=INFO)
time_label.grid(row=1, column=0, padx=100)
clock.update_time()
stopwatch_frame = ttb.Frame(clock.root)
stopwatch_label = ttb.Label(stopwatch_frame, text="This is another Frame for testing")
# I want to somehow select the button object from my class but I guess, I can not change or add attributes to the button widget or when selecting it, it would create a new one when I set the grid layout of it (ofc it would but I have no idea how to reference it from the object).
clock.root.mainloop()
I have tried to call the button object inside the main object in order to add a command attribute to it however it was unable to work as that is wrong syntax with it. Is there a way I can do this or I have to differ the very construction of my object?
Should I create multiple objects of each containing their frames of features? If so, how can I recycle the sidebar content I had to make the very aspect of the object oriented approach even worth it?
I would like at least the thought process behind the construction with the OOP approach with tkinter and ttkbootstrap however I will really appreciate if there is any example code for how this would work - with annotated theory in just few key lines of code.
Thank you in advance, I will edit this post if I find anything otherwise.
Related
I'm creating a program that displays a graph of real estate in cities in the States. I was able to create a main function, which mainly concerns creating a graph of each city.
Now, I am trying to add a new feature, which lets users choose one city among many options by creating a combobox.
What I want to make is basically just allows users to click one option among many cities in combobox, and when users click it, it should automatically call the main function so that the main function can generate the selected graph.
I am using tkinter and Custom Tkinter modules for my GUI design.
Code:
#Libraries
(...) # This is for graphing features
#Tkinter
from tkinter import *
import tkinter as tk
from PIL import ImageTk, Image
import customtkinter as ctk
import requests
import tkinter.messagebox
ctk.set_appearance_mode("Light")
ctk.set_default_color_theme("dark-blue")
class App(ctk.CTk,tk.Tk):
WIDTH = 780
HEIGHT = 520
def __init__(self):
super().__init__()
self.geometry(f"{700}x{500}")
self.title("Title of My Program")
self.protocol("Window Closed", self.stop) # "stop" function called when program closed
self.resizable(0,0)
# Options for light & Dark mode
self.option1 = ctk.CTkOptionMenu(master=self,
values=["Light", "Dark", "System"],
width=30,
height=30,
command=self.windowcolor)
self.option1.place(x=5, y=10)
self.option1.set("System") # Select default color for buttons
# Create center label
self.label1 = ctk.CTkLabel(master=self,
text="Graph is generated if you click one option from the below combobox.")
self.label1.place(x=200,y=10)
# City list
cities = ["LA", "CA", "IN", "AK" # etc ...]
# Center Combobox
global combobox1
self.cb_var = StringVar()
self.combobox1 = ctk.CTkComboBox(
master=self,
values=cities,
command=main,
variable=self.cb_var,
)
self.combobox1.place(x=280, y=50)
# Create center frame
self.frameCenter = ctk.CTkFrame(master=self,
width=682,
height=370,
corner_radius=5)
self.frameCenter.place(x=9, y=120)
global main
def main(self): # Main function
self.tkinter.messagebox.showinfo(title="Combobox", message="Clicked")
if combobox1.command == "CA":
graph_CA(self)
# graph photo is generated here
self.data = pd.read_excel("MyExcelFile.xlsx", sheet_name="MySheetName")
# Remove ctkCombobox, create a window fits for graph photo
def graph_CA(self):
# Graphing features added here
# Function that changes color of window
def windowcolor(self, new_appearance_mode):
ctk.set_appearance_mode(new_appearance_mode)
# Function that stops program
def stop(self, event=0):
self.destroy()
if __name__ == "__main__":
app = App()
app.mainloop()
Problem: When I run this code, everything works fine except it generates this error:
'str' object has no attribute 'tkinter'
after I click any options from the center combobox.
The main function works fine and generates graphs well but the program stops even before reaches the main function.
Question: How can I make a combobox that can call the main function when a user clicks any options from it?
Try removing "self." from messagebox function like this:
def main(self): # Main function
#self. removed
tkinter.messagebox.showinfo(title="Combobox", message="Clicked")
Updated after being able to replicate te problem.
Initially I could make it work by binding a key to the canvas, why that worked I do not know, but then that stopped working too so I investigated further.
After setting up some tests I managed to make a short code that replicates the problem:
from tkinter import *
class SomeClass:
def __init__(self, master):
self.can = Canvas(master, bg="gray")
self.can.pack()
thing = PhotoImage(file=("./img/thing.PNG"))
img = self.can.create_image(20, 20, image=thing)
stuff = self.can.find_all()
print(stuff)
app = Tk()
SomeClass(app) # Does not work
something = SomeClass(app) # Dos not work
# This part does work:
can = Canvas(app, bg="gray")
can.pack()
thing = PhotoImage(file=("./img/thing.PNG"))
img = can.create_image(20, 20, image=thing)
stuff = can.find_all()
print(stuff)
app.mainloop()
In all cases, the img show up as an item in the find_all(), but the first two does not show it on the canvas.
Also tried to put the creation of the image as a method with a bind to activate it thinking it went wrong during the init part. This did not change anything.
So what am I doing wrong here?
So answering my own question as I finally found out what was going wrong.
I didn't read this line in the documentation about the PhotoImage:
"You must keep a reference to the image object in your Python program, either by storing it in a global variable, or by attaching it to another object."
So in this case, the way to make it work is either do:
app = Tk()
thing = PhotoImage(file=("./img/thing.PNG")) # making it a global variable
or
class SomeClass:
def __init__(self, master):
self.can = Canvas(master, bg="gray")
thing = PhotoImage(file=("./img/thing.PNG")
self.can.img = thing # This attaches the image to the canvas object
Hope this helps anyone else that comes across this problem.
I am currently making a text based game using Python, and I have been wanting to use Kivy to make a Graphical Interface for it. I have been unable to get it to work so far though.
The reasoning is that I have changed from what was Print and Input to self.label1 = Label(text='Hello world') etc (There is multiple variables- label2, 3 and 4.) and then for the input, a text input box, which the input is used by a function when the button is pressed (Currently the textbox isn't involved, since I am first just testing whether the button works.). The problem is, I need a way to update the text displayed with the new value. For example I would like label1 to change to "These are the controls". But when a button is clicked, changes don't happen- I would like the GUI to be updated with the new text, through the changing of the values of the label variables. I believe that since those are being returned, the code above no longer loops through. The ideas I've been given, is to put the different sections into functions, or to use threading. Does anyone have any tips to push me in the right direction. I understand it may be too much be to ask, if so I'll continue to look for a solution myself. I can show some of the code if needed.
import kivy.uix.boxlayout
import kivy.uix.textinput
import kivy.uix.label
import kivy.uix.button
from kivy.app import App
from random import shuffle
import time
from kivy.uix.button import Button
from kivy.clock import Clock
alive = 1
buttonPressed = 0
class SimpleApp(App):
def build(self):
global alive
global buttonPressed
donext = 0
alive = 1
def callback(self):
global buttonPressed
buttonPressed = 1
self.label1 = kivy.uix.label.Label(text="")
self.label2 = kivy.uix.label.Label(text="")
self.label3 = kivy.uix.label.Label(text="You have found yourself in a dungeon, somewhere is your escape path, will you make it out, and if so, what with?")
self.label4 = kivy.uix.label.Label(text="")
print(buttonPressed)
if buttonPressed == 1:
print("Has been pressed should work theoretically")
self.label1 = kivy.uix.label.Label(text="These are the basic controls-")
self.label2 = kivy.uix.label.Label(text="The controls-")
self.label3 = kivy.uix.label.Label(text="A- approach enemy/ attack enemy")
self.label4 = kivy.uix.label.Label(text="C- Go to chest")
print("Press enter to continue.")
self.boxLayout = kivy.uix.boxlayout.BoxLayout(orientation="vertical")
self.boxLayout.add_widget(self.label1)
self.boxLayout.add_widget(self.label2)
self.boxLayout.add_widget(self.label3)
self.boxLayout.add_widget(self.label4)
self.btn1 = Button(text='Hello world 1', on_press=callback)
self.boxLayout.add_widget(self.btn1)
return self.boxLayout # Causes script not to continue
if __name__ == "__main__":
simple = SimpleApp()
simple.run()
If you have been advised to use threads it seems that your advisor does not know about GUI, in the GUIs the tasks are done asynchronously through events, that is, the GUI will provide you with methods to indicate when something has happened in the GUI, for example the event on_press notifies you when the button is pressed, so they connect a signal to that event. On the other hand the GUIs have a high component of object-oriented programming, event-oriented programming, and in the .kv is a declarative language, so I recommend you read about those concepts and for it kivy offers a great documentation and examples, review them. If you want to update a Label at least it must be accessible in the whole class, so it must be an attribute of the class and use the text property, on the other hand if you want to show a text of several lines use \n to indicate that there is a jump of line.
Considering the above, the solution is as follows:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
class SimpleApp(App):
def build(self):
self.label = Label(text="You have found yourself in a dungeon,\nsomewhere is your escape path,\nwill you make it out, and if so, what with?")
self.button = Button(text="Press Me", on_press=self.on_clicked, size_hint=(1.0, None))
layout = BoxLayout(orientation="vertical")
layout.add_widget(self.label)
layout.add_widget(self.button)
return layout
def on_clicked(self, instance):
self.label.text = "These are the basic controls-\nThe controls-\nA- approach enemy/ attack enemy\nC- Go to chest"
if __name__ == "__main__":
SimpleApp().run()
I've been trying for the past couple of hours to find a way to refresh a label with information without having to input anything myself.
The program I am trying to write is taking the CPU temp from the Raspberry Pi and displaying it in a window. I need to make that temp input to update every 5 seconds or so, but all attempts to do so have failed.
I tried while loops and found that they do not work inside tkinter, and I can't think how to refresh something constantly without input without one.
I am quite new to Python so I'm sure there is a way and I just haven't come across it yet. Similar questions on here don't quite lead to an answer that applies for me.
Here is my bit of code right now:
import subprocess
from tkinter import *
root = Tk()
root.title('CPU Temp')
cpuLab = Label(root, text = 'CPU Temp:',
font =('Nimbus Mono L',14,),
bg = 'black', fg = 'green').grid(row = 0, column = 0)
cpuTemp = subprocess.check_output(['/opt/vc/bin/vcgencmd', 'measure_temp'])
cpuVar = StringVar()
cpuDisplay = Label(root, textvariable = cpuVar,
font =('Nimbus Mono L',14),
bg = 'black', fg = 'green').grid(row = 0, column = 1)
cpuVar.set(cpuTemp[5:11])
root.mainloop()
This works perfectly for showing the temperature, it just has to be rerun in order to refresh.
Tkinter root windows have a method called after which can be used to schedule a function to be called after a given period of time. So call that function itself like (you will have to create a class first):
def update_label(self):
self.label.configure(cpuTemp)
self.root.after(1000, self.update_label)
This will then reload your label every second.
This may help you: Creating a Timer with tkinter
TKINTER
Refresh, Update, Rerender
This code works for any type of Tkinter widget update
#!/usr/bin/env python3
import sys
import time
from tkinter import *
# Global variables
running = True
# Button action updater
def callback():
if button_1["state"] == "disabled":
button_1["state"] = "normal"
else:
button_1["state"] = "disabled"
root.after(4000, callback)
# Window setup
root = Tk()
root.title("Buttons")
root.geometry("400x300")
# Buttons setup
button_1 = Button(root, text="Learn Python", command=callback)
button_1.pack()
# Software loop
root.mainloop()
Python version used to created this software is: >=3.x
I'm using the slider to update my visualization, but the command updateValue is sent everytime I move the slider thumb, even for intermediate values.
Instead I want to trigger it only when I release the mouse button and the interaction is complete.
self.slider = tk.Scale(self.leftFrame, from_=0, to=256, orient=tk.HORIZONTAL, command=updateValue)
How can I trigger the function only once, when the interaction is ended ?
This is quite an ancient question now, but in case anyone stumbles upon this particular problem just use the bind() function and the "ButtonRelease-1" event like so:
import Tkinter as tk
class App:
def __init__(self):
self.root = tk.Tk()
self.slider = tk.Scale(self.root, from_=0, to=256,
orient="horizontal")
self.slider.bind("<ButtonRelease-1>", self.updateValue)
self.slider.pack()
self.root.mainloop()
def updateValue(self, event):
print self.slider.get()
app=App()
Hope this helps anyone!
You can't.
What you can do instead is have your command delay any real work for a short period of time using 'after'. Each time your command is called, cancel any pending work and reschedule the work. Depending on what your actual requirements are, a half second delay might be sufficient.
Another choice is to not use the built-in command feature and instead use custom bindings. This can be a lot of work to get exactly right, but if you really need fine grained control you can do it. Don't forget that one can interact with the widget using the keyboard in addition to the mouse.
Here's a short example showing how to schedule the work to be done in half a second:
import Tkinter as tk
#create window & frames
class App:
def __init__(self):
self.root = tk.Tk()
self._job = None
self.slider = tk.Scale(self.root, from_=0, to=256,
orient="horizontal",
command=self.updateValue)
self.slider.pack()
self.root.mainloop()
def updateValue(self, event):
if self._job:
self.root.after_cancel(self._job)
self._job = self.root.after(500, self._do_something)
def _do_something(self):
self._job = None
print "new value:", self.slider.get()
app=App()