How to dynamically update a Kivy label from a continuously running function? - python

I am trying to have a function run continuously and spit out a distance that the label uses that I'll eventually tie to a sonar module, but the label remains blank and I am at a loss as to what I am doing wrong. If I just add a print statement for that distance variable it prints and updates just fine, just can't get the label to use it.
Part II of my question is how do I reference my same function in the second window and also have a label that updates from that same function?
Thanks for the help in advance, I am very very new to kivy and just started learning python a few months ago as well.
Python code:
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen # for multiple screens
from kivy.properties import StringProperty
class MySonar(Screen):
global i
i = 1
distance = StringProperty("")
#Generic function that just adds itself up, just using to try and get the label to change before I throw in my real function
def sonar(self):
global i
if i < 250:
distance = (10 + .1 * i)
i += 1
else:
i = 1
distance = 10
self.root.distance=str(distance)
class DropWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
kv = Builder.load_file("help.kv")
class HelpMe(App):
def build(self):
#running interval update to keep running code above
Clock.schedule_interval(lambda dt: MySonar.sonar(self), 0.1)
return kv
if __name__ == "__main__":
HelpMe().run()
Kivy:
WindowManager:
MySonar:
DropWindow:
<MySonar>:
name:"Main"
GridLayout:
cols:1
##Need this to update
Label:
text:root.distance
Button:
text:"Next Window"
on_release:
app.root.current="Drop"
root.manager.transition.direction="left"
<DropWindow>:
name:"Drop"
GridLayout:
cols:1
##Need this to update, commented out the text so the program will run and you can see the blank label for part one of my question
Label:
##text:root.distance
Button:
text:"Cancel"
on_release:
app.root.current="Main"
root.manager.transition.direction="right"

To simplify the access to distance, you can put that StringProperty in the HelpMe App:
class MySonar(Screen):
global i
i = 1
#Generic function that just adds itself up, just using to try and get the label to change before I throw in my real function
def sonar(self):
global i
if i < 250:
distance = (10 + .1 * i)
i += 1
else:
i = 1
distance = 10
# set the value of distance in the StringProperty of the App
App.get_running_app().distance=str(distance)
print(distance)
class DropWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
# kv = Builder.load_file("help.kv")
class HelpMe(App):
distance = StringProperty('')
def build(self):
kv = Builder.load_file("help.kv")
#running interval update to keep running code above
sonar_instance = kv.get_screen('Main')
Clock.schedule_interval(lambda dt: sonar_instance.sonar(), 0.1)
return kv
if __name__ == "__main__":
HelpMe().run()
Note that I have also moved the Builder.load_file() inside the App. This is good practice when you reference app in the kv file (as I have done). Also, calling the sonar() method using MySonar.sonar(self) will not work. You need to use a reference to the instance of MySonar that is in your GUI.
Now the kv file becomes:
WindowManager:
MySonar:
DropWindow:
<MySonar>:
name:"Main"
GridLayout:
cols:1
##Need this to update
Label:
text: app.distance
Button:
text:"Next Window"
on_release:
app.root.current="Drop"
root.manager.transition.direction="left"
<DropWindow>:
name:"Drop"
GridLayout:
cols:1
##Need this to update, commented out the text so the program will run and you can see the blank label for part one of my question
Label:
text: app.distance
Button:
text:"Cancel"
on_release:
app.root.current="Main"
root.manager.transition.direction="right"
The change is that the text attribute of both Labels is now just app.distance.

Related

How can i communicate between diffrent layout classes in kivy python

I wanted to know how can i make communication between multiple classes of the layout. I'm trying to make the button on the MainLayout called add, add another button to the stacklayout which is one of its children.
in doing so, I have to both share a variable and also a functionality between them to implement the add_widget function on the stack layout. no matter what I do, I can't find a solution
code main.py:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.metrics import dp
from kivy.uix.stacklayout import StackLayout
class Buttons_layout(StackLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.number = 0
for _ in range(5):
self.number += 1
self.add_widget(Button(text=str(self.number),color=(200,100,100),size_hint=(0.2,None),height=dp(100)))
class MainWidget(BoxLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
def add_button(self):
#dont know what to do here..................
pass
class CanvosExampleApp(App):
pass
if __name__ == '__main__':
CanvosExampleApp().run()
and the kv file:
MainWidget:
<Buttons_layout>:
<Scroll_layout#ScrollView>:
Buttons_layout:
size_hint: 1,None
height:self.minimum_height
<MainWidget>:
Button:
text:'add'
on_press: root.add_button()
size_hint:None,1
width:dp(50)
Scroll_layout:
To allow easy navigation in your GUI, you can use ids. Here is a modified version of your kv with two new ids:
MainWidget:
<Buttons_layout>:
<Scroll_layout#ScrollView>:
Buttons_layout:
id: butts # new id
size_hint: 1,None
height:self.minimum_height
<MainWidget>:
Button:
text:'add'
on_press: root.add_button()
size_hint:None,1
width:dp(50)
Scroll_layout:
id: scroll # new id
Then, your add_button() method can be:
def add_button(self):
scroll = self.ids.scroll # get reference to the Scroll_layout
butts = scroll.ids.butts # get reference to the Buttons_layout
butts.add_widget(Button(text='Added',color=(200,100,100),size_hint=(0.2,None),height=dp(100)))

Kivy: How to change the attribute's value like text of label from current screen in another screen

I tried to change the text of another screen from current screen. But it didn't work
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.properties import ObjectProperty
from kivy.lang.builder import Builder
Builder.load_string("""
<Input_Screen>:
input: input
BoxLayout:
orientation : "vertical"
TextInput:
id: input
text: "Changed"
Button:
text: "Enter"
on_press : root.clicked()
<Display_Screen>:
nice: nice
BoxLayout:
orientation : "vertical"
Label:
id: nice
text: "NotChanged"
""")
class Input_Screen(Screen):
input = ObjectProperty(None)
def clicked(self):
Display_Screen().change()
class Display_Screen(Screen):
nice = ObjectProperty(None)
def change(self):
print(self.nice.text) #Checking the Current text of The Label
print(Input_Screen().input.text) #Checking What do we want to change
self.nice.text = Input_Screen().input.text
print(self.nice.text) #Checking if it has change or not
MyApp().sm.current = "Ds" #Changing Screen to Display_Screen
print(self.nice.text) #Checking if it has change or not
class MyApp(App):
sm = ScreenManager()
def build(self):
self.sm.add_widget(Input_Screen(name="Is"))
self.sm.add_widget(Display_Screen(name="Ds"))
return self.sm
MyApp().run()
What I get in My console:
NotChanged #Checked the Current text of The Label
Changed #Checked What do we want to change
Changed #Checked if it has change or not
Changed #Checked if it has change or not
It says that my Label's text has been changed but when it goes to the next screen it hasn't been changed.
Display_Screen
Anyone knows whats the problem? Help me please
Whenever you use a construction like ClassName(), you are creating a new instance of ClassName. You are doing this in your change() method and in your clicked() method. Whenever you do that, you are referencing that new instance, and not the instance that is in your GUI.
To fix that, change:
def clicked(self):
Display_Screen().change()
to:
def clicked(self):
self.manager.get_screen('Ds').change()
and in your change() method, replace:
Input_Screen()
with:
self.manager.get_screen('Is')
and replace"
MyApp()
with:
App.get_running_app()

How to automatically update Kivy label created inside Python using Kivy properties

I want to implement the following:
Create a Kivy label inside python using add_widget() function. However, I want to benefit from the fact that the label is automatically updated when the text variable has changed (in my case self.gui_data.number). I know that I could achieve this when adding the label inside the .kv file, however this is not possible in my case since I need to build up parts of the GUI in run time.
Below you see code for a minimal example. There is a button add label which adds the label and the goal is that if one presses the decrement button, the label changes. Unfortunately this is not the case.
Any help is appreciated. Thanks!
main.py
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.widget import Widget
import parameters
from kivy.properties import StringProperty
from kivy.event import EventDispatcher
class PongBall(Widget):
gui_data = parameters.Data()
def add_label(self):
self.gui_data.number = self.gui_data.number + '5'
label = Label(text=str(self.gui_data.number))
self.ids.board.add_widget(label)
def decrement(self):
self.gui_data.number = self.gui_data.number + '6'
def reset_parameters(self):
self.gui_data.reset()
class PongApp(App):
def build(self):
game = PongBall()
return game
if __name__ == '__main__':
PongApp().run()
parameters.py
from kivy.properties import StringProperty
from kivy.event import EventDispatcher
class Data(EventDispatcher):
number = StringProperty("0")
def reset(self):
self.number = "0"
pong.kv
<PongBall>:
BoxLayout:
orientation: "vertical"
id: board
Button:
text: "Add label"
on_press: root.add_label()
Button:
text: "Decrement"
on_press: root.decrement()
Button:
text: "Reset parameters"
on_press: root.reset_parameters()
In order to do what you want, you need a Property that you can bind to. In the modified version of your code below, I have added a number Property to the PongBall class.
class PongBall(Widget):
gui_data = Data()
number = StringProperty(gui_data.number) # Property for binding
def add_label(self):
self.gui_data.number = self.gui_data.number + '5'
label = Label(text=str(self.gui_data.number))
# bind to the number Property
self.bind(number=partial(self.number_changed, label))
self.ids.board.add_widget(label)
def number_changed(self, label, pongball, number):
# change the Label text
label.text = number
def decrement(self):
self.gui_data.number = self.gui_data.number + '6'
def reset_parameters(self):

In kivy, How can I control the height of the VKeyboard used by TextInput

I have a Screen where I want to ask a question, so in the kv language, I have
Screen:
name: 'keyb'
BoxLayout:
orientation: 'vertical'
Label:
id: rnl
size_hint_y: .1
text: ''
TextInput:
id: tinput
hint_text: '.mid'
size_hint: (.8, .1)
pos_hint: {'x': .1}
multiline: False
on_focus: root.focusCallback(args[1])
Widget:
size_hint_y: .7
When I want to ask the question, I select the screen and set the focus to the TextInput
self.tinput.text = ""
self.screens.current = 'keyb'
Clock.schedule_once(self.focusKbd, .1)
which then cascades through this code:
def focusKbd(self, x):
self.tinput.focus = True
def focusCallback(self, f):
if not f:
# Deal with input now
self.screens.current = 'seq'
Somewhere in this code, I would like to
Select which VKeyboard layout the TextInput is going to pop up (and it will be different in other parts of my code)
Adjust the height of the VKeyboard.
I don't understand where the VKeyboard comes from in Text Input; is there some way I can get a reference to it from the TextInput?
This is a restatement of https://stackoverflow.com/questions/36414654/how-can-i-change-the-size-of-kivys-vkeyboard There is a hint there, but not enough to get me going!
*********************Edit: **************************
Trying to understand the answer from Tshirtman, I put this at the top of my main widget:
Window.set_vkeyboard_class(get_vkeyboard())
and then added
def get_vkeyboard():
print '>>>>>>>>>>>>>>>>>>>>>>>>>>in get_vkeyboard'
return VKeyboard
I found that get_vkeyboard() was called immediately, long before I needed a keyboard. So it seems that I'm not going to be able to control anything about the appearance of the VKeyboard dynamically in get_vkeyboard(). Please correct me if I'm confused (which I undoubtedly am!).
Is there some other way to modify the appearance of the VKeyboard dynamically?
P.S. There seems to be a hint in Focus Behavior:
input_type is an OptionsProperty and defaults to ‘text’. Can be one of
‘text’, ‘number’, ‘url’, ‘mail’, ‘datetime’, ‘tel’ or ‘address’.
I added "input_type: 'number'" to a TextInput in .kv, but it didn't have any affect.
You can set keyboard class using Window.set_vkeyboard_class, but nothing prevents you from registering any function that returns a keyboard instance there, so you could use the context (your app's state) to decide which class will be used, dynamically. Since you are returning an instance yourself in this case, you can decide of its size, pos, and other details. I used this technique in a few app to use Animation on the instance to place it in a particular spot of the screen smoothly.
pseudo example:
from kivy.core.window import Window
from kivy.uix.vkeyboard import VKeyboard
from kivy.animation import Animation
from kivy.uix.screenmanager import ScreenManager, Screen
class KeyboardA(VKeyboard):
def place(self):
self.center_x = Window.center_x
self.top = 0
Animation(y=100, t='out_elastic', d=.4).start(self)
class KeyboardB(VKeyboard):
def place(self):
self.opacity = 0
Animation(opacity=1).start(self)
class MyApp(App):
def build(self):
sm = ScreenManger()
sm.add_widget(Screen(name='a'))
sm.add_widget(Screen(name='b'))
return sm
def get_keyboard(self, **kwargs):
if self.root.current == 'a':
kb = KeyboardA(**kwargs)
else:
kb = KeyboardB(**kwargs)
kb.place()
return kb
Window.set_vkeyboard_class(app.get_keyboard)
untested, but you should get the idea.

Get value from kivy text input in kv during build

I have three questions/problems:
given below configuration of app/app kv file why do I have to call self.build() in ButtonsFactory class. If i remove it, i get black screen, probably because there is no root element in kv file, yet if i tried making MainView root element i end up with black screen as well.
second question, is it possible to set in my someapp.kv file minimum height for custom buttons? They should never be less than X.
last but for me most important why I am unable to get text property of TextInput from kv file, in class function get_list_of_files()? What would be best way to fix this? Global variable which hardcode this value in python code? Moving Builder before python code (embedd kv as string)?
last question...buttons "fill" scrollview_id instead of preserve size and be scrollable inside this view, I'd like them to stop self adjusting size.
someapp.py file
from kivy.app import App
from kivy.factory import Factory
from kivy.properties import ObjectProperty, StringProperty
# views
from kivy.uix.modalview import ModalView
# layouts
from kivy.uix.boxlayout import BoxLayout
import os
# defined in kv file.
class HeaderContainer(BoxLayout): pass
class ButtonsContainer(BoxLayout): pass
class MainView(ModalView): pass
class ButtonsFactory(BoxLayout):
target_location = StringProperty(None)
def __init__(self, *args, **kwargs):
super(ButtonsFactory, self).__init__(*args, **kwargs)
self.build() # Question 1: is that neccessary? (i think not, but black screen without it, why?)
def build(self):
self.orientation = "vertical"
for file_name in self.get_list_of_files():
btn = Factory.CustomButton()
with open(file_name, 'r') as test_file:
btn.file_name = test_file.readline().strip()[1:20]
btn.nice_name = file_name # Question 2: is it possible to set minimum height for kivy button? (havent found in api)
self.add_widget(btn)
# print ("1.!!!!!!", self.target_location) == NONE
#classmethod
def get_list_of_files(cls):
# print "2.!!!!!!", repr(cls.target_location) == <kivy.properties.StringProperty object at 0x7f1dd7596e20>
dir_ = "/tmp" #dir_ = cls.target_location
try:
files = [os.path.join(dir_, name) for name in os.listdir(dir_)
if os.path.isfile(os.path.join(dir_, name))]
except (OSError, IOError):
files = []
return files
class SomeApp(App):
def on_pause(self):
pass
def on_resume(self):
pass
def build(self):
return MainView()
if __name__ == '__main__':
SomeApp().run()
and someapp.kv file
#:kivy 1.8.0
#:import platform platform
<CustomButton#Button>:
file_name: ''
nice_name: ''
text: root.nice_name + "\n" + root.file_name
halign:'center'
size_hint:(1, 0.1)
<HeaderContainer>:
id: header_layout
size_hint:(1, 0.1)
orientation:'horizontal'
# 2-nd-try # target_location: textinput_target_location.text
# I was trying here to pass by ObjectProperty (or StringProperty) but unfortunately failed.
TextInput:
size_hint:(0.7, 1)
id: textinput_target_location
multiline: False
hint_text: "path where stress files are stored, default /sdcard/appdir"
text: "/tmp" if platform.machine() in ["x86_64", "i686", "i386"] else "/sdcard/appdir/" # arm "arm7l", but also other arm's
#on_text: my_callback_to_reload_dir_contents()
Button:
size_hint:(0.2, 1)
id: read_target_location
text: "read target_location directory"
#on_release: my_callback_to_reload_dir_contents()
<ButtonsContainer>:
size_hint:(1, 0.9)
orientation:'vertical'
ScrollView:
id: scrollview_id
orientation: 'vertical'
ButtonsFactory
<MainView>:
BoxLayout:
# 1-st-try # target_location: HeaderContainer.target_location
id: main_layout
padding:10
spacing: 5
orientation:'vertical'
HeaderContainer
# n-th-try # target_location: HeaderContainer.target_location
ButtonsContainer
I'll try to address your questions:
1) Looking at your code, the ButtonsFactory is just a BoxLayout, which is a container for holding other widgets. The self.build function creates CustomButton widgets and puts them in the container. If you don't call self.build, nothing gets added to the container and you have an empty (blank) BoxLayout.
2) This is a little more complicated as the hight is usually controlled by what container is holding your widget. You can manually set height of widgets by setting the size_hint property to None and specifying the height.
3) I would never use global variables if they can be avoided. In this case, if all you need is access to the text content of the TextInput, I would bind it to a StringProperty attached to a widget you can access or the App object itself (which can be accessed anywhere in .kv as app.<property>.
4) Again, the height is controlled by the container the widgets are in (a BoxLayout). You can manually control their size by setting size_hint to None on the widgets.
Another problem is that you are putting a BoxLayout (your ButtonsFactory) inside a ScrollViewer. The BoxLayout will resize itself to fit exactly inside the ScrollViewer so it will not scroll. You will need to override this behavior by clearing size_hint. See this SO question for details.

Categories

Resources