Kivy : How can i reset checkbox active value - python

When I switch between screens i want to clear the check boxes marked.Marked boxes remain checked when i change screens.
I think my problem will be solved if I find a way to change the activation of checkboxes when I switch to another screen.
But I don't know how to do that.
My Code also has a certain number of checkboxes I choose only six of them. The functions in my main file are to calculate them.
My main.py
class SkillChose(Screen):
checkboxvalues = {}
for i in range(1, 21):
checkboxvalues["s{}".format(i)] = -2
def __init__(self,**kwargs):
super(SkillChose,self).__init__(**kwargs)
self.click_count = 0
self.skills=[]
def click_plus(self,check,id):
if check is True:
self.click_count+=1
self.checkboxvalues[id]=1
return True
def click_extraction(self,id):
if self.checkboxvalues[id]==1:self.click_count-=1
self.checkboxvalues[id]=0
return False
def control(self,id):
if id==0:return False
count=0
for open in self.checkboxvalues.values():
if open==1:
count+=1
for i,j in self.checkboxvalues.items():
print(i,j)
if count<6:
return True
else:
return False
my.kv file
<SkillChose>:
name:"skill"
BoxLayout
ScrollView:
size: self.size
GridLayout:
id: grid
size_hint_y: None
row_default_height: '50sp'
height: self.minimum_height
cols:2
Label:
Label:
Label:
text:"skill1"
CheckBox:
value:"s1"
active:(root.click_plus(self.active,self.value) if root.control(self.value) else False ) if self.active else root.click_extraction(self.value)
Label:
text:"skill2"
CheckBox:
value:"s2"
active:(root.click_plus(self.active,self.value) if root.control(self.value) else False ) if self.active else root.click_extraction(self.value)
Label:
text:"skill3"
CheckBox:
value:"s3"
active:(root.click_plus(self.active,self.value) if root.control(self.value) else False ) if self.active else root.click_extraction(self.value)

The following enhancements (kv file & Python script) are required to clear the CheckBox's attribute active when leaving a screen.
kv file
Use ScreenManager on_leave event to invoke a callback e.g. reset_checkbox()
Snippets - kv file
<SkillChose>:
name:"skill"
on_leave: root.reset_checkbox()
BoxLayout:
...
Py file
Add import statement, from kivy.uix.checkbox import CheckBox
Use for loop to traverse the children of GridLayout: via ids.grid
Use isinstance() function to check for CheckBox widget
Snippets - Py file
class SkillChose(Screen):
...
def reset_checkbox(self):
for child in reversed(self.ids.grid.children):
if isinstance(child, CheckBox):
child.active = False
...

Related

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

How to use OOP in Kivy Python?

Please help me, my competition is around the corner
I tried to use OOP in Kivy. This is my simple Python code for testing:
class location:
def __init__(self, total_house, total_land):
self.total_house = total_house
self.total_land = total_land
class test(BoxLayout):
def addNum(self):
App.get_running_app().x.total_house += 1
class testApp(App):
x = location(NumericProperty(10),NumericProperty(5))
testApp().run()
this is my kv file:
<test>:
orientation: 'vertical'
Label:
text: str(app.x.total_house)
Button:
text: 'add'
on_press: root.addNum()
This is the output
I want the output to be 10 and when the button is pressed the number is added by one.
Please help me, I am new to KIVY
One way of getting pure value from Kivy Property is to use the built-in .get(EventDispatcher obj) method from the kivy.properties.Property class:
class test(BoxLayout):
def addNum(self):
App.get_running_app().x.get(EventDispatcher()) += 1
But before that, you need to import the EventDispatcher class first:
from kivy._event import EventDispatcher
Also please note that while this works in theory and it will indeed change the value of the x variable, I would recommend directly changing the label's own text, something like this:
.py
def numberify(*args):
# This functions is for universally changing str to either int or float
# so that it doesn't happen to return something like 8.0 which isn't that great
a = []
for w in range(0, len(args)):
try:
a.append(int(args[w]))
except ValueError:
a.append(float(args[w]))
return a if len(a) > 1 else a[0]
class test(BoxLayout):
def addNum(self):
self.ids.label1.text = str(numberify(self.ids.label1.text) + 1)
.kv
<test>:
orientation: 'vertical'
Label:
id: label1
text: str(app.x.total_house)
Button:
id: button1
text: 'add'
on_press: root.addNum()
Learn more about Kivy Property here and understand how it's not always necessary to use them :)

Kivy - Why does this unrelated (seemingly) change of button state affect the ability to clear a list? Python

does anyone understand what's going on here?
the goal: There are many buttons. click two, they both individually append an item to an empty list. when len of the list is 2 (after 2 different buttons clicked) if the two items are not the same, clear/empty the list. If the two items are the same, then clear/empty the list, and disable the two buttons. A memory game would be the idea here. The buttons start off blank, you click it, they show text, and that text is the item appended into the list.
The problem: If the two items appended match, it disables the buttons as intended but then it doesn't empty/clear the list anymore. Why? Also important to use screens as it's going to be a screen between many in a different app.
The relevant part
the_btn[0].text = ''
the_btn[1].text = ''
pairs.clear()
the_btn.clear()
elif pairs[0] == pairs[1]:
the_btn[0].disabled = True
the_btn[1].disabled = True
pairs.clear()
the_btn.clear()
The complete code:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
Builder.load_string("""
<ScreenOne>:
StackLayout:
Button:
id: btn_1
text:''
size_hint: 0.2, 0.15
on_release:
root.b1()
root.check()
Button:
id: btn_2
text:''
size_hint: 0.2, 0.15
on_release:
root.b2()
root.check()
Button:
id: btn_3
text:''
size_hint: 0.2, 0.15
on_release:
root.b3()
root.check()
Button:
id: btn_4
text:''
size_hint: 0.2, 0.15
on_release:
root.b4()
root.check()
Button:
id: exit
text:'exit'
size_hint: 1, 0.15
on_release: app.stop()
""")
class ScreenOne(Screen):
def b4(self):
b4 = self.ids['btn_4']
b4.text = 'K'
def b3(self):
b3 = self.ids['btn_3']
b3.text = 'K'
def b2(self):
b2 = self.ids['btn_2']
b2.text = 'L'
def b1(self):
b1 = self.ids['btn_1']
b1.text = 'L'
def check(self):
buttons = [(self.ids['btn_1']), (self.ids['btn_2']), (self.ids['btn_3']), (self.ids['btn_4'])]
pairs = []
the_btn = []
for x in buttons:
pairs.append(x.text)
if x.text != '':
the_btn.append(x)
for y in range(pairs.count('')):
pairs.remove('')
if len(pairs) == 2:
if pairs[0] != pairs[1]:
the_btn[0].text = ''
the_btn[1].text = ''
pairs.clear()
the_btn.clear()
elif pairs[0] == pairs[1]:
the_btn[0].disabled = True
the_btn[1].disabled = True
pairs.clear()
the_btn.clear()
print(the_btn)
print(pairs)
screen_manager = ScreenManager()
screen_manager.add_widget(ScreenOne(name='one'))
class testApp(App):
def build(self):
return screen_manager
if __name__=='__main__':
testApp().run()
Why wouldn't the list clear after the buttons get disabled? :
I appreciate your input
I believe the problem is not that the list isn't being cleared (it is being cleared), but each execution of check() adds them back onto the list. I think if you limit the initial compiling of the list in the check() method to non-disabled Buttons, it should work as you desire:
def check(self):
buttons = [(self.ids['btn_1']), (self.ids['btn_2']), (self.ids['btn_3']), (self.ids['btn_4']),
(self.ids['btn_5']), (self.ids['btn_6'])]
pairs = []
the_btn = []
for x in buttons:
if not x.disabled: # ignore disabled buttons
pairs.append(x.text)
if x.text != '':
the_btn.append(x)

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

Kivy: How to separate background touch from a widget touch?

In my app, I want to handle background touches and widget touches separately. The Widget documentation ignores how to prevent bubbling from .kv events. Here's a little test case:
from kivy.app import App
class TestApp(App):
def on_background_touch(self):
print("Background Touched")
return True
def on_button_touch(self):
print("Button Touched")
if __name__ == "__main__":
TestApp().run()
And the .kv:
#:kivy 1.8.0
BoxLayout:
orientation: "vertical"
on_touch_down: app.on_background_touch()
padding: 50, 50
Button:
text: "Touch me!"
on_touch_down: app.on_button_touch()
The result: touching either the background or button triggers both handlers. Should I perform collision detection, or is there another way?
You should perform collision detection. For instance, in a class definition:
class YourWidget(SomeWidget):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
do_stuff()
Edit: Actually, your method won't work anyway because the Button overlaps the BoxLayout. I would probably instead create a BoxLayout subclass and override on_touch_down, calling super first then if it returns False (indicating the touch hasn't been used yet) doing the BoxLayout interaction.
I wanted a solution that allows me to bind events from .kv files. #inclement solution won't allow me to do that because once you bind the event from .kv, you can't return True anymore to tell the parent you handled the event:
Button:
# you can't return True here, neither from the handler itself
on_touch_down: app.button_touched()
So what I've done is to perform collision detection at the parent, emitting a custom on_really_touch_down only if it doesn't hit any children, and performing collision detection yet again at the child, because all children receive the touch regardless of whatever (it's a mess, I know). Here's the complete solution (requires Kivy >= 1.9.0, because of the usage walk method):
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
class CustomTouchMixin(object):
def __init__(self, *args, **kwargs):
super(CustomTouchMixin, self).__init__(*args, **kwargs)
self.register_event_type("on_really_touch_down")
def on_really_touch_down(self, touch):
pass
class CustomTouchWidgetMixin(CustomTouchMixin):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
self.dispatch("on_really_touch_down", touch)
return super(CustomTouchWidgetMixin, self).on_touch_down(touch)
class CustomTouchLayoutMixin(CustomTouchMixin):
def on_touch_down(self, touch):
for child in self.walk():
if child is self: continue
if child.collide_point(*touch.pos):
# let the touch propagate to children
return super(CustomTouchLayoutMixin, self).on_touch_down(touch)
else:
super(CustomTouchLayoutMixin, self).dispatch("on_really_touch_down", touch)
return True
class TouchHandlerBoxLayout(CustomTouchLayoutMixin, BoxLayout):
pass
class TouchAwareButton(CustomTouchWidgetMixin, Button):
pass
class TestApp(App):
def on_background_touch(self):
print("Background Touched")
def on_button_touch(self, button_text):
print("'{}' Touched".format(button_text))
if __name__ == "__main__":
TestApp().run()
The .kv:
#:kivy 1.9.0
TouchHandlerBoxLayout:
padding: 50, 50
on_really_touch_down: app.on_background_touch()
TouchAwareButton:
text: "Button One"
on_really_touch_down: app.on_button_touch(self.text)
TouchAwareButton:
text: "Button Two"
on_really_touch_down: app.on_button_touch(self.text)
So this allows me to bind touches from .kv.
Methods for binding touch events via .kv file/string syntax are possible, here's an example that modifies the caller's background when collisions are detected.
<cLabel#Label>:
padding: 5, 10
default_background_color: 0, 0, 0, 0
selected_background_color: 0, 1, 0, 1
on_touch_down:
## First & second arguments passed when touches happen
caller = args[0]
touch = args[1]
## True or False for collisions & caller state
caller_touched = caller.collide_point(*touch.pos)
background_defaulted = caller.background_color == caller.default_background_color
## Modify caller state if touched
if caller_touched and background_defaulted: caller.background_color = self.selected_background_color
elif caller_touched and not background_defaulted: caller.background_color = caller.default_background_color
background_color: 0, 0, 0, 0
canvas.before:
Color:
rgba: self.background_color
Rectangle:
pos: self.pos
size: self.size
And for completeness, here's how to use the above code within a layout that is touch activated only if none of the children (or grandchildren and so on) have also collided with the same event.
<cGrid#GridLayout>:
on_touch_down:
caller = args[0]
touch = args[1]
caller_touched = caller.collide_point(*touch.pos)
spawn_touched = [x.collide_point(*touch.pos) for x in self.walk(restrict = True) if x is not self]
## Do stuff if touched and none of the spawn have been touched
if caller_touched and True not in spawn_touched: print('caller -> {0}\ntouch -> {1}'.format(caller, touch))
cols: 2
size_hint_y: None
height: sorted([x.height + x.padding[1] for x in self.children])[-1]
cLabel:
text: 'Foo'
size_hint_y: None
height: self.texture_size[1]
cLabel:
text: 'Bar'
size_hint_y: None
height: self.texture_size[1] * 2
I may have gotten the texture_size's backwards, or perhaps not, but the height trickery can be ignored for the most part as it's purpose is to aid in making the parent layout more clickable.
The color changing and printing of caller & touch objects should be replaced with do_stuff() or similar methods, as they're there to make the example self contained, and show another way handling caller saved state when touched.

Categories