I have a smallish test case that actually displays two problems.
The overall point of the app (the portion of interest) is to build a console-type display with a ScrollView window above a TextInput widget. For this stripped-down test case, it should echo the entered command "t" and then print out 3 lines of text to the ScrollView, and clear the TextInput. There are other buttons as placeholders to make the hierarchy realistic.
First, I am having a terrible time trying to call a kivy function for a widget down in the hierarchy of my FloatLayout. I have tried screen, app, root, self, <nothing>, and can't seem to find a way to get to the top of the hierarchy to then descend down to the widget of interest. This problem is displayed when the bool GUI_MODE is True. Lines 25 and 38 in test901.py have this problem. Trigger it by typing "t<enter>" in the TextInput block to the right of the ">>>" label.
Secondly, if I set bool GUI_MODE to False, I get a parameter mismatch (too many parameters) for a call that I am not passing any explicit parameters for. Lines 75, 101, and 127 display this problem. This code was lifted from another application, where it worked fine... Trigger it by typing "t<enter>" in the TextInput block to the right of the ">>>" label, with the bool GUI_MODE set to False. (I am nothing if not consistent...)
I would also be interesting to learn how to keep the focus on the TextInput widget after <enter> is pressed.
Here is the Python file (as minimally reproducible as I could make it....)
# test901.py
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.properties import StringProperty, NumericProperty, ObjectProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
__version__ = '0.1'
# Set GUI_MODE to True for scrollview output, to False for regular console output
GUI_MODE = True
def common_print(prompt):
if GUI_MODE:
print(prompt) # For debug only
# May need a "for row in prompt: print_gui_output(row)" here...
########################################################################################
# HERE IS PROBLEM 1a, I can't figure out the addressing to get to this function.... #
# game_screen, app, root, screen are all unrecognised #
# Error visible if GUI_MODE = True, and "t<enter>" is typed on the TextInput GUI >>> #
########################################################################################
game_screen.print_gui_output(prompt)
else:
print(prompt)
def common_input(prompt):
if GUI_MODE:
########################################################################################
# HERE IS PROBLEM 1b, I can't figure out the addressing to get to this function.... #
# game_screen, app, root, screen are all unrecognised #
# Error visible if GUI_MODE = True, and "t <enter>" is typed on the TextInput GUI #
########################################################################################
my_input = game_screen.gui_input(prompt)
else:
my_input = input(prompt)
return my_input
class ScrollWindow(BoxLayout):
line_count = 0
io_history = ObjectProperty(None)
def gui_input(self, command):
print("step1")
# Add output to history
self.line_count += 1
if command[-1] == 13: # got here via the enter key rather than the button
command = command[0:-2] # delete the "enter" keystroke
row = HistoryOutput()
row.line_num = str(self.line_count)
# Here is the place we add text to the scrolling text window
row.output_text = ' ' + command
self.io_history.add_widget(row)
# Here is where we prepare the input for parsing, and set the quik_param if provided
cmd = command.lower().strip()
cmd_list2 = cmd.split()
quik_param = ""
if len(cmd_list2) > 1:
cmd = cmd_list2[0]
quik_param = cmd_list2[1]
# Here we interpret the command and execute it
parse_cmd(cmd, quik_param)
# Work-around for displayed row height issues
print("step2")
########################################################################################
# HERE IS PROBLEM 2a, I can't figure why this complains of parameter mismatch #
# I am not passing any explicit parameters to recalc_height, just the implicit self #
# Error visible if GUI_MODE = False, and "t <enter>" is typed on the TextInput GUI #
########################################################################################
Clock.schedule_once(self.recalc_height)
print("step3")
return command
def print_gui_output(self, rows):
print("Step4")
# Add output to history
for my_row in rows:
self.line_count += 1
if my_row[-1] == 13: # got here via the enter key rather than the button
my_row = my_row[0:-2] # delete the "enter" keystroke
row = HistoryOutput()
row.line_num = str(self.line_count)
# Here is the place we add text to the scrolling text window
row.output_text = ' ' + my_row
self.io_history.add_widget(row)
# Work-around for displayed row height issues
print("Step5")
########################################################################################
# HERE IS PROBLEM 2b, I can't figure why this complains of parameter mismatch #
# I am not passing any explicit parameters to recalc_height, just the implicit self #
# Error visible if GUI_MODE = False, and "t <enter>" is typed on the TextInput GUI #
########################################################################################
Clock.schedule_once(self.recalc_height)
print("Step6")
def recalc_height(self):
""" A method to add and remove a widget from the io_history to force
the recalculation of its height. Without this, the scrollview will
not work correctly.
"""
work_around = Widget()
self.io_history.add_widget(work_around)
self.io_history.remove_widget(work_around)
class HistoryOutput(BoxLayout):
def collapse_row(self, app, lbl):
if lbl.shorten:
lbl.shorten = False
else:
lbl.shorten = True
print("Step7")
########################################################################################
# HERE IS PROBLEM 2c, I can't figure why this complains of parameter mismatch #
# I am not passing any explicit parameters to recalc_height, just the implicit self #
# Error visible if GUI_MODE = False, and "t <enter>" is typed on the TextInput GUI #
########################################################################################
Clock.schedule_once(app.root.recalc_height)
class ScrollBox(Button):
pass
index = NumericProperty(0)
text_name = StringProperty("")
class GameScreen(Widget):
pass
class test901App(App):
def build(self):
return GameScreen()
Window.clearcolor = (1, 1, 1, 1)
Window.size = (1700, 936)
# from dateutil.parser import parse
def parse_cmd(cmd, quik_param):
if cmd == "t":
common_print("This is line 1\nThis is line 2\nThis is line 3")
else:
common_print("Unrecognized command, try again")
def get_string(prompt):
# my_string = "" + common_input(prompt)
my_string = common_input(prompt)
if my_string == "q":
# print("*** Quick Exit from get_string ***")
raise KeyboardInterrupt
return my_string
if __name__ == '__main__':
game_screen = test901App().run()
print("Stage2")
# Command line parser
while True:
try:
print("Stage_Get_Command")
cmd2 = get_string(
"\n\nEnter a command ")
cmd2 = cmd2.lower().strip()
cmd_list = cmd2.split()
quik_param2 = ""
if len(cmd_list) > 1:
cmd2 = cmd_list[0]
quik_param2 = cmd_list[1]
# print("Long Command")
parse_cmd(cmd2, quik_param2)
except KeyboardInterrupt:
continue
And here is the kivy file, test901.kv
#:kivy 2.3.0
<ScrollBox>:
size_hint_y: None
height: 16
canvas:
Button:
size_hint_y: None
height: 16
halign: 'left'
font_size: 13
font_name: 'RobotoMono-Regular'
text_size: self.width, self.height
background_normal: str(False)
#on_press: self.parent.parent.parent.parent.make_selection(self.parent.index)
text:"Hi World"
<GameScreen>:
id: game_screen
canvas:
Color:
rgba: 0, 0, 1, 1 # Blue (For the border)
Rectangle:
pos: root.x, root.y
size: root.width, root.height
Color:
rgba: 1, 1, 1, 1 # White
Rectangle:
pos: root.x + 5, root.y + 5
size: root.width - 10, root.height - 10
FloatLayout:
pos: 0,0
size: root.width, root.height
id: float_layout
# Floating array of Top Buttons, most of which are MultiSelectSpinners
Button:
text: 'Hi World'
pos_hint: {'x': .006, 'y': .89 }
size_hint: 0.045, .1
font_size: 22
bold: True
background_normal: ''
background_color: 0.2, 0.7, .2, 1 # Green
halign: 'center'
ScrollWindow:
id: scroll_window
pos_hint: {'x': .005, 'y': .15 }
size_hint: 0.988, .7
# Grid of bottom buttons
GridLayout:
cols: 2
pos_hint: {'x': .05, 'y': .01}
size_hint: None, None
size: root.width * .9, root.height * .1
color: 1, 1, 0, 1
# rgba: 0, 1, 0, 1 # Black text
Button:
text: 'Selection'
Button:
text: 'Add'
#:set padding_base 2
#:set sm_button_width 36
<HistoryOutput@BoxLayout>
height: output_label.height
orientation: 'horizontal'
size_hint: 1, None
line_num: "0"
output_text: "???"
ToggleButton:
height: self.texture_size[1] + sp(padding_base)
id: output_label
size_hint: 1, None
font_name: 'RobotoMono-Regular'
font_size: 14
text: root.output_text
text_size: self.size[0], None
background_color: {'normal': (0,0,0,1), 'down': (1,1,0,1)} [self.state] # Black / Green
background_normal: "1"
<ScrollWindow>:
io_history: io_history
orientation: 'vertical'
padding: sp(padding_base), sp(padding_base)
# Scrolling text window
ScrollView:
BoxLayout:
height: sum([c.height for c in self.children]) + (2 * sp(padding_base))
id: io_history
orientation: 'vertical'
size_hint: 1, None
# Console Input Line
BoxLayout:
height: sp(32)
orientation: 'horizontal'
size_hint: 1, None
Label:
id: prompt_label
size_hint: None, 1
color: 0, 0, 0, 1 # Black
text: ">>>"
width: sp(sm_button_width)
TextInput:
id: main_input
multiline: False
on_text_validate: root.gui_input(main_input.text); main_input.text=""
Button:
on_press: root.gui_input(main_input.text)
size_hint: None, 1
text: "Enter"
width: self.texture_size[0] + (8 * sp(padding_base))
With the bool GUI_MODE set to True, and the command "t<enter>" entered into the TextInput widget, I get the following error trace:
C:\Users\rick\AppData\Local\Programs\Python\Python310\python.exe C:/Users/rick/PycharmProjects/cruise2cruise/test901.py
[INFO ] [Logger ] Record log in C:\Users\rick\.kivy\logs\kivy_24-05-02_34.txt
[INFO ] [deps ] Successfully imported "kivy_deps.angle" 0.4.0
[INFO ] [deps ] Successfully imported "kivy_deps.glew" 0.3.1
[INFO ] [deps ] Successfully imported "kivy_deps.sdl2" 0.7.0
[INFO ] [Kivy ] v2.3.0
[INFO ] [Kivy ] Installed at "C:\Users\rick\AppData\Local\Programs\Python\Python310\lib\site-packages\kivy\__init__.py"
[INFO ] [Python ] v3.10.5 (tags/v3.10.5:f377153, Jun 6 2022, 16:14:13) [MSC v.1929 64 bit (AMD64)]
[INFO ] [Python ] Interpreter at "C:\Users\rick\AppData\Local\Programs\Python\Python310\python.exe"
[INFO ] [Logger ] Purge log fired. Processing...
[INFO ] [Logger ] Skipped file C:\Users\rick\.kivy\logs\kivy_24-04-27_47.txt, PermissionError(13, 'The process cannot access the file because it is being used by another process')
[INFO ] [Logger ] Purge finished!
[INFO ] [Factory ] 195 symbols loaded
[INFO ] [Image ] Providers: img_tex, img_dds, img_sdl2 (img_pil, img_ffpyplayer ignored)
[INFO ] [Text ] Provider: sdl2
[INFO ] [Window ] Provider: sdl2
[INFO ] [GL ] Using the "OpenGL" graphics system
[INFO ] [GL ] GLEW initialization succeeded
[INFO ] [GL ] Backend used <glew>
[INFO ] [GL ] OpenGL version <b'4.6.0 Compatibility Profile Context 22.20.27.09.230330'>
[INFO ] [GL ] OpenGL vendor <b'ATI Technologies Inc.'>
[INFO ] [GL ] OpenGL renderer <b'AMD Radeon RX 5700 XT'>
[INFO ] [GL ] OpenGL parsed version: 4, 6
[INFO ] [GL ] Shading version <b'4.60'>
[INFO ] [GL ] Texture max size <16384>
[INFO ] [GL ] Texture max units <32>
[INFO ] [Window ] auto add sdl2 input provider
[INFO ] [Window ] virtual keyboard not allowed, single mode, not docked
[WARNING] [Factory ] Ignored class "HistoryOutput" re-declaration. Current - module: None, cls: <class '__main__.HistoryOutput'>, baseclass: None, filename: None. Ignored - module: None, cls: None, baseclass: BoxLayout, filename: C:\Users\rick\PycharmProjects\cruise2cruise\test901.kv.
[INFO ] [Base ] Start application main loop
[INFO ] [GL ] NPOT texture support is available
[INFO ] [Base ] Leaving application in progress...
Traceback (most recent call last):
File "C:\Users\rick\PycharmProjects\cruise2cruise\test901.py", line 170, in <module>
game_screen = test901App().run()
File "C:\Users\rick\AppData\Local\Programs\Python\Python310\lib\site-packages\kivy\app.py", line 956, in run
runTouchApp()
File "C:\Users\rick\AppData\Local\Programs\Python\Python310\lib\site-packages\kivy\base.py", line 574, in runTouchApp
EventLoop.mainloop()
File "C:\Users\rick\AppData\Local\Programs\Python\Python310\lib\site-packages\kivy\base.py", line 341, in mainloop
self.window.mainloop()
File "C:\Users\rick\AppData\Local\Programs\Python\Python310\lib\site-packages\kivy\core\window\window_sdl2.py", line 776, in mainloop
if self.dispatch('on_key_down', key,
File "kivy\\_event.pyx", line 727, in kivy._event.EventDispatcher.dispatch
File "kivy\\_event.pyx", line 1307, in kivy._event.EventObservers.dispatch
File "kivy\\_event.pyx", line 1231, in kivy._event.EventObservers._dispatch
File "C:\Users\rick\AppData\Local\Programs\Python\Python310\lib\site-packages\kivy\core\window\__init__.py", line 163, in _on_window_key_down
return self.dispatch('on_key_down', keycode, text, modifiers)
File "kivy\\_event.pyx", line 727, in kivy._event.EventDispatcher.dispatch
File "kivy\\_event.pyx", line 1307, in kivy._event.EventObservers.dispatch
File "kivy\\_event.pyx", line 1231, in kivy._event.EventObservers._dispatch
File "C:\Users\rick\AppData\Local\Programs\Python\Python310\lib\site-packages\kivy\uix\textinput.py", line 2984, in keyboard_on_key_down
self._key_down(key)
File "C:\Users\rick\AppData\Local\Programs\Python\Python310\lib\site-packages\kivy\uix\textinput.py", line 2890, in _key_down
self.dispatch('on_text_validate')
File "kivy\\_event.pyx", line 727, in kivy._event.EventDispatcher.dispatch
File "kivy\\_event.pyx", line 1307, in kivy._event.EventObservers.dispatch
File "kivy\\_event.pyx", line 1191, in kivy._event.EventObservers._dispatch
File "C:\Users\rick\AppData\Local\Programs\Python\Python310\lib\site-packages\kivy\lang\builder.py", line 60, in custom_callback
exec(__kvlang__.co_value, idmap)
File "C:\Users\rick\PycharmProjects\cruise2cruise\test901.kv", line 117, in <module>
on_text_validate: root.gui_input(main_input.text); main_input.text=""
File "C:\Users\rick\PycharmProjects\cruise2cruise\test901.py", line 67, in gui_input
parse_cmd(cmd, quik_param)
File "C:\Users\rick\PycharmProjects\cruise2cruise\test901.py", line 155, in parse_cmd
common_print("This is line 1\nThis is line 2\nThis is line 3")
File "C:\Users\rick\PycharmProjects\cruise2cruise\test901.py", line 25, in common_print
game_screen.print_gui_output(prompt)
NameError: name 'game_screen' is not defined. Did you mean: 'GameScreen'?
step1
This is line 1
This is line 2
This is line 3
Process finished with exit code 1
With the bool GUI_MODE set to False, and the command "t<enter>" entered into the TextInput widget, I get the following error trace:
C:\Users\rick\AppData\Local\Programs\Python\Python310\python.exe C:/Users/rick/PycharmProjects/cruise2cruise/test901.py
[INFO ] [Logger ] Record log in C:\Users\rick\.kivy\logs\kivy_24-05-02_35.txt
[INFO ] [deps ] Successfully imported "kivy_deps.angle" 0.4.0
[INFO ] [deps ] Successfully imported "kivy_deps.glew" 0.3.1
[INFO ] [deps ] Successfully imported "kivy_deps.sdl2" 0.7.0
[INFO ] [Kivy ] v2.3.0
[INFO ] [Kivy ] Installed at "C:\Users\rick\AppData\Local\Programs\Python\Python310\lib\site-packages\kivy\__init__.py"
[INFO ] [Python ] v3.10.5 (tags/v3.10.5:f377153, Jun 6 2022, 16:14:13) [MSC v.1929 64 bit (AMD64)]
[INFO ] [Python ] Interpreter at "C:\Users\rick\AppData\Local\Programs\Python\Python310\python.exe"
[INFO ] [Logger ] Purge log fired. Processing...
[INFO ] [Logger ] Skipped file C:\Users\rick\.kivy\logs\kivy_24-04-27_47.txt, PermissionError(13, 'The process cannot access the file because it is being used by another process')
[INFO ] [Logger ] Purge finished!
[INFO ] [Factory ] 195 symbols loaded
[INFO ] [Image ] Providers: img_tex, img_dds, img_sdl2 (img_pil, img_ffpyplayer ignored)
[INFO ] [Text ] Provider: sdl2
[INFO ] [Window ] Provider: sdl2
[INFO ] [GL ] Using the "OpenGL" graphics system
[INFO ] [GL ] GLEW initialization succeeded
[INFO ] [GL ] Backend used <glew>
[INFO ] [GL ] OpenGL version <b'4.6.0 Compatibility Profile Context 22.20.27.09.230330'>
[INFO ] [GL ] OpenGL vendor <b'ATI Technologies Inc.'>
[INFO ] [GL ] OpenGL renderer <b'AMD Radeon RX 5700 XT'>
[INFO ] [GL ] OpenGL parsed version: 4, 6
[INFO ] [GL ] Shading version <b'4.60'>
[INFO ] [GL ] Texture max size <16384>
[INFO ] [GL ] Texture max units <32>
[INFO ] [Window ] auto add sdl2 input provider
[INFO ] [Window ] virtual keyboard not allowed, single mode, not docked
[WARNING] [Factory ] Ignored class "HistoryOutput" re-declaration. Current - module: None, cls: <class '__main__.HistoryOutput'>, baseclass: None, filename: None. Ignored - module: None, cls: None, baseclass: BoxLayout, filename: C:\Users\rick\PycharmProjects\cruise2cruise\test901.kv.
[INFO ] [Base ] Start application main loop
[INFO ] [GL ] NPOT texture support is available
step1
This is line 1
This is line 2
This is line 3
step2
step3
[INFO ] [Base ] Leaving application in progress...
Traceback (most recent call last):
File "C:\Users\rick\PycharmProjects\cruise2cruise\test901.py", line 170, in <module>
game_screen = test901App().run()
File "C:\Users\rick\AppData\Local\Programs\Python\Python310\lib\site-packages\kivy\app.py", line 956, in run
runTouchApp()
File "C:\Users\rick\AppData\Local\Programs\Python\Python310\lib\site-packages\kivy\base.py", line 574, in runTouchApp
EventLoop.mainloop()
File "C:\Users\rick\AppData\Local\Programs\Python\Python310\lib\site-packages\kivy\base.py", line 339, in mainloop
self.idle()
File "C:\Users\rick\AppData\Local\Programs\Python\Python310\lib\site-packages\kivy\base.py", line 379, in idle
Clock.tick()
File "C:\Users\rick\AppData\Local\Programs\Python\Python310\lib\site-packages\kivy\clock.py", line 733, in tick
self.post_idle(ts, self.idle())
File "C:\Users\rick\AppData\Local\Programs\Python\Python310\lib\site-packages\kivy\clock.py", line 776, in post_idle
self._process_events()
File "kivy\\_clock.pyx", line 620, in kivy._clock.CyClockBase._process_events
File "kivy\\_clock.pyx", line 653, in kivy._clock.CyClockBase._process_events
File "kivy\\_clock.pyx", line 649, in kivy._clock.CyClockBase._process_events
File "kivy\\_clock.pyx", line 218, in kivy._clock.ClockEvent.tick
TypeError: ScrollWindow.recalc_height() takes 1 positional argument but 2 were given
Process finished with exit code 1
You can access the print_gui_output()
method by replacing:
game_screen.print_gui_output(prompt)
with:
App.get_running_app().root.ids.scroll_window.print_gui_output(prompt)
The App.get_running_app()
gets the current running App
, then root
gets the root widget of the App
(which is a GameScreen
instance returned by your build()
method), then ids.scroll_window
get the instance of ScrollWindow
that contains the print_gui_output()
method.
Note that ids
specified in the kv
are only added to the root of the rule in which they appear. So the game_screen
id
would only appear in the GameScreen
instance. In fact, since that id
would only reference itself, that id
is not included in the GameScreen
ids
.
The error concerning the recalc_height()
method is caused by the Clock.schedule_once()
adding a dt
argument when it calls recalc_height()
. You can handle that by just adding *args
to the signature of recalc_height()
:
def recalc_height(self, *args):