I want to create an Accordion containing content consisting of an RstDocument and a button. The Accordion shall be scrollable as well as the content of the RstDocument when this content is larger than the given space. So I came up with the following code but after some clicks on AccordionItems all further interaction is blocking. What am I doing wrong here?
from kivy.app import App
from kivy.uix.scrollview import ScrollView
from kivy.uix.rst import RstDocument
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.accordion import Accordion, AccordionItem
from kivy.uix.button import Button
class ShowrstApp (App):
def update_size(self, instance, *args):
instance.size = 60 * len(instance.children)
def build (self):
numitems = 10
root = BoxLayout()
accheight = numitems * 60
accitems = Accordion(id='acc_panel', orientation='vertical', pos_hint={'top': 1}, size_hint_y=None,
height=accheight, md_bg_color=(1, 1, 1, 1))
for i in xrange(numitems):
item = AccordionItem(title='This is item: %d' % i)
somecontent = BoxLayout(orientation='vertical')
somecontent.add_widget(RstDocument(text='Some nicely formatted text here'))
somecontent.add_widget(Button(text='click here', height=(42), size_hint=(1,None)))
item.add_widget(somecontent)
item.bind(children=self.update_size)
accitems.add_widget(item)
sv = ScrollView(do_scroll_x = False)
sv.add_widget(accitems)
root.add_widget(sv)
return root
Window.size = (350,650)
showrst = ShowrstApp()
showrst.run()
Below the scrolling effect works when the RstDocument Boxlayout is either horizontal of vertical, but an issue I saw was that when the BoxLayout was set to vertical, the toggle of each item was mute, you had to go one by one from the bottom-up. This was weird. You can click on each AccordionItem, where the RstDocument is not. This should be a great starting point. Noticed this effect doesn't occur when using a Label, so that could be a another option.
from kivy.app import App
from kivy.uix.scrollview import ScrollView
from kivy.uix.rst import RstDocument
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.accordion import Accordion, AccordionItem
from kivy.uix.button import Button
class ShowrstApp (App):
def update_size(self, instance, *args):
instance.height = 100 * len(instance.children) # set Accordion height to the number of accordionItem times the height accordionItem height
def build (self):
numitems = 10
root = BoxLayout()
accheight = numitems * 60
accitems = Accordion(id='acc_panel', orientation='vertical', size_hint_y=None, pos_hint={'top':1}
height=accheight, md_bg_color=(1, 1, 1, 1))
for i in xrange(numitems * 2): # *2 to show it works
item = AccordionItem(title='This is item: %d' % i)
somecontent = BoxLayout(orientation = 'horizontal') # couldn't solve an issue I notice so I used horizontal
somecontent.add_widget(RstDocument(text='Some nicely formatted text here' * 10))
somecontent.add_widget(Button(text='click here', height=(42), size_hint=(1,None)))
item.add_widget(somecontent)
accitems.bind(size=self.update_size)
accitems.add_widget(item)
sv = ScrollView(do_scroll_x = False)
sv.add_widget(accitems)
root.add_widget(sv)
return root
Window.size = (350,650)
showrst = ShowrstApp()
showrst.run()
I was having the same issue with RstDocuments in a ScrollView. The problem arises because RstDocuments have their own scrolling and 'intercept' the scroll command because it thinks you're trying to scroll inside the RstDocument. If you're fine with non-scrolling RstDocuments, you can set do_scroll_y: False for RstDocument, and scrolling will then work fine in the ScrollView regardless of mousing over an RstDocument.
Related
I am using kivy and I am using Windows 10, Python 3.7.
I want to print out the name of each button when I press each button.
However, only the name of the last button is printed.
Below is the Python code
import kivy
kivy.require('2.0.0')
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
import os
Window.clearcolor = (.6, 1, .8, 1)
Window.size = (563, 1001)
Window.top, Window.left = 30, 800
path_list = ["music1","music2","music3"]
music = ""
class TestApp(App):
def build(self):
global music
gl = GridLayout(cols=1, size_hint_y=None)
for music in path_list:
bt = Button(text=music, size_hint_y=None, height=50)
gl.add_widget(bt)
bt.bind(on_press=self.music_f)
sv = ScrollView(size_hint=(1, 1))
sv.add_widget(gl)
return sv
def music_f(self,name):
global music
name = music
print("pray "+str(name))
TestApp().run()
Bad design:
You use a music variable which will take the last value from the path_list due to the for loop, then you use that value to assign it to the name, this has no logic... Once the button is created with the text=music, you already have the value assign to the that button. As you bind on_press with music_f, the name should be the button, I would change that to button.
In music_f you can access the name by calling button.text
def music_f(self,button):
print("pray "+str(button.text))
I have a screen with a StackLayout. The first row of the stack includes a textinput and a "+" button which is meant to add another identical row below the actual one in a loop (i.e. another textinput with another "add" button). Then there is a "Save" button, which is supposed to be always at the end of the stack.
The dictionary is supposed to later grab the input from the text field, when pressing the save button, but this should not be relevant to my problem.
There are two problems with my code:
First, when I press the "+" button in a row, the button that gets highlighted is not this button itself, but the one in the row below (e.g. if there are three rows and I press the button in the second row, the button in the third row is highlighted)
Second and most importantly, starting from the second row onwards, each press of the "+" button adds a new row above the actual one, rather than below it.
I am aware that kivy assigns reverse order to the widgets (i.e. the one added last will be drawn first) and that is why I am manually assigning indexes to the new rows. However, I cannot achieve the desired behavior.
Here is a minimal working version:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.stacklayout import StackLayout
from kivy.uix.screenmanager import ScreenManager, Screen
class AddWindow(Screen):
def __init__(self, **kwargs):
super(AddWindow, self).__init__(**kwargs)
self.grid = StackLayout()
self.grid.pos_hint = {"x":0.05,"top":0.8}
self.grid.size_hint = (0.9,None)
self.add_widget(self.grid)
self.i = 1
self.n = 1
self.inputs = {}
self.ing1 = TextInput(size_hint=(0.9,'0.3sp'))
self.grid.add_widget(self.ing1)
self.inputs['0'] = 'ing1'
self.addIng = Button(text="+", size_hint=(0.1,'0.3sp'))
self.addIng.bind(on_press=self.addIngredient)
self.grid.add_widget(self.addIng)
self.doneButton = Button(text="Save")
self.grid.add_widget(self.doneButton, index=0)
def addIngredient (self, instance):
self.ing = TextInput(size_hint=(0.9,'0.3sp'))
self.inputs[self.i] = 'ing{}'.format(self.i+1)
self.grid.add_widget(self.ing, index=self.n+1)
self.addNext = Button(text="+", size_hint=(0.1,'0.3sp'))
self.addNext.bind(on_press=self.addIngredient)
self.grid.add_widget(self.addNext, index=self.n+2)
self.i += 1
self.n += 2
print(self.inputs)
WMan = ScreenManager()
WMan.add_widget(AddWindow(name='add'))
class RecipApp(App):
def build(self):
return WMan
if __name__ == "__main__":
RecipApp().run()
What am I missing? Here is a screenshot for better clarity:
Screenshot
Here is a brute force method to do what you want by rebuilding the StackLayout each time a + `Button is pressed:
def addIngredient(self, instance):
tmp_children_list = self.grid.children[:]
self.grid.clear_widgets()
for index in range(len(tmp_children_list)-1, -1, -1):
child = tmp_children_list[index]
self.grid.add_widget(child)
if child == instance:
# this is the pressed Button, so add new row after it
self.n += 1
self.ing = TextInput(size_hint=(0.9,'0.3sp'))
self.ing.text = str(self.n) # add text just for identification
self.grid.add_widget(self.ing)
self.addNext = Button(text="+", size_hint=(0.1,'0.3sp'))
self.addNext.bind(on_press=self.addIngredient)
self.grid.add_widget(self.addNext)
Here is a quick and dirty approach , where your problems should be fixed due to a remove and add of the save button. Between those to actions you add a new row before you finally add the save button again. Hope it helps you to adjust your code. The key part is the custom on_press method of the AddButton.
from kivy.app import App
from kivy.uix.stacklayout import StackLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.core.window import Window
class AddButton(Button):
def __init__(self, **kwargs):
super(AddButton, self).__init__(**kwargs)
def on_press(self):
self.parent.remove_widget(self.parent.save_btn)
self.parent.add_widget(TextInput(size_hint=(None, None), size=(Window.width * 0.8 - self.parent.padding[1], Window.height * 0.1)))
self.parent.add_widget(AddButton(text="+", size_hint=(None, None), size=(Window.width * 0.2, Window.height * 0.1)))
self.parent.add_widget(self.parent.save_btn)
class MyApp(App):
def build(self):
Window.size = (400, 600)
layout = StackLayout(orientation="lr-tb", padding=(20, 40))
layout.save_btn = Button(text="Save", size_hint=(None, None),
size=(Window.width - layout.padding[1], Window.height * 0.1))
layout.add_widget(TextInput(size_hint=(None, None), size=(Window.width * 0.8 - layout.padding[1], Window.height * 0.1)))
layout.add_widget(AddButton(text="+", size_hint=(None, None), size=(Window.width * 0.2, Window.height * 0.1)))
layout.add_widget(layout.save_btn)
return layout
MyApp().run()
I'm trying to output text from button presses and add it to a textbox each time it's pressed as well as delete the text when the delete button is pressed - in normal python.
I've been trying to use kivy.uix.textinput, but I'm not sure on how to output the value from the buttons as well as delete it.
This is what I've done so far.
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
class CodeScreen(Screen):
def __init__(self):
super(CodeScreen, self).__init__(name='code_screen')
main_grid_lay = GridLayout(cols=2,cols_minimum={0:640, 1:175})
self.add_widget(main_grid_lay)
#Code ouput display
display_code = TextInput(text='Code!', readonly = True, id=output)
main_grid_lay.add_widget(display_code)
#Options Buttons
box_lay = BoxLayout(orientation='vertical')
main_grid_lay.add_widget(box_lay)
delete_button = Button(
text='Delete',
size_hint_x= None,
width=160,
id='' #unsure on how to delete
)
box_lay.add_widget(delete_button)
base_left = Button(
text='Base Left',
#on_release= b_left(),
#id='left',
)
base_right = Button(
text='Base Right',
on_release=self.degree_popup,
#on_release= b_right(),
)
#b_left = self.ids.output.text='left'
#b_left = self.ids.output.text='right'
box_lay.add_widget(base_left)
box_lay.add_widget(base_right)
# The app class
class MyMain(App):
def build(self):
return CodeScreen()
# Runs the App
if __name__ == '__main__':
MyMain().run()
It currently sends an error and is probably because of the ids. Not fully sure how ids work without using kv language. Any help is appreciated.
The id only makes sense in .kv, in python they are useless. In this case the solution is to access the objects in the methods connected to on_release but for this they must be class attributes.
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
class CodeScreen(Screen):
def __init__(self):
super(CodeScreen, self).__init__(name="code_screen")
self.display_code = TextInput(text="Code!", readonly=True)
self.delete_button = Button(
text="Delete", size_hint_x=None, width=160, on_release=self.delete_clicked
)
self.base_left = Button(text="Base Left", on_release=self.left_clicked)
self.base_right = Button(text="Base Right", on_release=self.right_clicked)
main_grid_lay = GridLayout(cols=2, cols_minimum={0: 640, 1: 175})
main_grid_lay.add_widget(self.display_code)
self.add_widget(main_grid_lay)
box_lay = BoxLayout(orientation="vertical")
box_lay.add_widget(self.delete_button)
main_grid_lay.add_widget(box_lay)
box_lay.add_widget(self.base_left)
box_lay.add_widget(self.base_right)
def delete_clicked(self, instance):
self.display_code.text = ""
def left_clicked(self, instance):
self.display_code.text += "left"
def right_clicked(self, instance):
self.display_code.text += "right"
# The app class
class MyMain(App):
def build(self):
return CodeScreen()
# Runs the App
if __name__ == "__main__":
MyMain().run()
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.core.window import Window
from kivy.uix.scrollview import ScrollView
from kivy.effects.scroll import ScrollEffect
from kivy.uix.widget import Widget
from kivy.uix.button import Button
class BSGameMain(BoxLayout):
blmain = BoxLayout(orientation = 'vertical') # MainBoxLayout init
scrlFBtns = ScrollView(effect_cls = 'ScrollEffect')
blbtns = BoxLayout(
orientation = 'vertical',
size_hint_y = None
) # BoxLayout for buttons
blbtns.bind(minimum_height = blbtns.setter('height'))
scrlFBtns.add_widget(blbtns)
for i in range (2):
blbtns.add_widget(Button(
text='asd',
size_hint_y = None,
height = 40
))
lblmain = Label(text = 'asd')
blmain.add_widget(lblmain)
blmain.add_widget(scrlFBtns)
class BSApp(App):
def build(self):
game = BSGameMain()
return game.blmain
if __name__ == "__main__":
BSApp().run()
It is necessary to make the buttons appear from below, and not from the middle, taking into account the scrollview. I just can't make him do it.
Please keep in mind that BoxLayouts override modifications to positioning, as they automatically position their children. This makes them pretty self-sufficient but a little stubborn to work with.
Like you said, if there's only a couple buttons, it sticks to the middle. The scrollview works, but since it's a child of a boxlayout, it can only stretch as far as what the boxlayout says it can, which is the bottom half of the window. Also, since the boxlayout for the buttons adds children top-down, this is why your buttons appear in the middle. In reality, it's adding buttons to the top of the bottom half of the window.
You have specified boxlayouts where boxlayouts are not necessary. One thing you might want is to position your scrollview at will, depending on the number of buttons in the scrollview. This will require an if statement and an integer.
My changes:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.core.window import Window
from kivy.uix.scrollview import ScrollView
from kivy.effects.scroll import ScrollEffect
from kivy.uix.widget import Widget
from kivy.uix.button import Button
class BSGameMain(): #No longer a boxlayout.
scroll_height = 0;
blmain = Widget() #No longer a boxlayout. Fills the window.
blbtns = BoxLayout(
orientation = 'vertical',
size_hint = (1, None),
) # BoxLayout for buttons
blbtns.bind(minimum_height = blbtns.setter('height'))
for i in range (20): #number of buttons printed
blbtns.add_widget(Button(
text='asd',
size_hint_y = None,
height = 40
))
if (scroll_height < (Window.height / 2)):
scroll_height = scroll_height + 40 #This measures how much space we need for the scrollview. Make sure it matches button height!
scrlFBtns = ScrollView(effect_cls = 'ScrollEffect', pos = (0, 0), size = (Window.width, scroll_height))
#The pos of 0,0 ensures scrlFBtns stays at the bottom. Place at negative coordinates if you want it to extend off-screen.
scrlFBtns.add_widget(blbtns)
lblmain = Label(text = 'asd', halign = 'center', y = (Window.height * 0.75), width = Window.width, color = (1,1,1,1))
blmain.add_widget(lblmain)
blmain.add_widget(scrlFBtns)
class BSApp(App):
def build(self):
game = BSGameMain()
return game.blmain
if __name__ == "__main__":
BSApp().run()
Results:
One Button
,
Three Buttons
,
Twenty Buttons
Hopefully this is along the lines of what you're looking for!
In a part of my app I want to show a list of numbers (1 to 100) and when the "filter" button selected list numbers change (1 to 10). I've tried several solutions like this but none of them worked !
what is the problem in your idea?
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.listview import ListView
from kivy.uix.label import Label
class SingleApp(App):
def build(self):
hole = BoxLayout()
right = BoxLayout(size_hint_x=0.2)
left = BoxLayout(size_hint_x=0.8, orientation='vertical')
side_panel = ListView(item_strings=['no %i' %i for i in range(100)])
right.add_widget(side_panel)
btn = Button(text='Show 1 to 10 ')
lbl = Label(text='Show list of numbers (test)')
btn2 = Button(text='reset')
left.add_widget(btn)
left.add_widget(lbl)
left.add_widget(btn2)
hole.add_widget(right)
hole.add_widget(left)
btn.bind(on_press=self.change(right))
btn2.bind(on_press=self.reset(right))
return hole
def change(self, side):
side.clear_widgets()
side_panel = ListView(item_strings=['NUMBER %i' %i for i in range(10)])
side.add_widget(side_panel)
def reset(self, side):
side.clear_widgets()
side_panel = ListView(item_strings=['no %i' %i for i in range(100)])
side.add_widget(side_panel)
if __name__=='__main__':
SingleApp().run()