Kivy updating label when using function via button fails - python

I'm trying to build an App thats uses certain input Parameters and when hitting a submit button it uses a logic to generate output paramters. I managed to build the app and the input and the triggering via a submit button. Now i want to generate the output, for beginning with an easy logic. I looked up several similar solutions, but somehow they don't work for me.
For some reason my .kv file doen't get the updated value for the label text with the error: "ValueError: Label.text accept only str" Eventough everything is declared as a string in the .py. If i change it in the kv to str("...") I get some code line which i guess is the intern id of the attribute but not the assigned value i want to get.
I hope you can help. Pls don't be too harsh, I#M new to python and kivy...
main .py, shuldn't be part of the problem
Peenomat.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.anchorlayout import AnchorLayout
from kivy.core.text import LabelBase
Builder.load_file('Statusbar.kv')
Builder.load_file('Inputparameters.kv')
Builder.load_file('Outputparameters.kv')
#Layout
class Peenomat(AnchorLayout):
pass
class PeenomatApp(App):
def build(self):
return Peenomat()
if __name__=="__main__":
PeenomatApp().run()
.py with the classes and methods for the logic
StatusBar.py
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.properties import ObjectProperty
from kivy.properties import StringProperty
from kivy.app import App
class InputParameters(GridLayout):
verfahren = ObjectProperty(None)
def on_state(self, togglebutton):
tb = togglebutton
if tb.state == 'down':
self.verfahren = tb.text
self.verfahren = tb.text
print(self.verfahren)
return self.verfahren
class StatusBar(BoxLayout):
#InputGrößen
group_mode = False
prozess = ObjectProperty(None)
vorbehandlung = ObjectProperty(None)
material = ObjectProperty(None)
haerte = ObjectProperty(None)
rauheit = ObjectProperty(None)
#OutputGrößen
frequenz = StringProperty(None)
def btn_submit(self):
ip = App.get_running_app().root.ids._input_parameters
print("Haerte:", ip.haerte.value, "Rauheit:", ip.rauheit.value, "Material:", ip.material.text, "Vorbehandlung:", ip.vorbehandlung.text)
if ip.haerte.value < 50:
self.frequency = str(180)
elif ip.haerte.value < 60:
self.frequency = str(200)
else:
self.frequency = str(220)
#control to see if right value is taken
print(self.frequency, "Hz")
def btn_clear(self):
np = App.get_running_app().root.ids._input_parameters
np.pro1.state = "normal"
np.pro2.state = "normal"
np.pro3.state = "normal"
np.material.text = "Auswahl treffen"
np.haerte.value = 55
np.rauheit.value = 5.5
the .kv file that can't get the label text:
outputparameters.kv
#: import statusbar StatusBar
<OutputParameters#GridLayout>
#Initialisierung .py zu .kv
frequenz: _frequenz
Label:
text:'Frequenz:'
font_size:
Label:
id: _frequenz
text: root.frequenz
font_size: 20
the .kv file with the submit button, shouldn't be part of the problem either, worked perfectly fine before implementing the part ehre i try to update the text
statusbar.kv
#: import statusbar StatusBar
<StatusBar#BoxLayout>
orientation:'horizontal'
Button:
text: 'Clear'
on_press: root.btn_clear()
Button:
text: 'Submit'
on_press: root.btn_submit()
the file where i put in all the inputparameters, rather important:
Inputparameters.kv
#: import statusbar StatusBar
<InputParameters#GridLayout>
#Initialisierung .py zu .kv Ids
prozess: _prozess
pro1: _prozess1
pro2: _prozess2
pro3: _prozess3
vorbehandlung: _vorbehandlung
material: _material
haerte: _haerte
rauheit: _rauheit
#Prozess
Label:
text:'Prozess:
BoxLayout:
orientation: 'horizontal'
id: _prozess
ToggleButton:
id:_prozess1
text:'P-MOH'
group: "proc_group"
on_state: root.on_state(self)
ToggleButton:
id:_prozess2
text:'E-MOH'
group: "proc_group"
on_state: root.on_state(self)
ToggleButton:
id:_prozess3
text:'PE-MOH'
group: "proc_group"
on_state: root.on_state(self)
#Material
Label:
text: 'Material:'
Spinner:
id: _material
text: ""
values:
# Herstellschritte
Label:
text:'Fertigungsschritte:'
Spinner:
id: _vorbehandlung
text:
values:
# Haerte
Label:
text:'Haerte:'
BoxLayout:
orientation: 'vertical'
Label:
text: str(_haerte.value)
Slider:
id: _haerte
# Rauheit
Label:
text:'Rauheit:
BoxLayout:
orientation: 'vertical'
Label:
text:
Slider:
id: _rauheit
and the file where my layout is embedded (also rather necessary)
peenomat.kv
<Peenomat>
AnchorLayout:
anchor_x: 'left'
anchor_y: 'bottom'
GridLayout:
cols: 1
canvas.before:
Color:
Rectangle:
pos: self.pos
size: self.size
InputParameters:
id:_input_parameters
StatusBar:
id:_status_bar
OutputParameters:
id:_output_parameters
I really hope you can help, have been struggeling with this for a while and it should rather be easy...thanks in advance!

In your kv rule for <OutputParameters#GridLayout> you have a line:
frequenz: _frequenz
which sets frequenz to be a reference to the Label with the id of _frequenz. Then in that Label you are setting text using:
text: root.frequenz
So, you are trying to set the text of the Label to a reference to that Label
I suggest trying something like this:
<OutputParameters#GridLayout>
#Initialisierung .py zu .kv
frequenz: _frequenz
frequency: ''
And change the Label to:
Label:
id: _frequenz
text: root.frequency
font_size: 20
But to actually change the value shown in the label, you will need a reference to the instance of OutputParameters, using something like:
App.get_running_app().root.ids._output_parameters.frequency = str(500)

Related

Python Kivy self.add_widget() doesn't update while code running

The idea is to create a texting app that works like Messenger. I am having a problem with the chat history which is a "BoxLayer (or GridLayer)" containing all previous text. I want when I insert a new text, it's will appear as a new label or a box and stay below the previous text like this, but when I run the code and insert input text, it's not appearing. I spent hours to find the answer both myself and on the internet, but it's kind of hard for a beginner like me.
.Py file
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.core.image import Image
from kivy.properties import StringProperty
from kivy.core.window import Window
from kivy.graphics.context_instructions import Color
class MainWidget(Widget):
request = StringProperty("This is a previous text, don't mind")
insert_text = StringProperty("Insert Here")
window_size = (305,400)
refresh_key = False
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.Window_Minimum()
def on_size(self,*args):
print(self.width,self.height)
def on_text_validate(self,widget): #<<<<<<<<<<<<<<<<<<<<<<<<<<< input text
request=widget.text
Chat_history_update().chat_history(request)
def Window_Minimum(self):
Window.minimum_width,Window.minimum_height=self.window_size
class Chat_history_update(BoxLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
l = Label(text="This is a previous text, don't mind",size_hint=(1, None),height=("30dp"))
self.add_widget(l)
def chat_history(self,request): # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Add Label Function
l = Label(text=request, size_hint=(1, None),height=("30dp"))
self.add_widget(l) # <<<<<<<<<<<<< This won't update my app screen
class Assistant(App):
pass
if __name__ == "__main__":
Assistant().run()
Kv file
MainWidget:
<MainWidget>:
BoxLayout:
size: root.size
orientation: "vertical"
GridLayout:
cols: 3
size_hint: 1,None
height: "50dp"
spacing: "10dp"
padding: "10dp"
Label:
text:"Erza Assistant"
Button:
text:"Edit Path"
Button:
text:"Setting"
GridLayout:
size: self.size
rows: 2
spacing: "10dp"
padding: "10dp"
ScrollView: #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Here my text display
Chat_history_update:
orientation: "vertical"
size_hint: 1, None
height: self.minimum_height
TextInput:
size_hint: 1, None
height: "40dp"
text: root.insert_text
multiline: False
on_text_validate: root.on_text_validate(self)
Your code:
Chat_history_update().chat_history(request)
is creating a new instance of Chat_history_update, and calling chat_history() for that new instance. That new instance is not part of your GUI, so you will see no effect. The fix is to access the correct instance of Chat_history_update (the one that is in your GUI). To do that, you can add an id in your kv:
ScrollView: #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Here my text display
Chat_history_update:
id: chu
orientation: "vertical"
size_hint: 1, None
height: self.minimum_height
And then use that id in your py code:
def on_text_validate(self,widget): #<<<<<<<<<<<<<<<<<<<<<<<<<<< input text
request=widget.text
self.ids.chu.chat_history(request)
I think this here might help. You have to reference widgets in the kivy language by using id or ids.
If you do not yet i strongly suggest you learn how to reference widgets by their ids.

Recup input value through screen manager kivy

I want to catch a value from my first screen into my thirdscreen.
In the first, I write my name in an input field.
I go to the next window.
And I try to show my name in this last window.
So I share the code with you and I hope I will find an issue.
Python code :
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.properties import ObjectProperty
from kivy.uix.screenmanager import ScreenManager, Screen
#define ou different screens
class FirstWindow(Screen):
def envoyer(self):
name = self.ids.nom_input.text
self.ids.my_name.text = name
class SecondWindow(Screen):
pass
class ThirdWindow(Screen):
#PROBLEM HERE
def on_press(self):
self.ids.recup_infos.text = self.root.get_screen('FirstWindow').ids.my_name.text
class WindowManager(ScreenManager):
pass
class MonWidget(Widget):
pass
kv = Builder.load_file('new_window.kv')
class AwesomeApp(App):
def build(self):
Window.clearcolor = (0,0,0,0)
return kv
if __name__ == '__main__':
AwesomeApp().run()
My KV CODE :
WindowManager:
FirstWindow:
SecondWindow:
ThirdWindow:
<FirstWindow>:
name: "romain"
BoxLayout:
orientation: "vertical"
size: root.width, root.height
Label:
id: my_name
text: "Entrez votre nom"
font_size: 32
TextInput:
id: nom_input
multiline: False
size_hint: (1, .5)
Button:
text: "Next screen"
font_size: 32
on_press: root.envoyer()
on_release:
app.root.current = "Mickael"
root.manager.transition.direction = "left"
<SecondWindow>:
name: "Mickael"
BoxLayout:
orientation: "vertical"
size: root.width, root.height
Label:
text: "Entre votre ville"
font_size: 32
TextInput:
id: ville_input
multiline: False
size_hint: (1, .5)
Button:
text: "Vérifier les infos"
font_size: 32
on_release:
app.root.current = "foncier"
root.manager.transition.direction = "left"
Button:
text: "go back first screen"
font_size: 32
on_release:
app.root.current = "romain"
root.manager.transition.direction = "right"
<ThirdWindow>:
name: "foncier"
BoxLayout:
orientation: "vertical"
size: root.width, root.height
Label:
text: "Verifier : "
font_size: 32
Label:
id: recup_infos
text: ""
font_size: 32
color: 'white'
Button:
text: "On press"
font_size: 32
#Problem HERE
on_press: root.on_press()
Button:
text: "Précedent"
font_size: 32
on_release:
app.root.current = "Mickael"
root.manager.transition.direction = "right"
Could you help me ?
Thank you
Romain
In your on_press method:
def on_press(self):
self.ids.recup_infos.text = self.root.get_screen('FirstWindow').ids.my_name.text
self.root.get_screen('FirstWindow').ids.my_name.text isn't the correct way to get access to widgets outside of the class that you are in right now, or in this situation, screen. The correct way is to use the App.get_running_app() method:
self.ids.recup_infos.text = App.get_running_app().root.ids.First.ids.my_name.text
But before doing that, you have to give ids to the screens of your app, so that the First argument of the method demonstrated above actually makes sense:
WindowManager:
FirstWindow:
id: First
# "First" is the id of the FirstWindow class
# which can also explain why there was a "First" arg
# inside "App.get_running_app().root.ids.First.ids.my_name.text"
SecondWindow:
id: Second
ThirdWindow:
id: Third
Still confused to why this works? Let's divide the attributes of App.get_running_app().root.ids.First.ids.my_name.text into 3 parts:
App.get_running_app(): this method returns the location of your running App class, in this case AwesomeApp. This also acts as self if you were to get the variable inside the App object itself
.root.ids.First: if you read the Kivy documentation, or just simply watched Kivy course videos online, carefully, you should know that self.root.ids inside the App object returns a list of ids of the widgets inside your root widget. In this case, App.get_running_app().root.ids is doing the same thing here, and your screens are passed in the ScreenManager root widget, hence make First an available attribute in App.get_running_app().root.ids
.ids.my_name.text: same as above, App.get_running_app().root.ids.First acts the same as self if you were to run it in your FirstWindow class, which gives you the opportunity to get access to variables outside of your working classes/screens

Kivy - re-focusing input field not possible?

i have the following example using kivy -
When i write something in the input field1 and press Reset - everything works fine (input field get deleted, focus on field1).
But when i am changing something in field2 and press the Reset Button it seems that the App gets broken...
Why is that and why is the statement self.ids.stockTicker.focus = True not working every time?
py-file:
import threading
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.core.window import Window
Builder.load_file("TryApp.kv")
class MyLayout(Widget):
Window.size = (550, 700)
def Reset(self):
self.ids.stockTicker.text = ""
self.ids.stockTicker.focus = True
self.ids.index.text = "SP500"
def pressReset(self):
threading.Thread(target=self.Reset).start()
class MyTry(App):
def build(self):
return MyLayout()
if __name__ == "__main__":
MyTry().run()
kv-file:
<MyLayout>
BoxLayout:
orientation: "vertical"
size: root.width, root.height
GridLayout:
size_hint: (1, .5)
cols: 2
Label:
text: "Field1"
font_size: 18
TextInput:
id: stockTicker
focus: True
Label:
text: "Field2"
font_size: 18
TextInput:
id: index
text: "xyz"
Button:
id: buttonReset
text: "Reset"
#font_size: 20
on_press: root.pressReset()
size_hint: (None,None)
width: 110
height: 70
I think you just need to change:
on_press: root.pressReset()
to:
on_release: root.pressReset()
The reasoning is that when the pressReset() method is triggered by the on_press event, the focus is changed as you desire, but then the Button release event changes focus back to the Button. Changing it to the on_release event eliminates that problem.

Kivy function scope on button release (widget tree)

I am new to Kivy and need some help in understanding function scope. I have built a simple app with two screens. The first screen has two buttons and the second screen has a text label at the centre. On the first screen, I have used app.root.current='new_screen_name' with the on_release attribute for one button and it works fine. It takes me to the next screen which is the intended function. For the second button, I have used a function call which has been defined in the Python file under the class definition of the first screen (the root widget of the button). However, this method does not work and the app window simply closes. I guess I am making a mistake in the function scope and call But I cannot figure out what. Any help would be greatly appreciated.
Python file:
from kivy.config import Config
# Config.set should be used before importing any other Kivy module.
Config.set('kivy','window_icon','sivaicon.png')
# Config set for resizing image button
Config.set('graphics', 'resizable', True)
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.button import Button
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.lang.builder import Builder
class SivaLoginScreen(Screen):
def func_authentication(self):
app.root.current='tabbed_screen'
class SivaTabbedScreen(Screen):
pass
class SivaScreenManager(ScreenManager):
pass
class ImageButton(ButtonBehavior, Image):
pass
# Tell Kivy to directly load a file. If this file defines a root widget, it will be returned by the method.
root_widget = Builder.load_file('siva.kv')
class SivaApp(App):
def build(self):
# Initialize root widget
return root_widget
if __name__ == '__main__':
# Run application
SivaApp().run()
Kivy file (.kv):
SivaScreenManager:
SivaLoginScreen:
SivaTabbedScreen:
<ImageButton>:
keep_ratio: True
<SivaLoginScreen>:
name: 'login_screen'
canvas.before:
Color:
rgba: 195/255, 60/255, 35/255, 1
Rectangle:
pos: self.pos
size: self.size
FloatLayout:
size: root.width, root.height
Image:
id: login_logo_siva
source: 'images/sivalogo4.png'
keep_ratio: True
size_hint: 0.2, 0.2
pos_hint: {'center_x':0.5, 'center_y':0.75}
Label:
id: login_label_siva
pos: self.x*0.5-4, self.y*0.5+15
markup: True
font_name: 'roboto/Roboto-Medium.ttf'
text: '[color=#FDFD98]S.[/color][color=#B29DD9]I[/color][color=#FDFD98].[/color][color=#77DD77]V[/color][color=#FDFD98].[/color][color=#779ECB]A[/color]'
font_size: '50sp'
Label:
id: login_label_slogan1
pos: self.x*0.5-3, self.y*0.5-6
markup: True
font_name: 'roboto/Roboto-Regular.ttf'
text: '[color=#FDFD98]SLOGAN TEXT[/color]'
font_size: '15sp'
Label:
id: login_label_slogan2
pos: self.x*0.5-3, self.y*0.5-20
markup: True
font_name: 'roboto/Roboto-Regular.ttf'
text: '[color=#FDFD98]HEADLINE TEXT[/color]'
font_size: '15sp'
BoxLayout:
id:login_button_layout
orientation: 'horizontal'
size_hint: 0.2, 0.2
pos_hint: {'center_x':0.5, 'center_y':0.25}
ImageButton:
id: first_button
source: {'normal': 'images/first.png', 'down': 'images/first-down.png'} [self.state]
on_release: app.root.current='tabbed_screen'
ImageButton:
id: second_button
source: {'normal': 'images/second.png', 'down': 'images/second-down.png'} [self.state]
on_release: app.root.func_authentication()
<SivaTabbedScreen>:
name: 'tabbed_screen'
FloatLayout:
size: root.width, root.height
Label:
pos: self.x*0.5, self.y*0.5
text: 'SECOND SCREEN'
font_size: '50sp'
In your case, app.root link to SivaScreenManager which is the root widget of your application. And in these class, there is not a func_authenticationfunction, why you app crashed.
To refer a class itself in a KV definition, you must just use root, so right code must be :
on_release: root.func_authentication()
see Kivy Language - Reserved Keywords
Definition of func_authentication is not correct also, app is unknown. Use either :
App.get_running_app().root.current='tabbed_screen' or
self.manager.current='tabbed_screen'

Fail to get kivy widgets by ids

I made this to code to show what I want but fail to do. I get the message: "AttributeError: 'super' object has no attribute 'getattr'" when I try to acces a widget.
Someone please explain how to use widget id:s so that you can acces them from anywhere using python code, i'm sure I'm doing something fundamentally wrong.
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager, SlideTransition
from kivy.lang.builder import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
import random
Builder.load_string('''
<Manager>:
id: screen_manager
SettingScreen:
id: settings
name: 'settings'
manager: 'screen_manager'
MainScreen:
id: mainscreen
name: 'mainscreen'
manager: 'screen_manager'
ThirdScreen:
id: thirdscreen
name: 'thirdscreen'
manager: 'screen_manager'
<SettingScreen>:
BoxLayout:
id: settingbox
orientation: "vertical"
TextInput:
id: getthis
text: "this is the data"
font_size: 40
size_hint: 1,0.1
Button:
text: "NEXT SCREEN"
font_size: 40
size_hint: 1,0.1
on_release:
app.root.current = "mainscreen"
app.root.transition.direction="left"
<MainScreen>:
BoxLayout:
orientation: 'vertical'
Label:
id: changethis
text: "to be changed"
Button:
text: "Push this to make above into the text from TextInput on last screen"
on_release:
app.root.ids.changethis.text = app.root.ids.getthis.text #<<<<<
Button:
text: "Go to third Screen"
on_release:
app.root.current = "thirdscreen"
app.root.transition.direction="left"
<ThirdScreen>
put_label_here: put_label_here
BoxLayout:
orientation: 'horizontal'
Button:
text: 'Make make label'
on_release:
self.parent.parent.makelabel()
BoxLayout:
orientation: 'vertical'
id: put_label_here
''')
class MainScreen(Screen):
def __init__(self, **kwargs):
Screen.__init__(self, **kwargs)
class SettingScreen(Screen):
def __init__(self, **kwargs):
super(SettingScreen, self).__init__(**kwargs)
class ThirdScreen(Screen):
def __init__(self, **kwargs):
super(ThirdScreen, self).__init__(**kwargs)
def makelabel(self): #this should make a Label with text from TextInput on SettingScreen
print('Runs function makelabel')
thelabel=Label(text=self.parent.settings.settingbox.getthis.text)
self.put_label_here.add_widget(thelabel)
class Manager(ScreenManager):
pass
sm = ScreenManager(transition=SlideTransition())
sm.add_widget(MainScreen(name='mainscreen'))
sm.add_widget(SettingScreen(name='settings'))
sm.add_widget(SettingScreen(name='thirdscreen'))
class testApp(App):
def build(self):
return Manager()
if __name__ == "__main__":
testApp().run()
Well, I can fix this for you, but honestly I don't think you are making the right use of kv templates. But it will work anyway.
A new template is a rule by itself, it has its own root, so in return the application root (app.root) won't have direct access to children's ids defined inside their own templates. So you have to reach its direct child which makes a template containing the child you want to access then you reach that child by its id.
For instance, in your kv code, you will need to change line 63 (kv) from:
app.root.ids.changethis.text = app.root.ids.getthis.text
to:
app.root.ids.mainscreen.ids.changethis.text = app.root.ids.settings.ids.getthis.text
and line 99 (python) from:
thelabel=Label(text=self.parent.settings.settingbox.getthis.text)
to:
thelabel=Label(text=self.parent.ids.settings.ids.getthis.text)
Why shouldn't root (or any parent widget) access the ids defined inside separate templates ?
Templates, or Dynamic classes are made for re-usability, just like any none-static class.
Consider the following scenario:
BoxLayout:
CustomWidget:
id: wdg1
CustomWidget:
id: wdg2
Button:
text: 'change text of first label'
on_press: root.ids.lbl.text = 'new text'
<CustomWidget#Widget>:
Label:
text: 'some text'
id: lbl
Now The root widget have two instances of CustomWidget as children, thus two children Labels having the id 'lbl'. Which one should be called if the root called lbl directly?
To handle this, you can call lbl from its direct template instance. Thus: root.ids.wdg1.ids.lbl.text for the label inside the first widget, or root.ids.wdg2.ids.lbl.text for the label inside the second one.

Categories