Python & Kivy - Show / Hide Box - python

I built an app which is taking photo and goes to another empty screen and runs my main code on the background. I want to show a text input box in the empty screen while my main code is in its 1st if condition; and hide the box while the code is in 2nd if condition. My code is in below. I wrote "blablabla"s for unnecessary long things for my question.
class CheckScreen(Screen):
def deneme(self):
#MY MAIN CODE
#...
if(BLABLABLA)
self.isShownMenu = BooleanProperty(True)
else
self.isShownMenu = BooleanProperty(False)
GUI = Builder.load_string("""
#BLABLABLA1
#...
<SingleLineTextInput#TextInput>:
pos_hint: {'center_x': .5, 'center_y': .4}
size_hint: 0.5, 0.05
multiline: False
<CheckScreen>:
#BLABLABLA2
#...
SingleLineTextInput:
opacity: 1 if root.isShownMenu else 0
""")
class TestCamera(App):
def build(self):
return GUI
TestCamera().run()
While I'm running this, app always shows a textinput even I'm changing True to False in conditions. Where is my problem?

Your BooleanProperty needs to be defined at the class level:
class CheckScreen(Screen):
isShownMenu = BooleanProperty(True)
Use True or False as desired. Then in your code just reference self.isShownMenu, like:
if(BLABLABLA)
self.isShownMenu = True
else
self.isShownMenu = False

Related

Can't make ids work in Python! Help needed for Kivy newbie

I know that you will think I am an absolute newbie - and you are right!! Maybe some of you will be so kind as to help me anyway :)
I am making a little homedevice with a screen for controlling and running the lights (Phillips Hue), showing the weather an so forth.
I have made a script for running the lights which works fine. I am now trying to build an interface as well. The interface should update the buttonstates on the run as the script runs in the background.
I am trying to create a function that will update the lights on a regular basis using the clock function in Kivy. A have tried using Properties, I have tried using ids but no matter what I try I get errors. I see that people often have trouble referencing Kivy widgets and it seems that the errors are somewhat different and requires more insight than I have in the way Kivy builds and run.
So maybe some of you pros can help point out the things that I am doing wrong! I have included the affected parts of the kv file as well as pseudocode that shows the structure of my app.
KV file:
<FrontPage>:
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: 'tapet2.jpg'
GridLayout:
rows: 3
cols: 1
size: root.width * 1, root.height * 1
GridLayout:
rows: 1
cols: 2
Label:
size_hint_x: 0.35
text: "VH54 appen"
font_size: 30
Label:
text: ""
GridLayout:
rows: 1
cols: 5
padding: 20
spacing: 20
size_hint_y: 3
ToggleButton:
id: alrum_knap
text: 'Alrum'
on_state: root.alrum_knap(self.state)
... (goes on with other layout stuff)
Python Code:
from kivy.app import App, Widget
from kivy.clock import Clock
from kivy.core.window import Window
class FrontPage(Widget):
def update_alrum_knap(self):
( ... Code that defines alrum_on True or False)
if alrum_on == True:
FrontPage().ids.alrum_knap.state = 'down'
else:
FrontPage().ids.alrum_knap.state = 'normal'
def alrum_knap(self, state):
if state == 'down':
Hue.ongroup(alrum_gr, sc_dag_alrum, 10)
else:
Hue.offgroup(alrum_gr, 20)
def loop(self):
(Contains code for running the lights)
class Vh54App(App):
def build(self):
Clock.schedule_interval(FrontPage.loop, 0.5)
Clock.schedule_interval(FrontPage.update_alrum_knap, 0.5)
return FrontPage()
if __name__ == '__main__':
Vh54App().run()
I am thankful for all the help and insight I can get from you. I am perfectly aware that I have made all kinds of newbie errors! I am better at other things in life, but this is rather fun also :D
Regards
Simon, Denmark
In your code:
FrontPage().ids.alrum_knap.state = 'down'
The FrontPage() is creating a new instance of FrontPage, which is not the instance that appears in your GUI. Whenever you use a classname (like FrontPage) followed by (), you are creating a new instance of that class.
Since that code is in a method of the FrontPage class, you can simply use the self parameter, like this:
self.ids.alrum_knap.state = 'down'
Also, your use of FrontPage.loop and FrontPage.update_alrum_knap in the build() method is incorrect. You should again be using the instance of FrontPage, like this:
def build(self):
fp = FrontPage()
Clock.schedule_interval(fp.loop, 0.5)
Clock.schedule_interval(fp.update_alrum_knap, 0.5)
return fp
You will also need to add dt arguments to those methods called by Clock.

How to dynamically update a Kivy label from a continuously running function?

I am trying to have a function run continuously and spit out a distance that the label uses that I'll eventually tie to a sonar module, but the label remains blank and I am at a loss as to what I am doing wrong. If I just add a print statement for that distance variable it prints and updates just fine, just can't get the label to use it.
Part II of my question is how do I reference my same function in the second window and also have a label that updates from that same function?
Thanks for the help in advance, I am very very new to kivy and just started learning python a few months ago as well.
Python code:
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen # for multiple screens
from kivy.properties import StringProperty
class MySonar(Screen):
global i
i = 1
distance = StringProperty("")
#Generic function that just adds itself up, just using to try and get the label to change before I throw in my real function
def sonar(self):
global i
if i < 250:
distance = (10 + .1 * i)
i += 1
else:
i = 1
distance = 10
self.root.distance=str(distance)
class DropWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
kv = Builder.load_file("help.kv")
class HelpMe(App):
def build(self):
#running interval update to keep running code above
Clock.schedule_interval(lambda dt: MySonar.sonar(self), 0.1)
return kv
if __name__ == "__main__":
HelpMe().run()
Kivy:
WindowManager:
MySonar:
DropWindow:
<MySonar>:
name:"Main"
GridLayout:
cols:1
##Need this to update
Label:
text:root.distance
Button:
text:"Next Window"
on_release:
app.root.current="Drop"
root.manager.transition.direction="left"
<DropWindow>:
name:"Drop"
GridLayout:
cols:1
##Need this to update, commented out the text so the program will run and you can see the blank label for part one of my question
Label:
##text:root.distance
Button:
text:"Cancel"
on_release:
app.root.current="Main"
root.manager.transition.direction="right"
To simplify the access to distance, you can put that StringProperty in the HelpMe App:
class MySonar(Screen):
global i
i = 1
#Generic function that just adds itself up, just using to try and get the label to change before I throw in my real function
def sonar(self):
global i
if i < 250:
distance = (10 + .1 * i)
i += 1
else:
i = 1
distance = 10
# set the value of distance in the StringProperty of the App
App.get_running_app().distance=str(distance)
print(distance)
class DropWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
# kv = Builder.load_file("help.kv")
class HelpMe(App):
distance = StringProperty('')
def build(self):
kv = Builder.load_file("help.kv")
#running interval update to keep running code above
sonar_instance = kv.get_screen('Main')
Clock.schedule_interval(lambda dt: sonar_instance.sonar(), 0.1)
return kv
if __name__ == "__main__":
HelpMe().run()
Note that I have also moved the Builder.load_file() inside the App. This is good practice when you reference app in the kv file (as I have done). Also, calling the sonar() method using MySonar.sonar(self) will not work. You need to use a reference to the instance of MySonar that is in your GUI.
Now the kv file becomes:
WindowManager:
MySonar:
DropWindow:
<MySonar>:
name:"Main"
GridLayout:
cols:1
##Need this to update
Label:
text: app.distance
Button:
text:"Next Window"
on_release:
app.root.current="Drop"
root.manager.transition.direction="left"
<DropWindow>:
name:"Drop"
GridLayout:
cols:1
##Need this to update, commented out the text so the program will run and you can see the blank label for part one of my question
Label:
text: app.distance
Button:
text:"Cancel"
on_release:
app.root.current="Main"
root.manager.transition.direction="right"
The change is that the text attribute of both Labels is now just app.distance.

Why won't my Kivy program update the font size when I tell it to?

I'm making a choose your own adventure game, but sometimes I need to change the font size and Kivy isn't giving me the results I'm expecting. This is the full code so feel free to run it and see what I mean.
Here is the python file:
# A Choose your own adventure game
import kivy
kivy.require('1.11.1')
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.lang import Builder
global root
root = BoxLayout() #If I don't define root immediately the program won't work
#root is given a proper definition in class Main()
#Easily add new pages to the program
def add_page(pagenum):
root.clear_widgets()
root.add_widget(pagenum)
#The main window that encapsulates all other widgets
class RootBoxLayout(BoxLayout):
def __init__(self, **kwargs):
super(RootBoxLayout, self).__init__(**kwargs)
# The Menu that drives the game
class Menu(BoxLayout):
def __init__(self, **kwargs):
super(Menu, self).__init__(**kwargs)
# The Main Menu
class StartMenu(Menu):
def __init__(self, **kwargs):
super(Menu, self).__init__(**kwargs)
#Text Box
self.ids.textbox.text = "Opening Screen"
# Button 1
self.ids.button1.text = "Play"
self.ids.button1.bind(on_press = self.nextpage1)
def nextpage1(self, *args):
add_page(HappyBee())
class HappyBee(Menu):
def __init__(self, **kwargs):
super(Menu, self).__init__(**kwargs)
#############################################
### This is where the problem seems to be ###
#############################################
self.ids.textbox.font_size = self.ids.textbox.height/10 #Kivy says nah I don't feel like doing this
self.ids.textbox.text = "This is a very large block of text that I would like " \
"to decrease the font size of. Pressing the button below changes it but I don't " \
"want users to have to press a button just to get the game to function " \
"how it should function from the start."
# Button 1
self.ids.button1.text = "y tho"
self.ids.button1.bind(on_press = self.nextpage1)
# What to do when each button is pressed
def nextpage1(self, *args):
self.ids.textbox.font_size = self.ids.textbox.height/10 # Kivy says ok I can change it now lol
# An App class that will be used to umbrella everything else in the application
class Main(App):
def build(self):
Builder.load_file("cyoa.kv")
global root # Other classes and functions need to easily access root
root = RootBoxLayout()
first_screen = StartMenu()
add_page(first_screen) # Add the Main Menu to the root window
return root
if __name__ == '__main__':
Main().run()
and here is the corresponding kv file, which I have saved as cyoa.kv
<RootBoxLayout>:
orientation: 'vertical'
# Create the background color of the root layout
canvas.before:
Color:
rgba: 0,0,0,1 # black
Rectangle:
pos: self.pos
size: self.size
# This custom button allows to font size to change dynamically with the window
<MyButton#Button>:
font_size: self.height/3
halign: 'center'
valign: 'center'
text_size: self.size
size_hint_y: 0.14
<Menu>:
BoxLayout:
orientation: 'vertical'
Label:
id: textbox
font_size: self.height/6
text_size: self.size # Allows text to wrap
halign: 'center'
valign: 'center'
size_hint_y: 0.6
MyButton:
id: button1
text: 'Play'
I can change font_size in __init__ only if I remove font_size from .kv. It seems it gets value from .kv after running __init__ and this makes problem. There is also other problem: height (and width) in __init__ is 100 instead of expected size. Probably it calculates it after running __init__.
Searching in internet I found on Reddit: How can I use init for screen ids?
It uses Clock to run some function after all updates and in this function change values.
def __init__(self, **kwargs):
#super(...)
Clock.schedule_once(self._do_setup)
def _do_setup(self, *l):
self.ids.something = '....'
In your code it would be
from kivy.clock import Clock # <---
class HappyBee(Menu):
def __init__(self, **kwargs):
super(Menu, self).__init__(**kwargs)
self.ids.textbox.text = "This is a very large block of text that I would like " \
"to decrease the font size of. Pressing the button below changes it but I don't " \
"want users to have to press a button just to get the game to function " \
"how it should function from the start."
self.ids.button1.text = "y tho"
self.ids.button1.bind(on_press = self.nextpage1)
Clock.schedule_once(self.on_init_complete) # <---
def on_init_complete(self, *args, **kwargs):
self.ids.textbox.font_size = self.ids.textbox.height/10 # <---
It works but has one small problem - it display text in original size for few milliseconds. But if you don't know this then you may not notice this.
EDIT: Similar problem: How to Load Kivy IDs Before Class Method is Initialized (Python with Kivy)

How to create a login screen using Kivy

Desired result:
Currently my code is as follows:
class KeypadButton(Factory.Button):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
FocusBehavior.ignored_touch.append(touch)
return super(KeypadButton, self).on_touch_down(touch)
class Keypad(Factory.GridLayout):
target = Factory.ObjectProperty(None, allownone=True)
def __init__(self, **kwargs):
super(Keypad, self).__init__(**kwargs)
self.cols = 3
for x in list(range(1, 10)) + ['<-', 0, 'Enter']:
btn = KeypadButton(text=str(x), on_release=self._on_release)
self.add_widget(btn)
def _on_focus(self, ti, value):
self.target = value and ti
def _on_release(self, instance, *largs):
if self.target:
if instance.text == 'Enter':
print("Enter: {}".format(self.target.text))
self.target.text = ''
elif instance.text == '<-':
if self.target.text:
self.target.text = self.target.text[:-1]
else:
self.target.text += str(instance.text)
runTouchApp(Builder.load_string('''
<KeypadTextInput#TextInput>:
keypad: None
on_focus: root.keypad._on_focus(*args)
BoxLayout:
orientation: 'vertical'
KeypadTextInput:
keypad: keypad
Keypad:
id: keypad
size_hint_x: 0.5
pos_hint: {'center_x': 0.5}
'''))
What I want to achieve is that when I press 12345 the login screen disappears and a new screen pop ups. I have an image of the format that is given by the following what I want to achieve.
Not sure how exactly how close you want to get to your desired result, but you can get much closer by just changing your 'kv' to:
<KeypadTextInput#TextInput>:
keypad: None
on_focus: root.keypad._on_focus(*args)
BoxLayout:
orientation: 'vertical'
KeypadTextInput:
keypad: keypad
Keypad:
id: keypad
size_hint_x: 0.5
pos_hint: {'center_x': 0.5}
First when it comes to the issue of focus, I think it depends on how you load up the screen. Is it your root widget or a screen you load up?
As yours looks to be a root widget of sorts you'd probably want to do this with the start up of your app. You can use the 'on_start' event for this
class MyApp(App):
def on_start(self,*args):
self.ids.mytextinput.focus = True #replace mytextinput with whatever id name you give to your text input in the kv string
For the text input firing off events when you type a certain number of digits you could use on_text. For this I think it's best to instantiate your own class if you're starting out.
class KeyPadTextInput(TextInput):
def on_text(self,*args):
if len(self.text)==3:
#put your python code here
#you can launch MyApp functions by using app.function_name()
Another thing I've noticed is that you use on_focus to trigger your own '_on_focus' event with the same *args. You could achieve the same thing by removing the on_focus from your kv string and adjusting the class on_focus event, calling super().on_focus(*args) so the inherited function also fires as such:
class KeyPadTextInput(TextInput):
def on_focus(self,*args):
#your code either before the super call
super().on_focus(*args)
#or your code after the super call
Hope that helps point you in the right direction.
PS. TextInputs have a few prebuilt input filters such as a filter so you can only input numbers! This is handy if the users keyboard comes up or they have access to one too.
in the kv string simply add
input_filter: 'int'

In kivy, How can I control the height of the VKeyboard used by TextInput

I have a Screen where I want to ask a question, so in the kv language, I have
Screen:
name: 'keyb'
BoxLayout:
orientation: 'vertical'
Label:
id: rnl
size_hint_y: .1
text: ''
TextInput:
id: tinput
hint_text: '.mid'
size_hint: (.8, .1)
pos_hint: {'x': .1}
multiline: False
on_focus: root.focusCallback(args[1])
Widget:
size_hint_y: .7
When I want to ask the question, I select the screen and set the focus to the TextInput
self.tinput.text = ""
self.screens.current = 'keyb'
Clock.schedule_once(self.focusKbd, .1)
which then cascades through this code:
def focusKbd(self, x):
self.tinput.focus = True
def focusCallback(self, f):
if not f:
# Deal with input now
self.screens.current = 'seq'
Somewhere in this code, I would like to
Select which VKeyboard layout the TextInput is going to pop up (and it will be different in other parts of my code)
Adjust the height of the VKeyboard.
I don't understand where the VKeyboard comes from in Text Input; is there some way I can get a reference to it from the TextInput?
This is a restatement of https://stackoverflow.com/questions/36414654/how-can-i-change-the-size-of-kivys-vkeyboard There is a hint there, but not enough to get me going!
*********************Edit: **************************
Trying to understand the answer from Tshirtman, I put this at the top of my main widget:
Window.set_vkeyboard_class(get_vkeyboard())
and then added
def get_vkeyboard():
print '>>>>>>>>>>>>>>>>>>>>>>>>>>in get_vkeyboard'
return VKeyboard
I found that get_vkeyboard() was called immediately, long before I needed a keyboard. So it seems that I'm not going to be able to control anything about the appearance of the VKeyboard dynamically in get_vkeyboard(). Please correct me if I'm confused (which I undoubtedly am!).
Is there some other way to modify the appearance of the VKeyboard dynamically?
P.S. There seems to be a hint in Focus Behavior:
input_type is an OptionsProperty and defaults to ‘text’. Can be one of
‘text’, ‘number’, ‘url’, ‘mail’, ‘datetime’, ‘tel’ or ‘address’.
I added "input_type: 'number'" to a TextInput in .kv, but it didn't have any affect.
You can set keyboard class using Window.set_vkeyboard_class, but nothing prevents you from registering any function that returns a keyboard instance there, so you could use the context (your app's state) to decide which class will be used, dynamically. Since you are returning an instance yourself in this case, you can decide of its size, pos, and other details. I used this technique in a few app to use Animation on the instance to place it in a particular spot of the screen smoothly.
pseudo example:
from kivy.core.window import Window
from kivy.uix.vkeyboard import VKeyboard
from kivy.animation import Animation
from kivy.uix.screenmanager import ScreenManager, Screen
class KeyboardA(VKeyboard):
def place(self):
self.center_x = Window.center_x
self.top = 0
Animation(y=100, t='out_elastic', d=.4).start(self)
class KeyboardB(VKeyboard):
def place(self):
self.opacity = 0
Animation(opacity=1).start(self)
class MyApp(App):
def build(self):
sm = ScreenManger()
sm.add_widget(Screen(name='a'))
sm.add_widget(Screen(name='b'))
return sm
def get_keyboard(self, **kwargs):
if self.root.current == 'a':
kb = KeyboardA(**kwargs)
else:
kb = KeyboardB(**kwargs)
kb.place()
return kb
Window.set_vkeyboard_class(app.get_keyboard)
untested, but you should get the idea.

Categories

Resources