Skip to content

配置控件属性

一、什么决定了函数参数的控件类型?

(一)一般规则:函数参数类型信息

一般而言,函数参数所对应的输入控件由该参数的数据类型决定,而函数参数的类型则主要由Python类型标注确定。

def f1(a: str, b: int, c: float):
    pass

例如,在上面的函数f1中,参数abc的类型分别被标注为strintfloat。在运行时,PyGUIAdapter将读取这一信息,分别为abc生成LineEditIntSpinBoxFloatSpinBox,作为其输入控件。

PyGUIAdapter内部,实现了一套映射机制,可以将特定的数据类型与特定的控件类型关联起来,在判断应当为某个函数参数生成何种类型的控件时,只需按图索骥即可。因此,正确地为函数的参数标注数据类型至关重要,对于PyGUIAdapter而言,函数参数的类型标注不是那种可有可、无锦上添花的东西!

PyGUIAdapter除了从函数参数的类型注解中获取参数的数据类型信息,它还实现了一些“类型推断”机制。比如,可以从函数参数的默认值推断出其类型。

def f2(a=10, b=0.5, c="hello", e=True, f=(1, 2, 3)):
 pass

或者,可以通过文档字符串(docstring)中参数的描述推断其类型。比如在下面的代码片段中,使用文档字符串中描述了函数f3()各个参数的类型信息:

def f3(a, b, c):
 """
 这是一个示例函数.

 Args:
     a (int): 参数a的描述.
     b (str): 参数b的描述.
     c (list): 参数c的描述.

 Returns:
     bool: 函数返回的结果描述.
 """
 pass

但是,必须要指出的是,所谓的“类型推断”机制仅仅是一种辅助的手段,并不总是准确,也不保证可靠,通过类型标注语法准确、情形、无歧义地指明函数参数的类型,永远是更优的选择。

PyGUIAdapter已经为Python中常用数据类型实现了对应的输入控件,能够覆盖绝大多数需求。因此,一般情况下,开发者的主要工作就是正确地标注函数各个参数的类型,这是许多Python程序员已经习惯的做法。

关于PyGUIAdapter支持的数据类型及其所对应的控件,可以参考:控件与参数数据类型。。

当然,如果开发者需要将某个复杂的自定义数据类型作为参数,而内置的控件又无法满足需求,那么PyGUIAdapter也提供了自定义控件接口,并且允许开发者在自定义数据类型与自定义控件之间建立映射关系,这意味着,开发者可以像使用内置控件一样使用自定义控件。

关于如何实现自定义控件,可以参考:这篇文档

(二)例外规则:手动指定参数的控件类型

虽然PyGUIAdapter推荐开发者使用类型注解来确定函数参数的控件类型,但并不强制这么做。PyGUIAdapter提供了手动指定函数参数控件类型的方法。其中,最简单的一种方法是在调用GUIAdapter.add()函数时,通过widget_configs参数指定参数的控件配置类

每种类型的控件都有一个与之关联的控件配置类,控件配置类中不仅定义了控件类的属性,而且包含控件类本身的信息,也就是说,通过控件配置类可以获取其关联的控件类(当然反过来也成立,通过控件类我们也可以访问到其关联的配置类)。

例如,IntSpinBox控件类与其配置类IntSpinBoxConfig的关系如下:

  • IntSpinBox.ConfigClass == IntSpinBoxConfig
  • IntSpinBoxConfig.target_widget_class() == IntSpinBox

这意味着,当开发者指定了某个参数的控件配置类对象,那么他实际上也就同时指定了该参数所使用的控件的类型。

比如,在下面的例子中,函数foo()的参数abc虽然没有通过类型注解语法标注类型信息,但由于分别为它们指定了配置类对象:IntSpinBoxConfigFloatSpinBoxConfigBoolBoxConfig,这使得PyGUIAdapter也能够为其创建对应的控件——参数abc将分别使用IntSpinBoxFloatSpinBoxBoolBox作为其控件。

from pyguiadapter.adapter import GUIAdapter
from pyguiadapter.widgets import IntSpinBoxConfig, FloatSpinBoxConfig, BoolBoxConfig


def foo(a, b, c):
    pass


configs = {
    "a": IntSpinBoxConfig(),
    "b": FloatSpinBoxConfig(),
    "c": BoolBoxConfig(),
}

adapter = GUIAdapter()
adapter.add(foo, widget_configs=configs)
adapter.run()

除了在代码里指定参数的控件配置,也可以利用PyGUIAdapter提供了另一个机制做到这一点:PyGUIAdapter将函数文档字符串(docstring)中@params标记与@end标记之间的文本块视为该函数的控件配置区,在该区域中,开发者可以使用TOML语法配置函数参数的控件类型及其属性。

from pyguiadapter.adapter import GUIAdapter

def bar(a, b, c):
    """
    bar
    @param a:
    @param b:
    @param c:
    @return:

    @params
    [a]
    widget_class = "IntSpinBox"

    [b]
    widget_class = "FloatSpinBox"

    [c]
    widget_class = "BoolBox"

    @end
    """
    pass

adapter = GUIAdapter()
adapter.add(bar)
adapter.run()

如果开发者在@params-@end中配置了参数的控件,同时又向GUIAdapter.run()传入了widget_configs,那么PyGUIAdapter会将两处配置合并,对于发生冲突的属性,将以widget_configs中设置的为准。

二、配置的控件属性

控件属性控制着控件的外观和行为,不同类型的控件既共享部分共有的属性,也拥有各自专属的属性。

例如:

  • 所有类型的控件default_valuelabeldescriptiondefault_value_descriptiongroup

stylesheet等属性;

  • IntSpinBox额外定义了min_valuedisplay_integer_base等属性;
  • BoolBox额外定义了true_textfalse_text等属性;
  • LineEdit额外具有echo_modeplaceholder等属性;
  • ......

控件的属性定义在控件对应的配置类中,其中,公共属性来自共同的父类(基类)BaseParameterWidgetConfig,而独有属性则在各配置类子类中定义。

对控件属性进行配置,可以实现更加精细化的控制,比如:设置数字输入的上下限,设置文本框的输入掩码等等。很多时候,合适的配置不仅可以提高用户体验,还可以增强程序的健壮性。

为什么合理配置控件属性能增强程序的健壮性?

其实很好理解,如果能够提前将用户的输入值限定在某个特定的范围内,就可以在一定程度上避免出现那个著名的程序员笑话里的场景:“一个顾客走进一间酒吧,一切都运行正常,直到他点了一份炒饭......”。

在本文的第一部分其实已经演示过配置参数控件属性的方法,只不过当时讨论的重心是如何手动指定参数控件的类型。下面,让我们聚焦参数控件属性的配置。

(一)使用配置类对象配置控件属性

开发者可以通过向GUIAdapter.run()函数传入widget_configs来实现控件属性的配置。

1、使用配置类对象

实际上,传入配置类对象的同时也指定了参数控件的类型,也就是说,在这种情况下,参数的控件将不再由其类型注解决定。因此,开发者应当注意避免函数参数的类型注解与其配置类对象不兼容的情况(这种不兼容主要是指语义上的不兼容,比如:为类型标注为int的参数指定一个LineEditConfig类型的对象,这会把该参数的控件变成一个输入str的单行文本输入框)。

from pyguiadapter.adapter import GUIAdapter
from pyguiadapter.widgets import (
    IntSpinBoxConfig,
    SliderConfig,
    TextEditConfig,
)


def foo(a: int, b: int, c: str = "hello world!"):
    pass


foo_configs = {
    "a": IntSpinBoxConfig(
        default_value=1,
        min_value=0,
        max_value=10,
        step=1,
        label="a",
        description="parameter a",
    ),
    "b": SliderConfig(
        default_value=50,
        min_value=0,
        max_value=100,
        label="b",
        description="parameter b",
    ),
    "c": TextEditConfig(
        default_value="Hello PyGUIAdapter!",
        label="c",
        description="parameter c",
    ),
    }

adapter = GUIAdapter()
adapter.add(foo, widget_configs=foo_configs)
adapter.run()
2、使用配置项字典

使用配置项字典与上一种方法本质上是等价的,只不过在这种情况下,PyGUIAdapter会首先确定参数控件的类型(通过参数的类型注解),然后获取对应的配置类,最后用配置项字典实例化该配置类得到配置类的对象。

from pyguiadapter.adapter import GUIAdapter

def foo2(a: int, b: int, c: str = "hello world!"):
    pass

foo2_configs = {
    "a": {
        "default_value": 1,
        "min_value": 0,
        "max_value": 10,
        "step": 1,
        "label": "a",
        "description": "parameter a",
    },
    "b": {
        "default_value": 50,
        "min_value": 0,
        "max_value": 100,
        "label": "b",
        "description": "parameter b",
    },
    "c": {
        "default_value": "Hello PyGUIAdapter!",
        "label": "c",
        "description": "parameter c",
    },
}


adapter = GUIAdapter()
adapter.add(foo2, widget_configs=foo2_configs)
adapter.run()

(二)在@params-@end块中配置控件属性

前文提过,PyGUIAdapter将函数文档字符串(docstring)中@params标记与@end标记之间的文本块视为该函数的控件配置区,在该区域中,开发者可以使用TOML语法配置该函数的参数控件类型及其属性。

开发者可以在@params-@end块中同时指定参数的控件类型及其属性:

from pyguiadapter.adapter import GUIAdapter


def foo(a: int, b: int, c: str = "hello world!"):
    """
    foo
    @params
    [a]
    widget_class="IntSpinBox"
    default_value=1
    min_value=0
    max_value=10
    step=1
    label="a"
    description="parameter a"

    [b]
    widget_class="Slider"
    default_value=50
    min_value=0
    max_value=100
    label="b"
    description="parameter b"

    [c]
    widget_class="TextEdit"
    default_value="Hello PyGUIAdapter!"
    label="c"
    description="parameter c"
    @end
    """


adapter = GUIAdapter()
adapter.add(foo)
adapter.run()

也可以仅指定控件的属性,将控件的类型交由类型注解决定:

from pyguiadapter.adapter import GUIAdapter

def foo(a: int, b: int, c: str = "hello world!"):
    """
    @params
    [a]
    default_value=1
    min_value=0
    max_value=10
    step=1
    label="a"
    description="parameter a"

    [b]
    default_value=50
    min_value=0
    max_value=100
    label="b"
    description="parameter b"

    [c]
    default_value="Hello PyGUIAdapter!"
    label="c"
    description="parameter c"
    @end
    """


adapter = GUIAdapter()
adapter.add(foo)
adapter.run()

(三)特殊控件属性

有几个特殊的控件属性,若开发者未对这些属性进行配置,PyGUIAdapter将尝试从其他地方获取合适的值。

1、label

如果开发者未指定控件的label,则PyGUIAdapter将使用参数的名称作为其label

from pyguiadapter.adapter import GUIAdapter


def foo(a: int, b: int):
    """
    @params
    [b]
    label = "Parameter b"
    @end
    """
    pass

adapter = GUIAdapter()
adapter.add(foo)
adapter.run()
2、default_value

如果开发者未指定控件的default_value属性,那么PyGUIAdapter将尝试从函数签名中获取参数的默认值作为default_value属性的值

from pyguiadapter.adapter import GUIAdapter


def foo(a: int = -100, b: int = 1 + 1):
    pass


adapter = GUIAdapter()
adapter.add(foo)
adapter.run()
3、description

如果开发者未指定控件的description属性,那么PyGUIAdapter将尝试从函数的文档字符串(docstring)中获取参数的描述信息作为description属性的值。

支持多种风格的docstring,包括: ReST、Google、Numpydoc-style 、Epydoc

from pyguiadapter.adapter import GUIAdapter


def foo(a: int = -100, b: int = 1 + 1):
    """
    @param a: this is parameter a
    @param b: this is parameter b
    @return:
    """


adapter = GUIAdapter()
adapter.add(foo)
adapter.run()

三、总结

PyGUIAdapter内置了丰富的参数控件,并且建立了一种简单从数据类型到控件类型映射机制。开发者只需使用类型标注语法为函数参数标注正确的类型,PyGUIAdapter就能自动为其生成合适的输入控件。

与此同时,PyGUIAdapter充分考虑到了开发者的自定义需求,为开发者提供了多种配置控件属性的机制。通过对参数控件的合理配置,开发者不仅可以构建用户体验更加友好的GUI应用程序,而且可以增强程序的健壮性。