Kivy - inheritance from parent? - python

I have a question concerning inheritance of sizes from parents within Kivy.
My program layout so far is something along the lines of:
GridLayout (3 cols)
|-------> widget
|-------> widget
|-------> boxlayout (with screenmanager)
|-----> relative layouts used within each screen
I'm having an issue whereby the screenmanager windows all default to size 100, 100 regardless of what I change, and I think it's something to do with the different layouts inheriting from each other, but I am unable to find the source.
Can anyone see what I'm doing wrong here, and suggest a fix?
main.py
import kivy
from kivy.app import App
from kivy.core.window import Window
from layout import MainLayout
Window.size = (581, 142)
class MyApp(App):
def build(self):
self.title = 'Test'
return MainLayout()
if __name__ == '__main__':
MyApp().run()
layout.py
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from screentest import ScreenTestWidget
class MainLayout(GridLayout):
def __init__(self, **kwargs):
super(MainLayout, self).__init__(**kwargs)
layout = GridLayout(cols=3, col_default_width=200)
layout.add_widget(Label(text='test'))
layout.add_widget(Label(text='test'))
layout.add_widget(ScreenTestWidget())
self.add_widget(layout)
screentest.py
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
Builder.load_string("""
<MenuScreen>:
RelativeLayout:
Button:
size_hint: None, None
size: root.width, root.height * 0.5
pos: 0, 50
text: 'Settings'
on_press:
root.manager.current = 'settings'
root.manager.transition.direction = 'down'
Button:
size_hint: None, None
size: root.width, root.height * 0.5
pos: 0, 0
text: 'Quit'
on_press: app.stop()
<SettingsScreen>:
RelativeLayout:
Button:
size_hint: None, None
size: root.width, root.height * 0.5
pos: 0, 50
text: 'Settings'
Button:
size_hint: None, None
size: root.width, root.height * 0.5
pos: 0, 0
text: 'Menu'
on_press:
root.manager.current = 'menu'
root.manager.transition.direction = 'up'
""")
class ScreenTestWidget(BoxLayout):
def __init__(self, **kwargs):
super(ScreenTestWidget, self).__init__(**kwargs)
# Create the screen manager
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
self.add_widget(sm)
# Declare both screens
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass

First you create a MainLayout which inherits from GridLayout. This fills the complete screen. But because you never set columns or rows it never works and will not affect children at all.
Then (in MainLayout.__init__) you create a new gridlayout which you add to the MainLayout. MainLayout doesn't work, so size_hint does nothing so the GridLayout will default to size = 100,100. The position is pos = 0,0 again by default. This layout has 3 columns and a col_default_width = 200.
To this GridLayout you now add 2 Labels and the ScreenTestWidget. TheGridlayout has now a width of 600. Everything is display as it should
The ScreenTestWidget has size_hint = 1,1 by default. Thus its size is size = 200, 100. Each screen inside also has size_hint = 1,1 by default, because you never change it. Then each screen has a relativelayout which also has size_hint = 1,1 by default.
Then the buttons in each screen have custom position and resize with the parent. The parent is the Relativelayout. The Relativelayout resizes with the Screen. The Screen resizes with the ScreenTestWidget. The ScreenTestWidget gets resized by the ColumnLayout. The Columnlayout does not resize, because its parent has no cols or rows set and thus doesn't work. Thus it defaults to 100, 100 (but then each child is resized to 200 width).
To fix this you can change the code as following:
class MainLayout(GridLayout):
def __init__(self, **kwargs):
super(MainLayout, self).__init__(**kwargs)
# instead of creating a new Layout set the attributes of this one
self.cols = 3
self.col_default_width = 200
self.add_widget(Label(text='test'))
self.add_widget(Label(text='test'))
self.add_widget(ScreenTestWidget())
Also you don't need the RelativeLayouts in the screens, because Screens are already RelativeLayouts:
<SettingsScreen>:
Button:
size_hint: None, None
size: root.width, root.height * 0.5
pos: 0, 50
text: 'Settings'
Button:
size_hint: None, None
size: root.width, root.height * 0.5
pos: 0, 0
text: 'Menu'
on_press:
root.manager.current = 'menu'
root.manager.transition.direction = 'up'
Alternatively you could also set the either rows or cols for the MainLayout.

Related

How to remove a specific Rectangle in Kivy?

I want to know how to remove a specific Rectangle object in Kivy. I create the Rectangle in .py file by pressing a button and I want to the second button could be able to remove that specific Rectangle.
My .py code:
import kivy
kivy.require("1.10.1")
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.app import App
from kivy.graphics import RoundedRectangle
class Screen1(Screen):
def create_on_press(self):
with self.canvas:
RoundedRectangle(pos = (770, 1250), size = (400, 400), size_hint = (None, None), source = "Rectangle.jpeg")
def remove_on_press(self):
pass #I don't know what to write there
class Test(App):
def build(self):
Builder.load_file("Test.kv")
sm = ScreenManager(transition = FadeTransition())
sm.add_widget(Screen1(name = "scr1"))
return sm
Test().run()
And the .kv file:
#: kivy 1.10.1
<Screen1>:
id: scr1
orientation: "vertical"
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: "Background.png"
Button:
pos: (root.width - 600) / 2, 800
size: 600, 200
text: "Create rectangle"
on_press: scr1.create_on_press()
pos_hint: {'width': 0.5, 'top': 0.8}
size_hint: None, None
Button:
pos: (root.width - 600) / 2, 200
size: 600, 200
text: "Remove rectangle"
on_press: scr1.remove_on_press
pos_hint: {'width': 0.5, 'top': 0.2}
size_hint: None, None
Thanks for any help.
I tried to use self.parent.remove_widget, but it removed the whole Screen1. However, I want to remove only this Rectangle.
One of the various ways can be adding a group attr. and remove that group later. Since RoundedRectangle is inherited from class Instruction, you can set its attr. group and later use method remove_group to remove that particular group as follows,
def create_on_press(self):
with self.canvas:
RoundedRectangle(pos = (770, 1250), size = (400, 400), size_hint = (None, None), source = "Rectangle.jpeg", group = u"rect")
def remove_on_press(self):
self.canvas.remove_group(u"rect")
Also an obvious change you need in .kv,
...
Button:
pos: (root.width - 600) / 2, 200
size: 600, 200
text: "Remove rectangle"
on_press: scr1.remove_on_press() # Call the function.
...
Thanks, it finally works. My .py:
import kivy
kivy.require("1.10.1")
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.app import App
from kivy.graphics import RoundedRectangle
class Screen1(Screen):
def create_on_press(self):
with self.canvas:
RoundedRectangle(pos = (770, 1250), size = (400, 400), size_hint = (None, None), source = "Rectangle.jpeg", group = u"rect")
def remove_on_press(self):
self.canvas.remove_group(u"rect")
class Test(App):
def build(self):
Builder.load_file("Test.kv")
sm = ScreenManager(transition = FadeTransition())
sm.add_widget(Screen1(name = "scr1"))
return sm
Test().run()
And the .kv:
#: kivy 1.10.1
<Screen1>:
id: scr1
orientation: "vertical"
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: "Background.png"
Button:
pos: (root.width - 600) / 2, 800
size: 600, 200
text: "Create rectangle"
on_press: scr1.create_on_press()
pos_hint: {'width': 0.5, 'top': 0.8}
size_hint: None, None
Button:
pos: (root.width - 600) / 2, 200
size: 600, 200
text: "Remove rectangle"
on_press: scr1.remove_on_press()
size_hint: None, None
Thanks everyone for answer.

Kivy - Calling a pulsing method from one class to another

I have the following classes in my kivy app, and i would like to call the blink method in my mainApp class. The start pulsing and blink methods enable the MainWindow background to pulse. However it's not working inside the MainWindow class and i need to call it in my mainApp class. Commented out (build method in mainApp) is what i tried, which results to the error Exception: Invalid instance in App.root. My python file:
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.core.window import Window
from kivymd.app import MDApp
from kivy.uix.image import Image
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.properties import ColorProperty
from kivy.uix.popup import Popup
from kivy.uix.floatlayout import FloatLayout
from plyer import filechooser
data = ""
class MainWindow(Screen):
def analyze_data(self):
global data
data = self.ids.user_input.text
data = analyze(data)
animated_color = ColorProperty()
pulse_interval = 4
def blink(self):
x = Clock.schedule_once(self.start_pulsing, 5)
return x
def start_pulsing(self, *args):
d = self.pulse_interval / 2
anim = Animation(animated_color=(69/255, 114/255, 147/255, 1), duration=d) + Animation(animated_color=(1, 1, 1, 1), duration=d)
anim.repeat = True
anim.start(self)
class OutputScreen(Screen):
def on_enter(self, *args):
self.ids.output_label.text = data
class mainApp(MDApp):
def __init__(self):
super().__init__()
def choose_file(self):
try:
filechooser.open_file(on_selection = self.handle_selection)
except:
pass
def handle_selection(self,selection):
global path
selection_ls = selection[0]
path = selection_ls
#print(path)
def change_screen(self,screen):
screemanager = self.root.ids['screenmanager']
screemanager.current = screen
def change(self):
self.change_screen('output')
def back(self):
self.change_screen('main')
'''
def build(self):
x = MainWindow().blink()
return x'''
and my kv file:
#:import utils kivy.utils
GridLayout:
cols:1
ScreenManager:
id: screenmanager
MainWindow:
id: main
name: 'main'
OutputScreen:
id: output
name: 'output'
<MainWindow>:
BoxLayout:
orientation:'vertical'
MDBottomNavigation:
panel_color: utils.get_color_from_hex("#ffffff")
MDBottomNavigationItem:
name:'analytics'
text:'analytics'
icon:'account-circle'
FloatLayout:
size: root.width, root.height
canvas.before:
Color:
rgba: root.animated_color
Rectangle:
pos:self.pos
size:self.size
TextInput:
multiline:True
id: user_input1
pos_hint:{"x" : 0.05, "top" : 0.9}
size_hint: 0.9, 0.37
Label:
markup: True
id:input_label
pos_hint:{"x" : 0, "top":1}
size_hint: 1 ,0.08
font_size : 32
bold: True
canvas.before:
Color:
rgb: utils.get_color_from_hex("01121c")
Rectangle:
size: self.size
pos: self.pos
Button:
pos_hint:{"top" : 0.51, "x" : 0.05}
size_hint: (None,None)
width : 150
height : 40
font_size : 23
text:'Submit'
on_press: root.analyze_data()
on_release: app.change()
Button:
pos_hint:{"top":0.42, "x":0.05}
size_hint: (None,None)
width : 150
height : 40
font_size : 23
text:'Upload'
on_release:app.choose_file()
Button:
id:'info_button'
pos_hint:{"top":0.47, "x":0.8}
size_hint: (None,None)
width : 50
height : 22
font_size : 23
text:'i'
on_release:root.analytics_info()
<OutputScreen>:
ScrollView:
GridLayout:
cols:1
MDIconButton:
icon:'arrow-left'
pos_hint:{'top':1,'left':1}
size_hint: 0.1,0.1
user_font_size : '64sp'
on_release: app.back()
Label:
id: output_label
multiline:True
text_size: self.width, None
size_hint: 1, None
height: self.texture_size[1]
color: 0,0,0,1
padding_x: 15
Any help will be much appreciated.
The build() method of an App should return a Widget that will become the root of the App. But your build() method returns a ClockEvent (the return from Clock.schedule_once()). Try changing your build() method to:
def build(self):
x = MainWindow()
x.blink()
return x
Since you do not call Builder.load_file(), I assume that your kv file is named main.kv, and therefore will get loaded automatically. If that is true, then you do not need a build() method at all. Instead add an on_start() method to your mainApp class, like this:
def on_start(self):
self.root.ids.main.blink()

Kivy: Adding a label and image to a button for a reusable widget

I am making a reusable widget in kivy that contains a couple of child widgets. One of those child widgets is a button that I want to have a text centered on the button, and a small icon aligned on the right of the button. I am trying to achieve it by adding a StackLayout to the button, but because the button is a widget, the stacklayout's position isn't inside the button. Since I am going to reuse the widget in multiple places, I don't see how I can make the position relative to the parent widget.
With the current example, the image isn't displayed at all and the text is displayed at the bottom of the app.
A complete minimal working example is on https://github.com/dolfandringa/kivy_playground/blob/master/button_label_image/
But this is the relevant code for my widget:
from pathlib import Path
from kivy.uix.button import Button
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.stacklayout import StackLayout
ICON = Path(__file__).parent.resolve() / 'caret-down-solid.png'
class MyWidget(StackLayout):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.size_hint_y = None
self.height = '32dp'
self.button = Button()
button_layout = StackLayout()
self.button.add_widget(button_layout)
label1 = Label(text="[color=000000]text1[/color]",
markup=True)
icon = Image(source=str(ICON), size=(16, 16))
button_layout.add_widget(label1)
button_layout.add_widget(icon)
self.add_widget(self.button)
and this is how the widget is being used in a sample app kv file:
#:import MyWidget widgets
GridLayout:
canvas.before:
Color:
rgb: 1,1,1
Rectangle:
size: self.size
cols: 1
Button:
text: "hello"
size_hint_x: None
size_hint_y: None
MyWidget:
size_hint_x: None
width: root.width*0.5
Button:
text: "hello2"
size_hint_x: None
size_hint_y: None
You can change the widget py file like below to achieve your target:
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
Builder.load_string("""
<MyWidget>:
size_hint: 1, None
height: '32dp'
Button:
text: 'text1'
icon_size: 16, 16
canvas:
Rectangle:
source: 'caret-down-solid.png'
size: self.icon_size
pos: self.pos[0] + self.width - self.icon_size[0], self.pos[1] + self.icon_size[1] / 2
""")
class MyWidget(BoxLayout):
pass
Here a custom property icon_size is defined for Button and used it to adjust the size and position of the icon inside Button.
Another option in addition to the one from #amras is to use a RelativeLayout like this:
class MyWidget(StackLayout):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.size_hint_y = None
self.height = '32dp'
self.button = Button(
text="text1",
pos_hint={'center_x': 0.5, 'center_y': 0.5},
size_hint=(1, 1)
)
button_layout = RelativeLayout()
self.add_widget(button_layout)
button_layout.add_widget(self.button)
icon = Image(source=str(ICON), size=(16, 16), size_hint=(None, None))
icon.reload()
icon.pos_hint = {'center_x': 0.95, 'center_y': 0.5}
button_layout.add_widget(icon)

Use On_Press Event to Change Screen without KV Language for Dynamically Created Buttons

Question:
How do you use an On-Press event to change screen for dynamically created buttons in python, and without using KV language?
Goal:
Be able to navigate to new screen from clicking on dynamically created button,
[without needing to create button in Kivy, and still getting to use Screenmanager in both Python and Kivy (not sure if you have to stick with either Python or Kivy throughout entire program?]
Things I've already tried:
Using button_share.bind(on_press = self.changer), then this:
def changer(self,*args):
ScreenManager()
screenmanager.current = 'MainScreen'
But I get the error ScreenManagerException: No Screen with name "MainScreen".
Suspicion:
I think this is because I'm creating a new instance of ScreenManager, instead of referencing the existing one. To combat this issue, I considered instantiating Screenmanager() in the App class, then referencing that instantiation in the my button def changer(self, *args) method, but that's useless if it's not the same ScreenManager I'm actually using for all my screens. And those are all defined in KV language. I wouldn't be able to switch them all over without a substantial amount of effort.
Using:
button_share.bind(on_press=partial(app.sm.setter('current'), (app.sm, "MainScreen")))`
But the error I get here is ValueError: ScreenManager.current accept only str
Below is a fully runnable example:
Note: In this example, I want to click 'Continue Editing' button, then click on 'Test 1', 'Test 2' or 'Test 3' button and have it take me to another screen.
Python Code:
from kivy.app import App
# kivy.require("1.10.0")
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.clock import Clock
from kivy.uix.widget import Widget
#from kivy.base import runTouchApp
from kivy.properties import StringProperty, ObjectProperty, NumericProperty
from functools import partial
class ScrollableLabelDataEntryInstructions(BoxLayout):
pass
class NewGarageScreen(Screen):
pass
class ContinueEditingScreen(Screen):
pass
class GarageNameBoxLayout(BoxLayout):
box_share2 = ObjectProperty()
sm = ScreenManager()
def __init__(self, **kwargs):
super(GarageNameBoxLayout, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_interval(self.create_button, 5)
def create_button(self, *args):
self.box_share2.clear_widgets()
app = App.get_running_app()
#put GarageNameStartList data into app class, then pull from it in this class
top_button_share = 1.1
color = (.4, .4, .4, 1)
for i in range(len(app.GarageNameStartList)):
top_button_share -= .4
id_ = app.GarageNameStartList[i]
button_share = Button(background_normal='',
background_color = color,
id = id_,
pos_hint = {"x": 0, "top": top_button_share},
size_hint_y = None,
height = 60,
font_size = 30,
text = app.GarageNameStartList[i])
button_share.bind(on_press = self.changer)
#button_share.bind(on_press=partial(app.sm.setter('current'), (app.sm, "MainScreen")))
self.box_share2.add_widget(button_share)
def changer(self,*args):
ScreenManager()
#app = App.get_running_app()
screenmanager.current = 'MainScreen'
class BackHomeWidget(Widget):
pass
class MainScreen(Screen):
pass
class AnotherScreen(Screen):
pass
class ScreenManagement(ScreenManager):
pass
presentation = Builder.load_file("example_on_press.kv")
class MainApp(App):
GarageNameStartList = ["Test1", "Test2", "Test3"]
def Update_GarageNameStartList(self, *args):
self.GarageNameStartList = ["Test1", "Test2", "Test3"]
def build(self):
return presentation
if __name__ == "__main__":
MainApp().run()
KV Code:
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
ScreenManagement:
transition: FadeTransition()
MainScreen:
AnotherScreen:
NewGarageScreen:
ContinueEditingScreen:
<SmallNavButton#Button>:
font_size: 32
size: 125, 50
color: 0,1,0,1
<MedButton#Button>:
font_size: 30
size_hint: 0.25, 0.1
color: 0,1,0,1
<BackHomeWidget>:
SmallNavButton:
on_release: app.root.current = "main"
text: "Home"
pos: root.x, root.top - self.height
<MainScreen>:
name: "main"
FloatLayout:
MedButton:
on_release: app.root.current = "edit"
text: "Edit"
pos_hint: {"x":0.3728, "top": 0.4}
<AnotherScreen>:
name: "edit"
BackHomeWidget:
SmallNavButton:
on_release: app.root.current = "main"
text: "Back"
pos: root.x, root.top - (2.25*(self.height))
FloatLayout:
MedButton:
on_release: app.root.current = "continueediting"
text: "Continue Editing"
pos_hint: {"x":0.25, "top": 0.6}
MedButton:
on_release: app.root.current = "newgarage"
text: "Create New"
pos_hint: {"x":0.3728, "top": 0.4}
<NewGarageScreen>:
name: "newgarage"
BackHomeWidget:
SmallNavButton:
on_release: app.root.current = "edit"
text: "Back"
pos: root.x, root.top - (2.25*(self.height))
FloatLayout:
MedButton:
text: "1. Groundfloor"
pos_hint: {"x":0, "top": 0.6}
<GarageNameBoxLayout>:
box_share2: box_share2
ScrollView:
GridLayout:
id: box_share2
cols: 1
size_hint_y: None
size_hint_x: 0.5
spacing: 5
padding: 130
height: self.minimum_height
canvas:
Color:
rgb: 0, 0, 0
Rectangle:
pos: self.pos
size: self.size
<ContinueEditingScreen>:
name: "continueediting"
GarageNameBoxLayout:
BackHomeWidget:
SmallNavButton:
on_release: app.root.current = "edit"
text: "Back"
pos: root.x, root.top - (2.25*(self.height))
Your code can be improved in the following things:
You do not have to create box_share2 in the .py since you're creating it in the .kv
When you use sm = ScreenManager() you are creating another ScreenManager different from the original, that is not necessary.
It is not necessary to use range and len, make your code less readable, you just have to iterate.
If we look at the structure of the .kv we see that the presentation object is the ScreenManager so you can get it via app.root.
Using the above your code the solution is:
[...]
class GarageNameBoxLayout(BoxLayout):
def __init__(self, **kwargs):
super(GarageNameBoxLayout, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_interval(self.create_button, 5)
def create_button(self, *args):
self.box_share2.clear_widgets()
app = App.get_running_app()
sm = app.root
#put GarageNameStartList data into app class, then pull from it in this class
top_button_share = 1.1
color = (.4, .4, .4, 1)
for text in app.GarageNameStartList:
top_button_share -= .4
id_ = text
button_share = Button(background_normal='',
background_color = color,
id = id_,
pos_hint = {"x": 0, "top": top_button_share},
size_hint_y = None,
height = 60,
font_size = 30,
text = text)
button_share.bind(on_press=lambda *args: setattr(sm, 'current', "main"))
self.box_share2.add_widget(button_share)
[...]

How to unbind a property automatically binded in Kivy language?

The Kivy Language automatically creates internal binds in properties. For example, if we assign the position of the parent to the position of the child, then the position of the child is going to be updated automatically:
Widget:
Label:
pos: self.parent.pos
In this case, if we move the parent Widget, then the child is also going to move. How do I unbind the property pos from the child? I know how to unbind (properties)[http://kivy.org/docs/api-kivy.uix.widget.html#using-properties] that I bind myself but how do I unbind them if I don't know the name of the method it is bound.
Here is a small example to show what I mean. The Button Up moves the GridLayout to the top and Down to the Bottom. The Button Center center itself in the middle of the screen. My problem is that when I click Up or Down my Centered button is not anymore.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<Example>:
GridLayout:
id: _box
cols: 3
size_hint: .7, .3
pos_hint: {'center_x': .5}
x: 0
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _box.y = 0
text: "Down"
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: self.center_y = root.height/2
text: "Out of the Grid"
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _box.top = root.height
text: "Up"
""")
class Example(FloatLayout):
pass
class ExampleApp(App):
def build(self):
return Example()
if __name__ == "__main__":
ExampleApp().run()
Why do I want to do that in any case? I am using an animations on the GridLayout that constantly updates the position. The normal position of the buttons should be inside the gridlayout but once in a while one of the buttons flies over the screen and come back to the same position. The problem is that I cannot make them fly while my gridlayout is also moving because the property is bound and as soon as the button try to fly it goes back to the grid. That also means that the binding is sometimes desirable. What I want is have control of the bind and unbind.
Comments don't seem to be working right now so I'll post this as a answer.
You already have a FloatLayout(your root widget). Use that instead of
creating a new FloatLayout.
Before removing the widget from the grid.
store it's size,
set size_hint to None, None
set pos_hint to position the widget in the center.
When adding the widget to grid do the reverse.
Here's your code with these fixes::
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<Example>:
center_button: _center_button
center_widget: _center_widget
grid:_grid
GridLayout:
id: _grid
cols: 3
size_hint: .7, .3
pos_hint: {'center_x': .5}
x: 0
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _grid.y = 0
text: "Down"
Widget:
id: _center_widget
Button:
id: _center_button
pos: self.parent.pos
size: self.parent.size
on_press: root.centerize(*args)
text: "Out of the Grid"
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _grid.top = root.height
text: "Up"
""")
class Example(FloatLayout):
def centerize(self, instance):
if self.center_button.parent == self.center_widget:
_size = self.center_button.size
self.center_widget.remove_widget(self.center_button)
self.center_button.size_hint = (None, None)
self.add_widget(self.center_button)
self.center_button.pos_hint = {'center_x': .5, 'center_y':.5}
else:
self.remove_widget(self.center_button)
self.center_button.size_hint = (1, 1)
self.center_widget.add_widget(self.center_button)
self.center_button.size = self.center_widget.size
self.center_button.pos = self.center_widget.pos
class ExampleApp(App):
def build(self):
return Example()
if __name__ == "__main__":
ExampleApp().run()
Update 1:
If for whatever reason you still need to unbind the properties bound by kvlang you can do so using introspection to get a list of observers for the property. so for your case it would be something like this::
observers = self.center_widget.get_property_observers('pos')
print('list of observers before unbinding: {}'.format(observers))
for observer in observers:
self.center_widget.unbind(pos=observer)
print('list of observers after unbinding: {}'.format(self.center_widget.get_property_observers('pos')))
You would need to use the latest master for this. I should fore-warn you to be extremely careful with this though you'd need to reset the bindings set in kvlanguage, but then you loose the advantage of kv language... Only use this If you really understand what you are doing.
Following #qua-non suggestion, I am temporarily moving the child to another parent. It actually unbinds it, or maybe, rebinds it to the new parent. This is a partial solution because of whatever reason, it doesn't update the position automatically when I took it out of the GridLayout (i.e. when I press enter) and put it into the new parent. I need to press 'Up' (or 'Down') after the 'Out of the Box' button.
However, it does go back immediately. When you click again on the 'Out of the box' button the 2nd time, it goes back to its original position. This part works perfectly. And it continue obeying to its parent instructions.
In other words, it doesn't work immediately when I take out of the grid but it does when I put it back.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<Example>:
center_button: _center_button
center_widget: _center_widget
float: _float
grid:_grid
GridLayout:
id: _grid
cols: 3
size_hint: .7, .3
pos_hint: {'center_x': .5}
x: 0
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _grid.y = 0
text: "Down"
Widget:
id: _center_widget
Button:
id: _center_button
pos: self.parent.pos
size: self.parent.size
on_press: root.centerize(*args)
text: "Out of the Grid"
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _grid.top = root.height
text: "Up"
FloatLayout:
id: _float
size_hint: None,None
""")
class Example(FloatLayout):
def centerize(self, instance):
if self.center_button.parent == self.center_widget:
self.center_widget.remove_widget(self.center_button)
self.float.add_widget(self.center_button)
self.float.size = self.center_button.size
self.float.x = self.center_button.x
self.float.center_y = self.center_y
else:
self.float.remove_widget(self.center_button)
self.center_widget.add_widget(self.center_button)
self.center_button.size = self.center_widget.size
self.center_button.pos = self.center_widget.pos
class ExampleApp(App):
def build(self):
return Example()
if __name__ == "__main__":
ExampleApp().run()
Here is something very similar to what I was trying. The difference is that I ended binding the properties manually so I can unbind them. Basically if I uncomment the line #pos: self.parent.pos of the 'out of the box' button, then I cannot unbind them unless I assign the Button to another parent as #qua-non suggested.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<Example>:
center_button: _center_button
GridLayout:
cols: 3
size_hint: .7, .3
pos_hint: {'center_x': .5}
Button:
on_press: self.parent.y = 0
text: "Down"
Widget:
Button:
id: _center_button
size: self.parent.size
#pos: self.parent.pos
on_press: root.centerize(*args)
text: "Out of the Grid"
Button:
on_press: self.parent.top = root.height
text: "Up"
""")
class Example(FloatLayout):
def __init__(self, **kwargs):
super(Example, self).__init__(**kwargs)
self.center_button.parent.bind(pos=self.binder)
self.centered = False
def binder(self, instance, value):
self.center_button.pos = instance.pos
def centerize(self, instance):
if self.centered:
self.center_button.parent.bind(pos=self.binder)
self.center_button.y = self.center_button.parent.y
self.centered = False
else:
self.center_button.parent.unbind(pos=self.binder)
self.center_button.center_y = self.height/2
self.centered = True
class ExampleApp(App):
def build(self):
return Example()
if __name__ == "__main__":
ExampleApp().run()

Categories