【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 {
        
            private              WebView    webView;
            private static final String     HTML_PATH = "/public/index.html";
            private              BpmnScript 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-diagram
        container.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 availability
        if (!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 设计器
       */
      @FXMLController
      public 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 有兴趣的可以去看看.
二维码扫描直接查看:
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
javafx -fx-background-image是JavaFX中用于设置背景图片的样式属性。通过设置该属性,可以将指定的图片作为背景图像应用于JavaFX场景或节点。具体的使用方式是在CSS样式中使用类似以下语法进行设置: -fx-background-image: url("image.jpg"); 其中,url("image.jpg")表示要使用的图片的路径和文件名。注意,该路径可以是相对路径或绝对路径。 此外,还可以通过其他样式属性来调整背景图片的显示方式,例如设置背景图片的位置、尺寸、重复方式等。可以使用类似以下语法进行设置: -fx-background-position: center; -fx-background-size: cover; -fx-background-repeat: no-repeat; 以上是设置背景图片的一些基本用法,具体的样式属性和用法可以参考JavaFX的相关文档和API。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [JavaFX官方教程(一)之JavaFX概述](https://blog.csdn.net/moakun/article/details/83045838)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [JavaFX学习笔记](https://blog.csdn.net/lsj1997718117/article/details/122781016)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值