python - 为什么在添加使用 Builder.load_string() 创建的小部件后没有捕获到这个 kivy 错误?
问题描述
问题总结
我正在尝试将根小部件添加到现有的 .kv 应用程序,其中所述任意根小部件由 .kv 创建kivy.lang.Builder.load_string method
。如果提供给 Builder 的 kivy 字符串代表有效且合法的 .kv 代码,这将正常工作。预计,否则它将失败。
为了解决这个问题,我添加了一个try
-except
块,希望能发现任何可能导致无法添加适当的 kivy 小部件的错误。然后在弹出消息中使用对应Exception
的,之后最终不应该添加无效的小部件。
对于某些输入,这按预期工作(如果错误则显示弹出消息)。但是,对于特定的字符串输入,应用程序会崩溃而没有发现负责任的错误。现在我想知道为什么这些错误没有被捕获,以及如何正确地捕获它们。具体代码见下文。
我的应用程序
我的应用程序由一个.py
和一个.kv
文件1组成,如下(简化):
# main.kv
ScreenManager:
Screen:
name: 'string_screen'
BoxLayout:
orientation: 'vertical'
TextInput:
id: code_text
text: app.text
Button:
text: 'call'
on_release: app.call()
Screen:
name: 'called_screen'
BoxLayout:
id: render_layout
<Button>:
size_hint: 0.5, None
height: '1.2cm'
<MsgPopup>:
size_hint: .75, .6
title: "Attention"
BoxLayout:
orientation: 'vertical'
padding: 10
spacing: 20
Label:
id: message_label
size_hint_y: 0.4
text: "Label"
Button:
text: 'Dismiss'
size_hint_y: 0.4
on_press: root.dismiss()
和python文件:
# main.py
from kivy.app import App
from kivy.properties import StringProperty
from kivy.lang import Builder
from kivy.uix.popup import Popup
class MainApp(App):
text = StringProperty()
kv = None
def call(self):
kv_text = self.root.ids['code_text'].text
try:
self.kv = Builder.load_string(kv_text)
print(self.kv)
self.root.ids['render_layout'].clear_widgets()
print('cleared')
self.root.ids['render_layout'].add_widget(self.kv)
print('added')
self.root.current = 'called_screen'
self.root.transition.direction = 'left'
print('swiped')
except Exception as e:
popup = MsgPopup(e)
popup.open()
class MsgPopup(Popup):
def __init__(self, msg):
super().__init__()
self.ids.message_label.text = str(msg)
if __name__ == '__main__':
MainApp().run()
1我的实际应用程序包含一些额外的和更详细的代码,但它的这个简化版本足以重现不希望的行为。
正如您在代码中看到的,该应用程序由两个屏幕组成。第一个中的主要元素TextInput
是创建第二个的元素。下面的两张图片演示了没有错误的情况。
以下是正确行为的示例,当文本输入包含任何会产生错误的内容时:
意外行为
最后一张图片正确显示了弹出消息。但是,例如,当我在 TextInput 字段中输入以下输入时:
FloatLayout:
Label:
text: "Hello World"
pos_hint: 0.5, 0.7
这是pos_hint
value 参数中的错误。然后,一旦我按下呼叫按钮,应用程序就会崩溃。而不是预期的弹出消息,我得到一个实际的堆栈跟踪!
<kivy.uix.floatlayout.FloatLayout object at 0x0000016F7F0FC800>
cleared
File "C:/Users/ajdin/.PyCharmCE2019.1/config/scratches/scratch_1.py", line 34, in <module>
added
swiped
MainApp().run()
File "C:\Users\ajdin\Anaconda3\lib\site-packages\kivy\app.py", line 826, in run
runTouchApp()
File "C:\Users\ajdin\Anaconda3\lib\site-packages\kivy\base.py", line 502, in runTouchApp
EventLoop.window.mainloop()
File "C:\Users\ajdin\Anaconda3\lib\site-packages\kivy\core\window\window_sdl2.py", line 727, in mainloop
self._mainloop()
File "C:\Users\ajdin\Anaconda3\lib\site-packages\kivy\core\window\window_sdl2.py", line 460, in _mainloop
EventLoop.idle()
File "C:\Users\ajdin\Anaconda3\lib\site-packages\kivy\base.py", line 346, in idle
Clock.tick_draw()
File "C:\Users\ajdin\Anaconda3\lib\site-packages\kivy\clock.py", line 588, in tick_draw
self._process_events_before_frame()
File "kivy\_clock.pyx", line 427, in kivy._clock.CyClockBase._process_events_before_frame
File "kivy\_clock.pyx", line 467, in kivy._clock.CyClockBase._process_events_before_frame
File "kivy\_clock.pyx", line 465, in kivy._clock.CyClockBase._process_events_before_frame
File "kivy\_clock.pyx", line 167, in kivy._clock.ClockEvent.tick
File "C:\Users\ajdin\Anaconda3\lib\site-packages\kivy\uix\floatlayout.py", line 116, in do_layout
for key, value in c.pos_hint.items():
AttributeError: 'tuple' object has no attribute 'items'
Process finished with exit code 1
我在这里的预期输出将与之前类似:上面显示的堆栈跟踪消息显示在弹出窗口中,而应用程序没有崩溃!. 我希望这是因为我在按钮回调中处理异常的方式:
kv_text = self.root.ids['code_text'].text
try:
self.kv = Builder.load_string(kv_text)
print(self.kv)
self.root.ids['called_screen'].clear_widgets()
print('cleared')
self.root.ids['called_screen'].add_widget(self.kv)
print('added')
self.root.current = 'called_screen'
self.root.transition.direction = 'left'
print('swiped')
except Exception as e:
popup = MsgPopup(e)
popup.open()
因此,如果 load_string 方法中出现错误,我希望能抓住它。否则,如果它以某种方式通过,我希望在 add_widget 方法中捕获一个错误。但是,从上面的堆栈跟踪看来,它成功地传递了所有这些语句,并给出了错误的文本输入!。您可以从堆栈跟踪中的打印输出中看到这一点:
...
<kivy.uix.floatlayout.FloatLayout object at 0x0000016F7F0FC800>
cleared
File "C:/Users/ajdin/.PyCharmCE2019.1/config/scratches/scratch_1.py", line 34, in <module>
added
swiped
...
它打印 try 块中的所有语句,表明它通过它没有任何错误,对吗?
问题
因此,如果未捕获上述抛出的错误,是什么原因造成的,以及我如何/在哪里正确捕获它,以便应用程序以预期的行为结束(最多弹出错误消息)?
解决方案
我相信在您的方法完成后Exception
会被抛出。通常,GUI 更新只发生在主线程上,因此必须等到您的代码(在主线程上运行)完成。您仍然可以通过将以下代码添加到您的. 中来使用 s 捕获这些 s :mainloop
call()
Exception
Kivy
ExceptionHandler
Python
from kivy.base import ExceptionHandler, ExceptionManager
class E(ExceptionHandler):
def handle_exception(self, inst):
app = App.get_running_app()
if app.scheduled_switch is not None:
app.scheduled_switch.cancel() # cancel the scheduled switch
app.scheduled_switch = None
if app.Exception_counter == 0:
popup = MsgPopup(inst)
popup.open()
app.Exception_counter += 1
return ExceptionManager.PASS
ExceptionManager.add_handler(E())
上面的代码还取消了可能安排的Screen
切换。
然后,为了限制每次调用Popup
只出现一次,修改你的包含一个. 此外,为了防止切换到,修改后的代码用于安排切换(可能会被 取消):call()
App
Exception_counter
called
Screen
Clock
ExceptionHandler
class MainApp(App):
def __init__(self, **kwargs):
self.Exception_counter = 0
self.scheduled_switch = None
super(MainApp, self).__init__(**kwargs)
def call(self):
self.Exception_counter = 0
kv_text = self.root.ids['code_text'].text
try:
self.kv = Builder.load_string(kv_text)
print(self.kv)
self.root.ids['render_layout'].clear_widgets()
print('cleared')
self.root.ids['render_layout'].add_widget(self.kv)
print('added')
# schedule switch to 'called' screen
self.scheduled_switch = Clock.schedule_once(self.switch_to_called_screen, 0.25)
except Exception as e:
popup = MsgPopup(e)
popup.open()
def switch_to_called_screen(self, dt):
self.root.current = 'called_screen'
self.root.transition.direction = 'left'
print('swiped')
self.scheduled_switch = None
推荐阅读
- c# - 有没有办法减少共享通用功能的重载方法中出现的重复代码?如果是这样,怎么做?
- oracle - lsnrctl:在 Ubuntu 18.04 中找不到命令
- node.js - HTTP 调用集成模式 - 直接从 Javascript、Axios 和 Node 进行 HTTP 调用,哪个更安全?
- sql - 如何使用 SQL 选择每个唯一日期的唯一会话?
- javascript - 如果我以星号或所选字母开始书写,如何在搜索字段中定义文本颜色?
- android - Android Kotlin:如何在 Webview 内部或外部加载网页
- jquery - 如何将 jquery 放在 laravel 控制器上?
- javascript - 在 ie11 上使用 reactjs,在生产中使用空白页面,但如果检查器打开,则页面加载正常
- javascript - 如何使用javascript或jquery在输入中一个接一个地添加多个值?
- python - 如何访问熊猫中的一行?