大三上_javafx项目

前言

虽然标题写的是大三上,但实际上这个项目是从暑假的八月二十几号就开始做了。

记得那天中午吃饭的时候,Java老师突然给我发了个消息,问我有没有时间出差,他想让我单独负责一个项目

我看到这条消息当场就傻眼了,忙推辞说我没有能力单独负责一个项目。

但Java老师特别看好我,他说我的学习能力很强,做项目做的比很多研究生都好,还说如果我一个人忙不过来,他会安排一个研究生给我打下手。。。。

在这里插入图片描述

说真的,我这辈子都没有这么受宠若惊过

不过还好,后来可能是因为老师发现这个项目比他想象得复杂,所以他还是找了之前那个带我的研究生学长来带我,两个人做这个项目。

(这属实让我松了一口气,毕竟要是真让我带研究生,我可不好意思给研究生分配任务。。。。)

经过一个多月的努力,现在这个项目基本是完成了。

虽然从难度上来说,学长负责的数据解析部分(即从串口服务器读取数据并解析)比我负责的部分难。但从代码量来看,我完成了整个项目的百分之70~80

(这个项目为商业项目,所以本博客不会大量地贴出项目中的代码,只用来记录做项目的历程和遇到的困难)

一、效果展示

登陆界面

在这里插入图片描述

点击“修改登陆页面信息”按钮,弹出如下弹窗,可以修改登陆页面的公司名、协议、图片等信息

在这里插入图片描述

1、用户

使用普通用户账号登录显示如下界面,可实时监控各个厂区的状态

在这里插入图片描述

在左侧进行勾选或者点击图上的绿色小圆圈可查看具体信息

在这里插入图片描述

再进行勾选还可以查看更详细的内容,这里不再演示

点击左上角链接可进行搜索

在这里插入图片描述

选择筛选条件后,可以查询信息,并进行导出(PDF或Exel)

在这里插入图片描述

选择帮助,点击“软件操作指南”

在这里插入图片描述

自动弹出帮助文档供用户查看

在这里插入图片描述

2、管理员

使用管理员账号登录,可进行后台管理

在这里插入图片描述

其他模块不再展示,这里展示一下图片管理模块

在这里插入图片描述

编辑窗口如下所示

在这里插入图片描述

在这里插入图片描述

这里管理的就是用户界面显示的那张图片

点击添加点位则会在图片左上角生成一个点位,通过鼠标拖动可以将点位移到指定位置

在这里插入图片描述

已有的点位也可以通过鼠标拖动改变位置

在这里插入图片描述

如果不想保存修改,则点击重载图片按钮,反之则点击保存按钮

在这里插入图片描述

点击“改变点位大小”按钮可以改变点位大小,单位为像素

在这里插入图片描述

导入功能与编辑功能差不多,这里不再演示

点击改变点位样式按钮,可以改变点位的样式(比如正常的点位用绿色圆圈,异常点位用红色圆圈)

在这里插入图片描述

二、过程

其实做桌面软件的话最好是用C#,但由于我和学长之前都没学过C#,而且甲方说这个项目以后可能会推出网页版,用Java的话以后改成网页版比较方便,所以我们是选择了Java来做。

确定好语言后,由于我们俩之前也没接触过JavaFX,所以我们找了如下资料来学习JavaFX

JavaFX下载

JavaFX 程序退出时结束子线程

JavaFX 非Parent的Node只能真实地加在最后一个Parent中

javaGUI的替代者JavaFX

JavaFX入门(二):JavaFX和FXML

javaFX开发环境配置

Java: JavaFX桌面GUI开发

JavaFX中文资料

JavaFX教程

JavaFX 15官网

JavaFX教程

用maven创建javafx项目 解决“错误: 缺少 JavaFX 运行时组件, 需要使用该组件来运行此应用程序”

jdk8版本以上的javafx安装操作,通过下载javafx安装包,内附jdk8的安装包

1、前后端交互

在开始做项目之前,我先写了一个简单的Demo来搞明白JavaFX究竟如何使用。

由于我们俩对做Web项目比较熟悉,所以我们准备使用JavaFX中的Webview内嵌HTML页面来开发

1)第一个页面

首先需要搞明白的就是如何进行前后端交互

javaFX与js交互

JAVAFX应用程序嵌入本地的html文件(webview)

参考了这两篇博客后我明白了具体流程

要想实现内嵌HTML开发,需要写如下三个文件

Main

public class TestMain extends Application {
    public static void main(String[] args) {
        launch(args);
    }
    @Override
    public void start(final Stage primaryStage) throws IOException {
    	Pane root = FXMLLoader.load(getClass().getResource("main.fxml"));
		

        primaryStage.setScene(new Scene(root, 500, 400));
        primaryStage.show();
    }

}

fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.web.*?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.BaseController">
    <children>
        <WebView fx:id="webView" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
    </children>
</AnchorPane>

Controller

public class BaseController implements Initializable {

	public static final Window stage = null;

	private  JSObject win;
	
	@FXML
	private WebView webView;
	
	private WebEngine webengine;
	
	
	@Override
	public void initialize(URL location, ResourceBundle resources) {
		
		// TODO Auto-generated method stub
		webengine= webView.getEngine();
		
	     webengine.getLoadWorker().stateProperty().addListener(
	                (ObservableValue<? extends Worker.State> ov, State oldState, Worker.State newState) -> {
	                    if (newState == State.SUCCEEDED) {
	                        win = (JSObject) webengine.executeScript("window");
	                        win.setMember("app", new JavaApp(webengine));
	                    }
	                });
	     System.out.println("初始化");
		String Url = this.getClass().getResource("test.html").toExternalForm();
		webengine.load(Url);
	}
	

}

具体过程就是Main函数中利用如下代码加载fxml文件
Pane root = FXMLLoader.load(getClass().getResource("main.fxml"));

然后fxml文件中会指定一个controller文件
fx:controller="application.BaseController

最后在controller文件中把html文件加载到webview中即可

	String Url = this.getClass().getResource("test.html").toExternalForm();
	webengine.load(Url);

而前后端的交互就是通过以下代码在HTML文件中增加Java对象来实现

	win = (JSObject) webengine.executeScript("window");
    win.setMember("app", new JavaApp(webengine));

前端如果想要调用后端的函数,只需在JavaScript函数中使用这个Java对象的函数即可。

格式为

app.函数()

2)切换页面

参考了如下博客

JavaFX多个界面中的数据传递

我们可以在JavaApp(上面那个用来与前端交互的类)中加一个函数

public void turnto(String url) {
    	System.out.println(url);
    	String Url = Main.class.getResource(url).toExternalForm();
		webengine.load(Url);
    }

需要跳转时,只需在javascript中使用 app.turnto(url)即可,比如

app.turnto("/WEB-INF/views/admin/adminMenu.html");

2、项目架构的搭建

1)项目文件目录

在搭建项目架构时,我想尽可能地利用上一个项目的经验,所以使用了SM框架(SpringMVC肯定是用不了的)

SM整合(spring,mybatis)

接下来我像写SSM项目时一样,建了entity(实体类)、dao(与数据库进行交互)、service(业务逻辑)包。

接下来就是controller(与前端交互)层了,前面三个包和之前完全没区别,但controller层显然不能和之前一样。

不过了解了JavaFX如何与前端交互后,这一层也不难写,只需多写几个JavaApp类即可。

为了与上面的BaseController区分,我将controller层的包命名为controllerUtils

最后项目的架构如下所示

src里是Java文件,resource里是html、js 、css文件

在这里插入图片描述

src架构如下。

在这里插入图片描述

entity、dao、service、controllerUtils上面说过了
dto、tools、 utils 就是一些工具类
timer类用来进行实时刷新(后面会详细说)
config、config.mybatis、config.spring就是配置文件
config.mybatis.mapper就是写SQL的mapper映射文件(XML文件)

main和controller包里就是实现内嵌HTML的那三个文件,
只有BaseController和上面不太一样

首先就是spring配置文件需要手动导入

context = new ClassPathXmlApplicationContext("classpath:config/spring/applicationContext.xml");//context为org.springframework.context.ApplicationContext类

然后就是把JavaAPP换成了controllerUtils包里的那些类

	win.setMember("app", new LoginUtil(context, win, webView, webengine, user));
    win.setMember("normal", new NormalUserUtil(context,  win, webView, webengine, user));
	win.setMember("tiaoshi", new TiaoshiUtil(context,  win, webView, webengine, user));
	win.setMember("admin", new AdminUtil(context,  win, webView, webengine, user));

2)记录当前用户

之前是用Session记录当前用户,现在只能是使用静态变量了(登录后给静态变量currentUser赋值,退出后将该静态变量置为null)

3)自动编译

之前遇到的另一个小问题:有时更改了代码后没有效果,看了下面的博客后解决了该问题

eclipse不能自动编译生成class文件的解决办法

3、文件问题

1)文件选择与上传

做web项目是使用html里的标签就行,但这个项目使用html标签的话无法与后端交互

这个问题困扰了我很久,甚至让我一度想放弃使用javafx,最后我想到了可以用javafx自带的文件选择器

点击按钮时触发Java的函数

<button type="button" onclick="admin.chooseFile()" name="file">选择文件</button>

然后通过以下函数

选择文件:

public void chooseFile() {
		FileChooser fileChooser = new FileChooser();
        fileChooser.getExtensionFilters().addAll(
                new FileChooser.ExtensionFilter("All", "*.*"),
                new FileChooser.ExtensionFilter("JPG", "*.jpg"),
                new FileChooser.ExtensionFilter("PNG", "*.png")
            );
        file = fileChooser.showOpenDialog(stage);
        System.out.println(file);
	}

选择文件夹:

public void chooseFile() {
		DirectoryChooser directoryChooser = new DirectoryChooser();
        file = directoryChooser.showDialog(stage);
        //System.out.println(file);
	}

file即一个静态变量

得到选择的文件(静态变量file)后即可进行文件的上传了

最近学习了NIO,为了进行实践,我把文件上传函数中的文件复制部分改成了如下的样子(虽然效率提升不大,但聊胜于无)

(缓存、直接缓存、通道)

		FileChannel inchannel=FileChannel.open(Paths.get(mfile.toURI()), StandardOpenOption.READ);
		FileChannel outchannel=FileChannel.open(Paths.get(path), StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
		
		inchannel.transferTo(0, inchannel.size(), outchannel);

另一个问题就是最开始的时候,上传文件后需要手动刷新项目才能查看文件,查看了博客后解决了该问题

解决springboot上传文件至当前项目目录下,上传成功后,再次刷新项目才显示上传结果

2)导出文件

之前那个项目做过导出Exel,这次是导出PDF,其实也没多大区别:Java iText导出pdf功能实现

导出文件的时候遇到了一个让我挺无语的BUG,就是它会提示“指定的设备名无效”,看了下面的博客后才知道,给文件取名时有些名字是不能取的
win7下创建名为aux.c的文件提示“指定的设备名无效”

比如com3这个名字就不能取

在这里插入图片描述

3)打开本地文件

做项目时有一个需求是用户点击按钮,系统直接打开某个文件(不是点击下载),看了如下博客解决了该问题

使用java打开本地文件的方法

4、实时刷新及优化

实时检测系统,顾名思义就是可以实时地监测,一旦出了问题可以立即监测到

那如何实现呢?

一开始我和学长采用的方法是使用Javascript中的setInterval函数

在html页面实时显示系统时间

JS setInterval()/setTimeout()——实现动态时间,倒计时

setTimeout()、setInterval()不起作用?答案在这里

使用方法很简单:

setInterval(函数名,时间间隔) //时间间隔的单位为毫秒

但后来我们发现,用多了以后界面会非常卡

毕竟每次刷新都得从前端调用controllerUtils中的函数,然后controllerUtils调用service层,service层调用Dao层,Dao层查询数据库,最后再把数据一层层地返回回去,(尤其是涉及到数据解析的部分,还得从串口服务器读取数据,然后再解析,然后把解析出来的数据写入数据库)这么长的流程(而且还涉及到IO流操作)自然会很卡。

于是我和学长对此展开了讨论

学长说可以在后端写一个定时器,实时更新数据库(即把数据解析部分的实时刷新放到后端),然后前端再实时地读取数据库中的内容。

我在此基础上提出了另一个方案:把每一个需要实时刷新的数据存到controllerUtils中对应的一个静态变量中,后端用定时器实时更新这些静态变量,然后前端每次读取这些静态变量即可。

学长同意了这个方案,于是接下来的问题就是如何写定时器了

【简单定时器】JAVA 简单定时器的三种方法

我们选用了其中的第三种方法。

	ScheduledExecutorService service=Executors.newSingleThreadScheduledExecutor();
	service.scheduleAtFixedRate(runnable, 0, 1, TimeUnit.SECONDS);

第一行就是创建并管理一个只有一个线程的线程池

ScheduledExecutorService service=Executors.newSingleThreadScheduledExecutor();

第二行就是定时

service.scheduleAtFixedRate(runnable, 0, 1, TimeUnit.SECONDS);

第一个参数为运行的任务,第二个为延迟的时间,第三个为间隔的时间,第四个为时间单位

5、打包

每次迭代完一个版本后,我们就需要将项目打包成一个可运行软件发给甲方。

一开始是最笨的方法:让甲方安装JDK和Mysql

后来我们通过查看下面的博客学会了如何打包,如何在没有JDK的电脑上运行。

然后我们把Mysql换成了sqlite(一种轻量级数据库,可以理解为项目中内置一个数据库),解决了这个问题

区别:

  • sqlite数据类型只有integer 、real(小数) 、blob、 text
  • sqlite自增要插入null,mysql是0或null都可
  • sqlite无函数,没有concat函数,模糊查询时要写成
    … like '%$ {value}%'或…like ‘%${name}%’

第二种写法的前提是函数的参数前加上了@Param(“name”),

public List<Baojing> queryAll(@Param("name")String name);

附:

输入参数:parameterType

1.类型为 简单类型(8个基本类型+String)
  	#{}、$ {}的区别
	a.
	#{任意值}
	$ {value} ,其中的标识符只能是value	
	
	b.# {}自动给String类型加上''  (自动类型转换)
  $ {} 原样输出,但是适合于 动态排序(动态字段)
select stuno,stuname,stuage  from student where stuname = #{value}
select stuno,stuname,stuage  from student where stuname = '$ {value}
动态排序:
select stuno,stuname,stuage  from student  order by $ {value} asc

	c.# {}可以防止SQL注入
  $ {}不防止
  
$ {}、#{}相同之处:
	a.都可以 获取对象的值 (嵌套类型对象)
		i.获取对象值:
模糊查询,方式一:
select stuno,stuname,stuage  from student where stuage= #{stuAge}  or stuname like #{stuName} 
			Student student = new Student();
 			student.setStuAge(24);
 			student.setStuName("%w%");
 			List< Student> students = studentMapper.queryStudentBystuageOrstuName(student) ;//接口的方法->SQL
模糊查询,方式二:
	student.setStuName("w");
	select stuno,stuname,stuage  from student where stuage= #{stuAge}  or stuname like '%${stuName}%'
		ii.嵌套类型对象

2.对象类型
#{属性名}
${属性名}

1)如何打包

Java Swing实用小工具开发

javafx项目打包

使用exe4j打包javafx项目

jdk11使用jlink定制精简jre

jdk11订制jre + JavaFX11打包exe可执行程序

eclipse导出可执行的jar文件

jar导出与制作成exe在没jdk电脑下运行(图文教程+工具)

exe4j 将jar包封装为exe

甲方那边是Win7系统,为了测试他们的机器能否运行,我在虚拟机安了一个Win7系统

VMware 15 虚拟机安装 win 7 系统

打包后文件目录如下:

在这里插入图片描述

db为数据库文件(.db格式),jre8即运行环境,加上这个后没有jdk的电脑也可以运行。resource即项目涉及到的图片

其实还可以在此基础上再打包成可安装程序,不过我们目前还只是测试阶段,就没有进行再次打包

2)打包后配置文件的路径问题

jsp连接sqlite、Sqlite相对路径绝对路径问题

Spring配置文件打包到jar中无法加载问题之解决方案

使用exe4j把jar转换成exe文件时,报错java.lang.NoClassDefFoundError:

3)打包后上传、显示图片

参考博客:

Java将二进制流转Base64字符串并在页面显示(附Base64转二进制流)

解决Eclipse中无法直接使用sun.misc.BASE64Encoder及sun.misc.BASE64Decoder的问题

如何用JAVA将二进制文件转换成BASE64格式保存到MySQL的Blob字段里并读出下载

HTML base64格式的二进制流在 img 标签内显示

(1)显示图片

显示图片有很多种方法

①直接把img标签中的src写成相对路径即可显示

②先在后端利用反射加载classpath下的图片

this.getClass().getClassLoader().getResource(图片的相对路径)//返回URL

或者用

this.getClass().getResourceAsStream(图片相对路径)//返回InputStream

也可以用System.getProperty("user.dir") 获取项目的真实路径,然后在此基础上获取resource文件夹中的图片(考虑到之后还得上传图片,我采取的是这种方式)

然后把图片流转成base64格式的二进制流

	public String getImageByPath(String path) throws Exception {
    	File file=new File(path);
    	System.out.println("file:"+file.toString());
		FileInputStream fin = new FileInputStream(file);
		
		byte[] buffer = new byte[(int)file.length()];
		
		fin.read(buffer);
		
		String s=new BASE64Encoder().encode(buffer);  //将文件内容编码成base64格式后以字符串的方式保存到Blob字段中
			
		fin.close();
		
		return s;
	
    }

最后把该二进制流放到src里即可显示

document.getElementById("image").src="data:image/jpeg|png|gif;base64,"+app.getImageByPath(path);

③在数据库中直接存取图片二进制流,

然后用上面的方式显示。

但这样的话数据库查询效率会降低

(2)上传图片

打包后不能把图片上传到exe文件中,有以下两种解决方法(我想到的)

①直接把图片上传到数据库中(但数据库查询效率降低)

②打包后把用到的图片全放到与exe文件平行的resource文件夹里

在这里插入图片描述

可以这么做的原因是System.getProperty("user.dir") 这段代码比较神奇。

打包之前它获取的路径是项目的真实路径,打包之后它获取的是exe文件所在文件夹的路径

所以System.getProperty("user.dir")+"/resource/" 这段代码在在打包前可以正确获取到项目的resource文件夹中的图片,打包后也可以正确获取到与exe文件平行的resource文件夹中的图片

缺点就是每次打包都需要把项目中的resource文件夹中的图片复制到上面的resource文件夹中。(不影响正常使用)

6、写前端时遇到的问题

剩下的就是一些前端的问题,比如之前演示的拖动点位的实现

另外就是不知道为什么,写前端时JQuery有时好使有时不好使,EasyUI和LayUI中的控件也是有的好使有的不好使

我个人猜测这是因为JavaFX8使用的内嵌浏览器是非常落后的,对一些较新的前端技术不怎么支持

怎么让背景图片铺满整个页面

ajax 报错 400

用户在图片上点选并标记位置,js实现

FileReader.readAsDataURL()函数的使用

java流下载,前端ajax blob接收

window.URL.createObjectURL 的使用

解决EasyUi的combobox绑定change事件

layui+树结构表格+动态单元格加背景色

使用layer,layui,不能显示弹出效果

JavaScript数组方法

如何让两个div并排同行显示

table 每行 改变颜色

使用css实现一个圆形头像框效果

JS中String转int

实现div可拖放

关于js函数传参的问题

a标签href属性传递参数,onclick属性传递参数

form中的button问题

getElementsByClassName()的详细用法

Javascript改变css样式的四种方法

js点击οnclick=“函数”参数传入URL问题

a标签携带参数跳转并在跳转页面接收参数

Html代码中,< img src="">如何写图片路径

js刷新当前页面的5种方式

JS之String类型

js子级窗口相互调用父级的方法

向iframe传递数值简单方法(父页面向子页面传递数值)

iframe的跳转:直接改变iframe标签的src,如果需要传参,利用标签的参数传参

JSObject 的相关用法.

js获取单选按钮选项

关于z-index的详细解释

background-image 关于把一张图片完全显示在大div中,小div中

div标签常用属性

js获取图片宽高的方法

  • 14
    点赞
  • 123
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值