目标

开发打包一个exe软件,用于展现web我的项目内容。[仅供学习应用]

应用 javaFX | BorderPane 布局

1.top局部 自定义窗口头部 ( icon, 题目,返回按钮,清理按钮,放大按钮,放大按钮,敞开按钮)

2.center局部 嵌入chrome内核浏览器(jxbrowser),用于展现我的项目内容

开发环境

  1. 32位是为了打出32位的程序和安装包,应用32位是为了让32位和64位零碎都可应用。
  2. jre1.8是javafx的运行时环境,为了可能让程序在其余没有装置jdk的电脑上装置运行,须要把32位的jre环境打包进程序中.
  3. 应用jxbrowser不是用自带webview的起因在于 webview卡顿重大且渲染页面会造成款式错乱.
  1. jdk1.8
  2. exe4j 5.0.1 (32位)
  3. inno setup 5.6.1 (32位)
  4. jre1.8的运行文件(32位)
  5. jxbrowser-6.22.1.jar 和 jxbrowser-win32-6.22.1.jar (须要破解能力应用【仅供学习】, 破解步骤如下)

    1. teamdev.licenses (放在打jar包生成的 META-INF 中)

      Product: JxBrowserVersion: 6.xLicensed to: Kagura.meLicense type: EnterpriseLicense info: JxBrowser LicenseExpiration date: 01-01-9999Support expiration date: NO SUPPORTGeneration date: 01-01-1970Platforms: win32/x86;win32/x64;mac/x86;mac/x64;linux/x86;linux/x64Company name: TeamDev Ltd.SigB: 1SigA: 1
    2. 代码中(动态代码块, 必须比其余代码先运行)

      static { try { Field e = bb.class.getDeclaredField("e"); e.setAccessible(true); Field f = bb.class.getDeclaredField("f"); f.setAccessible(true); Field modifersField = Field.class.getDeclaredField("modifiers"); modifersField.setAccessible(true); modifersField.setInt(e, e.getModifiers() & ~Modifier.FINAL); modifersField.setInt(f, f.getModifiers() & ~Modifier.FINAL); e.set(null, new BigInteger("1")); f.set(null, new BigInteger("1")); modifersField.setAccessible(false); } catch (Exception e1) { e1.printStackTrace(); }}

开发思路

  1. 利用javaFX开发内部窗口,而后嵌入chrome浏览器
  2. 将开发出的程序打成jar包
  3. 利用exe4j将jar包打成exe启动程序
  4. 在用inno setup将exe程序封装成一个程序安装包

代码

  1. 如果没有我的项目地址,可用browser.loadHTML("测试页面"), 加载dom节点进行页面渲染;
  2. 如果有我的项目地址,可用browser.loadURL(testUrl); 间接获取我的项目页面

    或者可用第三方网站地址 browser.loadURL(“https://www.baidu.com”)获取内容查看

其余细节看代码正文

package com.yemin.inspect;import com.teamdev.jxbrowser.chromium.*;import com.teamdev.jxbrowser.chromium.javafx.BrowserView;import javafx.application.Application;import javafx.application.Platform;import javafx.beans.value.ChangeListener;import javafx.beans.value.ObservableValue;import javafx.event.ActionEvent;import javafx.event.EventHandler;import javafx.geometry.Insets;import javafx.geometry.Pos;import javafx.geometry.Rectangle2D;import javafx.scene.Cursor;import javafx.scene.Scene;import javafx.scene.control.Button;import javafx.scene.control.Label;import javafx.scene.image.Image;import javafx.scene.image.ImageView;import javafx.scene.input.MouseEvent;import javafx.scene.layout.BorderPane;import javafx.scene.layout.GridPane;import javafx.scene.layout.Priority;import javafx.scene.layout.VBox;import javafx.scene.paint.Color;import javafx.scene.text.Font;import javafx.stage.Modality;import javafx.stage.Screen;import javafx.stage.Stage;import javafx.stage.StageStyle;import java.lang.reflect.Field;import java.lang.reflect.Modifier;import java.math.BigInteger;import java.util.HashMap;import java.util.UUID;public class Main extends Application {    //破解代码,用于破解jxbrowser包(仅供学习应用)    static {        try {            Field e = bb.class.getDeclaredField("e");            e.setAccessible(true);            Field f = bb.class.getDeclaredField("f");            f.setAccessible(true);            Field modifersField = Field.class.getDeclaredField("modifiers");            modifersField.setAccessible(true);            modifersField.setInt(e, e.getModifiers() & ~Modifier.FINAL);            modifersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);            e.set(null, new BigInteger("1"));            f.set(null, new BigInteger("1"));            modifersField.setAccessible(false);        } catch (Exception e1) {            e1.printStackTrace();        }    }    private final boolean production = false;//是否生产    private final String url = "<h1>hello world</h1>";//生产地址    private HashMap<String, String> testEnvironmentsUrls;//测试地址    private double x = 0.00;    private double y = 0.00;    private double width = 0.00;    private double height = 0.00;    private boolean isMax = false;    private boolean isRight;// 是否处于右边界调整窗口状态    private boolean isBottomRight;// 是否处于右下角调整窗口状态    private boolean isBottom;// 是否处于下边界调整窗口状态    private double RESIZE_WIDTH = 5.00;    private double MIN_WIDTH = 400.00;    private double MIN_HEIGHT = 300.00;    private double xOffset = 0, yOffset = 0;//自定义dialog挪动横纵坐标     /**     * testEnvironmentsUrls 是 production变量为false(测试环境下),点击头部icon可     * 弹出窗口进行抉择拜访环境的地址     * @throws Exception     */    @Override    public void init() throws Exception {        super.init();        testEnvironmentsUrls = new HashMap<String, String>();        testEnvironmentsUrls.put("测试1", "<h1>测试1</h1>");        testEnvironmentsUrls.put("测试2", "<h1>测试2</h1>");    }         /**     * 1. Stage 是程序窗口 ---》 舞台     * 2. Scene 是程序页面 ----》 场景 (可舞台固定只切换场景)     * 3. 其余的按钮之类的货色是 放在scene上, 而后scene在放入stage     * 布局指的是在scene内布局(常见布局请查阅相干材料)     * 4 .依据须要对窗口,页面,元素增加对应的监听代码     * @param primaryStage     * @throws Exception     */    @Override    public void start(Stage primaryStage) throws Exception {        System.out.println("以后拜访页面: " + url);        primaryStage.initStyle(StageStyle.TRANSPARENT);        BorderPane root = new BorderPane();        //-------设置头部bar--------        GridPane gpTitle = new GridPane();        gpTitle.setAlignment(Pos.CENTER_LEFT);        gpTitle.setPadding(new Insets(8));        String title = "javaFX开发打包测试";        Label lbTitle = new Label(title);        lbTitle.setTextFill(Color.web("#ccc"));        lbTitle.setFont(new Font("Arial", 14));        ImageView imageView = new ImageView("/img/icon.png");        imageView.setFitWidth(20);        imageView.setFitHeight(20);        lbTitle.setGraphic(imageView);        Button btnMin = new Button();        btnMin.setId("minButton");        btnMin.setPrefSize(20, 20);        Button btnMax = new Button();        btnMax.setId("maxButton");        btnMax.setPrefSize(20, 20);        Button btnClose = new Button();        btnClose.setId("closeButton");        btnClose.setPrefSize(20, 20);        Button btnBack = new Button();        btnBack.setId("backButton");        btnBack.setPrefSize(20, 20);        Button btnClean = new Button();        btnClean.setId("btnClean");        btnClean.setPrefSize(18, 18);        gpTitle.add(lbTitle, 0, 0);        gpTitle.add(btnBack, 1, 0);        gpTitle.add(btnClean, 2, 0);        gpTitle.add(btnMin, 3, 0);        gpTitle.add(btnMax, 4, 0);        gpTitle.add(btnClose, 5, 0);        gpTitle.setStyle("-fx-background-color: black;");        gpTitle.setPrefHeight(20);        gpTitle.setMaxHeight(20);        GridPane.setHgrow(lbTitle, Priority.ALWAYS);        GridPane.setMargin(btnBack, new Insets(0, 6, 0, 0));        GridPane.setMargin(btnClean, new Insets(0, 6, 0, 0));        GridPane.setMargin(btnMin, new Insets(0, 6, 0, 0));        GridPane.setMargin(btnMax, new Insets(0, 6, 0, 0));        GridPane.setMargin(btnClose, new Insets(0, 6, 0, 0));        root.setTop(gpTitle);        //-------设置内容局部--------        BrowserContext context = new BrowserContext(new BrowserContextParams("/tmp/" + UUID.randomUUID().toString()));        Browser browser = new Browser(BrowserType.LIGHTWEIGHT, context);        BrowserView browserView = new BrowserView(browser);        if (production) {            browser.loadHTML(url);        } else {            String testUrl = testEnvironmentsUrls.get("测试1");            browser.loadHTML(testUrl);        }        root.setCenter(browserView);        root.getCenter().setStyle("-fx-background-color: white;visibility: visible");        //-----------按钮事件监听--------------        btnMin.setOnAction(new EventHandler<ActionEvent>() {            @Override            public void handle(ActionEvent event) {                primaryStage.setIconified(true);            }        });        btnMax.setOnAction(new EventHandler<ActionEvent>() {            @Override            public void handle(ActionEvent event) {                Rectangle2D rectangle2d = Screen.getPrimary().getVisualBounds();                isMax = !isMax;                if (isMax) {                    // 最大化                    primaryStage.setX(rectangle2d.getMinX());                    primaryStage.setY(rectangle2d.getMinY());                    primaryStage.setWidth(rectangle2d.getWidth());                    primaryStage.setHeight(rectangle2d.getHeight());                } else {                    // 缩放回原来的大小                    primaryStage.setX(x);                    primaryStage.setY(y);                    primaryStage.setWidth(width);                    primaryStage.setHeight(height);                }            }        });        btnClose.setOnAction(new EventHandler<ActionEvent>() {            @Override            public void handle(ActionEvent event) {                clearData(browser);                browser.stop();                primaryStage.close();                Platform.exit();                System.exit(0);            }        });        btnBack.setOnAction(new EventHandler<ActionEvent>() {            @Override            public void handle(ActionEvent event) {                String nowWebViewUrl = browser.getURL();                if (nowWebViewUrl.contains("menunav.jsp")) {                    browser.executeJavaScript("parent.document.getElementById('main-iframe').contentWindow.history.go(-1);");                }            }        });        btnClean.setOnAction(new EventHandler<ActionEvent>() {            @Override            public void handle(ActionEvent event) {                System.out.println("革除缓存,跳转页面: " + browser.getURL());                redirectUrl(browser, browser.getURL());            }        });        //窗口大小地位事件监听        primaryStage.xProperty().addListener(new ChangeListener<Number>() {            @Override            public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {                if (newValue != null && !isMax) {                    x = newValue.doubleValue();                }            }        });        primaryStage.yProperty().addListener(new ChangeListener<Number>() {            @Override            public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {                if (newValue != null && !isMax) {                    y = newValue.doubleValue();                }            }        });        primaryStage.widthProperty().addListener(new ChangeListener<Number>() {            @Override            public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {                if (newValue != null && !isMax) {                    width = newValue.doubleValue();                }            }        });        primaryStage.heightProperty().addListener(new ChangeListener<Number>() {            @Override            public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {                if (newValue != null && !isMax) {                    height = newValue.doubleValue();                }            }        });        //鼠标挪动事件监听        root.setOnMouseMoved((MouseEvent event) -> {            event.consume();            double x = event.getSceneX();            double y = event.getSceneY();            double width = primaryStage.getWidth();            double height = primaryStage.getHeight();            // 鼠标光标初始为默认类型,若未进入调整窗口状态,放弃默认类型            Cursor cursorType = Cursor.DEFAULT;            // 先将所有调整窗口状态重置            isRight = isBottomRight = isBottom = false;            if (y >= height - RESIZE_WIDTH) {                if (x <= RESIZE_WIDTH) {                    // 左下角调整窗口状态                    //不解决                } else if (x >= width - RESIZE_WIDTH) {                    // 右下角调整窗口状态                    isBottomRight = true;                    cursorType = Cursor.SE_RESIZE;                } else {                    // 下边界调整窗口状态                    isBottom = true;                    cursorType = Cursor.S_RESIZE;                }            } else if (x >= width - RESIZE_WIDTH) {// 右边界调整窗口状态                isRight = true;                cursorType = Cursor.E_RESIZE;            }            // 最初扭转鼠标光标            root.setCursor(cursorType);        });        //鼠标拖拽事件        root.setOnMouseDragged((MouseEvent event) -> {            //依据鼠标的横纵坐标挪动dialog地位            event.consume();            if (yOffset != 0) {                primaryStage.setX(event.getScreenX() - xOffset);                if (event.getScreenY() - yOffset < 0) {                    primaryStage.setY(0);                } else {                    primaryStage.setY(event.getScreenY() - yOffset);                }            }            double x = event.getSceneX();            double y = event.getSceneY();            // 保留窗口扭转后的x、y坐标和宽度、高度,用于预判是否会小于最小宽度、最小高度            double nextX = primaryStage.getX();            double nextY = primaryStage.getY();            double nextWidth = primaryStage.getWidth();            double nextHeight = primaryStage.getHeight();            // 所有左边调整窗口状态            if (isRight || isBottomRight) {                nextWidth = x;            }            // 所有下边调整窗口状态            if (isBottomRight || isBottom) {                nextHeight = y;            }            // 如果窗口扭转后的宽度小于最小宽度,则宽度调整到最小宽度            if (nextWidth <= MIN_WIDTH) {                nextWidth = MIN_WIDTH;            }            // 如果窗口扭转后的高度小于最小高度,则高度调整到最小高度            if (nextHeight <= MIN_HEIGHT) {                nextHeight = MIN_HEIGHT;            }            // 最初对立扭转窗口的x、y坐标和宽度、高度,能够避免刷新频繁呈现的屏闪状况            primaryStage.setX(nextX);            primaryStage.setY(nextY);            primaryStage.setWidth(nextWidth);            primaryStage.setHeight(nextHeight);        });        //鼠标点击获取横纵坐标        root.setOnMousePressed(event -> {            event.consume();            xOffset = event.getSceneX();            if (event.getSceneY() > 46) {                yOffset = 0;            } else {                yOffset = event.getSceneY();            }        });        //非生产可抉择环境        if (!production) {            lbTitle.setOnMouseClicked(event -> {                chooseEnvironment(browser);            });        }        //------------启动构建---------------        Scene scene = new Scene(root);        scene.setUserAgentStylesheet("/css/mainStage.css");        primaryStage.setScene(scene);        primaryStage.setTitle(title);        primaryStage.getIcons().add(new Image("/img/icon.png"));        primaryStage.show();        btnMax.fire();//触发最大化按钮    }    public void chooseEnvironment(Browser browser) {        Stage window = new Stage();        window.setTitle("抉择环境");        window.initModality(Modality.APPLICATION_MODAL);        window.setHeight(150);        window.setWidth(300);        Label label = new Label("请抉择环境");        VBox layout = new VBox(10);        layout.getChildren().add(label);        for (String key : testEnvironmentsUrls.keySet()) {            Button button = new Button(key);            button.setOnAction(e -> {                redirectUrl(browser, testEnvironmentsUrls.get(key));                window.close();            });            layout.getChildren().add(button);        }        layout.setAlignment(Pos.CENTER);        Scene scene = new Scene(layout);        window.setScene(scene);        window.showAndWait();    }    private void redirectUrl(Browser browser, String url) {        System.out.println("切换地址: " + url);        clearData(browser);        browser.loadHTML(url);    }    private void clearData(Browser browser){        browser.getCacheStorage().clearCache();        browser.getCookieStorage().deleteAll();        browser.getLocalWebStorage().clear();        browser.getSessionWebStorage().clear();    }    public static void main(String[] args) {        launch(args);    }}

我的项目编译成JAR包

1.引入第三方包 jxbrowser-6.22.1.jar 和 jxbrowser-win32-6.22.1.jar (将第三方包的export勾选起来), 这样能够把第三方的包最初全都打包打主jar包中

2.artifacts构建jar包

3.build --> build artifacts --> rebuild

JAR包 转 EXE (6,7两步是重点)

exe4j 注册码: A-XVK258563F-1p4lv7mg7sav

1.change license 弄一个能够用的注册码

2.咱们是jar包转exe,抉择第二个

3.配置exe的名字和输入到哪个文件夹

4.Icon File 能够配置利用图标

5.看你要打几位的exe文件本人按须要抉择

6.这一步是重点,你要把之前的打出的jar包放进来, 而后抉择main class

7.配置程序的运行时jre环境的地位. 这边我只配置为相邻的jre文件夹,所以编译输入的exe文件要与jre文件相邻,能力启动运行。如果须要拿到别的电脑运行,要把jre和exe都复制过来

8.其余的就程序一个个走上来,根本都是默认的就能够了

EXE 打成安装包(第4步是重点)

1.启动inno setup ,新建一个文件

2.安装包名称版本之类的信息,本人依据要求输出

3.按默认即可

4.这步是重点!! 把之前编译的exe文件放到主执行文件中, 而后jre的文件夹放到,上面的其余应用程序文件文件中

5.默认或依据需要抉择

6.输入文件夹,文件名称,安装包图标

7.其余默认

8.最初会生成一个编译脚本, 有编译按钮(构建安装包)和启动按钮(安装程序)

; 脚本由 Inno Setup 脚本向导 生成!; 无关创立 Inno Setup 脚本文件的详细资料请查阅帮忙文档!#define MyAppName "这个是app名称"#define MyAppVersion "app版本"#define MyAppPublisher "app发布者"#define MyAppURL "app url地址"#define MyAppExeName "app执行名称.exe"[Setup]; 注: AppId的值为独自标识该应用程序。; 不要为其余安装程序应用雷同的AppId值。; (生成新的GUID,点击 工具|在IDE中生成GUID。)AppId={{0B52F1C0-A71F-48DE-8C2E-940B29412328}AppName={#MyAppName}AppVersion={#MyAppVersion};AppVerName={#MyAppName} {#MyAppVersion}AppPublisher={#MyAppPublisher}AppPublisherURL={#MyAppURL}AppSupportURL={#MyAppURL}AppUpdatesURL={#MyAppURL}DefaultDirName={pf}{#MyAppName}DisableProgramGroupPage=yesOutputDir=C:UsersAdministratorDesktopxxSystem安装包名称setupOutputBaseFilename=安装包名称setupSetupIconFile=C:UsersAdministratorDesktopxxSystemprojecticon.icoCompression=lzmaSolidCompression=yes[Languages]Name: "chinesesimp"; MessagesFile: "compiler:Default.isl"[Tasks]Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkablealone; OnlyBelowVersion:0,6.3[Files]Source: "C:UsersAdministratorDesktopxxSystemprojectoutputapp启动程序名称.exe"; DestDir: "{app}"; Flags: ignoreversionSource: "C:UsersAdministratorDesktopxxSystemprojectjre*"; DestDir: "{app}jre"; Flags: ignoreversion recursesubdirs createallsubdirs; 留神: 不要在任何共享系统文件上应用“Flags: ignoreversion”[Icons]Name: "{commonprograms}{#MyAppName}"; Filename: "{app}{#MyAppExeName}"Name: "{commondesktop}{#MyAppName}"; Filename: "{app}{#MyAppExeName}"; Tasks: desktopicon[Run]Filename: "{app}{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent

总结

1.开发中的重点在于布局和嵌入浏览器

2.前面编译的重点在于jre环境程序,肯定要打进去,不然不带jre的安装包,他人装置了也无奈应用