How to make press method on this code working? When I press a button the list populates, but when I call it from Clock then not. I can see populate print on the console but the list does not appear in the view. I mean simply: how to stimulate pressing the button in the code?
from kivy.config import Config
Config.set('graphics', 'multisamples', '0')
from random import sample
from string import ascii_lowercase
import pyrebase
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
kv = """
<Row#BoxLayout>:
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 1
Rectangle:
size: self.size
pos: self.pos
value: ''
Label:
text: root.value
<Test>:
canvas:
Color:
rgba: 0.3, 0.3, 0.3, 1
Rectangle:
size: self.size
pos: self.pos
rv: rv
orientation: 'vertical'
GridLayout:
cols: 3
rows: 2
size_hint_y: None
height: dp(108)
padding: dp(8)
spacing: dp(16)
Button:
id: populate_btn
text: 'Populate list'
on_press: root.populate()
RecycleView:
id: rv
scroll_type: ['bars', 'content']
scroll_wheel_distance: dp(114)
bar_width: dp(10)
viewclass: 'Row'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(2)
"""
Builder.load_string(kv)
class Test(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def populate(self):
print("populate")
self.rv.data = [{'value': ''.join(sample(ascii_lowercase, 6))}
for x in range(50)]
def press(self):
self.ids.populate_btn.dispatch('on_press')
def interval(dt):
x = Test()
x.press()
Clock.schedule_interval(interval, 3)
class TestApp(App):
def build(self):
return Test()
if __name__ == '__main__':
TestApp().run()
The error is caused because the Test object created in interval function is different from the Test object that returns build method, besides that the Test object created in interval is eliminated since it is a local variable. So the solution is to use the same reference by passing it to the interval function for it I will use functools.partial() function.
# ...
from functools import partial
# ...
def interval(x, dt):
x.press()
class TestApp(App):
def build(self):
t = Test()
Clock.schedule_interval(partial(interval, t), 3)
return t
# ...
Related
I want to use a list of values like ['btn1', 'btn2','btn3'] to create buttons dynamically on my layout.
After that I want capture the name of the button when pressed
but I'm stuck at adding buttons step please help
This is my main.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.button import Button
Builder.load_file('design.kv')
class MyLayout(Widget):
btns = ['btn1', 'btn2','btn3']
for i in btns:
self.add_widget(Button(text = i))
class MyApp(App):
def build(self):
return MyLayout()
if __name__ == "__main__":
MyApp().run()
and this is my design.kv file
<MyButton#Button>:
font_size: 24
size_hint: [.50, None]
height: 60
<MyLayout>
GridLayout:
rows: 2
size: root.width, root.height
Label:
size_hint: 1, None
size: 120 , 120
text: "Welcome"
font_size: 42
rgb: utils.get_color_from_hex('#5ee2e5')
ScrollView:
do_scroll_x: False
do_scroll_y: True
StackLayout:
canvas:
Color:
rgb: utils.get_color_from_hex('#5ee2e5')
Rectangle:
pos: self.pos
size: self.size
cols:1
pos_hint: {'center_x':.5}
size_hint_y: None
spacing: 5
padding: 10
height: self.minimum_height
top: self.height
MyButton:
text: "Btn1"
You can do that by adding an id for the StackLayout in kv:
StackLayout:
id: stack
Then add a method in the MyLayout class to add the Buttons, along with a call to that method in the MyApp class:
class MyLayout(Widget):
def add_butts(self, dt):
btns = ['btn1', 'btn2', 'btn3']
for i in btns:
self.ids.stack.add_widget(Button(text=i, size_hint=(None, None), size=(100, 60)))
class MyApp(App):
def build(self):
ml = MyLayout()
Clock.schedule_once(ml.add_butts)
return ml
I have a music app written with kivymd that scans your default download directory and extracts information about your audio files. (Basically retrieving the IDV3 tags and other things)
I only have 20 audio files at max on my desktop which shouldn't be a problem but I decided to test my program with many audio (like 120) files and the results were horrible.
The interface was so slow that it barely worked. How am I supposed to display large set of widgets in kivy without causing such catastrophic performance degradation?
Also, how can I make the loading of the widgets asynchronous so that they don't take up a lot of time on startup as well?
A minimal reproducible example of my code:
import os
from kivy.lang import Builder
from kivy.uix.behaviors import ButtonBehavior
from kivy.properties import ObjectProperty
from kivymd.uix.behaviors import RectangularRippleBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.app import MDApp
class Song:
"""Class for extracting information about an audio file."""
def __init__(self, path: str):
self._path = path
self._title = "My Song"
self._artist = "Unknown artist"
# the album cover is going to a texture retrieved from the file itself
# but in this case, I will refer to a file.
self._album_cover = "Default-Album-Cover.jpg"
#property
def path(self):
return self._path
#property
def title(self):
return self._title
#property
def artist(self):
return self._artist
#property
def album_cover(self):
return self._album_cover
class SongCard(ButtonBehavior, RectangularRippleBehavior, MDBoxLayout):
"""Custom widget for creating cards."""
song_obj = ObjectProperty(Song("dummy song"), rebind=True)
class MainApp(MDApp):
def __init__(self, **kwargs):
super(MainApp, self).__init__(**kwargs)
self.theme_cls.theme_style = "Dark"
self.kv = Builder.load_string('''
#:kivy 2.0.0
<SongCard>:
orientation: "vertical"
size_hint_y: None
height: "300dp"
radius: ("10dp",)
canvas.before:
Color:
rgba: app.theme_cls.bg_dark
RoundedRectangle:
pos: self.pos
size: self.size
radius: root.radius
FitImage:
source: root.song_obj.album_cover
MDLabel:
text: root.song_obj.title
adaptive_height: True
MDLabel:
text: root.song_obj.artist
adaptive_height: True
ScrollView:
do_scroll_x: False
MDGridLayout:
id: song_grid
cols: 2
adaptive_height: True
spacing: "10dp"
padding: "10dp"
''')
def build(self):
return self.kv
def on_start(self):
for _ in range(25):
for audio_file in os.listdir(os.path.join(os.path.expanduser('~'), "Downloads")):
if audio_file.endswith(".mp3"):
song_obj = Song(audio_file)
self.kv.ids.song_grid.add_widget(
SongCard(
song_obj=song_obj
)
)
if __name__ == '__main__':
MainApp().run()
You can use the RecycleView class to manage the amount of widgets and help your program to be able to accept a big amount of widgets, the ScrollView is great but recycleview manages data as dictionary and then you can use two BoxLayout class under a BoxLayout widget to have the GridLayout class functionality, I tested this with 213 elements and it works with a lot of widgets and good performance:
import os
from kivy.lang import Builder
from kivy.uix.behaviors import ButtonBehavior
from kivy.properties import ObjectProperty, StringProperty
from kivymd.uix.behaviors import RectangularRippleBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.app import MDApp
from kivy.uix.recyclegridlayout import RecycleGridLayout
class Song:
"""Class for extracting information about an audio file."""
def __init__(self, path: str):
self._path = path
self._title = "My Song"
self._artist = "Unknown artist"
# the album cover is going to a texture retrieved from the file itself
# but in this case, I will refer to a file.
self._album_cover = "Default-Album-Cover.jpg"
## #property
## def path(self):
## return self._path
##
## #property
## def title(self):
## return self._title
##
## #property
## def artist(self):
## return self._artist
##
## #property
## def album_cover(self):
## return self._album_cover
class SongCard(MDBoxLayout):
"""Custom widget for creating cards."""
#song_obj = ObjectProperty(Song("dummy song"), rebind=True)
song_obj = StringProperty()
class B2(ButtonBehavior, RectangularRippleBehavior, MDBoxLayout):
"""Custom widget for creating cards."""
pass
class MainApp(MDApp):
def __init__(self, **kwargs):
super(MainApp, self).__init__(**kwargs)
self.theme_cls.theme_style = "Dark"
self.kv = Builder.load_string('''
#:kivy 2.0.0
<SongCard>:
orientation: "vertical"
size_hint_y: None
height: "300dp"
#radius: ("10dp",)
BoxLayout:
padding: dp(10)
spacing: dp(5)
B2:
padding: dp(10)
canvas.before:
Color:
rgba: [0,1,0,.2]
RoundedRectangle:
pos: self.pos
size: self.size
radius: [30,]
orientation: "vertical"
spacing: dp(5)
FitImage:
size_hint: 1,1
source: "Help5/cc.png" #root.song_obj.album_cover
MDLabel:
text: root.song_obj[:40] #.title
adaptive_height: True
MDLabel:
text: " - jbsidis" #.artist
adaptive_height: True
B2:
padding: dp(10)
canvas.before:
Color:
rgba: [1,0,0,.2]
RoundedRectangle:
pos: self.pos
size: self.size
radius: [30,]
orientation: "vertical"
spacing: dp(5)
FitImage:
source: "Help5/cc.png" #root.song_obj.album_cover
MDLabel:
text: root.song_obj[:40] #.title
adaptive_height: True
MDLabel:
text: " - jbsidis" #.artist
adaptive_height: True
Screen:
BoxLayout:
orientation: "vertical"
#do_scroll_x: False
RecycleSupportGridLayoutjbsidis:
id: song_grid
key_viewclass: 'viewclass'
key_size: 'height'
RecycleBoxLayout:
padding: dp(10)
default_size: None, dp(248)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
<RecycleSupportGridLayoutjbsidis#RecycleView>:
''')
def build(self):
return self.kv
def on_start(self):
#for _ in range(25):
for audio_file in os.listdir("/media/jbsidis/Android/Q/D/Anthony Robbins/"): #os.path.join(os.path.expanduser('~'), "Downloads")
#if audio_file.endswith(".mp3"):
#song_obj = Song(audio_file)
self.kv.ids.song_grid.data.append(
{
"viewclass": "SongCard",
"song_obj": audio_file
}
)
## {
## "viewclass": "CustomOneLineIconListItem",
## "icon": name_icon,
## "text": name_icon,
## "on_release": lambda:Clipboard.copy(name_icon),
## }
## self.kv.ids.song_grid.add_widget(
## SongCard(
## song_obj=song_obj
## )
## )
if __name__ == '__main__':
MainApp().run()
Pictures:
I am pretty new to the kivy language and faced this problem when try to run the following code. it just runs without any errors. But it just only generate a blank application. without any widgets.
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager
from kivy.uix.gridlayout import GridLayout
from kivymd.uix.boxlayout import MDBoxLayout
kv = """
<SCManager>:
Screen:
c_screen:
<c_screen>:
cols: 2
canvas.before:
Color:
rgba: 1,0,0,1
Rectangle:
pos: self.pos
size: self.size
MDBoxLayout:
md_bg_color: (1, 1, 1 ,1)
padding: 10
adaptive_height: True
MDLabel:
text: "helow 2"
color: (0.0003, 0.34, 0.60,1)
font_style: "H6"
halign: "left"
adaptive_height: True
<navigator>:
canvas.before:
Color:
rgba: 1,1,0,1
Rectangle:
pos: self.pos
size: self.size
orientation: "vertical"
MDLabel:
text: "hellow"
adaptive_height: True
MDList:
OneLineAvatarListItem:
text : "Dashboard"
IconLeftWidgetWithoutTouch:
icon: "view-dashboard"
OneLineAvatarListItem:
text : "Manage Users"
IconLeftWidgetWithoutTouch:
icon: "account"
MDRoundFlatIconButton:
text: "bye"
font_size: 11
"""
class SCManager(ScreenManager):
pass
class navigator(MDBoxLayout):
pass
class c_screen(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.n = navigator()
self.add_widget(self.n)
class test(MDApp):
def build(self):
Builder.load_string(kv)
return SCManager()
if __name__ == '__main__':
test().run()
But if i add another widget(a label) to Screen from kv string, it works fine or if i use add_widget(c_screen) in SCManager it works fine.But i need to add c_screen class from the kv string. Is there any way to add widgets without adding any widgets to Screen??
here is the working code
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager
from kivy.uix.gridlayout import GridLayout
from kivymd.uix.boxlayout import MDBoxLayout
kv = """
<SCManager>:
Screen:
MDLabel:
text: "helow 2"
adaptive_height: True
c_screen:
<c_screen>:
cols: 2
canvas.before:
Color:
rgba: 1,0,0,1
Rectangle:
pos: self.pos
size: self.size
MDBoxLayout:
md_bg_color: (1, 1, 1 ,1)
adaptive_height: True
MDLabel:
text: "helow 2"
adaptive_height: True
<navigator>:
canvas.before:
Color:
rgba: 1,1,0,1
Rectangle:
pos: self.pos
size: self.size
orientation: "vertical"
MDLabel:
text: "hellow"
adaptive_height: True
MDList:
OneLineAvatarListItem:
text : "Dashboard"
IconLeftWidgetWithoutTouch:
icon: "view-dashboard"
OneLineAvatarListItem:
text : "Manage Users"
IconLeftWidgetWithoutTouch:
icon: "account"
MDRoundFlatIconButton:
text: "bye"
font_size: 11
"""
class SCManager(ScreenManager):
pass
class navigator(MDBoxLayout):
pass
class c_screen(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.n = navigator()
self.add_widget(self.n)
class test(MDApp):
def build(self):
Builder.load_string(kv)
return SCManager()
if __name__ == '__main__':
test().run()
I have the following classes in my kivy app, and i would like to call the blink method in my mainApp class. The start pulsing and blink methods enable the MainWindow background to pulse. However it's not working inside the MainWindow class and i need to call it in my mainApp class. Commented out (build method in mainApp) is what i tried, which results to the error Exception: Invalid instance in App.root. My python file:
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.core.window import Window
from kivymd.app import MDApp
from kivy.uix.image import Image
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.properties import ColorProperty
from kivy.uix.popup import Popup
from kivy.uix.floatlayout import FloatLayout
from plyer import filechooser
data = ""
class MainWindow(Screen):
def analyze_data(self):
global data
data = self.ids.user_input.text
data = analyze(data)
animated_color = ColorProperty()
pulse_interval = 4
def blink(self):
x = Clock.schedule_once(self.start_pulsing, 5)
return x
def start_pulsing(self, *args):
d = self.pulse_interval / 2
anim = Animation(animated_color=(69/255, 114/255, 147/255, 1), duration=d) + Animation(animated_color=(1, 1, 1, 1), duration=d)
anim.repeat = True
anim.start(self)
class OutputScreen(Screen):
def on_enter(self, *args):
self.ids.output_label.text = data
class mainApp(MDApp):
def __init__(self):
super().__init__()
def choose_file(self):
try:
filechooser.open_file(on_selection = self.handle_selection)
except:
pass
def handle_selection(self,selection):
global path
selection_ls = selection[0]
path = selection_ls
#print(path)
def change_screen(self,screen):
screemanager = self.root.ids['screenmanager']
screemanager.current = screen
def change(self):
self.change_screen('output')
def back(self):
self.change_screen('main')
'''
def build(self):
x = MainWindow().blink()
return x'''
and my kv file:
#:import utils kivy.utils
GridLayout:
cols:1
ScreenManager:
id: screenmanager
MainWindow:
id: main
name: 'main'
OutputScreen:
id: output
name: 'output'
<MainWindow>:
BoxLayout:
orientation:'vertical'
MDBottomNavigation:
panel_color: utils.get_color_from_hex("#ffffff")
MDBottomNavigationItem:
name:'analytics'
text:'analytics'
icon:'account-circle'
FloatLayout:
size: root.width, root.height
canvas.before:
Color:
rgba: root.animated_color
Rectangle:
pos:self.pos
size:self.size
TextInput:
multiline:True
id: user_input1
pos_hint:{"x" : 0.05, "top" : 0.9}
size_hint: 0.9, 0.37
Label:
markup: True
id:input_label
pos_hint:{"x" : 0, "top":1}
size_hint: 1 ,0.08
font_size : 32
bold: True
canvas.before:
Color:
rgb: utils.get_color_from_hex("01121c")
Rectangle:
size: self.size
pos: self.pos
Button:
pos_hint:{"top" : 0.51, "x" : 0.05}
size_hint: (None,None)
width : 150
height : 40
font_size : 23
text:'Submit'
on_press: root.analyze_data()
on_release: app.change()
Button:
pos_hint:{"top":0.42, "x":0.05}
size_hint: (None,None)
width : 150
height : 40
font_size : 23
text:'Upload'
on_release:app.choose_file()
Button:
id:'info_button'
pos_hint:{"top":0.47, "x":0.8}
size_hint: (None,None)
width : 50
height : 22
font_size : 23
text:'i'
on_release:root.analytics_info()
<OutputScreen>:
ScrollView:
GridLayout:
cols:1
MDIconButton:
icon:'arrow-left'
pos_hint:{'top':1,'left':1}
size_hint: 0.1,0.1
user_font_size : '64sp'
on_release: app.back()
Label:
id: output_label
multiline:True
text_size: self.width, None
size_hint: 1, None
height: self.texture_size[1]
color: 0,0,0,1
padding_x: 15
Any help will be much appreciated.
The build() method of an App should return a Widget that will become the root of the App. But your build() method returns a ClockEvent (the return from Clock.schedule_once()). Try changing your build() method to:
def build(self):
x = MainWindow()
x.blink()
return x
Since you do not call Builder.load_file(), I assume that your kv file is named main.kv, and therefore will get loaded automatically. If that is true, then you do not need a build() method at all. Instead add an on_start() method to your mainApp class, like this:
def on_start(self):
self.root.ids.main.blink()
I'm trying to build a log viewer on kivy using recycleview since logs can be pretty large. I'm assigning one label widget per line so I can have more control over the text in the future. Some lines will have more text than others so adapted the Label widget to resize according, but when putting that inside recycleview can't seem to be able to control the height of the widget per line anymore, it stays at the same size. What I expect is the label to wrap on the text and adjust height since don't need the extra space between lines. If there's to little text a lot of free space is shown, if I put to much text in the label it floods and label doesn't grow.
One workaround that I tried with different code was to assign at least a 200 lines per label, that seems to work, but I do need more control over each line of text.
This is the example code:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
Builder.load_string('''
<Row#BoxLayout>:
canvas:
Color:
rgba: 1, 0.1, 0.1, 0.5 #Red Marker
Rectangle:
size: self.size
pos: self.pos
value: ''
orientation: 'vertical'
Label:
text: root.value
text_size: self.width, None
size_hint_y: None
height: self.texture_size[1]
font_size: 20
<LogDisplayWidget>:
rv: rv
orientation: 'vertical'
RecycleView:
id: rv
scroll_type: ['bars', 'content']
scroll_wheel_distance: dp(114)
bar_width: dp(10)
viewclass: 'Row'
RecycleBoxLayout:
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(2)
''')
class LogDisplayWidget(BoxLayout):
rv = ObjectProperty()
def __init__(self):
super(LogDisplayWidget, self).__init__()
self.load_text()
def load_text(self):
for i in range(10):
line = str(i) + 'This is a test of a bunch of text'
self.rv.data.append({'value': line})
class TestApp(App):
def build(self):
return LogDisplayWidget()
if __name__ == "__main__":
TestApp().run()
enter image description here
enter image description here
Did a code rewrite, the labels appear resized correctly in first page, but getting jerky unexpected results after scrolling, it shows correct label size sometimes then some are to big, and the scroll skips like trying to adjust itself and it fixes size again. Does anyone have a better way to implement this or I'm missing something? I'm suspecting it has something to do with the way the view refreshes
This is the new code:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
import random
Builder.load_string('''
<Row#Label>:
canvas.before:
Color:
rgba: 0.8, 0.1, 0.1, 0.5 #Red Marker
Rectangle:
size: self.size
pos: self.pos
text_size: self.width, None
size_hint_y: None
height: self.texture_size[1]
font_size: dp(20)
<RV>:
viewclass: 'Row'
RecycleBoxLayout:
default_size: None, dp(20)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(3)
''')
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
line = ''
for i in range(50):
n = random.randint(0, 1)
if n:
j = random.randint(5, 30)
line = 'Line: ' + str(i+1) + ' This is a test of a bunch of text' * j
else:
line = 'Line: ' + str(i+1) + ' This is a test of a bunch of text'
self.data.append({'text': line})
class TestApp(App):
def build(self):
return RV()
if __name__ == '__main__':
TestApp().run()