How to position buttons inside a scrollview using kivy - python

I have some buttons inside a gridlayout in a scrollView that move vertically. I have been trying to postion those buttons at different positions but they are not changing. Please how do I position those buttons at different x and y coordinate. I used floatlayout but it is not working for ScrollView(), why is that?
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 ScrollViewApp(App):
def build(self):
grid = GridLayout(cols=2, spacing=90, size_hint=(None,None))
grid.bind(minimum_height=grid.setter('height'))
btn1=Button(text='1', size=(90,90), size_hint=(None,None), pos_hint={'center_x':.5, 'center_y': 6})
btn2=Button(text='1', size=(90,90), size_hint=(None,None), pos_hint={'center_x':.5, 'center_y': 2})
btn3=Button(text='1', size=(90,90), size_hint=(None,None), pos_hint={'center_x':.5, 'center_y':.9})
grid.add_widget(btn1)
grid.add_widget(btn2)
grid.add_widget(btn3)
# pos_hint={center_x and center_y} not working
scroll = ScrollView( size_hint=(1, 1), do_scroll_x=False, do_scroll_y=True, scroll_type=['content'])
scroll.effect_cls= 'ScrollEffect'
scroll.add_widget(grid)
return scroll
if __name__ == '__main__':
ScrollViewApp().run()

to do this you can either give the each input of the x and y coordinate yourself:
for i in range(15):
a = float(input(f"Enter X postion value for #00{i}: "))
b = float(input(f"Enter Y postion value for #00{i}: "))
screen.add_widget(Button(text='#00' + str(i), size=(90,90), size_hint=(None,None),pos_hint={'center_x':a, 'center_y':b} ))
or
You can import random and set the different values or use "for" loop to insert different sequential values. I am showing you the examples 'random' below here:
for i in range(15):
a = random.randrange(0,10)
b = random.randrange(1,20)
screen.add_widget(Button(text='#00' + str(i), size=(90,90), size_hint=(None,None),pos_hint={'center_x':a, 'center_y':b} ))
I hope this will help you. And I am not sure about the usage of grid layouts here. You try to remove gridlayouts and just placing these buttons on different coordinate as you have gave the value for spacing of the button in the grid layout which may affect the output display. So, I hope it will work for you. Happy Coding...

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.screenmanager import Screen, ScreenManager
import random
class ScrollViewApp(App):
def build(self):
screen = Screen()
for i in range(3):
a = float(input(f"Enter X postion value for #00{i}: "))
b = float(input(f"Enter Y postion value for #00{i}: "))
screen.add_widget(Button(text='#00' + str(i), size=(90,90), size_hint=(None,None),pos_hint={'center_x':a, 'center_y':b} ))
return screen
if __name__ == '__main__':
ScrollViewApp().run()
This one works fine. I hope this will help you....

float layout should certainly work in scrollview, here's an example:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
import random
class ScrollViewApp(App):
def build(self):
float = FloatLayout(size=(600,1000),size_hint=(None,None))
for i in range(20):
btn1=Button(text=str(i), size=(90,90), size_hint=(None,None), pos_hint={'center_x':random.random(), \
'center_y': random.random()})
float.add_widget(btn1)
scroll = ScrollView( size_hint=(.8, .3), do_scroll_x=False, do_scroll_y=True, scroll_y=0.5,scroll_type=['content','bars'],\
bar_color = [1,0,0,.9],bar_inactive_color=[1,0,0.3,.5],bar_width=25)
scroll.add_widget(float)
return scroll
if __name__ == '__main__':
ScrollViewApp().run()
whereas with the attempted gridlayout, the position of each button will be at regular intervals, at least by default. To get arbitrary offsets at each cell of a gridlayout might be possible, but it would be three times as much work at least as just using float layout.
Note that the size_hint for the float layout is None,None, and the absolute size (600,1000) is intentionally larger than the scrollview (fractional 0.8,0.3 of default Window.size()). Scroll behaviour won't be seen otherwise.

Related

Problem that figures were not drawn in expected cell location on grid layout using canvas

In the following code, there are problems (1) and (2) according to the title.
If kvLang is used as described in this code, the figure (blue ellipse) will be drawn to the expected Cell position (#(1, 1) = upper left).
(However, the character string specified by text: is not displayed at this time. Please tell me how to display text characters .... Problem (1))
I have intended to have coded a Python script to draw with .add_widget method followed to the kvLang.
In this script, the yellow ellipse appears in the lower right instead of the expected Cell position ((2, 2) => lower left) ... problem (2)
For the purpose, it is necessary to add a widget to Grid Laytout using the .add_widget method so that the shape drawn in canvas can be displayed in the cell.
Please tell us how to solve it.
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Ellipse
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.app import App
Builder.load_string('''
<MyGridLayout#GridLayout>:
cols: 2
Label:
text:"From Kv_1" #(1)Not show. What's problem?
canvas:
Color:
rgb:0,0,1
Ellipse:
pos:self.pos
size:self.size
''')
class MyGridLayout(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.add_widget(Button(text="From_Py_1"))
self.add_widget(Button(text="From_Py_2"))
labl = Button(text="From_Py_canvas_1")
self.add_widget(labl)
with labl.canvas:
# (2) Expected to draw at cell(2,2) which is same locaton as the lable of "From_Py_canvas_1" but not.
Color(rgb=(1, 1, 0))
Ellipse(pos=labl.pos, size_hint=labl.size_hint)
class MyApp(App):
def build(self):
return MyGridLayout()
if __name__ == '__main__':
MyApp().run()
Problem 1: You just need to use canvas.before: instead of canvas: in your kv. This draws the ellipse before the text, instead of after (obscuring the text).
Problem 2: Your Ellipse is the using the current properties of the labl, which are default values until the app is displayed. To fix that, create the Ellipse after by using Clock.schedule_once(). In the modified code below, I saved a reference to labl in order to access it in the draw_yellow_ellipse() method:
from kivy.clock import Clock
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Ellipse
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.app import App
Builder.load_string('''
<MyGridLayout#GridLayout>:
cols: 2
Label:
text:"From Kv_1" #(1)Not show. What's problem?
canvas.before:
Color:
rgb:0,0,1
Ellipse:
pos:self.pos
size:self.size
''')
class MyGridLayout(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.add_widget(Button(text="From_Py_1"))
self.add_widget(Button(text="From_Py_2"))
self.labl = Button(text="From_Py_canvas_1")
self.add_widget(self.labl)
Clock.schedule_once(self.draw_yellow_ellipse)
def draw_yellow_ellipse(self, dt):
with self.labl.canvas:
# (2) Expected to draw at cell(2,2) which is same locaton as the lable of "From_Py_canvas_1" but not.
Color(rgb=(1, 1, 0))
Ellipse(pos=self.labl.pos, size_hint=self.labl.size_hint)
class MyApp(App):
def build(self):
return MyGridLayout()
if __name__ == '__main__':
MyApp().run()
Also, I don't think the Ellipse honors size_hint. Perhaps you meant to use size=self.labl.size. And if you do that, you will again have an Ellipse obscuring, in this case, your Button.

Python: Kivy: Divide two number from a single TextInput

Simple question, how can i divide two numbers from a single InputBox? I have no idea, example, i have only one inputbox and i write two numbers "40 10" how can i divide automatically this? Here the code:
import kivy
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.popup import Popup
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.listview import ListView
class Widget(GridLayout):
def __init__(self, **kwargs):
super(Widget, self) .__init__(**kwargs)
self.cols = 2
self.add_widget(Label(text="RPM"))
self.rpm = TextInput(multiline=False)
self.add_widget(self.rpm)
btn1 = Button(text="Division:")
btn1.bind(on_press=self.buttonClicked)
self.add_widget(btn1)
Example of what i need:
def buttonClicked(self, btn):
self.rpm.text(first input / second input)
x = self.rpm.text
popup = Popup(title='Result', content=x, size_hint=(None,
None), size=(500, 90))
popup.open()
The procedure is the next:
Get the text from TextInput
Separate it by the space
Verify that only 2 terms exist
Convert it to float, so an error may appear, exceptions must be used.
And for the last one we establish the popup with a Label that contains the text.
import kivy
kivy.require("1.0.6")
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.popup import Popup
class Widget(GridLayout):
def __init__(self, **kwargs):
super(Widget, self) .__init__(**kwargs)
self.cols = 2
self.add_widget(Label(text="RPM"))
self.rpm = TextInput(multiline=False)
self.add_widget(self.rpm)
btn1 = Button(text="Division:")
btn1.bind(on_press=self.buttonClicked)
self.add_widget(btn1)
def buttonClicked(self, btn):
texts = self.rpm.text.split()
if len(texts) == 2:
try:
x, y = map(float, texts)
res = x/y
popup = Popup(title='Result', content=Label(text=str(res)), size_hint=(None, None), size=(500, 90))
popup.open()
except (ValueError, ZeroDivisionError):
print("error")
class TestApp(App):
def build(self):
return Widget()
if __name__ == '__main__':
TestApp().run()
This type of interfaces are few useful since the user could place anything, the appropriate thing is to validate the text while it is being written as for example to accept a certain set of characters.
Another improvement is that it is separated into 2 TextInputs.

Adding spacing between dropdown items in kivy

My problem is in accessing container property of DropDown, which is a GridLayout by default and contains it's children.
Simple app:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.uix.label import Label
class TestApp(App):
def build(self):
root = BoxLayout(orientation='vertical')
dropdown = DropDown()
for i in range(3):
dropdown.add_widget(Button(
text=str(i),
size_hint_y=None
)) # add 3 buttons to dropdown
dropdown.container.bind(spacing=8) # this line does not work
dropdown_button = Button(size_hint_y=.2, text='Open DropDown')
dropdown_button.bind(on_release=dropdown.open)
root.add_widget(dropdown_button)
root.add_widget(Label()) # empty space under button
return root
TestApp().run()
I tried using bind method for this, but there is no result. No indentation is set. I also would like to see the solution in kivy language because I want to use dp() function for setting spacing and it's not very convenient to pass the parameter to python file for this. Thanks in advance for any help.
Instead using bind you should just set the value directly:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.uix.widget import Widget
class TestApp(App):
def build(self):
root = BoxLayout(orientation='vertical')
dropdown = DropDown()
for i in range(3):
dropdown.add_widget(Button(
text=str(i),
size_hint_y=None
)) # add 3 buttons to dropdown
dropdown.container.spacing = 10
dropdown.container.padding = (0, 10, 0, 0)
dropdown_button = Button(size_hint_y=.2, text='Open DropDown')
dropdown_button.bind(on_release=dropdown.open)
root.add_widget(dropdown_button)
root.add_widget(Widget()) # empty space under button
return root
TestApp().run()
Using a custom container class is not supported directly. You can do it like this, but its hacky and ugly:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.lang import Builder
Builder.load_string('''
<MyContainer>:
# copied from kivy.uix.dropdown._grid_kv
size_hint_y: None
height: self.minimum_size[1]
cols: 1
# custom settings
spacing: 10
padding: (0, 10, 0, 0)
''')
class MyContainer(GridLayout):
pass
class TestApp(App):
def build(self):
root = BoxLayout(orientation='vertical')
container = MyContainer()
dropdown = DropDown(container=container)
super(DropDown, dropdown).add_widget(container)
dropdown.on_container(dropdown, container)
for i in range(3):
dropdown.add_widget(Button(
text=str(i),
size_hint_y=None
)) # add 3 buttons to dropdown
dropdown_button = Button(size_hint_y=.2, text='Open DropDown')
dropdown_button.bind(on_release=dropdown.open)
root.add_widget(dropdown_button)
root.add_widget(Widget()) # empty space under button
return root
TestApp().run()
So I'd say it'd more clean to make custom spacing class as a subclass of Widget to fill space between buttons:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.lang import Builder
Builder.load_string('''
<DropDownSpacing>:
size_hint_y: None
height: 20
''')
class DropDownSpacing(Widget):
pass
class TestApp(App):
def build(self):
root = BoxLayout(orientation='vertical')
dropdown = DropDown()
for i in range(3):
dropdown.add_widget(DropDownSpacing())
dropdown.add_widget(Button(
text=str(i),
size_hint_y=None
)) # add 3 buttons to dropdown
dropdown_button = Button(size_hint_y=.2, text='Open DropDown')
dropdown_button.bind(on_release=dropdown.open)
root.add_widget(dropdown_button)
root.add_widget(Widget()) # empty space under button
return root
TestApp().run()
This is the same you're doing in your main BoxLayout, except I prefer to use Widget class directly instead of Label with no text.

Kivy scrollable label: impossible to read the beginning and the end of the label

I have these scrollable labels but I can't read the very beginning and the very end of them(the alphabet starting with 1 and the one starting with 8).
Another issue is that the scrollview starts in the center and jumps back automatically to the center when the scroll is released. It would be better to have it display the left part and let the label where I have stop to scroll.
I use python 3.6 and Kivy 1.9.2.dev0 and my code has to be in python (no .kv file or builder)
import kivy
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.popup import Popup
from kivy.uix.label import Label
# from kivy.properties import StringProperty
from kivy.uix.scrollview import ScrollView
class Test(App):
def build(self):
layout_pop = GridLayout (cols=3)
for i in range(3):
l = Label(
text="1abcdefghijklmnopqrstuvwxyz_2abcdefghijklmnopqrstuvwxyz_3abcdefghijklmnopqrstuvwxyz_4abcdefghijklmnopqrstuvwxyz_5abcdefghijklmnopqrstuvwxyz_6abcdefghijklmnopqrstuvwxyz_7abcdefghijklmnopqrstuvwxyz_8abcdefghijklmnopqrstuvwxyz",
font_size=15,
color=(1,1,3,1),
size_hint_x= None,
width=600)
l.bind(size_hint_min_x=l.setter('width'))
scroll = ScrollView(size_hint=(None, None), size=(200, 30))
scroll.add_widget(l)
layout_pop.add_widget(scroll)
return layout_pop
Test().run()
I simply had to use l.bind(texture_size=l.setter('size')). That fixed the 2 issues.
This is the updated def function:
def build(self):
layout_pop = GridLayout (cols=3)
for i in range(3):
l = Label(
text="1abcdefghijklmnopqrstuvwxyz_2abcdefghijklmnopqrstuvwxyz_3abcdefghijklmnopqrstuvwxyz_4abcdefghijklmnopqrstuvwxyz_5abcdefghijklmnopqrstuvwxyz_6abcdefghijklmnopqrstuvwxyz_7abcdefghijklmnopqrstuvwxyz_8abcdefghijklmnopqrstuvwxyz \n1abcdefghijklmnopqrstuvwxyz_2abcdefghijklmnopqrstuvwxyz_3abcdefghijklmnopqrstuvwxyz_4abcdefghijklmnopqrstuvwxyz_5abcdefghijklmnopqrstuvwxyz_6abcdefghijklmnopqrstuvwxyz_7abcdefghijklmnopqrstuvwxyz_8abcdefghijklmnopqrstuvwxyz",
font_size=15,
color=(1,1,3,1),
size_hint_x= None)
l.bind(texture_size=l.setter('size'))
l.bind(size_hint_min_x=l.setter('width'))
scroll = ScrollView(size_hint=(None, None), size=(200, 30))
scroll.add_widget(l)
layout_pop.add_widget(scroll)
return layout_pop

Anchor a label to the corner?

In Kivy:
from kivy.app import App
from kivy.uix.label import Label
class TestApp(App):
def build(self):
label = Label(text="TEST")
return label
TestApp().run()
My label is centred in the window:
How can I instead anchor my label to the bottom right corner of the window?
You'd think
label.halign = 'right'
label.valign = 'bottom'
would do the trick, but as the Label documentation points out,
The valign property will have no effect and halign will only have an effect if your text has newlines; a single line of text will appear to be centered even though halign is set to left (by default).
It looks like adding the label to an AnchorLayout, then shrinking the size of the label relative to its parent widget, together achieves what I want.
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.anchorlayout import AnchorLayout
class TestApp(App):
def build(self):
anchor_layout = AnchorLayout(anchor_x='right', anchor_y='bottom')
label = Label(text="TEST")
label.size_hint = (0.1, 0.1)
anchor_layout.add_widget(label)
return anchor_layout
TestApp().run()
Produces:
Set the Label's text_size to its size, e.g. in kv text_size: self.size. The text_size controls the bounding box within which text is wrapped.

Categories