How to change the string value in multiple places? - python

Let's say I have a string that I used to set a value for a few Kivy widgets. How could I make all the widgets react to the change of that string?
I could make that string a property for each widget, but if I change the property of one widget, the others with the same property aren't going to change accordingly.
The behaviour I want is like list referencing:
st = ["1"]
st1 = st
st.append("2")
print(st1, st) # output: ['1', '2'] ['1', '2']
As you can see, when I change the list st, the list st1 also changes. That's because st1 is a reference to a list. How can I make this behaviour apply to strings? Possibly using Kivy, if Python's standard library does not have a way to achieve what I want
And here is the demonstration in Kivy:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
class TestApp(App):
def change_text(self, bt):
self.string += "!"
print(self.string)
def build(self):
self.string = "Hello"
lb = Label(text = self.string)
bt = Button(on_press = self.change_text)
tx = TextInput(text = self.string)
bl = BoxLayout()
bl.add_widget(lb)
bl.add_widget(tx)
bl.add_widget(bt)
return bl
TestApp().run()
So when the button gets pressed, the App's attribute string changes, but I want that change to be reflected on the widgets to left(Label, TextInput)

In kivy you can use properties for that (not to be confused with python properties, which are a complete different thing). In kv language you should be able to do the following:
MainWidget:
id: "main"
mystring: "asdf"
widget1:
content: main.mystring
widget2:
content: main.mystring
...
Read more in the kivy documentation for properties.

Related

Kivy Buttons on_press method doesn't work

So I have written this basic kivy code. I want to read sentences from a file, and dynamically create buttons for each one of them. Then, I want these buttons to disappear when they are clicked. In a for loop, I create my buttons, and put my buttons with the index i in a list. Then with the on_press method, it should delete itself.
Button_List[i].bind(on_press= lambda x: self.remove_widget(Button_List[i]))
So there is a Button in Button_List[i] , and when it is clicked, it should run:
self.remove_widget(Button_List[i])
so it should delete itself
I have 5 buttons for example, the problem is, that it whichever button I click, it deletes the button with the highest index. And the other buttons dont get deleted. I feel like kivy is only executing the last index, but I am not sure.
Here is my code:
new.py:
import kivy.uix.button as kb
from kivy.app import App
from kivy.uix.widget import Widget
from Nils_Programm_verkürzt import lektionstextlesen
from kivy.uix.gridlayout import GridLayout
sentences = ['example_sentence1','example_sentence2','example_sentence3','example_sentence4','example_sentence5',]
class Button_Widget(Widget):
def __init__(self, **kwargs):
super(Button_Widget, self).__init__(**kwargs)
global Button_List
Button_List = []
for i in range(len(sentences)):
print(i)
Button_List.append(kb.Button(text=sentences[i],pos=(self.width * i, self.height * i)))
Button_List[i].size = 50, 50
print('binding'+ str(i))
Button_List[i].bind(on_press= lambda x: self.remove_widget(Button_List[i]))
print(Button_List[i])
self.add_widget(Button_List[i])
class ButtonApp(App):
def build(self):
return Button_Widget()
if __name__ == "__main__":
ButtonApp().run()
Your help is very much appreciated :).
That's a common problem when using lambda in a loop. They all get the last value of the loop. A fix is to create a new variable that holds the current loop variable value. So, try replacing:
Button_List[i].bind(on_press= lambda x: self.remove_widget(Button_List[i]))
with:
Button_List[i].bind(on_press= lambda x, j=i: self.remove_widget(Button_List[j]))

Kivy Python Scrollview event misfiring when in a class instantiated multiple times

In a Kivy/Python program, I have a class which contains a scrollview. I instantiate this class multiple times. The scrollview is bound to the on_scroll_stop event. My desired outcome is that the on_scroll_stop event will only fire within the instantiated class to which it belongs, but it seems that the event fires across all of the instantiated classes. Am I doing something wrong or is this expected behavior? Working sample below. In this example the "Left" section shows the issue most often, however the error is seen in the right section once you scroll up at the top or scroll down once reaching the bottom. To recreate scroll only one side, preferably the "Left" side. In my actual code which is far more complex the issue is much more prevalent.
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
class DisplaySection(Widget):
def CreateSection(self):
self.display_panel_main_layout = BoxLayout(orientation='vertical')
self.elements_layout = FloatLayout(size_hint_y=None)
self.sv = ScrollView()
self.sv.bind(on_scroll_stop=self.on_scrolling_stop)
self.sv.add_widget(self.elements_layout)
self.display_panel_main_layout.add_widget(self.sv)
return self.display_panel_main_layout
def LoadElements(self,section_name):
self.section_name = section_name
number_of_elemements = 100
element_hieght = 40
layout_height = number_of_elemements * element_hieght
self.elements_layout.height = layout_height
xcord = 0
for x in range(number_of_elemements):
ycord = self.elements_layout.height - element_hieght*x
name = Label(text='Name' + str(x),size_hint_y=None, height=40,pos=(xcord,ycord))
self.elements_layout.add_widget(name)
def on_scrolling_stop(self, sv, value):
#print(sv,value)
print('I am',self.section_name)
class testscrollapp(App):
def build(self):
main_layout = BoxLayout()
section_names = ['Left','Right']
for x, section_name in enumerate(section_names):
section_layout = BoxLayout(orientation='vertical')
btn = Button(text=section_name,size_hint=(1,None))
section_layout.add_widget(btn)
# instantiate the class containing the scroll view
scroll_layout = DisplaySection()
scr = scroll_layout.CreateSection()
section_layout.add_widget(scr)
scroll_layout.LoadElements(section_name)
main_layout.add_widget(section_layout)
return main_layout
testscrollapp().run()
The on_scroll_stop event is dispatched just like touch events, so any ScrollView instance that subscribes to on_scroll_stop will get all the on_scroll_stop events. And just like a touch event, the subscribing ScrollView instance must determine which of the events it is interested in, and ignore the rest. Since the value argument to your on_scrolling_stop is the MouseMotionEvent, you can do a collide_point() test to determine if the scroll event is within the ScrollView:
def on_scrolling_stop(self, sv, value):
if sv.collide_point(*value.pos):
print('\tI am',self.section_name)

How can i update layout on kivy?

I'm doing a YouTube converter. It's ok but when i download and convert i want to let user know that i'm working: how can i update my interface? I'm using kivy. I tried using a popup, i tried using various kind of loading bar. Now i'm trying to update text in a box in the main interface but nothing, it updates after conversion. Why?
Here's my code:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.popup import Popup
from pydub import AudioSegment
from pytube import YouTube
import os
import time
class YouTube2mp3App(App):
def build(self):
self.mylayout = FloatLayout()
mylabel = Label(text= "Benvenuto su YouTube2mp3!\nInserisci il link e premi converti", size_hint=(.8,.2),pos_hint={"x":.1, "y":.7}, font_size=20)
self.txt = TextInput(hint_text="Incolla qui",size_hint=(.9,.1), pos_hint={"x":.05, "y":.4})
self.pb = Label(text="0%", size_hint=(.9,.2), pos_hint={"x":.1, "y":.1}, font_size=20)
#self.txt.bind(on_text_validate=self.convert)
mybutton =Button(text="Nuovo/i video", on_press= self.clearText,size_hint=(.45,.1), pos_hint={"x":.5, "y":.3})
mybutton2 =Button(text="Converti", on_press= self.convert,size_hint=(.45,.1), pos_hint={"x":.05, "y":.3})
self.mylayout.add_widget(mylabel)
self.mylayout.add_widget(self.txt)
self.mylayout.add_widget(self.pb)
self.mylayout.add_widget(mybutton)
self.mylayout.add_widget(mybutton2)
return self.mylayout
def clearText(self, instance):
#clear input
self.txt.text = ''
def convert(self, instance):
try:
#starting to convert using 3 methods
self.pb.text="Preparazione... 10%"
self.mylayout.add_widget(self.pb)
ns=self.check_spazio(self.txt.text)
self.pb.text="Conversione... 33%"
self.mylayout.add_widget(self.pb)
print(ns)
links=self.list_of_links(ns)
self.pb.text="Scaricamento... 80%"
self.mylayout.add_widget(self.pb)
print(links)
self.Tube(links)
self.pb.text="Fatto!... 100%"
self.mylayout.add_widget(self.pb)
content = Button(text='Chiudi', size_hint=(.3,.7))
popup = Popup(title="Fatto!", content=content, auto_dismiss=False, size_hint=(.3,.2))
content.bind(on_press=popup.dismiss)
popup.open()
self.pb.value="0%"
except:
content = Button(text='Chiudi', size_hint=(.3,.7))
popup = Popup(title="Link non valido", content=content, auto_dismiss=False, size_hint=(.3,.2))
content.bind(on_press=popup.dismiss)
popup.open()
def Tube(self, lista):
#convert
for text in lista:
yt=YouTube(text)
ys=yt.streams.get_audio_only()
file=ys.download()
ya=file[:-1]+"3"
AudioSegment.from_file(file).export(ya, format="mp3")
os.remove(file)
def check_spazio(self, string):
#check space between input string
s=list(string)
strig=[]
for i in s:
if i!=" ":
strig.append(i)
string = "".join(strig)
return string
def list_of_links(self, string):
#create a list of strings. Each string is a link
prev=0
links=[]
for i in range (len(string)):
if string[i]=="\n" or string[i]==",":
links.append(string[prev:i])
prev=i+1
elif i==len(string)-1:
links.append(string[prev:i+1])
return links
YouTube2mp3App().run()
When you run a method on the main thread, as you are doing with convert() method, no updates are made to the GUI until that method returns. This is a common problem and is typically handled by running the long running method in a separate thread. See the threading documentation. Then, from inside that method use Clock.schedule_once() to schedule calls that update the GUI on the main thread.

Is ScreenManager compatible with DropDown in Kivy?

I want to generate a dropdown-list in my second screen managed by Kivys ScreenManager. If I do so, I get this traceback:
...
File "C:/Users/ORANG/PycharmProjects/waldi/playground/cw.py", line 76, in on_text
instance.drop_down.open(instance)
File "C:\Kivy-1.9.0-py2.7-win32-x64\kivy27\kivy\uix\dropdown.py", line 215, in open
'Cannot open a dropdown list on a hidden widget')
kivy.uix.dropdown.DropDownException: Cannot open a dropdown list on a hidden widget
This is the code, which is basically the same as in this
example, just embedded in a screenmanager scenario simple as can be:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.textinput import TextInput
from kivy.properties import ListProperty, StringProperty
import re
from kivy.lang import Builder
Builder.load_string('''
<Lieferant>:
ComboLayout:
Label:
text: 'Label'
ComboEdit:
size_hint: .5, .3
pos_hint: {'center':(.5, .5)}
# `args` is the keyword for arguments passed to `on_text` in kv language
on_text: self.parent.on_text(self, args[1])
''')
class ComboEdit(TextInput):
"""
This class defines a Editable Combo-Box in the traditional sense
that shows it's options
"""
options = ListProperty(('',))
'''
:data:`options` defines the list of options that will be displayed when
touch is released from this widget.
'''
def __init__(self, **kw):
ddn = self.drop_down = DropDown()
ddn.bind(on_select=self.on_select)
super(ComboEdit, self).__init__(**kw)
def on_options(self, instance, value):
ddn = self.drop_down
# clear old options
ddn.clear_widgets()
for option in value:
# create a button for each option
but = Button(text=option,
size_hint_y=None,
height='36sp',
# and make sure the press of the button calls select
# will results in calling `self.on_select`
on_release=lambda btn: ddn.select(btn.text))
ddn.add_widget(but)
def on_select(self, instance, value):
# on selection of Drop down Item... do what you want here
# update text of selection to the edit box
self.text = value
class ComboLayout(BoxLayout):
rtsstr = StringProperty("".join(("Substrate1,,,Substrate1,,,Substrate1,,,",
"Substrate1,,,Substrate1,,,Substrate_coating",
",,,silicon,,,silicon_Substrate,,,substrate_",
"silicon,,,")))
def on_text(self, instance, value):
if value == '':
instance.options = []
else:
match = re.findall("(?<=,{3})(?:(?!,{3}).)*?%s.*?(?=,{3})" % value, \
self.rtsstr, re.IGNORECASE)
# using a set to remove duplicates, if any.
instance.options = list(set(match))
instance.drop_down.open(instance)
class Intro(Screen):
pass
class Lieferant(Screen):
pass
class CWApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(Intro(name='Intro'))
sm.add_widget(Lieferant(name='Lieferant'))
return sm
if __name__ == '__main__':
CWApp().run()
Is it possible to combine them? How would you do this?
This code is running if I just comment this line out, which adds a screen before the screen with the dropdown:
sm.add_widget(Intro(name='Intro'))
Ok - for this question I got the "Tumbleweed Badget" - LOL
I admit it's a very special one and makes obvious, how much I am a beginner here. So I asked another question based on the same problem and spend some effort to make it easier to understand the code and to reproduce it. This paid off! Look here for the solution if you tumble once into that kind of problem: Is it a bug in Kivy? DropDown + ScreenManager not working as expected

Kivy: iterate list with lambda

I want to iterate through a list of button and bind each button with a different function. But the result is always the last Button's function, not all in the list.
Here's my code:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.stacklayout import StackLayout
from kivy.uix.button import Button
class MyApp(App):
def build(self):
parent = StackLayout()
button_list = []
for i in range(0,11):
button_list.append( Button(text = str(i), size_hint = (None,0.15)) )
print i
for a_button in button_list:
parent.add_widget(a_button)
a_button.bind( on_press = lambda x: parent.add_widget( Label(text = a_button.text) ) )
return parent
if __name__ == "__main__":
MyApp().run()
There is some problem with my lambda function, I guessed.
This is a classic python problem, not actually related to kivy. It is discussed e.g. here.
For those still running into this problem 6 years later, here is my solution:
def buttongenerator(layout):
hellodict = {}
for k in range(50):
hellodict[str(k)] = 'Hello' + str(k)
btn = Button(text=str(k),
size_hint_y=None,
height='40dp')
btn.bind(on_release=(lambda instance, var=hellodict[str(k)]: print(var)))
layout.add_widget(btn)
Layout simply refers to a grid/box layout.
The instance variable is necessary because otherwise the button object will be passed into the lambda function and (in this case) will be printed to the console as a string. The instance variable sort of "absorbs" the button object, if that makes any sense. (The variable does not have to be named instance, it just has to be present)

Categories

Resources