KIVY ScreenManager 使用案例常见错误总结

# 导入Kivy的App类,它是所有kivy应用的基类
from kivy.app import App
# kivy内置了丰富的控件widget  如 按钮button 复选框checkbox 标签label 输入框textinput 滚动容器scrollable container等
from kivy.uix.button import Button
# 引入BoxLayout 布局
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen



class IndexPage(BoxLayout):
    # 初始化
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # 添加一个按钮
        self.join = Button(text="Hello World")
        # 将按钮添加到页面控件中
        self.add_widget(self.join)
# 从App类中继承了kivy应用组基本的方法,如创建窗口、设置窗口的大小和位置等

class TestApp(App):
    def build(self):
        self.sm = ScreenManager()
        #build()方法返回的控件,在kivy中称之为“根控件” root widget
        # kivy将自动缩放根控件,让它填满整个窗口

        screen = Screen(name = 'index')
        screen.add_widget(IndexPage)
        self.sm.add_widget(screen)
        return self.sm
# 当.py文件被直接运行时, if__name__ == '__main__' 之下的代码块将被运行
# 当.py文件以模块的形式被导入时,if __name__ == '__main__' 之下的代码块不被运行
if __name__ == '__main__':
    TestApp().run()  #启动应用程序

 TypeError: unbound method EventDispatcher.bind() needs an argument

如图报错,是因为第二十八行的代码中应该做这样修改

       screen.add_widget(IndexPage())

即可正常运行。

kivy中 screen.add_widget(IndexPage()) 为什么不可以用 screen.add_widget(IndexPage)

在Kivy框架中,screen.add_widget(IndexPage()) 和 screen.add_widget(IndexPage) 之间的主要区别在于它们如何引用和实例化IndexPage类。

1. screen.add_widget(IndexPage())

当你使用IndexPage()时,你实际上是在调用IndexPage类的构造函数(也称为__init__方法),从而创建了一个IndexPage的实例。这个实例是一个Kivy小部件(Widget)对象,可以被添加到其他小部件中,比如屏幕(Screen)或布局(Layout)。因此,screen.add_widget(IndexPage())是正确的用法,因为它将一个IndexPage的实例作为子小部件添加到了screen上。

2. screen.add_widget(IndexPage)

然而,当你只写IndexPage而不加括号时,你并没有创建IndexPage的实例,而是引用了IndexPage类本身。在Kivy中,你不能直接将一个类作为小部件添加到另一个小部件中;你必须添加类的实例。因此,screen.add_widget(IndexPage)会导致一个错误,因为add_widget方法期望一个Widget的实例作为参数,而不是一个类。

总结

  • IndexPage() 创建了一个IndexPage的实例。
  • IndexPage 引用了IndexPage类本身,而不是其实例。

在Kivy(以及大多数面向对象的编程语言)中,类是用于创建对象的蓝图或模板。对象(或实例)是类的一个具体实现,包含类的所有属性和方法,以及可能的额外数据。因此,当你想要使用某个类的功能时,你需要先创建该类的一个实例。

另一方面,在使用纯py文件时,onpress要和bind()方法相搭配才能实现页面转换功能以及current的赋值。

# 导入Kivy的App类,它是所有kivy应用的基类
from kivy.app import App
# kivy内置了丰富的控件widget  如 按钮button 复选框checkbox 标签label 输入框textinput 滚动容器scrollable container等
from kivy.uix.button import Button
# 引入BoxLayout 布局
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen



class IndexPage(BoxLayout):
    # 初始化
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # 添加一个按钮

        self.join = Button(text="Hello World")
        self.join.bind(on_press=self.go2)
        # 将按钮添加到页面控件中
        self.add_widget(self.join)
# 从App类中继承了kivy应用组基本的方法,如创建窗口、设置窗口的大小和位置等

    def go2(self, instance):
        sm = App.get_running_app().root
        sm.current='menu'


class MenuPage(FloatLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)



class TestApp(App):
    def build(self):
        self.sm = ScreenManager()
        #build()方法返回的控件,在kivy中称之为“根控件” root widget
        # kivy将自动缩放根控件,让它填满整个窗口

        screen = Screen(name = 'index')
        screen.add_widget(IndexPage())
        self.sm.add_widget(screen)

        screen = Screen(name ='menu')
        screen.add_widget(MenuPage())
        self.sm.add_widget(screen)
        return self.sm
# 当.py文件被直接运行时, if__name__ == '__main__' 之下的代码块将被运行
# 当.py文件以模块的形式被导入时,if __name__ == '__main__' 之下的代码块不被运行
if __name__ == '__main__':
    TestApp().run()  #启动应用程序

def go2(self, instance):函数中为什么必须要加个instance?

 TypeError: go2() takes 1 positional argument but 2 were given

在Kivy的go2函数中,instance参数是on_press事件自动传递的,代表触发事件的按钮实例。尽管在这个特定的例子中,你可能不需要直接使用这个instance参数来执行屏幕切换,但将其包含在函数定义中是一种好的编程习惯,特别是当你打算在未来扩展按钮的行为时。

然而,对于简单的屏幕切换逻辑,instance参数确实不是必需的。你完全可以忽略它,或者如果你确信不会用到它,甚至可以从函数签名中去掉它。但是,保持它在那里通常不会造成任何问题,而且它提供了一种灵活性,允许你在未来需要时轻松地引用触发事件的特定按钮。

如果你打算在按钮的回调函数中执行与按钮本身相关的操作(比如改变按钮的文本、颜色或禁用按钮),那么instance参数就变得非常有用了
def go2(self, instance):  
    instance.text = "Going to Screen Two..."  # 更新按钮文本  
    sm = App.get_running_app().root  
    sm.current = 'screen_two'

在这个例子中,instance参数允许你访问并修改触发事件的按钮的属性。

详细解析 sm = App.get_running_app().root

在Kivy框架中,sm = App.get_running_app().root 这行代码是获取当前正在运行的Kivy应用程序的根控件的一种方式。为了更好地理解这行代码,我们可以将其分解为几个部分进行解析。

1. App.get_running_app()

  • App 是Kivy中所有应用程序的基类。当你定义自己的应用程序类时,你通常会继承自这个类。
  • get_running_app() 是一个类方法,用于获取当前正在运行的Kivy应用程序的实例。这个方法会遍历所有已创建的应用程序实例,并返回最顶层的那个(即用户最近启动的那个)。

2. .root

  • .root 是App实例的一个属性,它指向应用程序的根控件。在Kivy应用程序中,根控件是整个应用程序的入口点,所有其他控件都是这个根控件的子控件或子控件的子控件,以此类推。
  • 通常,在App类的build方法中,你会创建并返回一个控件作为根控件。这个控件可以是任何类型的控件,但最常见的是布局控件(如BoxLayoutGridLayout等)或ScreenManager控件(用于管理多个屏幕)。

综合解析

因此,sm = App.get_running_app().root 这行代码的作用是:

  1. 调用App.get_running_app()获取当前正在运行的Kivy应用程序的实例。
  2. 通过.root属性访问这个应用程序实例的根控件。
  3. 将根控件的引用赋值给变量sm(在这个上下文中,sm很可能是ScreenManager的一个实例,但这不是强制的,它取决于你在build方法中返回了什么作为根控件)。

一旦你有了根控件的引用,你就可以通过它来访问或修改应用程序中的其他控件了。在多屏应用程序中,根控件通常是ScreenManager,因为它允许你管理多个Screen控件并在它们之间切换。

示例

假设你有一个名为MyApp的应用程序类,它继承自App,并在其build方法中创建了一个ScreenManager作为根控件:

from kivy.app import App  
from kivy.uix.screenmanager import ScreenManager  
  
class MyApp(App):  
    def build(self):  
        # 创建一个ScreenManager实例  
        sm = ScreenManager()  
        # ...(可能添加了一些Screen到sm中)  
        # 返回ScreenManager实例作为根控件  
        return sm  
  
# ...(MyApp类的其他部分和应用程序的启动代码)

在这个例子中,App.get_running_app().root将返回你在build方法中创建的ScreenManager实例。因此,你可以通过sm = App.get_running_app().root来获取这个ScreenManager的引用,并使用它来切换屏幕或访问其他屏幕控件。

因为关于相关传参,这里on_press又需要再py文件进行函数的设置,才能实现页面的转变,倒不如kivy人家原先自带的、正统的screen使用方法了。

然后再py文件中添加了代码

        print(dir(MenuPage()))

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__events__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__metaclass__', '__module__', '__ne__', '__new__', '__proxy_getter', '__proxy_setter', '__pyx_vtable__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '_apply_transform', '_context', '_disabled_count', '_disabled_value', '_kwargs_applied_init', '_proxy_ref', '_trigger_layout', '_walk', '_walk_reverse', 'add_widget', 'apply_class_lang_rules', 'apply_property', 'bind', 'canvas', 'center', 'center_x', 'center_y', 'children', 'clear_widgets', 'cls', 'collide_point', 'collide_widget', 'create_property', 'dec_disabled', 'disabled', 'dispatch', 'dispatch_children', 'dispatch_generic', 'do_layout', 'events', 'export_as_image', 'export_to_png', 'fbind', 'funbind', 'get_center_x', 'get_center_y', 'get_disabled', 'get_parent_window', 'get_property_observers', 'get_right', 'get_root_window', 'get_top', 'get_window_matrix', 'getter', 'go1', 'height', 'ids', 'inc_disabled', 'is_event_type', 'layout_hint_with_bounds', 'on_kv_post', 'on_opacity', 'on_touch_down', 'on_touch_move', 'on_touch_up', 'opacity', 'parent', 'pos', 'pos_hint', 'properties', 'property', 'proxy_ref', 'register_event_type', 'remove_widget', 'right', 'set_center_x', 'set_center_y', 'set_disabled', 'set_right', 'set_top', 'setter', 'size', 'size_hint', 'size_hint_max', 'size_hint_max_x', 'size_hint_max_y', 'size_hint_min', 'size_hint_min_x', 'size_hint_min_y', 'size_hint_x', 'size_hint_y', 'to_local', 'to_parent', 'to_widget', 'to_window', 'top', 'uid', 'unbind', 'unbind_uid', 'unregister_event_types', 'walk', 'walk_reverse', 'width', 'x', 'y']
 

main.py

# 导入Kivy的App类,它是所有kivy应用的基类
from kivy.app import App
from kivy.properties import ObjectProperty
# kivy内置了丰富的控件widget  如 按钮button 复选框checkbox 标签label 输入框textinput 滚动容器scrollable container等
from kivy.uix.button import Button
# 引入BoxLayout 布局
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen



class IndexPage(BoxLayout):
    # 初始化
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # 添加一个按钮

        self.join = Button(text="Hello World")
        self.join.bind(on_press=self.go2)
        # 将按钮添加到页面控件中
        self.add_widget(self.join)
# 从App类中继承了kivy应用组基本的方法,如创建窗口、设置窗口的大小和位置等

    def go2(self, instance):
        sm = App.get_running_app().root
        sm.current='menu'


class MenuPage(FloatLayout):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def go1(self, instance):
        sm = App.get_running_app().root
        sm.current = instance
class TestApp(App):


    def build(self):
        self.sm = ScreenManager()
        #build()方法返回的控件,在kivy中称之为“根控件” root widget
        # kivy将自动缩放根控件,让它填满整个窗口

        screen = Screen(name = 'index')
        screen.add_widget(IndexPage())
        self.sm.add_widget(screen)

        screen = Screen(name ='menu')
        screen.add_widget   (MenuPage())
        self.sm.add_widget(screen)

        return self.sm


# 当.py文件被直接运行时, if__name__ == '__main__' 之下的代码块将被运行
# 当.py文件以模块的形式被导入时,if __name__ == '__main__' 之下的代码块不被运行
if __name__ == '__main__':
    TestApp().run()  #启动应用程序

test.py

<MenuPage>
    Button:
        text: 'homepage'
        size_hint: .2, .15
        pos_hint: {'x':0.2, 'y':.6}
        on_press: root.go1('index')

屏幕推荐方法使用kivy学习笔记-ScreenManager实现GUI界面切换_kivy 切换界面并开启相关定时器-优快云博客

Screen Manager — Kivy 2.3.0 documentation

扩展案例

如图效果,有3个屏幕,点击右下方页码后跳转到对性的屏幕。

screen.kv


<BottomPageButton@ToggleButton>:
    group:'page'
    background_normal: 'black'
    color: (0,0,0,1)
    border:(3,3,3,3)
    on_press: root.change_page(self.text)



<PageBox@BoxLayout>:
    id: bottom_box
    orientation: 'horizontal'
    padding: 2
    spacing: 4
    size: root.width, 60


    BottomPageButton:
        id: b1
        text: '1'

        state: 'down'

    BottomPageButton:
        id: b2
        text: '2'

    BottomPageButton:
        id: b3
        text: '3'



<A1Screen>:

    Widget:
        BoxLayout:
            orientation: 'horizontal'
            x: 0
            top: root.top

            Label:
                size_hint: None, None
                pos_hint: {'left': 1,'top':1}
                size: 50, 50
                text:"PAGE 1"

        PageBox:

<A2Screen>:
    Button:
        size_hint: None, None
        pos_hint: {'right': 1,'top':1}
        size: 150, 50
        text:"Return"
        on_press:root.manager.current="1"
    Label:
        text:"PAGE 2"
        size_hint: None, None
        pos_hint: {'left': 1,'top':1}
        size: 150, 50

    Widget:
        PageBox

<A3Screen>:
    Button:
        size_hint: None, None
        pos_hint: {'right': 1,'top':1}
        size: 150, 50
        text:"Return"
        on_press:root.manager.current="1"
    Label:
        text:"PAGE 3"
        size_hint: None, None
        pos_hint: {'left': 1,'top':1}
        size: 150, 50

    Widget:
        PageBox

main.py

from kivy.app import App
from kivy.graphics import Color, Rectangle
from kivy.uix.behaviors import ToggleButtonBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
from kivy.uix.label import Label
from kivy.factory import Factory
from kivy.uix.image import Image
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.button import Button
class PageBox(BoxLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        



class BottomPageButton(ToggleButton):
    def change_page(self, instance):
        sm = App.get_running_app().root

        sm.current = instance
        print(self.ids)
        #self.ids.instance.status = 'down'



    def __init__(self, **kwargs):
        super(BottomPageButton, self).__init__(**kwargs)


class A1Screen(Screen):
    pass


class A2Screen(Screen):
    pass

class A3screen(Screen):
    pass

class ScreenApp(App):

    def build(self):
        sm = ScreenManager()
        A1 = A1Screen(name="1")
        A2 = A2Screen(name="2")
        A3 = A3screen(name='3')
        sm.add_widget(A1)
        sm.add_widget(A2)
        sm.add_widget(A3)
        return sm


if __name__ =='__main__':
    ScreenApp().run()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xinzheng新政

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值