Inaccurate hover behavior with KivyMD - python

I am using Kivy and KivyMD and running into an issue when combining a Kivy RecycleView with a KivyMD HoverBehavior. What happens is that the bottom element in my list (inside the recycle view) behaves the way I expect it to with the hover behavior, correctly detecting when the mouse enters and exits the bounding box of the list element. However, as I move up higher on the list, the element that is being detected as hovered gets further and further from where my mouse pointer actually is.
For example, when hovering over element 10 of 10, the on_enter and on_leave methods of the hover behavior correctly show that, but I would have to hover over element 8 to get element 9 to be detected, 6 to get 8 to be detected, and so on.
Here is the code I have so far, pared down as far as I can to be able to reproduce the issue. You can install the dependencies (Kivy and KivyMD) and run the code. From there, just try to hover over various elements in the list and notice how the wrong checkboxes appear checked and the wrong element numbers output to the console.
What might be causing this type of behavior?
from kivy.app import App
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.properties import BooleanProperty, StringProperty
from kivymd.uix.behaviors import HoverBehavior
from kivy.uix.relativelayout import RelativeLayout
Builder.load_string('''
<RV>:
viewclass: 'TestItem'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
<TestItem>:
canvas:
Color:
rgba: .8, .8, .8, 1
Line:
points: 0,0,self.width,0
width:1
CheckBox:
id: "checkbox"
active: True if root.status == 'completed' else False
Label:
size_hint_x: .86
text: root.title
''')
class TestItem(RecycleDataViewBehavior, RelativeLayout, HoverBehavior):
''' Add selection support to the Label '''
status = StringProperty()
title = StringProperty()
def on_enter(self, *args):
self.children[1].background_checkbox_normal ="atlas://data/images/defaulttheme/checkbox_on"
print("Entering", self.title)
def on_leave(self, *args):
self.children[1].background_checkbox_normal ="atlas://data/images/defaulttheme/checkbox_off"
print("Leaving", self.title)
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'title': str(x), 'status': "not started"} for x in range(10)]
self.refresh_from_data()
class TestApp(App):
def build(self):
return RV()
if __name__ == '__main__':
TestApp().run()

The HoverBehavior does not appear to work correctly for RelayiveLayout. Try changing
class TestItem(RecycleDataViewBehavior, RelativeLayout, HoverBehavior):
to
class TestItem(RecycleDataViewBehavior, HoverBehavior, FloatLayout):
That will also require refactoring the <TestItem> rule in your kv. Something like:
<TestItem>:
canvas:
Color:
rgba: .8, .8, .8, 1
Line:
points: self.x, self.y,self.width,self.y
width:1
CheckBox:
id: "checkbox"
size_hint_x: .14
pos_hint: {'x': 0.86}
center_y: root.center_y
active: True if root.status == 'completed' else False
Label:
size_hint_x: .86
pos_hint: {'x': 0}
center_y: root.center_y
text: root.title

Related

Spinner at bottom of recyleview in kivy and kivymd

I want to user to see the spinner when they reach the bottom of the Recycleview..
In this bellow code the spinner is visible in the viewport this is the issue so there is some code below..
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.scrollview import ScrollView
from kivy.uix.screenmanager import Screen
Builder.load_string('''
<MyScreen>:
ScrollView:
BoxLayout:
orientation:"vertical"
size_hint_y:None
height:root.height
RV:
size_hint_y:None
height:root.height
viewclass: 'Button'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
size_hint_y:None
height:self.minimum_height
MDSpinner:
size_hint:None,None
width:dp(40)
height:dp(20)
pos_hint:{"center_x":.5}
''')
class MyScreen(Screen):
pass
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'text': str(x)} for x in range(25)]
class TestApp(MDApp):
def build(self):
return MyScreen()
if __name__ == '__main__':
TestApp().run()
Thanks in advance..
If you want the last item in the RecycleView to be a spinner, you cannot do that with a viewclass of Button (unless you can make a Button look like a Spinner). However, you can design your own viewclass that can look like either a Button or a Spinner. So defining a custom viewclass like this:
class MyViewClass(RelativeLayout):
text = StringProperty('') # text for the Button
spinner = BooleanProperty(False) # if True, show a Spinner instead
Then add a rule for it in your kv:
<MyViewClass>:
Button:
text: root.text
opacity: 0 if root.spinner else 1
MDSpinner:
pos_hint:{"center_x":.5}
opacity: 1 if root.spinner else 0
active: True if root.spinner else False
size_hint: None, None
height: root.height
width: root.height
The above rule uses opacity to determine whether the Button or the Spinner is visible. And the active attribute of the MDSpinner is used to avoid having a spinning MDSpinner running in the items where it is not visible.
Then just specify MyViewClass as the viewclass:
viewclass: 'MyViewClass'
And remove the old MDSpinner from your kv.
The last thing to do is to adjust your data:
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'text': str(x), 'spinner': False} for x in range(25)]
self.data.append({'text': '', 'spinner': True})
The data now sets spinner to False for all the entries, and adds an extra entry with spinner set to True.

How to replace deprecated ListView in kivy?

I am studying kivy for now and reading Creating Apps with kivy. Author use follwing code:
.kv
AddLocationForm:
<AddLocationForm#BoxLayout>:
orientation : 'vertical'
BoxLayout:
pos : 100, 100
height : '40dp'
size_hint_y : None
TextInput:
size_hint_x : 50
Button:
text : 'search'
size_hint_x : 25
Button:
text : 'current location'
size_hint_x : 25
ListView:
item_strings: ["Palo Alto, MX", "Palo Alto, US"]
and .py
from kivy.app import App
class FirstKivyApp(App):
pass
FApp = FirstKivyApp()
FApp.run()
But as much as I understand ListView is deprecated now. It is supposed to be changed on RecycleView now. I've checked for some solutions but they don't make sense for me because use things I've not accomplished yet. I tried to use
RecycleView:
data : ["Palo Alto, MX", "Palo Alto, US"]
instead of ListView but it isn't shown whereas I can access the data through id and ObjectProperty. Is there a way to display data in simplier way, than using ScreenManager, constructing classes and referring to build method? For example something like in author's or my example, but working. Adding RecycleBoxLayout didn't work too.
When calling your Recycleview in kivy, make sure it has an appropriate id, this can then be called in your python code with data applied with the following:
rows = ["Palo Alto, MX", "Palo Alto, US"] # declare list
self.ids.rv.data = [{'text':str(row)}for row in rows] # add list
The kivy docs site has a great example of how to implement RecycleView, found here: https://kivy.org/doc/stable/api-kivy.uix.recycleview.html
Try this for a basic example showing how to use ScreenManager with RecycleView:
import kivy
# import main libraries, import object types and layouts
from kivy.config import Config
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.widget import Widget
from kivy.properties import (StringProperty, ObjectProperty,
OptionProperty, NumericProperty, ListProperty, BooleanProperty)
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.recyclegridlayout import RecycleGridLayout
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.behaviors import FocusBehavior
# import screen features
from kivy.uix.label import Label
from kivy.uix.button import Button
# load in kv file, deals with cosmetics of each screen
kv = """
<SelectableLabel>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if root.selected else (0, 0, 0, .1)
Rectangle:
pos: self.pos
size: self.size
<SelectScreen>:
BoxLayout:
canvas:
Color:
rgba: 0.3, 0.3, 0.3, 1
Rectangle:
size: self.size
orientation: 'vertical'
GridLayout:
cols: 2
rows: 1
size_hint_y: .25
height: dp(54)
padding: dp(8)
spacing: dp(16)
Button:
text: 'Select all'
font_size: 24
on_release:
controller.select_all(len(rv.data))
Button:
text: 'Deselect all'
font_size: 24
on_release:
controller.clear_all()
RecycleView:
id: rv
scroll_type: ['bars', 'content']
scroll_wheel_distance: dp(114)
bar_width: dp(10)
viewclass: 'SelectableLabel'
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)
# Adds selectable labels to lists (recycleview)
class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,RecycleBoxLayout):
''' Adds selection and focus behaviour to the view. '''
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_all(self, num):
print(num)
#print(len(self.ids.rv.data))
last, nodes = self.get_nodes()
print(nodes)
for x in range(num):
print(x)
self.select_node(x)
def clear_all(self):
self.clear_selection()
# Create action on selectable list, for example apply selection remembers previous selection and saves to sqlite db
class SelectableLabel(RecycleDataViewBehavior, Label):
''' Add selection support to the Label '''
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
index = int
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 self.selected:
print("selection changed to {0}".format(rv.data[index]))
else:
print("selection removed for {0}".format(rv.data[index]))
# Screen for selecting from recycleview
class SelectScreen(Screen):
interfacename = StringProperty()
def __init__(self, **kwargs):
super(SelectScreen, self).__init__(**kwargs)
rows = ["Palo Alto, MX", "Palo Alto, US"] # declare list
self.ids.rv.data = [{'text':str(row)}for row in rows] # add list
def select(self): # selects participants from list new value to database
print (self.ids.rv.data[val[0]]['text'])
def selectall(self):
for num in range(len(self.ids.rv.data)):
SelectableLabel.apply_selection(self, self.ids.rv, num, True)
# screen manager
sm = ScreenManager()
sm.add_widget(SelectScreen(name='select')) # main starting menu
# Build the app return screenmanager
class RecycleviewApp(App):
def build(self):
return sm
if __name__ == '__main__':
RecycleviewApp().run()
Example image of application and terminal printing application and removal of RecycleView selection:
I founded out the issue.
Firstly, format of data must be suitable. As twicejiggled showed it must be list of dicts.
Secondly, as much as I understood, some layout is neede for showing. For example this code in .kv will work:
RecycleView:
data : [{'text':'text'} for x in range(50)]
viewclass: 'Label'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'

Kivy widget can't change pos

I would like to put a circular button (buttonBehavior that I changed to make it circle) to the bottom right of a TabbedPanelItem.
So I did like this :
Kivy file :
#:kivy 1.0.9
<CircularButton>:
size: (min(self.width,self.height),min(self.width,self.height)) # force circle
canvas:
Color:
rgba: ((1,1,1,1) if self.state == "normal" else (.5,.5,.5,1))
Ellipse:
pos: self.pos
size: self.size
<MainWindow>:
tab_pos: 'left_mid'
size_hint: .5, .5
pos_hint: {'center_x': .5, 'center_y': .5}
do_default_tab: False
TabbedPanelItem:
addIngre: buttonAdd
text: 'All'
CircularButton:
id: buttonAdd
size_hint: .1, .1
pos : 90, 90 #Here it doesn't work
TabbedPanelItem:
text: 'first tab'
Label:
text: 'First tab content area'
TabbedPanelItem:
text: 'tab2'
BoxLayout:
Label:
text: 'Second tab content area'
Button:
text: 'Button that does nothing'
python file :
from kivy.app import App
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.vector import Vector
from kivy.uix.behaviors.button import ButtonBehavior
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty,\
ObjectProperty
class CircularButton(ButtonBehavior, Widget):
def __init__(self, **kwargs):
super(CircularButton, self).__init__(**kwargs)
def collide_point(self, x, y):
return Vector(x, y).distance(self.center) <= self.width / 2
def on_release(self):
print("Ok")
class MainWindow(TabbedPanel):
addIngre = ObjectProperty()
pass
class ListApp(App):
def build(self):
return MainWindow()
if __name__ == '__main__':
ListApp().run()
The size_hint works. I believe it gives my widget 10% of the size of the tab, but pos doesn't change the position of my widget.
I tried with pos, pos_hint, ... but nothing seems to works, position is not applied to my widget
Do I have to put a position in my widget, and call for this position instead of what I'm doing here?
Kivy » TabbedPanel
The TabbedPanel widget manages different widgets in tabs, with a
header area for the actual tab buttons and a content area for
showing the current tab content.
Solution
Add a FloatLayout inside the TabbedPanelItem
Add your CircularButton inside the FloatLayout
Use pos_hint:
Snippets
TabbedPanelItem:
addIngre: buttonAdd
text: 'All'
FloatLayout:
CircularButton:
id: buttonAdd
size_hint: .1, .1
# pos: root.center
# pos_hint: {'x': 0.9, 'y': 0.9}
pos_hint: {'x': 0.3, 'y': 0.5}
Output

How to Periodically Update Parent (Screen) Class UI from Child (BoxLayout) Class (Python with Kivy)

Goal:
Periodic update of parent (screen) class / UI from child (boxlayout) class. Theconf2.dat is occasionally updated (from various other screens), and I want the UI to update every 5 seconds or so by re-running this class.
Latest code update:
In the __init__ function, I have Clock.schedule_interval(self.create_button, 1), which should cause the create_button function to rerun every second.
At the top of the create_button function, I have self.box_share.clear_widgets(), which should clear all the widgets so they can be repopulated (per the instructions outlined further down the create_button function).
Action:
I run the code
I navigate to NoTags screen by clicking the button with text title 'updating sequence'
I make changes to buttons that were dynamically created under scrollview by clicking on them. They successfully change color. This information is written to the conf2.dat file.
I navigate to SequenceScreen screen by first clicking 'home' button, then clicking 'sequence display' button. This SequenceScreen screen is the screen I wish to have updated to reflect the changes made to conf2.dat file.
Result:
UI associated withSequenceScreen(Screen) class still does not update per changes made from UI associated with NoTags(Screen) class.
However, when I restart the app altogether, I find the SequenceScreen UI successfully updated.
Suspicion:
I'm just one line of code away from getting this UI to update properly.
Python Code:
from kivy.app import App
# kivy.require("1.10.0")
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.textinput import TextInput
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.properties import NumericProperty
from kivy.clock import Clock
from kivy.uix.widget import Widget
from kivy.uix.scrollview import ScrollView
#from kivy.base import runTouchApp
from kivy.properties import StringProperty, ObjectProperty, NumericProperty
from kivy.storage.dictstore import DictStore
import pickle
import datetime, threading
import time
from kivy.clock import mainthread
class BackHomeWidget(Widget):
pass
class SequenceBoxLayout_NoEdits(BoxLayout):
box_share = ObjectProperty()
config_file = DictStore('conf2.dat')
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoEdits, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_interval(self.create_button, 1)
def create_button(self, *args):
self.box_share.clear_widgets()
top_button_share = 1.1
color = (.4, .4, .4, 1)
for i in range(25):
top_button_share -= .4
id_ = "part" + str(i + 1)
if self.config_file.exists(id_):
btn_color = self.config_file[id_]["background_color"]
else:
self.config_file.put(id_, background_color=color)
btn_color = color
button_share = Button(background_normal='',
background_color=btn_color,
id=id_,
pos_hint={"x": 0, "top": top_button_share},
size_hint_y=None,
height=60,
font_size = 30,
text= str(i+1)
self.box_share.add_widget(button_share)
#Clock.schedule_interval(self.parent.ids.updatedisplay.create_button(self, *args) , 1)
#self.parent.ids.updatedisplay.create_button(self, *args)
class SequenceBoxLayout_NoTag(BoxLayout):
box_share = ObjectProperty()
config_file = DictStore('conf2.dat')
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoTag, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_once(self.create_button)
def create_button(self, *args):
df = pd.read_excel("Test.xlsx","Sheet1")
parts = df['parts'].values.tolist()
top_button_share = 1.1
color = (.4, .4, .4, 1)
for i in range(len(parts)):
top_button_share -= .4
id_ = "part" + str(i + 1)
if self.config_file.exists(id_):
btn_color = self.config_file[id_]["background_color"]
else:
self.config_file.put(id_, background_color=color)
btn_color = color
button_share = Button(background_normal='',
background_color=btn_color,
id=id_,
pos_hint={"x": 0, "top": top_button_share},
size_hint_y=None,
height=60,
font_size = 30,
text= str(i+1)+ ". " + str(parts[i]))
if self.parent.name == 'notags':
button_share.bind(on_press=self.update_buttons_notag)
self.box_share.add_widget(button_share)
def update_buttons_notag(self, button):
button.background_color = 0.86,0.54,0.04,1
self.config_file.put(button.id, background_color=(0.86,0.54,0.04,1))
class MainScreen(Screen):
pass
class AnotherScreen(Screen):
pass
class SequenceScreen(Screen):
pass
class NoTags(Screen):
pass
class ScreenManagement(ScreenManager):
pass
presentation = Builder.load_file("updatelistexample.kv")
class MainApp(App):
def build(self):
return presentation
if __name__ == "__main__":
MainApp().run()
KV Code:
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
ScreenManagement:
transition: FadeTransition()
MainScreen:
AnotherScreen:
NoTags:
SequenceScreen:
<SmallNavButton#Button>:
font_size: 32
size: 125, 50
color: 0,1,0,1
<BigButton#Button>:
font_size: 40
size_hint: 0.5, 0.15
color: 0,1,0,1
<BackHomeWidget>:
SmallNavButton:
on_release: app.root.current = "main"
text: "Home"
pos: root.x, root.top - self.height
<MainScreen>:
name: "main"
FloatLayout:
BigButton:
on_release: app.root.current = "notags"
text: "updating sequence"
pos_hint: {"x":0.25, "top": 0.4}
BigButton:
on_release: app.root.current = "sequence"
text: "sequence display"
pos_hint: {"x":0.25, "top": 0.7}
<AnotherScreen>:
name: "newgarage"
<NoTags>:
name: "notags"
SequenceBoxLayout_NoTag:
BackHomeWidget:
FloatLayout:
BigButton:
text: "Select Parts w/o Tags"
pos_hint: {"x":0.5, "top": 0.6}
background_normal: ''
background_color: (0.4,0.4,0.4,1)
<SequenceBoxLayout_NoEdits>:
box_share: box_share
ScrollView:
GridLayout:
id: box_share
cols: 1
size_hint_y: None
size_hint_x: 0.5
spacing: 5
padding: 130
height: self.minimum_height
canvas:
Color:
rgb: 0, 0, 0
Rectangle:
pos: self.pos
size: self.size
<SequenceBoxLayout_NoTag>:
box_share: box_share
ScrollView:
GridLayout:
id: box_share
cols: 1
size_hint_y: None
size_hint_x: 0.5
spacing: 5
padding: 130
height: self.minimum_height
canvas:
Color:
rgb: 0, 0, 0
Rectangle:
pos: self.pos
size: self.size
<SequenceScreen>:
name: "sequence"
SequenceBoxLayout_NoEdits:
id: updatedisplay
BackHomeWidget:
Credit:
Based on advice provided by #Tshirtman in the comments thread of the posted question...
Summary:
The problem with the code had to do with the fact that I had two different DictStore pointing to the same file, which was tripping up the communication between both classes.
The solution was to instead use only one DictStore and define that variable in the App class, then reference that particular variable in the child classes [using App.get_running_app()], like so:
Define config_file in App class:
class MainApp(App):
config_file = DictStore('conf2.dat')
def build(self):
return presentation
Reference App variable in child classes:
class SequenceBoxLayout_NoEdits(BoxLayout):
...
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoEdits, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_interval(self.create_button, 1)
def create_button(self, *args):
self.box_share.clear_widgets()
app = App.get_running_app()
...
for i in range(len(parts)):
...
if app.config_file.exists(id_):
btn_color = app.config_file[id_]["background_color"]
else:
app.config_file.put(id_, background_color=color)
...
...
class SequenceBoxLayout_NoTag(BoxLayout):
...
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoTag, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_once(self.create_button)
def create_button(self, *args):
...
app = App.get_running_app()
...
for i in range(len(parts)):
...
if app.config_file.exists(id_):
btn_color = app.config_file[id_]["background_color"]
else:
app.config_file.put(id_, background_color=color)
...
...
def update_buttons_notag(self, button):
app = App.get_running_app()
...
app.config_file.put(button.id, background_color=(0.86,0.54,0.04,1))
Clock has a schedule_interval method, which works much like the schedule_once one, but will be called every n seconds instead of just once after n seconds.
So you could certainly just change that call in __init__ and start the create_button by calling self.box_share.clear_widgets() to remove widgets from the previous call before re-creating them.
This might be a bit wasteful, if you find yourself recreating a lot of widgets even if nothing changed, so you could add some logic to first check if the data didn't change, or even if it did, just reuse the old buttons if possible.
box = self.box_share
old_buttons = box.children[:]
box.clear_widgets()
# [your logic about computing the list of parts]
for i, p in enumerate(parts): # this is much better than doing range(len(parts))
# [the logic to get the content of the button]
if old_buttons: # check there are still buttons in the list of old buttons
btn = old_buttons.pop()
else:
btn = Button(
background_normal='',
background_color=btn_color,
pos_hint={"x": 0, "top": top_button_share},
# etc, all the things that are common to all your buttons
# but really, hardcoding them like this is a bit painful,
# you should use a subclass of button so you can style it
# in a kv rule so it would apply to all of them directly
)
btn.id=id_
btn.text = "{}. {}".format(i+1, p)
btn.pos_hint["top"] = top_button_share
# re-apply any other property that might have changed for this part
box.add_widget(btn)
But this logic is quite a common one, actually, and there are other things you can do to improve things in even more situations, though that's quite some work.
Thankfully, you are not the first one to need such thing, and we have been blessed with the wonderful RecycleView, which automates all of this and more, you just need to feed it a data directory, and it'll create the necessary widgets to fill the visible part of the scrollview if there is enough widgets to warrant scrolling, and automatically update when data changes, and when you scroll to see different parts of the list. I encourage you to check it, yourself. but the end result would certainly be something like.
<PartButton#Button>:
id_: None
part: ''
text: '{}. {}'.format(self.id, self.part)
<SequencedBoxLayout#Recycleview>:
parts: self.get_parts()
viewclass: 'PartButton'
data:
[
{
id_: i,
part: part
} for i, p in enumerate(self.parts or [])
]
RecycleBoxLayout:

update a kivy label text in another class

This is a follow up to a previous question I've asked on how to change the properties of a kivy widget (update a kivy label text in another class). I've been trying to figure out why the temperature reading on the Menuscreen updates but within the Mashscreen, the text doesn't update. It looks like the values are being passed to eh temperature1def method but the screen widget doesn't update.
Also, is it better to send the value using
Mashscreen().temperature1def(self.test_temp)
or is it better practice to use
self.stuff_p.text = str(self.test_temp) + u'\u00B0F'
within the MenuScreen to update the label within the Mashscreen? Thanks in advance.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.properties import ObjectProperty
from kivy.clock import Clock
sm = """
ScreenManager:
#Handling the gesture event.
id:manager
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 0.5
Rectangle:
pos: 0,0
size: 800, 480
MenuScreen:
id:MenuScreen
name:'MenuScreen'
manager: manager
Mashscreen:
id:Mashscreen
name: 'Mashscreen'
manager: manager
<MenuScreen>:
stuff_r: mainel1temp
Button:
text: "Go to mashscreen"
on_release:
root.manager.current = "Mashscreen"
Label:
id: mainel1temp
text:'0'
size_hint: None, None
size: 75,50
pos: 295,308
font_size:'22sp'
text_size: self.size
halign: 'left'
valign: 'middle'
<Mashscreen>:
stuff_p: temperature1
FloatLayout:
Label:
id: temperature1
text:'100'
size_hint: None, None
size: 75,50
pos: 50,275
font_size:'22sp'
text_size: self.size
halign: 'left'
valign: 'middle'
"""
class MenuScreen(Screen):
test_temp = 99
stuff_r = ObjectProperty(None)
def __init__(self,**kwargs):
super(MenuScreen,self).__init__(**kwargs)
Clock.schedule_interval((self.read_temp), 1)
#self.read_temp(1)
def read_temp(self, dt):
self.test_temp += 1
self.stuff_r.text = str(self.test_temp) + u'\u00B0F'
Mashscreen().temperature1def(self.test_temp)
#self.parent.ids.Mashscreen.stuff_p.text = str(self.test_temp) + u'\u00B0F'
class Mashscreen(Screen):
stuff_p = ObjectProperty(None)
def __init__(self, **kwargs):
super(Mashscreen, self).__init__(**kwargs)
def temperature1def(self, temp1):
print(temp1)
self.stuff_p.text = str(temp1)
class TestApp(App):
def build(self):
return Builder.load_string(sm)
if __name__ == '__main__':
TestApp().run()
First…
Mashscreen().temperature1def(self.test_temp)
This doesn't call the temperature1def method on your Mashscreen instance in the UI, instead, it creates a new Mashscreen instance, calls the method on it, and then let this object be garbage collected by python. If you want to update your UI, you need to get a reference to the widget you want to update.
You define your Mashscreen in the root rule of your application, so you can get it by its id in this object.
App.get_running_app() will return a reference to your currently running app, which has a root attribute, which is your root widget, any widget at the root of a rule can use its ids attribute to get a reference to any id defined in its scope, so.
App.get_running_app().root.ids.Mashscreen.temperature1def(self.test_temp)
will certainly be more like what you actually want to do.
Now, regarding the question about how to do it best in python kivy, i find that it's cleaner to do something like.
App.get_running_app().root.ids.Mashscreen.temperature = self.test_temp
and then to change your Mashscreen class to have a temperature NumericProperty, and to change your kv rule to use this value in the Label.
<Mashscreen>:
stuff_p: temperature1
FloatLayout:
Label:
id: temperature1
text: '%s' % root.temperature
size_hint: None, None
size: 75,50
pos: 50,275
font_size:'22sp'
text_size: self.size
halign: 'left'
valign: 'middle'

Categories

Resources