I'm trying to make a custom layout using kivy, I need to generate a gridlayout with scrollview buttons and put it inside of another layout.
Everytime I click the button that generates the buttons it pushes the "get links" button up and if I click it a second time instead of adding buttons to the existing gridlayout it creates a new gridlayout.
This is the app before I press the 'get links' button
The first time I press the 'get links' button
The second time the button is pressed
class RootWidget(BoxLayout):
pass
class Scrollbox(BoxLayout):
def __init__(self, **kwargs):
super(Scrollbox, self).__init__(**kwargs)
self.orientation = 'vertical'
class CustomLayout(FloatLayout):
def __init__(self, **kwargs):
# make sure we aren't overriding any important functionality
super(CustomLayout, self).__init__(**kwargs)
with self.canvas.before:
Color(0, 1, 0, 1) # green; colors range from 0-1 instead of 0-255
self.rect = Rectangle(size=self.size, pos=self.pos)
self.bind(size=self._update_rect, pos=self._update_rect)
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
class MainApp(App):
link_buttons = 0
def build(self):
root = RootWidget()
c = CustomLayout()
s = Scrollbox()
root.add_widget(c)
root.add_widget(s)
def on_enter(self):
func = Function()
buttons = func.buttons()
s.add_widget(buttons)
get_buttons = Button(
text='Get links',
size_hint=(1, 0),
pos=(20, 20))
s.add_widget(get_buttons)
get_buttons.bind(on_press=on_enter)
return root
class Function(MainApp):
def buttons(self):
if self.link_buttons == 0:
layout = GridLayout(cols=1, padding=1, spacing=10,
size_hint=(None, None), width=10)
layout.bind(minimum_height=layout.setter('height'))
self.link_buttons += 1
for buttn in range(20):
btn = Button(text='test', size=(200, 50),
size_hint=(None, None))
try:
self.layout.add_widget(btn)
except:
layout.add_widget(btn)
# create a scroll view, with a size < size of the grid
root = ScrollView(size_hint=(None, None), size=(200, 400),
pos_hint={'center_x': .5, 'center_y': .5}, do_scroll_x=False)
root.add_widget(layout)
return root
if __name__=='__main__':
MainApp().run()
You have a few problems:
1) Function inherts from MainApp - don't do that - its weird!!
2) You are recreating the ScrollView on each click
Here is a modified (part of) the source code that worked for me
class MainApp(App):
link_buttons = 0
def build(self):
layout = GridLayout(cols=1, padding=1, spacing=10,
size_hint=(None, None), width=10)
layout.bind(minimum_height=layout.setter('height'))
sv = ScrollView(size_hint=(None, None), size=(200, 400),
pos_hint={'center_x': .5, 'center_y': .5}, do_scroll_x=False)
sv.add_widget(layout)
self.layout = layout
root = RootWidget()
c = CustomLayout()
s = Scrollbox()
root.add_widget(c)
root.add_widget(s)
s.add_widget(sv)
get_buttons = Button(
text='Get links',
size_hint=(1, 0),
pos=(20, 20))
s.add_widget(get_buttons)
get_buttons.bind(on_press=self.buttons)
return root
def buttons(self, btn):
layout = self.layout
self.link_buttons += 1
for buttn in range(20):
btn = Button(text='test', size=(200, 50),
size_hint=(None, None))
self.layout.add_widget(btn)
Related
I have tried to add a simple scroll view on PC, I define the scroll view like any other widget and then adds its layout and that I then add the widgets.
Here is the code I use:
Window.size = (339, 600)
self.w1 = self
with self.w1.canvas.before:
Color(0.941176, 0.941176, 0.941176, 1)
self.rect = Rectangle(size=self.w1.size, pos=self.w1.pos)
self.w1.bind(pos=self.update_rect, size=self.update_rect)
self.scroll1 = ScrollView(size_hint=(None, 1), size=(Window.width, Window.height),scroll_type = ['bars',"content"])
self.w1.add_widget(self.scroll1)
self.scroll1_layout = RelativeLayout(pos_hint ={'x':0.0, 'y':0.0},size_hint = (0.626844, 1))
self.scroll1.add_widget(self.scroll1_layout)
self.button1 = Button(text = "Button", pos_hint ={'x':0.568182, 'y':0.47585}, size_hint = (1.30682, 0.0545617))
self.scroll1_layout.add_widget(self.button1)
self.button2 = Button(text = "Button", pos_hint ={'x':0.227273, 'y':0.864937}, size_hint = (0.909091, 0.0992844))
self.scroll1_layout.add_widget(self.button2)
How do I make it so that it has a button and below the widget origin. the widget origin is the bottom left of the widget and the bottom below it would not be visible until I scrolled down.
Thank You for any help.
I've been trying to change the label2 text in layout 2, with button1 in layout1, but it doesn't seem to work, when I press the button nothing happens
Here's the code:
class layout1(GridLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.cols = 2
self.button1 = Button(text = "Button 1 changes screen 2", on_press = self.change_label)
self.add_widget(self.button1)
self.change_button = Button(text = "move to screen 2", on_press = self.change_screen)
self.add_widget(self.change_button)
def change_screen(self, instance):
practice_app.sm.current = "screen2"
def change_label(self,instance):
func_layout = layout2()
func_layout.label2.text = "changed"
class layout2(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 2
self.label2 = Label(text = "this should change")
self.add_widget(self.label2)
class TestApp(App):
def build(self):
self.sm = ScreenManager()
screen1 = Screen(name = "screen1")
screen1.add_widget(layout1())
self.sm.add_widget(screen1)
screen2 = Screen(name = "screen2")
screen2.add_widget(layout2())
self.sm.add_widget(screen2)
return self.sm
if __name__ == "__main__":
practice_app = TestApp()
practice_app.run()
There are many ways to do what you want. Since you are not using kv, perhaps the easiest way is to save a reference to layout2. Here is a modified version of your build() method that does that:
class TestApp(App):
def build(self):
self.sm = ScreenManager()
screen1 = Screen(name = "screen1")
screen1.add_widget(layout1())
self.sm.add_widget(screen1)
screen2 = Screen(name = "screen2")
self.layout2 = layout2() # save reference to layout2
screen2.add_widget(self.layout2)
self.sm.add_widget(screen2)
return self.sm
And then, use that reference in the change_label() method:
def change_label(self,instance):
# func_layout = layout2() # creates a new instance of layout2 (not the one in the GUI)
func_layout = App.get_running_app().layout2
func_layout.label2.text = "changed"
I'm trying to make a horizontal scroll view in Kivy (Python) which would display an image (buttons for now) that you can scroll through. I've been able to figure out how to do the horizontal scroll view, but I just can't seem to figure out what I'm doing wrong to center on the screen. Here is what I have so far:
import kivy
kivy.require('1.8.0')
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
from kivy.uix.gridlayout import GridLayout
class MyGrid(GridLayout):
def __init__(self, **kwargs):
super(MyGrid, self).__init__(**kwargs)
self.rows = 1
self.spacing=50
self.size_hint=(None, .5)
self.bind(minimum_width=self.setter('width'))
class ScrollViewApp(App):
def build(self):
scroll = ScrollView( size_hint=(1,1), do_scroll_x=True, do_scroll_y=False )
grid = MyGrid()
for i in range(60):
grid.add_widget(Button(text='#00' + str(i),size=(100,100), size_hint=(1,1)))
scroll.add_widget(grid)
return scroll
if __name__ == '__main__':
ScrollViewApp().run()
I looked through the documentation and I think it has something to do with pos_hint: https://kivy.org/doc/stable/api-kivy.uix.widget.html?highlight=pos_hint#kivy.uix.widget.Widget.pos_hint
I tried adding:
self.pos={'center_y':1}
in my MyGrid() class, but nothing is being changed. No matter what value is I put after center_y, my the ScrollView contained in the grid still appears at the top of the screen, rather than centered halfway down. I've also tried adjusting things in the ScrollViewApp() class too, using
scroll.pos={'center_y':1}
as well. I can't seem to figure out what I'm doing wrong. Does anyone have any ideas on what I'm not understanding here? I feel like I'm so close!!!
Edit:
I'm trying to have the buttons centered, but also be able to scroll by dragging the black space around the buttons. In the picture below, you can see there is a bunch of black space beneath the buttons. I can click a button or this black space and scroll horizontally. I just need this to happen with the buttons centered!
The layout is behaving as you told it to.
You have a ScrollView as a root widget, and inside it you put your Grid that has size_hint_y = 0.5.
It takes half of the parent widget height, that takes the full window height.
One way to center your Grid is to put some spacers over and under it, but this is not possible, since ScrollView accepts onle one widget as content.
So, one solution is to..
let the Grid have the full height of the ScrollView
add the ScrollView inside another Layout (e.g. a BoxLayout)
and finally, add two widgets to force the ScrollView in the center.
class MyGrid(GridLayout):
def __init__(self, **kwargs):
super(MyGrid, self).__init__(**kwargs)
self.rows = 1
self.spacing = 50
self.size_hint = (None, 1)
self.bind(minimum_width=self.setter('width'))
class ScrollViewApp(App):
def build(self):
base = BoxLayout(orientation="vertical", size_hint=(1, 1))
base.add_widget(Widget(size_hint_y=.3))
scroll = ScrollView(size_hint=(1, .4), do_scroll_x=True, do_scroll_y=False)
grid = MyGrid()
for i in range(60):
grid.add_widget(
Button(text='#00' + str(i), size=(100, 100), size_hint=(1, 1)))
scroll.add_widget(grid)
base.add_widget(scroll)
base.add_widget(Widget(size_hint_y=.3))
return base
if __name__ == '__main__':
ScrollViewApp().run()
Another approach would be to..
put the ScrollView inside a FloatLayout
and position it using pos_hint
class MyGrid(GridLayout):
def __init__(self, **kwargs):
super(MyGrid, self).__init__(**kwargs)
self.rows = 1
# self.spacing = 50
self.size_hint = (None, 1)
self.bind(minimum_width=self.setter('width'))
class ScrollViewApp(App):
def build(self):
base = FloatLayout(size_hint=(1, 1))
scroll = ScrollView(size_hint=(1, .4), do_scroll_x=True, do_scroll_y=False,
pos_hint={"center_y": .5})
grid = MyGrid()
for i in range(60):
grid.add_widget(
Button(text='#00' + str(i), width=100, size_hint=(None, 1)))
scroll.add_widget(grid)
base.add_widget(scroll)
return base
if __name__ == '__main__':
ScrollViewApp().run()
Edit:
OK. After the update of the question, this is a way to to center and use the black bars to scroll:
Builder.load_string("""
<BaseWidget>:
ScrollView:
do_scroll_y: False
BoxLayout:
orientation: "vertical"
size_hint_x: None
width: self.minimum_width
Widget:
size_hint_y: .3
GridLayout:
id: grid
rows: 1
size_hint_y: .4
size_hint_x: None
width: self.minimum_width
Widget:
size_hint_y: .3
""")
class BaseWidget(FloatLayout):
pass
class ScrollViewApp(App):
def build(self):
base = BaseWidget()
for i in range(60):
base.ids.grid.add_widget(Button(text='#00' + str(i),
width=100, size_hint_x=None))
return base
if __name__ == '__main__':
ScrollViewApp().run()
It uses kv_lng to create the layout because I find it easier to the eye.
For a "Python only" code you can use this:
class MyGrid(GridLayout):
def __init__(self, **kwargs):
super(MyGrid, self).__init__(**kwargs)
self.rows = 1
self.size_hint = None, .4
self.bind(minimum_width=self.setter('width'))
class MyBox(BoxLayout):
def __init__(self, **kwargs):
super(MyBox, self).__init__(**kwargs)
self.orientation = "vertical"
self.size_hint_x = None
self.bind(minimum_width=self.setter('width'))
class ScrollViewApp(App):
def build(self):
base = FloatLayout()
grid = MyGrid()
box = MyBox()
box.add_widget(Widget(size_hint_y=.3))
box.add_widget(grid)
box.add_widget(Widget(size_hint_y=.3))
for i in range(60):
grid.add_widget(Button(text='#00' + str(i), width=100, size_hint_x=None))
scroll = ScrollView(do_scroll_y=False, pos_hint={"center_y": .5})
scroll.add_widget(box)
base.add_widget(scroll)
return base
if __name__ == '__main__':
ScrollViewApp().run()
Very often in my code, I need to say in a Screen object this:
self.manager.current = 'screenname'
But sometimes my interpreter says that the None type has no attribute current...
Is it normal that my screen manager disappears?
EDIT:
The problem happens when I add this piece of code to my project:
class EditClass(Screen):
def __init__(self, **kwargs):
super(EditClass, self).__init__(**kwargs)
self.myinit()
def go_to_home(self):
self.manager.current = "home_screen"
def myinit(self):
self.box0 = BoxLayout(orientation='vertical')
self.box1 = BoxLayout(spacing=-2, size=(50,50), size_hint=(1,None))
self.box2 = BoxLayout(orientation='vertical', padding = (5,5,5,5), spacing = 5)
self.btn_back = Button(size=(32, 50), on_press=self.go_to_home(), size_hint=(None, 1), text="<", background_color=(0.239, 0.815, 0.552, 1))
self.btn_title = Button(text="Edit a class", background_color = (0.239, 0.815, 0.552, 1))
self.btn_more= Button(size=(32, 50), size_hint=(None, 1), text="=", background_color = (0.239, 0.815, 0.552, 1))
self.anchor0 = AnchorLayout(anchor_x='right', anchor_y = 'bottom', padding=(5,5,5,5))
self.btn_plus = Button(text="+", size=(46, 46), size_hint=(None, None), background_color=(0.239, 0.815, 0.552, 1))
self.box1.add_widget(self.btn_back)
self.box1.add_widget(self.btn_title)
self.box1.add_widget(self.btn_more)
self.anchor0.add_widget(self.btn_plus)
self.box2.add_widget(self.anchor0)
self.box0.add_widget(self.box1)
self.box0.add_widget(self.box2)
self.add_widget(self.box0)
Instead of adding myinit to __init__ you could schedule it:
from kivy.clock import Clock
...
class EditClass(Screen):
def __init__(self, **kwargs):
super(EditClass, self).__init__(**kwargs)
Clock.schedule_once(self.myinit, 1)
...
def myinit(self, *args):
...
...
self.manager is None in a Screen object until that object has been added to a ScreenManager with the add_widget() method.
From the below code I expect layout to be changed from BoxLayout to GridLayout in show_buttons() method but it is not happening and I am still seeing BoxLayout. I would appreciate an explanation, thank you.
class MainScreen(BoxLayout):
def show_buttons(self, button):
self.clear_widgets()
self.layout = GridLayout(cols=2)
if button.text == 'Button 1':
for x in range (100, 110):
t = ('Button %s' % x)
self.add_widget(Button(text=t))
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
self.orientation='vertical'
self.add_widget(Label(text='Select Button', size_hint = (1, 0.2)))
self.button1=Button(text="Button 1")
self.button1.bind(on_press=self.show_buttons)
self.add_widget(self.button1)
self.button2=Button(text="Button 2")
self.button2.bind(on_press=self.show_buttons)
self.add_widget(self.button2)
class MyApp(App):
def build(self):
return MainScreen()
if __name__ == '__main__':
MyApp().run()
You forgot to add the GridLayout to the parent...
def show_buttons(self, button):
self.clear_widgets()
self.layout = GridLayout(cols=2, size_hint=(1.0, 1.0))
if button.text == 'Button 1':
for x in range (100, 110):
t = ('Button %s' % x)
self.add_widget(Button(text=t))
self.add_widget(self.layout) # <-----------------
That said, You might want to rethink about clearing the widgets and just move to a diffent screen using a ScreenManager