In the app I am creating I have a screen manager that holds a few screen instances. In one of them I want to have 2 standard widgets, and a number of buttons that are added dynamically by the user. If those buttons are too many, the user should be able to scroll through them till he finds the one he wants.
To accomplish that I use a grid layout that holds two objects: another grid layout and a scrollview. The grid layout is in charge of the 2 standard widgets(a text input and a button). The scrollview is in charge of the dynamically added buttons.
I want the scrollview part to occupy the bigger part of the window (say 75%) so the user can see the buttons more clearly, and the gridlayout with the 2 standard widgets should occupy the remaining part. However, the gridlayout ends up taking the bigger part of the window for itself.
Here's the piece of code (assume that the dynamically added buttons right now are 15):
sm = ScreenManager()
class scr1(Screen):
pass
#layout that occupies the entire window. Everything else will be added on this
glayout = GridLayout(cols=1)
#layout that should occupy 25% of the window
layout1 = GridLayout(cols=1,size_hint_y=0.25)
#layout to be added on the scrollview
layout2 = GridLayout(cols=1,size_hint_y=None)
layout2.bind(minimum_height=layout2.setter('height'))
#screen to hold the glayout
screen1 = scr1(name = '1')
#adding a couple of widgets to layout1
layout1.add_widget(Button(text = 'hey'))
layout1.add_widget(TextInput(text = 'hey'))
#scroller that should occupy 75% of the window
scroller = ScrollView(size_hint = (1,None))
scroller.add_widget(layout2)
#adding buttons to be scrolled
for i in range(15):
layout2.add_widget(Button(text=str(i),size_hint_y=None))
#adding scroller and layout1 to glayout and then the glayout to the screen
glayout.add_widget(scroller)
glayout.add_widget(layout1)
screen1.add_widget(glayout)
#adding the screen to the screen manager
sm.add_widget(screen1)
I am not very familiar with the positioning system that kivy uses, and I am not sure how I should go about solving this problem. Here's what the program looks like when I run it.
You set size_hint_y of ScrollView to None.
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
sm = ScreenManager()
class scr1(Screen):
pass
#layout that occupies the entire window. Everything else will be added on this
glayout = GridLayout(cols=1)
#layout that should occupy 25% of the window
layout1 = GridLayout(cols=1,size_hint_y=0.25)
#layout to be added on the scrollview
layout2 = GridLayout(cols=1,size_hint_y=None)
layout2.bind(minimum_height=layout2.setter('height'))
#screen to hold the glayout
screen1 = scr1(name = '1')
#adding a couple of widgets to layout1
layout1.add_widget(Button(text = 'hey'))
layout1.add_widget(TextInput(text = 'hey'))
#scroller that should occupy 75% of the window
scroller = ScrollView(size_hint_y=.75)
scroller.add_widget(layout2)
#adding buttons to be scrolled
for i in range(15):
layout2.add_widget(Button(text=str(i),height=70,size_hint_y=None))
#adding scroller and layout1 to glayout and then the glayout to the screen
glayout.add_widget(scroller)
glayout.add_widget(layout1)
screen1.add_widget(glayout)
#adding the screen to the screen manager
sm.add_widget(screen1)
from kivy.app import App
class Ap(App):
def build(self):
return sm
Ap().run()
Related
I am trying to draw 10 images on the screen, then draw 10 more images (of the same size and shape) over them, in the same locations. This is fine but I also want it all the be scrollable but I get the error Exception: ScrollView accept only one widget
This is my code:
root = ScrollView(size_hint=(1,1), scroll_wheel_distance=40)
layout1 = GridLayout(cols=2, spacing=0, size_hint=(1, None), row_force_default=True, row_default_height=270)
layout1.bind(minimum_height=layout1.setter('height'))
for i in range(10):
img = Image(source=UI_bottom_path, size_hint_y=1, allow_stretch=True)
layout1.add_widget(img)
layout2 = GridLayout(cols=2, spacing=0, size_hint=(1, None), row_force_default=True, row_default_height=270)
for i in range(10):
img = Image(source=UI_top_path, size_hint_y=1, allow_stretch=True)
layout2.add_widget(img)
root.add_widget(layout1)
root.add_widget(layout2)
self.add_widget(root)
If I comment out "root.add_widget(layout2)" it works fine but doesn't draw the images.
This is how it currently looks:
This is how each should look:
Is there a way to get ScrollView and have the layers on top of each other?
Like your error says, ScrollView accepts only one widget. When you commented out "root.add_widget(layout2)" Scrollview has only one widget and that is why it works.
So you can add only one layout to Scrollview like BoxLayout or GridLayout and add the images to the same layout.
And please add your complete code and then we can show an example.
I am trying to make a slider using a button and when pressed reveals two other buttons but does not change colors when pressed solely for aesthetics. I am very comfortable with the .kv language and know you can use
background_color: (1,0,0,1) if self.state == 'normal' else (0,1,0,1)
in the .kv portion of the script and have tried variations of this and using if self.state == 'down': and others such as that in python. However I am trying to learn the dynamic aspect of kivy now and am just starting to play around with animations and would like to be able to do this without using a .kv file or Builder.
The goal is to have two buttons that change background colors beneath a button that simply slides on_press. My issue is that the sliding button is changing colors when I want the background color to remain static and the buttons underneath are not highlighting on_press if I adjust their background colors. I am mainly looking for help with the sliding button remaining one color and am not too concerned if the buttons beneath must remain default. If anyone could offer some help I would greatly appreciate it.
from kivy.animation import Animation
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.behaviors import ButtonBehavior
global active
active = True
class TestApp(App):
def plop_slide_animation(self, instance):
global active
if active == True:
plop_slide = Animation(pos=(250, 350), duration=0.5)
active = False
elif active == False:
plop_slide = Animation(pos=(250, 200), duration=0.5)
active = True
plop_slide.start(instance)
def ploppi_press(self,instance):
print('ploppi')
def plopper_press(self,instance):
print('plopper')
def build(self):
button1 = Button(background_color = (1,.6,1,1), size_hint=(.2, .2), pos=(250, 200), text='PLOP', on_press=self.plop_slide_animation)
button3 = Button(background_color = (128,0,0,.5), size_hint=(.09, .09), pos=(260, 250), text='ploppi', on_press=self.ploppi_press) #background_color = (128,0,0,.5),
button4 = Button(background_color = (0,0,255,.5), size_hint=(.09, .09), pos=(450, 250), text='plopper', on_press=self.plopper_press) #background_color = (0,0,255,.5),
layout = FloatLayout()
def change_text(button):
global active
if button1.state == 'down':
print(button1.state)
if active == True:
button1.text = 'PLOOOOP'
print('PLOOOOP')
if active == False:
button1.text = 'PLOP'
print('PLOP')
button1.bind(on_press=change_text)
layout.add_widget(button3)
layout.add_widget(button4)
layout.add_widget(button1)
return layout
if __name__ == '__main__':
TestApp().run()```
Assing empty string to button's images in different states
button1.background_down = ''
button1.background_normal = ''
and you get one color all the time.
But it will be little different color because it will show real color, not dim color.
Doc: Button
EDIT: You can also assign one image to another and it will use the same image for both states - and you get the same dim color.
button1.background_down = button1.background_normal
I'm looking to make a popup on the python-side that has a dynamic height.
So far, I have this within the screens __init__ class. The kv file has another widget that called the popup on_release. Anyways, I have found that this produces a popup with very wonky formatting:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import Screen, ScreenManager
kv = '''
ScreenManagement:
id: 'manager'
BrokenPopup:
name: 'broken'
manager: 'manager'
<BrokenPopup>:
BoxLayout:
Button:
text: 'Test'
on_release: root.p.open()
'''
class ScreenManagement(ScreenManager):
pass
class BrokenPopup(Screen):
def __init__(self, **kwargs):
super(BrokenPopup,self).__init__(**kwargs)
self.p = Popup(auto_dismiss=False, size_hint_x=.6, size_hint_y=None, title='A popup')
self.g = GridLayout(cols=1, spacing=10)
self.g.add_widget(Button(text='Test1', size_hint_y=None, height=32))
self.g.add_widget(Button(text='Test2', size_hint_y=None, height=32))
self.g.bind(minimum_height=self.g.setter('height'))
self.p.add_widget(self.g)
self.p.bind(height=self.g.setter('height')) #<- this does not work to change the popup height!
class TheApp(App):
def build(self):
return Builder.load_string(kv)
TheApp().run()
The popup size is set to fit only one widget, leaving the second button (and all others that may be included) to float beyond the confines of the popup border.
How should I change the code so that all of the widgets fit within the confines of the popup? I am trying to do that by dynamically setting the height of the popup, however that is not proving effective.
I have modified your code to do what I think you want. Basically it adds the minimum_height from the GridLayout, that is added to your Popup, to the calculated height of the title and the dividing bar. The first Button in the GridLayout now adds another Button to the GridLayout for testing.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import Screen, ScreenManager
kv = '''
ScreenManagement:
id: 'manager'
BrokenPopup:
name: 'broken'
manager: 'manager'
<BrokenPopup>:
BoxLayout:
Button:
text: 'Test'
on_release: root.p.open()
'''
class ScreenManagement(ScreenManager):
pass
class BrokenPopup(Screen):
def __init__(self, **kwargs):
super(BrokenPopup,self).__init__(**kwargs)
self.popup_title_height = None
self.p = Popup(auto_dismiss=False, size_hint_x=.6, size_hint_y=None, title='A popup')
self.g = GridLayout(cols=1, spacing=10)
self.g.bind(minimum_height=self.fix_size)
self.g.add_widget(Button(text='Test1', size_hint_y=None, height=32, on_release=self.add_one))
self.g.add_widget(Button(text='Test2', size_hint_y=None, height=32))
self.p.add_widget(self.g)
def add_one(self, *args):
self.g.add_widget(Button(text='Another', size_hint_y=None, height=32))
def get_popup_title_height(self):
height = 0
popupGrid = self.p.children[0]
height += popupGrid.padding[1] + popupGrid.padding[3]
for child in popupGrid.children:
if isinstance(child, BoxLayout):
continue
else:
height += child.height + popupGrid.spacing[1]
self.popup_title_height = height
def fix_size(self, *args):
if self.popup_title_height is None:
self.get_popup_title_height()
self.p.height = self.g.minimum_height + self.popup_title_height
class TheApp(App):
def build(self):
return Builder.load_string(kv)
TheApp().run()
I cheated a bit by looking at the code for Popup and the style.kv file to see how the Popup is displayed. So, if any of that is changed, this may not work.
I have found a solution for my original problem that is influenced by John Anderson's answer. I'll provide a walkthrough below for how I came to this solution.
1) Here's a photo of my original problem; I needed to dynamically set the popup height based on the widgets that are assigned to it. Before finding the below solution, my popup looked like this with the code in the OP:
As you can see, the widgets go beyond the borders of the popup.
2) I found something interesting while looking inside the popup widget with the inspector tool.
python '/path/to/your/file.py' -m inspector
Using control-e, I can click widgets and inspect their attributes. I clicked the popup button and cycled through the parent widgets until I found the popup widget.
As you can see in the photo, the popup widget has one child: a grid layout. Here are the children of that grid layout:
Interestingly, the grid layout contains:
One label, with a height of 33
One line, with a height of 4
A box layout, which contains the contents of the popup
2 units of spacing between these three widgets
12 units of padding all-around; so 24 additional units to consider for the height
3) In my solution, I have hard-written the default heights of the label, the line widget, and all default popup spacing and padding. Then, I cycle through the children inside the box layout, and add their heights. I also add 10 to those children heights, as the gridlayout that contains all of these widgets uses a spacing of 10.
Solution:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import Screen, ScreenManager
kv = '''
ScreenManagement:
id: 'manager'
BrokenPopup:
name: 'broken'
manager: 'manager'
<BrokenPopup>:
BoxLayout:
Button:
text: 'Test'
on_release: root.p.open()
'''
class ScreenManagement(ScreenManager):
pass
class BrokenPopup(Screen):
def __init__(self, **kwargs):
super(BrokenPopup,self).__init__(**kwargs)
self.p = Popup(auto_dismiss=False, size_hint_x=.6, size_hint_y=None, title='A popup')
self.g = GridLayout(cols=1, spacing=10, padding=[0,10,0,-5])
self.g.bind(minimum_height=self.fix_popup_height) # <- here's the magic
self.g.add_widget(Button(text='Test1', size_hint_y=None, height=32))
self.g.add_widget(Button(text='Test2', size_hint_y=None, height=32))
self.p.add_widget(self.g)
def fix_popup_height(self, grid, *args):
# a generalized function that, when bound to minimum_height for a grid with popup widgets,
# this will set the height of the popup
height = 0
height += 33 # for popup label
height += 4 # for popup line widget
height += 24 # for popup padding
height += 2 # for spacing between main popup widgets
for child in grid.children:
height += child.height + 10 # adds 10 for the spacing between each child
grid.parent.parent.parent.height = height # sets popup height
pass
class TheApp(App):
def build(self):
return Builder.load_string(kv)
TheApp().run()
Notable changes from the OP:
Bind the minimum_height of the widget container to the fix_popup_height() function; this will trigger each time a widget is added to the popup.
Declare the fix_popup_height() within the screen class.
Here's the fixed popup:
I'm trying to add some widgets to a Kivy mapview like this
mapview = self.mapWidget(self.lat, self.lon)
touchBarbtn1 = Button(text='Unlock')
touchBarbtn1.bind(on_press=lambda x: self.centerOnUser())
mapview.add_widget(touchBarbtn1)
But no matter what layout options I give it, it's always stuck in the bottom left corner. Even if I add more widgets they all just stack on top of each other in the bottom left corner. My Goal is to make a button on the top right corner to center the map on the user.
Use Kivy Anchor Layout.
If you are going to add more buttons, my suggestion is to use Kivy Drop-Down List.
Example
main.py
from kivy.garden.mapview import MapView
from kivy.app import App
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.button import Button
from kivy.properties import NumericProperty
class RootWidget(AnchorLayout):
lat = NumericProperty(50.6394)
lon = NumericProperty(3.057)
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
self.anchor_x = 'right'
self.anchor_y = 'top'
mapview = MapView(zoom=11, lat=self.lat, lon=self.lon)
self.add_widget(mapview)
touchBarbtn1 = Button(text='Unlock', size_hint=(0.1, 0.1))
touchBarbtn1.bind(on_press=lambda x: self.centerOnUser())
self.add_widget(touchBarbtn1)
def centerOnUser(self):
pass
class MapViewApp(App):
def build(self):
return RootWidget()
MapViewApp().run()
Output
Problem in hand is that i have a screen where i have created 4 widgets under gridlayout . Widget one is a custom widget which have a boxlayout and have 2 images and a button .
and other 3 widgets are simple buttons
Now i want to have a background image or coloured rectangle to the 1st widget but image is getting drawn at position 0,0 . I am trying to use canvas instructions to create a rectangle or Border Image but seems gridlayout shows the widget position as 0,0 and hence it created the rectangle at 0,0 . please see the code below :
Any ideas how to fix this and create rectangle as at the border of widget 1?
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen,FallOutTransition
from kivy.uix.image import Image
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.core.window import Window
from kivy.graphics import BorderImage
from kivy.graphics import Color, Rectangle ,Line
################# Images ######################
S = Image(source='images/Sti.png')
L = 'images/spinnin.gif'
################# Images ######################
class CButtonW(BoxLayout):
def __init__(self, **kwargs):
print "init --> CButton self.pos is ",self.pos
super(CButtonW, self).__init__(**kwargs)
self.orientation = 'vertical'
with self.canvas.before:
Color(1, 1, 0, 1, mode='rgba')
Rectangle(pos=self.pos,size=self.size)
BorderImage(
border=(10, 10, 10, 10),
source='images/tex.png')
self.add_widget(S)
self.add_widget(Button(text="Button 1"))
self.add_widget(Image(source=L))
class LevelScreen(Screen,GridLayout):
def __init__(self, **kwargs):
super(LevelScreen, self).__init__(**kwargs)
with self.canvas:
Line(points=(10, 10, 20, 30, 40, 50))
Color(1, 0, 1, 1, mode='rgba')
Rectangle(pos=self.pos, size=Window.size)
self.layout = GridLayout(cols=3,spacing=1,padding=10)
self.Button1 = CButtonW(id='1',text='Level 1')
self.Button2 = Button(id='2',text='Level 2')
self.Button3 = Button(id='3',text='Level 3')
self.Button4 = Button(id='4',text='Level 4')
self.layout.add_widget(self.Button1)
self.layout.add_widget(self.Button2)
self.layout.add_widget(self.Button3)
self.layout.add_widget(self.Button4)
LevelScreen.cols=3
LevelScreen.add_widget(self,self.layout)
print "position of 1st button is ",self.Button1.pos
print "position of 2 button is ",self.Button2.pos
# App Class
class MyJBApp(App):
def build(self):
sm = ScreenManager(transition= FallOutTransition())
sm.add_widget(LevelScreen(name='level'))
return sm
if __name__ == '__main__':
MyJBApp().run()
Your canvas instructions are drawn before the CButtonW is positioned by its layout, so at that point it's still in its default position of 0, 0.
You can fix it by adding code to reposition the instructions when the widget's position or size changes. The easiest way is to just use kv language in the first place, which will automatically create the relevant bindings, but you can also bind to pos or size in python, or use the on_propertyname events associated with their changes.