Почему эта ошибка не обнаруживается после добавления виджета, созданного с помощью Builder.load_string ()? - PullRequest
0 голосов
/ 22 июня 2019

Сводка о том, что идет не так

Я пытаюсь добавить корневой виджет в существующее приложение .kv, где указанный произвольный корневой виджет создается с помощью kivy.lang.Builder.load_string method.Это будет хорошо работать, если строка kivy, предоставленная в Builder, представляет действительный и допустимый код .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, из которого создается второй.Приведенные ниже два изображения демонстрируют отсутствие ошибок.

Screen1 Screen2

Ниже приведен пример правильного поведения, когда текстовый ввод содержит все, что приводит к ошибке:

enter image description here

Неожиданное поведение

Это последнее изображение правильно показывает всплывающее сообщение.Однако при вводе следующего ввода в поле TextInput, например:

FloatLayout:
    Label:
        text: "Hello World"
        pos_hint: 0.5, 0.7

Что является ошибкой в ​​аргументе значения pos_hint.Затем приложение вылетает, когда я нажимаю кнопку call .И вместо ожидаемого всплывающего сообщения я получаю фактическую трассировку стека!

<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, сигнализируя, что он прошел через него без каких-либо ошибок , верно?

Вопрос

Таким образом, если вышеупомянутая сгенерированная ошибка не была обнаружена, что ее вызвало и как / где я могу ее правильно отловить, чтобы приложение в конечном итоге соответствовало предполагаемому поведению (Ошибкасамое большее всплывающее сообщение)?

1 Ответ

1 голос
/ 23 июня 2019

Я полагаю, что Exception добавляется в mainloop после завершения вашего call() метода.Обычно обновления GUI происходят только в главном потоке, и поэтому должны ждать, пока ваш код (который выполняется в основном потоке) завершится.Вы все еще можете поймать эти Exception s, используя 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
...