Kivy Adding Widget to a screen - python

This seems to be a silly question. But I have a widget that I want to add to a screen called GameScreen.
This is my Python code:
class WelcomeScreen(Screen):
pass
class BasicScreen(Screen):
pass
class GameScreen(Screen):
parent = Widget()
game = ShootingGame()
parent.add_widget(game)
Clock.schedule_interval(game.update, 1.0 / 60.0)
# return parent
sm = ScreenManager()
sm.add_widget(WelcomeScreen(name='welcome'))
sm.add_widget(BasicScreen(name='basic'))
sm.add_widget(GameScreen(name='game'))
class ShootingApp(App):
def build(self):
print(sm.current)
return sm
if __name__ == '__main__':
ShootingApp().run()
And this is my kivy code:
<WelcomeScreen>:
Button:
text: "Learn about haptic illusions"
size_hint: None, None
size: 500, 70
pos: 100, 200
font_size: 30
on_release: app.root.current = "basic"
Button:
text: "Play our game"
size_hint: None, None
size: 500, 70
pos: 100, 100
font_size: 30
on_release: app.root.current = "game"
<BasicScreen>:
name: "basic"
<GameScreen>:
name: "game"
The error I am getting is this. And I think this is because I already defined a parent for the widget game. However, I need that parent because the game widget uses width and height values of its parent (e.g., self.parent.width). Is there any workaround for this so that the game widget can be nested in a parent and add the parent to the screen?
kivy.uix.widget.WidgetException: Cannot add <Screen name='game'>, it already has a parent <kivy.uix.widget.Widget object at 0x1093dc8d8>
Thanks guys!!

you can do something like this
class GamesScreen(Screen):
def __init__(self, **kwargs):
super(GameScreen, self).__init__(**kwargs)
self.game = ShootingGame()
self.add_widget(self.game)
clock.schedule_interval(self.game.update, 1.0 / 60.0)

It looks to me like what's happening is that you attempt to give GameScreen a parent TWICE. Once when telling it it's parent is Widget(), and again when you add it to the ScreenManager(which would make sm it's parent). Whichever of them is executed first(I think the parent = Widget() line from looking at the Exception) is causing the error when you try it the second time.

Related

KIVY: 'float' has no attributes 'ids'

I am trying to make a clock which is just a label update every second with the current time. Each time I try to update the label I am thrown this error:
File "C:\Users\Nitro\Documents\MirOS\MirOS-core.py", line 33, in currentTime
self.ids.current_time.text = timeData
AttributeError: 'float' object has no attribute 'ids'
I did a little research into the kivy.Clock function and I found out that this is most likely happening because the clock function calls the currentTime() and includes a delta time argument which is what causes the AttributeError. Unfortunately, I need the self argument to stay where it is as otherwise my label does not update and I am thrown more errors.
Here is my .py file:
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.clock import Clock
from kivy.uix.screenmanager import ScreenManager, Screen, FallOutTransition
import time
sm = ScreenManager(transition = FallOutTransition())
Window.clearcolor = 0, 0, 0, 1
Window.size = 1920, 1080
Window.fullscreen = True
class StartUP(Screen):
def SystemCheck(self):
sm.current = 'active_home'
print('WORKING')
class StartUPCavas(Widget):
pass
class ActiveHome(Screen):
class ActiveHomeCanvas(Widget):
pass
class ActiveClock(Widget):
def currentTime(self):
timeData = time.strftime("%H:%M:%S")
self.ids.current_time.text = timeData
Clock.schedule_interval(currentTime, 1)
class MirOSApp(App):
def build(self):
sm.add_widget(StartUP(name = 'startup'))
sm.add_widget(ActiveHome(name = 'active_home'))
return sm
if __name__ == '__main__':
MirOSApp().run()
Here is the .kv file:
#kivy 2.1.0
<StartUP>:
StartUPCavas:
Image:
source: 'images/MirOS.png'
texture: self.texture
size_hint_y: None
width: 300
center_x: root.width / 2
center_y: root.height / 2
Button:
center_x: root.width / 2
center_y: (root.height / 2) - 100
on_press:
root.SystemCheck()
<ActiveHome>:
ActiveHomeCanvas:
ActiveClock:
Label:
id: current_time
text: ''
font_size: 40
font_name: 'fonts/bahnschrift.ttf'
center_x: root.width / 2
center_y: root.height / 2
color: 1, 1, 1, 1
I am really confused and have tried to solve this issue on my own but I can't seem to find any solution. Any ideas? Thanks for your time!
You are correct about the delta time argument (dt). That argument is being passed into the currentTime() method, but the currentTime() method is expecting to receive the self. So when that method does a self.ids, it is trying to access the ids attribute of dt, causing the error.
In order to provide the self argument, you must call the currentTime() method as an instance method of ActiveHome (since that is where that id is defined).
You can fix this by modifying your ActiveHome class like this:
class ActiveHome(Screen):
def __init__(self, **kwargs):
super(ActiveHome, self).__init__(**kwargs)
Clock.schedule_interval(self.currentTime, 1)
def currentTime(self, dt):
timeData = time.strftime("%H:%M:%S")
self.ids.current_time.text = timeData
This arrangement schedules the calls to currentTime() in an __init__() method, and the currentTime() method handles the correct arguments.

how can i add text to my popup label from another class function with kivy

I want to display the color name that i found at { closest_colour(requested_colour) } function
in popup window.
In practice application supposed to ask for file from your computer (only specific image types) then ask for mouse input while displaying image that you choose.Finally opens popup window to display color of pixel that you clicked.
.py
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.factory import Factory
from kivy.properties import ObjectProperty, StringProperty
from kivy.uix.popup import Popup
import cv2
import webcolors
class LoadDialog(FloatLayout):
load = ObjectProperty(None)
cancel = ObjectProperty(None)
class Root(FloatLayout):
loadfile = ObjectProperty(None)
def dismiss_popup(self):
self._popup.dismiss()
def show_load(self):
content = LoadDialog(load=self.load, cancel=self.dismiss_popup)
self._popup = Popup(title="Load file", content=content,
size_hint=(0.9, 0.9))
self._popup.open()
def load (self,path, filename):
filename=str(filename)
filename = filename.replace("[", "")
filename = filename.replace("]", "")
filename = filename.replace("\\\\", "\\")
filename = filename.replace("'", "")
print(filename)
img=cv2.imread(filename)
cv2.imshow('image', img)
self.dismiss_popup()
def closest_colour(requested_colour):
min_colours = {}
for key, name in webcolors.CSS3_HEX_TO_NAMES.items():
b_c, g_c, r_c = webcolors.hex_to_rgb(key)
rd = (r_c - requested_colour[0]) ** 2
gd = (g_c - requested_colour[1]) ** 2
bd = (b_c - requested_colour[2]) ** 2
min_colours[(rd + gd + bd)] = name
return min_colours[min(min_colours.keys())]
def click_event(event, x, y, flags, params):
if event == cv2.EVENT_LBUTTONDOWN:
print(x, ' ', y)
requested_colour=img[y,x]
global answer
answer=closest_colour(requested_colour)
print(closest_colour(requested_colour))
print(img[y,x])
cv2.destroyAllWindows()
cv2.setMouseCallback('image', click_event)
cv2.waitKey(0)
return answer
class MyPopup(Popup):
PopUp=ObjectProperty(None)
text = StringProperty(Root.answer)
def MyPopup(self):
content = MyPopup(PopUp=self.PopUp)
self._popup = Popup(title="Color Found!!", content=content, size_hint=(0.2, 0.4))
self._popup.open()
class main(App):
pass
Factory.register('Root', cls=Root)
Factory.register('LoadDialog', cls=LoadDialog)
Factory.register('MyPopup', cls=MyPopup)
if __name__ == '__main__':
main().run()
.kv
#:kivy 1.1.0
#:import Factory kivy.factory.Factory
Root:
FloatLayout:
Button:
id: load
size_hint:(1,.1)
pos_hint:{'x':0, 'y':0}
text: 'Load'
on_release: root.show_load()
background_color :(0, 0, 1, 1)
Image:
id: img
size_hint:(1,.9)
pos_hint:{'x':0, 'y':.1}
source: 'palet.jpg'
size: self.texture_size
keep_ratio: False
allow_stretch: True
<LoadDialog>:
id:load_dialog
BoxLayout:
size: root.size
pos: root.pos
orientation: "vertical"
FileChooserIconView:
id: filechooser
filters:['*.png','*.jpg','*.jpeg']
BoxLayout:
size_hint_y: None
height: 30
Button:
text: "Cancel"
on_release: root.cancel()
Button:
text: "Load"
on_release: root.load(filechooser.path, filechooser.selection)
on_release: Factory.MyPopup().open()
<MyPopup#Popup>
auto_dismiss: False
size_hint:0.4,0.2
pos_hint:{"x":0.3,"y":0.4}
title:"Color Found!!"
BoxLayout:
Label:
text:root.text
font_size:24
Button:
text:"Ok"
width:.5
size_hint:0.2,0.2
pos_hint:{"x":0.2,"y":0}
on_release: root.dismiss()
I tried to create global answer variable but it didnt work out.
Okay, the structure of what you're trying to do is a little confusing, the class LoadDialog(FloatLayout) looks like it's being called just to make the connection between the two classes, is that it?
But from the looks of it, you are trying to pass the text from variable
answer
that is returned when you call the function def load(). if so, try creating a variable eg text_answer = None in class Root()
then, before returning the result, pass the variable's value to the new variable created within the class, in the code snippet
cv2.setMouseCallback('image', click_event)
cv2.waitKey(0)
# add this
self.text_answer = answer
# don't need the return
return answer
later in class MyPopup(Popup), add the variable that was created in the Root() class. But remember that, for object-oriented concepts, you need to use the variable from the instance of class Root() and not directly from the class, and that's the tricky part.
There are many ways to do this:
Pass the Root() class instance to the Popup constructor.
Use an ObjectProperty to use the Root instance.
Use kivy's own tree of objects, doing self.parent.parent.[...] until
finding the instance of Root()
For this case, the easiest way is to use the self.parent method, until you find the Root instance and retrieve the text value, try doing something like this:
class MyPopup(Popup):
PopUp=ObjectProperty(None)
# go looking for the instance of Root in the parentage
text = StringProperty(self.parent.parent.text_answer)
I can't give the answer to your specific problem because I don't have some of the libraries you are using, but I believe this should help with communication between classes.
Always remember to try to reference the instances of the classes, not the classes themselves.

Python - Kivy - Hiding a widget as soon as screen shows up

I want to hide the progress bar at below code as soon as screen appears. In order to do that I defined a hide_widget function and added the code to "on_pre_enter" section. However it doesn't work. What am I missing here ?
By the way if you can introduce a shorter way to hide a widget in kivy, it is also very welcome.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
Builder.load_string("""
<MyLayout>
BoxLayout:
orientation: "vertical"
size: root.width, root.height
spacing: 20
padding: 50
Button:
text: "Test"
ProgressBar:
id: pgb_excel_read
min: 0
max: 100
value: 0
size_hint: ( 1, 0.1)
""")
def hide_widget(wid, dohide=True):
if hasattr(wid, 'saved_attrs'):
if not dohide:
wid.height, wid.size_hint_y, wid.opacity, wid.disabled = wid.saved_attrs
del wid.saved_attrs
elif dohide:
wid.saved_attrs = wid.height, wid.size_hint_y, wid.opacity, wid.disabled
wid.height, wid.size_hint_y, wid.opacity, wid.disabled = 0, None, 0, True
class MyLayout(Widget):
def on_pre_enter(self, *args):
hide_widget(self.ids.pgb_excel_read, True)
class MyApp(App):
def build(self):
return MyLayout()
if __name__ == '__main__':
MyApp().run()
on_pre_enter function is only avaliable for Screen classes. But you try to use it in your custom Widget. So there is a nothing to trigger this function. We can trigger it by __init__:
class MyLayout(Widget):
def __init__(self):
super(MyLayout, self).__init__()
hide_widget(self.ids.pgb_excel_read, True)
For hide any widget we just need to change 2 things:
Disabled : True
Opacity : 0
Short way to do that, lets stop triggering any function and just change variables: (Also Remove hide_widget function)
class MyLayout(Widget):
def __init__(self):
super(MyLayout, self).__init__()
# hide_widget(self.ids.pgb_excel_read, True)
self.ids.pgb_excel_read.opacity = 0
self.ids.pgb_excel_read.disabled = True

Python, Kivy: Problem with calling functions from different classes/screens

I'm having trouble with correctly calling functions from different classes.
I am making a simple game which calculates the score using the amount of time it takes to clear a level. There's a stopwatch running in the background and I want to add a pause button that popup menu, and a resume button inside this popup menu.
The problem is that when calling the pause function from within the popup menu, it will also be returned inside the popup, instead of inside the main widget.
Here is a simplified version of the code:
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import NumericProperty
from kivy.uix.widget import Widget
from kivy.uix.popup import Popup
from kivy.clock import Clock
root_widget = Builder.load_file('app.kv')
class ExampleWidget(Widget):
time = NumericProperty(0)
paused = False
stop = False
# Keeping time
def increment_time(self, interval):
self.time += .1
print(self.time) # To check if stopwatch is running or not
# Stop should mean that the stopwatch must reset when it starts again.
# When paused it should resume when it starts again
def stop_start_or_pause(self):
# stop stopwatch
if self.stop:
Clock.unschedule(self.increment_time)
print('Stopped')
# Make sure time is 0 when restarting
elif not self.stop and not self.paused:
# Keeping time
self.time = 0
Clock.schedule_interval(self.increment_time, .1)
# Pause stopwatch
elif self.paused:
Clock.unschedule(self.increment_time)
print("!!", self.time) # To make it easier to see if stopwatch actually resumes where it left off
print('unscheduled') # Just to confirm and to make it a bit easier to see
# resume stopwatch
elif not self.paused:
Clock.schedule_interval(self.increment_time, .1)
class PopupMenu(Popup):
example = ExampleWidget()
class MyApp(App):
ExampleWidget = ExampleWidget()
def build(self):
return ExampleWidget()
MyApp().run()
.kv file:
#:import Factory kivy.factory.Factory
<PopupMenu#Popup>
auto_dismiss: False
size_hint_y: .8
size_hint_x: .9
title: 'Pause'
example: app.ExampleWidget
BoxLayout:
Button:
text: 'resume'
on_press: root.example.paused = False
on_release: root.dismiss(); root.example.stop_start_or_pause()
size: self.size
<ExampleWidget>:
GridLayout:
col: 2
rows: 3
size: root.size
Button:
text: 'start'
size: self.size
on_press: root.stop = False; root.stop_start_or_pause()
Button:
text: 'stop'
size: self.size
on_press: root.stop = True; root.stop_start_or_pause()
Button:
text: 'Pause menu'
size: self.size
on_press: root.paused = True
on_release: Factory.PopupMenu().open(); root.stop_start_or_pause()
Label:
text: str(round(root.time))
size: self.size
I tried making a function and using Clock.schedule.interval() to keep checking if paused == True, but it keeps returning:
AttributeError: 'float' object has no attribute 'stopped'
This didn't seem like efficient solution anyways, so I didn't want to spend too much time on this function. I also tried to find 'stupid' mistakes (I.e. ',' instead of '.') but that was before I realised that the resume button returned a 'second' stopwatch instead of updating the one I actually wanted to use.
I hope that someone can help, and that my question is clear. English is not my first language so I sometimes have a hard time finding the best way to explain/ask questions.
Thank you in advance!
If I understand your question, the problem is with your MyApp class:
class MyApp(App):
ExampleWidget = ExampleWidget()
def build(self):
return ExampleWidget()
This code is creating two instances of ExampleWidget. One is returned in the build() method, and one is saved as the ExampleWidget attribute of MyApp. Now, when you use the ExampleWidget attribute of MyApp, you are not referencing the ExampleWidget that is the root of your GUI, so it has no effect on what appears on the screen. The fix is to just creat a single instance of ExampleWidget, like this:
class MyApp(App):
ExampleWidget = ExampleWidget()
def build(self):
return self.ExampleWidget

Making a slightly more efficient function structure

I have a bunch of lights I'm trying to control. Rather than have each button state change call a unique function I want to try and have a multipurpose function as thats what functions are for (as far as I understand).
Button calling function:
ToggleButton:
id: KitchenSpot1Toggle
text: "Kitchen Spot 1"
on_press: root.changeKS1(1)
The function:
def changeKS1(self,change):
if change==1 and b.get_light(1, 'on'):
self.KitchenSpot1(False)
else:
self.KitchenSpot1(True)
That function then calls this function to physically change the state of the light using a 3rd part library.
def KitchenSpot1(self,state):
lights[0].name
lights[0].on = state
The reason I passed "1" inside of the function is because it didn't like having nothing passed in it (I don't know why it didn't like it). If you hadn't already guessed it, I am new at this. I have a bit of a cpp micro controller background but I'm trying to get my head around python and PC based programming. I'm looking for a bit of advice on how best I can condense this and make it as efficient as possible. I may not know much about python, but, I know I shouldn't be typing practically the same thing out 30 times.
Thanks in advance to anyone that can share some of their wisdom.
Its with noting I am using kivy with python to generate the button.
Full main.py code:
from kivy.properties import StringProperty
import kivy
from kivy.uix.togglebutton import ToggleButton
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.app import App
kivy.require('1.10.0')
from phue import Bridge
import nest
b = Bridge('xx.xxx.xxx.xxx')
b.connect()
b.get_api()
lights = b.lights
class Controller(GridLayout):
state = StringProperty('down')
def __init__(self, **kwargs):
super(Controller, self).__init__(**kwargs)
Clock.schedule_interval(self.update, 1.0 / 60.0)
def KitchenSpot1(self,state):
lights[0].name
lights[0].on = state
def changeKS1(self,change):
if change==1 and b.get_light(1, 'on'):
self.KitchenSpot1(False)
else:
self.KitchenSpot1(True)
def KitchenSpot2(self,state):
lights[1].name
lights[1].on = state
def KitchenSpot3(self,state):
lights[2].name
lights[2].on = state
def OfficeSpot1(self,state):
lights[3].name
lights[3].on = state
def OfficeSpot2(self,state):
lights[4].name
lights[4].on = state
def OfficeSpot3(self,state):
lights[5].name
lights[5].on = state
def OfficeSpot4(self,state):
lights[6].name
lights[6].on = state
def JuliaBedside(self,state):
lights[7].name
lights[7].on = state
def JohnBedside(self,state):
lights[8].name
lights[8].on = state
def update(self, dt):
if b.get_light(1, 'on'):
self.state = 'down'
else:
self.state = 'normal'
class ActionApp(App):
def build(self):
return Controller()
if __name__ == "__main__":
myApp = ActionApp()
myApp.run()
Full action.kv code
<Controller>:
cols: 4
rows: 3
spacing: 10
state: "normal"
ToggleButton:
id: KitchenSpot1Toggle
text: "Kitchen Spot 1"
on_press: root.changeKS1(1)
#on_release: root.KitchenSpot1(False)
#state1 = app.update.h
state: root.state
ToggleButton:
text: "Kitchen Spot 2"
Button:
text: "Kitchen Spot 3"
Button:
text: "Kitchen Spot 4"
Button:
text: "Office Spot 1"
Button:
text: "Office Spot 2"
Button:
text: "Office Spot 3"
Button:
text: "Office Spot 4"
Update:
Python program:
def lightcontrol(self,lightnumber):
if b.get_light(1, 'on'):
lights[lightnumber].name
lights[lightnumber].on (False)
#self.KitchenSpot1(False)
else:
lights[lightnumber].name
lights[lightnumber].on (True)
#self.KitchenSpot1(True)
Kivy button:
ToggleButton:
id: KitchenSpot1Toggle
text: "Kitchen Spot 1"
on_press: root.lightcontrol(0)
Have each button call the same function but with a different parameter.
# Add the a number parameter here based on what you've
def KitchenSpot(self,state, light_index):
lights[light_index].name
lights[light_index].on = state
Then in the KV file,
Button:
text: "Kitchen Spot 3"
on_press: root.KitchenSpot(state, light_index = 3)
Button:
text: "Kitchen Spot 4"
on_press: root.KitchenSpot(state, light_index = 4)
You only have to create the function one, with each button passing in the relevant light_index number.
Neither knowing kivy nor phue I've tried to reduce the problem to your code redundancy by abstracting the definition of your methods (and also the creation of your action.kv file).
So I hope this what you are looking for: First, I would define all the relevant data of the buttons in a global variable, like:
BUTTONS = [
{'id': 0, 'methodname': 'KitchenSpot1', 'text': 'Kitchen Spot 1'},
{'id': 1, 'methodname': 'KitchenSpot2', 'text': 'Kitchen Spot 2'},
...
]
Then define your Controller-class with all unique methods just like you did (__init__ and update in your case; however I don't see what update should do, I just left it be):
class Controller(GridLayout):
state = StringProperty('down')
def __init__(self, **kwargs):
super(Controller, self).__init__(**kwargs)
Clock.schedule_interval(self.update, 1.0 / 60.0)
def update(self, dt):
if b.get_light(1, 'on'):
self.state = 'down'
else:
self.state = 'normal'
# this iteratively creates your button-individual methods
for button_dict in BUTTONS:
def func(self, state):
lights[button_dict['id']].name # whatever this might do just adressing the name attribute. Is it a python-property on lights that does some action?
lights[button_dict['id']].on = state
def func_change(self, change):
if change == True and b.get_light(button_dict['id'], 'on'):
getattr(self, button_dict['methodname'])(False)
else:
getattr(self, button_dict['methodname'])(True)
# create .KitchenSpot1, .KitchenSpot2, ...
setattr(Controller, button_dict['methodname'], func)
# create .changeKitchenSpot1, .changeKitchenSpot2, ...
setattr(Controller, "change{}".format(button_dict['methodname']), func_change)
The instantiated Controller will have bound methods named accordingly to all methodnames and change-methodnames.
Finally you can create your action.kv file dynamically
actionkv_toggle_button = """
ToggleButton:
id: {methodname}Toggle
text: "{text}"
on_press: root.change{methodname}(1)
#on_release: root.{methodname}(False)
#state1 = app.update.h
state: root.state
"""
actionkv_str = """
<Controller>:
cols: 4
rows: 3
spacing: 10
state: "normal"
{buttons}
""".format(
buttons="".join([
actionkv_toggle_button.format(
methodname=button_dict['methodname'],
text=button_dict['text']
) for button_dict in BUTTONS
])
)
this gives the output
<Controller>:
cols: 4
rows: 3
spacing: 10
state: "normal"
ToggleButton:
id: KitchenSpot1Toggle
text: "Kitchen Spot 1"
on_press: root.changeKitchenSpot1(1)
#on_release: root.KitchenSpot1(False)
#state1 = app.update.h
state: root.state
ToggleButton:
id: KitchenSpot2Toggle
text: "Kitchen Spot 2"
on_press: root.changeKitchenSpot2(1)
#on_release: root.KitchenSpot2(False)
#state1 = app.update.h
state: root.state
Save it to a file
with open('action.kv', 'w') as f:
f.write(actionkv_str)
Helpful links:
Dynamically attaching methods to a class
String formatting in python
List comprehension

Categories