Dropdown partially visible in Kivy (Python) - python

I am trying to create a generic menu bar in Kivy (GUI for Python) and I am having trouble with dropdown menus. They only partially appear and I don't know why (see under Submenu 1):
Here is my code if you want to check it :
#!/usr/bin/env python3
from kivy.app import App
#from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.dropdown import DropDown
from kivy.properties import ListProperty, ObjectProperty
#from kivy.uix.actionbar import ActionBar, ActionView, ActionGroup, ActionButton
class MenuItem(ButtonBehavior, Label):
'''Background color, in the format (r, g, b, a).'''
background_color_normal = ListProperty([0.2, 0.2, 0.2, 1])
background_color_down = ListProperty([0.3, 0.3, 0.3, 1])
background_color = ListProperty()
separator_color = ListProperty([0.8, 0.8, 0.8, 1])
pass
class MenuSubmenu(MenuItem):
# The list of submenu items in dropdown menu
submenu = ObjectProperty(None)
def add_widget(self, submenu, **kwargs):
if isinstance(submenu, MenuDropDown):
self.submenu = submenu
super().add_widget(submenu, **kwargs)
def on_release(self, **kwargs):
super().on_release(**kwargs)
self.submenu.open(self)
class MenuDropDown(DropDown):
pass
class MenuButton(MenuItem):
pass
class MenuBar(BoxLayout):
'''Background color, in the format (r, g, b, a).'''
background_color = ListProperty([0.2, 0.2, 0.2, 1])
separator_color = ListProperty([0.8, 0.8, 0.8, 1])
def __init__(self, **kwargs):
self.itemsList = []
super().__init__(**kwargs)
def add_widget(self, item, index=0):
if not isinstance(item, MenuItem):
raise TypeError("MenuBar accepts only MenuItem widgets")
super().add_widget(item, index)
if index == 0:
index = len(self.itemsList)
self.itemsList.insert(index, item)
class MenuApp(App):
def button(self, nb):
print("Button", nb, "triggered")
if __name__ == '__main__':
MenuApp().run()
And here the kv file :
#:kivy 1.0.9
<MenuItem>:
background_color: self.background_color_down if self.state=='down' else self.background_color_normal
color: (1,1,1,1)
canvas.before:
Color:
rgba: self.background_color
Rectangle:
pos: self.pos
size: self.size
canvas.after:
Color:
rgba: self.separator_color
Line:
rectangle: self.x,self.y,self.width,self.height
<MenuBar>:
size_hint_y: None
height: 40
canvas.before:
Color:
rgba: self.background_color
Rectangle:
pos: self.pos
size: self.size
canvas.after:
Color:
rgba: self.separator_color
Line:
points: self.x, self.y, self.x + self.width, self.y
BoxLayout:
orientation: "vertical"
MenuBar:
MenuSubmenu:
text: "Submenu 1"
MenuDropDown:
Button:
text: "Button 11"
on_release: app.button(11)
Button:
text: "Button 12"
on_release: app.button(12)
Button:
text: "Button 13"
on_release: app.button(13)
MenuButton:
text: "Button 2"
on_release: app.button(2)
MenuButton:
text: "Button 3"
on_release: app.button(3)
Button:
text: "Nothing"
background_color: 0.4, 0.4, 0.4, 1
background_normal: ""
It would be great if you know what happen or if you could redirect me to some place more appropriate to find an answer.

As warned by #inclement I was not using dropdown properly. You should not add directly a dropdown widget in a kv file.
So instead I looked at how was working the actionbar in kivy (explanation here and source code here) and I updated in consequence my menu bar.
Here is then my python code :
#!/usr/bin/env python3
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.dropdown import DropDown
from kivy.uix.spinner import Spinner
from kivy.properties import ListProperty, ObjectProperty,\
StringProperty, BooleanProperty, NumericProperty
#from kivy.uix.actionbar import ActionBar, ActionView, ActionGroup, ActionButton
class MenuItem(Widget):
'''Background color, in the format (r, g, b, a).'''
background_color_normal = ListProperty([0.2, 0.2, 0.2, 1])
background_color_down = ListProperty([0.3, 0.3, 0.3, 1])
background_color = ListProperty([])
separator_color = ListProperty([0.8, 0.8, 0.8, 1])
text_color = ListProperty([1,1,1,1])
inside_group = BooleanProperty(False)
pass
class MenuSubmenu(MenuItem, Spinner):
triangle = ListProperty()
def __init__(self, **kwargs):
self.list_menu_item = []
super().__init__(**kwargs)
self.dropdown_cls = MenuDropDown
def add_widget(self, item):
self.list_menu_item.append(item)
self.show_submenu()
def show_submenu(self):
self.clear_widgets()
for item in self.list_menu_item:
item.inside_group = True
self._dropdown.add_widget(item)
def _build_dropdown(self, *largs):
if self._dropdown:
self._dropdown.unbind(on_dismiss=self._toggle_dropdown)
self._dropdown.dismiss()
self._dropdown = None
self._dropdown = self.dropdown_cls()
self._dropdown.bind(on_dismiss=self._toggle_dropdown)
def _update_dropdown(self, *largs):
pass
def _toggle_dropdown(self, *largs):
self.is_open = not self.is_open
ddn = self._dropdown
ddn.size_hint_x = None
if not ddn.container:
return
children = ddn.container.children
if children:
ddn.width = max(self.width, max(c.width for c in children))
else:
ddn.width = self.width
for item in children:
item.size_hint_y = None
item.height = max([self.height, 48])
def clear_widgets(self):
self._dropdown.clear_widgets()
class MenuDropDown(DropDown):
pass
class MenuButton(MenuItem,Button):
icon = StringProperty(None, allownone=True)
pass
class MenuEmptySpace(MenuItem):
pass
class MenuBar(BoxLayout):
'''Background color, in the format (r, g, b, a).'''
background_color = ListProperty([0.2, 0.2, 0.2, 1])
separator_color = ListProperty([0.8, 0.8, 0.8, 1])
def __init__(self, **kwargs):
self.itemsList = []
super().__init__(**kwargs)
def add_widget(self, item, index=0):
if not isinstance(item, MenuItem):
raise TypeError("MenuBar accepts only MenuItem widgets")
super().add_widget(item, index)
if index == 0:
index = len(self.itemsList)
self.itemsList.insert(index, item)
if __name__ == '__main__':
class MenuApp(App):
def button(self, nb):
print("Button", nb, "triggered")
MenuApp().run()
And here is the corresponding file menu.kv :
#:kivy 1.0.9
<MenuButton>:
size_hint_x: None if not root.inside_group else 1
width: self.texture_size[0] + 32
Image:
allow_stretch: True
opacity: 1 if root.icon else 0
source: root.icon
pos: root.x + 4, root.y + 4
size: root.width - 8, root.height - 8
<MenuSubmenu>:
size_hint_x: None
width: self.texture_size[0] + 32
triangle: (self.right-14, self.y+7, self.right-7, self.y+7, self.right-7, self.y+14)
canvas.after:
Color:
rgba: self.separator_color
Triangle:
points: self.triangle
<MenuButton,MenuSubmenu>:
background_normal: ""
background_down: ""
background_color: self.background_color_down if self.state=='down' else self.background_color_normal
color: self.text_color
<MenuEmptySpace>:
size_hint_x: 1
<MenuItem>:
background_color: self.background_color_normal
canvas.before:
Color:
rgba: self.background_color
Rectangle:
pos: self.pos
size: self.size
canvas.after:
Color:
rgba: self.separator_color
Line:
rectangle: self.x,self.y,self.width,self.height
<MenuBar>:
size_hint_y: None
height: 48
canvas.before:
Color:
rgba: self.background_color
Rectangle:
pos: self.pos
size: self.size
canvas.after:
Color:
rgba: self.separator_color
Line:
rectangle: self.x, self.y, self.width, self.height
BoxLayout:
orientation: "vertical"
MenuBar:
MenuButton:
icon: "/home/matthieu/internships/singapore/drawings/joytube_color_small.png"
width: 100
MenuButton:
text: "Button 1"
on_release: app.button(1)
MenuSubmenu:
text: "Submenu 2"
MenuButton:
text: "Button 21"
on_release: app.button(21)
MenuButton:
text: "Button 22"
on_release: app.button(22)
MenuButton:
text: "Button 23"
on_release: app.button(23)
MenuEmptySpace:
MenuSubmenu:
text: "Submenu 3"
MenuButton:
text: "Button 31"
on_release: app.button(31)
Button:
text: "Nothing"
background_color: 0.4, 0.4, 0.4, 1
background_normal: ""
MenuBar:
height: 30
Hope it will help other persons wanting to make menu bars.

Related

Kivy recycleview spacing issue when on android

So I have managed to get widgets of varying height into a recycleview and items can be added, this looked perfect on my PC but was very wrong when running on android. I re-built the app with buildozer to make sure it wasn't something to do with that, I put together the demo program seen below and ran it on both platforms, and experienced the same result. I have used the kivy inspector and not been able to see any values that are not what I didn't manually program. And unless I have missed one I have used dp(X) or 'Xdp' whenever needed.
Any help or tips will be apreciated, thank you :)
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, NumericProperty
class NewPostGrid(BoxLayout):
votes_ = StringProperty()
message_id_ = StringProperty()
text_ = StringProperty()
group_ = StringProperty()
_size = ListProperty()
class SizeLabel(Label):
pass
class RV(RecycleView):
distance_to_top = NumericProperty()
scrollable_distance = NumericProperty()
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(App.get_running_app().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(App.get_running_app().message_id_num),
'text_': text,
'_size': sl.texture_size,
'group_': str(App.get_running_app().message_id_num),
'votes_': str(20)}])
App.get_running_app().message_id_num = App.get_running_app().message_id_num + 1
def on_scrollable_distance(self, *args):
if self.scroll_y > 0:
self.scroll_y = (self.scrollable_distance - self.distance_to_top) / self.scrollable_distance
def on_scroll_y(self, *args):
self.distance_to_top = (1 - self.scroll_y) * self.scrollable_distance
#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
message_id_num = 0
rv_data_list = ListProperty()
pending_data = ListProperty()
def build(self):
#Clock.schedule_interval(self.add_log, .01)
Clock.schedule_interval(self.flush_pending_data, .250)
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')
def flush_pending_data(self, *args):
if self.pending_data:
pending_data, self.pending_data = self.pending_data, []
self.rv_data_list.extend(pending_data)
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_log(self, dt):
for i in range(10):
text = self.generate_post()
sl = SizeLabel(text=text)
sl.texture_update()
self.pending_data.append({'message_id_': str(self.message_id_num),
'text_': text,
'_size': sl.texture_size,
'group_': str(self.message_id_num),
'votes_': str(20)})
self.message_id_num = self.message_id_num + 1
if __name__ == '__main__':
DemoApp().run()
demo.kv
<SizeLabel>:
padding: "10dp", "12dp"
size_hint: 0.9, None
height: self.texture_size[1]
font_size: "12dp"
text_size: self.width, None
color: 0,0,0,1
multiline: True
markup: True
<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'
BoxLayout:
size_hint: 1, .1
orientation: 'horizontal'
Button:
text: 'Add widget to RV list'
on_release: rv.add()
ToggleButton:
id: active
text: 'active'
on_state: app.add_log(self)
RV:
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']
#on_scroll_y: app.fill_data(self, box)
scrollable_distance: box.height - self.height
bar_width: dp(2)
RecycleBoxLayout:
id: box
# 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'
default_size_hint: 1, None
size_hint_y: None
spacing: '16dp'
height: self.minimum_height # To scroll you need to set the layout height.
orientation: 'vertical'
padding: ['10dp', '20dp']
Image of app running on windows
Video of app running on android
Video of full app running (The code for the recycleview is identical)

Unable to access a particular id from kivy file to my python ERROR "AttributeError: 'super' object has no attribute '__getattr__'"

The main code <main.py>
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
from kivy.uix.popup import Popup
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.clock import Clock
from kivy.uix.behaviors import ButtonBehavior
class Background(Widget):
cloud_texture = ObjectProperty(None)
tree1_texture = ObjectProperty(None)
tree2_texture = ObjectProperty(None)
grass_texture = ObjectProperty(None)
bush_texture = ObjectProperty(None)
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.cloud_texture = Image(source="cloud.png").texture
self.cloud_texture.wrap = 'repeat'
self.cloud_texture.uvsize = (Window.width/ self.cloud_texture.width, -1)
self.tree1_texture = Image(source="tree_1.png").texture
self.tree1_texture.wrap = 'repeat'
self.tree1_texture.uvsize = (Window.width/ self.tree1_texture.width, -1)
self.tree2_texture = Image(source="tree_2.png").texture
self.tree2_texture.wrap = 'repeat'
self.tree2_texture.uvsize = (Window.width/ self.tree2_texture.width, -1)
self.grass_texture = Image(source="grass.png").texture
self.grass_texture.wrap = 'repeat'
self.grass_texture.uvsize = (Window.width/ self.grass_texture.width, -1)
self.bush_texture = Image(source="bush.png").texture
self.bush_texture.wrap = 'repeat'
self.bush_texture.uvsize = (Window.width/ self.bush_texture.width, -1)
def on_size(self, *args):
self.cloud_texture.uvsize = (self.width / self.cloud_texture.width, -1)
self.tree1_texture.uvsize = (self.width / self.tree1_texture.width, -1)
self.tree2_texture.uvsize = (self.width / self.tree2_texture.width, -1)
self.grass_texture.uvsize = (self.width / self.grass_texture.width, -1)
self.bush_texture.uvsize = (self.width / self.grass_texture.width, -1)
def scroll_textures(self, time_passed):
self.cloud_texture.uvpos = ((self.cloud_texture.uvpos[0] + time_passed/8.0)% Window.width , self.cloud_texture.uvpos[1])
self.tree1_texture.uvpos = ((self.tree1_texture.uvpos[0] + time_passed/2.0)% Window.width , self.tree1_texture.uvpos[1])
self.tree2_texture.uvpos = ((self.tree2_texture.uvpos[0] + time_passed/2.0)% Window.width , self.tree2_texture.uvpos[1])
self.grass_texture.uvpos = ((self.grass_texture.uvpos[0] + time_passed/8.0)% Window.width , self.grass_texture.uvpos[1])
self.bush_texture.uvpos = ((self.bush_texture.uvpos[0] + time_passed/8.0)% Window.width , self.bush_texture.uvpos[1])
texture = self.property('cloud_texture')
texture.dispatch(self)
texture = self.property('tree1_texture')
texture.dispatch(self)
texture = self.property('tree2_texture')
texture.dispatch(self)
texture = self.property('grass_texture')
texture.dispatch(self)
texture = self.property('bush_texture')
texture.dispatch(self)
print("scroll")
pass
def on_stop(self):
pass
class StartWindow(Screen):
def __init__(self, **kwargs):
super(StartWindow,self).__init__(**kwargs)
pass
class GameWindow(Screen):
def __init__(self, **kwargs):
super(GameWindow,self).__init__(**kwargs)
pass
class OptionsWindow(Screen):
def __init__(self, **kwargs):
super(OptionsWindow,self).__init__(**kwargs)
pass
class WindowManager(ScreenManager):
pass
class MainApp(App):
def __getattr__(self, attr):
return super().__getattr__(attr)
def on_start(self):
Clock.schedule_interval(self.root.ids.background.scroll_textures,1/60.)
pass
MainApp().run()
The line "Clock.schedule_interval(g.ids.background.scroll_textures,1/60.)" is not working. I am not understanding the fundamental way to call the id from the kv file.
main.kv file
WindowManager:
StartWindow:
GameWindow:
OptionsWindow:
<StartWindow>:
name: "start"
id: startwindow
FloatLayout:
cols:4
Image:
source: 'Flying_whale.png'
allow_stretch: True
keep_ratio: False
Button:
pos_hint:{"x":0,"y":0.50}
background_color: 0,0,0,0
color: 0,0,0,1
size_hint: 0.4, 0.1
font_size: (root.width**2 + root.height**2) / 17**3.3
text: "Start!"
id: set_event
on_press:
root.manager.transition.direction = "down"
root.on_start()
Button:
pos_hint:{"x":0.15,"y":0.25}
background_color: 0,0,0,0
color: 0,0,0,1
size_hint: 0.4, 0.1
font_size: (root.width**2 + root.height**2) / 17**3.5
text: "Options"
on_release:
root.manager.transition.direction = "right"
app.root.current = "options"
Button:
pos_hint:{"x":0.15,"y":0.10}
background_color: 0,0,0,0
color: 0,0,0,1
size_hint: 0.4, 0.1
font_size: (root.width**2 + root.height**2) / 17**3.5
text: "Exit"
<GameWindow>
name:"game"
id: gamewindow
FloatLayout:
Background:
id: background
canvas.before:
Rectangle:
size: self.size
pos: self.pos
source: "sky.png"
Rectangle:
size: self.width, 150
pos: self.pos[0],self.pos[1] + self.height - 138
texture: self.cloud_texture
Rectangle:
size: self.width, 160
pos: self.pos[0],self.pos[1]
texture: self.grass_texture
Rectangle:
size: self.width, 100
pos: self.pos[0]-10,self.pos[1] + 145
texture: self.tree1_texture
Rectangle:
size: self.width, 100
pos: self.pos[0]+30,self.pos[1] + 160
texture: self.tree2_texture
Rectangle:
size: self.width, 20
pos: self.pos[0],self.pos[1] + 155
texture: self.bush_texture
Label:
id: score
size_hint_y : None
height : 96
text: "0"
font_size: 40
Button:
pos_hint:{"x":0,"y":0.25}
background_color: 0,0,0,0
color: 0,0,0,1
size_hint: 0.4, 0.1
font_size: (root.width**2 + root.height**2) / 17**3.3
text: "Back"
id: set_event
on_release:
<OptionsWindow>:
name: "options"
FloatLayout:
cols:4
Image:
source: 'Flying_whale.png'
allow_stretch: True
keep_ratio: False
Button:
pos_hint:{"x":0.15,"y":0.20}
background_color: 0,0,0,0
color: 0,0,0,1
size_hint: 0.4, 0.1
font_size: (root.width**2 + root.height**2) / 17**3.5
text: "Back"
on_release:
root.manager.transition.direction = "left"
app.root.current = "start"
Button:
pos_hint:{"x":0.15,"y":0.35}
background_color: 0,0,0,0
color: 1,1,1,1
size_hint: 0.4, 0.1
font_size: (root.width**2 + root.height**2) / 17**3.5
text: "Mode : Night / Day"
The error is given by:
AttributeError: 'super' object has no attribute 'getattr'
I know it is a simple understanding problem but I am not able to work around it. Thank you
The problem is that the id: background is defined in the GameWindow rule in the kv. According to the documentation:
When the kv file is processed, weakrefs to all the widgets tagged with
ids are added to the root widget’s ids dictionary.
While the term root widget is over used and has different meanings depending on context, here it means the rule that contains the id. So the background id will only appear in the ids of the GamWindow widget.
Knowing that, your questionable line of code can be replaced with:
Clock.schedule_interval(self.root.get_screen('game').ids.background.scroll_textures,1/60.)
The above code uses get_screen() to access the GameWindow.

Using entries from other kivy classes

I am a beginner in Language and I am trying to make a simple guessing game and I would like to know how I use the data entered in the Players Class in the Start_p1 Class. I want the name typed in the TextInput of the Players Class, to appear in the Label Text: of the Start_p1 Class.
Arquivo .py:
import kivy
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.textinput import TextInput
from kivy.properties import StringProperty
class Home(Screen):
def next(self):
self.manager.current = "players"
class Players(Screen):
def start(self):
self.manager.current = 'st1'
class Start_p1(Screen):
def runn(self):
self.manager.current = 'st2'
class Start_p2(Screen):
def back(self):
self.manager.current = 'st1'
class Finish(Screen): pass
class Myapp(App):
sm = None
def build(self):
self.sm = ScreenManager()
self.sm.add_widget(Home(name = 'home'))
self.sm.add_widget(Players(name = 'players'))
self.sm.add_widget(Start_p1(name = 'st1'))
self.sm.add_widget(Start_p2(name = 'st2'))
self.sm.add_widget(Finish(name = 'finish'))
self.sm.current = 'home'
return self.sm
if __name__ == '__main__':
Myapp().run()
Arquivo.kv
#: import utils kivy.utils
<Home>:
FloatLayout:
canvas.before:
Color:
rgb: utils.get_color_from_hex('#2169af')
Rectangle:
pos: self.pos
size: self.size
Button:
text: 'Iniciar'
pos_hint: {'center_x': .5, 'center_y': .5}
size_hint: 0.2, .1
background_color: utils.get_color_from_hex('#13447d')
on_release: root.next()
Button:
text: 'Configurações'
pos_hint: {'center_x': .5, 'center_y': .4}
size_hint: 0.2, .1
background_color: utils.get_color_from_hex('#13447d')
<Players>:
FloatLayout:
canvas.before:
Color:
rgb: utils.get_color_from_hex('#2169af')
Rectangle:
pos: self.pos
size: self.size
Label:
pos: 0, 270
text: 'JOGADORES'
Label:
pos: 0, 150
text: 'Informe o nome do 1° Jogador:'
TextInput:
id: txtt
pos: 270, 400
size: 250, 30
multiline: False
Label:
pos: 0, -20
text: 'Informe o nome do 2° Jogador:'
TextInput:
id: txt
pos: 270, 230
size: 250, 30
multiline: False
Button:
text: 'Iniciar'
pos_hint: {'center_x': .5, 'center_y': .2}
size_hint: 0.2, .1
background_color: utils.get_color_from_hex('#13447d')
on_release: root.start()
<Start_p1>:
Players:
id: players
FloatLayout:
canvas.before:
Color:
rgb: utils.get_color_from_hex('#2169af')
Rectangle:
pos: self.pos
size: self.size
Label:
text:
That is actually a bit tricky. One would expect that you can use:
Label:
text: app.sm.get_screen('players').ids.txt.text
That will technically work, but it won't set up the automatic update if the text of the TextInput changes. A careful reading the documentation explains why, and suggests a work around. Using the suggestion from the documentation, you can do something like this:
Label:
players_screen: app.sm.get_screen('players')
text: self.players_screen.ids.txt.text
This produces the desired result with automatic updates.

Image display blank upon being updated

I am using a timer to update the image on the UI after every second. After starting the UI the first image is being displayed. After the timer thread kicks in to display the next image, the widget is displayed blank (i cannot see the image). I can see the image if i start the index from a different value. I do not have an issue with the images. I am unable to update the image on the UI screen. Can anyone help me figure out the issue?
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.image import AsyncImage
from kivy.properties import StringProperty, ListProperty, ObjectProperty
import json, timer
import threading
data = json.load(open('demotest.json'))
Builder.load_file('dv_demo.kv')
class DVDemo(Widget):
message = StringProperty()
image = StringProperty('')
sms = ListProperty()
idx = 0
def __init__(self, **kwargs):
super(DVDemo, self).__init__(**kwargs)
self.update_data_index(self.idx)
def schedule_screens(self):
self.idx = self.idx + 1
if self.idx >= len(data):
time.sleep(500)
temp = threading.Thread(target=self.update_data_index, args=(self.idx,))
threading.Timer(1, temp.start).start()
def update_data_index(self, idx):
# self.image = data[idx]['picture_url']
self.message = data[idx]['message']
self.sms = data[idx]['sms']
message_layout = self.ids.message_panel
message_btn = Button(text=self.message, background_color=[0, 1, 1, 1])
message_layout.add_widget(message_btn)
sms_layout = self.ids.sms_panel
sms_layout.clear_widgets()
for sms_msg in self.sms:
sms_btn = Button(text=sms_msg['sms'], background_color=[0, 0, 2, 1])
sms_layout.add_widget(sms_btn)
self.ids.image_object.source = data[idx]['picture_url']
self.schedule_screens()
class DVDemoApp(App):
def build(self):
self.title = 'DV Demo App'
return DVDemo()
if __name__ == "__main__":
DVDemoApp().run()
<ColoredLabel#Label>:
text_size: self.size
halign: 'left'
valign: 'top'
padding: 4, 4
bold: True
color: (.6, .6, .6, 1)
canvas.before:
Color:
rgb: (.9, .9, .9)
Rectangle:
pos: self.pos
size: self.width - sp(2), self.height - sp(2)
<DVDemo>:
FloatLayout:
id: demo_panels
size: root.width - 50, root.height - 50
pos: root.x + 25, root.y + 25
BoxLayout:
id: message_panel
pos_hint: {"x":0, "top":1}
size_hint: 0.5, 1
orientation: 'vertical'
canvas.before:
Color:
rgb: (.9, .9, .9)
Rectangle:
pos: self.pos
size: self.width - sp(2), self.height - sp(2)
#ColoredLabel:
# id: message_panel
# text: root.message
# pos_hint: {"x":0, "top":1}
# size_hint: 0.5, 1
BoxLayout:
id: image_panel
#text: "Image"
pos_hint: {"x":0.5, "top":1}
size_hint: 0.5, 0.5
Image:
id: image_object
source: root.image
center_x: self.parent.center_x
center_y: self.parent.center_y
size: self.parent.height,self.parent.width
BoxLayout:
id: sms_panel
pos_hint: {"x":0.5, "top":0.5}
size_hint: 0.5, 0.5
orientation: 'vertical'
canvas.before:
Color:
rgb: (.9, .9, .9)
Rectangle:
pos: self.pos
size: self.width - sp(2), self.height - sp(2)
Setting the GUI properties must be done on the main thread. Conveniently, there is a Clock.schedule_once() method that schedules a call on the main thread after a delay. So, I would suggest modifying part of the DVDemo class as:
class DVDemo(Widget):
message = StringProperty()
image = StringProperty('')
sms = ListProperty()
idx = 0
def __init__(self, **kwargs):
super(DVDemo, self).__init__(**kwargs)
self.update_data_index()
def schedule_screens(self):
self.idx = self.idx + 1
Clock.schedule_once(self.update_data_index, 5)
def update_data_index(self, *args):
idx = self.idx
.
.
.
The above code uses Clock.schedule_once() and eliminates the passing of idx among methods, since it is an attribute of the DVDemo instance.

Is there a focus lost event dispatcher for a kivy TextInput?

Problem:
Is there a way to fire an event if a multiline = True TextInput lost its focus?
Background:
I have tried on_touch_up function. But it returns the instances of all my TextInputs, not just the current widget. I tried text_validate_unfocus = False, with the same result.
Code:
import kivy
kivy.require("1.10.1")
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import ObjectProperty, NumericProperty
from kivy.uix.textinput import TextInput
from kivy.uix.bubble import Bubble
from kivy.lang import Builder
from kivy.storage.jsonstore import JsonStore
Builder.load_string('''
#: import Window kivy.core.window.Window
<Button>:
background_normal: ''
<Label>:
canvas.before:
Color:
rgba: (0,0.59,0.36,1)
Rectangle:
pos: self.pos
size: self.size
<TextInput>:
hint_text: 'Nuwe nota'
font_size: self.height / 4.5 if self.focus else self.height / 3
background_normal: ''
background_active: ''
foreground_color: (0,0.61,0.36,1) if self.focus else (0.71,0.75,0.71,1)
unfocus_on_touch: False
canvas.after:
Color:
rgb: (0,0,0,1)
Line:
points: self.pos[0] , self.pos[1], self.pos[0] + self.size[0], self.pos[1]
size_hint_y: None
height: Window.height / 6 if self.focus else Window.height / 12
<ChoiceBubble>:
orientation: 'horizontal'
size_hint: (None, None)
size: (160, 120)
pos_hint: {'top': 0.2, 'right': 0.8}
arrow_pos: 'top_left'
BubbleButton:
text: 'Save'
BubbleButton:
text: 'Encrypt..'
BubbleButton:
text: 'Delete'
on_release: root.del_txt_input()
<Notation>:
canvas:
Color:
rgba: (0,0.43,0.37,1)
Rectangle:
pos: self.pos
size: self.size
Label:
pos_hint: {'top': 1, 'right': 0.8}
size_hint: [0.8, None]
height: Window.height / 15
Button:
color: (0,0,0,1)
pos_hint: {'top': 1, 'right': 0.9}
size_hint: [0.1, None]
height: Window.height / 15
Image:
source: 'gear_2.png'
center_y: self.parent.center_y
center_x: self.parent.center_x
size: self.parent.width /1.5, self.parent.height/ 1.5
allow_stretch: True
Button:
color: (0,0,0,1)
pos_hint: {'top': 1, 'right': 1}
size_hint: [0.1, None]
height: Window.height / 15
on_release: root.add_input()
Image:
source: 'plus_text12354.png'
center_y: self.parent.center_y
center_x: self.parent.center_x
size: self.parent.width /1.5, self.parent.height/ 1.5
allow_stretch: True
ScrollView:
size_hint_y: None
size: Window.width, Window.height
pos_hint: {'top': 0.92, 'right': 1}
GridLayout:
id: text_holder
cols: 1
pos_hint: {'top': 0.92, 'right': 1}
padding: 4
size_hint_x: 1
size_hint_y: None
height: self.minimum_height
''')
class ChoiceBubble(Bubble):
pass
class TextInput(TextInput):
got_txt = ObjectProperty(None)
def on_touch_up(self, touch):
if not self.collide_point(*touch.pos):
self.text_validate_unfocus = False
note = Notation()
note.show_bubble
self.got_txt=note.que_txt_input(self)
return super(TextInput, self).on_touch_up(touch)
class Notation(FloatLayout):
which_txt = ObjectProperty(None)
new_txt = ObjectProperty(None)
cnt = NumericProperty(0)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.the_file=JsonStore('txt_input.json')
self.cnt = self.the_file.count()
lst = self.the_file.keys
def add_input(self):
txt_hld = self.ids.text_holder
self.cnt += 1
self.new_txt = TextInput(id=str(self.cnt))
self.the_file.put(str(self.cnt), the_id=str(self.cnt), the_input='')
txt_hld.add_widget(self.new_txt)
def que_txt_input(self, instance):
self.which_txt = instance
print(instance.text, instance)
return instance
def del_txt_input(self):
print(self.which_txt)
def the_file(self, notestore):
self.notestore = notestore
def show_bubble(self):
self.add_widget(ChoiceBubble())
def get_store(self):
the_keys = list(self.the_file.keys)
print(the_keys)
return the_keys
class theNoteApp(App):
title = 'My Notes'
def build(self):
return Notation()
if __name__ == '__main__':
theNoteApp().run()
Desired Result:
Once the focus is lost I want a bubble widget to be added to the top of my root class. This will give the user the option to save, encrypt or delete the TextInput id and text that just lost its focus, to a JSON file.
Problems
Multiple instances of object, Notation. One from build() method (return Notation()) in App class, and another instance created in on_touch_up() method (note = Notation()) whenever on_touch_up event is fired. Those instances created in on_touch_up() method does not has a visible view i.e. it won't show up in the window.
AttributeError: 'ChoiceBubble' object has no attribute 'del_txt_input'
Text in ChoiceBubble not visible i.e. the default text color is white on white background.
Solutions
Use App.get_running_app().root to get the instantiated root.
Replace root.del_txt_input() with app.root.del_txt_input()
Add color: 0, 0, 0, 1 to class rule, <Label>:
Use on_focus event to display BubbleButton
Snippets - kv
<Label>:
color: 0, 0, 0, 1
...
<ChoiceBubble>:
...
BubbleButton:
text: 'Delete'
on_release: app.root.del_txt_input()
Snippets - py file
class TextInput(TextInput):
got_txt = ObjectProperty(None)
def on_focus(self, instance, value):
if not value: # defocused
note = App.get_running_app().root
note.show_bubble()
self.got_txt = note.que_txt_input(self)
Output
The on_focus event will fire when the focus boolean changes.
I have tried on_touch_up function. But it returns the instances of all my TextInputs, not just the current widget.
This is because you didn't write any code that would limit it to the widget you care about, you can do this if you want to.

Categories