Kivy, Recycleview with custom widget - python

I made a recycleview list of custom cards. For each card i want to pass a custom object called "show", its type is "Content".
The class of custom card is "StreamingShowCard". I can't create the card by KVlang because its class contains some pytho methods.
This is the code:
from kivymd.app import MDApp
from kivy.lang import Builder
from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
from kivymd.uix.card import MDCard
from kivy.properties import StringProperty
from kivy.uix.behaviors import ButtonBehavior
from kivymd.uix.list import OneLineIconListItem
from kivymd.uix.tab import MDTabsBase
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.label import MDLabel
from kivymd.uix.gridlayout import MDGridLayout
from kivymd.uix.fitimage import FitImage
from kivy.uix.image import AsyncImage
from kivymd.uix.button import MDFlatButton
#import datetime
from datetime import datetime
from kivy.uix.image import Image
from kivymd.uix.dialog import MDDialog
from kivymd.uix.screen import MDScreen
from kivymd.uix.responsivelayout import MDResponsiveLayout
from kivymd.uix.progressbar import MDProgressBar
from kivy.clock import Clock
from kivymd.uix.button import MDIconButton
from kivymd.uix.list import IconLeftWidget
from kivymd.uix.recycleview import MDRecycleView
from kivy import properties
KV = '''
<Cover>:
<StreamingShowCard>:
show: root.show
MDScreen:
MDRecycleView:
size_hint_y: 1
size_hint_x: 1
viewclass: 'StreamingShowCard'
id: rv
MDRecycleGridLayout:
cols: 2
height: self.minimum_height
size_hint_y: None
row_default_height: '250dp'
row_force_default: True
padding: dp(10)
spacing: dp(10)
MDFloatingActionButton:
id: fab
icon: "plus"
pos_hint: {"center_x": .5, "center_y": .1}
on_release: app.add_item()
'''
class MD3Card(MDCard, RoundedRectangularElevationBehavior):
'''Implements a material design v3 card.'''
text = StringProperty()
class Content: #eg: film found
name = ""
url = ""
platform = ""
imageUrl = ""
Image = False
#free = False
def __init__(self, name, url, platform, imageUrl):
self.name = name
self.url = url
self.platform = platform
self.imageUrl = imageUrl
class StreamingShowCard(MD3Card):
#show = None #the object of Content class attached to the
show = properties.ObjectProperty()
def __init__(self, **kwargs):
super(StreamingShowCard, self).__init__(**kwargs)
self.__set_cardGraphic()
self.image = Cover(size_hint_min_y=0.7, source="https://kivy.org/doc/stable/_static/logo-kivy.png", opacity= 100 if True else 0, radius= ["10dp", "10dp", "0dp", "0dp"])
grid1 = MDGridLayout(cols=1, size_hint_x=1, size_hint_y=1, rows=3)
grid1.add_widget(self.image)
grid1.add_widget(MDLabel(text=self.show, size_hint_y = 0.3, font_style='Subtitle2', halign='left', valign='middle'))
box1 = MDBoxLayout(orientation = "horizontal", size_hint_x=1, size_hint_y=0.2)
box1.add_widget(MDLabel(text="self.show.platform", size_hint_y = 1, size_hint_x=0.8, theme_text_color="Secondary", font_style='Caption', halign='left', valign='middle'))
grid1.add_widget(box1)
self.add_widget(grid1)
def __set_cardGraphic(self):
self.md_bg_color = "#18222c"
#self.size_hint_y = None
self.elevation = 10
self.padding = "1dp"
self.size_hint_x = 1
self.radius = "10dp"
self.ripple_behavior = True
self.on_press = self.cardPress
def cardPress(self, *args):
dialog = MDDialog(
title = "Aprire la pagina in un browser?",
text = "Text",#self.show.name,
size_hint = (0.8, 0.8),
auto_dismiss = False,
buttons=[
MDFlatButton(text="No", text_color=self.theme_cls.primary_color, on_release=lambda x: dialog.dismiss()),
MDFlatButton(text="Si", text_color=self.theme_cls.primary_color, on_release=lambda x: yesClick())
]
)
dialog.open()
def yesClick():
#openUrl(self.show.url)
dialog.dismiss(force=True)
def loadImage(self, *args):
if not self.show.Image:
self.show.loadImage(True)
self.image.source = self.show.imageUrl
self.image.opacity = 100
self.image.reload()
class Cover(AsyncImage, FitImage):
pass
class MainApp(MDApp):
def build(self):
return Builder.load_string(KV)
def add_item(self):
for i in range(100):
self.root.ids.rv.data.append({
"show": Content("name", "www.google.com", "platform", "https://kivy.org/doc/stable/_static/logo-kivy.png")
})
MainApp().run()
If the recycleview's viewclass is a Label i can pass the text directly from main by appending dict to RV data, but whit this custom class of recycle view i cannot pass Content to show property.
Is it possible to populate the RecycleView by the main?

Couldn't get your code to run, but if you want to use an ObjectProperty in the data of a RecycleView, you need to handle it a bit differently. Here is an example of using an ObjectProperty in the data:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ObjectProperty, StringProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy.uix.recycleview import RecycleView
Builder.load_string('''
<MyObject>:
size_hint_y: None
height: 100
Label:
id: label
text: root.text # uses the text StringProperty
size_hint: None, None
size: 200, 100
<RV>:
viewclass: 'MyObject'
RecycleBoxLayout:
default_size: None, 100
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
''')
class MyObject(BoxLayout):
show = ObjectProperty(None)
text = StringProperty('Abba')
def on_show(self, instance, new_obj):
# handle the ObjectProperty named show
if new_obj.parent:
# remove this obj from any other MyObject instance
new_obj.parent.remove_widget(new_obj)
for ch in self.children:
if isinstance(ch, Image):
# remove any previous obj instances
self.remove_widget(ch)
break
# add the new obj to this MyObject instance
self.add_widget(new_obj)
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'text': str(x),
'show': Image(source='tester.png', size_hint=(None, None), size=(100, 100),
allow_stretch=True, keep_ratio=True)}
for x in range(100)]
class TestApp(App):
def build(self):
return RV()
if __name__ == '__main__':
TestApp().run()

Related

MDExpansionPanel doesn't behave very well in a RecycleView Python/Kivy

For some reason MDExpansionPanel doesn't behave as it should in a RecycleView, each time you expand the list, the more it deviates from the pattern. I don't know if there is something wrong with my code or the MDExpansionPanel was not made for a RecycleView
main.py
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.expansionpanel import (MDExpansionPanel,
MDExpansionPanelOneLine)
Builder.load_string("""
<MyRecycleView>:
viewclass: "MyExpansionPanel"
effect_cls: "ScrollEffect"
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(10)
<Content>:
adaptive_height: True
IconLeftWidget:
icon: "account"
""")
class MyRecycleView(RecycleView):
pass
class Content(MDBoxLayout):
pass
class MyExpansionPanel(MDExpansionPanel, RecycleDataViewBehavior):
text = StringProperty()
def __init__(self, **kwargs):
self.panel_cls = MDExpansionPanelOneLine()
super().__init__(**kwargs)
def refresh_view_attrs(self, rv, index, data):
self.panel_cls.text = data["text"]
return super().refresh_view_attrs(rv, index, data)
class MyApp(MDApp):
def build(self):
recycle = MyRecycleView()
recycle.data = [
{
"icon":"arrow-right",
"text":"Test",
"content":Content()
}for _ in range(5)
]
return recycle
MyApp().run()
Result:
Any idea how to resolve this?

How to change screens in Kivy while on FloatLayout()?

This app shows a screen with a video, logo, user input field, and submit button. It uses FloatLayout and GridLayout. How can I use this to switch screens when a valid input is given to the user input? I want to import ScreenManager but it looks like that FloatLayout and ScreenManager are not very compatible.
'''
from kivy.app import App
from kivy.uix.video import Video
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.widget import Widget
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
class iaScreen(Screen):
pass
class rtScreen(Screen):
pass
class MZ_Invest(App):
def build(self):
self.root_layout = FloatLayout()
self.window = GridLayout()
self.window.cols = 1
self.window.size_hint = (0.6,0.7)
self.window.pos_hint = {"center_x":0.5, "center_y":0.5}
#add widgets
#Video
video = Video(source='birds.mp4', state='play', volume = 0)
video.allow_stretch = False
video.options = {'eos': 'loop'}
video.opacity = 0.5
#Image
self.window.add_widget(Image(
source="mzi.png",
size_hint = (1.5,1.5)
))
#Label
self.greeting = Label(
text="How much would you like to invest?",
font_size = 18,
color='90EE90'
)
self.window.add_widget(self.greeting)
#User Input
self.user = TextInput(
multiline=False,
padding_y= (20,20),
size_hint = (1, 0.5)
)
self.window.add_widget(self.user)
#Button
self.button = Button(
text="Submit",
size_hint = (1,0.5),
bold = True,
background_color = '90EE90',
)
self.button.bind(on_press=self.callback)
self.window.add_widget(self.button)
self.root_layout.add_widget(video)
self.root_layout.add_widget(self.window)
return self.root_layout
def callback(self, instance):
if self.user.text.isnumeric() and int(self.user.text) >= 10000:
self.greeting.text = "Calculating: $" + self.user.text
self.greeting.color = '90EE90'
else:
self.greeting.text = "Invalid"
self.greeting.color = "#FF0000"
if __name__ == "__main__":
MZ_Invest().run()
'''
mz_invest.py:
from kivy.app import App
from kivy.uix.video import Video
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.widget import Widget
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
class SM(ScreenManager):
def __init__(self, **kwargs):
super(SM, self).__init__(**kwargs)
class Screen1(Screen):
def __init__(self, **kwargs):
super(Screen1, self).__init__(**kwargs)
def callback(self, instance):
entry = self.manager.ids['screen1'].ids.my_input.text
greeting = self.manager.ids['screen1'].ids.my_label
if entry.isnumeric() and int(entry) >= 10000:
greeting.text = "Calculating: $" + entry
greeting.color = '90EE90'
self.manager.current = 'screen2'
else:
greeting.text = "Invalid"
greeting.color = "#FF0000"
class Screen2(Screen):
def __init__(self, **kwargs):
super(Screen2, self).__init__(**kwargs)
class MZ_Invest(App):
def build(self):
sm = SM()
return sm
if __name__ == "__main__":
MZ_Invest().run()
mz_invest.kv:
<SM>:
Screen1:
id: screen1
name: 'screen1'
Screen2
id: screen2
name: 'screen2'
<Screen1>:
FloatLayout:
Video:
source: 'birds.mp4'
state: 'play'
volume: 0
allow_stretch: False
options: {'eos': 'loop'}
opacity: 0.5
GridLayout:
cols: 1
size_hint: (0.6,0.7)
pos_hint: {"center_x":0.5, "center_y":0.5}
Image:
size_hint: (1.5,1.5)
Label:
id: my_label
name: 'my_label'
text: "How much would you like to invest?"
font_size: 18
color: '90EE90'
TextInput:
id: my_input
name: 'my_input'
multiline: False
padding_y: (20,20)
size_hint: (1, 0.5)
Button:
text:"Submit"
size_hint: (1,0.5)
bold: True
background_color: '90EE90'
on_press: root.parent.ids['screen1'].callback(self)
<Screen2>:
Label:
text:"Welcome to Screen2"

kivymd Card scroll list items with the help of loop?

I am working on a mobile app and I want to create multiple of scoll items by using for loop but whenever I use for loop, gives me some error It has already parent widget. how can I make list of MDCard?
My App:
Click here
My Code:
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.screenmanager import Screen, ScreenManager
from kivymd.uix.boxlayout import BoxLayout
from kivy.uix.image import AsyncImage
from kivymd.uix.card import MDCard
from kivymd.uix.label import MDLabel
Window.size = (450, 740)
kv = '''
ScreenManager:
Main:
<main>:
name: 'main'
video_list: video_list
BoxLayout:
orientation: 'vertical'
MDToolbar:
title: 'Video downloader'
ScrollView:
Screen:
id: video_list
'''
class Main(Screen):
pass
sm = ScreenManager()
sm.add_widget(Main(name='main'))
class Ytube(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.theme_cls.colors = 'Red'
self.theme_cls.primary_palette = "Red"
self.root = Builder.load_string(kv)
image = AsyncImage(
source='https://static.hub.91mobiles.com/wp-content/uploads/2020/05/How-to-download-youtube-videos.jpg', size_hint=(1, .7), )
screen_id = self.root.get_screen('main').ids.video_list
for i in range(1):
card = MDCard(orientation='vertical', pos_hint={
'center_x': .5, 'center_y': .7}, size_hint=(.9, .4))
card.add_widget(image)
card.add_widget(MDLabel(
text='Phishing attacks are SCARY easy to do!! (let me show you!)', size_hint=(.6, .2), ))
screen_id.add_widget(card)
def build(self):
return self.root
if __name__ == "__main__":
Ytube().run()
Is there any way to make it
look like this.
The problem is that you are trying to use the same image widget for every MDCard. Any widget can only have one parent. You can only use that image widget once. You can fix that by moving the creation of the image widget inside the loop.
Also, a BoxLayout is a better choice for the child of a ScrollView, since it has a minimum_height property that you can use. Here is a modified version of your code that applies both those suggestions:
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.image import AsyncImage
from kivymd.uix.card import MDCard
from kivymd.uix.label import MDLabel
Window.size = (450, 740)
kv = '''
ScreenManager:
Main:
<main>:
name: 'main'
video_list: video_list
BoxLayout:
orientation: 'vertical'
MDToolbar:
title: 'Video downloader'
ScrollView:
do_scroll_x: False
BoxLayout:
id: video_list
orientation: 'vertical'
size_hint_y: None
height: self.minimum_height
'''
class Main(Screen):
pass
sm = ScreenManager()
sm.add_widget(Main(name='main'))
class Ytube(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.theme_cls.colors = 'Red'
self.theme_cls.primary_palette = "Red"
self.root = Builder.load_string(kv)
screen_id = self.root.get_screen('main').ids.video_list
for i in range(5):
# Make a new image widget for each MDCard
image = AsyncImage(
source='https://static.hub.91mobiles.com/wp-content/uploads/2020/05/How-to-download-youtube-videos.jpg', size_hint=(1, .7), )
card = MDCard(orientation='vertical', pos_hint={
'center_x': .5, 'center_y': .7}, size_hint=(.9, None), height=200)
card.add_widget(image)
card.add_widget(MDLabel(
text='Phishing attacks are SCARY easy to do!! (let me show you!)', size_hint=(.6, .2), ))
screen_id.add_widget(card)
def build(self):
return self.root
if __name__ == "__main__":
Ytube().run()

kivy python buttonbehavior on_press disable

I'm trying to build an app using kivy. I have added close button and then I added on_release. However, pressing the button does not work.
python code:
import kivy
kivy.require('1.11.0')
from kivy.lang import Builder
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.behaviors import ButtonBehavior
from kivy.core.window import Window
Window.size = (350 * 1.5 , 600 * 1.5)
with open("./template.kv", encoding='utf8') as f:
Builder.load_string(f.read())
class CloseButton(ButtonBehavior, Image):
def __init__(self, **kwargs):
super(CloseButton, self).__init__(**kwargs)
self.source = './close_btn#2x.png'
always_release = True
def on_press(self):
App.get_running_app().stop()
class Background(Screen):
def __init__(self, **kwargs):
super(Background, self).__init__(**kwargs)
class TemplateApp(App):
def build(self):
# title bar remove
# Window.borderless = True
sm = ScreenManager()
sm.add_widget(Background(name='back'))
return sm
if __name__ == '__main__':
TemplateApp().run()
kivy code:
<Background>:
canvas:
Rectangle:
pos: self.pos
size: self.size
source: "background.png"
Label:
font_size: 12 * 1.5
text: 'Template'
font_name: './NotoSans-hinted/NotoSans-Regular.ttf'
size_hint: (1.0, 1.0)
halign: "left"
valign: "top"
color: 0.43568, 0.43568, 0.43568, 1
text_size: root.width - (40 * 1.5), 583 * 1.5
BoxLayout:
size_hint: 1.9, 1.938
CloseButton:
id: close_btn
Instead of
def on_press(self):
App.get_running_app().stop()
try this
self.on_press = App.get_running_app().stop()
Ok, let's try this:
from kivy.clock import Clock
...
class CloseButton(ButtonBehavior, Image):
def __init__(self, **kwargs):
super(CloseButton, self).__init__(**kwargs)
self.source = './close_btn#2x.png'
# no parentheses after method's name!
self.on_press = self.closeapp
def closeapp(self):
Clock.schedule_once(App.get_running_app().stop())

Python/kivy : AttributeError: 'int' object has no attribute 'replace' in python

I have 2 file test.py and test.kv. When I run test.py and pass numeric value in self.abc.text=10 then it gives error
File "/usr/lib/python2.7/dist-packages/kivy/uix/textinput.py", line 2930, in _set_text
text = text.replace(u'\r\n', u'\n')
AttributeError: 'int' object has no attribute 'replace'
If I pass string value then it's working. I think text for string value but I don't what is for numeric value?
test.py
import kivy
kivy.require('1.9.0') # replace with your current kivy version !
import sqlite3 as lite
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import BooleanProperty, ListProperty, StringProperty, ObjectProperty, NumericProperty
from kivy.lang import Builder
from kivy.uix.popup import Popup
from kivy.core.window import Window
from kivy.uix.label import Label
#Window.maximize()
from kivy.clock import Clock
from kivy.uix.treeview import TreeView, TreeViewLabel, TreeViewNode
Window.size = (500, 530)
class GroupScreen(Screen):
groupName = ObjectProperty(None)
popup = ObjectProperty(None)
abc = ObjectProperty(None)
def display_groups(self, instance):
self.abc.text=10
class Group(App):
def build(self):
self.root = Builder.load_file('test.kv')
return self.root
if __name__ == '__main__':
Group().run()
test.kv
#:kivy 1.10.0
<CustomLabel#Label>:
text_size: self.size
valign: "middle"
padding_x: 5
<SingleLineTextInput#TextInput>:
multiline: False
<GreenButton#Button>:
background_color: 1, 1, 1, 1
size_hint_y: None
height: self.parent.height * 0.150
GroupScreen:
groupName: groupName
abc:abc
GridLayout:
cols: 2
padding : 30,30
spacing: 10, 10
row_default_height: '40dp'
CustomLabel:
text: 'Number'
SingleLineTextInput:
id: abc
CustomLabel:
text: 'Test'
SingleLineTextInput:
id: groupName
on_text: root.display_groups(self)
GreenButton:
text: 'Ok'
GreenButton:
text: 'Cancel'
Use NumericProperty and then str(root.abc) in kv.
Try this example:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty
class MyBoxLayout(BoxLayout):
abc = NumericProperty(0)
def set_text(self):
self.abc = 42
KV = """
MyBoxLayout:
Button:
text: str(root.abc)
on_release:
root.set_text()
"""
class Testapp(App):
def build(self):
root = Builder.load_string(KV)
return root
Testapp().run()
You need to type self.abc.text = str(rows[1]) in order for it to be passed as the correct type.
Hope this helps!

Categories