I have a Label and a button that I define in the init of my class. In the init I bind my button to a method that should change the label. However the label does not update on button press even tho the variable does.
Why does my variable change but my label text stays the same even tho the text is an ObjectProperty?
class ReviewLayout(BoxLayout):
Price = Price()
textover = ObjectProperty(None)
ordered_products = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.get_order()
l = Label(text = str(self.textover))
self.add_widget(l)
b = Button(text= 'button')
b.bind(on_press=lambda x: self.get_order())
self.add_widget(b)
def get_order(self):
ordered_products = self.ordered_products
ordered_products.append("%s")
print("this shall be present", ordered_products)
self.textover = ordered_products
When you declare your label you set its value to self.textover value but when self.textover value changes it doesn't update the label.
You need to change the value of your label by storing it as a class property and updating it whenever you want.
Just refer to this Update label's text when pressing a button in Kivy for Python
class ReviewLayout(BoxLayout):
Price = Price()
textover = ObjectProperty(None)
ordered_products = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
# declare label variable as a class property (in self)
self.label = Label(text = str(self.textover))
self.add_widget(self.label)
self.button = Button(text= 'button')
self.add_widget(self.button)
self.button.bind(on_press=lambda x: self.get_order())
def get_order(self):
ordered_products = self.ordered_products
ordered_products.append("%s")
print("this shall be present", ordered_products)
self.textover = ordered_products
# change class variable text property to be the new textover content
self.label.text = str(self.textover)
I think an easier solution is to use kv to allow it to do the updates for you automatically, like this:
Builder.load_string('''
<ReviewLayout>:
Label:
text: str(root.textover)
Button:
text: 'button'
on_press: root.get_order()
''')
class ReviewLayout(BoxLayout):
Price = Price()
textover = ListProperty() # note change to ListProperty
def get_order(self):
self.textover.append("%s")
print("this shall be present", self.textover)
Related
With Kivy, I understand we can use set the text of a label to a StringProperty() object, so whenever that string is updated, the label will automatically show the updated text.
My minimal example code, works fine, will show "apple" then "banana" one second later:
#test.kv
<MyLabel>:
font_size: 30
text: self.examppleStringProperty
#test.py
class MyLabel(Label):
examppleStringProperty = StringProperty("apple")
def on_kv_post(self, base_widget):
Clock.schedule_interval(lambda dt : self.runlater(), 1)
def runlater(self):
self.examppleStringProperty = "banana"
class TestApp(App):
def build(self): return MyLabel()
Question: How do I do exactly the same, but for a Float? i.e have the label automatically update it's next, whenever the value of the Float is changed?
I have a module that updates a Float value with the current room temperature, which I would just like to show on a label in Kivy, but I'm not sure how to bind it "automagically".
I have tried NumericProperty(), but of course I can't set a label.text to a NumericProperty() object, as it is not a string.
For example, the following code does not work, simply never updates the label text to the number 42, because the NumericProperty isn't bound to the label text in anyway.
class MyLabel(Label):
examppleStringProperty = StringProperty("apple")
exampleNumericProperty = NumericProperty(0)
def on_kv_post(self, base_widget):
self.text = str(self.exampleNumericProperty)
Clock.schedule_interval(lambda dt : self.runlater(), 1)
def runlater(self):
self.exampleNumericProperty = 42
class TestApp(App):
def build(self): return MyLabel()
Just looking for any good way to keep a Label automatically updated with the current value of a Float..
I have found a way to do it (Keep a label.text updated with the value of a Float), by using an "on_NumericProperty" function.
But I would appreciate any advice, if this is good or a bad design - or any suggested alternatives.
class MyLabel(Label):
exampleStringProperty = StringProperty("no data yet")
exampleNumericProperty = NumericProperty(0)
def on_exampleNumericProperty(self, *args):
self.exampleStringProperty = str(self.exampleNumericProperty)
def on_kv_post(self, base_widget):
Clock.schedule_interval(lambda dt : self.runlater(), 3)
def runlater(self):
self.exampleNumericProperty = 42
class TestApp(App):
def build(self): return MyLabel()
Is there a way to check or print a widgets id if you are not sure what id it has? I have a layout I made, its a simple BoxLayout where my Label has its id(its done in the kv file), than I use that Layout multiple times in a GridLayout. Now I have multiple Labels, but they technically have the same id or maybe kivy changes the id if you use it multiple times? Thats what I wanted to check by printining all their ids.
a3.py
from BareBones import Skeleton
menu = ['espresso', 'latte', 'capuccino', 'nescafe', 'Sahlep', 'Mahlep']
keys = []
class Double(BoxLayout):
pass
class NewLayout(GridLayout):
def set_text(self,text):
print(self.root.ids.Skeleton.ids['label_id'].text)
pass
class MainPage(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
iterator = 1
for i in range(len(menu)):
b = Button(size=(100,50))
self.ids[str(iterator)] = b
self.add_widget(b)
keys.append(str(iterator))
iterator = iterator+1
print(keys)
print(len(menu))
for x, y in zip(menu, keys):
self.ids[y].text = x
class RightSide(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
for i in range(len(menu)):
self.add_widget(Skeleton())
kv = Builder.load_file("b3.kv")
class MainApp(App):
def build(self):
return kv
MainApp().run()
b3.kv
NewLayout:
cols: 2
MainPage:
RightSide:
<MainPage>:
orientation: 'vertical'
<Skeleton>
b1: b1
b2: b2
label_id: label_id
Button:
id: b1
text:'+'
on_press: root.on_button_click_plus()
Label:
id: label_id
Button:
id: b2
text:'-'
on_press: root.on_button_click_minus()
BareBones.py
class Skeleton(BoxLayout):
count = 0
my_text = ''
drink_name = ''
def on_button_click_plus(self):
print("Button clicked")
self.count += 1
self.ids.label_id.text = str(self.count)
self.my_text = str(self.count)
return self.my_text
def on_button_click_minus(self):
print("Button clicked")
self.my_text = str(self.count)
self.ids.label_id.text = str(self.count)
if self.count > 0:
self.count -= 1
self.ids.label_id.text = str(self.count)
else:
pass
def trying(self):
pass
class PlusMinus(BoxLayout):
menu = ['espresso']
keys = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
menu = self.menu
self.add_widget(Skeleton())
So this basically looks like this:
What I basically want to do is have a button that collects all the labels texts (the numbers) So that I can pair them with the menu array to know how many of them were added to our basket. I also want to reset the labels however I dont know how I can get the texts that are so far deep in the code.
If you change the drink_name to a StringProperty in the Skeleton class:
class Skeleton(BoxLayout):
count = 0
my_text = ''
drink_name = StringProperty('')
and assign a value to that property when each Skeleton instance is created:
class RightSide(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
for i in range(len(menu)):
self.add_widget(Skeleton(drink_name=menu[i]))
Then you can get a tally of the number of drinks added with a method in the MainPage class:
def tally(self):
rightside = kv.ids.rightside
for skel in rightside.children:
if isinstance(skel, Skeleton):
print(skel.drink_name, skel.ids.label_id.text)
Plus one other required change. In your kv, add an id for the RightSide instance:
NewLayout:
cols: 2
MainPage:
RightSide:
id: rightside
So this is the error Pycharm shows, the red highlighting under kv.
class MainPage(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
iterator = 1
for i in range(len(menu)):
b = Button(size=(100,50))
self.ids[str(iterator)] = b
self.add_widget(b)
keys.append(str(iterator))
iterator = iterator+1
print(keys)
print(len(menu))
for x, y in zip(menu, keys):
self.ids[y].text = x
def tally(self):
rightside = self.kv.ids.rightside
for skel in rightside.children:
if isinstance(skel, Skeleton):
kv = Builder.load_file("b3.kv")
print(skel.drink_name, skel.ids.label_id.text)
class RightSide(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
for i in range(len(menu)):
self.add_widget(Skeleton(drink_name=menu[i]))
kv = Builder.load_file("b3.kv")
class MainApp(App):
def build(self):
return kv
MainApp().run()
So I changed the Stringproperty in the Skeleton file and the code looks like that with the changes you told me to add. However nothing happens, how do I actually use the tally() method?
I am trying to implement an html style SelectBox widget in kivy using the kivy.uix.DropDown.
In the actual application I want to use it like this using kvlang:
SelectBox:
id: appeui
label: "Select an application"
SelectOption:
label: 'Option 1'
value: 1
SelectOption:
label: 'Option 2'
value: 2
SelectOption:
label: 'Option 3'
value: 3
To implement this widget, I have defined the SelectBox and SelectOption below. In the constructor of the SelectBox I am checking properties on the SelectBox, that I am expecting to be set in kvlang. One is the label which is then used as the label for the button. And I am also checking the children of the SelectBox and moving all of them (of type SelecOption) to the DropDown. The problem I am encountering is that in the constructor of SelectBox, there is no label argument, and no children yet either.
I think the SelectBox instantiation already happens before the kvlang stuff is read. So during instantiation the attributes defined in kvlang for SelectBox and also its children aren't known yet.
Is there any other function that gets called after the widget is actually built by kvlang? Or any other way I can actually act upon the way the widget was defined in kvlang?
from kivy.graphics import Color, Rectangle
from kivy.uix.button import Button
from kivy.uix.dropdown import DropDown
from kivy.uix.label import Label
from kivy.uix.widget import Widget
class SelectOption(Label):
def __init__(self,
*args,
label='option',
value=None,
background_color=Color(rgba=(1, 1, 1, 1)),
**kwargs):
super().__init__(*args, **kwargs)
self.label = label
self.value = value
self.background_color = background_color
self.bind(on_touch_down=self.select)
with self.canvas.before:
Color(self.background_color)
Rectangle(pos=self.pos, size=self.size)
def select(self, *args):
self.parent.select(self.value)
class SelectBox(Widget):
def __init__(self, label='Select an option', *args, **kwargs):
super().__init__(*args, **kwargs)
self.dropdown = DropDown()
self.value = None
self.button = Button(text=label)
self.add_widget(self.button)
self.button.id = 'dropdown_label'
self.button.size_hint = (None, None)
self.button.height = '32dp'
self.button.bind(on_release=self.openDropDown)
self.dropdown.bind(on_select=self.set_value)
for child in self.children:
if isinstance(child, SelectOption):
self.dropdown.add_widget(child)
self.remove_widget(child)
def openDropDown(self, *args):
self.dropdown.open(self.button)
def set_value(self, instance, value):
self.ids.dropdown_label.text = value
Ok, I am getting somewhere. The issue here is to just not use the constructor, but use events instead. You can define properties on the SelectBox and use events to intercept changes in that property. When the property is defined in kvlang, this will trigger the event. So in my case I could do this:
class SelectBox(StackLayout, EventDispatcher):
label = StringProperty('Select an option')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.dropdown = DropDown()
self.value = None
self.button = Button(text=self.label)
self.button.id = 'dropdown_label'
self.add_widget(self.button)
self.button.bind(on_release=self.openDropDown)
self.dropdown.bind(on_select=self.set_value)
def on_label(self, instance, value):
print("Label changed")
self.button.text = value
def add_widget(self, child):
print(f"Adding widget {child}")
if isinstance(child, SelectOption):
self.dropdown.add_widget(child)
else:
super().add_widget(child)
def openDropDown(self, *args):
print("Opening dropdown")
self.dropdown.open(self.button)
def set_value(self, instance, value):
self.ids.dropdown_label.text = value
The on_label event gets called as soon as the label property is set in kvlang, and then I just change the button text.
For the child widgets, I am doing something similar by intercepting add_widget. If its a SelectOption, then add it to the dropdown instead of adding it to the SelectBox.
There are still issues with this code, but they are not directly related to this question.
I am new in Kivy: python. I am trying to do an application using ScreenManager. The idea, that I have a map of maps and keys of maps should generate buttons on several screens. For instance:
map: {user1: {thing1: value1, thing2, value2}, user2: {thing1: value1...}}
The first screen will have two buttons in scrollview: user1 and user2.
Collection of buttons on the next screen will be thing1, thingN depending on what "user_name" user has pressed. This is just "toy" project, here I am studying the Kivy
I have some global variables. In the first screen the global variable "user_name" is initialized depending on what user has pressed on the first screen. Then in the class of the second screen I tried using map_of_maps[user_name].keys() and place these keys as buttons on the second screen.
# kivy_test.py
class MyMainApp(App):
gapi = gAPI("tilit.txt")
gapi.file_download()
gapi.parse_downloaded_file()
global data
global user_name
global account
data = gapi.get_data()
user_name = None
account = None
def build(self):
return kv
class MainWindow(Screen):
f_view = ObjectProperty(None)
def __init__(self,**kwargs):
super(MainWindow, self).__init__(**kwargs)
Clock.schedule_once(self.create_scrollview)
def create_scrollview(self, inst):
base = data.keys()
layout = GridLayout(cols=1)
layout.bind(minimum_height=layout.setter("height"))
for element in base:
button = Button(text=str(element), size_hint=(1, 0.1))
button.bind(on_press=self.on_scrbutton_pressed)
layout.add_widget(button)
scrollview = ScrollView(size=(Window.width, Window.height))
scrollview.add_widget(layout)
self.f_view.add_widget(scrollview)
def on_scrbutton_pressed(self, instance):
user_name = instance.text
print(instance.text)
class SecondWindow(Screen):
s_view = ObjectProperty(None)
def __init__(self,**kwargs):
super(SecondWindow,self).__init__(**kwargs)
Clock.schedule_once(self.create_scrollview)
def create_scrollview(self, inst):
base = data[user_name].keys() # Here I have a problem
layout = GridLayout(cols=1)
layout.bind(minimum_height = layout.setter("height"))
for element in base:
button = Button(text = str(element), size_hint=(1,0.1))
button.bind(on_press=self.on_scrbutton_pressed)
layout.add_widget(button)
scrollview = ScrollView(size=(Window.width, Window.height))
scrollview.add_widget(layout)
self.s_view.add_widget(scrollview)
def on_scrbutton_pressed(self, instance):
print(instance.text)
While running the code I have an error: base = data[user_name].keys(). KeyError: None
# my.kv
# Filename: my.kv
WindowManager:
MainWindow:
SecondWindow:
ThirdWindow:
<MainWindow>:
name: "main"
f_view: f_view
GridLayout:
cols: 1
ScrollView:
id : f_view
# Button:
# text: "User1"
# on_release:
# app.root.current = "second"
# root.manager.transition.direction = "left"
# Button:
# text: "User2"
# on_release:
# app.root.current = "second"
# root.manager.transition.direction = "left"
<SecondWindow>:
name: "second"
s_view : s_view
GridLayout:
cols: 1
ScrollView:
id : s_view
Button:
text: "Add"
<ThirdWindow>:
name: "third"
t_view : t_view
GridLayout:
cols: 1
id : t_view
And one more question: How to perform transitions between screens in this case?
Problem 1 - KeyError
base = data[user_name].keys() # Here I have a problem
KeyError: None
Root Cause
When Kivy process your kv file, it instantiates WindowManager object as the root, and it also instantiates its children, MainWindow and SecondWindow. While instantiating SecondWindow, it invoked the constructor (__init__) and method create_scrollview. It threw a KeyError because the global variable, user_name contains the initial value of None which does not exist in the dictionary type, data.
Solution
Add global user_name in method on_scrbutton_pressed of class MainWindow before populating user_name. Without global user_name, Python creates a new local variable when instance.text is assigned to user_name.
Problem 2
How to perform transitions between screens in this case?
Solution
Each screen has by default a property manager that gives you the
instance of the ScreenManager used. Add the following into method
on_scrbutton_pressed of class MainWindow.
self.manager.current = "second"
self.manager.transition.direction = "left"
In class Secondwindow, remove constructor and replace method create_scrollview(self, inst) with on_pre_enter(self)
In class MainWindow, replace constructor, __init__ with on_pre_enter(self):
Add method on_pre_leave to remove widgets added to prevent duplicates
Snippets
class MainWindow(Screen):
f_view = ObjectProperty(None)
def on_pre_enter(self):
Clock.schedule_once(self.create_scrollview)
...
def on_pre_leave(self):
self.f_view.clear_widgets()
def on_scrbutton_pressed(self, instance):
global user_name
user_name = instance.text
print(instance.text)
self.manager.current = "second"
self.manager.transition.direction = "left"
class SecondWindow(Screen):
s_view = ObjectProperty(None)
def on_pre_enter(self):
base = data[user_name].keys()
...
def on_pre_leave(self):
self.s_view.clear_widgets()
I've implemented almost the same thing in my app too.
I have a list of chapter names as keys and their summaries as the
values.
When the user enters the screen, the keys() are called and
buttons are created.
Depending on the button clicked, the summary is
displayed on the next screen
Here's how I coded it (Snippets)
class chapter_list(Screen):
def on_pre_enter(self):
self.buttons = []
for i in flamingo.keys(): #for loop to generate buttons
button = Button(text=str(i),height=150) #the text on the button is the chapter name
button.bind(on_press=self.pressed) #when the button is clicked, the function pressed on called
self.ids.fgrid.add_widget(button) #added to the flamingogrid
self.buttons.append(button)
Here the fgrid is the id: of the Layout(GridLayout in this case) I choose for this screen in the kv file
The self.buttons = [] is just to collect the buttons so that I can remove them on the screen exit.
Let me know if this was helpful.
last day i came across on very irritate behave my program.
Mabye at first i show screenshot, describe error, and finnaly shows code.
As you can see, Coin is a button which contains a few sub-buttons. I generate these widgets in loop and dynamically add to layer.
But it works correctly only in last iterating.
Please look now at code.
Here is a code "Coin Button: class.
Note this, that for example: button apx, Refresh, H, DH and D is a member of one class
class SubButtonRefreshCoinData(Button):
def __init__(self, coin_name, **kwargs):
super(SubButtonRefreshCoinData, self).__init__(**kwargs)
self.CoinName = coin_name
self.text = "Refresh"
def on_press(self):
PopupNotImplementedItem().open()
class SubButtonCoinName(Button):
def __init__(self, **kwargs):
super(SubButtonCoinName, self).__init__(**kwargs)
self.text = r'[color=ff3333]{}[/color]'.format(kwargs["text"])
self.markup = True
self.font_size='20sp'
class SubButtonGoToCoinHistory(Button):
def __init__(self, coin_name, **kwargs):
super(SubButtonGoToCoinHistory, self).__init__(**kwargs)
self.CoinName = coin_name
self.text = "H"
def on_press(self):
subprocess.Popen(f'py HistoryWindow.py {self.CoinName}', shell=True)
class SubButtonDeleteCoin(Button):
def __init__(self, coin_name, **kwargs):
super(SubButtonDeleteCoin, self).__init__(**kwargs)
self.CoinName = coin_name
self.text = "D"
def on_press(self):
PopupNotImplementedItem().open()
class SubButtonDeleteCoinHistory(Button):
def __init__(self, coin_name, **kwargs):
super(SubButtonDeleteCoinHistory, self).__init__(**kwargs)
self.CoinName = coin_name
self.text = "DH"
print("sdfecvsdcdfwreafsq3456tedcqr4536wtger34r5cedwt4g5aced erf34")
def on_press(self):
PopupNotImplementedItem().open()
Now, please take a look on the Builder class these pieces:
class CoinButton(FloatLayout):
def __init__(self, coin_name, **kwargs):
super(CoinButton, self).__init__(**kwargs)
self.CoinName = coin_name
topHalfLayout = BoxLayout(pos_hint={"top":1}, size_hint = [1,0.49], orientation = "horizontal")
topHalfLayout.add_widget(SubButtonCoinName(text=str(self.CoinName)))
topHalfLayout.add_widget(SubButtonRefreshCoinData(self.CoinName))
self.add_widget(topHalfLayout)
downHalfLayout = BoxLayout(pos_hint={"down":1}, size_hint = [1,0.49], orientation = "horizontal")
downHalfLayout.add_widget(SubButtonGoToCoinHistory(self.CoinName))
downHalfLayout.add_widget(SubButtonDeleteCoinHistory(self.CoinName))
downHalfLayout.add_widget(SubButtonDeleteCoin(self.CoinName))
self.add_widget(downHalfLayout)
As you can see, everything seems be correct, but only ONE picece of class is visible.
In class SubButtonDeleteCoinHistory i tired primitive debug this problem to see did this code is already run. As i saw in console, text was printed 3 times, that is correct value.
I am not really sure what is happening here, but I take a shot. I believe that every time a CoinButton is initialized, the bottom button were added with respect to the FloatLayout which and using the pos_hint: down push the button off the kivy app window. But for the life of me, when changing the down value to various ints and floats, there was no change. The best approach was to use pos_hint 'y' since we know that the height of the button is 0.49 the top can start at 0.5 upto 0.99 and the bottom can start at 0 upto 0.49.
topHalfLayout = BoxLayout(pos_hint={"y":0.5},size_hint = [1,0.49], orientation = "horizontal")
......
downHalfLayout = BoxLayout(pos_hint={"y":0},size_hint = [1,0.49], orientation = "horizontal")
Another better approach that I used was to create another BoxLayout which holds the top and bottom layouts, allowing the FloatLayout to add the widget as one thing. It provides better spacing in my opinion. 'outside[ [top/bottom] ]outside'
outside = BoxLayout(pos_hint={"top":1},size_hint = [1,2*0.49], orientation = "vertical")
......
topHalfLayout = BoxLayout(size_hint = [1,0.49], orientation = "horizontal")
......
outside.add_widget(topHalfLayout)
......
downHalfLayout = BoxLayout(size_hint = [1,0.49], orientation = "horizontal")
......
outside.add_widget(downHalfLayout)
self.add_widget(outside)