Kivy popup call structure and bindings not making sence - python

Below is a working snippet example of a program that presents the user with menu popups to enter info.
The issues is getting the dismiss bindings working correctly. The program flow is currently:
declare content with a return callback
Load content into a popup object
call the popup
__init__ is called and sets up something like _keyboard bindings
User enters data and presses accept
Return call back is called, the popup is no longer needed so we call popup.dismiss()
popup closes and is it
The issue is if I do _keyboard binding in the __init__ then when the popup closes I MUST call the unbind method or else the keyboard input is still calling the old popups functions!
Another thing I dislike is the return callback needing to call self._popup.dismiss(). I think it is much cleaner if the popup is completely self contained and completely reuseable. This is a numpad entry popup, it should bind the keyboard and unbind it by itself. The callback recieves an instance snapshot of the popup so the return data is easy to access. The popup itself should be the one to close itself as it knows for sure that the returnCB() was its final goal.
I have no idea how to implement this though. Binding on_dismiss inside of the __init__ does nothing at all as TouchGoToInput_dismiss is never called. I also cant figure out how to get TouchGoToInput to close itself.
Another issue is if ESC is pressed the popup closes and once again the keyboard binding is messed up.
Can anyone lend me a hand understanding the call case structure?
from kivy.app import App
from kivy.lang import Builder
from kivy.factory import Factory
from kivy.uix.gridlayout import GridLayout
from kivy.properties import ObjectProperty
from kivy.properties import StringProperty
from kivy.core.window import Window
from kivy.uix.popup import Popup
Builder.load_string('''
<TouchGoToInput>:
textInput:textInput
cols: 1
size: root.size
pos: root.pos
GridLayout:
cols: 1
size_hint_y:.25
TextInput:
size_hint_x:1.0
font_size: self.height - 15
padding_y: [self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), 0]
id:textInput
disabled: True
GridLayout:
cols: 3
Button:
text: "1"
on_release: root.addText("1")
Button:
text: "2"
on_release: root.addText("2")
Button:
text: "3"
on_release: root.addText("3")
Button:
text: "4"
on_release: root.addText("4")
Button:
text: "5"
on_release: root.addText("5")
Button:
text: "6"
on_release: root.addText("6")
Button:
text: "7"
on_release: root.addText("7")
Button:
text: "8"
on_release: root.addText("8")
Button:
text: "9"
on_release: root.addText("9")
Button:
text: "."
on_release: root.addText(".")
Button:
text: "0"
on_release: root.addText("0")
Button:
text: "Done"
on_release: root.accept()
''')
class TouchGoToInput(GridLayout):
returnCB = ObjectProperty(None)
def __init__(self, **kwargs):
super(TouchGoToInput, self).__init__(**kwargs)
self.bind(on_dismiss=self.dismiss)
print('TouchGoToInput.__init__')
def dismiss(self):
print('TouchGoToInput_dismiss')
def addText(self, text):
self.textInput.text = self.textInput.text + text
def accept(self):
print('TouchGoToInput.accept')
self.returnCB(self)
def __del__(self):
print('TouchGoToInput.__del__')
self.returnCB(self)
class TestApp(App):
def build(self):
self.popupContent = TouchGoToInput(returnCB=self.gotoLinePopup)
self._popup = Popup(title="GoTo...", content=self.popupContent,
size_hint=(0.9, 0.9))
#self._popup.bind(on_dismiss=self.main_dismiss)
return Factory.Button(text="press me", on_press=self._popup.open)
def gotoLinePopup(self, instance):
print('returnCB.text: ', instance.textInput.text)
self._popup.dismiss()
def main_dismiss(self, instance):
print('main_dismiss')
TestApp().run()

In the example, it demonstrates implementation of numpad using Popup widget with keyboard binding. It accepts input from Buttons, Keyboard, and NumPad.
Popup » dismiss
By default, any click outside the popup will dismiss/close it. If you
don’t want that, you can set auto_dismiss to False:
Popup » auto_dismiss
auto_dismiss
This property determines if the view is automatically dismissed when
the user clicks outside it.
auto_dismiss is a BooleanProperty and defaults to True.
Example
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.core.window import Window
Builder.load_string('''
#:kivy 1.11.0
<NumPad>:
title: "GoTo..."
size_hint: (0.9, 0.9)
auto_dismiss: False
<TouchGoToInput>:
textInput: textInput
cols: 1
size: root.size
pos: root.pos
GridLayout:
cols: 1
size_hint_y: .25
TextInput:
size_hint_x:1.0
font_size: self.height - 15
padding_y: [self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), 0]
id: textInput
disabled: True
GridLayout:
cols: 3
Button:
text: "1"
on_release: root.addText(self.text)
Button:
text: "2"
on_release: root.addText(self.text)
Button:
text: "3"
on_release: root.addText(self.text)
Button:
text: "4"
on_release: root.addText(self.text)
Button:
text: "5"
on_release: root.addText(self.text)
Button:
text: "6"
on_release: root.addText(self.text)
Button:
text: "7"
on_release: root.addText(self.text)
Button:
text: "8"
on_release: root.addText(self.text)
Button:
text: "9"
on_release: root.addText(self.text)
Button:
text: "."
on_release: root.addText(self.text)
Button:
text: "0"
on_release: root.addText(self.text)
Button:
text: "Done"
on_release:
app._popup.dismiss()
''')
class TouchGoToInput(GridLayout):
def addText(self, text):
self.textInput.text = self.textInput.text + text
class NumPad(Popup):
def __init__(self, **kwargs):
super(NumPad, self).__init__(**kwargs)
self.popupContent = TouchGoToInput()
self.content = self.popupContent
def on_open(self):
# erase previous textInput
self.popupContent.textInput.text = ''
# keyboard binding
self._keyboard = Window.request_keyboard(
self._keyboard_closed, self, 'text')
if self._keyboard.widget:
# If it exists, this widget is a VKeyboard object which you can use
# to change the keyboard layout.
pass
self._keyboard.bind(on_key_down=self._on_keyboard_down)
def _keyboard_closed(self):
# keyboard have been closed!
if self._keyboard is not None:
self._keyboard.unbind(on_key_down=self._on_keyboard_down)
self._keyboard = None
def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
# check for 0...9, or '.' pressed from keyboard
if (keycode[0] in list(range(48, 58))) or (keycode[0] == 46):
# keyboard: 0 / 48 to 9 / 57, or decimal / 46
self.popupContent.addText(text)
# check for 0...9, or '.' pressed from numpad
elif (keycode[0] in list(range(256, 267))):
# numpad0 / 256 to numpad9 / 265, or numpaddecimal / 266
if keycode[0] == 266:
self.popupContent.addText('.')
else:
self.popupContent.addText(keycode[1][-1:])
# Keycode is composed of an integer + a string
# If we hit escape, release the keyboard
if keycode[1] == 'escape':
keyboard.release()
# Return True to accept the key. Otherwise, it will be used by
# the system.
return True
def on_dismiss(self):
print('\tNumPad.on_dismiss: self.popupContent.textInput.text=', self.popupContent.textInput.text)
self._keyboard_closed()
class TestApp(App):
def build(self):
self._popup = NumPad()
return Button(text="press me", on_press=self._popup.open)
if __name__ == "__main__":
TestApp().run()
Output

Related

how to access popup button from main file in kivy and change its text according to situation

in my login system I want to create a popup box that will be displayed when there is an error in the username, password, or verification password. Can someone help me in how to make the popup show with different labels everytime an error happens
here is the code for main file:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
class LoginWindow(Screen):
def signIn(self):
pass
def signUp(self):
# db=open('QuizData.txt','r')
username=self.ids.uname.text
password=self.ids.passw.text
repassword=self.ids.repassw.text
if password!=repassword:
pass #i want a popup box to appear if this is true
def popup(self):
pass
class QuizWindow(Screen):
pass
class QuizScreenManager(ScreenManager):
pass
kv = Builder.load_file('loginLayoutCSS.kv')
class Login(App):
def build(self):
return kv
if __name__=='__main__':
Login().run()
here is the code for .kv file
#:kivy 2.1.0
#:import utils kivy.utils
#:import Factory kivy.factory.Factory
<MyPopup#Popup>
auto_dismiss: True
size_hint:0.6,0.3
pos_hint:{'center_x':0.5}
title: 'Error'
Button:
text:'close'
fonr_size: 20
on_release: root.dismiss()
<MyTextInput#TextInput>
font_size:25
# size_
background_normal:''
background_color: utils.get_color_from_hex('#8c8c8c')
QuizScreenManager:
LoginWindow:
QuizWindow:
<LoginWindow>:
name:'login'
BoxLayout:
orientation:'vertical'
size: root.width,root.height
spacing: 20
padding: 20
Label:
id:heading
text:'Welcome back, sign in'
font_size:32
MyTextInput:
id:uname
text:'username'
MyTextInput:
id:passw
text:'password'
MyTextInput:
id:repassw
text:'re-enter Password'
multiline:False
size_hint_y: None
height:0
opacity:0
Button:
text:'Sign In'
id:signin
size_hint_y:0.6
on_release:
app.root.current='quizwindow'
root.manager.transition.direction='up'
Button:
text:'New here? Sign up Now! click here'
id:signup
on_press:
# signin.size_hint_y = None
repassw.size_hint_y = 0.6
repassw.opacity = 1
repassw.background_color = utils.get_color_from_hex('#8c8c8c')
root.signUp()
root.popup()
signup.text = 'Sign Up'
signin.opacity = 0
<QuizWindow>:
name:'quizwindow'
Button:
text:'go back'
on_release:
app.root.current='login'
root.manager.transition.direction='down'
You can add an id to the MyPopup class to allow you to access a Label that can contain your message. Here is a modified version of that portion of your kv file:
<MyPopup#Popup>
auto_dismiss: True
size_hint:0.6,0.3
pos_hint:{'center_x':0.5}
title: 'Error'
BoxLayout:
orientation: 'vertical'
Label:
id: message
Button:
text:'close'
fonr_size: 20
size_hint: None, None
size: self.texture_size
on_release: root.dismiss()
Then you can access that Label in your python code like this:
def signUp(self):
# db=open('QuizData.txt','r')
username = self.ids.uname.text
password = self.ids.passw.text
repassword = self.ids.repassw.text
if password != repassword:
popup = Factory.MyPopup()
popup.ids.message.text = 'passwords do not match'
popup.open()

Kivy Two Buttons... How to make one of the Buttons remove the other on pressing it?

I have this code, there are two Buttons, "Normal Button" and "Remove Button" in a Screen. I need the Normal Button to be removed on pressing the "Remove Button"... Please help, need it for a imp project.
Main code:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.screenmanager import Screen,ScreenManager
class Main(Screen):
def remove(self):
self.remove_widget(self.btn) # I'm not sure what to put here
class Manager(ScreenManager):
pass
kv=Builder.load_file("btn.kv")
screen=Manager()
screen.add_widget(Main(name="main"))
class Test(App):
def build(self):
return screen
Test().run()
KV code:
<Main>:
name: "main"
GridLayout:
id: GL
cols: 1
size_hint: (.5,.5)
pos_hint: {"center_x":.5,"center_y":.5}
Button:
id: btn
text: "Remove Button"
size_hint: (.5,.5)
pos_hint: {"center_x":.5,"center_y":.6}
Button:
id: btn2
text: "Normal Button"
size_hint: (.5,.5)
pos_hint: {"center_x":.5,"center_y":.4}
on_press:
root.remove()
First, if you want the Remove Button to do the remove, then you must bind the Remove Button to the remove() method:
Button:
id: btn
text: "Remove Button"
size_hint: (.5,.5)
pos_hint: {"center_x":.5,"center_y":.6}
on_press:
root.remove(btn2)
Button:
id: btn2
text: "Normal Button"
size_hint: (.5,.5)
pos_hint: {"center_x":.5,"center_y":.4}
You can pass the Button to be removed to the remove() method by using the btn2 id as an argument to the remove() method.
Then you can use that argument in the remove() method:
class Main(Screen):
def remove(self, butt):
gl = self.ids.GL
if butt in gl.children:
gl.remove_widget(butt)
else:
print('already removed')

Kivy - re-focusing input field not possible?

i have the following example using kivy -
When i write something in the input field1 and press Reset - everything works fine (input field get deleted, focus on field1).
But when i am changing something in field2 and press the Reset Button it seems that the App gets broken...
Why is that and why is the statement self.ids.stockTicker.focus = True not working every time?
py-file:
import threading
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.core.window import Window
Builder.load_file("TryApp.kv")
class MyLayout(Widget):
Window.size = (550, 700)
def Reset(self):
self.ids.stockTicker.text = ""
self.ids.stockTicker.focus = True
self.ids.index.text = "SP500"
def pressReset(self):
threading.Thread(target=self.Reset).start()
class MyTry(App):
def build(self):
return MyLayout()
if __name__ == "__main__":
MyTry().run()
kv-file:
<MyLayout>
BoxLayout:
orientation: "vertical"
size: root.width, root.height
GridLayout:
size_hint: (1, .5)
cols: 2
Label:
text: "Field1"
font_size: 18
TextInput:
id: stockTicker
focus: True
Label:
text: "Field2"
font_size: 18
TextInput:
id: index
text: "xyz"
Button:
id: buttonReset
text: "Reset"
#font_size: 20
on_press: root.pressReset()
size_hint: (None,None)
width: 110
height: 70
I think you just need to change:
on_press: root.pressReset()
to:
on_release: root.pressReset()
The reasoning is that when the pressReset() method is triggered by the on_press event, the focus is changed as you desire, but then the Button release event changes focus back to the Button. Changing it to the on_release event eliminates that problem.

Displaying an image over multiple buttons - kivy

I want to display a single image over multiple different buttons (as shown in the picture: https://i.stack.imgur.com/BQ99R.jpg)
When I try to do so, the image gets covered by the buttons and hidden in the background.
What I want to know is that is this even possible in kivy? If yes, where do I define the image, in the label or button?
This code:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<Base>:
GridLayout:
cols: 2
size_hint: 1, 1
Button:
text: "Button 1"
on_press: print(self.text)
Button:
text: "Button 2"
on_press: print(self.text)
Button:
text: "Button 3"
on_press: print(self.text)
Button:
text: "Button 4"
on_press: print(self.text)
Image:
source: 'wolf.png'
""")
class Base(FloatLayout):
def __init__(self, **kwargs):
super(Base, self).__init__(**kwargs)
class SampleApp(App):
def build(self):
return Base()
if __name__ == "__main__":
SampleApp().run()
produces this:
Of course you have to provide an image yourself ;o)

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