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()
Related
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.
For beginners. Examples and preferably explanations.
Kivy is object oriented GUI framework. Such basic things as in my question are not obvious to beginners. I think they need to be explained and shown by examples.
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
class BeginnerApp(App):
def build(self):
root = BoxLayout(orientation='vertical')
self.count = 0
a = root.add_widget(Button(text='add button', on_press=self.add_button))
b = root.add_widget(Button(text='remove button', on_press=self.delete_button))
return root
def add_button(self, *args):
self.count += 1
self.root.add_widget(Button(text=str(self.count)))
def delete_button(self, *args):
if self.count > 0:
self.root.remove_widget(self.root.children[0])
self.count -= 1
else:
pass
if __name__ == '__main__':
app = BeginnerApp()
app.run()
This is an example without kv language.
Note 1: when you bind a method, specify its name without brackets. Otherwise, binding does not work normally.
on_press=self.add_button
on_press=self.delete_button
Note 2: to add widgets in layout you can use method "add_widget()".
self.root.add_widget(Button())
For delete widget you can use method "remove_widget()". To delete a widget in layout, you need to specify this widget. This can be done through the "children" method. The last widget in the "children" method is numbered "0". So you can delete the last widget in layout.
self.root.remove_widget(self.root.children[0])
Note 3: When declaring methods, don't forget *args.
def add_button(self, *args):
wonder if anyone can help me out.
I am stuck with a problem. I want to change a buttons background_normal TWICE by clicking another button.
So for example first change to yellow than wait 2 seconds (time.sleep(2)) and then change to red.
I tried to make a simple functions that does this. But the problem is that it does not update the first change of the background_normal. It only displays the latest change.
Tried to split it into two functions, one changes it to the first color, the other one changes it to the second color. But still it does not display anything but the latest change.
Is there any possibility to solve this?
Update:
My code is ~2k lines so i will post a short form of my problem
class Game(FloatLayout):
def firstaction(self):
#change button1 to yellow and wait 2 seconds
def secondaction(self):
#change button1 to red
class GameApp(App):
def build(self):
return Game
kv file looks something like
button2:
on_press: Game.firstaction
on_press: Game.secondaction
You talk about colors so you should use background_color instead of background_normal. Going to the problem, the cause is time.sleep() because it blocks the event loop, the event loop helps the GUI to update due to OS or user events. The solution is to use Clock.
from kivy.app import App
from kivy.uix.button import Button
from kivy.clock import Clock
class MyButton(Button):
def on_press(self):
self.background_color = (1,1,0,1)
fn = lambda dt: setattr(self, "background_color", (1,0,0,1))
Clock.schedule_once(fn, 2) # <--- 2 seconds
class MyApp(App):
def build(self):
button = MyButton(text='Hello World')
return button
if __name__ == '__main__':
MyApp().run()
I created a simple text-to-speech app with Kivy, using the FloatLayout option but am having trouble changing the color of the GUI without actually creating a .kv file (which I do not wish to do). The code of my app is here:
import kivy
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
import requests
from threading import Thread
import os
class ButtonApp(App):
def talk(self):
self.info.text = self.text.text
command = "say %s" % (self.text.text)
os.system(command)
def say(self,instance):
t = Thread(target=self.talk)
t.start()
def build(self):
self.b = FloatLayout()
self.info = Label(text="Hello!", pos=(20,400) ,size_hint=(1,0.5), font_size="40sp")
self.text = TextInput(text='Hello!', pos=(20,200), size_hint=(1,0.5))
self.submit = Button(on_press=self.say,text='Submit',pos=(20,100), size_hint=(1,0.5))
self.b.add_widget(self.info)
self.b.add_widget(self.text)
self.b.add_widget(self.submit)
self.b.bind()
return self.b
if __name__ == "__main__":
ButtonApp().run()
Like I mentioned beforehand, all the suggestions I found doing prior research involved either Canvas (which I am not using), or creating a .kv file. Is there a pure python-kivy method of changing the color of a GUI?
You can do anything in pure python, though the reason you see so many kv examples is because it's easier and more concise due to being a more domain specific language, so I don't recommend avoiding it.
What kind of change do you actually want to make? For instance, you can change the background image of the Button with the background_normal or background_down properties (which take a filepath to an image), or tint its colour by setting its background_color to e.g. (1, 0, 0, 1) for red.
Me again, here is my code, why is "beep.wav" playing when I launch the application rather than when I press the buttons I assume I have bound it to?
import kivy
import random
from kivy.core.audio import SoundLoader
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
sound = SoundLoader.load('beep.wav')
red = [1,0,0,1]
green = [0,1,0,1]
blue = [0,0,1,1]
purple = [1,0,1,1]
class A_Cool_Program(App):
def Orientation(self, orient):
self.orient = orient
def build(self):
layout = BoxLayout(padding=0, orientation=self.orient)
colors = [red, green, blue, purple]
for i in range(5):
btn = Button(text="Test Button %s" % (i+1), background_color=random.choice(colors))
layout.add_widget(btn)
sound = SoundLoader.load('beep.wav')
btn.bind(on_press=sound.play())
return layout
if __name__ == "__main__":
app = A_Cool_Program()
app.Orientation(orient="vertical")
app.run()
btn.bind(on_press=sound.play())
This line calls the function, just as you should always expect when you use the function call syntax - you called sound.play(), therefore the function is called and you hear the sound. The bind method doesn't know about any of this, it only sees the return value of the function, which is probably just None.
I think in this case you want instead something like
btn.bind(on_press=lambda *args: sound.play())
This passes in a lambda function, and when that function is called then sound.play() is run. This example is actually unusual because sound.play doesn't seem to accept any arguments, but kivy bindings automatically pass at least the instance of the button, so this code uses a lambda function to eat that argument.
You normally could just do:
def some_function(*args):
print('the button was pressed!')
...
btn.bind(on_press=some_function)
Note the lack of brackets to actually call some_function. It will be called later when the button is pressed.