Change TextInput to display only function for Kivy Python - python-3.x

I'm still new to kivy and still relatively new to Python.
I have main. py. I plan to have WindowGUI take a number of inputs from main.py and display them in its textbox.
Here is what I have:
from os import curdir
from posixpath import dirname
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout # one of many layout structures
from kivy.uix.textinput import TextInput # allow for ...text input.
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.spinner import Spinner
from kivy.uix.dropdown import DropDown
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from kivy.properties import ObjectProperty
kivy.require('2.0.0')
class MainGUI(Screen):
encoder = ObjectProperty()
mileage = ObjectProperty()
voltage = ObjectProperty()
power = ObjectProperty()
camera = ObjectProperty()
engaged = ObjectProperty()
idle = ObjectProperty()
log = ObjectProperty()
clear = ObjectProperty()
status = ObjectProperty(None)
def __init__(self, saySomething):
super().__init__() #Go up to __init__ of App
self.saySomething = saySomething
def power_option(self):
self.power.text = "Power"
def print_status(self):
self.ids.Status_screen.text = self.saySomething
return self.ids.Status_screen.text
class CameraGUI(Screen):
def __init__(self, **kwargs): #kwargs is just a variable. it could be "banana" or "apple"
super().__init__() #Go up to __init__ of App
class GUIManager(ScreenManager):
pass
kv = Builder.load_file('newWindow.kv')
class WindowGUI(App):
def __init__(self):
super().__init__()
# self.say = say
print("TRY ME!!!!")
def build(self):
self.title = 'IamHero'
return kv
if __name__ == '__main__':
myWindowGUI = WindowGUI()
myMainGUI = MainGUI('I am hero!')
myWindowGUI.run()
###########################_newWindow.kv_#############################
GUIManager:
MainGUI:
CameraGUI:
<MainGUI>:
name: "main"
status: Status_screen
GridLayout:
cols: 2
size: root.width, root.height #covers entire window
padding: 15
GridLayout:
cols:1
spacing: 10
GridLayout:
cols:2
size_hint: .25,.1
Label:
id: Engaged
text: "Engaged"
Label:
id: Idle
text: "Idle"
TextInput:
# size_hint: 1
id: Status_screen
multiline: True
readonly: True
background_color: .6, .7, 0, 1
text: root.print_status()
#on_text: root.print_status()
GridLayout:
cols:2
size_hint: .25,.1
Button:
id: Log
text: "Log status"
# size_hint_x: .5
Button:
id: Clear
text: "Clear"
However, I got the following error:
Traceback (most recent call last):
File "c:/Users/Jojo/#RaspberryPi4/Test/WindowGUI.py", line 57, in <module>
kv = Builder.load_file('newWindow.kv')
File "C:\Users\Jojo\Anaconda3\lib\site-packages\kivy\lang\builder.py", line 306, in load_file
return self.load_string(data, **kwargs)
File "C:\Users\Jojo\Anaconda3\lib\site-packages\kivy\lang\builder.py", line 408, in load_string
self._apply_rule(
File "C:\Users\Jojo\Anaconda3\lib\site-packages\kivy\lang\builder.py", line 659, in _apply_rule
child = cls(__no_builder=True)
TypeError: __init__() got an unexpected keyword argument '__no_builder'
Any ideas about what I did wrong?

You cannot require a positional argument in the __init__() of a class that you intend to use in kv. Your __init__() for MainGUI:
def __init__(self, saySomething):
requires an argument for saySomething. An easy way around that is to just make saySomething into a StringProperty of MainGUI. Then you don't even need to implement an __init__():
class MainGUI(Screen):
encoder = ObjectProperty()
mileage = ObjectProperty()
voltage = ObjectProperty()
power = ObjectProperty()
camera = ObjectProperty()
engaged = ObjectProperty()
idle = ObjectProperty()
log = ObjectProperty()
clear = ObjectProperty()
status = ObjectProperty(None)
saySomething = StringProperty('Hi')
# def __init__(self, saySomething):
# super().__init__() #Go up to __init__ of App
# self.saySomething = saySomething
Then you can set the value of saySomething just like any other property in the kv:
<MainGUI>:
name: "main"
status: Status_screen
saySomething: 'I am hero!'
or in python you can do:
MainGUI(saySomething='I am hero!')
Also, the line:
myMainGUI = MainGUI('I am hero!')
(which will no longer work) is creating an instance of MainGUI that is unused. So that line of code can be deleted.

Related

How to simultaneously display keypress for 2 separate Kivy windows

I have a case where I need 2 Kivy windows each on a separate monitor to display keyboard press events simultaneously. I have a simple toy example of the code for 'app1.py' and 'app2.py' below.
I was able to successfully use 'subprocess' and kivy.config so that when you press the 'Press to open second window' button in my main application (i.e., app1) a second window (i.e., app2) opens on my second monitor. However, I am stuck on to how I can simultaneously display keyboard events to both application windows when the spacebar is pressed.
Any ideas on how this can be achieved, is it even possible in Kivy?
Best,
Tom
File name: app1.py
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
from kivy.properties import StringProperty
from subprocess import Popen
Window.size = (1920, 1080)
Window.borderless = True
Builder.load_string('''
<FirstWindow>:
id: _FirstWindow
FloatLayout:
Label:
text: "First Window"
size_hint: None, None
font_size: 50
pos: (900,940)
Label:
text: _FirstWindow.key_down
size_hint: None, None
font_size: 30
pos: (900,800)
Button:
text: "Press to open second window"
size_hint: None, None
font_size: 30
size: (450, 60)
pos: (720, 600)
on_press: root.OpenSecondWindow()
''')
class FirstWindow(FloatLayout):
key_down = StringProperty() # perform button state
def __init__(self, **kwargs):
super(FirstWindow, self).__init__(**kwargs)
self._keyboard = Window.request_keyboard(None, self)
self._keyboard.bind(on_key_down=self.on_keyboard_down, on_key_up=self.on_keyboard_up)
def OpenSecondWindow(self):
p = Popen(['python ./app2.py'], shell=True)
def on_keyboard_down(self, keyboard, keycode, text, modifiers):
if keycode[1] == 'spacebar':
self.key_down = 'spacebar pressed!'
def on_keyboard_up(self, keyboard, keycode):
if keycode[1] == 'spacebar':
self.key_down = ''
class App1(App):
def build(self):
return FirstWindow()
if __name__ == '__main__':
App1().run()
File name: app2.py
from kivy.config import Config
Config.set('graphics', 'resizable', '0')
Config.set('graphics', 'position', 'custom')
Config.set('graphics', 'top', '-900')
Config.set('graphics', 'left', '0')
Config.set('graphics', 'borderless', '1')
Config.set('graphics', 'fullscreen', 'auto')
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import StringProperty
from kivy.lang import Builder
Window.size = (1920, 1080)
Window.borderless = True
Builder.load_string('''
<SecondWindow>:
id: _SecondWindow
FloatLayout:
Label:
text: "Second Window"
size_hint: None, None
font_size: 50
pos: (900,940)
Label:
text: _SecondWindow.key_down
size_hint: None, None
font_size: 30
pos: (900,800)
''')
class SecondWindow(FloatLayout):
key_down = StringProperty() # perform button state
def __init__(self, **kwargs):
super(SecondWindow, self).__init__(**kwargs)
self._keyboard = Window.request_keyboard(None, self)
self._keyboard.bind(on_key_down=self.on_keyboard_down, on_key_up=self.on_keyboard_up)
def on_keyboard_down(self, keyboard, keycode, text, modifiers):
if keycode[1] == 'spacebar':
self.key_down = 'spacebar pressed!'
def on_keyboard_up(self, keyboard, keycode):
if keycode[1] == 'spacebar':
self.key_down = ''
class App2(App):
def build(self):
return SecondWindow()
if __name__ == '__main__':
App2().run()
From app1, you can write to the stdin of app2. Here are the changes to make that happen. In your OpenSecondWindow() method add stdin=PIPE and save a reference to the Popen object:
def OpenSecondWindow(self):
self.p = Popen(['python3 ./app2.py'], shell=True, stdin=PIPE, universal_newlines=True)
Then, in your on_keyboard_down() method, write to the stdin of app2:
def on_keyboard_down(self, keyboard, keycode, text, modifiers):
if keycode[1] == 'spacebar':
self.key_down = 'spacebar pressed!'
self.p.stdin.write('spacebar pressed\n')
self.p.stdin.flush()
And in your app2, you must listen for input on stdin. Add this code to your SecondWindow:
def __init__(self, **kwargs):
super(SecondWindow, self).__init__(**kwargs)
self._keyboard = Window.request_keyboard(None, self)
self._keyboard.bind(on_key_down=self.on_keyboard_down, on_key_up=self.on_keyboard_up)
Thread(target=self.listen_for_input).start()
def listen_for_input(self):
while True:
data = input()
self.set_label(data)
#mainthread
def set_label(self, data):
self.ids.label.text = data
And the above requires an id in the kv for app2:
Label:
id: label
And you can add stdout=PIPE to the Popen, and use a similar construct to allow communication in the opposite direction.

A non-existent line

I'm learning Kivy and I'm on the MDExpansionPanel widget.
Data source from a JSON I use the keys to assemble my panels and the values to compose my Contents.
It happens that I'm able to do it but an extra line always appears in my contents.
I would like your help to delete this line.
I will post my code below:
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.expansionpanel import MDExpansionPanel, MDExpansionPanelOneLine
from kivy.properties import StringProperty, ObjectProperty
json_data = {'01001BA476': {'price': '74.73',
'product_code': '000003',
'quantity': '100'},
'0100251633': {'price': '92.07',
'product_code': '000156',
'quantity': '1000'}}
KV = '''
<ClassDetails>
orientation: 'vertical'
adaptive_height: True
OneLineIconListItem:
id: info_line
text: root.text
on_press: root.action()
IconLeftWidget:
icon: 'star'
on_press: print(f'star pressed on line: {info_line.text}')
ScrollView:
MDGridLayout:
id: box
cols: 1
adaptive_height: True
'''
class ClassDetails(MDBoxLayout):
text = StringProperty()
action = ObjectProperty()
class InvoicePanel(MDExpansionPanel):
pass
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
def fechar_pedido(self):
print('You clicked on the information line')
def on_start(self):
for class_title, class_details in json_data.items():
cd = ClassDetails()
expansion_panel = InvoicePanel(panel_cls=MDExpansionPanelOneLine(text=f'Invoice #: {class_title}'), content=cd)
self.root.ids.box.add_widget(expansion_panel)
for item in class_details.items():
cd.add_widget(ClassDetails(
text=str(class_details.values()), action=self.fechar_pedido))
Test().run()
I think the extra line is coming from setting your initial content to a ClassDetails instance, which has a OneLineIconListItem in its definition. Try replacing the content with a simple MDBoxLayout instead:
def on_start(self):
for class_title, class_details in json_data.items():
print(class_title, class_details)
# cd = ClassDetails()
cd = MDBoxLayout(orientation='vertical', adaptive_height=True)
expansion_panel = InvoicePanel(panel_cls=MDExpansionPanelOneLine(text=f'Invoice #: {class_title}'), content=cd)
self.root.ids.box.add_widget(expansion_panel)
for item in class_details.items():
print('\titem:', item)
cd.add_widget(ClassDetails(
text=str(class_details.values()), action=self.fechar_pedido))

Recycle view inside not Showing in box layout - Kivy

I have this code below - working on some kind of online streaming app.
My aim is to make the recycler view item occupy 90% while my top navigation takes 10%.
what I have done is put both in a box layout but the recycler view item seem to dissappear. I dont get errors but just a blank screen in the place where the recycler view item is supposed to show.
what Im I doing wrong
Thank you for your help
py
import os
from kivymd.app import MDApp
from kivy.uix.boxlayout import BoxLayout
from kivymd.uix.list import OneLineListItem, OneLineRightIconListItem, OneLineAvatarIconListItem, OneLineAvatarListItem, IRightBodyTouch
from kivymd.uix.gridlayout import MDGridLayout
from kivymd.uix.button import MDFlatButton, MDRectangleFlatButton, MDRaisedButton
from kivymd.uix.boxlayout import MDBoxLayout
from kivy.uix.recycleview import RecycleView
from kivy.uix.popup import Popup
from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.uix.screen import MDScreen
from kivy.properties import StringProperty
from pathlib import Path, PurePath
# Music Path
storageLocation = Path.cwd()
if Path('Books').is_dir():
storageLocation = Path.cwd() / 'Books'
# Check if file is in SD card
# elif Path.is_mount():
else:
storageLocation = Path.cwd() / 'Books'
storageLocation.mkdir()
class RecycleViewRow(BoxLayout):
text = StringProperty()
class MainScreen(MDScreen):
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
booksdir = [f for f in storageLocation.iterdir() if f.is_dir()]
self.children[0].data = [{'text': str(x), 'id': str(x)} for x in booksdir]
self.theFiles = [self.children[0].data]
return
class Playlist(MDScreen):
def on_enter(self, *args):
return
class Main(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
sm = ScreenManager()
sm.add_widget(MainScreen(name='MainScreen'))
sm.add_widget(Playlist(name='Playlist'))
sm.add_widget(Test(name="Test"))
return sm
def fill_playlist(self, dir):
self.root.current = 'Playlist' # this also clears the play list
playlist = self.root.get_screen('Playlist')
for sub in os.listdir(dir):
playlist.ids.Box.add_widget(OneLineAvatarIconListItem(text=sub))
Kivy
<RecycleViewRow>:
orientation: 'vertical'
OneLineAvatarIconListItem:
text: root.text
#on_press: app.root.message_box(root.text)
#on_release:app.root.current = 'Playlist'
on_release: app.fill_playlist(root.text)
Container:
id: container
MDIconButton:
icon: "download"
<MainScreen>:
MDBoxLayout
orientation:'horizontal'
MDToolbar:
title: 'Books'
left_action_items: [['menu', lambda x: x]]
right_action_items: [['dots-vertical', lambda x: x]]
MDBoxLayout
RecycleView:
id: rv
viewclass: 'RecycleViewRow'
spacing: 40
padding:10, 10
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
<Playlist>
MDBoxLayout:
orientation:'vertical'
spacing: 5
size_hint_y: None
height: self.minimum_height
MDList:
id:Box
The main issue with your code is the line:
self.children[0].data = [{'text': str(x), 'id': str(x)} for x in booksdir]
because self.children[0] in the MainScreen class is the MDBoxLayout. You should set the data for the RecycleView class, which has an id of rv. So that line should be:
self.ids.rv.data = [{'text': str(x), 'id': str(x)} for x in booksdir]
However, that won't work inside the __init__() method of MainScreen, bcause the ids have not yet been assigned at that point. Here is an approach that handles both those issues:
class MainScreen(MDScreen):
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
Clock.schedule_once(self.build_rv)
def build_rv(self, dt):
booksdir = [f for f in storageLocation.iterdir() if f.is_dir()]
self.ids.rv.data = [{'text': str(x), 'id': str(x)} for x in booksdir]
self.theFiles = [self.ids.rv.data]
By delaying the execution of build_rv() with Clock.schedule_once(), the ids are available.

add recycle view item to another screen from a recycle view- kivy

I have two screens. MainScreen and Playlist. MainScreen has a RecycleView method added that displays list of items. These items are created from folder names(directories). I want the files inside each folder(directories) to open on the Playlist Screen depending on which directory name is clicked.
I intend to clear the screen immediately after the user leaves the screen.
from kivymd.app import MDApp
from kivymd.uix.button import MDFlatButton, MDRectangleFlatButton
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.recycleview import RecycleView
from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.uix.screen import MDScreen
from pathlib import Path, PurePath
# Music Path
storageLocation = Path.cwd()
if Path('Books').is_dir():
storageLocation = Path.cwd() / 'Books'
class RecycleViewRow(BoxLayout):
text = StringProperty()
class MainScreen(MDScreen):
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
booksdir = [f for f in storageLocation.iterdir() if f.is_dir()]
self.children[0].data = [{'text': str(x), 'id': str(x)} for x in booksdir]
# print(self.children[0].data)
class Playlist(MDScreen):
def on_pre_enter(self, *args):
self.ids.Box.clear_widgets()
#self.ids.Box.add_widget(MDRectangleFlatButton(text="done", ))
print(self.manager.ids)
return
class Main(MDApp):
def build(self):
sm = ScreenManager()
sm.add_widget(MainScreen(name='MainScreen'))
sm.add_widget(Playlist(name='Playlist'))
sm.add_widget(Test(name="Test"))
return sm
Main().run()
.kv
<RecycleViewRow>:
orientation: 'vertical'
Button:
text: root.text
#on_press: app.root.message_box(root.text)
on_release:app.root.current = 'Playlist'
<MainScreen>:
RecycleView:
id: rv
viewclass: 'RecycleViewRow'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
<Playlist>
BoxLayout:
orientation: 'vertical'
id:Box
You can trigger a method from your RecycleViewRow, like this:
<RecycleViewRow>:
orientation: 'vertical'
Button:
text: root.text
on_release: app.fill_playlist(root.text)
Then, the the fill_playlist() method. you can fill the PlayList:
def fill_playlist(self, dir):
self.root.current = 'Playlist' # this also clears the play list
playlist = self.root.get_screen('Playlist')
for sub in os.listdir(dir):
playlist.ids.Box.add_widget(MDRectangleFlatButton(text=sub))

dynamic binding of functions not working in kivy

I wanted to make a dynamic class of a group of widget so whenever I add them to my main app, I had to only bring in the changes from somewhere(e.g. a python file, class etc, here I've done them in explicit lists), but binding the widget with properties like "on_text" which respond to events didn't really work, other properties like 'text', 'hint_text' worked perfectly but on_text doesn't really budge. I can't figure out the cause for this because I've checked the correct object being passed along the functions, below is my code:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
class Dynamic_TextInput(BoxLayout):
def __init__(self,changes=None, **kwargs):
super().__init__(**kwargs)
self.widgets = {'Mylabel':self.ids.mylabel,
'Myinput': self.ids.mytext}
self.Change(changes)
def Change(self, changes=None, **kwargs):
if changes:
for change in changes:
curwidget = self.widgets[change[0]]
cur_properties = change[1]
for attr in cur_properties.keys():
if attr=='bind':
print("The cur properties are: ")
print(cur_properties[attr])
curwidget.bind(**(cur_properties[attr]))
else:
setattr(curwidget, attr, cur_properties[attr])
class mainwidget(BoxLayout):
myobj1 = ObjectProperty()
myobj2 = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
change1=[('Mylabel', {'text':'firstchange',
'bind':{'on_text':lambda *_: print('something')}
}),
('Myinput', {'hint_text': 'Changed hint text'})
]
self.add_widget(Dynamic_TextInput(changes=change1))
self.add_widget(Dynamic_TextInput())
class MainApp(App):
def build(self):
return mainwidget()
if __name__ == '__main__':
MainApp().run()
And, in the kivy file:
#:kivy 1.10.0
<Dynamic_TextInput>:
myobj1: mylabel
myobj2: mytext
orientation: 'horizontal'
Label:
id: mylabel
text: 'testlable'
TextInput:
id: mytext
hint_text: 'some test'
What is the cause? And how can I fix it?
There are 2 ways to make a binding of a property:
*.py
object.bind(property=callback)
*.kv
object:
on_property: callback
So in the case of making the connection in python you should not use on_text, but text.
To verify the change, the Label can not be edited from the GUI, so I will use the TextInput to write on the Label:
*.py
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
class Dynamic_TextInput(BoxLayout):
def __init__(self,changes=None, **kwargs):
super().__init__(**kwargs)
self.widgets = {'Mylabel':self.ids.mylabel,
'Myinput': self.ids.mytext}
self.Change(changes)
def Change(self, changes=None, **kwargs):
if changes:
for change in changes:
curwidget = self.widgets[change[0]]
cur_properties = change[1]
for attr in cur_properties.keys():
if attr=='bind':
print("The cur properties are: ")
curwidget.bind(**(cur_properties[attr]))
print(cur_properties[attr])
else:
setattr(curwidget, attr, cur_properties[attr])
class mainwidget(BoxLayout):
myobj1 = ObjectProperty()
myobj2 = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
change1=[('Mylabel', {'text':'firstchange',
'bind':{'text':lambda *_: print('something')}
}),
('Myinput', {'hint_text': 'Changed hint text'})
]
self.add_widget(Dynamic_TextInput(changes=change1))
self.add_widget(Dynamic_TextInput())
class MainApp(App):
def build(self):
return mainwidget()
if __name__ == '__main__':
MainApp().run()
*.kv
#:kivy 1.10.0
<Dynamic_TextInput>:
myobj1: mylabel
myobj2: mytext
orientation: 'horizontal'
Label:
id: mylabel
text: mytext.text # <----
TextInput:
id: mytext
hint_text: 'some test'

Resources