How to update individual widgets inside combo-widget

311 views
Skip to first unread message

Shoumik Das

unread,
Jul 25, 2020, 12:46:26 PM7/25/20
to Kivy users support
Hi,

I have a custom master widget built up of a combination of child widgets such as labels, image-buttons, etc. The display works fine when I hard code the widgets statically inside the Kivy layout. But I would like to create these widgets dynamically from inside Python and would like to set/update the values of each child widget at the time of creation. How can I reference each child widget under the master widget?

Python

class ConnectionDetails(BoxLayout):
pass

class ImageLabel(BoxLayout):
source = StringProperty('images/expand-arrow-48.png')
text = StringProperty('User')

Master Widget

<ConnectionDetails>:
orientation: 'vertical'
size_hint_x: 0.95
size_hint_y: None
pos_hint: {'center_x':0.5}
height: '90dp'
BoxLayout:
orientation: 'horizontal'
canvas.after:
Color:
rgba: 128/255, 128/255, 128/255, 1
Line:
width: dp(1)
rectangle: (*self.pos, self.width, self.height)
ImageLabel:
canvas.after:
Color:
rgba: 128/255, 128/255, 128/255, 1
Line:
width: dp(1)
rectangle: (*self.pos, self.width, self.height)
size_hint_x: 0.8
size_hint_y: None
height: '30dp'
source: 'images/twitter-circled-48.png'
text: 'Twitter'
ImageButton:
size_hint_x: 0.2
size_hint_y: None
height: '30dp'
source: {'normal': 'images/edit-48.png', 'down': 'images/edit-green-48.png'} [self.state]
BoxLayout:
orientation: 'horizontal'
canvas.after:
Color:
rgba: 128/255, 128/255, 128/255, 1
Line:
width: dp(1)
rectangle: (*self.pos, self.width, self.height)
Label:
canvas.after:
Color:
rgba: 128/255, 128/255, 128/255, 1
Line:
width: dp(1)
rectangle: (*self.pos, self.width, self.height)
size_hint_x: 0.8
markup: True
color: 0, 0, 0, 1
text: 'User'
ImageButton:
size_hint_x: 0.2
source: {'normal': 'images/trash-48.png', 'down': 'images/trash-green-48.png'} [self.state]
Label:

Kivy Implementation

<SivaAccountsScreen>:
dropdown2: dropdown2.__self__
name: 'accounts_screen'
canvas.before:
Color:
rgba: 255/255, 255/255, 255/255, 1
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
id: accounts_layout
orientation: 'vertical'
BoxLayout:
id: actionbar_layout2
size_hint_y: None
height: 48
ActionBar:
id: accounts_actionbar
pos_hint: {'top': 1}
background_image: ''
background_color: 195/255, 60/255, 35/255, 1
ActionView:
use_separator: True
ActionPrevious:
title: 'SIVA'
with_previous: False
ActionOverflow:
CustomActionButton:
id: status-button2
important: True
source: {'normal': 'images/communication-48.png', 'down': 'images/communicationgreen-48.png'} [self.state]
on_release: root.to_statusscreen()
CustomActionButton:
id: accounts-button2
important: True
source: {'normal': 'images/keygreen-48.png', 'down': 'images/key-48.png'} [self.state]
on_release: root.to_accountsscreen()
CustomActionButton:
id: bot-button2
important: True
source: {'normal': 'images/bot-48.png', 'down': 'images/botgreen-48.png'} [self.state]
on_release: root.to_botscreen()
CustomActionButton:
id: logout-button2
important: True
source: {'normal': 'images/shutdown-48.png', 'down': 'images/shutdowngreen-48.png'} [self.state]
on_release: root.to_logout()
BoxLayout:
id: accounts_display
orientation: 'vertical'
BoxLayout:
id: accounts_label
orientation: 'horizontal'
size_hint_y: None
height: 40
BoxLayout:
orientation: 'horizontal'
Image:
keep_ratio: True
source: 'images/key-48.png'
Label:
text: 'Accts'
markup: True
color: 0, 0, 0, 1
ImageLabelButtonTop:
# conflict in the on_release defined for ImageLabelButton and this instance.
id: parent_button2
source: 'images/twitter-circled-48.png'
text: 'Twitter'
on_release:
# root.add_dd_acc()
dropdown2.open(self)
on_parent: dropdown2.dismiss()
size_hint_y: None
height: '40dp'
DropDown:
id: dropdown2
on_select:
# args is a reserved keyword which returns only two values: object alias (0) and data (1).
parent_button2.text = args[1][0]
parent_button2.source = args[1][1]
# Invoked inside dropdown
# root - Screen, self - DropDown object
ImageLabelButtonAcc:
source: 'images/twitter-circled-48.png'
text: 'Twitter'
ImageLabelButtonAcc:
source: 'images/linkedin-circled-48.png'
text: 'LinkedIn'
ScrollView:
do_scroll_x: False
do_scroll_y: True
scroll_type: ['bars', 'content']
bar_width: 15
BoxLayout:
id: accounts_content
orientation: 'vertical'
height: self.minimum_height
size_hint_y: None
ConnectionDetails:
ConnectionDetails:
ConnectionDetails:
ConnectionDetails:
ConnectionDetails:
BoxLayout:
id: accounts_add_layout
orientation: 'horizontal'
size_hint_y: None
height: '48dp'
Label:
ImageButton:
id: status_addbtn
size_hint_x: 0.05
size_hint_min_x: 48
source: {'normal': 'images/plus-96.png', 'down': 'images/plusblue-96.png'} [self.state]
on_release: #root.new_account_entry()

Here is a screenshot to explain what I am trying to do:



The drop down above has only 2 values. When I select any 1 value and click on the plus button (bottom-right), It should add a new record/row each of which is a master widget. Depending upon the selection, the image and the text inside the ImageLabel needs to be updated at the time of creation/updation. If the widgets are created dynamically, I cannot use static id values. What option do I have to update the value (image, text) of each child widget?

Please advise.

Thanks

Elliot Garbus

unread,
Jul 25, 2020, 1:33:43 PM7/25/20
to kivy-...@googlegroups.com

Inside the definition of <ConnecitionDetails>: in kv, give each of the objects you need to access an id.

I add an id to each of instances I want to access…  Then use those ids, to access the attributes you want to change:

 

<ConnectionDetails>:

      orientation: 'vertical'

      size_hint_x: 0.95

      size_hint_y: None

      pos_hint: {'center_x':0.5}

      height: '90dp'

      BoxLayout:

            orientation: 'horizontal'

            canvas.after:

                  Color:

                        rgba: 128/255, 128/255, 128/255, 1

                  Line:

                        width: dp(1)

                        rectangle: (*self.pos, self.width, self.height)

            ImageLabel:

                  id: image_label

                  canvas.after:

                        Color:

                              rgba: 128/255, 128/255, 128/255, 1

                        Line:

                              width: dp(1)

                              rectangle: (*self.pos, self.width, self.height)

                  size_hint_x: 0.8

                  size_hint_y: None

                  height: '30dp'

                  source: 'images/twitter-circled-48.png'

                  text: 'Twitter'

            ImageButton:

                  Id: image_button

In Python:

 

class ConnectionDetails(BoxLayout):

    def my_method_name(self):
        self.ids.image_label.text = ‘New text’

Each kivy rule has it’s own ids dict.

--
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/49b6615c-9019-4197-96dc-955de8459809o%40googlegroups.com.

 

Shoumik Das

unread,
Jul 28, 2020, 12:26:59 PM7/28/20
to Kivy users support
Hi Elliot. Sorry for the delayed response. I have not been able to update the internal fields yet. Basically I was planning to define a function in Python which would create the composite widgets by reading the contents of a file but the problem is that I would need the function to run every time the screen is loaded. Is there an on load or on start attribute or method in Kivy which I can invoke to ensure that my function gets loaded every time a particular screen is loaded/visited?

On  different note, I did manage to get the delete button working for each composite widget so that a particular (selected) widget can be deleted from the screen. This is what I did:

Python

class ConnectionDetails(BoxLayout):
def delete_social_account(self):
print(self) # self - ConnectionDetails object
App.get_running_app().root.get_screen('accounts_screen').ids.accounts_content.remove_widget(self)

Kivy

<SivaAccountsScreen>:
dropdown2: dropdown2.__self__
accounts_content: accounts_content.__self__
on_release: #root.new_status_entry()

The explicit accounts_content: accounts_content.__self__ statement was required to avoid a weakref error I was getting without it: AttributeError: 'super' object has no attribute '__getattr__'.

If you can kindly advise on the on_load/startup function for each screen load, I will try to edit the values of each child widget.

Thanks as always for your valuable advice.

To unsubscribe from this group and stop receiving emails from it, send an email to kivy-...@googlegroups.com.

Elliot Garbus

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

Screens have 2 events that can be used to ensure a method is run when the screen is entered.

on_enter, and on_pre_enter.  https://kivy.org/doc/stable/api-kivy.uix.screenmanager.html?highlight=screenmanager#kivy.uix.screenmanager.Screen

 

on_pre_enter: is fired when the screen transition animation is starting.  If you are going to be making changes on the screen, this is the event to use.

on_enter: is fired when the animation is complete.

The drop down above has only 2 values. When I select any 1 value and click on the plus button (bottom-right), It should add a new record/row each of which is a master widget. Depending upon the selection, the image and the text inside the ImageLabel needs to be updated at the time of creation/updation. If the widgets are created dynamically, I cannot use static id values. What option do I have to update the value (image, text) of each child widget?

 

Please advise.

 

Thanks

--
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-...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/49b6615c-9019-4197-96dc-955de8459809o%40googlegroups.com.

 

--
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/066f1ced-69d3-4ca7-ac80-96e30f2fb95bo%40googlegroups.com.

 

Shoumik Das

unread,
Aug 1, 2020, 2:09:14 PM8/1/20
to Kivy users support
Hi Elliot, I have come close but not quite successful yet. I am able to generate the widgets after reading a file but I am not able to access the child widgets (text label and image label) inside as they are in two different classes. The function being called in the on_pre_enter method is defined in a different class. Hence, I am not able to use self the way you advised me to.

Python

class ConnectionDetails(BoxLayout):
def delete_social_account(self):
print(self) # self - ConnectionDetails object
App.get_running_app().root.get_screen('accounts_screen').ids.accounts_content.remove_widget(self)


class SivaAccountsScreen(Screen):
def to_statusscreen(self):
App.get_running_app().root.current='status_screen'
App.get_running_app().root.transition.direction='right'

def to_accountsscreen(self):
App.get_running_app().root.current='accounts_screen'
App.get_running_app().root.transition.direction='left'

def to_botscreen(self):
App.get_running_app().root.current='bot_screen'
App.get_running_app().root.transition.direction='left'

def to_logout(self):
App.get_running_app().root.current='login_screen'
App.get_running_app().root.transition.direction='right'

def load_social_accounts(self):
print("on_pre_enter", self) # self - <Screen name='accounts_screen'>
# Clearing default widgets from the Accounts Screen
App.get_running_app().root.get_screen('accounts_screen').ids.accounts_content.clear_widgets()
s=open("social_accounts","r")
for x in s:
# print(x)
# Create child widgets for each record in the flat file
App.get_running_app().root.get_screen('accounts_screen').ids.accounts_content.add_widget(ConnectionDetails())
s.close()

Kivy

<ConnectionDetails>:
orientation: 'vertical'
size_hint_x: 0.95
size_hint_y: None
pos_hint: {'center_x':0.5}
height: '90dp'
BoxLayout:
orientation: 'horizontal'
canvas.after:
Color:
rgba: 128/255, 128/255, 128/255, 1
Line:
width: dp(1)
rectangle: (*self.pos, self.width, self.height)
ImageLabel:
id: cd_image_label
canvas.after:
Color:
rgba: 128/255, 128/255, 128/255, 1
Line:
width: dp(1)
rectangle: (*self.pos, self.width, self.height)
size_hint_x: 0.8
size_hint_y: None
height: '30dp'
source: 'images/twitter-circled-48.png'
text: 'Twitter'
ImageButton:
id: cd_edit_button
size_hint_x: 0.2
size_hint_y: None
height: '30dp'
source: {'normal': 'images/edit-48.png', 'down': 'images/edit-green-48.png'} [self.state]
BoxLayout:
orientation: 'horizontal'
canvas.after:
Color:
rgba: 128/255, 128/255, 128/255, 1
Line:
width: dp(1)
rectangle: (*self.pos, self.width, self.height)
Label:
id: cd_user_text
canvas.after:
Color:
rgba: 128/255, 128/255, 128/255, 1
Line:
width: dp(1)
rectangle: (*self.pos, self.width, self.height)
size_hint_x: 0.8
markup: True
color: 0, 0, 0, 1
text: 'User'
ImageButton:
id: cd_delete_button
size_hint_x: 0.2
source: {'normal': 'images/trash-48.png', 'down': 'images/trash-green-48.png'} [self.state]
on_release: root.delete_social_account()
Label:






<SivaAccountsScreen>:
dropdown2: dropdown2.__self__
accounts_content: accounts_content.__self__
name: 'accounts_screen'
on_pre_enter: root.load_social_accounts()

I am using this statement to dynamically create a ConnectionDetails object/widget from inside the SivaAccountsScreen class:

App.get_running_app().root.get_screen('accounts_screen').ids.accounts_content.add_widget(ConnectionDetails())

What do I put inside the ConnectionDetails() part to update the text and image labels? Please advise. Right now, I am getting the widget created with the default label text and image label.

Thanks in advance

Elliot Garbus

unread,
Aug 1, 2020, 2:45:55 PM8/1/20
to kivy-...@googlegroups.com

Okay there are a few questions here, I’m not sure I understand all of them:

To Access the child widgets and text use the id’s you have created in the widget:

 

cd = ConnectionDetails()

cd.ids.cd_image_label.text = ‘Linkedin’

….add_widget(cd)

 

If you want to pass a set of values into the construction  ConnectionDetails(label_source=’file.png’, lable_text=’sometext’) you would pull them out of the kwargs dict.  Let me know, I can show you a simple example.  I think for your use case, pulling data from a file,  it would be simpler to do as shown above

 

To access another screen from within a screen you can use the screens manager attribute, this will work if both screens are under the same screenmanager:

This: App.get_running_app().root.get_screen('accounts_screen').ids.accounts_content.add_widget(ConnectionDetails())

Simplifies to:

self.manager.get_screen('accounts_screen').ids.accounts_content.add_widget(ConnectionDetails())

It looks like self is the accounts screen, so you can simplify further:

self.ids.accounts_content.add_widget()

 

<SivaAccountsScreen>:

   dropdown2: dropdown2.__self__

   accounts_content: accounts_content.__self__

   name: 'accounts_screen'

   on_pre_enter: root.load_social_accounts()

Here I think you want to use self.load_slocail_accounts()  self refers to the SivaAccountsScreen.

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/05e95d6e-2972-40ee-a983-083ac9b859a1o%40googlegroups.com.

 

Shoumik Das

unread,
Aug 2, 2020, 2:11:02 PM8/2/20
to Kivy users support
Hi Elliot, thanks for your suggestions. They were really helpful. I was wondering if repeatedly creating an object with the same name inside a for loop would throw an error but it did not. It worked fine.

def load_social_accounts(self):
print("on_pre_enter", self) # self - <Screen name='accounts_screen'>
# Clearing default widgets from the Accounts Screen
App.get_running_app().root.get_screen('accounts_screen').ids.accounts_content.clear_widgets()
s=open("social_accounts","r")
for x in s:
# print(x)
# Create child widgets for each record in the flat file
cd = ConnectionDetails()
cd.ids.cd_user_text.text = x
self.manager.get_screen('accounts_screen').ids.accounts_content.add_widget(cd)
#App.get_running_app().root.get_screen('accounts_screen').ids.accounts_content.add_widget(cd)
#App.get_running_app().root.get_screen('accounts_screen').ids.accounts_content.add_widget(ConnectionDetails())
s.close()



This helped to resolve the problem. However, I would like to understand how to use kwargs to pass a set of values into the construction ConnectionDetails(label_source=’file.png’, lable_text=’sometext’).

Can you please show me an example?

I was playing around with **kwargs inside the __init__() function for the ConnectionDetails class but was getting a key error. Kindly help.

Thanks in advance.

Elliot Garbus

unread,
Aug 2, 2020, 3:11:38 PM8/2/20
to kivy-...@googlegroups.com

At the risk of overkill on a hazy Sunday afternoon…

Here are 2 ways to use kwargs in kivy.  The ComboLine class uses the magic of kivy properties. 

 

ManualKW is the ‘traditional’ way us using kwargs.  The keyword arguments are passed in a dict, the keywords you want to use a popped out of the dict (removing them) before the dictionary is passed on to the parent __init__.  You might want to do this if you are initializing values that are not kivy properties. 

 

I found this a useful reference: https://realpython.com/python-kwargs-and-args/

 

 

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty

kv =
"""
<ComboLine>:
    Label:
        text: root.label_1
    Label:
        text: root.label_2
    Button:
        text: root.button

RootBoxLayout:
    orientation: 'vertical'
    ComboLine:
    ComboLine:
        label_1: "I'm initialized in kv"
        label_2: 'me too'
        button: 'Yes'

"""


class RootBoxLayout(BoxLayout):
   
def on_kv_post(self, base_widget):
       
for i in range(5):
           
self.add_widget(ComboLine(label_1=f'Hello {i}', label_2=f'Yo {i + 100}', button='kwargs'))
       
self.add_widget(ManualKW())
       
for i in range(5):
           
self.add_widget(ManualKW(one=f'name {i}', two=f'something {i}', b='Button text'))


class ComboLine(BoxLayout):  # Automagically using kivy properties...
   
label_1 = StringProperty('default 1')
    label_2 = StringProperty(
'default 2')
    button = StringProperty(
'default button')


class ManualKW(ComboLine): # doing in the manual way
   
def __init__(self, **kwargs):
       
self.label_1 = kwargs.pop('one', 'use me if one not defined')
       
self.label_2 = kwargs.pop('two', 'More Nothing')
       
self.button = kwargs.pop('b', '?')
       
super().__init__(**kwargs)


class KwargsExampleApp(App):
   
def build(self):
       
return Builder.load_string(kv)


KwargsExampleApp().run()

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/b62cf91a-3e5d-49a6-823a-a8a3bfa01446o%40googlegroups.com.

 

Shoumik Das

unread,
Aug 3, 2020, 12:27:48 PM8/3/20
to Kivy users support
Thank you for helping me understand the concepts of kwargs. That cleared my doubts. However, as you suggested earlier, for my use case of reading from a flat file, the object method is simpler to understand and implement.

Much appreciated.

Elliot Garbus

unread,
Aug 3, 2020, 12:40:06 PM8/3/20
to kivy-...@googlegroups.com

😊

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/1a6a61be-f9e8-4bd0-a42a-26db57ce0d0co%40googlegroups.com.

 

Reply all
Reply to author
Forward
0 new messages