I have two questions according to kivy moving between components.
.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.textinput import TextInput
from kivy.uix.widget import Widget
from kivy.properties import StringProperty
Builder.load_file("keyboardreader.kv")
class MyTextInput(TextInput):
focused=StringProperty('id1')
def keyboard_on_key_down(self, window, keycode, text, modifiers):
print(keycode, text, modifiers)
if keycode[1] == "backspace":
print("print backspace down", keycode)
TextInput.keyboard_on_key_down(self, window, keycode, text, modifiers)
if keycode[1]=="right":
if int(self.focused[2])<5:
focusedid=int(self.focused[2])+1
self.focused="id"+str(focusedid)
if keycode[1]=="left":
if int(self.focused[2])>1:
focusedid=int(self.focused[2])-1
self.focused="id"+str(focusedid)
print(self.focused)
class MainScreen(Widget):
pass
class TestingappApp(App):
def build(self):
return MainScreen()
TestingappApp().run()
.kv
<MainScreen>:
BoxLayout:
id: layout
size: root.width,root.height
MyTextInput:
id: id1
MyTextInput:
id: id2
MyTextInput:
id: id3
MyTextInput:
id: id4
MyTextInput:
id: id5
I have few problems according to this code.
How can I access the element with certain id from the .kv file?
I was always using the same form and it used to work, now it doesn't. For example, if I wanted to make a focus on element with id1, I was thinking about using
self.ids["id1"].focus=True
How can I access all children elements of an element? For example, I want the list of children of BoxLayout with id:layout, that there are id1,id2,...,id5 elements.
Usually in order to access the children of a certain widget you can use the prop. children of that widget. But from the following...
I want the list of children of BoxLayout with id:layout, that there are id1,id2,...,id5 elements.
it seems you want to pack all the children into something (here, in 'BoxLayout with id:layout'). For this I modified your code in kvlang into this,
<MainScreen>:
CustomBox:
id: layout
size: root.size
<CustomBox#BoxLayout>:
MyTextInput:
id: id1
focused: "id1"
MyTextInput:
id: id2
focused: "id2"
# etc.
This will enable you to access its children (i.e. TextInput objects) by ids. Also note that you have to initialize each MyTextInput instance with different focused property (in order to access them later).
Now keeping the method keyboard_on_key_down as it is, I bind the focused property to a callback function, say, change_focus (you can use on_prop method or take any other approaches) as,
class MyTextInput(TextInput):
focused=StringProperty('id1')
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(focused = self.change_focus) # Will call this function each time 'focused' changes.
def change_focus(self, *args):
id_val = self.focused
app = App.get_running_app()
if app.root is not None:
# Now access the container.
layout = app.root.ids["layout"]
# Access the required widget and set its focus.
layout.ids[id_val].focus = True
def keyboard_on_key_down(self, window, keycode, text, modifiers):
print(keycode, text, modifiers)
...
Note:
This should make the method keyboard_on_key_down happen as expected but again you need to change some logic in it to get perfect result.
Related
I wanted to know how can i make communication between multiple classes of the layout. I'm trying to make the button on the MainLayout called add, add another button to the stacklayout which is one of its children.
in doing so, I have to both share a variable and also a functionality between them to implement the add_widget function on the stack layout. no matter what I do, I can't find a solution
code main.py:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.metrics import dp
from kivy.uix.stacklayout import StackLayout
class Buttons_layout(StackLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.number = 0
for _ in range(5):
self.number += 1
self.add_widget(Button(text=str(self.number),color=(200,100,100),size_hint=(0.2,None),height=dp(100)))
class MainWidget(BoxLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
def add_button(self):
#dont know what to do here..................
pass
class CanvosExampleApp(App):
pass
if __name__ == '__main__':
CanvosExampleApp().run()
and the kv file:
MainWidget:
<Buttons_layout>:
<Scroll_layout#ScrollView>:
Buttons_layout:
size_hint: 1,None
height:self.minimum_height
<MainWidget>:
Button:
text:'add'
on_press: root.add_button()
size_hint:None,1
width:dp(50)
Scroll_layout:
To allow easy navigation in your GUI, you can use ids. Here is a modified version of your kv with two new ids:
MainWidget:
<Buttons_layout>:
<Scroll_layout#ScrollView>:
Buttons_layout:
id: butts # new id
size_hint: 1,None
height:self.minimum_height
<MainWidget>:
Button:
text:'add'
on_press: root.add_button()
size_hint:None,1
width:dp(50)
Scroll_layout:
id: scroll # new id
Then, your add_button() method can be:
def add_button(self):
scroll = self.ids.scroll # get reference to the Scroll_layout
butts = scroll.ids.butts # get reference to the Buttons_layout
butts.add_widget(Button(text='Added',color=(200,100,100),size_hint=(0.2,None),height=dp(100)))
I want to be able to use my custom (python written) widget in both the kv file and in python code. I don't know how to create a widget, then use it in my chosen layout.
class SelectableList(FocusBehavior, CompoundSelectionBehavior, StackLayout):
previous_nodes=[]
# this class has a whole bunch of "def's" that I got rid of so that stack would let me post a shorter question
class myScreen(Screen):
def build(self):
self.root_boi = StackLayout(size=(1,1))
stack = SelectableList(touch_multiselect=True,
multiselect=True)
for i in range(0, 6):
stack.add_widget(Button(size_hint_y=None, height=30, text="Button {0}".format(i)))
self.root_boi.add_widget(stack)
return self.root_boi
class TestApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(myScreen(name='whatever you want it to be...'))
return myScreen()
TestApp().run()
I'm not sure exactly what I'm doing wrong here. I'm trying to add the "SelectableList" widget into a stack layout. I want to be able to add it to whatever widget I choose, either within a kv file or in my python code. If that makes sense...
I suspect your build() method of myScreen should actually be an __init__() method (with no return). I added self.add_widget(self.root_boi) in place of the return. And the class name should be capitalized (MyScreen), since the kv language gets fussy about that sometimes.
Here is a modified version of your code that demonstrates using your custom widgets:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.behaviors import FocusBehavior, CompoundSelectionBehavior
from kivy.uix.button import Button
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.stacklayout import StackLayout
class SelectableList(FocusBehavior, CompoundSelectionBehavior, StackLayout):
previous_nodes=[]
# this class has a whole bunch of "def's" that I got rid of so that stack would let me post a shorter question
class MyScreen(Screen):
def __init__(self, **kwargs):
super(MyScreen, self).__init__(**kwargs)
self.root_boi = StackLayout(size=(1,1))
stack = SelectableList(touch_multiselect=True,
multiselect=True)
for i in range(0, 6):
stack.add_widget(Button(size_hint_y=None, height=30, text="Button {0}".format(i)))
self.root_boi.add_widget(stack)
self.add_widget(self.root_boi)
class TestApp(App):
def build(self):
return Builder.load_string(kv)
kv = '''
# ScreenManager:
# MyScreen:
#
# or
#
ScreenManager:
Screen:
name: 'whatever'
StackLayout:
SelectableList:
touch_multiselect: True
multiselect: True
Button:
text: 'Button 0'
size_hint_y: None
height: 30
Button:
text: 'Button 1'
size_hint_y: None
height: 30
Button:
text: 'Button 2'
size_hint_y: None
height: 30
'''
TestApp().run()
The __init__() method of MyScreen must include a call to the super __init__().
The kv string in the above code shows two different uses of your custom widgets. The first (commented out) uses the MyScreen custom widget. The second (not commented out) uses the SelectableList custom widget.
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.
I want to access the id of the child to decide whether to delete the widget or not. I have the following code:
main.py
#!/usr/bin/kivy
# -*- coding: utf-8 -*-
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
class Terminator(BoxLayout):
def DelButton(self):
print("Deleting...")
for child in self.children:
print(child)
print(child.text)
if not child.id == 'deleto':
print(child.id)
#self.remove_widget(child)
else:
print('No delete')
class TestApp(App):
def build(self):
pass
if __name__ == '__main__':
TestApp().run()
test.kv
#:kivy 1.9.0
<Terminator>:
id: masta
orientation: 'vertical'
Button:
id: deleto
text: "Delete"
on_release: masta.DelButton()
Button
Button
Terminator
However when printing the id with: print(child.id), it always returns: None. Even though print(child.text) correct returns Delete or .
Question
Why does child.id not return deleto, but instead None?
How can I make a check that I don't delete the button with "Delete" on it, but delete all other buttons?
As you can read in the documentation:
In a widget tree there is often a need to access/reference other
widgets. The Kv Language provides a way to do this using id’s. Think
of them as class level variables that can only be used in the Kv
language.
Accesing ids from Python code is described here. Working example:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.uix.button import Button
Builder.load_string("""
<Terminator>:
id: masta
orientation: 'vertical'
MyButton:
id: deleto
button_id: deleto
text: "Delete"
on_release: masta.DelButton()
MyButton
MyButton
""")
class MyButton(Button):
button_id = ObjectProperty(None)
class Terminator(BoxLayout):
def DelButton(self):
for child in self.children:
print(child.button_id)
class TestApp(App):
def build(self):
return Terminator()
if __name__ == '__main__':
TestApp().run()
To skip deleting a button with "Delete" label you can examine its text property. Hovewer deleting from inside the loop will lead to the bugs, since some of the children will get skiped after the list you're iterating on will get altered:
class Terminator(BoxLayout):
def DelButton(self):
for child in self.children:
self.remove_widget(child) # this will leave one child
You have to create a list of children to delete:
class Terminator(BoxLayout):
def DelButton(self):
for child in [child for child in self.children]:
self.remove_widget(child) # this will delete all children
In your case:
class Terminator(BoxLayout):
def DelButton(self):
for child in [child for child in self.children if child.text != "Delete"]:
self.remove_widget(child)
The above are good options for implementing the task. I've used them quite successfully in my code. Now I want to recommend the visual code that the Kivy Framework directly recommends in its documentation.
Look at it, it is clear and available at this link:
https://kivy.org/doc/stable/api-kivy.uix.widget.html?highlight=objectproperty
You can quickly figure it out and use it.
This section is - Widget class, anchor is - ids.
This is a dictionary of ids defined in your kv language. This will only be populated if you use ids in your kv language code.
ids is a DictProperty and defaults to an empty dict {}.
The ids are populated for each root level widget definition. For example:
# in kv
<MyWidget#Widget>:
id: my_widget
Label:
id: label_widget
Widget:
id: inner_widget
Label:
id: inner_label
TextInput:
id: text_input
OtherWidget:
id: other_widget
<OtherWidget#Widget>
id: other_widget
Label:
id: other_label
TextInput:
id: other_textinput
# Then, in python:
>>> widget = MyWidget()
>>> print(widget.ids)
{'other_widget': <weakproxy at 041CFED0 to OtherWidget at 041BEC38>,
'inner_widget': <weakproxy at 04137EA0 to Widget at 04138228>,
'inner_label': <weakproxy at 04143540 to Label at 04138260>,
'label_widget': <weakproxy at 04137B70 to Label at 040F97A0>,
'text_input': <weakproxy at 041BB5D0 to TextInput at 041BEC00>}
>>> print(widget.ids['other_widget'].ids)
{'other_textinput': <weakproxy at 041DBB40 to TextInput at 041BEF48>,
'other_label': <weakproxy at 041DB570 to Label at 041BEEA0>}
>>> print(widget.ids['label_widget'].ids)
{}
I have three questions/problems:
given below configuration of app/app kv file why do I have to call self.build() in ButtonsFactory class. If i remove it, i get black screen, probably because there is no root element in kv file, yet if i tried making MainView root element i end up with black screen as well.
second question, is it possible to set in my someapp.kv file minimum height for custom buttons? They should never be less than X.
last but for me most important why I am unable to get text property of TextInput from kv file, in class function get_list_of_files()? What would be best way to fix this? Global variable which hardcode this value in python code? Moving Builder before python code (embedd kv as string)?
last question...buttons "fill" scrollview_id instead of preserve size and be scrollable inside this view, I'd like them to stop self adjusting size.
someapp.py file
from kivy.app import App
from kivy.factory import Factory
from kivy.properties import ObjectProperty, StringProperty
# views
from kivy.uix.modalview import ModalView
# layouts
from kivy.uix.boxlayout import BoxLayout
import os
# defined in kv file.
class HeaderContainer(BoxLayout): pass
class ButtonsContainer(BoxLayout): pass
class MainView(ModalView): pass
class ButtonsFactory(BoxLayout):
target_location = StringProperty(None)
def __init__(self, *args, **kwargs):
super(ButtonsFactory, self).__init__(*args, **kwargs)
self.build() # Question 1: is that neccessary? (i think not, but black screen without it, why?)
def build(self):
self.orientation = "vertical"
for file_name in self.get_list_of_files():
btn = Factory.CustomButton()
with open(file_name, 'r') as test_file:
btn.file_name = test_file.readline().strip()[1:20]
btn.nice_name = file_name # Question 2: is it possible to set minimum height for kivy button? (havent found in api)
self.add_widget(btn)
# print ("1.!!!!!!", self.target_location) == NONE
#classmethod
def get_list_of_files(cls):
# print "2.!!!!!!", repr(cls.target_location) == <kivy.properties.StringProperty object at 0x7f1dd7596e20>
dir_ = "/tmp" #dir_ = cls.target_location
try:
files = [os.path.join(dir_, name) for name in os.listdir(dir_)
if os.path.isfile(os.path.join(dir_, name))]
except (OSError, IOError):
files = []
return files
class SomeApp(App):
def on_pause(self):
pass
def on_resume(self):
pass
def build(self):
return MainView()
if __name__ == '__main__':
SomeApp().run()
and someapp.kv file
#:kivy 1.8.0
#:import platform platform
<CustomButton#Button>:
file_name: ''
nice_name: ''
text: root.nice_name + "\n" + root.file_name
halign:'center'
size_hint:(1, 0.1)
<HeaderContainer>:
id: header_layout
size_hint:(1, 0.1)
orientation:'horizontal'
# 2-nd-try # target_location: textinput_target_location.text
# I was trying here to pass by ObjectProperty (or StringProperty) but unfortunately failed.
TextInput:
size_hint:(0.7, 1)
id: textinput_target_location
multiline: False
hint_text: "path where stress files are stored, default /sdcard/appdir"
text: "/tmp" if platform.machine() in ["x86_64", "i686", "i386"] else "/sdcard/appdir/" # arm "arm7l", but also other arm's
#on_text: my_callback_to_reload_dir_contents()
Button:
size_hint:(0.2, 1)
id: read_target_location
text: "read target_location directory"
#on_release: my_callback_to_reload_dir_contents()
<ButtonsContainer>:
size_hint:(1, 0.9)
orientation:'vertical'
ScrollView:
id: scrollview_id
orientation: 'vertical'
ButtonsFactory
<MainView>:
BoxLayout:
# 1-st-try # target_location: HeaderContainer.target_location
id: main_layout
padding:10
spacing: 5
orientation:'vertical'
HeaderContainer
# n-th-try # target_location: HeaderContainer.target_location
ButtonsContainer
I'll try to address your questions:
1) Looking at your code, the ButtonsFactory is just a BoxLayout, which is a container for holding other widgets. The self.build function creates CustomButton widgets and puts them in the container. If you don't call self.build, nothing gets added to the container and you have an empty (blank) BoxLayout.
2) This is a little more complicated as the hight is usually controlled by what container is holding your widget. You can manually set height of widgets by setting the size_hint property to None and specifying the height.
3) I would never use global variables if they can be avoided. In this case, if all you need is access to the text content of the TextInput, I would bind it to a StringProperty attached to a widget you can access or the App object itself (which can be accessed anywhere in .kv as app.<property>.
4) Again, the height is controlled by the container the widgets are in (a BoxLayout). You can manually control their size by setting size_hint to None on the widgets.
Another problem is that you are putting a BoxLayout (your ButtonsFactory) inside a ScrollViewer. The BoxLayout will resize itself to fit exactly inside the ScrollViewer so it will not scroll. You will need to override this behavior by clearing size_hint. See this SO question for details.