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 是JFXPanelwebPanel.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);// 重新加载httphttp.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();// 初始化工具栏1initToolBar1();// 初始化工具栏2initToolBar2();}/*** 初始化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)")) {// Aitem.setMnemonic(KeyEvent.VK_A);// ctrl + sitem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK));item.addActionListener(new SaveCodeListener());}else if (Constant.menuList1[i].equals("退出(I)")) {// Iitem.setMnemonic(KeyEvent.VK_I);// ctrl + eitem.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 + zitem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, ActionEvent.CTRL_MASK));item.addActionListener(new URLmoveListener(URLmoveListener.FORWARD));}else if (Constant.menuList2[i].equals("后退")){// ctrl + ditem.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 + uitem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_U, ActionEvent.CTRL_MASK));item.addActionListener(new FullScreenListener(this));}else if (Constant.menuList3[i].equals("查看源码(C)")) {// citem.setMnemonic(KeyEvent.VK_C);// ctrl + citem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK));item.addActionListener(new GetSourceCodeListener());}else if (Constant.menuList3[i].equals("刷新")) {// ctrl + ritem.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*/@Overridepublic 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*/@Overridepublic void actionPerformed(ActionEvent e) {// 创建JFrameSourcePage 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*/@Overridepublic void actionPerformed(ActionEvent e) {// 点击退出按钮时,关闭当前JFramejFrame.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 {@Overridepublic 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 {@Overridepublic 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());}}
}