Kivy: How to change ScreenManager's "current" property on __init__ - python

My App is a ScreenManager. Depending on the circumstances, I want the active screen to either be "Screen 1" or "Screen 2" when the app opens. How would I do this the most elegant way? I thought that this is as trivial as changing the current property in the initialization of the app. Sadly, this does not work. Here's what should work imo:
main.py:
MyApp(App):
def build(self):
return Builder.load_file("MyApp.kv")
def __init__(self, **kwargs):
super(MyApp, self).__init__(**kwargs)
if foo: # Here's the problem:
self.root.current = "Screen 1"
else:
self.root.current = "Screen 2"
MyApp.kv:
ScreenManager:
Screen1:
name: "Screen 1"
Screen2:
name: "Screen 2"
<Screen1#Screen>
etc...
But it doesn't. Throws the following error:
self.root.current = "Screen 1"
AttributeError: 'NoneType' object has no attribute 'current'
My guess is that I set the current attribute to early, before root is set. An idea of mine is to 1) create a property-var for MyApp, 2) set current to be that property, 3) change that property in the init method. That's a lot of effort and code-cluttering just to change a screen on initialization.
How would I do it? Thanks a lot in advance!

That's simply because you don't have self.root object specified. Why would you need to change Screens during __init__ ? You should use build function for that.
My example:
import random
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager
Builder.load_string('''
<Root>:
Screen:
name: "Screen 1"
Label:
text: "Screen 1!"
Screen:
name:"Screen 2"
Label:
text: "Screen 2!"
''')
class Root(ScreenManager):
pass
class MyApp(App):
def build(self):
self.root = Root()
foo = random.randint(0,1)
if foo:
self.root.current = "Screen 1"
else:
self.root.current = "Screen 2"
return self.root
MyApp().run()
self.root.cureent_screen property will be changed before self.root object will be visible

Only this is working.
I said above statement because I tryed many codes(but not works)
class NextScreen(ScreenManager):
def __init__(self,**kwargs):
super(NextScreen,self).__init__(**kwargs)
#code goes here and add:
Window.bind(on_keyboard=self.Android_back_click)
def Android_back_click(self,window,key,*largs):
# print(key)
if key == 27:
if self.current_screen.name == "Screen1" or self.current_screen.name == "Screen_main":
return False
elif self.current_screen.name == "Screen2":
try:
self.current = "Screen1"
except:
self.current = "Screen_main"
return True
Use 'esc' button to get key(27) which means back in android

Related

python kivy app stops functioning after adding widget (on PC)

I removed everything superfluous, leaving only what was necessary to reproduce the same behavior.
There is an MD Text Field in which, when entering text, if there are matches, MDDropdownMenu appears with options to choose from. The options are stored in the P_LIST list. If you don't enter text into this Mytextfield, everything works. As soon as you enter the text, the function is triggered, a menu appears, you select. After that, the application does not function.
I determined that this is happening because of the line: self.add_widget(list drop down) # <----------- marked in the code
The menu appears without add_widget, but if you enter more than one letter, a new instance of the ListDropdownValue class is created each time and the menus overlap.
#kivymd 0.104.2
#kivy 2.0.0
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.properties import ObjectProperty
from kivymd.app import MDApp
from kivy.uix.boxlayout import BoxLayout
from kivymd.uix.menu import MDDropdownMenu
kv_str = """
<StartScreen>:
startscreen_textfield_1: textfield_id
BoxLayout:
orientation: "vertical"
BoxLayout:
size_hint: 1, 0.5
BoxLayout:
size_hint: 1, 0.5
orientation: "vertical"
BoxLayout:
MDTextField:
id: textfield_id
on_text:
root.open_listdropdown(textfield_id)#
BoxLayout:
MDTextField:
BoxLayout:
MDTextField:
"""
P_LIST = ["ASD", "SDF", "AASD"]
def search_product(prefix):
filtered_list = []
filtered_list = list(filter(lambda l: l.startswith(prefix), P_LIST))
return filtered_list
class MyListDropdownValue(MDDropdownMenu):
def __init__(self, dropdown_list, **kwargs):
super().__init__(**kwargs)
self.dropdown_list_id = dropdown_list
def list_dropdown(self):
if len(self.dropdown_list_id.text) != 0:
prefix = self.dropdown_list_id.text.upper()
filtered_list = search_product(prefix)
menu_items = [{'text':f'{value}',
"height": dp(56),
"viewclass": "OneLineListItem",
"on_release": lambda x= f"{value}": self.set_item(x)}
for value in filtered_list]
self.menu = MDDropdownMenu(
caller=self.dropdown_list_id,
items=menu_items,
width_mult=5,
)
self.menu.open()
def set_item(self, value):
def set_item(interval):
self.dropdown_list_id.text = value
self.menu.dismiss()
Clock.schedule_once(set_item, 0.1)
class StartScreen(BoxLayout):
startscreen_textfield_1 = ObjectProperty()
def open_listdropdown(self, id):
if len(self.children) == 1:
listdropdown = MyListDropdownProduct(id)
self.add_widget(listdropdown)
self.children[0].list_dropdown()
else:
self.children[0].menu.dismiss()
self.children[0].list_dropdown()
kv = Builder.load_string(kv_str)
class Program(MDApp):
def build(self):
self.screen = StartScreen()
return self.screen
def main():
app = Program()
app.run()
if __name__ == "__main__":
main()
Your MyListDropdownValue(MDDropdownMenu) class inherits from a MDDropdownMenu.
Then you make dropdown instance with
openlistdrop_down = MyDropdownValue(id)
Then you add that instance every time you "on_text" with
self.add_widget(listdropdown)
So you are adding multiple dropdowns.
In Kv try changing
on_text:
root.open_listdropdown(textfield_id)
To
on_validate:
root.open_listdropdown(textfield_id)
Then the user will need to hit enter before the list is made instead of with every letter added.

kivy ToggleButton : How can I trigger a method on the state changement?

Here is my actual code. It does what I want except when I change the state with a script, it doesn't give the information. It only gives the information when the change is made with a user interaction.
from kivymd.app import MDApp
from kivy.lang import Builder
KV = '''
BoxLayout:
ToggleButton:
id: toggle_button
text: "click on me"
'''
class ExampleApp(MDApp):
loading_layout = None
def build(self):
screen = Builder.load_string(KV)
screen.ids["toggle_button"].bind(on_press=lambda instance: self.on_click(instance))
print("I change the state and I want to be aware")
screen.ids["toggle_button"].state = "down"
print("on_click method isn't enough :(")
return screen
def on_click(self, instance):
print("State changed !", instance.state)
ExampleApp().run()
Question: How can I link a function to the state changement and not only on the click ?
What you could do is take advantage of the on_state method of the ToggleButton class. Here is an example:
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.togglebutton import ToggleButton
KV = '''
BoxLayout:
CustomToggle:
id: toggle_button
text: "click on me"
'''
class CustomToggle(ToggleButton):
def on_state(self, *args):
print('State changed!', self.state)
def on_press(self):
print("Button pressed!", self.state)
class ExampleApp(MDApp):
loading_layout = None
def build(self):
screen = Builder.load_string(KV)
screen.ids["toggle_button"].state = "down"
return screen
ExampleApp().run()
The on_state method is automatically bound to the state attribute, so that whenever the state is changed the function is called.

why does the Label text not change after I assigned it the value?

Here is a part of my code - the python file creates the buttons with the text. Then, when one of the buttons is pressed - the screen is changed and the label on the new screen must be the same as the text of the button that pressed. However the text of the label does not change even though everything else is working and there is no mistake given.
PYTHON:
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.lang import Builder
from kivy.uix.button import Button
class ScreenOne(Screen):
pass
class ScreenTwo(Screen):
def on_pre_enter(self, *args):
btn = Button(text = "word is here", on_release =self.pressedFunction)
self.ids.container.add_widget(btn)
btn1 = Button(text = "another word is here", on_release =self.pressedFunction)
self.ids.container.add_widget(btn1)
def pressedFunction(self, instance, *args):
self.manager.current= "three"
screenThree = ScreenThree()
text = str(instance.text)
screenThree.changing_label(text)
class ScreenThree(Screen):
def changing_label(self, text):
self.ids.my_label.text = text
class ScreenManagement(ScreenManager):
pass
presentation = Builder.load_file("example.kv")
class MainApp(App):
def build(self):
return presentation
if __name__ == "__main__":
MainApp().run()
KIVY:
ScreenManagement:
ScreenOne:
ScreenTwo:
ScreenThree:
<ScreenOne>:
BoxLayout:
Button:
text: "press me"
on_release: app.root.current = "two"
<ScreenTwo>:
name: "two"
BoxLayout:
id: container
<ScreenThree>:
name: "three"
BoxLayout:
id: labelContainer
Label:
text: ""
id: my_label
Since you're not providing any kind of error, i'm gonna guess what could be wrong.
First of all:
if spawned_List[0] == "False":
spawned_List is not defined in your code, and i'm not sure why you would compare that object with a string "False".
for x in range(0,len(languages)):
I suggest you to do this instead:
for word in languages:
btn = Button(
text=str(word), size_hint=(None, None), size=(140,70),
font_size=18, bold=True, color=[0,1,1,1],
background_color=(.156, .172, .24, 0.7),
on_release=self.changing_screen
)
Another guess i could make:
screenSlider = SliderScreen()
Does not make sense if you already have an instance of it, you should use the main one instead.
And if you could provide us more about, we could maybe answer your question easily.
-----------EDIT----------------
screenThree = ScreenThree()
You're creating a new instance of ScreenThree that is not the one you expect to work, so you have two different instances of the same Class, that's why it is not working. Instead you should get that instance using the ScreenManager, or where ever you instanciated it and call the method you want.

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 multiple selection with checkboxes

I'm trying to create a view using Kivy that has a list of options that are all selected by default, and the user can choose to deselect some entries (by clicking on the checkbox or anywhere on the row).
Clicking on the label part of the row item works, but I noticed that clicking on the checkbox doesn't change the selection which I can't work out how to solve (I tried a few different state bindings, I left them commented out in the example code)
Here is a quick example showing what I've tried.
from kivy.app import App
from kivy.properties import StringProperty, ListProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.selectableview import SelectableView
from kivy.uix.togglebutton import ToggleButtonBehavior
from kivy.adapters.models import SelectableDataItem
from kivy.lang import Builder
Builder.load_string("""
#: import ListAdapter kivy.adapters.listadapter.ListAdapter
#: import Factory kivy.factory.Factory
<MyListItem>:
height: 50
on_state: root.is_selected = args[1] == "down"
state: "down" if root.is_selected else "normal"
BoxLayout:
spacing: 10
CheckBox:
on_state: root.is_selected = args[1] == "down"
state: "down" if root.is_selected else "normal"
# on_state: root.state = args[1]
# state: root.state
Label:
text: root.name
<Page>:
orientation: "vertical"
ListView:
id: LV
adapter: ListAdapter(data=root.data, cls=Factory.MyListItem, args_converter=root.args_converter, selection_mode="multiple", propagate_selection_to_data=True)
Button:
size_hint_y: None
text: "print selection"
on_press: print(LV.adapter.selection)
""")
class MyListItem(ToggleButtonBehavior, SelectableView, BoxLayout):
name = StringProperty()
def __repr__(self):
return "%s(name=%r)" % (type(self).__name__, self.name)
def on_state(self, me, state):
print me, state
if state == "down":
self.select()
else:
self.deselect()
# self.is_selected = state == "down"
class DataItem(SelectableDataItem):
def __init__(self, name, **kwargs):
super(DataItem, self).__init__(**kwargs)
self.name = name
def __repr__(self):
return "%s(name=%r, is_selected=%r)" % (type(self).__name__, self.name, self.is_selected)
class Page(BoxLayout):
data = ListProperty()
def __init__(self, **kwargs):
super(Page, self).__init__(**kwargs)
self.data = [DataItem("Item {}".format(i), is_selected=True) for i in range(10)]
def args_converter(self, index, data_item):
return {
"index": index,
"name": data_item.name,
}
class ExampleApp(App):
def build(self):
return Page()
if __name__ == "__main__":
ExampleApp().run()
I'm using Kivy v1.9.1-dev
Edit: I worked out how to get all the entries pre-selected, I've updated the code and took that part of the question out.
Just in case someone else has the the question I point to the right url:
You should consider the new RecycleView, which has all the functionality you request. Look here for a sample: Kivy: alternative to deprecated features

Categories