kivy 之 Kv语言 (Kv language)

148 阅读5分钟

如何加载Kv文件

有2中加载Kv文件的方式:

  1. 通过命名约定
# 如果自定义App是以'App'结尾,约定的命名会移除'App', (所有字母小写也是OK的)
MyApp(App) 的kv文件命名: my.kv、My.kv
# 如果自定义App不是以'App'结尾,约定的命名会直接转为小写字母
MySomething(App)的kv文件命名: mysomething.kv、MySomething.kv

2. 通过Builder类直接指定要加载的文件路径

Builder.load_file('path/to/file.kv')
Builder.load_string(kv_string)

规则上下文

类规则的定义

类规则是实际存在的一个class的映射, Kv文件对应的py的作用域中应该有这个对应的class.

类规则由小括号<>内的窗口部件类名声明,后面紧跟冒号,用于定义该类任何实例的外观和行为

<MyWidget>:

规则以4个空格作为缩进. 如在代码中有一个MyGridLayout(GridLayout),此处就可以声明一个<MyGridLayout>并在其中定义其内部组件.

关键字

  • app:始终指代你的应用程序实例
  • root:指当前规则中的基础部件/模板
  • self:始终指代当前部件

特殊用法

import的用法

##访问python的模块和类
#:import name x.y.z
#:import isdir os.path.isdir
#:import np numpy

#等同于
from x.y import z as name
from os.path import isdir
import numpy as np

全局变量

## 设置全局值
#:set name value

#等同于
name = value

实例化子项

构建组件

#在kv中使用4个空格嵌套组件
#一下代码表示MyRootWidget嵌套BoxLayout, BoxLayout嵌套2个Button
MyRootWidget:
    BoxLayout:
        Button:
        Button:

#等同于
root = MyRootWidget()
box = BoxLayout()
root.add_widget(box)
box.add_widget(Button())
box.add_widget(Button())

组件的属性设置

GridLayout:
    cols: 3

#等同于
grid = GridLayout(cols = 3)

观察者与组件的结合应用

GridLayout:
    cols: len(root.data)

#等同于
grid = GridLayout(cols=len(slf.data))
self.bind(data=grid.setter('cols'))

## 待验证

事件绑定

# 对默认绑定事件设置方法
Widget:
    on_size: my_callback()

# 可以对方法进行传参, 通过args
TextInput:
    on_text: app.search(args[1])

扩展画布

MyWidget:
    canvas:
        Color:
            rgba: 1, .3, .8, .5
        Line:
            points: zip(self.data.x, self.data.y)

引用组件(Widget)

在Widget树中,通常需要访问/引用其他Widget。Kv语言提供了一种使用ID来实现此目的的方法。可以将它们视为只能在Kv语言中使用的类级变量

<myfirstwidget>:
    Button:
        id: f_but
    TextInput:
        text: f_but.state

<mysecondwidget>:
    Button:
        id: s_but
    TextInput:
        text: s_but.state

一个id的作用域仅限于声明它的规则, s_but只能在<mysecondwidget>规则中访问. > 警告 当访问一个id时, 不能把它当做一个字符串. 不应该有单引号. 正确的方式是id: value, 错误的方式是id: 'value'

id是对一个组件的弱引用, 并不代表组件本身, 因此存储id并不能组织组件被垃圾回收,例如:

<mywidget>:
    label_widget: label_widget
    Button:
        text: 'Add Button'
        on_press: root.add_widget(label_widget)
    Button:
        text: 'Remove Button'
        on_press: root.remove_widget(label_widget)
    Label:
        id: label_widget
        text: 'widget'

虽然对label_widget的引用存储在MyWidget中,但一旦其他引用被移除,这不足以使该对象继续存活,因为它只是一个弱引用。因此,在点击移除按钮(这会移除对该部件的任何直接引用)并且调整窗口大小(这会调用垃圾回收器,导致删除label_widget)之后,当点击添加按钮将该部件添加回来时,将会抛出一个ReferenceError: weakly-referenced<br>object no longer exists

为了使小部件保持存活状态,必须保留对label_widget小部件的直接引用。在这种情况下,可通过 id.__self__label_widget.__self__ 来实现。正确的做法如下:

<mywidget>:
    label_widget: label_widget.__self__

在Python代码中访问Kv文件中定义的组件

未完待续

动态类

未完待续

在多个部件中重复使用样式

未完待续

使用Kv语言进行设计

未完待续

Kv language示例

原始代码方式:

首先我们实现一个以Python代码形式实现的Kivy界面.

源码如下, 我们命名为sample1.py:

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.label import Label


# 这是一个布局容器类, 布局容器类用于填充基本组件(Widget,或叫控件)
class RootLayout(GridLayout):

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

        # 设置GridLayout是2行2列
        self.cols = 2
        self.rows = 2

        self.button1 = Button(text="Button1")  # 新建一个按钮
        self.add_widget(self.button1)  # 把这个按钮添加到当前GridLayout中

        self.button2 = Button(text="Button2")  # 新建一个按钮
        self.add_widget(self.button2)  # 把这个按钮添加到当前GridLayout中

        self.label1 = Label(text="This is label1")  # 新建一个Label
        self.add_widget(self.label1)  # 把这个标签添加到当前GridLayout中

        self.label2 = Label(text="This is label2")  # 新建一个Label
        self.add_widget(self.label2)  # 把这个标签添加到当前GridLayout中


# 这是Kivy的应用App入口类,自己实现的App需要集成kivy.app.App
# 自定义的类命名上,应该以App结尾,因为在使用kv language重新定义组件时,会"去除结尾的App,并小写所有字母"
class Sample1App(App):

    # 自定义的App需要实现方法build(self), 此方法返回App的Root容器
    def build(self):
        return RootLayout()


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

运行后,界面效果: 效果图

Kv language方式:

我们对上边Python代码中的RootLayout进行改造, 使它能使用Kv language解析.

基于命名规则, kv文件应该被命名为sample1.kv

sample1.kv的内容:

<RootLayout>:
    Button:
        text: 'new Button1'
    Button:
        text: 'new Button2'
    Label:
        text: 'new Label1'
    Label:
        text: 'new Label2'

再次运行sample1.py, 会发现命令行报出一个异常: kivy.uix.gridlayout.GridLayoutException: Too many children in GridLayout. Increase rows/cols!

这说明我们的Kv文件加载成功了,在sample1.py中我们定义的GridLayout的行数和列数均为2.

现在我们修改sample1.py中的self.cols = 4, 再一次执行, 得到的界面效果: 效果图

此时GridLayout的组件变成了8个, Kv文件中的组件优先加载, 通过Python代码编写的组件在之后加载.

现在可以移除Python代码中的组件的定义过程, 只保留Kv文件中的结果.

最终的sample1.py代码:

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.label import Label


# 这是一个布局容器类, 布局容器类用于填充基本组件(Widget,或叫控件)
class RootLayout(GridLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)


# 这是Kivy的应用App入口类,自己实现的App需要集成kivy.app.App
# 自定义的类命名上,应该以App结尾,因为在使用kv language重新定义组件时,会"去除结尾的App,并小写所有字母"
class Sample1App(App):

    # 自定义的App需要实现方法build(self), 此方法返回App的Root容器
    def build(self):
        return RootLayout()


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

最终的sample1.kv代码:

<RootLayout>:
    cols: 2
    cols: 2
    Button:
        text: 'new Button1'
    Button:
        text: 'new Button2'
    Label:
        text: 'new Label1'
    Label:
        text: 'new Label2'

后续的研究我会及时更新此文, 敬请收藏关注

官网链接: kivy.org/doc/stable/…