> 文档中心 > 【JavaFx 构建Bpmn-JS 设计器】

【JavaFx 构建Bpmn-JS 设计器】


【JavaFx 构建Bpmn-JS 设计器】

  • 简介

    bpmn-js设计器web端组件非常多各式各样的, bpmn-js也提供很多实例,但是都只是功能实现,完全拿来使用可能并不能满足需求,经过一段时间的资料收集与研究,在javafx中实现了bpmn设计器组件功能的实现.现将核心实现分享与大家,希望共同探讨研究.

  • 实例效果

    当前JavaFx 构建Bpmn-JS 设计器效果如下:
    在这里插入图片描述

  • 引入框架

    • Bpmn资源:
      https://bpmn.io/toolkit/bpmn-js/
    • Bpmn examples:
      https://github.com/bpmn-io/bpmn-js-examples
  • 功能实现

    • bpmn-designer Bpmn 设计器组件实现

      • 项目结构
        在这里插入图片描述

      • BpmnComponent 组件实现

         package com.liangchao.cloud.bpmn;import javafx.concurrent.Worker;import javafx.scene.Node;import javafx.scene.web.WebView;import javafx.util.Callback;import netscape.javascript.JSObject;import java.io.BufferedReader;import java.io.File;import java.io.FileReader;public class BpmnComponent {    privateWebView    webView;    private static final String     HTML_PATH = "/public/index.html";    privateBpmnScript bpmnScript;//用户回调函数管理    public BpmnComponent() { this.webView = new WebView();    }    /**     * 获取组件对象     */    public Node getNode() { // 测试阶段 // VBox vBox = new VBox(); // vBox.getChildren().addAll( //  new ToolBar( //   new Button("创建") {{ //setOnAction(event -> { //    createNewDiagram(); //}); //   }}, //   new Button("放大") {{ //setOnAction(event -> { //    zoomInAction(); //}); //   }}, //   new Button("缩小") {{ //setOnAction(event -> { //    zoomOutAction(); //}); //   }}, //   new Button("还原") {{ //setOnAction(event -> { //    zoomResetAction(); //}); //   }}, //   new Button("保存BPMN") {{ //setOnAction(event -> { //    savaBpmnXmlAction(); //}); //   }}, //   new Button("保存SVG") {{ //setOnAction(event -> { //    savaBpmnPictureAction(); //}); //   }}, //   new Button("获取bpmn数据") {{ //setOnAction(event -> { //    getBpmnXmlAction(new Callback() { // @Override // public Object call(Object param) { //     System.out.println(param.toString()); //     return true; // } //    }); //}); //   }}, //   new Button("导入BPMN") {{ //setOnAction(event -> { //    String xml = file(new File("C:\\Users\\Administrator\\Desktop\\qingjia.bpmn")); //    openDiagram(xml); //}); //   }} //  ) // ); // // vBox.getChildren().add(webView); // return vBox; // 正式阶段 return webView;    }    /**     * 初始化节点数据     */    public BpmnComponent binds() { // 使用默认BpmnScript if (this.bpmnScript == null) {     this.bpmnScript = new BpmnScriptImpl(webView); } // 初始化 webView webView.getEngine().setOnAlert(event -> {     System.out.println(event.getData()); }); webView.getEngine().load(this.getClass().getResource(HTML_PATH).toExternalForm()); webView.getEngine().getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> {     if (newValue == Worker.State.SUCCEEDED) {  JSObject win = (JSObject) webView.getEngine().executeScript("window");  // 然后把应用程序对象设置成为js对象  win.setMember("bpmnScript", bpmnScript);     } }); return this;    }    /**     * 执行保存当前bpmn数据对象     */    public void savaBpmnXmlAction() { this.bpmnScript.executeScript("savaBpmnXml");    }    /**     * 执行保存当前bpmn 图片数据对象     */    public void savaBpmnPictureAction() { this.bpmnScript.executeScript("savaBpmnPicture");    }    /**     * 创建bpmn对象     */    public void createNewDiagram() { this.bpmnScript.executeScript("createNewDiagram");    }    /**     * 放大     */    public void zoomInAction() { this.bpmnScript.executeScript("zoomInAction");    }    /**     * 缩小     */    public void zoomOutAction() { this.bpmnScript.executeScript("zoomOutAction");    }    /**     * 还原     */    public void zoomResetAction() { this.bpmnScript.executeScript("zoomResetAction");    }    /**     * 用户获取bpmn xml数据处理     */    public void getBpmnXmlAction(Callback callback) { if (callback != null) {     this.bpmnScript.setGetBpmnXmlCallback(callback);     this.bpmnScript.executeScript("getBpmnXml"); }    }    /**     * 用户导入bomn     */    private void openDiagram(String xml) { this.bpmnScript.executeScript("openDiagram", xml);    }    /**     * 导入bpmn数据     */    public void openDiagramAction(String file) { openDiagram(file(new File(file)));    }    /**     * 导入bpmn数据     */    public void openDiagramAction(File file) { openDiagram(file(file));    }    /**     * 获取bpmn数据     */    public String file(File file) { String result = ""; try {     //构造一个BufferedReader类来读取文件     BufferedReader br = new BufferedReader(new FileReader(file));     String  s  = null;     //使用readLine方法,一次读一行     while ((s = br.readLine()) != null) {  result = result + "\n" + s;     }     br.close(); } catch (Exception e) {     e.printStackTrace(); } return result;    }}
      • BpmnScript 调用Bpmn 页面脚本

        package com.liangchao.cloud.bpmn;import javafx.util.Callback;/** * Bpmn 回调接口 */public interface BpmnScript {    /**     * 执行js调用函数     *     * @param method 函数名称     * @param param  参数     */    Object executeScript(String method, String... param);    /**     * 保存bpmn xml 数据     *     * @param xml bpmn 数据     */    boolean savaBpmn(String xml);    /**     * 获取bpmn xml 数据     *     * @param xml bpmn xml 数据     */    void getBpmnXml(String xml);    /**     * 获取bpmn xml 数据 用户端回调函数处理     *     * @param callback 回调处理     */    void setGetBpmnXmlCallback(Callback callback);    /**     * 保存bpmn svg 数据     *     * @param data svg数据     */    boolean savaBpmnSvg(String data);    /**     * WebView 页面消息返回调用请求处理     *     * @param msg 消息     */    void msg(String... msg);}
      • BpmnScript 抽象类 AbstractBpmnScript

         package com.liangchao.cloud.bpmn;import javafx.concurrent.Worker;import javafx.scene.Node;import javafx.scene.web.WebView;import javafx.stage.FileChooser;import java.io.File;import java.io.UnsupportedEncodingException;import java.net.URLDecoder;public abstract class AbstractBpmnScript implements BpmnScript {    private WebView node;    protected void setNode(WebView node) { this.node = node;    }    /**     * 执行js调用函数     *     * @param method 函数名称     * @param param  参数     */    public Object executeScript(String method, String... param) { if (!node.getEngine().getLoadWorker().getState().equals(Worker.State.SUCCEEDED)) {     throw new RuntimeException("WebView State :" + node.getEngine().getLoadWorker().getState() + ", 状态不可用"); } String jsHandle = ""; for (Object object : param) {     if (object != null) {  try {      // 进行转码处理      jsHandle += "," + "\'" + URLDecoder.decode(object.toString().replaceAll("\r|\n", ""), "UTF-8") + "\'";  } catch (UnsupportedEncodingException e) {      e.printStackTrace();  }     } } jsHandle = method + "(" + (jsHandle.length() > 1 ? jsHandle.substring(1) : "") + ")"; System.out.println("JS 调用函数:" + jsHandle); return node.getEngine().executeScript(jsHandle);    }    public Object fileChooser(String title, File openFile, Type type, FileChooser.ExtensionFilter... filters) { return fileChooser(this.node, title, openFile, type, filters);    }    /**     * 创建 FileChooser     *     * @param node     绑定对象     * @param title    标题     * @param openFile 默认打开位置     * @param type     打开类型     * @param filters  //过滤     */    public static Object fileChooser(Node node, String title, File openFile, Type type, FileChooser.ExtensionFilter... filters) { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle(title); fileChooser.setInitialDirectory(openFile == null ?  new File(System.getProperty("user.home")) :  openFile ); if (filters != null) {     fileChooser.getExtensionFilters().addAll(filters); } Object files = null; switch (type) {     case OPEN:  files = fileChooser.showOpenDialog(node.getScene().getWindow());  break;     case OPEN_MULTIPLE:  files = fileChooser.showOpenMultipleDialog(node.getScene().getWindow());  break;     case SAVA:  files = fileChooser.showSaveDialog(node.getScene().getWindow());  break; } return files;    }    public enum Type { /**  * 打开  */ OPEN, /**  * 打开多个  */ OPEN_MULTIPLE, /**  * 保存  */ SAVA;    }}
      • Bpmn-js 属性面板组件核心调用js处理

         import $ from 'jquery';import BpmnModeler from 'bpmn-js/lib/Modeler';import {BpmnPropertiesPanelModule, BpmnPropertiesProviderModule, CamundaPlatformPropertiesProviderModule} from 'bpmn-js-properties-panel';import diagramXML from '../resources/newDiagram.bpmn';/** ************************************* **//** 属性面板组件**//** ************************************* **/// ZeebePropertiesProviderModule// import ZeebeBpmnModdle from 'zeebe-bpmn-moddle/resources/zeebe.json'import CamundaBpmnModdle from 'camunda-bpmn-moddle/resources/camunda.json'import customTranslate from './i18n/customTranslate/customTranslate';/** ************************************* **//** 国际化组件  **//** ************************************* **/var customTranslateModule = {    translate: ['value', customTranslate]};/** ************************************* **//** bpmn 创建对象     **//** ************************************* **/var container = $('#js-drop-zone');var canvas = $('#js-canvas');var bpmnModeler = new BpmnModeler({    container: canvas,    propertiesPanel: { parent: '#js-properties-panel'    },    additionalModules: [ BpmnPropertiesPanelModule, BpmnPropertiesProviderModule, // ZeebePropertiesProviderModule CamundaPlatformPropertiesProviderModule, // 国际化 customTranslateModule    ],    moddleExtensions: { // zeebe: ZeebeBpmnModdle camunda: CamundaBpmnModdle    }});// 移除样式 with-diagramcontainer.removeClass('with-diagram');/** * 创建新Bpmn */function createNewDiagram() {    openDiagram(diagramXML);}/** * 打开bpmn xml文件 */async function openDiagram(xml) {    try { await bpmnModeler.importXML(xml); container     .removeClass('with-error')     .addClass('with-diagram');    } catch (err) { container     .removeClass('with-diagram')     .addClass('with-error'); container.find('.error pre').text(err.message); console.error(err);    }}function registerFileDrop(container, callback) {    function handleFileSelect(e) { e.stopPropagation(); e.preventDefault(); var files = e.dataTransfer.files; var file = files[0]; var reader = new FileReader(); reader.onload = function (e) {     var xml = e.target.result;     callback(xml); }; reader.readAsText(file);    }    function handleDragOver(e) { e.stopPropagation(); e.preventDefault(); e.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.    }    container.get(0).addEventListener('dragover', handleDragOver, false);    container.get(0).addEventListener('drop', handleFileSelect, false);}// file drag / drop ///// check file api availabilityif (!window.FileList || !window.FileReader) {    window.alert( 'Looks like you use an older browser that does not support drag and drop. ' + 'Try using Chrome, Firefox or the Internet Explorer > 10.');} else {    registerFileDrop(container, openDiagram);}// bootstrap diagram functions$(function () {    // 绑定创建bpmn    $('#js-create-diagram').click(function (e) { e.stopPropagation(); e.preventDefault(); createNewDiagram();    });});/** * 保存bpmn */async function exportDiagram() {    try { var result = await bpmnModeler.saveXML({format: true}); // console.log('DIAGRAM', result.xml); return result.xml;    } catch (err) { console.error('could not save BPMN 2.0 diagram', err);    }    return null;}/** * 保存 SVG */async function exportSvgDiagram() {    try { const {svg} = await bpmnModeler.saveSVG(); var encodedData = encodeURIComponent(svg); // return 'data:application/bpmn20-xml;charset=UTF-8,' + encodedData; return encodedData;    } catch (err) { console.error('Error happened saving SVG: ', err);    }    return null;}/***********************************************//** 提升作用域绑定windows ************************//***********************************************/window.createNewDiagram = function () {    createNewDiagram();}/** * 绑定createNewDiagram */BpmnFactory.createNewDiagram = function () {    createNewDiagram();}/** * 绑定重置大小 */BpmnFactory.zoomResetAction = function () {    bpmnModeler.get("zoomScroll").reset();}/** * 绑定放大 */BpmnFactory.zoomInAction = function () {    bpmnModeler.get("zoomScroll").stepZoom(1);}/** * 绑定缩小 */BpmnFactory.zoomOutAction = function () {    bpmnModeler.get("zoomScroll").stepZoom(-1);}/** * 绑定打开Bpmn数据文件 * @param xml bpmn Xml数据文件 */BpmnFactory.openXmlDiagram = function (xml) {    openDiagram(xml);}/** * 保存bpmn xml 数据 * @returns 返回bpmn xml 数据 */BpmnFactory.savaBpmnXml = function () {    return exportDiagram();}/** * 保存bpmn svg 图片 * @returns 返回bpmn svg 图片 */BpmnFactory.savaBpmnSvg = function () {    return exportSvgDiagram();}/** * 打开bpmn数据文件 * @param xml bpmn 数据 */BpmnFactory.openDiagram = function (xml) {    openDiagram(xml);}
    • Bpmn JavaFx 调用 bpmn-designer组件

       package com.liangchao.cloud.ui.controller.approve;import com.liangchao.cloud.bpmn.AbstractBpmnScript;import com.liangchao.cloud.bpmn.BpmnComponent;import com.liangchao.cloud.bpmn.BpmnScriptImpl;import com.liangchao.cloud.utils.javafx.SupperController;import com.liangchao.cloud.utils.javafx.Win;import de.felixroske.jfxsupport.FXMLController;import javafx.fxml.FXML;import javafx.scene.Node;import javafx.scene.control.TextArea;import javafx.scene.input.MouseEvent;import javafx.scene.layout.Priority;import javafx.scene.layout.VBox;import javafx.stage.FileChooser;import java.io.File;import java.io.UnsupportedEncodingException;/** * BPMN 设计器 */@FXMLControllerpublic class BpmnDesignerUIController extends SupperController {    @FXML    private VBox   webViewBox;    private BpmnComponent bpmnComponent;    public void getBpmnXmlAction(MouseEvent mouseEvent) { bpmnComponent.getBpmnXmlAction(param -> {     Win.open(new TextArea(param.toString()) {{  setPrefWidth(700);  setPrefHeight(400);     }}, Win.SwitchBtn.OK);     return true; });    }    public void importBpmnAction(MouseEvent mouseEvent) throws UnsupportedEncodingException { File file = (File) BpmnScriptImpl.fileChooser(webViewBox, "选择Bpmn文件", null, AbstractBpmnScript.Type.OPEN, new FileChooser.ExtensionFilter("Bpmn", "*.bpmn")); if (file != null) {     bpmnComponent.openDiagramAction(file); }    }    @Override    public void initNodesBus() { webViewBox.getChildren().clear(); bpmnComponent = new BpmnComponent().binds(); Node node = bpmnComponent.getNode(); webViewBox.getChildren().add(node); VBox.setVgrow(node, Priority.ALWAYS);    }    public void newBpmnAction(MouseEvent mouseEvent) { bpmnComponent.createNewDiagram();    }    public void savaBpmnSvgAction(MouseEvent mouseEvent) { bpmnComponent.savaBpmnPictureAction();    }    public void savaBpmnXmlAction(MouseEvent mouseEvent) { bpmnComponent.savaBpmnXmlAction();    }    public void zoomInAction(MouseEvent mouseEvent) { bpmnComponent.zoomInAction();    }    public void zoomOutAction(MouseEvent mouseEvent) { bpmnComponent.zoomOutAction();    }    public void zoomResetAction(MouseEvent mouseEvent) { bpmnComponent.zoomResetAction();    }}

- 项目缺陷

该功能组件虽然实现了,但是放在JavaFx中由于WebView解析问题,可能产生卡顿,效率等问题,需要优化WebView组件.

- 完整dome视频欣赏

该项目实现功能已经生成视频,地址:https://www.kuaishou.com/f/X2iaKlRJG1l26d 有兴趣的可以去看看.
二维码扫描直接查看:
在这里插入图片描述