Widget update in ScrollView

32 views
Skip to first unread message

Tomaz

unread,
Jul 30, 2020, 1:40:16 AM7/30/20
to Kivy users support
Dear all,

I have a question regarding widgets update in ScrollView. 
1) I have a grid (GridLayout)

devices_grid_rows = GridLayout(rows=12, cols=16, row_default_height=58, col_default_width=50, size_hint_y=None)

2) I add widget to every address in this grid (12x16)
devices_grid = devices_grid_rows
        for x in xrange(0, num_devices):
            devices_address[x].reset()
            devices_grid.add_widget(devices_address[x])

3) Since ScrollView accept only one widget, I put all these widgets together
        self.add_widget(devices_grid)

Every widget in a grid (12x16) has a couple of labels (text which is updating). My problem is if I change only one label in one widget in a grid the whole grid refreshes (all elements). If there are not many updates on widgets in a grid, then FPS (frame per second) is suitable, but if there are more updates on separate widgets in a grid, the FPS drops significantly. So for every change in a grid, the whole screen is refreshed (main widget which consists of the grid). Is it possible to refresh only the widget that changes the data and not the entire main widget (grid)? Or is it there any other solution that can fix my problem?

Best regards.

Elliot Garbus

unread,
Jul 30, 2020, 12:44:28 PM7/30/20
to kivy-...@googlegroups.com

If you have a small executable example, it is easier to provide feedback.

 

Here are a few thoughts:

  1. The size_hint_x is defaulting to 1, can you fix the size? Ie size_hint: None, None.
  2. How are you updating the Labels?  Are you using a Kivyproperty?
  3. Is the size causing the layout to recalculate?
  4. Profile the code that does the refresh to see where time is being spent.

--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/430074fc-3f0c-43e8-88fe-da64f451ffe1o%40googlegroups.com.

 

Tomaž - bolha

unread,
Jul 31, 2020, 5:18:43 AM7/31/20
to kivy-...@googlegroups.com
Hi Elliot,
Thanks for the feedback. I will try to provide more exact information on how code is working.

1) Yes, I can fix the size, but this does not change anything :)
2) Yes, kivyproperty is used for updating.

The basic code structure is seen in the picture below. FPS drops with a number of 'elements' in the grid even if the update is always the same. So for every single change in the grid, the whole grid update.
class FboGridLayout(GridLayout):
texture = ObjectProperty(None, allownone=True)

alpha = NumericProperty(1)

def __init__(self, **kwargs):
self.canvas = Canvas()
self.border = Fbo(size=(kwargs['col_default_width'], kwargs['row_default_height']))
with self.border:
Color(0, 0, 1, 1)
Line(rectangle=(0, 0, kwargs['col_default_width'], kwargs['row_default_height']), width=20)

with self.canvas:
self.fbo = Fbo(size=self.size)
self.fbo_color = Color(1., 1., 1., 1.)
self.fbo_rect = Rectangle()

with self.fbo:
ClearColor(0, 0, 0, 0)
ClearBuffers()

# wait that all the instructions are in the canvas to set texture
self.texture = self.fbo.texture
super(FboGridLayout, self).__init__(**kwargs)

def add_widget(self, *largs):
# trick to attach graphics instruction to fbo instead of canvas
canvas = self.canvas
self.canvas = self.fbo
ret = super(FboGridLayout, self).add_widget(*largs)
self.canvas = canvas
return ret

def remove_widget(self, *largs):
canvas = self.canvas
self.canvas = self.fbo
super(FboGridLayout, self).remove_widget(*largs)
self.canvas = canvas

def on_size(self, instance, value):
self.fbo.size = value
self.texture = self.fbo.texture
self.fbo_rect.size = value

def on_pos(self, instance, value):
self.fbo_rect.pos = value

def on_texture(self, instance, value):
self.fbo_rect.texture = value

def on_alpha(self, instance, value):
self.fbo_color.rgba = (1, 1, 1, value)


number_of_devices = 192

devices=[]
add_dev=[]

grid_dev_192 = GridLayout(rows=12, cols=16, row_default_height=58, col_default_width=50, size_hint_y=None)

class DItem(RelativeLayout):
sqr_n = NumericProperty(0)

def __init__(self, **kwargs):
super(DItem, self).__init__(**kwargs)
app = App.get_running_app()
self.dev_lay = app.devices_view
self.large_view = app.large_view

def on_touch_down(self, touch):
if (self.collide_point(*touch.pos) and not touch.is_mouse_scrolling
and self.parent.parent.parent.parent.parent.parent._anim_progress <= 0.1):
touch.grab(self)
whole_view = App.get_running_app().whole_view
self.parent.parent.singlesqr_n.sqr_n = self.sqr_n
whole_view.selection_pl_start = self.sqr_n
whole_view.selection_pl = False
Clock.schedule_once(whole_view.selection_st, .5)
return True

def on_touch_move(self, touch):
if self.collide_point(*touch.pos):
whole_view = App.get_running_app().whole_view
if whole_view.selection_pl:
whole_view.update_poll_ran_sqr_n(self.sqr_n)
return True
elif self.parent.parent.singlesqr_n.sqr_n <> self.sqr_n:
Clock.unschedule(whole_view.selection_st)

def on_touch_up(self, touch, *args):
whole_view = App.get_running_app().whole_view

touch.ungrab(self)
whole_view.prevent_selection_pl()

if not touch.ud.get('ignore-up', False):
if self.collide_point(*touch.pos) and not touch.is_mouse_scrolling:

time = touch.time_end - touch.time_start
if (time < 0.3 and self.parent.parent.singlesqr_n.sqr_n == self.sqr_n
and self.parent.parent.parent.parent.parent.parent._anim_progress <= 0.1
and self.parent.parent.parent.parent._anim_progress <= 0.1
and abs(touch.oy - touch.y) < 30):
touch.ud['ignore-up'] = True
if self.polling:
self.on_touch_up_action()
else:
whole_view.reset_poll_ran()
return True


class Devicesqr_n(DItem):
data = DictProperty({'pre_em': True, 'double_err': False, 'series': '', 'type': '', 'value_a': -1})
bg_color = ListProperty([.2, .2, .2, .1])

def __init__(self, **kwargs):
super(Devicesqr_n, self).__init__(**kwargs)
self.sqr_n_label_white = CoreLabel(text=str(self.sqr_n),
font_size=12, bold=True,
color=(1., 1., 1., 1.))

self.sqr_n_label_white.refresh()
self.sqr_n_label_black = CoreLabel(text=str(self.sqr_n),
font_size=12, color=(0., 0., 0., 1.))
self.sqr_n_label_black.refresh()
self.series_label = CoreLabel(text='', font_size=10,
color=(0., 0., 0., 1.), bold=self.large_view)
self.series_label.refresh()
self.type_label = CoreLabel(text='', font_size=17,
color=(0., 0., 0., 1.), bold=self.large_view)
self.type_label.refresh()
with self.canvas:
Color(1, 1, 1, 1)
self.sqr_n_rect = Rectangle(pos=((2, 40)),
texture=self.sqr_n_label_white.texture,
size=self.sqr_n_label_white.texture.size)

def on_touch_up_action(self):
self.dev_lay.singlesqr_n.pre_open()

def reset(self):
self.parent = None
self.data = {'pre_em': True, 'double_err': False, 'series': '', 'type': '', 'value_a': -1}
self.bg_color = (.2, .2, .2, 1.)
self.series_label.text = ''
self.series_label.refresh()
self.type_label.text = ''
self.type_label.refresh()
self.canvas.clear()
with self.canvas:
Color(1, 1, 1, 1)
self.sqr_n_rect = Rectangle(pos=((2, 40)),
texture=self.sqr_n_label_white.texture,
size=self.sqr_n_label_white.texture.size)

# @mainthread
def update(self, data):

App.get_running_app().devices_updated[self.sqr_n - 1] = True
change = False
bg_color = self.bg_color

if data['pre_em'] <> self.data['pre_em'] or data['double_err'] <> self.data['double_err']:
change = True
if data['pre_em']:
bg_color = [.2, .2, .2, 1.]
elif data['pre_em'] == None:
bg_color = [.0, .0, .0, 1.]
elif data['double_err']:
bg_color = [.65, .3, 1., 1.]
else:
bg_color = [1., 1., 1., 1.]

try:
if data['series'] <> self.data['series']:
change = True
self.series_label.text = data['series']
self.series_label.refresh()
if data['type'] <> self.data['type']:
change = True
self.type_label.text = data['type']
self.type_label.refresh()
if data['value_a'] <> self.data['value_a']:
if data['value_a'] > -1:
value_a = data['value_a']
if value_a < 8:
bg_color = [.594, .594, 1., 1.]
elif value_a < 45:
bg_color = [.414, .829, .414, 1.]
elif value_a < 55:
bg_color = [.72, .72, .36, 1.]
else:
bg_color = [1., .5, .5, 1.]

if self.bg_color <> bg_color:
change = True
self.bg_color = bg_color

if change:
self.data = data
self.canvas.clear()
with self.canvas:
Color(1, 1, 1, 1)
if data['pre_em']:
self.sqr_n_rect = Rectangle(pos=((5, 48) if self.large_view else (2, 40)),
texture=self.sqr_n_label_white.texture,
size=self.sqr_n_label_white.texture.size)
elif data['pre_em'] == None:
self.sqr_n_rect = Rectangle(pos=((5, 48) if self.large_view else (2, 40)),
texture=self.sqr_n_label_white.texture,
size=self.sqr_n_label_white.texture.size)
else:
self.sqr_n_rect = Rectangle(pos=((5, 48) if self.large_view else (2, 40)),
texture=self.sqr_n_label_black.texture,
size=self.sqr_n_label_black.texture.size)
Rectangle(pos=((95 if self.large_view else 47) - self.series_label.texture.size[0],
(47 if self.large_view else 42)),
texture=self.series_label.texture, size=self.series_label.texture.size)
Rectangle(pos=((49 if self.large_view else 24) - self.type_label.texture.size[0] / 2,
5 if self.large_view else 10),
texture=self.type_label.texture, size=self.type_label.texture.size)
self.previous_sqr_n_rect = self.sqr_n_rect
if data['pre_em'] != None:
if not self.selected:
if data['pre_em']:
self.sqr_n_rect.texture = self.sqr_n_label_black.texture
self.sqr_n_rect.size = self.sqr_n_label_black.texture.size
else:
self.sqr_n_rect.texture = self.sqr_n_label_white.texture
self.sqr_n_rect.size = self.sqr_n_label_white.texture.size
self.selected = True
else:
if self.data['pre_em']:
self.sqr_n_rect.texture = self.sqr_n_label_white.texture
self.sqr_n_rect.size = self.sqr_n_label_white.texture.size
else:
self.sqr_n_rect.texture = self.sqr_n_label_black.texture
self.sqr_n_rect.size = self.sqr_n_label_black.texture.size
except KeyError:
print 'File: %s; Line: %s -> I got a KeyError - ' % (
os.path.basename(sys.argv[0]), lineno())

def deselect(self, data):
if data['pre_em'] != None:
if data['pre_em']:
self.sqr_n_rect.texture = self.sqr_n_label_white.texture
self.sqr_n_rect.size = self.sqr_n_label_white.texture.size
else:
self.sqr_n_rect.texture = self.sqr_n_label_black.texture
self.sqr_n_rect.size = self.sqr_n_label_black.texture.size
self.selected = False

class DevView(ScrollView):
current_view = 'Basic1 view'

def __init__(self, **kwargs):
super(DevView, self).__init__(**kwargs)
app = App.get_running_app()

def start_operation_b(self, *args):
global devices
global add_dev
app = App.get_running_app()
self.current_view = 'operation_b'
app.info_overlay = self.operation_b_info_overlay
whole_view = app.whole_view
whole_view.close_navigation_drawer_scheduler()
whole_view.title.text = tr._('operation_b')

self.clear_widgets()

grid_dev = grid_dev_192
grid_dev.clear_widgets()
grid_dev.parent = None

self.scroll_y = 1
for x in xrange(0, number_of_devices):
add_dev[x].reset()
add_dev[x].selected = False
grid_dev.add_widget(add_dev[x])
self.add_widget(grid_dev)
whole_view.loop_index = number_of_devices
devices = add_dev
app.disable_updates = False

whole_view.update_poll_ran()

# r - currently selected menu (1-disable updates, 2-operation_b, 3-mode and polling LED, etc.)
previous_sqr_n = -1

@mainthread
def update_sqr_n(self, sqr_n, data):
if self.update_block and sqr_n != 1:
print "operation_b - update block"
else:
self.update_block = False
global number_of_devices
if App.get_running_app().disable_updates:
self.r = 1
return

device = add_dev[sqr_n - 1]
if self.r != 1:
if self.previous_sqr_n > -1:
if self.r not in [3, 4, 5, 6, 7]: # deselect only if current menu is used
previous_device = add_dev[self.previous_sqr_n]
previous_device.deselect(self.previous_data)
else: # otherwise just change to current menu
self.previous_sqr_n = 0
self.r = 2
device.update(data)
self.previous_sqr_n = sqr_n - 1
self.previous_data = data
else: # if update is not available do not use deselect option
device.update(data)
self.previous_sqr_n = sqr_n - 1
self.previous_data = data
self.r = 2

#kivy#
< Devicesqr_n >:
polling: True
selected: False
size_hint: 1, 1
opacity: 1 if self.polling else .3
canvas.before:
Color:
rgba: self.bg_color
Rectangle:
pos: 0, 1
size: (self.width - 1, self.height - 1)

V V čet., 30. jul. 2020 ob 18:44 je oseba Elliot Garbus <elli...@cox.net> napisala:
You received this message because you are subscribed to a topic in the Google Groups "Kivy users support" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/kivy-users/GEk5gXKpm1k/unsubscribe.
To unsubscribe from this group and all its topics, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/5f22f8e1.1c69fb81.bce45.6704SMTPIN_ADDED_MISSING%40gmr-mx.google.com.

Elliot Garbus

unread,
Jul 31, 2020, 9:44:40 AM7/31/20
to kivy-...@googlegroups.com

Taking a look at your code I see something that could be an issue.  I don’t think you are canceling the schedule. This could cause a significant performance issue.  In the code:

 

def on_touch_down(self, touch):
   
if (self.collide_point(*touch.pos) and not touch.is_mouse_scrolling
           
and self.parent.parent.parent.parent.parent.parent._anim_progress <= 0.1):
        touch.grab(
self)
        whole_view = App.get_running_app().whole_view
       
self.parent.parent.singlesqr_n.sqr_n = self.sqr_n
        whole_view.selection_pl_start =
self.sqr_n
        whole_view.selection_pl =
False
       
Clock.schedule_once(whole_view.selection_st, .5)
       
return True

def
on_touch_move(self, touch):
   
if self.collide_point(*touch.pos):
        whole_view = App.get_running_app().whole_view
       
if whole_view.selection_pl:
            whole_view.update_poll_ran_sqr_n(
self.sqr_n)
           
return True
        elif
self.parent.parent.singlesqr_n.sqr_n <> self.sqr_n:
            Clock.unschedule(whole_view.selection_st)

 

Add self.schedule to the __init__()

Change:

Clock.schedule_once(whole_view.selection_st, .5)

To:

self.schedule = Clock.schedule_once(whole_view.selection_st, .5)

 

and Change:

Clock.unschedule(whole_view.selection_st)
self.schedule.cancel()

Tomaž - bolha

unread,
Aug 2, 2020, 3:19:09 AM8/2/20
to kivy-...@googlegroups.com
Elliot,
thank you for your reply. I try it, but it does not see that scheduler causes the problem. When I was testing different scenarios, it seems like every additional element in the grid causes fps drop. So the scroll view main widget (whole grid) is updated with every change on the widget inside one element of this grid. Is it possible to update only one part of the main widget and not the whole one?

V V pet., 31. jul. 2020 ob 15:44 je oseba Elliot Garbus <elli...@cox.net> napisala:

Elliot Garbus

unread,
Aug 2, 2020, 9:27:06 AM8/2/20
to kivy-...@googlegroups.com

Profile to code to see where time is being spent.

https://kivy.org/doc/stable/api-kivy.app.html#profiling-with-on-start-and-on-stop

 

Or provide an executable example.  It’s tough to see what is going on just looking at a code sample.

Tomaž - bolha

unread,
Aug 2, 2020, 11:32:54 AM8/2/20
to kivy-...@googlegroups.com
Thanks Elliot,

I will try to prepare an executable example and I will also try the first option. Based on my today's testing I found this...
If I comment part of the code below (texture change) I get FPS which is enough for my application. Is any option of how texture can be changed in a more elegant manner?

V V ned., 2. avg. 2020 ob 15:27 je oseba Elliot Garbus <elli...@cox.net> napisala:
Reply all
Reply to author
Forward
0 new messages