package
book.net;
import
java.awt.BorderLayout;
import
java.awt.event.ActionEvent;
import
java.awt.event.ActionListener;
import
java.awt.event.WindowAdapter;
import
java.awt.event.WindowEvent;
import
java.beans.PropertyChangeEvent;
import
java.beans.PropertyChangeListener;
import
java.io.File;
import
java.io.IOException;
import
java.net.MalformedURLException;
import
java.net.URL;
import
java.util.ArrayList;
import
javax.swing.JButton;
import
javax.swing.JEditorPane;
import
javax.swing.JFileChooser;
import
javax.swing.JFrame;
import
javax.swing.JLabel;
import
javax.swing.JMenu;
import
javax.swing.JMenuBar;
import
javax.swing.JMenuItem;
import
javax.swing.JOptionPane;
import
javax.swing.JScrollPane;
import
javax.swing.JTextField;
import
javax.swing.JToolBar;
import
javax.swing.event.HyperlinkEvent;
import
javax.swing.event.HyperlinkListener;
import
javax.swing.filechooser.FileFilter;
/**
* 實現一個簡單的Web瀏覽器,支持HTML和HTM頁面的顯示。使用了JEditorPane組件
**/
public
class
WebBrowser
extends
JFrame
implements
HyperlinkListener,
PropertyChangeListener {
/**下面是使用的Swing組件**/
// 顯示HTML的面板
JEditorPane textPane;
// 最底下的狀態欄
JLabel messageLine;
// 網址URL輸入欄
JTextField urlField;
// 文件選擇器,打開本地文件時用
JFileChooser fileChooser;
// 后退和前進 按鈕
JButton backButton;
JButton forwardButton;
// 保存歷史記錄的列表
java.util.List history =
new
ArrayList();
// 當前頁面的在歷史記錄列表中位置
int
currentHistoryPage = -
1
;
// 當歷史記錄超過MAX_HISTORY時,清除舊的歷史
public
static
final
int
MAX_HISTORY =
50
;
// 當前已經打開的瀏覽器窗口數
static
int
numBrowserWindows =
0
;
// 標識當所有瀏覽器窗口都被關閉時,是否退出應用程序
static
boolean
exitWhenLastWindowClosed =
false
;
// 默認的主頁
String home =
"http://www.google.com"
;
/**
* 構造函數
*/
public
WebBrowser() {
super
(
"WebBrowser"
);
// 新建顯示HTML的面板,並設置它不可編輯
textPane =
new
JEditorPane();
textPane.setEditable(
false
);
// 注冊事件處理器,用於超連接事件。
textPane.addHyperlinkListener(
this
);
// 注冊事件處理器,用於處理屬性改變事件。當頁面加載結束時,觸發該事件
textPane.addPropertyChangeListener(
this
);
// 將HTML顯示面板放入主窗口,居中顯示
this
.getContentPane().add(
new
JScrollPane(textPane),
BorderLayout.CENTER);
// 創建狀態欄標簽,並放在主窗口底部
messageLine =
new
JLabel(
" "
);
this
.getContentPane().add(messageLine, BorderLayout.SOUTH);
// 初始化菜單和工具欄
this
.initMenu();
this
.initToolbar();
// 將當前打開窗口數增加1
WebBrowser.numBrowserWindows++;
// 當關閉窗口時,調用close方法處理
this
.addWindowListener(
new
WindowAdapter() {
public
void
windowClosing(WindowEvent e) {
close();
}
});
}
/**
* 初始化菜單欄
*/
private
void
initMenu(){
// 文件菜單,下面有四個菜單項:新建、打開、關閉窗口、退出
JMenu fileMenu =
new
JMenu(
"文件"
);
fileMenu.setMnemonic(
'F'
);
JMenuItem newMenuItem =
new
JMenuItem(
"新建"
);
newMenuItem.setMnemonic(
'N'
);
// 當“新建”時打開一個瀏覽器窗口
newMenuItem.addActionListener(
new
ActionListener() {
public
void
actionPerformed(ActionEvent e) {
newBrowser();
}
});
JMenuItem openMenuItem =
new
JMenuItem(
"打開"
);
openMenuItem.setMnemonic(
'O'
);
// 當“打開”是打開文件選擇器,選擇待打開的文件
openMenuItem.addActionListener(
new
ActionListener() {
public
void
actionPerformed(ActionEvent e) {
openLocalPage();
}
});
JMenuItem closeMenuItem =
new
JMenuItem(
"關閉窗口"
);
closeMenuItem.setMnemonic(
'C'
);
// 當“關閉窗口”時關閉當前窗口
closeMenuItem.addActionListener(
new
ActionListener() {
public
void
actionPerformed(ActionEvent e) {
close();
}
});
JMenuItem exitMenuItem =
new
JMenuItem(
"退出"
);
exitMenuItem.setMnemonic(
'E'
);
// 當“退出”時退出應用程序
exitMenuItem.addActionListener(
new
ActionListener() {
public
void
actionPerformed(ActionEvent e) {
exit();
}
});
fileMenu.add(newMenuItem);
fileMenu.add(openMenuItem);
fileMenu.add(closeMenuItem);
fileMenu.add(exitMenuItem);
//幫助菜單,就一個菜單項:關於
JMenu helpMenu =
new
JMenu(
"幫助"
);
fileMenu.setMnemonic(
'H'
);
JMenuItem aboutMenuItem =
new
JMenuItem(
"關於"
);
aboutMenuItem.setMnemonic(
'A'
);
helpMenu.add(aboutMenuItem);
JMenuBar menuBar =
new
JMenuBar();
menuBar.add(fileMenu);
menuBar.add(helpMenu);
// 設置菜單欄到主窗口
this
.setJMenuBar(menuBar);
}
/**
* 初始化工具欄
*/
private
void
initToolbar(){
// 后退按鈕,退到前一頁面。初始情況下該按鈕不可用
backButton =
new
JButton(
"后退"
);
backButton.setEnabled(
false
);
backButton.addActionListener(
new
ActionListener() {
public
void
actionPerformed(ActionEvent e) {
back();
}
});
// 前進按鈕,進到前一頁面。初始情況下該按鈕不可用
forwardButton =
new
JButton(
"前進"
);
forwardButton.setEnabled(
false
);
forwardButton.addActionListener(
new
ActionListener() {
public
void
actionPerformed(ActionEvent e) {
forward();
}
});
// 前進按鈕,進到前一頁面。初始情況下該按鈕不可用
JButton refreshButton =
new
JButton(
"刷新"
);
refreshButton.addActionListener(
new
ActionListener() {
public
void
actionPerformed(ActionEvent e) {
reload();
}
});
// 主頁按鈕,打開主頁
JButton homeButton =
new
JButton(
"主頁"
);
homeButton.addActionListener(
new
ActionListener() {
public
void
actionPerformed(ActionEvent e) {
home();
}
});
JToolBar toolbar =
new
JToolBar();
toolbar.add(backButton);
toolbar.add(forwardButton);
toolbar.add(refreshButton);
toolbar.add(homeButton);
// 輸入網址的文本框
urlField =
new
JTextField();
// 當用戶輸入網址、按下回車鍵時,觸發該事件
urlField.addActionListener(
new
ActionListener() {
public
void
actionPerformed(ActionEvent e) {
displayPage(urlField.getText());
}
});
// 地址標簽
toolbar.add(
new
JLabel(
" 地址:"
));
toolbar.add(urlField);
// 將工具欄放在主窗口的北部
this
.getContentPane().add(toolbar, BorderLayout.NORTH);
}
/**
* 設置瀏覽器是否在所有窗口都關閉時退出
* @param b
*/
public
static
void
setExitWhenLastWindowClosed(
boolean
b) {
exitWhenLastWindowClosed = b;
}
/**
* 設置主頁
* @param home 新主頁
*/
public
void
setHome(String home) {
this
.home = home;
}
/**
* 獲取主頁
*/
public
String getHome() {
return
home;
}
/**
* 訪問網址URL
*/
private
boolean
visit(URL url) {
try
{
String href = url.toString();
// 啟動動畫,當網頁被加載完成時,觸發propertyChanged事件,動畫停止。
startAnimation(
"加載 "
+ href +
"..."
);
// 設置顯示HTML面板的page屬性為待訪問的URL,
// 在這個方法里,將打開URL,將URL的流顯示在textPane中。
// 當完全打開后,該方法才結束。
textPane.setPage(url);
// 頁面打開后,將瀏覽器窗口的標題設為URL
this
.setTitle(href);
// 網址輸入框的內容也設置為URL
urlField.setText(href);
return
true
;
}
catch
(IOException ex) {
// 停止動畫
stopAnimation();
// 狀態欄中顯示錯誤信息
messageLine.setText(
"不能打開頁面:"
+ ex.getMessage());
return
false
;
}
}
/**
* 瀏覽器打開URL指定的頁面,如果成功,將URL放入歷史列表中
*/
public
void
displayPage(URL url) {
// 嘗試訪問頁面
if
(visit(url)) {
// 如果成功,則將URL放入歷史列表中。
history.add(url);
int
numentries = history.size();
if
(numentries > MAX_HISTORY+
10
) {
history = history.subList(numentries-MAX_HISTORY, numentries);
numentries = MAX_HISTORY;
}
// 將當前頁面下標置為numentries-1
currentHistoryPage = numentries -
1
;
// 如果當前頁面不是第一個頁面,則可以后退,允許點擊后退按鈕。
if
(currentHistoryPage >
0
){
backButton.setEnabled(
true
);
}
}
}
/**
* 瀏覽器打開字符串指定的頁面
* @param href 網址
*/
public
void
displayPage(String href) {
try
{
// 默認為HTTP協議
if
(!href.startsWith(
"http://"
)){
href =
"http://"
+ href;
}
displayPage(
new
URL(href));
}
catch
(MalformedURLException ex) {
messageLine.setText(
"錯誤的網址: "
+ href);
}
}
/**
* 打開本地文件
*/
public
void
openLocalPage() {
// 使用“懶創建”模式,當需要時,才創建文件選擇器。
if
(fileChooser ==
null
) {
fileChooser =
new
JFileChooser();
// 使用文件過濾器限制只能夠HTML和HTM文件
FileFilter filter =
new
FileFilter() {
public
boolean
accept(File f) {
String fn = f.getName();
if
(fn.endsWith(
".html"
) || fn.endsWith(
".htm"
)){
return
true
;
}
else
{
return
false
;
}
}
public
String getDescription() {
return
"HTML Files"
;
}
};
fileChooser.setFileFilter(filter);
// 只允許選擇HTML和HTM文件
fileChooser.addChoosableFileFilter(filter);
}
// 打開文件選擇器
int
result = fileChooser.showOpenDialog(
this
);
// 如果確定打開文件,則在當前窗口中打開選擇的文件
if
(result == JFileChooser.APPROVE_OPTION) {
File selectedFile = fileChooser.getSelectedFile( );
try
{
displayPage(selectedFile.toURL());
}
catch
(MalformedURLException e) {
e.printStackTrace();
}
}
}
/**
* 后退,回到前一頁
*/
public
void
back() {
if
(currentHistoryPage >
0
){
// 訪問前一頁
visit((URL)history.get(--currentHistoryPage));
}
// 如果當前頁面下標大於0,允許后退
backButton.setEnabled((currentHistoryPage >
0
));
// 如果當前頁面下標不是最后,允許前進
forwardButton.setEnabled((currentHistoryPage < history.size()-
1
));
}
/**
* 前進,回到后一頁
*/
public
void
forward() {
if
(currentHistoryPage < history.size( )-
1
){
visit((URL)history.get(++currentHistoryPage));
}
// 如果當前頁面下標大於0,允許后退
backButton.setEnabled((currentHistoryPage >
0
));
// 如果當前頁面下標不是最后,允許前進
forwardButton.setEnabled((currentHistoryPage < history.size()-
1
));
}
/**
* 重新加載當前頁面
*/
public
void
reload() {
if
(currentHistoryPage != -
1
) {
// 先顯示為空白頁
textPane.setDocument(
new
javax.swing.text.html.HTMLDocument());
// 再訪問當前頁
visit((URL)history.get(currentHistoryPage));
}
}
/**
* 顯示主頁
*/
public
void
home() {
displayPage(getHome());
}
/**
* 打開新的瀏覽器窗口
*/
public
void
newBrowser() {
WebBrowser b =
new
WebBrowser();
// 新窗口大小和當前窗口一樣大
b.setSize(
this
.getWidth(),
this
.getHeight());
b.setVisible(
true
);
}
/**
* 關閉當前窗口,如果所有窗口都關閉,則根據exitWhenLastWindowClosed屬性,
* 判斷是否退出應用程序
*/
public
void
close() {
// 先隱藏當前窗口,銷毀窗口中的組件。
this
.setVisible(
false
);
this
.dispose();
// 將當前打開窗口數減1。
// 如果所有窗口都已關閉,而且exitWhenLastWindowClosed為真,則退出
// 這里采用了synchronized關鍵字,保證線程安全
synchronized
(WebBrowser.
class
) {
WebBrowser.numBrowserWindows--;
if
((numBrowserWindows==
0
) && exitWhenLastWindowClosed){
System.exit(
0
);
}
}
}
/**
* 退出應用程序
*/
public
void
exit() {
// 彈出對話框,請求確認,如果確認退出,則退出應用程序
if
((JOptionPane.showConfirmDialog(
this
,
"你確定退出Web瀏覽器?"
,
"退出"
,
JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)){
System.exit(
0
);
}
}
/**
* 實現HyperlinkListener接口。處理超連接事件
*/
public
void
hyperlinkUpdate(HyperlinkEvent e) {
// 獲取事件類型
HyperlinkEvent.EventType type = e.getEventType();
// 如果是點擊了一個超連接,則顯示被點擊的連接
if
(type == HyperlinkEvent.EventType.ACTIVATED) {
displayPage(e.getURL());
}
// 如果是鼠標移動到超連接上,則在狀態欄中顯示超連接的網址
else
if
(type == HyperlinkEvent.EventType.ENTERED) {
messageLine.setText(e.getURL().toString());
}
// 如果是鼠標離開了超連接,則狀態欄顯示為空
else
if
(type == HyperlinkEvent.EventType.EXITED) {
messageLine.setText(
" "
);
}
}
/**
* 實現PropertyChangeListener接口。處理屬性改變事件。
* 顯示HTML面板textPane的屬性改變時,由該方法處理。
* 當textPane調用完setPage方法時,page屬性便改變了。
*/
public
void
propertyChange(PropertyChangeEvent e) {
if
(e.getPropertyName().equals(
"page"
)) {
// 頁面加載完畢時,textPane的page屬性發生改變,此時停止動畫。
stopAnimation();
}
}
// 動畫消息,顯示在最底下狀態欄標簽上,用於反饋瀏覽器的狀態
String animationMessage;
// 動畫當前的幀的索引
int
animationFrame =
0
;
// 動畫所用到的幀,是一些字符。
String[] animationFrames =
new
String[] {
"-"
,
"\\"
,
"|"
,
"/"
,
"-"
,
"\\"
,
"|"
,
"/"
,
","
,
"."
,
"o"
,
"0"
,
"O"
,
"#"
,
"*"
,
"+"
};
/**
* 新建一個Swing的定時器,每個125毫秒更新一次狀態欄標簽的文本
*/
javax.swing.Timer animator =
new
javax.swing.Timer(
125
,
new
ActionListener() {
public
void
actionPerformed(ActionEvent e) {
animate();
}
});
/**
* 顯示動畫的當前幀到狀態欄標簽上,並將幀索引后移
*/
private
void
animate() {
String frame = animationFrames[animationFrame++];
messageLine.setText(animationMessage +
" "
+ frame);
animationFrame = animationFrame % animationFrames.length;
}
/**
* 啟動動畫
*/
private
void
startAnimation(String msg) {
animationMessage = msg;
animationFrame =
0
;
// 啟動定時器
animator.start();
}
/**
* 停止動畫
*/
private
void
stopAnimation() {
// 停止定時器
animator.stop();
messageLine.setText(
" "
);
}
public
static
void
main(String[] args)
throws
IOException {
// 設置瀏覽器,當所有瀏覽器窗口都被關閉時,退出應用程序
WebBrowser.setExitWhenLastWindowClosed(
true
);
// 創建一個瀏覽器窗口
WebBrowser browser =
new
WebBrowser();
// 設置瀏覽器窗口的默認大小
browser.setSize(
800
,
600
);
// 顯示窗口
browser.setVisible(
true
);
// 打開主頁
browser.displayPage(browser.getHome());
}
}
