目标
开发打包一个exe软件,用于展现web我的项目内容。[仅供学习应用]应用 javaFX | BorderPane 布局
1.top局部 自定义窗口头部 ( icon, 题目,返回按钮,清理按钮,放大按钮,放大按钮,敞开按钮)
2.center局部 嵌入chrome内核浏览器(jxbrowser),用于展现我的项目内容
开发环境
- 32位是为了打出32位的程序和安装包,应用32位是为了让32位和64位零碎都可应用。
- jre1.8是javafx的运行时环境,为了可能让程序在其余没有装置jdk的电脑上装置运行,须要把32位的jre环境打包进程序中.
- 应用jxbrowser不是用自带webview的起因在于 webview卡顿重大且渲染页面会造成款式错乱.
- jdk1.8
- exe4j 5.0.1 (32位)
- inno setup 5.6.1 (32位)
- jre1.8的运行文件(32位)
jxbrowser-6.22.1.jar 和 jxbrowser-win32-6.22.1.jar (须要破解能力应用【仅供学习】, 破解步骤如下)
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
代码中(动态代码块, 必须比其余代码先运行)
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(); }}
开发思路
- 利用javaFX开发内部窗口,而后嵌入chrome浏览器
- 将开发出的程序打成jar包
- 利用exe4j将jar包打成exe启动程序
- 在用inno setup将exe程序封装成一个程序安装包
代码
- 如果没有我的项目地址,可用browser.loadHTML("测试页面"), 加载dom节点进行页面渲染;
- 如果有我的项目地址,可用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的安装包,他人装置了也无奈应用