Kivy: making a table widget - python-3.x

I am working with kivy in a table widget using GridLayout and ScrollView. This is what i have:
https://github.com/Skucul/listwidget
there is a better way to implement it? What about RecicleView?

I got very nice results with RecycleView and KivyMD, which has list elements with Icons and images, etc.
I saw your other repositories too, eg Datepicker and Timepicker, there are similar, there are very similar Widgets in KivyMD, maybe you would consider to use them or even better, enhance them.

from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivymd.label import MDLabel
from kivy.uix.button import Button
from kivy.properties import NumericProperty, ListProperty,StringProperty
from kivy.graphics import Color
from kivy.metrics import dp
Builder.load_string('''
#:import MDLabel kivymd.label.MDLabel
<Table>
orientation:'vertical'
size_hint_y:0.9
GridLayout:
id:header
spacing:2
padding:[10,10,10,10]
size_hint_y:None
height:dp(48)
ScrollView:
size_hint_y:1
GridLayout:
id:body
spacing:2
padding:[10,10,10,10]
size_hint_y:None
#spacing:dp(2)
height:self.minimum_height
# GridLayout:
# id:footer
# height:dp(48)
# pos_hint:{'center_y':0.1}
<Header>
padding:[10,10,10,10]
canvas.before:
Color:
rgba: app.theme_cls.accent_dark
Rectangle:
pos: self.pos
size: self.size
size_hint_y:None
size_hint_x:header.size_hint_x
height:dp(48)
MDLabel:
id:header
text:root.text
<Cell>
padding:[10,10,10,10]
canvas.before:
Color:
rgba: app.theme_cls.accent_color
Rectangle:
pos: self.pos
size: self.size
size_hint_y:None
size_hint_x:cell.size_hint_x
height:dp(48)
MDLabel:
id:cell
text:root.text
''')
class Header(BoxLayout):
text = StringProperty()
class Cell(BoxLayout):
text = StringProperty()
class Table(BoxLayout):
cols = NumericProperty(1)
table_content = ListProperty([{"col 1":"row 11","col 2":"row 21"},{"col 1":"row 12","col 2":"row 22"}],allownone=True)
thead = ListProperty()
tbody = ListProperty()
color = [128, 0, 2, 0.8]
def __init__(self, **kwargs):
super(Table, self).__init__(**kwargs)
for i in self.table_content:
self.thead =[]
for j in i.keys():
self.thead.append(j)
self.ids['header'].cols = len(self.thead)
self.ids['body'].cols = len(self.thead)
for i in self.thead:
head = Header(text=i.upper())
self.ids['header'].add_widget(head)
for i in self.table_content:
for j in i.keys():
body = Cell(text=i[j])
self.ids['body'].add_widget(body)
#create MDTable.py file and save above code
#then import class where to add table widget
#simply use below function pass id where to add table and list of data
def add_table(self,id,list):
from table import Table
id.add_widget(Table(table_content=list))
# list example:[{'head1':'content1','head2':'content2'},###{'head1':'content1','head2':'content2'}]

Related

How do I connect the Spinner ids from the kivy file with the data that I provide from my products database?

How do I connect my ids to the python code considering I want to load the string from self.listcode variable into the values of the spinner, I already use self. ids. and I don't get anything from the KV file. Thanks in advance for ur help. One of the errors popping out is the one below:
AttributeError: 'super' object has no attribute '__getattr__
I think if I'm not wrong that it has to do with that Screen doesn't having the possibility to access ids
KIVY FILE###
<WindowManager>:
FirstWindow:
SecondWindow:
<FirstWindow>:
name: "menu"
canvas.before:
Color:
rgba: (242/255, 202/255, 5/255, 1)
RoundedRectangle:
size: self.size
pos: self.pos
radius: [55]
Image:
source: 'Logoapp.png'
BoxLayout:
orientation: "horizontal"
size: 50, 50
Button:
font_size: 20
size_hint: 0.3, 0.2
pos_hint: {"x":.5, "top":.2}
pos:100, 100
background_color: 0,0,0,0
text: "Cotizar"
color: 0,0,0,1
canvas.before:
Color:
rgba: (23/255,152/255,216/255,0.71)
RoundedRectangle:
size: self.size
pos: self.pos
radius:[55]
on_release:
app.root.current = "cotiza"
root.manager.transition.direction = "left"
<SecondWindow>:
name: "cotiza"
FloatLayout:
CodigoSpin:
id: "Cod_prod"
pos: 20, 100
Label:
text: "Descripcion"
pos: 20, 35
CodigoSpin:
id: "Cod_prod2"
pos: 20, 200
Label:
text: "Descripcion"
pos: 20, 130
CodigoSpin:
id: "Cod_prod3"
pos: 20, 300
Label:
text: "Descripcion"
pos: 20, 235
<CodigoSpin#Spinner>:
size_hint: None, None
size: 100, 44
text: "CodigoSpin"
Main Program
from types import new_class
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.config import Config
from kivy.core.window import Window
from kivy.properties import ListProperty
from kivy.uix.spinner import Spinner, SpinnerOption
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.graphics import Color, Rectangle, RoundedRectangle
import sqlite3
Window.size = (250, 500 ) #Changed the size of the screen to fit my phone
from kivy.uix.textinput import TextInput
from kivy.uix.screenmanager import ScreenManager, Screen
class FirstWindow(Screen, FloatLayout):
pass
class SecondWindow(Screen, Widget):
def __init__(self, **kwargs):
super(SecondWindow, self).__init__(**kwargs)
#Create de Database connection
conn = sqlite3.connect("inventingt.db")
cursor = conn.cursor()
cursor.execute("""SELECT Codigo FROM masterinv """)
rows = cursor.fetchall()
#Create the variable listcode of objects from the database
self.listcode = [str(t[0]) for t in rows]
#This method will initialize the values on the Spinner
self.inicializarvalores()
def inicializarvalores(self):
self.ids.Cod_prod.values = self.listcode
class WindowManager(ScreenManager, Widget):
pass
class Buendes(App):
def build(self):
#Inicializamos el screen manager para inicializar las paginas
self.window = WindowManager()
#add widgets to window
#Agregamos el boton de inicializar la aplicacion
#Conectamos una base de datos
return self.window
if __name__ == "__main__":
Buendes().run()
Other way to connect your widgets which are created in .kv file to .py file is:
Give id to your widget. ( ✓ You did it as Cod_prod )
in .kv file, top of your class (under <SecondWindow>:), type this: Cod_prod:Cod_prod ( ☓ )
Import this : from kivy.properties import ObjectProperty ( ☓ )
Now under class SecondWindow(in .py file), identify your object: Cod_prod = ObjectProperty()
Now you can access your widget which is created in .kv file with:
def inicializarvalores(self):
self.Cod_prod.values = self.listcode
I couldn't understand the situation on Spinner and Screen connection. I had same problem. Also tried both solutions but couldn't get the solution.
I couldn't assign values to Spinner on root class inherited Screen.
What I tried:
On py file: self.ids.spinnerid.values = list -> not working. But py connected the spinner.
On kv file: Spinner: values: root.list -> not working.
Try to ListProperty, ObjectProperty... -> not working.
I turned around the problem using values: app.list on kv file and define the list on App class.

How to position Async Images inside KivyMD's MDCard in .py File

I would like to position an Async Image inside the kivyMD's MDCard but for some reason the card gets positioned correctly using pos_hint in the x direction but doesn't work the same in the y direction
Code
from kivymd.app import MDApp
from kivymd.uix.screen import Screen
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.label import MDLabel
from kivymd.uix.button import MDRectangleFlatButton
from kivymd.uix.card import MDCard, MDSeparator
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.uix.image import AsyncImage
Builder.load_string("""
<AppScreen>:
MDBoxLayout:
canvas.before:
Color:
rgba:1,0,0,1 #Red
Rectangle:
size:self.size
pos:self.pos
orientation:'vertical'
pos_hint: {'center_x':.5, 'center_y':.4}
size_hint: (.75, .6)
ScrollView:
canvas.before:
Color:
rgba: 0,1,0,.5 #Green
Rectangle:
size:self.size
pos:self.pos
do_scroll_x:False
do_scroll_y:True
pos_hint:{'center_x':0.5, 'center_y':0.5}
size_hint:(0.5,1)
MDBoxLayout:
canvas.before:
Color:
rgba: 0,0,1,.5
Rectangle:
size:self.size
pos:self.pos
orientation:'vertical'
id:container
size_hint_y:None #This makes the stuff scroll O.o
height:self.minimum_height
spacing:10
MDRectangleFlatButton:
text:"Press Me"
pos_hint:{'center_x':0.5, 'center_y':.8}
on_release:root.printing()
""")
class AppScreen(Screen):
def __init__(self, **kwargs):
super(AppScreen, self).__init__(**kwargs)
self.scroll_id = self.ids.container
def printing(self):
for x in range(5):
img=AsyncImage(
source="https://i.redd.it/x1js663rq7s41.jpg",
pos_hint={'center_x':0.9, 'center_y':0.5},
size_hint_y=None,
size=(dp(20), dp(30))
)
card = MDCard(
orientation='vertical',
pos_hint={'center_x':0.5},
size_hint=(1, None),
#size=(dp(100), dp(100)) #Size is required to view stuff in Cards
)
card.add_widget(img)
self.scroll_id.add_widget(card)
class DemoApp(MDApp):
def build(self):
self.theme_cls.theme_style="Dark"
return AppScreen()
DemoApp().run()
I used a another Screen inside the MDCard, and added the Async Image as a child to the new Screen created as layout
Note:
You may want to use GridLayout or BoxLayout if you have multiple widgets inside a MDCard. (GridLayout ignores the pos and size hint of it's children though)
Solution :
def __init__(self, **kwargs):
super(AppScreen, self).__init__(**kwargs)
self.scroll_id = self.ids.container
def printing(self):
for x in range(5):
#New screen as a child of MDCard
layout = Screen(
pos_hint={'center_x':0.5},
size_hint=(1,None)
)
img=AsyncImage(
source="https://i.redd.it/x1js663rq7s41.jpg",
pos_hint={'center_x':0.5, 'center_y':0.6},
size_hint = (.5,.5)
)
card = MDCard(
orientation='vertical',
pos_hint={'center_x':0.5},
size_hint=(1, None),
#size=(dp(100), dp(100)) #Size is required to view stuff in Cards
)
layout.add_widget(img)
card.add_widget(layout)
self.scroll_id.add_widget(card)

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

Kivy RecycleView SelectableButton Does not switch to a Dynamic Layout Page

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()

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