Kivy: пользовательская кнопка не обновляется с изменением self.state

#python #kivy

#python #kivy

Вопрос:

Я пытаюсь создать графический интерфейс и, просмотрев различные сообщения здесь, я все еще в тупике. Моя проблема в том, что пользовательская кнопка, которую я создал для отражения состояния кнопки GPIO, не обновляет свой внешний вид, когда я устанавливаю self.state что-то другое. Я думаю, что это может быть связано с конструкциями объектов, но я не могу понять, как это исправить.

main.py

 import kivy
kivy.require('1.11.1')

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ObjectProperty
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.clock import Clock
from kivy.core.window import Window
import RPi.GPIO as GPIO

# make app run in fullscreen mode
Window.fullscreen = 'auto'  # uses display's current resolution

# Set up GPIO
ok_btn_pin = 4
GPIO.setmode(GPIO.BCM)
GPIO.setup(ok_btn_pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.add_event_detect(ok_btn_pin, GPIO.BOTH)    #detect if GPIO.RISING or GPIO.FALLING occur


class GPIOButton(Button):

    btn_gpio_pin = NumericProperty(-1)

    def __init__(self, **kwargs):
        super(GPIOButton, self).__init__(**kwargs)
        print("GPIOButton __init__ called")
        print("btn_gpio_pin =", self.btn_gpio_pin)

    def update(self, dt):
        #print("GPIOButton update() called")
        if GPIO.input(self.btn_gpio_pin) == GPIO.HIGH and GPIO.event_detected(self.btn_gpio_pin):
            self.state = 'down'            
            print("Pin", self.btn_gpio_pin, self.state)
        elif GPIO.input(self.btn_gpio_pin) == GPIO.LOW and GPIO.event_detected(self.btn_gpio_pin):
            self.state = 'normal'
            print("Pin", self.btn_gpio_pin, self.state)


class LeftSidebar(FloatLayout):

    ok_btn = GPIOButton(btn_gpio_pin = ok_btn_pin)

    def __init__(self, **kwargs):
        super(LeftSidebar, self).__init__(**kwargs)
        print("LeftSidebar __init__ called")

    def update(self, dt):
        #print("LeftSidebar update() called")
        self.ok_btn.update(dt)


class LifterGUI(FloatLayout):

    left_sidebar = LeftSidebar()

    def __init__(self, **kwargs):
        super(LifterGUI, self).__init__(**kwargs)
        print("LifterGUI __init__ called")
        Clock.schedule_interval(self.update, 1.0/10.0)

    def update(self, dt):
        #print("LifterGUI update() called")
        self.left_sidebar.update(dt)


class LifterApp(App):

    def build(self):
        self.root = LifterGUI()
        return self.root


if __name__ == '__main__':
    try:
        LifterApp().run()
    finally:
        GPIO.cleanup()
  

lifter.kv

 #:kivy 1.11.1
#:set camera_width_percent 4.0/5.0
#:set sidebar_width_percent (1.0 - camera_width_percent) / 2.0

<LeftSidebar>:
    ok_btn_button: ok_btn

    #:set num_btns 10.0
    
    canvas:
        Color:
            rgb: 1, 0, 0
        Rectangle:
            pos: self.pos
            size: self.size
    GPIOButton:
        id: ok_btn
        btn_gpio_pin: 4
        text: "ok"
        size_hint: (1.0, 1.0/num_btns)
        pos_hint: {'top': 1.0/num_btns}
    

<LifterGUI>:
    left_sidebar_widget: left_sidebar

    LeftSidebar: 
        id: left_sidebar
        size_hint: (sidebar_width_percent, 1.0) 

  

Вывод терминала

Я нажимаю кнопку GPIO 3 раза, которая выводится на терминал, но не обновляет внешний вид GPIOButton. Обратите внимание, что LeftSidebar __init__ и GPIOButton __init__ по какой-то причине вызываются дважды (я думаю, один раз из моих объявлений атрибутов класса и один раз из файла kv, но я не уверен). Я попытался использовать ObjectProperty(None) вместо ok_btn и left_sidebar, чтобы посмотреть, поможет ли это, но тогда я просто получаю AttributeError: 'NoneType' object has no attribute 'update' ошибки, поэтому я не уверен, что это правильный подход.

 [INFO   ] [Logger      ] Record log in /home/pi/.kivy/logs/kivy_20-09-30_53.txt
[INFO   ] [Kivy        ] v1.11.1
[INFO   ] [Kivy        ] Installed at "/usr/local/lib/python3.7/dist-packages/kivy/__init__.py"
[INFO   ] [Python      ] v3.7.3 (default, Dec 20 2019, 18:57:59) 
[GCC 8.3.0]
[INFO   ] [Python      ] Interpreter at "/usr/bin/python3"
[INFO   ] [Factory     ] 184 symbols loaded
[INFO   ] [Image       ] Providers: img_tex, img_dds, img_sdl2, img_pil, img_gif (img_ffpyplayer ignored)
[INFO   ] [Text        ] Provider: sdl2(['text_pango'] ignored)
[INFO   ] [Window      ] Provider: sdl2(['window_egl_rpi'] ignored)
[INFO   ] [GL          ] Using the "OpenGL" graphics system
[INFO   ] [GL          ] Backend used <sdl2>
[INFO   ] [GL          ] OpenGL version <b'3.1 Mesa 19.3.2'>
[INFO   ] [GL          ] OpenGL vendor <b'VMware, Inc.'>
[INFO   ] [GL          ] OpenGL renderer <b'llvmpipe (LLVM 9.0.1, 128 bits)'>
[INFO   ] [GL          ] OpenGL parsed version: 3, 1
[INFO   ] [GL          ] Shading version <b'1.40'>
[INFO   ] [GL          ] Texture max size <8192>
[INFO   ] [GL          ] Texture max units <32>
[INFO   ] [Window      ] auto add sdl2 input provider
[INFO   ] [Window      ] virtual keyboard not allowed, single mode, not docked
GPIOButton __init__ called
btn_gpio_pin = 4
LeftSidebar __init__ called
LeftSidebar __init__ called
GPIOButton __init__ called
btn_gpio_pin = -1
LifterGUI __init__ called
[INFO   ] [ProbeSysfs  ] device match: /dev/input/event0
[INFO   ] [MTD         ] Read event from </dev/input/event0>
[INFO   ] [ProbeSysfs  ] device match: /dev/input/event0
[INFO   ] [HIDInput    ] Read event from </dev/input/event0>
[INFO   ] [Base        ] Start application main loop
[INFO   ] [HIDMotionEvent] using <WaveShare WS170120>
[INFO   ] [MTD         ] </dev/input/event0> range position X is 0 - 800
[INFO   ] [HIDMotionEvent] <WaveShare WS170120> range ABS X position is 0 - 800
[INFO   ] [MTD         ] </dev/input/event0> range position Y is 0 - 480
[INFO   ] [HIDMotionEvent] <WaveShare WS170120> range ABS Y position is 0 - 480
[INFO   ] [MTD         ] </dev/input/event0> range touch major is 0 - 0
[INFO   ] [HIDMotionEvent] <WaveShare WS170120> range ABS pressure is 0 - 255
[INFO   ] [MTD         ] </dev/input/event0> range touch minor is 0 - 0
[INFO   ] [HIDMotionEvent] <WaveShare WS170120> range position X is 0 - 800
[INFO   ] [MTD         ] </dev/input/event0> range pressure is 0 - 255
[INFO   ] [HIDMotionEvent] <WaveShare WS170120> range position Y is 0 - 480
[INFO   ] [MTD         ] </dev/input/event0> axes invertion: X is 0, Y is 0
[INFO   ] [HIDMotionEvent] <WaveShare WS170120> range pressure is 0 - 255
[INFO   ] [MTD         ] </dev/input/event0> rotation set to 0
[INFO   ] [GL          ] NPOT texture support is available
Pin 4 down
Pin 4 normal
Pin 4 down
Pin 4 normal
Pin 4 down
Pin 4 normal
  

Ответ №1:

__init__() Оф GPIOButton вызывается дважды. Один раз, когда ваш build() метод вызывается и self.root = LifterGUI() выполняется. Это создает GPIOButton то, что отображается в вашем графическом интерфейсе с помощью kv правил. __init__() Метод вызывается снова при ok_btn = GPIOButton(btn_gpio_pin = ok_btn_pin) выполнении в вашем LeftSidebar классе. Этот второй вызов создает экземпляр GPIOButton , который не отображается в вашем графическом интерфейсе, но на который ссылается update() метод.

Поскольку у вас уже настроена ссылка на GPIOButton в вашем kv , вы можете изменить LeftSidebar класс, чтобы использовать эту ссылку:

 class LeftSidebar(FloatLayout):

    ok_btn_button = ObjectProperty(None)

    def __init__(self, **kwargs):
        super(LeftSidebar, self).__init__(**kwargs)
        print("LeftSidebar __init__ called")

    def update(self, dt):
        #print("LeftSidebar update() called")
        self.ok_btn_button.update(dt)
  

ok_btn_button В вашем kv и ok_btn_button в LeftSidebar настройке создается ссылка на GPIOButton то, что встроено в kv . При этом вы можете ссылаться на кнопку, используемую self.ok_btn_button в LeftSidebar классе.

Обратите внимание, что у вас почти такая же ошибка в вашем LifterGUI .

Комментарии:

1. Это работает, спасибо. Я нахожу это немного запутанным, что либо ok_btn_button = ObjectProperty(None) .... self.ok_btn_button.update(dt) работает, либо #no object declaration in main.py .... self.ids.ok_btn.update(dt) работает, но нет self.ids.ok_btn_button.update(dt) . Изначально у меня были разные имена, чтобы я мог лучше понять, как работает язык kv, но, возможно, проще просто иметь ok_btn_button: ok_btn_button для удобства обслуживания, ха-ха. Еще раз спасибо

2. На самом деле вы можете ok_btn_button = ObjectProperty(None) не учитывать, потому ok_btn_button: ok_btn что в kv будет создано ObjectProperty , если оно еще не существует. Я предпочитаю явно создавать ObjectProperty в коде python для ясности.