Reputation: 4043
I have a ScrollView
and a Bubble
, that partially overlaps it and contains a GridLayout
Questions:
I've read answers to a question on that topic, the suggestions were either to combine the disabled
and opacity
properties, which is what I ended up using, or to temporarily move the widget off-screen. Using the first way to hide the Bubble
, I found out that even if it is disabled, it blocks the scrolling of the view behind it, even though the documentation states that this property
Indicates whether this widget can interact with input or not
So I would assume it shouldn't have blocked the scrolling. Interesting enough, when it wasn't hidden (disabled=False
), the scrolling passed right through it, which is even more confusing
I also had that Bubble
before contain a ScrollView
, which, in turn, held that GridLayout
. The following question is not an issue anymore, but still an interesting behaviour:
Bubble
pass the scrolling up, but didn't pass the scrolling down?To understand what I mean, run the code, mouse over the Bubble
and try scrolling in different directions using the mouse wheel. That is considering the GridLayout
in the ScrollView
contains nothing, even though it doesn't affect the behaviour
Here is the code for both questions with some instructions to get the needed behaviour:
from kivy.app import App
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.screenmanager import Screen
from kivy.uix.widget import Widget
from kivy.uix.textinput import TextInput
from kivy.uix.scrollview import ScrollView
from kivy.uix.gridlayout import GridLayout
from kivy.uix.bubble import Bubble
from kivy.properties import ListProperty
Builder.load_string('''
<SmileBubble>:
size_hint: None, None
pos: 220, 90
size: 175, 250
#ScrollView:
#GridLayout:
#rows: 8 # To see the second question's example, uncomment this section
# and comment out the one below
GridLayout:
rows: 8
<MessageView>:
canvas:
Color:
rgba: 1, 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
<Message>:
BoxLayout:
pos: root.pos
height: self.height
TextInput:
pos: root.pos
size: root.size
id: msg
''')
class Message(Widget):
bg_color = ListProperty([0.99, 0.99, 0.99, 1])
class SmileBubble(Bubble):
def hide(self):
self.disabled = True
def show(self):
self.disabled = False
class MessageView(ScrollView):
pass
class TestApp(App):
def msg_in(self, text):
msg = Message()
msg.ids['msg'].text = text
msg.size_hint = [None, None]
msg.width = 160
self.msg_layout.add_widget(msg)
def build(self):
self.scr = Screen()
self.sv1_main = MessageView()
self.msg_layout = GridLayout(cols = 1,
size_hint_y = None)
self.msg_layout.bind(minimum_height = self.msg_layout.setter('height'))
self.smile_bbl = SmileBubble()
for i in range(10):
self.msg_in("test")
self.smile_bbl.hide() # To hide/show the Bubble, comment out this line. For the second question, comment out this line
self.scr.add_widget(self.sv1_main)
self.sv1_main.add_widget(self.msg_layout)
self.scr.add_widget(self.smile_bbl)
return self.scr
TestApp().run()
If it matters, I'm using Kivy v1.9.2-dev0
Upvotes: 0
Views: 623
Reputation: 12199
If you don't want opacity&disabled trick (nice one) or something like y = 5000
that doesn't work well in e.g. BoxLayout - and clearly using that would result in stretching your ScrollView
, I see "two" simple options though not really without removing - yet with preserving!
First one is to take all canvas instructions if you can access them, copy them somewhere and canvas.clear
, but good luck finding a bug if something goes wrong.
The second one is basically the first one, but in three commands and you can't screw something up except if you forget where did the widget go(hehe):
from kivy.lang import Builder
from kivy.base import runTouchApp
from kivy.uix.boxlayout import BoxLayout
Builder.load_string('''
<Test>:
orientation: 'vertical'
Button:
on_release: root.move_me()
BoxLayout:
id: box
Button:
text: 'Hi %s!' % self.parent
Button:
id: box2
''')
class Test(BoxLayout):
def move_me(self):
if self.ids.box.children:
button = self.ids.box.children[0]
self.ids.box.remove_widget(button)
self.ids.box2.add_widget(button)
else:
button = self.ids.box2.children[0]
self.ids.box2.remove_widget(button)
self.ids.box.add_widget(button)
runTouchApp(Test())
You can see that the widget is still present in a variable, you can access its properties via button.<something>
as you can see in the text
.
Now what may happen if you use this in e.g. Boxlayout
or GridLayout
in a wrong way: widgets' pos
inside the parent gets updated and exactly as in a list [1, 2, 3].remove(2)
the final list would be [1, 3]
, which in BoxLayout
means dividing size into halves and not into thirds.
How to fix this? Simply, again use the trick from above, but now save more widgets - the one you want to hide + every widget that is added to parent later
my_widgets = <parent>.children[<widget you want to hide>:len(<parent>.children)-1]
which will give you a list of objects i.e. again preserving everything as is and you'll only "pop" the widget you don't want to see. Finally make a placeholder(e.g. Widget
or anything transparent) with the same size
(and maybe even pos
, but this is calculated automatically, so...) of the widget you want to hide and:
for child in my_widgets:
<parent>.add_widget(child)
This method(or rather my explaining) may seem a difficult, but is more simple than throwing away a widget out of parent's bounding box or trying to disabled=True
, which for you resulted in not being able to scroll(otherwise I'd definitely go for that). Replace Bubble
with Widget
and you'll be able to scroll. Ofc it'll be placed to [0, 0], but that's not an argument as if you make it visible with Color
&Rectangle
, scrolling with a cursor placed on Widget
works (at least for me on master
with your code).
Finally: make a function from that
from kivy.lang import Builder
from kivy.base import runTouchApp
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
Builder.load_string('''
<Test>:
orientation: 'vertical'
Button:
text: 'hide'
on_release: root.hide(box)
BoxLayout:
id: box
Button:
text: 'Hi %s!' % self.parent
Button
text: 'Retrieve'
on_release: root.hide(root.placeholder, root.saved)
''')
class Test(BoxLayout):
def hide(self, what, retrieve=None):
# you'll need that object accessible and ".parent" will disappear
parent = what.parent
children = what.parent.children[:]
# check for position in children list
place = children.index(what)
# save the widget you want to hide
self.saved = children[place]
# save children from the latest added to the removed one
saved_children = children[0:place+1]
# sizes are optional here
self.placeholder = Widget(size_hint=[None, None],
size=saved_children[0].size)
for child in saved_children:
parent.remove_widget(child) # here you still can use what.parent
# here the ".parent" is not available - the reason for "parent" var.
# add Widget instead of whatever you will "hide"
# pass to "retrieve" the saved widget if you want it back
parent.add_widget(self.placeholder if not retrieve else retrieve)
# add widgets in order they were originally added to the parent
for child in list(reversed(saved_children[:place])):
parent.add_widget(child)
# cleanup mess ^^
del children, saved_children
runTouchApp(Test())
Upvotes: 1