Skip to content

Instantly share code, notes, and snippets.

@SongFuZhen
Last active October 26, 2020 12:17
Show Gist options
  • Save SongFuZhen/928f0baa00212a13163134879614931e to your computer and use it in GitHub Desktop.
Save SongFuZhen/928f0baa00212a13163134879614931e to your computer and use it in GitHub Desktop.
主要记录一下在使用 `CUBA Platform` 过程中,遇到的一些 `GUI` **问题**以及**解决方案**,以便后查

目录 table of contens

界面和界面片段 Fragments

  • Screen 的打开与传值
  • Fragments的打开与传值
  • ScreenFragments 的父子、子子、子父之间的传值

可视化组件库

可视化组件

  • 一些组件的使用
  • Table 使用自定义容器
  • DataGrid 使用 KV 属性容器

布局容器

  • 一些布局

数据组件

  • 数据容器的创建和使用
  • KV属性容器的创建和使用
  • 数据加载器的使用

非可视化组件

  • 使用 Timer 定时触发事件

操作

  • Actions 的定义和使用

对话框消息

  • Dialog 的使用

通知消息

  • Notification 的使用

后台任务

暂无

主题

  • 自定义主题

图标

  • 使用其他字体库中的图标

可视化组件的 DOMCSS 属性

暂无

Screens 的打开和传值 Screen's open and get/set params

打开 Screen 共有两种方式可以实现,用 screens 接口 或者 ScreenBuilders Bean,可以参考官方介绍 https://doc.cuba-platform.cn/manual-7.2-chs/opening_screens.html

我更倾向于使用后者多一些,主要的一个原因就是比较灵活一些,提供的方法比较多。

这两者区别的一点是:screens 不能选择打开方式,而 screenBuilders 可以

可以选择打开方式 NEW_TAB, THIS_TAB, DIALOG, NEW_WINDOW, ROOT

一些准备工作

创建要打开界面的 Screen,命名为 child-screen

主要有以下文件被创建:

// Controller
ChildScreen.java

// 界面
child-screen.xml

// 国际化等翻译文件
messages.properties
messages_zh_CN.properties

代码片段

// Controller.java

package com.ason.testing.web.screens.test_screen;

import com.haulmont.cuba.gui.components.Label;
import com.haulmont.cuba.gui.screen.Screen;
import com.haulmont.cuba.gui.screen.UiController;
import com.haulmont.cuba.gui.screen.UiDescriptor;

import javax.inject.Inject;

@UiController("testing_ChildScreen")
@UiDescriptor("child-screen.xml")
public class ChildScreen extends Screen {

    @Inject
    private Label<String> label_message; // 界面组件

    public void setMessage(String message) {
        label_message.setValue(message); // 界面组件设置值
    }
}

// child-screen.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd"
        caption="msg://caption"
        messagesPack="com.ason.testing.web.screens.test_screen">
    <layout>
        <vbox spacing="true">
            <hbox spacing="true">
                <label id="label_child" value="msg://label_child"/>
                <label id="label_message"/> <!--显示 message 信息-->
            </hbox>
            <button id="btn_close" caption="msg://btn_close"/>
        </vbox>
    </layout>
</window>

screens 接口打开界面操作

 @Subscribe("btn_open_child")
 public void onBtn_open_childClick(Button.ClickEvent event) {
     ChildScreen childScreen = screens.create(ChildScreen.class);
     childScreen.setMessage("Hello World! I am created by Screens");
     childScreen.addAfterCloseListener(e -> {
     		// TODO: after close child screen
     });
		screens.show(childScreen);
 }

优缺点如下:

  • 简单明了
  • 能传递参数给子界面
  • 可以监听界面关闭等事件
  • 不能选择打开方式,如果要用弹框方式打开的话,只能在 ChildScree.java 上面添加 @DialogMode(forceDialog = true, width="80%", height="80%", resizable=true)

screenBuilders 打开界面

@Subscribe("btn_open_child")
public void onBtn_open_childClick(Button.ClickEvent event) {
  ChildScreen childScreen = screenBuilders.screen(this)
    .withScreenClass(ChildScreen.class)
    .withOpenMode(OpenMode.DIALOG) // 可以选择打开方式 NEW_TAB, THIS_TAB, DIALOG, NEW_WINDOW, ROOT
    .withAfterCloseListener(e -> {
    // TODO: after close child screen
    })
    .build();

  childScreen.setMessage("Hello World! I am created by ScreenBuilders");
	childScreen.show();
}

优缺点如下:

  • 写起来比较复杂,但好在能链式
  • 能传递参数给子界面
  • 可以监听界面关闭等事件
  • 能选择打开方式,不一定要在 ChildScreen.java 中定义,更加灵活

以上

Fragments 的加载和传值

加载 Fragments 同样有两种方式,一种是直接在 xml 文件中写,一种是纯java代码创建

我比较推荐的是在 xml 中写,在界面文件中比较直观,好管理

一些准备工作

创建 user-fragment

有以下文件被创建:

// Controller
userFragment.java

// fragment 界面
user-fragment.xml

// 国际化等翻译文件
messages.properties
messages_zh_CN.properties

创建 fragment-collection 界面

有以下文件被创建:

// Controller
FragmentCollectionScreen.java

// fragment 界面
fragment-collection-screen.xml

// 国际化等翻译文件
messages.properties
messages_zh_CN.properties

代码片段

UserFragment 代码

// UserFragment.java
package com.ason.testing.web.screens.test_fragment.user;

import com.haulmont.cuba.gui.model.InstanceLoader;
import com.haulmont.cuba.gui.screen.*;
import com.haulmont.cuba.security.entity.User;

import javax.inject.Inject;
import java.util.UUID;

@UiController("testing_UserFragment")
@UiDescriptor("user-fragment.xml")
public class UserFragment extends ScreenFragment {

    //此处定义为 public 是因为要在 Java 代码中直接使用,如果使用方式一,可以定义为 private
    @Inject
    public InstanceLoader<User> userDl; 

    private UUID userId;

    // 获取
    public void setUserId(UUID userId) {
        this.userId = userId;
    }

    private String fromWhere;

    public void setFromWhere(String fromWhere) {
        this.fromWhere = fromWhere;
    }

    @Subscribe(target = Target.PARENT_CONTROLLER)
    public void onAfterShow(Screen.AfterShowEvent event) {
        System.out.println(fromWhere);

        userDl.setParameter("userId", userId);
        userDl.load();
    }
}
<!-- user-fragment.xml -->
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<fragment xmlns="http://schemas.haulmont.com/cuba/screen/fragment.xsd">
    <data>
        <instance id="userDc"
                  class="com.haulmont.cuba.security.entity.User"
                  view="user.browse">
            <loader id="userDl">
                <query>
                    <![CDATA[select e from sec$user where e.id = :userId]]>
                </query>
            </loader>
        </instance>
    </data>
    <layout>
        <groupBox spacing="true" collapsable="true" caption="msg://group_user">
            <hbox width="100%" spacing="true">
                <hbox spacing="true">
                    <label value="msg://label_user_name"/>
                    <label id="value_user_name" dataContainer="userDc" property="name"/>
                </hbox>
                <hbox spacing="true" align="TOP_RIGHT">
                    <label value="msg://label_email"/>
                    <label id="value_email" dataContainer="userDc" property="email"/>
                </hbox>
            </hbox>
        </groupBox>
    </layout>
</fragment>

FragmentCollectionScreen 代码

package com.ason.testing.web.screens.test_fragment;

import com.ason.testing.web.screens.test_fragment.user.UserFragment;
import com.haulmont.cuba.gui.screen.Screen;
import com.haulmont.cuba.gui.screen.Subscribe;
import com.haulmont.cuba.gui.screen.UiController;
import com.haulmont.cuba.gui.screen.UiDescriptor;
import com.haulmont.cuba.security.global.UserSession;

import javax.inject.Inject;

@UiController("testing_FragmentCollectionScreen")
@UiDescriptor("fragment-collection-screen.xml")
public class FragmentCollectionScreen extends Screen {
    @Inject
    private UserSession userSession;
    @Inject
    private UserFragment fragment_user;

    @Subscribe
    public void onAfterShow(AfterShowEvent event) {
        // fragment_user.setUserId(userSession.getId()); // 使用 Java 代码传值
        
        // 使用 Java 代码创建
        UserFragment userFragment = fragments.create(this, UserFragment.class);
        userFragment.getFragment().setId("main_with_java");
        userFragment.setUserId(userSession.getUser().getId());
        userFragment.setFromWhere("main_with_java");
        userFragment.userDl.setParameter("userId", userSession.getUser().getId());
        userFragment.userDl.load();
        vbox.add(userFragment.getFragment());
    }
}
<!-- fragment-collection-screen.xml -->
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd"
        caption="msg://caption"
        messagesPack="com.ason.testing.web.screens.test_fragment">
    <layout>
        <vbox id="vbox">
            <fragment id="fragment_user" screen="testing_UserFragment">
                <properties>
                    <property name="fromWhere" value="main" />
                </properties>
            </fragment>
        </vbox>
    </layout>
</window>

方式一:直接将 Fragment 添加到主界面到 xml

如上代码所示,直接在 fragment-collection-screen.xml 里面添加 fragment 组件即可

传值方式有两种,一种是直接在 properies 中 定义 property,传递静态值。

Tips:此处需要注意的一点是:fromWhere(或 xx 属性) 必须要有相对应的 setFromWhere 方法在 Fragement 中,否则会报错

<fragment id="fragment_user" screen="testing_UserFragment">
    <properties>
        <property name="fromWhere" value="main" />
    </properties>
</fragment>

另一种也可以在 FragmentCollectionScreen.java 中,直接使用 Java 代码传动态值

@Inject
private UserSession userSession;
    
@Inject
private UserFragment fragment_user;
    
fragment_user.setUserId(userSession.getId()); // 传值

优缺点:

  • 比较繁琐一些,没有那么直观
  • 主界面上看起来比较直观
  • 更好的调整位置等
  • 静态值可以直接在 xml 中传递

方式二: 使用 Java 代码创建一个 Fragment ,然后添加到界面上

// 使用 Java 代码创建
UserFragment userFragment = fragments.create(this, UserFragment.class);
userFragment.getFragment().setId("main_with_java");
userFragment.setUserId(userSession.getUser().getId());
userFragment.setFromWhere("main_with_java");
// 直接加载数据,否则不会显示内容
userFragment.userDl.setParameter("userId", userSession.getUser().getId());
userFragment.userDl.load();
vbox.add(userFragment.getFragment());

优缺点:

  • 代码简介明了
  • 不会在主界面显示,位置等不好调节
  • 必须定义一个容器,用来装 Fragment

以上

Fragment 和 Screen 之间互相刷新 fragment and screen refresh each other

【父子】ScreenFragment 传值

有两种方式,不过要看 Fragment 是怎么加载的

方式一: 在xml 中直接传递静态变量

 <fragment id="fragment_user" screen="testing_UserFragment">
    <properties>
        <property name="fromWhere" value="main" />
    </properties>
</fragment>

方式二: 在 Java 代码中传递动态变量

  fragment_user.setUserId(userSession.getUser().getId()); // 传值

或者

// 使用 Java 代码创建
UserFragment userFragment = fragments.create(this, UserFragment.class);
userFragment.getFragment().setId("main_with_java");
userFragment.setUserId(userSession.getUser().getId()); // 传递变量
userFragment.setFromWhere("main_with_java");
...

【子父】Fragment 调用 Host Screen 的方法或者传值

使用 getHostScreen 或者 getHostController() 来访问父级界面,然后通过定义在父级界面中的 public 变量来访问

FragmentCollectionScreen hostScreen = (FragmentCollectionScreen)getHostScreen();
hostScreen.setCaption("Set by UserFragment");

【子子】Fragment 之间互相传值或者调用方法

使用 getHostScreen 或者 getHostController() 来访问父级界面,然后通过定义在父级界面中的 public 变量来访问 fragment

在主界面定义 fragmentpublic

@Inject
public UserFragment fragment_user;

other fragment 中刷新 UserFragment

FragmentCollectionScreen hostScreen = (FragmentCollectionScreen)getHostScreen();
hostScreen.fragment_user.userDl.setParameter("userId", userSession.getUser().getId());
hostScreen.fragment_user.userDl.load();

TabSheet 使用中遇到的一些问题

在程序开发过程中,经常会遇到多页签的情况,这里简单记录一下

TabCaption 不能动态改变

Tips: 不能直接使用 Tab.setCaption 改变 Caption,使用 TabSheet.getTab("tab").setCaption

@Named("tabsheet.tab_active")
private VBoxLayout tab_active;

@Named("tabsheet.tab_unactive")
private VBoxLayout tab_unactive;

@Inject
private TabSheet tabsheet;

@Subscribe
public void onAfterShow(AfterShowEvent event) {
  // 这样改没有用
  // tab_active.setCaption("Active_Change_by_Tab");
  // tab_unactive.setCaption("Unactive_Change_by_Tab");

  tabsheet.getTab("tab_active").setCaption("Active_Change_by_TabSheet");
  tabsheet.getTab("tab_unactive").setCaption("Unactive_Change_by_TabSheet");
}

键值对容器显示在表格 KeyValueCollection show in Table

在开发时,会遇到没有实体或者实体属性不定或者需要动态更改属性的时候,普通的集合容器(Collection)已经没法满足,这时候就可以使用键值对容器(KeyValueCollection)。

这里介绍一种完全动态 KeyValueCollection 显示在 Table 里的方式。

一些准备工作

业务需求

上传完文件之后需要先预览一下数据信息,确认无误之后,再提交数据库验证

接下来要做的是使用后端返回的 List<T> 数据,进行 Table 的渲染

关键技术点

主要思路:动态生成表格,然后添加到界面上进行显示

使用 ValueGroupDatasourceImpl 存储 KeyValueCollection 数据,然后设置到表格上

Step1:处理数据 使用 addTable 方法,将后端传递过来到数据,转换成 KeyValueCollection 形式,用来在 Table 里面显示

Step2:生成表格 使用 componentsFactory,动态创建 Table,然后将数据设置到 Table 里面。

创建界面 UploadReviewScreen

主要有以下文件被创建:

// Controller
UploadReviewScreen.java

// 界面
upload-review-screen.xml

// 国际化等翻译文件
messages.properties
messages_zh_CN.properties

代码片段

// UploadReviewScreen.java
package com.ason.testing.web.screens.kvCollection;

import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.chile.core.model.MetaPropertyPath;
import com.haulmont.cuba.core.entity.KeyValueEntity;
import com.haulmont.cuba.core.global.Messages;
import com.haulmont.cuba.gui.components.Table;
import com.haulmont.cuba.gui.components.VBoxLayout;
import com.haulmont.cuba.gui.data.DsBuilder;
import com.haulmont.cuba.gui.data.impl.ValueGroupDatasourceImpl;
import com.haulmont.cuba.gui.screen.Screen;
import com.haulmont.cuba.gui.screen.Subscribe;
import com.haulmont.cuba.gui.screen.UiController;
import com.haulmont.cuba.gui.screen.UiDescriptor;
import com.haulmont.cuba.gui.xml.layout.ComponentsFactory;

import javax.inject.Inject;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

@UiController("testing_UploadReviewScreen")
@UiDescriptor("upload-review-screen.xml")
public class UploadReviewScreen extends Screen {
    final static String packPath = "com.ason.testing.web.screens.kvCollection";

    @Inject
    private ComponentsFactory componentsFactory;

    @Inject
    private Messages messages;

    @Inject
    private VBoxLayout vbox;

    private final static List<String> props = new ArrayList<>(
            Arrays.asList("id", "name", "email", "address", "birthday", "errorMsg")
    );

    private final static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");

    // 测试数据, 后端返回的
    private List<ImportUser> fakeUserList = new ArrayList<>();

    @Subscribe
    public void onAfterInit(AfterInitEvent event) {
        fakeUserList = generateUserList(10);

        addTable(fakeUserList, vbox, props);
    }

    /**
     * 动态添加数据表格
     *
     * @param items      返回的数据项
     * @param vBox       添加的位置
     * @param allProps   属性
     * @param <GenericT> 类型
     */
    private <GenericT> void addTable(List<GenericT> items, VBoxLayout vBox, List<String> allProps) {
        ValueGroupDatasourceImpl ds = DsBuilder.create().buildValuesGroupDatasource();
        for (String prop : allProps) {
            ds.addProperty(prop);
        }

        for (GenericT item : items) {
            KeyValueEntity kvEntity = new KeyValueEntity();

            Field[] fields = ImportUser.class.getDeclaredFields();

            for (int i = 0; i < fields.length; i++) {
                Field field = fields[i];
                field.setAccessible(true);

                try {
                    Object val = field.get(item);
                    if (val instanceof Date) {
                        kvEntity.setValue(field.getName(), formatter.format(val));
                    } else {
                        kvEntity.setValue(field.getName(), val != null ? val.toString() : null); // 防止 toString 报错
                    }
                } catch (IllegalAccessException e) {
                    System.out.println(e);
                }
            }

            ds.addItem(kvEntity);
        }

        generateTable(ds, vBox, allProps);
    }


    /**
     * 绘制表格
     *
     * @param ds       数据源
     * @param vBox     要添加在哪个box
     * @param allProps 所有属性
     */
    private void generateTable(ValueGroupDatasourceImpl ds, VBoxLayout vBox, List<String> allProps) {
        vBox.removeAll();

        Table table = componentsFactory.createComponent(Table.class);
        table.setColumnHeaderVisible(true);
        table.setSortable(false);

        // 添加列
        MetaClass metaClass = ds.getMetaClass();
        for (String prop : allProps) {
            MetaPropertyPath propMpp = metaClass.getPropertyPath(prop);
            Table.Column propColumn;
            if (propMpp != null) {
                propColumn = new Table.Column(propMpp, messages.getMessage(packPath, "ImportUser." + prop));
                propColumn.setType(propMpp.getRangeJavaClass());
            } else {
                // 此种情况是应对在 ImportUser 中没有定义,但是在 props 里面定义了的情况, 可以酌情使用
                propColumn = new Table.Column(prop, messages.getMessage(packPath, "ImportUser." + prop));
                propColumn.setType(String.class);
            }

            table.addColumn(propColumn);
        }

        table.setWidth("100%");
        table.setHeight("100%");
        table.setDatasource(ds);
        vBox.add(table);
    }


    /**
     * 模拟后端数据返回
     *
     * @param count
     * @return
     */
    private List<ImportUser> generateUserList(int count) {
        List<ImportUser> result = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            ImportUser importUser = new ImportUser(i, "Name" + i, System.currentTimeMillis() + "@ason.com", "Add" + i, new Date());

            result.add(importUser);
        }

        return result;
    }
}


/**
 * 后端定义的数据结构
 */
class ImportUser implements Serializable {
    private long id;
    private String name;
    private String email;
    private String address;
    private Date birthday;

    ImportUser(long id, String name, String email, String address, Date birthday) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.address = address;
        this.birthday = birthday;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd"
        caption="msg://caption"
        messagesPack="com.ason.testing.web.screens.kvCollection">
    <layout>
        <vbox id="vbox" spacing="true"/>
    </layout>
</window>

DataGrid use KeyValueCollection

DataGrid 中使用 KeyValueCollection 也是比较常见的,最简单的也是一些上传之类的数据,需要先校验,然后再确认上传。

一些准备工作

关键技术点

主要思路:创建一个 KeyValueCollection,然后给 kvCollection 设置 items, 然后由 DataGrid 来渲染

Step1:先在 xml 中定义一个键值对容器 kvCollection,然后填写 properties id(UUID), name(string), extension(string), size(long), createDate(Date)

Step2:将 kvCollection 设置到 dataGrid 上到 dataContainer 属性上

Step3:使用 Java 代码,动态设置 kvCollctionitems kvCollction.setItems(Collection<KeyValueEntity> T)

一些问题

// 这个代表更改 item,如果是新增等 item, 也不能使用 setItem
kvCollection.setItem();

// 这个代表添加 items
kvCollection.setItems();

代码实现

参看下一篇 8. upload file preview and remove functions 实现上传文件并预览、删除功能

实现上传文件并预览、删除等

一些准备

创建一个界面 datagrid-use-kv-screen

有以下文件被创建:

// Controller
DatagridUseKvScreen.java

// 界面
datagrid-use-kv-screen.xml

// 国际化等翻译文件
messages.properties
messages_zh_CN.properties
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment