Kivy. Refreshing screen with different data - python-3.x

When I click on the button I need the data from the code to be shown on different screen. Below code does what I need despite that Clock adds widgets every second. I would like the TextInput to be editable so when I type some value to the 'Row' it will stay as typed. I've tried to use Clock.schedule_once() but then the widgets does not appear. I've even tried something like below:
clock = 1
def fill_with_data(self, dt)
if clock == 1:
for item ...
clock +=1
witch locks adding new widgets (ridicules I know) but when I go back to MainScreen to show another data then it does not appear on the screen. Below you will find whole code.
from kivy.config import Config
Config.set('graphics', 'multisamples', '0')
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import Screen
from kivy.clock import Clock
kv = """
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
ScreenManager:
MainScreen:
ShowData:
<DataSwitch>
Button:
text: 'Data 1'
on_press: app.root.current = 'ShowData'
on_press: root.show_data1()
Button:
text: 'Data 2'
on_press: app.root.current = 'ShowData'
on_press: root.show_data2()
<Row>
TextInput:
id: text_input
size_hint_y: None
height: 30
<Rows>
orientation: 'vertical'
<MainScreen>
name: 'MainScreen'
DataSwitch:
<ShowData>:
name: 'ShowData'
Rows:
Button:
text: 'Go back'
size_hint_y: None
height: 20
on_press: app.root.current = 'MainScreen'
"""
class Row(BoxLayout):
text = ''
def __init__(self, **kwargs):
super(Row, self).__init__(**kwargs)
self.set_text()
def set_text(self):
print('set', self.text)
self.ids.text_input.text = self.text
class Rows(BoxLayout):
data =[]
def __init__(self, **kwargs):
super(Rows, self).__init__(**kwargs)
#self.fill_with_data()
Clock.schedule_interval(self.fill_with_data, 1)
#Clock.schedule_once(self.fill_with_data)
def fill_with_data(self, dt):
for item in self.data:
Row.text = item
row = Row()
self.add_widget(row)
#self._rows[str(self.row_id)] = weakref.ref(row)
class MainScreen(Screen):
pass
class DataSwitch(BoxLayout):
data1 = ['1', '2', '3', '4']
data2 = ['5', '6', '7', '8']
def __init__(self, **kwargs):
super(DataSwitch, self).__init__(**kwargs)
def show_data1(self):
print('data1', self.data1)
Rows.data = self.data1
Rows()
def show_data2(self):
Rows.data = self.data2
Rows()
class ShowData(Screen):
pass
sm = Builder.load_string(kv)
class TestApp(App):
def build(self):
return sm
if __name__ == '__main__':
TestApp().run()

Running the app will produce the following:
Problem 1 - too many Rows instantiated
Rows: as per kv file
Rows() as per Python script in methods show_data1() and show_data2(). These Rows instances created does not have an associated ModalView.
Problem 2 - switched screen before populating list, data
Button:
text: 'Data 1'
on_press: app.root.current = 'ShowData'
on_press: root.show_data1()
Button:
text: 'Data 2'
on_press: app.root.current = 'ShowData'
on_press: root.show_data2()
Solution
The following example is just an illustration.
kv file - Add id: rows
Add id: rows to Rows: in class rule, <ShowData>:. This will be use to reference attributes or methods in class Rows().
kv file - invoke fill_with_data()
Use on_pre_enter event of Screen and Clock.schedule_once() function to invoke method fill_with_data()
Note:
Row will be added each time, the screen is displayed. In other words, Row widget will double each time. To prevent this, one might want to delete the widgets added in on_pre_leave event.
Snippets - kv file
<ShowData>:
name: 'ShowData'
on_pre_enter:
Clock.schedule_once(self.ids.rows.fill_with_data, 0.1)
Rows:
id: rows
py file - Access attributes / methods in class Rows()
Get object, app using App.get_running_app()
Get object, ShowData using Screen Manager's get_screen() function e.g. root.get_screen('ShowData')
Access attribute, data using ids.rows.data
Snippets
def show_data1(self):
App.get_running_app().root.get_screen('ShowData').ids.rows.data = self.data1
def show_data2(self):
App.get_running_app().root.get_screen('ShowData').ids.rows.data = self.data2
Example
main.py
from kivy.config import Config
Config.set('graphics', 'multisamples', '0')
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import Screen
kv = """
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
#:import Clock kivy.clock.Clock
ScreenManager:
MainScreen:
ShowData:
<DataSwitch>
Button:
text: 'Data 1'
on_press: root.show_data1()
on_press: app.root.current = 'ShowData'
Button:
text: 'Data 2'
on_press: root.show_data2()
on_press: app.root.current = 'ShowData'
<Row>:
TextInput:
id: text_input
size_hint_y: None
height: 30
<Rows>:
orientation: 'vertical'
<MainScreen>:
name: 'MainScreen'
DataSwitch:
<ShowData>:
name: 'ShowData'
on_pre_enter:
Clock.schedule_once(self.ids.rows.fill_with_data, 0.1)
Rows:
id: rows
Button:
text: 'Go back'
size_hint_y: None
height: 20
on_press: app.root.current = 'MainScreen'
"""
class Row(BoxLayout):
def __init__(self, text, **kwargs):
super(Row, self).__init__(**kwargs)
self.ids.text_input.text = text
class Rows(BoxLayout):
data = []
def fill_with_data(self, dt):
for item in self.data:
row = Row(text=str(item))
self.add_widget(row)
class MainScreen(Screen):
pass
class DataSwitch(BoxLayout):
data1 = ['1', '2', '3', '4']
data2 = ['5', '6', '7', '8']
def show_data1(self):
App.get_running_app().root.get_screen('ShowData').ids.rows.data = self.data1
def show_data2(self):
App.get_running_app().root.get_screen('ShowData').ids.rows.data = self.data2
class ShowData(Screen):
def on_pre_leave(self):
self.ids.rows.clear_widgets()
sm = Builder.load_string(kv)
class TestApp(App):
def build(self):
return sm
if __name__ == '__main__':
TestApp().run()

Related

Print button dynamic size in console - Kivy

I would like to print button size in console. Able to get root window size, any idea how to get the buttons?
Hoping some way iterate through the widget ids?
.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
class TestOut(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def on_size(self, *args):
# print(str(self.size))
pass
def get_size(self):
for key, val in self.ids.items():
print(key, val)
# print(self.ids.button1.size)
class PlaygroundApp(App):
title = "test"
def build(self):
return TestOut()
if __name__ == "__main__":
PlaygroundApp().run()
.kv
<TestOut>:
Button:
id: button1
text: 'A'
width: dp(100)
size_hint: None, 1
Button:
id: button2
text: 'B'
Thanks in advance!
<TestOut>:
Button:
id: button1
text: 'A'
width: dp(100)
size_hint: None, 1
on_release:
print(button1.size)
Button:
id: button2
text: 'B'
on_release:
print(button2.size)

A non-existent line

I'm learning Kivy and I'm on the MDExpansionPanel widget.
Data source from a JSON I use the keys to assemble my panels and the values to compose my Contents.
It happens that I'm able to do it but an extra line always appears in my contents.
I would like your help to delete this line.
I will post my code below:
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.expansionpanel import MDExpansionPanel, MDExpansionPanelOneLine
from kivy.properties import StringProperty, ObjectProperty
json_data = {'01001BA476': {'price': '74.73',
'product_code': '000003',
'quantity': '100'},
'0100251633': {'price': '92.07',
'product_code': '000156',
'quantity': '1000'}}
KV = '''
<ClassDetails>
orientation: 'vertical'
adaptive_height: True
OneLineIconListItem:
id: info_line
text: root.text
on_press: root.action()
IconLeftWidget:
icon: 'star'
on_press: print(f'star pressed on line: {info_line.text}')
ScrollView:
MDGridLayout:
id: box
cols: 1
adaptive_height: True
'''
class ClassDetails(MDBoxLayout):
text = StringProperty()
action = ObjectProperty()
class InvoicePanel(MDExpansionPanel):
pass
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
def fechar_pedido(self):
print('You clicked on the information line')
def on_start(self):
for class_title, class_details in json_data.items():
cd = ClassDetails()
expansion_panel = InvoicePanel(panel_cls=MDExpansionPanelOneLine(text=f'Invoice #: {class_title}'), content=cd)
self.root.ids.box.add_widget(expansion_panel)
for item in class_details.items():
cd.add_widget(ClassDetails(
text=str(class_details.values()), action=self.fechar_pedido))
Test().run()
I think the extra line is coming from setting your initial content to a ClassDetails instance, which has a OneLineIconListItem in its definition. Try replacing the content with a simple MDBoxLayout instead:
def on_start(self):
for class_title, class_details in json_data.items():
print(class_title, class_details)
# cd = ClassDetails()
cd = MDBoxLayout(orientation='vertical', adaptive_height=True)
expansion_panel = InvoicePanel(panel_cls=MDExpansionPanelOneLine(text=f'Invoice #: {class_title}'), content=cd)
self.root.ids.box.add_widget(expansion_panel)
for item in class_details.items():
print('\titem:', item)
cd.add_widget(ClassDetails(
text=str(class_details.values()), action=self.fechar_pedido))

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

Changing button text with popup text input kivy

So, I'm new to kivy and can't get this code to work. I'm trying to update a button text with a popup text input. The popup shows when the button is pressed, and when it's dismissed it should update the text on the button with whatever text was typed in it.
I've tried many variations of this code, but no one have worked. Either nothing happens or I get this error:
AttributeError: 'super' object has no attribute '__getattr__'
Here it is:
main.py
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.popup import Popup
class MainWidget(GridLayout):
pass
class PopText(Popup):
def textChange(self):
MyButton().change()
def getText(self):
text = self.display
return text
class MyButton(AnchorLayout):
def change(self):
self.ids.equip_bt.text = PopText().getText()
print(self.ids.my_bt.text)
class MyApp(App):
def build(self):
return MainWidget()
if __name__ == "__main__":
MyApp().run()
my.kv
#:kivy 1.10.0
#:import F kivy.factory.Factory
<PopText>:
size_hint: .7, .3
title: "Input Text"
on_dismiss: self.textChange()
display: pop_text.text
TextInput:
id: pop_text
focus: True
multiline: False
on_text_validate: root.dismiss()
<MyButton>:
anchor_y: "top"
anchor_x: "right"
Button:
id: my_bt
text: "Input Text"
on_release: F.PopText().open()
<MainWidget>:
cols: 1
rows: 2
MyButton:
MyButton:
Any ideas on how to solve this?
Here is a minimum example of what you are trying to achieve. The hard part is connection the button from the Popup to the Button which opened it. I am going through the app class to achieve that. I got to admit it is not a pretty solution.
from kivy.app import App
from kivy.uix.popup import Popup
from kivy.uix.button import Button
class PopupBttn(Button):
def openPopup(self):
Pop = TextPopup().open(self.text)
class TextPopup(Popup):
def open(self, text, **kwargs):
super(TextPopup, self).open(**kwargs)
self.ids.txtipt.text = text
class MyApp(App):
pass
if __name__ == "__main__":
MyApp().run()
kv file:
BoxLayout:
PopupBttn:
id: bttn
text: 'open Popup'
on_press: self.openPopup()
<TextPopup>:
BoxLayout:
orientation: "vertical"
TextInput:
id: txtipt
Button:
text: "OK"
on_release: app.root.ids.bttn.text=root.ids.txtipt.text
on_release: root.dismiss()
Here is an updated version to use multiple buttons. Unfortunately, you will need to set ids and name to the string of id per Button.
python file
from kivy.app import App
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.properties import StringProperty
class PopupBttn(Button):
def openPopup(self):
print(self.name)
Pop = TextPopup().open(self.text, self.name)
class TextPopup(Popup):
bttnid = StringProperty()
text = StringProperty()
def open(self, text, id, **kwargs):
super(TextPopup, self).open(**kwargs)
self.ids.txtipt.text = text
self.bttnid = id
def setText(self):
App.get_running_app().root.ids[self.bttnid].text = self.text
class MyApp(App):
pass
if __name__ == "__main__":
MyApp().run()
kv file
BoxLayout:
orientation: 'vertical'
PopupBttn:
name: 'one'
id: one
text: 'I am the first Button'
PopupBttn:
name: 'two'
id: two
PopupBttn:
name: 'three'
id: three
PopupBttn:
name: 'four'
id: four
text: 'I am the fourth button'
<PopupBttn>:
text: 'open Popup'
on_press: self.openPopup()
<TextPopup>:
text: txtipt.text
BoxLayout:
orientation: "vertical"
TextInput:
id: txtipt
Button:
text: "OK"
on_release: root.setText()
on_release: root.dismiss()
In order to update both buttons, you need to assign unique id to each of them. Please refer to the example below for details.
Example
main.py
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.popup import Popup
from kivy.properties import ObjectProperty
class MainWidget(GridLayout):
btn_top = ObjectProperty()
btn_bottom = ObjectProperty()
class PopText(Popup):
pass
class MyButton(AnchorLayout):
my_btn = ObjectProperty()
class TestApp(App):
title = "Changing button text with popup text input Kivy"
def build(self):
return MainWidget()
if __name__ == "__main__":
TestApp().run()
test.kv
#:kivy 1.10.0
#:import F kivy.factory.Factory
<PopText>:
size_hint: .7, .3
title: "Input Text"
TextInput:
focus: True
multiline: False
on_text_validate:
app.root.btn_top.my_btn.text = self.text
app.root.btn_bottom.my_btn.text = self.text
root.dismiss()
<MyButton>:
my_btn: my_bt
anchor_y: "top"
anchor_x: "right"
Button:
id: my_bt
text: "Input Text"
on_release: F.PopText().open()
<MainWidget>:
btn_top: btn_top
btn_bottom: btn_bottom
cols: 1
rows: 2
MyButton:
id: btn_top
MyButton:
id: btn_bottom
Output

Changing Kivy Spinner values dynamically

I´m looking for a way to set the values of a Kivy Spinner depending on the text chosen from the parent spinner. The problem is that I have defined both spinners in kv language. I´m not sure if the approach would be to destroy the second spinner each time a new value is chosen from the first one, then regenerate it (if so I have no idea how to do that because of the kv code), or if there would be a way to dynamically update the "values" of the second spinner based on an on_text procedure updating the list.
In all cases the below code does not work. Any help appreciated. Thanks.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.spinner import Spinner
Builder.load_string('''
<MainScreen>:
AnchorLayout:
anchor_x: 'center'
anchor_y: 'top'
BoxLayout:
size_hint: 1, .9
orientation: 'vertical'
padding: 80
spacing: 20
Spinner:
id: spinner_1
text: '< Select >'
values: root.pickType
on_text: root.updateSubSpinner(spinner_1.text)
Spinner:
id: spinner_2
text: '< Select >'
values: root.pickSubType
AnchorLayout:
anchor_x: 'center'
anchor_y: 'bottom'
size_hint: 1, .1
Button:
on_press: root.onExit()
text: 'Exit'
font_size: 50
''')
class MainScreen(FloatLayout):
def __init__(self, **kwargs):
self.buildLists()
super(MainScreen, self).__init__(**kwargs)
def buildLists(self):
self.pickType = ['Select','#1','#2','#3']
self.pickSubType = ['Select']
def updateSubSpinner(self,text):
if text == '#1':
self.pickSubType.extend('A'+'B')
elif text == '#2':
self.pickSubType.extend('P'+'Q')
else:
self.pickSubType.extend('Y'+'Z')
def onExit(self):
App.get_running_app().stop()
class TestApp(App):
def build(self):
return MainScreen()
if __name__ == "__main__":
TestApp().run()
You should reference the child spinner values list, not the pickSubType list, which isn't even bound to it. Example:
def updateSubSpinner(self, text):
self.ids.spinner_2.text = 'Select'
if text == '#1':
self.ids.spinner_2.values = ['A', 'B']
elif text == '#2':
self.ids.spinner_2.values = ['P', 'Q']
else:
self.ids.spinner_2.values = ['Y', 'Z']
Writing values: root.pickSubType doesn't bind the pickSubType to the values, if the first isn't a ListProperty. You could upgrade it to a ListProperty if you like, but it is not necessary.

Resources