ScrollView in a popup appearing at the bottom and too small? - python

I am trying to add a ScrollView to a popup in kivy but the scroll view only seems to be taking up a small portion of the popup and down the bottom for some reason.
The only thing I can think to do is to set the height of the scroll view explicitly but I don't know how to do this because it needs to scale with the window size and the popup.height seems to include the header bar.
Here is my code:
scroll = ScrollView(size_hint=(1, None))
popup = Popup(title='Thanks Stack Overflow!', size_hint=(0.9, 0.9), content=scroll)
box = BoxLayout(orientation='vertical', size_hint=(1, None))
scroll.add_widget(box)
box.bind(minimum_height=box.setter('height'))
for i in range(1000):
box.add_widget(Button(text='test button {}'.format(i), size_hint=(1, None)))
and this is the result I get:
It is scrollable and works fine it is just that it doesn't take up the correct space.
EDIT: So for now I am setting the height manually to be with respect to the popup size as it seems like the popup title doesn't change too much. But I would still like a better method if available.

Here is my updated python code file
from kivy.uix.popup import Popup
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
from kivy.uix.boxlayout import BoxLayout
from kivy.app import runTouchApp
scroll = ScrollView()
popup = Popup(title='Thanks Stack Overflow!', size_hint=(0.9, 0.9), content=scroll)
grid = GridLayout(cols=1, size_hint=(1, None))
scroll.add_widget(grid)
grid.bind(minimum_height=grid.setter('height'))
for i in range(1000):
grid.add_widget(Button(text='test button {}'.format(i), size_hint=(1, None)))
box = BoxLayout()
box.add_widget(popup)
runTouchApp(box)
I removed the size_hint(1, None) for the ScrollView. Now they are the default value (1,1). Furthermore it always makes sense to use GridLayout with ScrollView and not BoxLayout. BoxLayout just takes the size of its parent. You need something bigger than its parent, so that it is scrollable.
Sidenote: Take a look at kivy files, I think they make it a lot easier.
Also, I would have set the height of the Buttons. I don't know where they get the height if you don't set them. I did not add that, since I wanted to keep my solution as close as possible to your code.

Related

Flip a widget in kivy

How do I flip a (custom) kivy Widget from Python code? I've tried setting its height or width to negative value, but it actually rotates widget, ie flips both axes. The Rotate instruction obviously does the same. I'm aware there is a flip_horizontal() method of the Texture, but I don't have a slightest idea how to actually cause it to affect an existing widget rather than in-memory texture from eg Atlas.
If it helps, I use kivy 1.9.2 with Python 3.4.3.
UPD I tried Scale() to no effect.
event.actor.widget.img.canvas.before.add(PushMatrix())
event.actor.widget.canvas.before.add(Scale(x=2.0, origin=event.actor.widget.center))
event.actor.widget.img.canvas.after.add(PopMatrix())
event.actor.widget.canvas.ask_update()
It doesn't work either with or without PushMatrix/PopMatrix. The widget in question is a simple Widget subclass with stretchable image and a couple callbacks for size and position.
An easy way to achieve your goal is using a Scatter. Here is a full working example
from kivy.lang import Builder
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.boxlayout import BoxLayout
import random
from kivy.graphics.transformation import Matrix
class MyBox(BoxLayout):
def later_(self, dt=None):
self.t.apply_transform(Matrix().scale(-1, 1.0, 1.0),
post_multiply=True,
anchor=self.to_local(*self.center))
Clock.schedule_once(self.later_, 1.0)
class TestApp(App):
def build(self):
Builder.load_string("""\
<MyBox>:
t: s
orientation: 'vertical'
Scatter:
id: s
do_scale: 0
do_rotate: 0
do_translation: 0,0
Label:
pos: s.pos
size: s.size
text: "The text below will keep changing using a delayed function..."
"""
)
mybox = MyBox()
Clock.schedule_once(mybox.later_, 1.0)
return mybox
if __name__ == '__main__':
TestApp().run()
How about a Scale instruction with a negative scaling?
If the widget also needs to take touch events, you'll need to modify its on_touch_down to transform them appropriately.

Kivy: Add ScreenManager to window not as root?

I'm using Kivy and I'm trying to setup a ScreenManager, but I don't want that ScreenManager to be the root widget in my window. Here's a test code snippet that demonstrates what I'm trying to do. (This code demonstrates my problem.)
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.widget import Widget
class MyRootWidget(Widget):
def __init__(self, **kwargs):
super(MyRootWidget, self).__init__(**kwargs)
self.screen_manager = ScreenManager()
self.add_widget(self.screen_manager)
# self.add_widget(Label(text='label direct'))
class MyApp(App):
def build(self):
root = MyRootWidget()
new_screen = Screen(name='foo')
new_screen.add_widget(Label(text='foo screen'))
root.screen_manager.add_widget(new_screen)
root.screen_manager.current = 'foo'
for x in root.walk():
print x
return root
if __name__ == '__main__':
MyApp().run()
When I run this code, the window is blank, though I would expect that it would show the text "foo screen"?
The print of the walk() command shows that the root widget contains the screenmanager, my screen, and the label, like this:
<__main__.MyRootWidget object at 0x109d59c80>
<kivy.uix.screenmanager.ScreenManager object at 0x109eb4ae0>
<Screen name='foo'>
<kivy.uix.label.Label object at 0x109ecd390>
So that's working as I would expect.
If I uncomment the line which adds the label widget directly to the root widget, that label shows up as expected.
Also if I change the MyApp.build() method so that it returns new_screen instead of returning root, it also works in that I see the label "foo screen" on the display.
BTW, the reason I want to not have the screen manager be the root widget is because I want to be able to print messages (almost like popups) on the screen in front of whichever screen is active (even if screens are in the process of transitioning), so I was thinking I would need the screen manager not to be root in order to do that?
Also my ultimate goal is to have multiple "quadrants" in the window, each with its own screen manager, so I was thinking I needed to make sure I can show screen managers that are not the root widget in order to do this.
So if there's a better way for me to do this, please let me know. Also I do not want to use .kv files for this as I want to set this entire environment up programmatically based on other config options (which I've left out of this example.)
Overall though I wonder if anyone knows why this code doesn't work?
Thanks!
Brian
The problem is you are sticking your ScreenManager in a generic Widget. If you put it in a Layout, it will display properly, ie:
class MyRootWidget(BoxLayout):
There are several layouts available: http://kivy.org/docs/gettingstarted/layouts.html

Kivy CheckBox Looks Like Solid Black Box (Not a Checkbox)

I am making a BoxLayout widget (orientation = 'horizontal') that contains three widgets inside of it, a label, a text box, and a check box.
thisRow = BoxLayout(orientation='horizontal')
l = Label(text='Enter plate 1:\n(Plate #)')
t = TextInput(text = 'this is a text box')
c = CheckBox()
thisRow.add_widget(l)
thisRow.add_widget(t)
thisRow.add_widget(c)
This produces the following widget (thisRow):
After the box is checked...
The rightmost black box is actually the checkbox, and works functionally, however there is no way for the user to know that it is in fact a checkbox. I would expect a smaller empty square in the middle, as is depicted in pictures here.
How do i get the traditional checkbox image (smaller empty square box)? Or generally, how can I make it more obvious that the box is a check box and not just an empty label?
Thank you
This is really interesting question and Malonge tried it in a good way. Right now(1.9.2-dev) there is still fixed size on CheckBox's well, call it a background. It's an image that Widget takes from atlas and changes if the state changes. Therefore until now there was no clear way how to do it. Here is an example. Soon on master there'll be CheckBox(color=[r,g,b,a]) option. Thanks ;)
from kivy.lang import Builder
from kivy.base import runTouchApp
from kivy.uix.boxlayout import BoxLayout
Builder.load_string('''
<CheckBoxBG>:
Label:
TextInput:
CheckBox:
canvas.before:
Color:
rgb: 1,0,0
Rectangle:
pos:self.center_x-8, self.center_y-8
size:[16,16]
Color:
rgb: 0,0,0
Rectangle:
pos:self.center_x-7, self.center_y-7
size:[14,14]
''')
class CheckBoxBG(BoxLayout):pass
runTouchApp(CheckBoxBG())
Looks like the smaller check box is hidden when the background color is black. Here is an example of a red background.
It's not ideal because I do like the black background, but I can run with it for now. If anyone knows how to do this with a black background that would be great. Thank you
Alternatively, to change your checkboxes background, you can use another image from the atlas or create images and then load them:
mycheckbox= CheckBox(
background_checkbox_normal ='tlas://data/images/defaulttheme/button_disabled'
background_checkbox_down = 'my_checkboxes_checked.png'
)
In Kivy 1.9.2.dev0 (and apparently since version 1.9.0) you can change the background image of checkboxes. By default, Kivy uses for these backgrounds from the atlas*.
background_checkbox_normal = StringProperty('atlas://data/images/defaulttheme/checkbox_off') #when the checkbox is not active.
background_checkbox_down = StringProperty('atlas://data/images/defaulttheme/checkbox_on') # when the checkbox is active.
background_checkbox_disabled_normal = StringProperty('atlas://data/images/defaulttheme/checkbox_disabled_off') #when the checkbox is disabled and not active.
background_checkbox_disabled_down = StringProperty('atlas://data/images/defaulttheme/checkbox_disabled_on') #when the checkbox is disabled and active.
You can have a look here at all the attributes :
*The atlas is a package of multiple textures that reduces the number of images loaded and speedup the application loading. You have see a preview of the atlas in Python Install Folder\Lib\site-packages\kivy\data\images\defaulttheme-0.png

Error on ScrollView+StackLayout when binding minimum_height on Kivy

I'm new to Kivy and I am trying to create a scroll view based the official ScrollView example on Kivy docs.
I'm using the Kivy portable package for Windows with Python version 3.3.3.
When i try to run the code below with the layout.bind line uncommented i got repeated lines of the following error:
Warning, too much iteration done before the next frame. Check your code, or increase the Clock.max_iteration attribute
When i comment the layout.bind line I get a normal startup with the buttons i added where i would expect them, but the scroll doesn't work.
from kivy.app import App
from kivy.uix.scrollview import ScrollView
from kivy.uix.stacklayout import StackLayout
from kivy.uix.button import Button
class Example(App):
def build(self):
layout = StackLayout(size_hint_y=None)
# If i uncomment this line, i got the error:
# layout.bind(minimum_height=layout.setter('height'))
for i in range(30):
btn = Button(text=str(i), size_hint=(.33,.8))
layout.add_widget(btn)
root = ScrollView(size_hint=(None,None), size=(400, 400))
root.add_widget(layout)
return root
if __name__ == '__main__':
Example().run()
The question is why the scroll doesn't work? and why the layout.bind is causing an error.
How should i do to have the same visual and the scroll working on x axis without the error?
I made this piece of code as close as possible to the Kivy official example.
This is happening because you're creating an infinite loop. The size of each Button is set based on the size of the StackLayout. This causes the StackLayout to increase in size, causing the Button sizes to be recalculated, causing the StackLayout to increase in size, ad infinitum.
You're telling Kivy that you want the Button size based on the StackLayout size, while also wanting the StackLayout size based on the combined Buttons size.
To fix this, you need to specify a real size instead of using size_hint:
btn = Button(text=str(i), size_hint=(None, None), size=(100, 100))

Image is the wrong size in Kivy

I'm trying to use Kivy to display a background image, so I want the root widget to be the same size as the image. Currently, when I load the image from the kv file it appears as a small thumbnail in the bottom-left corner. The app window appears to be about the correct (full-scale) size, but hard to tell. Code below, any thoughts?
.kv file:
#:kivy 1.8.0
<BarBot_splash>:
BoxLayout:
size: image.size
Image:
id: image
source: 'MainMenu.png'
.py file:
import kivy
kivy.require('1.8.0')
from kivy.app import App
from kivy.uix.widget import Widget
class BarBot_splash(Widget):
def on_touch_up(self, touch):
if touch.x < self.width/3:
#we touched the menu button
pass
elif touch.x > self.width/3*2:
#we touched the custom button
pass
else:
return False
class BarBot(App):
def build(self):
return BarBot_splash()
if __name__=='__main__':
BarBot().run()
In main.py:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
class BarBot_splash(BoxLayout): # subclass BoxLayout.. now it's inherently a Boxlayout
kv:
#:kivy 1.8.0
<BarBot_splash>: # I'm a BoxLayout...
Image:
id: image
source: 'MainMenu.png'
That should do the trick. The BoxLayout should take up the window. It's one child, the Image, should in turn take up the full size of the BoxLayout. Why? Well, someone correct me if I'm wrong, but I think it's because the size_hint property of the BoxLayout and the Image both default to (1, 1), which translates to: "Take up as much space in your parent as you can" or (100% width, 100% height). Though it may not be possible for a child to take up all of it's parents area if there are also other children in the parent, like if you had a few Images in the BoxLayout, or more than one BoxLayout in your app etc.. Setting a size_hint to (.5, .3) would mean take up (50% the width, 30% the height) of your parent/container, or available space.
BarBot_splash is just a widget, so it doesn't apply any position or size to its children, therefore the boxlayout (and thus its child image) have only the default position of (0, 0) and size of (100, 100).
Change BarBot_splash to a BoxLayout or other resizing layout and this will propagate correctly. You also don't need the size: image.size line, this does nothing.
Obviously very old question but I'm new to Kivy and just had to work through a very similar problem and there was very little help on the topic, so this is for future people like me:
I had the same problem but the image was also in a scatter widget. Solution is really simple, just frustrating to find. Hierarchy has to be as follows:
BoxLayout:
Scatter:
size: image.size
Image:
id: image
size: root.size

Categories

Resources