#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 для ясности.