3.5.17.4.5. 在 CUBA Studio 中支持自定义可视化组件和 Facet

开发者可以使用 CUBA Studio 中的 界面设计器 集成自定义的 UI 组件(或 Facet),这些组件可以是在项目中,也可以是在扩展插件中。通过在组件定义处添加特殊的元数据注解实现。

自定义 UI 组件和 Facet 的支持需要 CUBA Studio 版本 14.0 以及平台版本 7.2.5 以上。

界面设计器中支持 UI 组件有以下功能:

  • 在工具箱面板(Palette panel) 展示组件。

  • 如果从工具箱添加组件,则能生成组件的 XML 脚手架代码。如果在组件的元数据配置了 XSD 命名空间,则可以自定添加至 XML。

  • 支持在界面层级结构面板(Hierarchy panel)展示新增组件的图标。

  • 支持在 Inspector 面板展示和修改组件属性。当组件属性修改时,能自动修改相应的 XML 标签属性。

  • 支持属性值提示。

  • 支持验证属性值。

  • 在界面控制器注入组件。

  • 支持生成组件的事件处理器和代理方法。

  • 支持在布局预览面板展示组件原型。

  • 支持跳转至文档的 web 页,需要开发者提供文档链接。



预先准备

我们知道,界面编辑器的工作是在界面描述中生成 XML 脚手架代码。但是,如果要在运行程序中成功加载自定义的组件或 facet,项目中还需要实现下列代码:

  • 组件或 Facet 接口

  • 对组件来说,要实现组件 loader。对 Facet 来说,要创建 facet provider,provider 是一个实现了 com.haulmont.cuba.gui.xml.FacetProvider 接口的 Spring bean,并且用 facet class 作为参数。

  • 组件及其 loader 在 cuba-ui-component.xml 文件注册。

  • 可选:需要在界面描述中定义描述组件(或facet)结构和限制的 XML shema。

这些步骤在 集成 Vaadin 组件到通用 UI 中 章节介绍。

Stepper 示例项目

使用元数据注解创建自定义 UI 组件的完整示例可以参考 Stepper Vaadin 插件集成。源码在这里:https://github.com/cuba-labs/vaadin-stepper-addon-integration

需要注意其中的部分文件:

  • 元数据注解添加至组件接口:com.company.demo.web.gui.components.Stepper

  • 组件显示在工具箱的图标(stepper.svgstepper_dark.svg)放置于 modules/web/src/com/company/demo/web/gui/components/icons 目录。

  • customer-edit.xml 界面描述在布局中使用了 stepper 组件。

import com.haulmont.cuba.gui.meta.*;

@StudioComponent(category = "Samples",
        unsupportedProperties = {"description", "icon", "responsive"},
        xmlns = "http://schemas.company.com/demo/0.1/ui-component.xsd",
        xmlnsAlias = "app",
        icon = "com/company/demo/web/gui/components/icons/stepper.svg",
        canvasBehaviour = CanvasBehaviour.INPUT_FIELD)
@StudioProperties(properties = {
        @StudioProperty(name = "dataContainer", type = PropertyType.DATACONTAINER_REF),
        @StudioProperty(name = "property", type = PropertyType.PROPERTY_PATH_REF, options = "int"),
}, groups = @PropertiesGroup(
        properties = {"dataContainer", "property"}, constraint = PropertiesConstraint.ALL_OR_NOTHING
))
public interface Stepper extends Field<Integer> {

    @StudioProperty(name = "manualInput", type = PropertyType.BOOLEAN, defaultValue = "true")
    void setManualInputAllowed(boolean value);

    boolean isManualInputAllowed();

// ...

}

如果在界面设计器打开 customer-edit.xml 界面,可以看到组件是如何集成到设计器的各个面板中。

组件工具箱面板包含 Stepper 组件:

palette

组件结构面板将组件与其他组件一起显示在树中:

hierarchy

组件 Inspector 面板展示并提供编辑组件属性的功能:

inspector

最后,布局预览面板以文本控件的形式展示组件:

preview

下面我们了解一下如果要达到这种集成效果,需要在组件接口添加哪些注解和属性。

元数据注解列表

所有 UI 元数据注解和相关类都在 com.haulmont.cuba.gui.meta 包内。下列 UI 元素支持 UI 元数据注解:

全部注解列表如下:

  • @StudioComponent - 表示带有此注解的 UI 组件可以用在界面设计器中。还需要提供一些属性支持界面设计器中其他的面板。带有该注解的接口必须是 com.haulmont.cuba.gui.components.Component 的直接或间接子类。

  • @StudioFacet - 表示带有此注解的接口可以作为 facet 用在界面设计器中。还需要提供一些属性支持界面设计器中其他的面板。带有该注解的接口必须是 com.haulmont.cuba.gui.components.Facet 的直接或间接子类。此 facet 还需要在项目中有一个关联的 FacetProvider bean。

  • @StudioProperty - 表示带有此注解的 setter 方法作为 UI 组件或 facet 的一个属性需要在 Inspector 面板展示。

  • @StudioProperties - 声明 UI 组件或 facet 的附加属性和属性组。可以用来声明与组件属性 setter 无关的其他属性,或者用来重写继承的属性,亦或从语义上验证相关的属性组。

  • @PropertiesGroup - 声明属性分组:表示一组依赖的属性,要么一起使用要么互斥。

  • @StudioElementsGroup - 表示带有此注解的 setter 方法需要在界面设计器中显示为 UI 组件或 facet 的嵌套元素组,比如 columns(多列),actions(多操作)或属性映射。

  • @StudioElement - 表示带有此注解的类或接口,在界面设计器中,需要以 UI 组件或 facet 的一部分出现,比如 column,action 或属性映射。

  • @StudioEmbedded - 用在需要将一些组件参数被抽取到 单独的 POJO 时。

  • @StudioCollection - 为嵌套子元素组声明元数据,这些子元素组需要在界面控制器支持,比如列、操作、字段。

UI 组件定义

com.haulmont.cuba.gui.meta.StudioComponent 标注组件接口,即表明该 UI 组件可用于界面设计器。

@StudioComponent(caption = "GridLayout", xmlElement = "bgrid", category = "Containers")
private interface BGridLayout extends BLayout {
    // ...
}

@StudioComponent 注解有如下属性:

  • caption - 显示在工具箱面板的组件名称。

  • description - 显示在工具箱面板的组件描述,鼠标浮上时,作为 tooltip 展示。

  • category - 工具箱面板中的分类(Containers、Components 等),组件将放到指定的分类中。

  • icon - 用在工具箱和界面结构中的组件图标路径,SVG 或 PNG 格式,路径为以组件模块根路径为起点的相对路径。注意,组件图标可以有两类,分别支持 IDE 的 light 和 dark 主题。Dark 图标的文件名需要在图标文件名后添加 _dark 后缀,比如 stepper.svgstepper_dark.svg 分别对应 light 和 dark 主题。

  • xmlElement - XML 标签的名称,当组件添加到界面时,用该名称添加至 XML 描述。

  • xmlns - XML 命名空间。当组件添加至界面,Studio 会自动在界面描述中添加对命名空间的引入。

  • xmlnsAlias - XML 命名空间别名。比如,如果命名空间别名是 track 且 XML 标签名是 googleTracker,则组件会作为 <track:googleTracker/> 标签添加至界面。

  • defaultProperty - 组件默认属性名称,当在布局选中该组件时,这些默认属性会自动在 Inspector 面板选中。

  • unsupportedProperties - 从组件父接口继承下来的属性,但是本组件并不支持。这些属性会在 Inspector 面板隐藏。

  • canvasBehavior - 定义此 UI 组件如何在布局预览面板展示。支持的选项:

    • COMPONENT - 组件在预览面板显示为一个方框,带有图标。

    • INPUT_FIELD - 组件在预览面板显示为文本输入控件。

    • CONTAINER - 组件预览面板显示为组件容器。

  • canvasIcon - 组件显示在预览面板的图标路径。canvasBehaviour 属性需要是 COMPONENT 值。图标文件需要 SVG 或 PNG 格式。如果没有设置该属性,则使用 icon 属性。

  • canvasIconSize - 显示在预览面板的图标大小。支持:

    • SMALL - 小图标

    • LARGE - 大图标,且组件 id 显示在图标下方。

  • containerType - 容器布局的类型(vertical,horizontal,flow),canvasBehaviour 属性需要是 CONTAINER

  • documentationURL - UI 组件文档的 URL。界面设计器的 CUBA Documentation 操作会用到该属性。如果文档路径是有版本的,则可以用 %VERSION% 作为占位符。平台会使用包含 UI 组件的库的 小版本(比如 1.2) 替换。

Facet 定义

com.haulmont.cuba.gui.meta.StudioFacet 标注 facet 接口,即表明该 facet 可用于界面设计器。

需要在界面设计器支持自定义 facet,还需要在项目实现关联的 FacetProvider。

FacetProvider 是实现了 com.haulmont.cuba.gui.xml.FacetProvider 接口的 Spring bean,用 facet 类作为参数。参考平台的 com.haulmont.cuba.web.gui.facets.ClipboardTriggerFacetProvider 作为示例。

@StudioFacet 注解的属性与上面介绍的 @StudioComponent 类似。

示例:

@StudioFacet(
        xmlElement = "clipboardTrigger",
        category = "Facets",
        icon = "icon/clipboardTrigger.svg",
        documentationURL = "https://doc.cuba-platform.com/manual-%VERSION%/gui_ClipboardTrigger.html"
)
public interface ClipboardTrigger extends Facet {

    @StudioProperty(type = PropertyType.COMPONENT_REF, options = "com.haulmont.cuba.gui.components.TextInputField")
    void setInput(TextInputField<?> input);

    @StudioProperty(type = PropertyType.COMPONENT_REF, options = "com.haulmont.cuba.gui.components.Button")
    void setButton(Button button);

    // ...
}
标准组件属性

组件属性通过使用两个注解声明:

  • @StudioProperty - 表示注解的方法(setter,setXxx)需要在 Inspector 面板作为组件属性显示。

  • @StudioProperties - 用在接口上,定义与 setter 方法不相关的其他组件属性和属性分组。

示例:

@StudioComponent(caption = "RichTextArea")
@StudioProperties(properties = {
        @StudioProperty(name = "css", type = PropertyType.CSS_BLOCK)
})
interface RichTextArea extends Component {
    @StudioProperty(type = PropertyType.CSS_CLASSNAME_LIST)
    void setStylename(String stylename);

    @StudioProperty(type = PropertyType.SIZE, defaultValue = "auto")
    void setWidth(String width);

    @StudioProperty(type = PropertyType.SIZE, defaultValue = "auto")
    void setHeight(String height);

    @StudioProperty(type = PropertyType.LOCALIZED_STRING)
    void setContextHelpText(String contextHelpText);

    @StudioProperty(type = PropertyType.LOCALIZED_STRING)
    void setDescription(String description);

    @StudioProperty
    @Min(-1)
    void setTabIndex(int tabIndex);
}

setter 方法可以用不带任何额外数据的 @StudioProperty 注解。此时:

  • 属性名和显示名称会从 setter 方法名生成。

  • 属性类型从方法的参数类型获取。

@StudioProperty 注解有如下属性:

  • name - 属性名

  • type - 定义该属性中存储的什么内容,比如可以是字符串,实体名或在同一界面中其他组件的引用。支持的属性类型在 下面列出

  • caption - 显示名,展示在 Inspector 面板。

  • description - 附加描述,作为 Inspector 面板中鼠标的 tooltip 展示。

  • category - Inspector 面板中属性的分类。(目前在 Studio 14.0 还未实现此功能)

  • required - 属性是必须的,此时界面设计器不允许用空值声明该属性。

  • defaultValue - 当 XML 中省略此属性时,组件会用该默认值。默认值不会在 XML 代码显示。

  • options - 依赖上下文的组件属性列表:

    • 对于 ENUMERATION 属性类型 - 枚举选项

    • 对于 BEAN_REF 属性类型 - Spring bean 基类列表

    • 对于 COMPONENT_REF 属性类型 - 组件基类列表

    • 对于 PROPERTY_PATH_REF 属性类型 - 实体属性类型列表。对 datatype 属性需要使用已注册的 Datatype 名称;对于关联属性则使用 to_oneto_many

  • xmlAttribute - 目标 XML 属性名称,如果未设置,则与属性名相同。

  • xmlElement - 目标 XML 元素名称。用该属性可以将组件属性映射为主组件 XML 标签的子标签,参阅这里

  • typeParameter - 指定泛型参数的名称,泛型参数由该属性为组件提供。参考 下面 详细介绍。

组件属性类型

支持下列属性类型(com.haulmont.cuba.gui.meta.PropertyType):

  • INTEGERLONGFLOATDOUBLESTRINGBOOLEANCHARACTER - 基本类型。

  • DATE - YYYY-MM-DD 格式的日期类型。

  • DATE_TIME - YYYY-MM-DD hh:mm:ss 格式的时间日期类型。

  • TIME - hh:mm:ss 格式的时间类型。

  • ENUMERATION - 枚举值。枚举选项列表由 options 注解属性提供。

  • COMPONENT_ID - 组件、子组件或操作的 标识符。必须是有效的 Java 标识符。

  • ICON_ID - CUBA 或项目提供的 图标路径或图标 ID

  • SIZE - 大小值,即宽高。

  • LOCALIZED_STRING - 本地化消息,用字符串表示或用 msg://mainMsg:// 前缀的消息键值表示。

  • JPA_QUERY - JPA QL 字符串。

  • ENTITY_NAME - 实体名(通过 javax.persistence.Entity#name 注解属性指定)

  • ENTITY_CLASS - 项目中定义的实体全路径名称。

  • JAVA_CLASS_NAME - Java 类的全路径名称。

  • CSS_CLASSNAME_LIST - 用空格分隔的 CSS 类名

  • CSS_BLOCK - 行内 CSS 属性。

  • BEAN_REF - 项目中定义的 Spring bean ID。允许使用的 Spring bean 基类通过 options 注解属性提供。

  • COMPONENT_REF - 界面中定义的组件 ID。允许使用的组件基类通过 options 注解属性提供。

  • DATASOURCE_REF - 界面中定义的 数据源 ID(legacy API)。

  • COLLECTION_DATASOURCE_REF - 界面定义的 集合数据源 ID(legacy API)。

  • DATALOADER_REF - 界面中定义的 数据加载器 ID。

  • DATACONTAINER_REF - 界面中定义的 数据容器 ID。

  • COLLECTION_DATACONTAINER_REF - 界面中定义的 集合数据容器 ID。

  • PROPERTY_REF - 实体属性名称。允许使用的实体属性类型通过 options 注解属性提供。如需显示该字段提示,此组件属性需要跟另外一个定义了数据容器或数据源的其他组件属性关联,通过 属性组 实现。

  • PROPERTY_PATH_REF - 实体属性名,或者实体关系图的属性路径,比如 user.group.name。允许使用的实体属性类型通过 options 注解属性提供。如需显示该字段提示,此组件属性需要跟另外一个定义了数据容器或数据源的其他组件属性关联,通过 属性组 实现。

  • DATATYPE_ID - Datatype ID,比如 stringdecimal

  • SHORTCUT - 快捷键,比如 CTRL-SHIFT-U

  • SCREEN_CLASS - 项目中定义的 界面控制器 类全路径名。

  • SCREEN_ID - 项目中定义的界面 ID。

  • SCREEN_OPEN_MODE - 界面 打开模式

组件属性验证

Inspector 面板支持有限的一些组件属性验证,使用 BeanValidation 注解:

  • @Min, @Max, @DecimalMin, @DecimalMax.

  • @Negative, @Positive, @PosizitiveOrZero, @NegativeOrZero.

  • @NotBlank, @NotEmpty.

  • @Digits.

  • @Pattern.

  • @Size, @Length.

  • @URL.

示例:

@StudioProperty(type = PropertyType.INTEGER)
@Positive
void setStepAmount(int amount);
int getStepAmount();

如果用户尝试输入无效属性值,会显示如下错误:

bean validation
@StudioProperties 和属性分组

@StudioProperty 定义的元数据可以用组件接口上的 @StudioProperties 注解覆盖。

@StudioProperties 注解在 groups 属性内可以有 0 个或多个 @PropertiesGroup 类型声明。每个分组定义一个属性组,类型通过 @PropertiesGroup#constraint 属性确定:

  • ONE_OF - 分组的特殊属性,表示组内的属性是互斥的。

  • ALL_OR_NOTHING - 一组互相依赖的属性,只能一起使用。

属性分组的一个特别重要的应用场景就是组件中能绑定数据容器的 dataContainerproperty 两个属性。这两个属性必须包含在 ALL_OR_NOTHING 分组中。参考下面包含这种属性分组的示例:

@StudioComponent(
        caption = "RichTextField",
        category = "Fields",
        canvasBehaviour = CanvasBehaviour.INPUT_FIELD)
@StudioProperties(properties = {
        @StudioProperty(name = "dataContainer", type = PropertyType.DATACONTAINER_REF),
        @StudioProperty(name = "property", type = PropertyType.PROPERTY_PATH_REF, options = "string")
}, groups = @PropertiesGroup(
        properties = {"dataContainer", "property"}, constraint = PropertiesConstraint.ALL_OR_NOTHING
))
interface RichTextField extends Component {
    // ...
}
@StudioCollection 声明子元素元数据

组合组件,比如 tablepickerField 或 charts 在界面描述中是用几个嵌套的 XML 标签定义。子标签也是组件的一部分,通过父组件的 ComponentLoader 加载至界面。声明子元素的元数据,有下面两种方式:

  • 使用 @StudioCollection - 在组件接口直接指定子元素的元数据。

  • 使用 @StudioElementGroup@StudioElement - 子元素的元数据在单独表示 XML 子标签的类指定。

com.haulmont.cuba.gui.meta.StudioCollection 注解有如下属性:

  • xmlElement - 集合的 XML 标签

  • itemXmlElement - 集合中元素的 XML 标签

  • documentationURL - 子元素文档的 URL。界面设计器的 CUBA Documentation 操作会用到该属性。如果文档路径是有版本的,则可以用 %VERSION% 作为占位符。平台会使用包含 UI 组件的库的 小版本(比如 1.2) 替换。

  • itemProperties - 一组 @StudioProperty 注解,定义集合元素的属性。

以下是示例。

这是在界面描述中需要的 XML 结构:

<layout>
    <langPicker>
        <options>
            <option caption="msg://lang.english" code="en" flagIcon="icons/english.png"/>
            <option caption="msg://lang.french" code="fr" flagIcon="icons/french.png"/>
        </options>
    </langPicker>
</layout>

带有 @StudioCollection 的组件类:

@StudioComponent(xmlElement = "langPicker", category = "Samples")
public interface LanguagePicker extends Field<Locale> {

    @StudioCollection(xmlElement = "options", itemXmlElement = "option",
            itemProperties = {
                    @StudioProperty(name = "caption", type = PropertyType.LOCALIZED_STRING, required = true),
                    @StudioProperty(name = "code", type = PropertyType.STRING),
                    @StudioProperty(name = "flagIcon", type = PropertyType.ICON_ID)
            })
    void setOptions(List<LanguageOption> options);
    List<LanguageOption> getOptions();
}

在父组件的 Inspector 面板还会额外显示 Add{element caption} 按钮,可以添加子元素:

collection owner inspector

如果在布局中选中子元素,Inspector 面板会显示子元素指定在 StudioCollection 注解中的属性:

collection element inspector
@StudioElementGroup 和 @StudioElement 声明子元素元数据

@StudioElementGroup 用来标记组件接口中的 setter 方法。这样就告诉 Studio,需要在引用的类中查找子元素的元数据。

@StudioElementsGroup(xmlElement = "subElementGroupTagName")
void setSubElements(List<ComponentSubElement> subElements);

@StudioElementGroup 注解有如下属性:

  • xmlElement - 子元素分组的 XML 标签名

  • icon - 用在工具箱和界面结构中的子元素分组的图标路径,SVG 或 PNG 格式,路径为以组件模块根路径为起点的相对路径。

  • documentationURL - 子元素分组文档的 URL。界面设计器的 CUBA Documentation 操作会用到该属性。如果文档路径是有版本的,则可以用 %VERSION% 作为占位符。平台会使用包含 UI 组件的库的 小版本(比如 1.2) 替换。

@StudioElement 用来标记表示组件子元素的类。XML 标签可用的属性通过 @StudioProperty@StudioProperties 属性声明。

@StudioElement(xmlElement = "subElement", caption = "Sub Element")
public interface SubElement {
    @StudioProperty
    void setElementProperty(String elementProperty);
    // ...
}

@StudioElement 注解的属性与 @StudioComponent 类似:

  • xmlElement - 子元素的 XML 标签名。

  • caption - 显示在 Inspector 面板的子元素名称。

  • description - 显示在 Inspector 面板的组件描述,鼠标浮上时,作为 tooltip 展示。

  • icon - 用在工具箱和界面结构中的子元素图标路径,SVG 或 PNG 格式,路径为以组件模块根路径为起点的相对路径。

  • xmlns - XML 命名空间。当子元素添加至界面,Studio 会自动在界面描述中添加对命名空间的引入。

  • xmlnsAlias - XML 命名空间别名。比如,如果命名空间别名是 map 且 XML 标签名是 layer,则组件会作为 <map:layer/> 标签添加至界面。

  • defaultProperty - 子元素默认属性名称,当在布局选中子元素时,这些默认属性会自动在 Inspector 面板选中。

  • unsupportedProperties - 从子元素父接口继承下来的属性,但是本元素并不支持。这些属性会在 Inspector 面板隐藏。

  • documentationURL - 子元素文档的 URL。界面设计器的 CUBA Documentation 操作会用到该属性。如果文档路径是有版本的,则可以用 %VERSION% 作为占位符。平台会使用包含 UI 组件的库的 小版本(比如 1.2) 替换。

请看下面的示例。

这是在界面描述中需要的 XML 结构:

<layout>
    <serialChart backgroundColor="#ffffff" caption="Weekly Stats">
        <graphs>
            <graph colorProperty="color" valueProperty="price"/>
            <graph colorProperty="costColor" valueProperty="cost"/>
        </graphs>
    </serialChart>
</layout>

带有 @StudioElementsGroup 注解的组件类:

@StudioComponent(xmlElement = "serialChart", category = "Samples")
public interface SerialChart extends Component {

    @StudioProperty
    void setCaption(String caption);
    String getCaption();

    @StudioProperty(type = PropertyType.STRING)
    void setBackgroundColor(Color backgroundColor);
    Color getBackgroundColor();

    @StudioElementsGroup(xmlElement = "graphs")
    void setGraphs(List<ChartGraph> graphs);
    List<ChartGraph> getGraphs();
}

@StudioElement 声明的子元素:

@StudioElement(xmlElement = "graph", caption = "Graph")
public interface ChartGraph {

    @StudioProperty(type = PropertyType.PROPERTY_PATH_REF)
    void setValueProperty(String valueProperty);
    String getValueProperty();

    @StudioProperty(type = PropertyType.PROPERTY_PATH_REF)
    void setColorProperty(String colorProperty);
    String getColorProperty();
}

在父组件的 Inspector 面板还会额外显示 Add{element caption} 按钮,可以添加子元素:

element group owner inspector

如果在布局中选中子元素,Inspector 面板会显示子元素声明时指定的属性:

element group element inspector
声明子标签属性

还可以声明某些组件属性不在主标签定义,而是在主标签内的某个子标签定义。请看下面例子的 XML 布局:

<myChart>
    <scrollBar color="white" position="TOP"/>
</myChart>

这里,scrollBarmyChart 组件的一部分,并不是一个独立组件,因此我们希望在主组件接口中声明属性。

定义子标签属性的元数据注解可以在主组件声明,使用 @StudioProperty 注解的 xmlElement 属性即可。该属性定义子标签的名称。带注解的组件定义如下:

@StudioComponent(xmlElement = "myChart", category = "Samples")
public interface MyChart extends Component {

    @StudioProperty(name = "position", caption = "scrollbar position",
            xmlElement = "scrollBar", xmlAttribute = "position",
            type = PropertyType.ENUMERATION, options = {"TOP", "BOTTOM"})
    void setScrollBarPosition(String scrollBarPosition);
    String getScrollBarPosition();

    @StudioProperty(name = "color", caption = "scrollbar color",
            xmlElement = "scrollBar", xmlAttribute = "color")
    void setScrollBarColor(String scrollBarColor);
    String getScrollBarColor();
}
@StudioEmbedded 声明标签属性并抽取至 POJO

有些情况下,您可能希望抽取一组组件的属性至一个单独的 POJO。但同时,在 XML schema 中,这些抽取出来的属性仍然作为主 XML 标签的属性使用。此时,可以用 @StudioEmbedded 注解满足这个要求。需要将接收这个 POJO 对象的 setter 标注为 @StudioEmbedded,意思是这里的 POJO 包含了额外的组件属性。

com.haulmont.cuba.gui.meta.StudioEmbedded 注解无属性。

下面是一个用例:

需要的 XML 结构,注意,所有的属性都是给主组件标签设置的:

<layout>
    <richTextField textColor="blue" editable="false" id="rtf"/>
</layout>

带有注解属性的 POJO 类:

public class FormattingOptions {
    private String textColor = "black";
    private boolean foldComments = true;

    @StudioProperty(defaultValue = "black", description = "Main text color")
    public void setTextColor(String textColor) {
        this.textColor = textColor;
    }

    @StudioProperty(defaultValue = "true")
    public void setFoldComments(boolean foldComments) {
        this.foldComments = foldComments;
    }
}

组件接口:

@StudioComponent(category = "Samples",
        unsupportedProperties = {"icon", "responsive"},
        description = "Text field with html support")
public interface RichTextField extends Field<String> {

    @StudioEmbedded
    void setFormattingOptions(FormattingOptions formattingOptions);
    FormattingOptions getFormattingOptions(FormattingOptions formattingOptions);
}

在组件的 Inspector 面板,这些属性与其他属性一起显示:

embedded inspector
事件和代理方法支持

Studio 为自定义 UI 组件(或 Facet)提供了与自带 UI 组件相同的事件和代理方法支持。在组件接口中声明事件监听器或代理方法时,不需要任何注解。

下面是声明事件处理器和它自己事件类的组件示例:

@StudioComponent(category = "Samples")
public interface LazyTreeTable extends Component {

    // ...

    Subscription addNodeExpandListener(Consumer<NodeExpandEvent> listener);

    class NodeExpandEvent extends EventObject {
        private final Object nodeId;

        public NodeExpandEvent(LazyTreeTable source, Object nodeId) {
            super(source);
            this.nodeId = nodeId;
        }

        public Object getNodeId() {
            return nodeId;
        }
    }
}

在 Inspector 面板,可以看到声明的事件处理器:

event handler in inspector

Studio 生成的事件处理器实现的桩代码如下:

@UiController("demo_Dashboard")
@UiDescriptor("dashboard.xml")
public class Dashboard extends Screen {
    // ...

    @Subscribe("regionTable")
    public void onRegionTableNodeExpand(LazyTreeTable.NodeExpandEvent event) {

    }
}

下面的例子演示了在支持泛型的 facet 中如何声明代理方法:

@StudioFacet
public interface LookupScreenFacet<E extends Entity> extends Facet {

    // ...

    void setSelectHandler(Consumer<Collection<E>> selectHandler);

    void setOptionsProvider(Supplier<ScreenOptions> optionsProvider);

    void setTransformation(Function<Collection<E>, Collection<E>> transformation);

    @StudioProperty(type = PropertyType.COMPONENT_REF, typeParameter = "E",
            options = "com.haulmont.cuba.gui.components.ListComponent")
    void setListComponent(ListComponent<E> listComponent);
}
泛型参数支持

Studio 支持使用泛型参数定义的组件。参数可以是实体类,界面类或者其他的 Java 类。类型参数会在组件注入到界面控制器和生成代理方法的桩代码时使用。

Studio 的类型推断,在特定组件内是通过检查 XML 中组件的属性配置完成的。组件属性可以直接或间接的指定泛型类型。比如,table 组件展示实体列表,所以泛型参数是实体类。为了推断具体的实体类型,Studio 会检查 dataContainer 属性,查看集合数据容器的定义。如果集合数据容器的所有属性都有赋值,那么集合数据容器用到的实体类会被推断为表格使用的泛型参数。

@StudioProperty 注解的 typeParameter 参数指定泛型 UI 组件或 facet 的类型参数。类型参数可以为下面这些属性类型推断具体具体类:

  • PropertyType.JAVA_CLASS_NAME - 使用指定类。

  • PropertyType.ENTITY_CLASS - 使用指定的实体类。

  • PropertyType.SCREEN_CLASS_NAME - 使用指定的界面类。

  • PropertyType.DATACONTAINER_REF, PropertyType.COLLECTION_DATACONTAINER_REF - 使用指定数据容器中用到的实体类。

  • PropertyType.DATASOURCE_REF, PropertyType.COLLECTION_DATASOURCE_REF - 使用指定数据源中用到的实体类。

  • PropertyType.COMPONENT_REF - 使用指定组件绑定的实体类(实体类通过绑定的数据容器或者数据源确定)

下面是用例。

组件接口代码,UI 组件用来展示通过集合容器提供的一组实体:

@StudioComponent(category = "Samples")
public interface MyTable<E extends Entity> extends Component { (1)

    @StudioProperty(type = PropertyType.COLLECTION_DATACONTAINER_REF,
            typeParameter = "E") (2)
    void setContainer(CollectionContainer<E> container);

    void setStyleProvider(@Nullable Function<? super E, String> styleProvider); (3)
}
1 - 组件接口使用 E 作为参数,E 表示展示在表格内的实体类。
2 - 在 COLLECTION_DATACONTAINER_REF 类型的属性上指定 typeParameter 这个注解属性,我们可以告诉 Studio 通过查看关联的集合容器推断具体实体类型。
3 - 泛型参数也用在了组件的代理方法上。

为了让 Studio 能自动推断组件的类型参数,此组件需要在界面描述中关联集合数据容器:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd"
        caption="msg://dashboard.caption"
        messagesPack="com.company.demo.web.screens">
    <data>
        <collection id="regionsDc" class="com.company.demo.entity.Region">
            <!-- ... -->
        </collection>
    </data>
    <layout>
        <myTable id="regionTable" container="regionsDc"/>
    </layout>
</window>

Studio 会在界面控制器生成如下代码:

@UiController("demo_Dashboard")
@UiDescriptor("dashboard.xml")
public class Dashboard extends Screen {
    @Inject
    private MyTable<Region> regionTable; (1)

    @Install(to = "regionTable", subject = "styleProvider")
    private String regionTableStyleProvider(Region region) { (2)
        return "bold-text";
    }
}
1 - 控制器中注入了正确类型参数的组件。
2 - 代理方法的签名也是用了正确的类型参数。