Java GUI——网页浏览器开发
前言:为了做java课设,学了一手Java GUI。感觉蛮有意思的,写写文章,做个视频记录一下。欢迎大家友善指出我的不足
网页浏览器开发录制视频,从头敲到尾
任务需求
界面需求
-
菜单栏
- 文件 【热键F】
- 另存为 【热键A,快捷键CTRL+S】
- 退出 【热键I,快捷键CTRL+E】
- 编辑 【热键E】
- 前进 【快捷键CTRL+Z】
- 后退 【快捷键CTRL+D】
- 视图 【热键V】
- 全屏 【快捷键CTRL+U】
- 查看源码 【热键C,快捷键CTRL+C】
- 刷新 【快捷键CTRL+R】
- 文件 【热键F】
-
工具栏
- 另存为
- 后退
- 前进
- 查看源代码
- 退出
-
工具栏
- label(地址)
- 文本框
- 转向按钮
功能需求
另存为:保存正在访问的界面
前进:访问现有的上一个界面
后退:访问现有页面的下一个页面
查看源文件:查看访问页面的HTML源文件、并提供保存、退出功能
参考样式
功能解析
界面搭建
- 界面解析【主界面】
tip:
WebView的使用注意事项
需要在FX线程中使用
/** * <p>{@code WebView} objects must be created and accessed solely from the * FX thread. */
Platform.runLater说明
能够启动JavaFX线程
/** * Run the specified Runnable on the JavaFX Application Thread at some * unspecified */
启动前,不能启动FX runtime. 对于Swing,只要初始化第一个JFXPanel,就算启动FX runtime
/** * <p> * This method must not be called before the FX runtime has been * initialized. * For Swing applications that use JFXPanel to display FX content, the FX * runtime is initialized when the first JFXPanel instance is constructed. * For SWT application that use FXCanvas to display FX content, the FX * <p> */
WebView无法直接添加到JPanel中,需要借助JFXPanel,同时WebView的创建需要在独立的FX线程,因此WebView添加JPanel的代码稍微有些麻烦
Platform.runLater(() -> { // 不能再Platform.runLater之前运行, 否则就启动了FX runtime, Platform运行会报错 webView = new WebView(); // webPanel 是JFXPanel webPanel.setScene(new Scene(webView)); // 加载网页 webView.getEngine().load(url); }); // TODO 将webPanel添加到JPanel中
- 界面解析【查看源代码界面】
模块划分
URL存储数组,用单独的类(URLList)来维护
URLList,建立起
主界面
,html代码界面
,监听器界面
沟通的桥梁。同时URLList维护的数组,能够存储URL访问历史记录,实现网页前进
,后退功能
代码编写
maven坐标
<dependencies>
<!--Servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Lombok 依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
<!--MyBatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.5.2</version>
</dependency>
<!--MySQL-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.0</version>
</plugin>
</plugins>
</build>
utils
Constant
全局常量
package com.xhf.keshe.utils;
import java.awt.*;
public interface Constant {
int MAIN_WIDTH = 1920;
int MAIN_HEIGHT = 1080;
String[] menuList1 = {"另存为(A)", "退出(I)"};
String[] menuList2 = {"前进", "后退"};
String[] menuList3 = {"全屏", "查看源码(C)", "刷新"};
Font baseFont = new Font("仿宋", Font.BOLD, 20);
Font smallFont = new Font("仿宋", Font.BOLD, 15);
String[] toolBarButtonNameList = {"另存为", "后退", "前进", "查看源码", "退出"};
int SOURCE_WIDTH = 800;
int SOURCE_HEIGHT = 600;
}
WebsiteHTMLGetter
通过URL解析出html代码
package com.xhf.keshe.utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class WebsiteHTMLGetter {
public static String getHTMLCode(String url) throws IOException {
URL website = new URL(url);
HttpURLConnection connection = (HttpURLConnection) website.openConnection();
connection.setRequestMethod("GET");
// 获取网页内容
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder htmlCode = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
htmlCode.append(line).append("\n");
}
reader.close();
return htmlCode.toString();
}
}
URLList
维护URL记录
package com.xhf.keshe.utils;
import java.util.ArrayList;
import java.util.List;
public class URLList {
private static List<String> queue = new ArrayList<String>();
private static int pointer = -1;
// 获取当前元素
public static String getCur() {
if (queue.size() == 0) return null;
return queue.get(pointer);
}
// 指针右移
public static boolean right() {
if (pointer == queue.size() - 1) {
return false;
}
pointer++;
return true;
}
// 指针左移
public static boolean left() {
if (pointer == 0) {
return false;
}
pointer--;
return true;
}
// 添加元素
public static boolean add(String url) {
if (pointer + 1 > queue.size() - 1) {
queue.add(url);
++pointer;
}else {
queue.set(++pointer, url);
}
return true;
}
}
界面
主界面
package com.xhf.keshe;
import com.xhf.keshe.listener.*;
import com.xhf.keshe.utils.Constant;
import com.xhf.keshe.utils.URLList;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.web.WebView;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
/**
* 主界面
*/
public class Main extends JFrame {
public static JPanel toolBarPanel;
/**
* http地址接收栏
*/
private static JTextField http = new JTextField();
/**
* 转向
*/
private JButton redirect = new JButton("转向");
/**
* 显示web界面的panel
*/
public static JFXPanel webPanel;
/**
* 显示website
*/
public static WebView webView;
/**
* 刷新界面
*/
public static void refreshWebSite(String url) {
Platform.runLater(() -> {
webView = new WebView();
webPanel.setScene(new Scene(webView));
// 加载网页
webView.getEngine().load(url);
// 重新加载http
http.setText(url);
});
}
public Main() {
this.setSize(Constant.MAIN_WIDTH, Constant.MAIN_HEIGHT);
// 居中
this.setLocationRelativeTo(null);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 初始化界面
initUI();
refreshWebSite(http.getText());
}
/**
* 初始化界面
*/
private void initUI() {
// 初始化所有的panel界面
initPanel();
// 初始化菜单栏
initMenu();
// 初始化工具栏1
initToolBar1();
// 初始化工具栏2
initToolBar2();
}
/**
* 初始化panel界面
*/
private void initPanel() {
toolBarPanel = new JPanel();
toolBarPanel.setLayout(new GridLayout(2, 1));
webPanel = new JFXPanel();
webPanel.setLayout(new BorderLayout());
webPanel.add(toolBarPanel, BorderLayout.NORTH);
add(webPanel);
}
/**
* 初始化二号工具栏
*/
private void initToolBar2() {
JToolBar jToolBar2 = new JToolBar();
JLabel jLabel = new JLabel("地址");
jLabel.setFont(Constant.smallFont);
jToolBar2.add(jLabel);
http.setFont(Constant.smallFont);
http.setText("https://www.baidu.com/");
URLList.add("https://www.baidu.com/");
jToolBar2.add(http);
redirect.setFont(Constant.smallFont);
// 执行网页跳转逻辑
redirect.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
URLList.add(http.getText());
refreshWebSite(http.getText());
}
});
jToolBar2.add(redirect);
toolBarPanel.add(jToolBar2);
}
/**
* 初始化一号工具栏
*/
private void initToolBar1() {
JToolBar jToolBar1 = new JToolBar();
for (int i = 0; i < Constant.toolBarButtonNameList.length; i++) {
JButton jButton = new JButton(Constant.toolBarButtonNameList[i]);
jButton.setFont(Constant.smallFont);
jToolBar1.add(jButton);
try {
if (Constant.toolBarButtonNameList[i].equals("另存为")) {
jButton.addActionListener(new SaveCodeListener());
} else if (Constant.toolBarButtonNameList[i].equals("后退")) {
jButton.addActionListener(new URLmoveListener(URLmoveListener.BACKEND));
} else if (Constant.toolBarButtonNameList[i].equals("前进")) {
jButton.addActionListener(new URLmoveListener(URLmoveListener.FORWARD));
} else if (Constant.toolBarButtonNameList[i].equals("查看源码")) {
jButton.addActionListener(new GetSourceCodeListener());
} else {
jButton.addActionListener(new QuitListener(this));
}
}catch (Exception e) {
e.printStackTrace();
}
}
toolBarPanel.add(jToolBar1);
}
/**
* 初始化菜单栏
*/
private void initMenu() {
JMenuBar jMenuBar = new JMenuBar();
// 初始化 '文件' 菜单
JMenu jMenu1 = new JMenu("文件(ALT+F)");
jMenu1.setMnemonic(KeyEvent.VK_F);
jMenu1.setFont(Constant.baseFont);
for (int i = 0; i < Constant.menuList1.length; i++) {
JMenuItem item = new JMenuItem(Constant.menuList1[i]);
item.setFont(Constant.baseFont);
// 添加热键, 快捷键
if (Constant.menuList1[i].equals("另存为(A)")) {
// A
item.setMnemonic(KeyEvent.VK_A);
// ctrl + s
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK));
item.addActionListener(new SaveCodeListener());
}else if (Constant.menuList1[i].equals("退出(I)")) {
// I
item.setMnemonic(KeyEvent.VK_I);
// ctrl + e
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, ActionEvent.CTRL_MASK));
item.addActionListener(new QuitListener(this));
}
jMenu1.add(item);
}
// 初始化 '编辑' 菜单
JMenu jMenu2 = new JMenu("编辑(ALT+E)");
jMenu2.setMnemonic(KeyEvent.VK_E);
jMenu2.setFont(Constant.baseFont);
for (int i = 0; i < Constant.menuList2.length; i++) {
JMenuItem item = new JMenuItem(Constant.menuList2[i]);
item.setFont(Constant.baseFont);
// 添加快捷键
if (Constant.menuList2[i].equals("前进")) {
// ctrl + z
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, ActionEvent.CTRL_MASK));
item.addActionListener(new URLmoveListener(URLmoveListener.FORWARD));
}else if (Constant.menuList2[i].equals("后退")){
// ctrl + d
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, ActionEvent.CTRL_MASK));
item.addActionListener(new URLmoveListener(URLmoveListener.BACKEND));
}
jMenu2.add(item);
}
// 初始化 '视图' 菜单
JMenu jMenu3 = new JMenu("视图(ALT+V)");
jMenu3.setMnemonic(KeyEvent.VK_V);
jMenu3.setFont(Constant.baseFont);
for (int i = 0; i < Constant.menuList3.length; i++) {
JMenuItem item = new JMenuItem(Constant.menuList3[i]);
item.setFont(Constant.baseFont);
// 添加热键, 快捷键
if (Constant.menuList3[i].equals("全屏")) {
// ctrl + u
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_U, ActionEvent.CTRL_MASK));
item.addActionListener(new FullScreenListener(this));
}else if (Constant.menuList3[i].equals("查看源码(C)")) {
// c
item.setMnemonic(KeyEvent.VK_C);
// ctrl + c
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK));
item.addActionListener(new GetSourceCodeListener());
}else if (Constant.menuList3[i].equals("刷新")) {
// ctrl + r
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, ActionEvent.CTRL_MASK));
item.addActionListener(new RefreshWebSite());
}
jMenu3.add(item);
}
jMenuBar.add(jMenu1);
jMenuBar.add(jMenu2);
jMenuBar.add(jMenu3);
this.setJMenuBar(jMenuBar);
}
public static void main(String[] args) {
Main main = new Main();
}
}
查看源代码界面
package com.xhf.keshe.source;
import com.xhf.keshe.listener.QuitListener;
import com.xhf.keshe.listener.SaveCodeListener;
import com.xhf.keshe.utils.Constant;
import com.xhf.keshe.utils.URLList;
import com.xhf.keshe.utils.WebsiteHTMLGetter;
import javax.swing.*;
import java.awt.*;
import java.io.*;
public class SourcePage extends JFrame{
/**
* 工作界面
*/
private JPanel workspace = new JPanel();
public SourcePage() {
this.setSize(Constant.SOURCE_WIDTH, Constant.SOURCE_HEIGHT);
// 居中
this.setLocationRelativeTo(null);
this.setVisible(true);
// 初始化界面
initUI();
}
/**
* 初始化UI
*/
private void initUI() {
workspace.setLayout(new BorderLayout());
// 初始化标题
JLabel title = new JLabel("源代码");
title.setFont(Constant.baseFont);
title.setHorizontalAlignment(SwingConstants.CENTER);
// 初始化文本域
initTextArea();
// 初始化按钮
initButton();
add(workspace);
}
/**
* 初始化按钮
*/
private void initButton() {
// 添加按钮显示区域
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new FlowLayout());
// 创建按钮
JButton save = new JButton("保存");
save.setFont(Constant.baseFont);
save.addActionListener(new SaveCodeListener());
JButton quit = new JButton("退出");
quit.addActionListener(new QuitListener(this));
quit.setFont(Constant.baseFont);
buttonPanel.add(save);
buttonPanel.add(quit);
// 添加到panel中
workspace.add(buttonPanel, BorderLayout.SOUTH);
}
/**
* 初始化文本域
*/
private void initTextArea() {
JTextArea sourceCode = new JTextArea();
// 设置自动换行
sourceCode.setLineWrap(true);
// 设置自动换行时,以单词为单位换行
sourceCode.setWrapStyleWord(true);
try {
String URL = URLList.getCur();
String htmlCode = WebsiteHTMLGetter.getHTMLCode(URL);
// 获取源代码
sourceCode.setText(htmlCode);
} catch (IOException e) {
e.printStackTrace();
JOptionPane.showMessageDialog(null, "Could not read website source code, please try again or check the http is correct");
}
JScrollPane jScrollPane = new JScrollPane(sourceCode);
// 添加源代码显示区域
workspace.add(jScrollPane, BorderLayout.CENTER);
}
}
listener
FullScreenListener
监听器:实现窗体全屏功能
package com.xhf.keshe.listener;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class FullScreenListener implements ActionListener {
private final JFrame fullScreenFrame;
private boolean isFullScreen = false;
public FullScreenListener(JFrame fullScreenFrame) {
this.fullScreenFrame = fullScreenFrame;
}
/**
* Invoked when an action occurs.
*
* @param e
*/
@Override
public void actionPerformed(ActionEvent e) {
isFullScreen = !isFullScreen;
if (isFullScreen) {
fullScreenFrame.setExtendedState(JFrame.MAXIMIZED_BOTH);
} else {
fullScreenFrame.setExtendedState(JFrame.NORMAL);
}
}
}
GetSourceCodeListener
监听器:获取查看源代码
窗体
package com.xhf.keshe.listener;
import com.xhf.keshe.source.SourcePage;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class GetSourceCodeListener implements ActionListener {
/**
* Invoked when an action occurs.
*
* @param e
*/
@Override
public void actionPerformed(ActionEvent e) {
// 创建JFrame
SourcePage sourcePage = new SourcePage();
}
}
QuitListener
package com.xhf.keshe.listener;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* 执行退出逻辑
*/
public class QuitListener implements ActionListener {
private JFrame jFrame;
public QuitListener(JFrame frame) {
this.jFrame = frame;
}
/**
* Invoked when an action occurs.
*
* @param e
*/
@Override
public void actionPerformed(ActionEvent e) {
// 点击退出按钮时,关闭当前JFrame
jFrame.dispose();
}
}
RefreshWebSite
监听器:刷新网页
package com.xhf.keshe.listener;
import com.xhf.keshe.Main;
import com.xhf.keshe.utils.URLList;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class RefreshWebSite implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
Main.refreshWebSite(URLList.getCur());
}
}
SaveCodeListener
监听器:保存源代码
package com.xhf.keshe.listener;
import com.xhf.keshe.utils.URLList;
import com.xhf.keshe.utils.WebsiteHTMLGetter;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class SaveCodeListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
String URL = URLList.getCur();
String htmlCode = null;
try {
htmlCode = WebsiteHTMLGetter.getHTMLCode(URL);
} catch (IOException ioException) {
ioException.printStackTrace();
}
String fileName = removeUrlPrefix(URL);
System.out.println(fileName);
try {
String path = "E:\\B站视频创作\\Java课设\\网页浏览器开发\\代码\\src\\main\\resources\\sourceCode\\";
// 写文件
writeFile(path + fileName, htmlCode);
} catch (IOException exp) {
exp.printStackTrace();
}
}
/**
* 写文件
* @param filePath
* @param content
* @throws IOException
*/
private static void writeFile(String filePath, String content) throws IOException {
File file = new File(filePath);
System.out.println(filePath);
// 创建文件输出流
try (PrintWriter writer = new PrintWriter(new FileWriter(file))) {
// 写入文件内容
writer.print(content);
}
}
/**
* 去除网址前缀
* @param url
* @return
*/
private static String removeUrlPrefix(String url) {
// 去除"http://"前缀
if (url.startsWith("http://")) {
url = url.substring(7);
}
// 去除"https://"前缀
else if (url.startsWith("https://")) {
url = url.substring(8);
}
return url;
}
}
URLmoveListener
监听器:控制网页界面前进、后退
package com.xhf.keshe.listener;
import com.xhf.keshe.Main;
import com.xhf.keshe.utils.URLList;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class URLmoveListener implements ActionListener {
public static int FORWARD = 0;
public static int BACKEND = 1;
private int direction;
public URLmoveListener(int direction) {
this.direction = direction;
}
public void actionPerformed(ActionEvent e) {
boolean flag = false;
// 向前移动
if (direction == FORWARD) {
flag = URLList.right();
if (!flag) {
JOptionPane.showMessageDialog(null, "已经是最新的网页了");
}
}else if (direction == BACKEND) {
flag = URLList.left();
if (!flag) {
JOptionPane.showMessageDialog(null, "已经是最旧的网页了");
}
}
if (flag) {
Main.refreshWebSite(URLList.getCur());
}
}
}