Kivy RecycleView SelectableButton Does not switch to a Dynamic Layout Page - python-3.x

Am working on a Project that has a RecycleView that contains details of patients.My aim is that when i click on a specific patient on the recycleView row, it should take me to specific dynamic page layout containing specific details of the selected patient in the RecycleView without using the Screen Manager, the switching of dynamic pages.how can i go about this?
i have created a method called change_dynamic_Layout() for switching dynamic pages and it works fine when using the normal button to call it. I have used a print statement in the method to show if the method is executed or not. But when i use the SelectableButton,the print statement is executed but the statement for changing page-layout is not. No errors is shown and the dynamic page does not change. Here is the code try to run it and see what am talking about?
Demo.py
from kivy.app import App
from kivy.properties import ObjectProperty, StringProperty, ListProperty,
NumericProperty
from kivy.uix.behaviors import ButtonBehavior
from kivy.graphics import Color, Rectangle
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.factory import Factory
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.properties import BooleanProperty
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.recyclegridlayout import RecycleGridLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
lost = Builder.load_file('Demo.kv')
class SelectableRecycleGridLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleGridLayout):
''' Adds selection and focus behaviour to the view. '''
class SelectableButton(RecycleDataViewBehavior, Button):
''' Add selection support to the Label '''
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
return super(SelectableButton, self).refresh_view_attrs(
rv, index, data)
def on_touch_down(self, touch):
''' Add selection on touch down '''
if super(SelectableButton, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos) and self.selectable:
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, rv, index, is_selected):
''' Respond to the selection of items in the view. '''
self.selected = is_selected
#selectablebutton to call the change_dynamic_Layout() method in patient class
def on_enter(self):
layout=Patient()
layout.change_dynamic_Layout()
class victor(BoxLayout):
pass
class Patient(Screen):
manage_prescription: ObjectProperty(None)
#Method to change the dynamic pagelayout
def change_dynamic_Layout(self):
layout = Factory.victor()
self.manage_prescription.clear_widgets()
self.manage_prescription.add_widget(layout)
print ('pressed')
class DemoApp(App):
title = 'Hospital Management System'
def build(self):
n= Patient()
return n
if __name__ == "__main__":
DemoApp().run()
demo.kv
<Patient>:
manage_prescription:manage_prescription
BoxLayout:
GridLayout :
cols:1
BoxLayout:
id:manage_prescription
orientation:'vertical'
BoxLayout:
size_hint_y:None
height:40
Button:
text:"NO."
font_size: 25
Button:
text:"Date"
font_size: 25
Button:
text:"Patient"
font_size: 25
Button:
text:"Doctor"
font_size: 25
on_press: root.change_dynamic_Layout()
BoxLayout:
RecycleView:
bar_width: 10
bar_color: 1, 0, 0, 1 # red
bar_inactive_color: 0, 0, 1, 1 # blue
#effect_cls: "ScrollEffect"
scroll_type: ['bars']
viewclass: 'SelectableButton'
data:[{'text': str(x)} for x in range(20)]
SelectableRecycleGridLayout:
cols:4
default_size: None, dp(56)
default_size_hint:1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: True
touch_multiselect: True
<victor>:
Label:
text:" Switched to specific page for patient's details"
<SelectableButton>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if self.selected else (1, 1, 1, 1)
Rectangle:
pos: self.pos
size: self.size
on_press: root.on_enter()
Any insight or help is much appreciated, thanks in advance.

The problem is that your on_enter method of the SelectableButton is creating a new Patient layout (Screen), and calling the change_dynamic_Layout method of that new Patient Screen. That newly created Patient Screen is not the one displayed in your app, so it has no effect on what you see. You actually want to call change_dynamic_Layout on the displayed Patient Screen. One way of doing this is to access it via App.get_running_app().root. So that your on_enter method can be changed to:
def on_enter(self):
#layout=Patient()
layout = App.get_running_app().root
layout.change_dynamic_Layout()
Here is the entire python file:
from kivy.app import App
from kivy.properties import ObjectProperty, StringProperty, ListProperty, NumericProperty
from kivy.uix.behaviors import ButtonBehavior
from kivy.graphics import Color, Rectangle
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.factory import Factory
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.properties import BooleanProperty
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.recyclegridlayout import RecycleGridLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
lost = Builder.load_file('Demo.kv')
class SelectableRecycleGridLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleGridLayout):
''' Adds selection and focus behaviour to the view. '''
class SelectableButton(RecycleDataViewBehavior, Button):
''' Add selection support to the Label '''
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
return super(SelectableButton, self).refresh_view_attrs(
rv, index, data)
def on_touch_down(self, touch):
''' Add selection on touch down '''
if super(SelectableButton, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos) and self.selectable:
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, rv, index, is_selected):
''' Respond to the selection of items in the view. '''
self.selected = is_selected
#selectablebutton to call the change_dynamic_Layout() method in patient class
def on_enter(self):
#layout=Patient()
layout = App.get_running_app().root
layout.change_dynamic_Layout()
class victor(BoxLayout):
pass
class Patient(Screen):
manage_prescription: ObjectProperty(None)
#Method to change the dynamic pagelayout
def change_dynamic_Layout(self):
layout = Factory.victor()
self.manage_prescription.clear_widgets()
self.manage_prescription.add_widget(layout)
print ('pressed')
class DemoApp(App):
title = 'Hospital Management System'
def build(self):
n= Patient()
return n
if __name__ == "__main__":
DemoApp().run()
And here is the Demo.kv:
<Patient>:
manage_prescription:manage_prescription
BoxLayout:
GridLayout :
cols:1
BoxLayout:
id:manage_prescription
orientation:'vertical'
BoxLayout:
size_hint_y:None
height:40
Button:
text:"NO."
font_size: 25
Button:
text:"Date"
font_size: 25
Button:
text:"Patient"
font_size: 25
Button:
text:"Doctor"
font_size: 25
on_press: root.change_dynamic_Layout()
BoxLayout:
RecycleView:
bar_width: 10
bar_color: 1, 0, 0, 1 # red
bar_inactive_color: 0, 0, 1, 1 # blue
#effect_cls: "ScrollEffect"
scroll_type: ['bars']
viewclass: 'SelectableButton'
data:[{'text': str(x)} for x in range(20)]
SelectableRecycleGridLayout:
cols:4
default_size: None, dp(56)
default_size_hint:1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: True
touch_multiselect: True
<victor>:
Label:
text:" Switched to specific page for patient's details"
<SelectableButton>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if self.selected else (1, 1, 1, 1)
Rectangle:
pos: self.pos
size: self.size
on_press: root.on_enter()

Related

add recycle view item to another screen from a recycle view- kivy

I have two screens. MainScreen and Playlist. MainScreen has a RecycleView method added that displays list of items. These items are created from folder names(directories). I want the files inside each folder(directories) to open on the Playlist Screen depending on which directory name is clicked.
I intend to clear the screen immediately after the user leaves the screen.
from kivymd.app import MDApp
from kivymd.uix.button import MDFlatButton, MDRectangleFlatButton
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.recycleview import RecycleView
from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.uix.screen import MDScreen
from pathlib import Path, PurePath
# Music Path
storageLocation = Path.cwd()
if Path('Books').is_dir():
storageLocation = Path.cwd() / 'Books'
class RecycleViewRow(BoxLayout):
text = StringProperty()
class MainScreen(MDScreen):
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
booksdir = [f for f in storageLocation.iterdir() if f.is_dir()]
self.children[0].data = [{'text': str(x), 'id': str(x)} for x in booksdir]
# print(self.children[0].data)
class Playlist(MDScreen):
def on_pre_enter(self, *args):
self.ids.Box.clear_widgets()
#self.ids.Box.add_widget(MDRectangleFlatButton(text="done", ))
print(self.manager.ids)
return
class Main(MDApp):
def build(self):
sm = ScreenManager()
sm.add_widget(MainScreen(name='MainScreen'))
sm.add_widget(Playlist(name='Playlist'))
sm.add_widget(Test(name="Test"))
return sm
Main().run()
.kv
<RecycleViewRow>:
orientation: 'vertical'
Button:
text: root.text
#on_press: app.root.message_box(root.text)
on_release:app.root.current = 'Playlist'
<MainScreen>:
RecycleView:
id: rv
viewclass: 'RecycleViewRow'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
<Playlist>
BoxLayout:
orientation: 'vertical'
id:Box
You can trigger a method from your RecycleViewRow, like this:
<RecycleViewRow>:
orientation: 'vertical'
Button:
text: root.text
on_release: app.fill_playlist(root.text)
Then, the the fill_playlist() method. you can fill the PlayList:
def fill_playlist(self, dir):
self.root.current = 'Playlist' # this also clears the play list
playlist = self.root.get_screen('Playlist')
for sub in os.listdir(dir):
playlist.ids.Box.add_widget(MDRectangleFlatButton(text=sub))

How to preselect an item in a Kivy RecycleView?

The code below displays a RecycleView filled with selectable lines. Initially, no item is selected. Pressing on one of the two buttons selects a line. My question is: how can we preselect an item so that one is selected at the dialog opening ?
A solution would be to be able to set a callback function whiqh is called after the RecycleView has been displayed, but how ?
# -*- coding: utf-8 -*-
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.properties import BooleanProperty, StringProperty, NumericProperty
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
kv = """
<SelectableLabel>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (0.4, 0.4, 0.4, 1) if self.selected else (0.5, 0.5, 0.5, 1)
Rectangle:
pos: self.pos
size: self.size
<KivyPlayer>:
canvas:
Color:
rgba: 0.3, 0.3, 0.3, 1
Rectangle:
size: self.size
pos: self.pos
orientation: 'vertical'
BoxLayout:
orientation: 'vertical'
BoxLayout:
Button:
id: next_track
text: "Next Track"
on_release: controller.select_next()
Button:
id: previous_track
text: "Previous Track"
on_release: controller.select_previous()
BoxLayout:
RecycleView:
id: media_list
viewclass: 'SelectableLabel'
scroll_type: ['bars', 'content']
scroll_wheel_distance: dp(114)
bar_width: dp(10)
SelectableRecycleBoxLayout:
id: controller
key_selection: 'selectable'
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
# multiselect: True
touch_multiselect: True
spacing: dp(2)
"""
Builder.load_string(kv)
class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleBoxLayout):
''' Adds selection and focus behaviour to the view. '''
# required to forbid unselecting a selected item. An item can be unselected
# only by selecting another item
touch_deselect_last = BooleanProperty(False)
def get_nodes(self):
nodes = self.get_selectable_nodes()
if self.nodes_order_reversed:
nodes = nodes[::-1]
if not nodes:
return None, None
selected = self.selected_nodes
if not selected: # nothing selected, select the first
self.select_node(nodes[0])
return None, None
if len(nodes) == 1: # the only selectable node is selected already
return None, None
last = nodes.index(selected[-1])
self.clear_selection()
return last, nodes
def select_next(self):
last, nodes = self.get_nodes()
if not nodes:
return
if last == len(nodes) - 1:
self.select_node(nodes[0])
else:
self.select_node(nodes[last + 1])
def select_previous(self):
last, nodes = self.get_nodes()
if not nodes:
return
if not last:
self.select_node(nodes[-1])
else:
self.select_node(nodes[last - 1])
class SelectableLabel(RecycleDataViewBehavior, Label):
''' Add selection support to the Label '''
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
return super(SelectableLabel, self).refresh_view_attrs(
rv, index, data)
def on_touch_down(self, touch):
''' Add selection on touch down '''
if super(SelectableLabel, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos) and self.selectable:
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, rv, index, is_selected):
''' Respond to the selection of items in the view. '''
self.selected = is_selected
if is_selected:
print("selection changed to {0}".format(rv.data[index]))
else:
print("selection removed for {0}".format(rv.data[index]))
class KivyPlayer(BoxLayout):
''' Main Kivy class for creating the initial BoxLayout '''
def __init__(self, **kwargs):
super(KivyPlayer, self).__init__(**kwargs)
# Set media_list data
self.ids.media_list.data = [{'text': str(x), 'selectable': True} for x in range(100)]
class KivyApp(App):
def build(self):
return KivyPlayer()
KivyApp().run()
In your KivyPlayer class, you can specify the selected node in the SelectableRecycleBoxLayout using the selected_nodes list:
class KivyPlayer(BoxLayout):
''' Main Kivy class for creating the initial BoxLayout '''
def __init__(self, **kwargs):
super(KivyPlayer, self).__init__(**kwargs)
# Set media_list data
self.ids.media_list.data = [{'text': str(x), 'selectable': True} for x in range(100)]
# specify pre-selected node by its index in the data
self.ids.controller.selected_nodes = [17]

Is ListView used the same way as RecycleView?

I'm trying to have self.search_results.data printed on my console, but this is not working. Am I using RecycleView the right way? The textbook I'm studying with is from 2014 and uses ListView. I found on the internet that ListView is deprecated. I can't seem to understand how the RecycleView actually works. I've read the documentation, but still can't see.
Kivy:
WeatherRoot:
<WeatherRoot>:
AddLocationForm
<AddLocationForm>:
orientation: "vertical"#
search_input: search_input
search_results: search_results_list
BoxLayout:
height: "40dp"
size_hint_y:None
TextInput:
id: search_input
size_hint_x: 50
focus: True
multiline: False
on_text_validate: root.search_location()
Button:
text: "Search"
size_hint_x: 25
on_press: root.search_location()
Button:
text: "Current Location"
size_hint_x: 25
RecycleView:
id: search_results_list
data: []
Python:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty, ListProperty
from kivy.network.urlrequest import UrlRequest
from kivy.factory import Factory
import json
class WeatherApp(App):
pass
class WeatherRoot(BoxLayout):
pass
class AddLocationForm(BoxLayout):
search_input = ObjectProperty()
search_results = ObjectProperty()
# do something
def search_location(self):
search_template = "http://api.openweathermap.org/data/2.5/find?q=
{}&type=like"
search_url = search_template.format(self.search_input.text)
request = UrlRequest(search_url, self.found_location)
def found_location(self, request, data):
data = json.loads(data.decode()) if not isinstance(data, dict)
else data
cities = ["{} ({})".format(d['name'], d['sys']['country'])
for d in data['list']]
self.search_results.data= cities
print(self.search_results.data)
if __name__ =='__main__':
WeatherApp().run()
The strings in the list should be printed on the console
Kivy ListView » Deprecated
ListView is no longer defined in the recently released stable Kivy version 1.11.0.
Kivy RecycleView » MVC (Model-View-Controller)
The view is generatad by processing the data, essentially a list
of dicts, and uses these dicts to generate instances of the
viewclass as required. Its design is based on the MVC
(Model-view-controller) pattern.
Model: The model is formed by data you pass in via a list of dicts.
View: The View is split across layout and views and implemented using adapters.
Controller: The controller determines the logical interaction and is implemented by RecycleViewBehavior.
Solution
To create a RecycleView of selectable item, one needs to implement the following classes as part of the viewclass. The item is usually a widget e.g. Label, Button, or a group/row of widgets in a layout (BoxLayout or GridLayout).
viewclass
Selectabel recycle layout class, e.g. SelectableRecycleBoxLayout(), or SelectableRecycleGridLayout()
Selectable widget class, e.g. SelectableLabel(), SelectableButton(), or SelectableRow()
data
Creates a list of dicts for data
Example
The following example illustrates the equivalence of a ListView by using RecycleView. The viewclass is a selectable RecycleBoxLayout of Label widget. The app is using OpenWeatherMap's API to retrieve a sample weather data of London, GB (Great Britain).
Note:
To make calls to OpenWeatherMap using the real API point, you need an API key (APPID).
main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.properties import BooleanProperty, ObjectProperty
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.network.urlrequest import UrlRequest
from kivy.lang import Builder
import json
class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleBoxLayout):
''' Adds selection and focus behaviour to the view. '''
class SelectableLabel(RecycleDataViewBehavior, Label):
''' Add selection support to the Label '''
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
return super(SelectableLabel, self).refresh_view_attrs(
rv, index, data)
def on_touch_down(self, touch):
''' Add selection on touch down '''
if super(SelectableLabel, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos) and self.selectable:
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, rv, index, is_selected):
''' Respond to the selection of items in the view. '''
self.selected = is_selected
class AddLocationForm(BoxLayout):
search_input = ObjectProperty()
search_results = ObjectProperty()
def search_location(self):
search_template = "https://samples.openweathermap.org/data/2.5/find?q={}&appid=b6907d289e10d714a6e88b30761fae22"
# search_template = "https://api.openweathermap.org/data/2.5/find?q={}&typle=like&appid=xyz"
search_url = search_template.format(self.search_input.text)
request = UrlRequest(search_url, self.found_location)
def found_location(self, request, data):
data = json.loads(data.decode()) if not isinstance(data, dict) else data
cities = ["{} ({})".format(d['name'], d['sys']['country']) for d in data['list']]
self.search_results.data = [{'text': str(x)} for x in cities]
print(f"self.search_results.data={self.search_results.data}")
class WeatherRoot(BoxLayout):
pass
class TestApp(App):
title = "Weather App"
def build(self):
return Builder.load_file("main.kv")
if __name__ == '__main__':
TestApp().run()
main.kv
WeatherRoot:
<WeatherRoot>:
AddLocationForm:
<SelectableLabel>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (1, 0, 0, 1) if self.selected else (.0, 0.9, .1, .3)
Rectangle:
pos: self.pos
size: self.size
Color:
rgba: (0, 0.9, .1, .3)
Rectangle:
pos: self.pos
size: self.size
<AddLocationForm>:
orientation: "vertical"
search_input: search_input
search_results: search_results_list
BoxLayout:
height: "40dp"
size_hint_y:None
TextInput:
id: search_input
size_hint_x: 50
focus: True
multiline: False
hint_text: 'Your city name'
on_text_validate: root.search_location()
Button:
text: "Search"
size_hint_x: 25
on_press: root.search_location()
Button:
text: "Current Location"
size_hint_x: 25
RecycleView:
id: search_results_list
viewclass: 'SelectableLabel'
SelectableRecycleBoxLayout:
default_size: None, dp(26)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: True
touch_multiselect: True
Output

Listview with popup on selection

I am trying to make a listview in which the selected buttons produce a popup with different information depending on which list button was selected. Any suggestions? Thank you
ListView has been deprecated since Kivy version 1.10.0. In the example below, we are using Recycleview.
Example
main.py
from kivy.app import App
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.button import Button
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.popup import Popup
from kivy.properties import ListProperty, StringProperty, ObjectProperty
class MessageBox(Popup):
def popup_dismiss(self):
self.dismiss()
class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior, RecycleBoxLayout):
""" Adds selection and focus behaviour to the view. """
selected_value = StringProperty('')
btn_info = ListProperty(['Button 0 Text', 'Button 1 Text', 'Button 2 Text'])
class SelectableButton(RecycleDataViewBehavior, Button):
""" Add selection support to the Label """
index = None
def refresh_view_attrs(self, rv, index, data):
""" Catch and handle the view changes """
self.index = index
return super(SelectableButton, self).refresh_view_attrs(rv, index, data)
def on_press(self):
self.parent.selected_value = 'Selected: {}'.format(self.parent.btn_info[int(self.id)])
def on_release(self):
MessageBox().open()
class RV(RecycleView):
rv_layout = ObjectProperty(None)
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'text': "Button " + str(x), 'id': str(x)} for x in range(3)]
class TestApp(App):
title = "RecycleView Button Popup Demo"
def build(self):
return RV()
if __name__ == "__main__":
TestApp().run()
test.kv
#:kivy 1.10.0
<MessageBox>:
title: 'Popup Message Box'
size_hint: None, None
size: 400, 400
BoxLayout:
orientation: 'vertical'
Label:
text: app.root.rv_layout.selected_value
Button:
size_hint: 1, 0.2
text: 'OK'
on_press:
root.dismiss()
<SelectableButton>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (0.0, 0.9, 0.1, 0.3)
Rectangle:
pos: self.pos
size: self.size
<RV>:
rv_layout: layout
viewclass: 'SelectableButton'
SelectableRecycleBoxLayout:
id: layout
default_size: None, dp(56)
default_size_hint: 0.1, None
size_hint_y: None
height: self.minimum_height
orientation: "vertical"
Output

How I can adjust variable height text property kivy?

I have a ver long text in kivy. I want adjustment dynamic height depend of qty of text.
My code is this.
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
class DynamicHeight(App):y
def build(self):
grid = gl = GridLayout(cols=1)
for i in range(3):
l = Label(text='Text a longer line line line line line line line line', halign='left',text_size=(300, None))
grid.add_widget(l)
return grid
DynamicHeight().run()
I want that height of label or height row of gridlayout adjustment according to the amount of text.
Although there are solutions proposed already, i feel they don't leverage the kivy way of doing things, and that this way is cleaner. What you need is to bind the text_size to the available width, and bind the height of the widget to the rendered texture size.
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
class MyApp(App):
def build(self):
root = FloatLayout()
b = GridLayout(
cols=1,
pos_hint={
'center_x': .5,
'center_y': .5},
size_hint=(None, None),
spacing=20,
width=200)
b.bind(minimum_height=b.setter('height'))
root.add_widget(b)
for text_length in range(0, 80, 20):
l = Label(
text='word ' * text_length,
size_hint_y=None)
l.bind(width=lambda s, w:
s.setter('text_size')(s, (w, None)))
l.bind(texture_size=l.setter('size'))
b.add_widget(l)
return root
if __name__ == '__main__':
MyApp().run()
I found the solution, with help of thopiekar.
For those needing this. So far I have not found kivy do without this method
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
class MultiLineLabel(Button):
def __init__(self, **kwargs):
super(MultiLineLabel, self).__init__( **kwargs)
self.text_size = self.size
self.bind(size= self.on_size)
self.bind(text= self.on_text_changed)
self.size_hint_y = None # Not needed here
def on_size(self, widget, size):
self.text_size = size[0], None
self.texture_update()
if self.size_hint_y == None and self.size_hint_x != None:
self.height = max(self.texture_size[1], self.line_height)
elif self.size_hint_x == None and self.size_hint_y != None:
self.width = self.texture_size[0]
def on_text_changed(self, widget, text):
self.on_size(self, self.size)
class DynamicHeight(App):
def build(self):
grid = GridLayout(cols=1,size_hint_x=None, width="300dp")
l=['This Text very long, should add multiple lines, automatically. This Text very long, should add multiple lines, automatically', 'One line']
for i in l:
l = MultiLineLabel(text=i)
grid.add_widget(l)
return grid
DynamicHeight().run()
And works perfectly!!!!!
text.size() method isn't changing height atribute of Label. If text is too long, it will overlap with content below:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
class DynamicHeight(App):
def build(self):
layout = GridLayout(cols=1, spacing=20)
l = Label(text='! '*100, text_size=(10, None), size_hint_y=None, height=10)
b = Button(text='...', size_hint_y=None)
layout.add_widget(l)
layout.add_widget(b)
return layout
DynamicHeight().run()
You need to calculate the heigh of the text and set it with height attribute manually. I don't know any nice and clean way to do this. Here is a dirty way:
before = label._label.render()
label.text_size=(300, None)
after = label._label.render()
label.height = (after[1]/before[1])*before[1] # ammount of rows * single row height
Example:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
class DynamicHeight(App):
def build(self):
layout = GridLayout(cols=1, size_hint_y=None, spacing=20)
layout.bind(minimum_height=layout.setter('height'))
for i in xrange(1, 20):
l = Label(text='Text ' * (i*10), text_size=(300, None), size_hint_y=None)
# calculating height here
before = l._label.render()
l.text_size=(300, None)
after = l._label.render()
l.height = (after[1]/before[1])*before[1] # ammount of rows * single row height
# end
layout.add_widget(l)
root = ScrollView()
root.add_widget(layout)
return root
DynamicHeight().run()
Following up on tshirtman's answer, I created code for doing the same thing in kv-lang. It might be a bit clearer what's going on, since you don't need to analyse the callback functions.
What's happening is that the label's width gets set in accordance with the layout, while the height gets set to the texture size of the text. So as the text string gets longer, the texture can only grow in height, which we can apply above, by setting the height of the boxlayout to my_label.height.
# -*- coding:utf8 -*-
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
from kivy.core.window import Window
Window.size = (400, 700)
PLACEHOLDER_TEXT = u'''The bindings illustrated in tshirtman's example are created automatically when using kv-lang.
Notice how the "id: my_label" allows us to access the Label's attributes in the BoxLayout's height declaration.'''
kv_string = """
<Example>:
orientation: 'vertical'
BoxLayout:
# colored background for affected area:
canvas.before:
Color:
rgba: 0.3, .4, .4, .6
Rectangle:
pos: self.pos
size: self.size
size_hint_y: None
height: my_label.height
Label:
id: my_label
text: root.text
font_size: '14dp'
text_size: (self.width, None)
size: self.texture_size
valign: 'top'
halign: 'left'
BoxLayout:
orientation: 'vertical'
size_hint_y: 1
canvas.before:
Color:
rgba: 0.9, .0, .5, .6
Rectangle:
pos: self.pos
size: self.size
TextInput:
text: root.PLACEHOLDER_TEXT
on_text: root.text = self.text
text_size: self.size
auto_indent: True
Label:
size_hint_y: None
height: '50dp'
text: 'String length: ' + str(len(root.text))
"""
Builder.load_string( kv_string )
class Example (BoxLayout ):
PLACEHOLDER_TEXT = PLACEHOLDER_TEXT
text = StringProperty( )
def __init__(self, **kwargs):
super( Example, self).__init__(**kwargs)
class MyApp(App):
def build(self):
root = Example()
return root
MyApp().run()

Resources