'Store toggle button states in the widget (Kivy)
I have a recycleview that contains BoxLayouts with two buttons, that share a group for each BoxLayout. I would like the buttons to function as the up and down votes buttons on StackOverflow. However, the issue at the moment is that the state of the buttons is not stored in the BoxLayout widget and this results in them showing up multiple times when they shouldn't for widgets that are not on the screen.
There is a similar question here: How can I keep my Kivy ToggleButton state when scrolling using a RecycleView?
But I have not been able to implement the same logic into my code, additionally, if one button is selected, the other cannot be (Just like not being able to upvote and downvote a question on StackOverflow at the same time) and the ability to not have any buttons selected at all.
I have included a minimal example below, where some lines are commented out of the .kv file so that it can still run but still with the issue. (The code is as similar to the question linked above as possible as my attempts have strayed away and haven't worked)
Any help will be greatly appreciated, I don't think that I'm miles off and I will continue to try different things
main.py
from random import randint
from kivy.app import App
from kivy.uix.label import Label
from kivy.clock import Clock
from kivy.graphics import Color, Rectangle
from kivy.uix.recycleview import RecycleView
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty, ListProperty
class NewPostGrid(BoxLayout):
votes_ = StringProperty()
message_id_ = StringProperty()
text_ = StringProperty()
group_ = StringProperty()
_size = ListProperty()
vote_state_up = StringProperty()
vote_state_down = StringProperty()
#def update_message_size(self, message_id, texture_size): # Updates the '_size' value in rv_data_list based on the texture_size
# App.get_running_app().rv_data_list[int(message_id)] = {**App.get_running_app().rv_data_list[int(message_id)], '_size':[0, texture_size[1]]}
# #print(App.get_running_app().rv_data_list)
class SizeLabel(Label):
def __init__(self, *args, **kwargs):
Label.__init__(self, *args, **kwargs)
Clock.schedule_once(lambda dt: self.initialize_widget(), 0.002)
def initialize_widget(self):
self.canvas.before.add(Color(1, 1, 1, 0))
self.canvas.before.add(Rectangle(pos=self.pos, size=self.size))
self.text_size = self.size
self.text = ''
class RV(RecycleView):
message_id_num = 0
def __init__(self, **kwargs):
super().__init__(**kwargs)
App.get_running_app().rv_data_list = []
def generate_post(self): # This is only to test posts with different line height
e = ['Test post ID: ', str(self.message_id_num)]
for i in range(randint(1, 8)): e.append('\n')
e.append('end of post')
return "".join(e)
def add(self):
l = len(App.get_running_app().rv_data_list)
text = self.generate_post()
sl = SizeLabel(text=text)
sl.texture_update()
print(sl.text)
App.get_running_app().rv_data_list.extend([{'message_id_': str(self.message_id_num),
'text_': text, '_size': sl.texture_size,
'group_': str(self.message_id_num), 'votes_': str(20), 'vote_state_up': 'down', 'vote_state_down': 'normal'}])
self.message_id_num = self.message_id_num + 1
def adjust_vote_state(self, id_):
for d in self.data:
if d['text_'] == id_.text_:
d['state'] == id_.ids.button_up.state
id_.state = id_.ids.button_up.state
class DemoApp(App):
# One post format = {'message_id':0, 'text':'post_test_here','_size':[0,0], '_group':str(0), '_score':20}
# Text fromat string = [font=Nunito-Bold.ttf][color=161616]Someone:[/color][/font]\n
rv_data_list = ListProperty()
def up_vote(self, button, mode): # Not part of the problem
if button.state == 'down':
if mode == 'all':
print("+1 upvote for message index:" + str(button.parent.parent.message_id) + ' in all posts')
else:
print("+1 upvote for message index:" + str(button.parent.parent.message_id) + ' in top posts')
def down_vote(self, button, mode): # Not part of the problem
if button.state == 'down':
if mode == 'all':
print("-1 upvote for message index:" + str(button.parent.parent.message_id) + ' in all posts')
else:
print("-1 upvote for message index:" + str(button.parent.parent.message_id) + ' in top posts')
if __name__ == '__main__':
DemoApp().run()
demo.kv
<NewPostGrid>:
spacing: "6dp"
message_id: root.message_id_
BoxLayout:
id: voting_menu
orientation: "vertical"
spacing: "2dp"
size_hint: .2, None
height: label.height
ToggleButton:
id: button_up
#on_state: app.up_vote(self, 'all')
group: root.group_
#state: root.vote_state_up
text: "UP"
color: (1,1,1,1) if self.state=='normal' else (.8,0,0,1)
font_size: "10dp"
size_hint: 1, .3
background_color: .2, .2, .2, 0
#on_release: app.root.ids.rv.adjust_vote_state(root)
canvas.before:
Color:
rgba: (.1,.1,.1,1)
RoundedRectangle:
pos: self.pos
size: self.size
radius: [6,]
canvas:
Color:
rgba: (.2,.2,.2,1)
Line:
width: 1.4
rounded_rectangle:(self.x,self.y,self.width,self.height, 5)
Label:
id: vote_count
text: root.votes_
size_hint: 1, .4
multiline: False
ToggleButton:
id: button_down
#on_state: app.down_vote(self, 'all')
group: root.group_
#state: root.vote_state_down
text: "DOWN"
color: (1,1,1,1) if self.state=='normal' else (.8,0,0,1)
font_size: "10dp"
size_hint: 1, .3
background_color: .2, .2, .2, 0
canvas.before:
Color:
rgba: (.1,.1,.1,1)
RoundedRectangle:
pos: self.pos
size: self.size
radius: [6,]
canvas:
Color:
rgba: (.2,.2,.2,1)
Line:
width: 1.4
rounded_rectangle:(self.x,self.y,self.width,self.height, 5)
Label:
id: label
text: root.text_
padding: "10dp", "12dp"
size_hint: .9, None
height: self.texture_size[1]
font_size: "12dp"
text_size: self.width, None
color: 0,0,0,1
multiline: True
markup: True
#on_texture_size: root.update_message_size(root.message_id, self.texture_size)
pos: self.x, self.y
canvas.before:
Color:
rgba: (0.8, 0.8, 0.8, 1)
RoundedRectangle:
size: self.texture_size
radius: [5, 5, 5, 5]
pos: self.x, self.y
canvas:
Color:
rgba:0.8,0,0,1
Line:
width: 1.4
rounded_rectangle:(self.x,self.y,self.width,self.height, 5)
BoxLayout:
orientation: 'vertical'
Button:
size_hint_y: None
height: 48
text: 'Add widget to RV list'
on_release: rv.add()
RV: # A Reycleview
id: rv
viewclass: 'NewPostGrid' # The view class is TwoButtons, defined above.
data: app.rv_data_list # the data is a list of dicts defined below in the RV class.
scroll_type: ['bars', 'content']
bar_width: 2
RecycleBoxLayout:
# This layout is used to hold the Recycle widgets
# default_size: None, dp(48) # This sets the height of the BoxLayout that holds a TwoButtons instance.
key_size: '_size' # key for the datalist
default_size_hint: 1, None
size_hint_y: None
spacing: '20dp'
height: self.minimum_height # To scroll you need to set the layout height.
orientation: 'vertical'
padding: ['10dp', '20dp']
Solution 1:[1]
I think adjusting your demo.kv to modify the data when a ToggleButton state is changed will work. Here is a modified version of your demo.kv that I think will work:
<NewPostGrid>:
spacing: "6dp"
message_id: root.message_id_
BoxLayout:
id: voting_menu
orientation: "vertical"
spacing: "2dp"
size_hint: .2, None
height: label.height
ToggleButton:
id: button_up
state: root.vote_state_up
#on_state: app.up_vote(self, 'all')
on_state:
root.vote_state_up = self.state
app.rv_data_list[int(root.message_id_)]['vote_state_up'] = self.state
group: root.group_
#state: root.vote_state_up
text: "UP"
color: (1,1,1,1) if self.state=='normal' else (.8,0,0,1)
font_size: "10dp"
size_hint: 1, .3
background_color: .2, .2, .2, 0
#on_release: app.root.ids.rv.adjust_vote_state(root)
canvas.before:
Color:
rgba: (.1,.1,.1,1)
RoundedRectangle:
pos: self.pos
size: self.size
radius: [6,]
canvas:
Color:
rgba: (.2,.2,.2,1)
Line:
width: 1.4
rounded_rectangle:(self.x,self.y,self.width,self.height, 5)
Label:
id: vote_count
# text: root.votes_
text: button_up.state + ' ' + button_down.state
size_hint: 1, .4
multiline: False
ToggleButton:
id: button_down
state: root.vote_state_down
#on_state: app.down_vote(self, 'all')
on_state:
root.vote_state_down = self.state
app.rv_data_list[int(root.message_id_)]['vote_state_down'] = self.state
group: root.group_
#state: root.vote_state_down
text: "DOWN"
color: (1,1,1,1) if self.state=='normal' else (.8,0,0,1)
font_size: "10dp"
size_hint: 1, .3
background_color: .2, .2, .2, 0
canvas.before:
Color:
rgba: (.1,.1,.1,1)
RoundedRectangle:
pos: self.pos
size: self.size
radius: [6,]
canvas:
Color:
rgba: (.2,.2,.2,1)
Line:
width: 1.4
rounded_rectangle:(self.x,self.y,self.width,self.height, 5)
Label:
id: label
text: root.text_
padding: "10dp", "12dp"
size_hint: .9, None
height: self.texture_size[1]
font_size: "12dp"
text_size: self.width, None
color: 0,0,0,1
multiline: True
markup: True
#on_texture_size: root.update_message_size(root.message_id, self.texture_size)
pos: self.x, self.y
canvas.before:
Color:
rgba: (0.8, 0.8, 0.8, 1)
RoundedRectangle:
size: self.texture_size
radius: [5, 5, 5, 5]
pos: self.x, self.y
canvas:
Color:
rgba:0.8,0,0,1
Line:
width: 1.4
rounded_rectangle:(self.x,self.y,self.width,self.height, 5)
BoxLayout:
orientation: 'vertical'
Button:
size_hint_y: None
height: 48
text: 'Add widget to RV list'
on_release: rv.add()
RV: # A Reycleview
id: rv
viewclass: 'NewPostGrid' # The view class is TwoButtons, defined above.
data: app.rv_data_list # the data is a list of dicts defined below in the RV class.
scroll_type: ['bars', 'content']
bar_width: 2
RecycleBoxLayout:
# This layout is used to hold the Recycle widgets
# default_size: None, dp(48) # This sets the height of the BoxLayout that holds a TwoButtons instance.
key_size: '_size' # key for the datalist
default_size_hint: 1, None
size_hint_y: None
spacing: '20dp'
height: self.minimum_height # To scroll you need to set the layout height.
orientation: 'vertical'
padding: ['10dp', '20dp']
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | John Anderson |
