制作一個自己的 Java 編輯器
難度中等,適合 Java 基礎紮實,對 Java 核心 API 有所熟悉的同學學習
No1、制作GUI界面
一、實驗介紹
1.1 實驗內容
本節課程的主要內容是準備開發環境,建立項目並完成 GUI 界面的編程實現。
1.2 實驗知識點
Java Swing 編程
1.3 實驗環境
本實驗環境采用帶桌面的 Ubuntu Linux 環境,實驗中會用到的環境或軟件:
JDK1.7
Eclipse。
1.4 適合人群
本節課程難度較低,屬於初級課程,適合要想學習Java Swing 編程的同學學習。
1.5 代碼獲取
你可以在Xfce終端下通過下面命令將實驗的完整工程項目下載到實驗樓環境中,作為參照對比進行學習。
$ wget http://labfile.oss.aliyuncs.com/courses/287/MyEdit.tar.gz
二、項目文件結構
三、實驗步驟
這一節我們將開發GUI界面。
3.1 新建項目
首先請雙擊打開桌面上的 Eclipse ,等待它啟動完成後,在菜單 File 中點擊 New -> Java Project選項。 此處輸入圖片的描述
在彈出的窗口裏填寫項目的名稱 MyEdit,然後點擊 Finish 按鈕。
3.2 創建包和類
項目創建完成後,我們需要按照之前的項目結構來創建各個類。本項目一共有兩個類:
FileWindow:主要方法類,用作GUI界面以及邏輯功能的實現
Main:測試類
因此我們首先需要創建一個名為 com.hijackykun.myedit 的包。
請在創建好的項目目錄 src 文件夾上右鍵點擊,然後選擇 New -> Package。
在彈出的 New Java Package 對話框中填寫包名com.shiyanlou.myedit,並點擊 Finish 按鈕。
最後,在新建好的包下新建FileWindow和Main類。
3.3 GUI 界面的實現
GUI界面的效果圖如下:
界面的設計采用卡片布局(CardLayout),白色文本域為程序輸入區,粉紅色文本域為編譯結果顯示區,淺藍色文本域為程序運行結果區。點擊上方不同的功能按鈕顯示對應的文本域。
在FileWindow類中編寫實現 GUI 的代碼,相關的代碼如下,代碼的講解將會以註釋的形式進行,請在編寫代碼的同時留意相關的註釋。
註:以下代碼均未導入相關的包
public class FileWindow extends JFrame implements ActionListener, Runnable {
/*註意:因為實現了ActionListener 和Runnable接口,所以必須要實現這兩個接口的方法。這裏我們先把這兩個方法簡單實現以下。下節課將徹底完成這兩個方法。*/
Thread compiler = null;
Thread run_prom = null;
boolean bn = true;
CardLayout mycard; //聲明布局,以後會用到
File file_saved = null;
JButton button_input_txt, //按鈕的定義
button_compiler_text,
button_compiler,
button_run_prom,
button_see_doswin;
JPanel p = new JPanel();
JTextArea input_text = new JTextArea(); // 程序輸入區
JTextArea compiler_text = new JTextArea();// 編譯錯誤顯示區
JTextArea dos_out_text = new JTextArea();// 程序的輸出信息
JTextField input_file_name_text = new JTextField();
JTextField run_file_name_text = new JTextField();
public FileWindow() {
// TODO Auto-generated constructor stub
super("Java語言編譯器");
mycard = new CardLayout();
compiler=new Thread(this);
run_prom=new Thread(this);
button_input_txt=new JButton("程序輸入區(白色)");
button_compiler_text=new JButton("編譯結果區(粉紅色)");
button_see_doswin=new JButton("程序運行結果(淺藍色)");
button_compiler=new JButton("編譯程序");
button_run_prom=new JButton("運行程序");
p.setLayout(mycard);//設置卡片布局
p.add("input",input_text);//定義卡片名稱
p.add("compiler", compiler_text);
p.add("dos",dos_out_text);
add(p,"Center");
compiler_text.setBackground(Color.pink); //設置顏色
dos_out_text.setBackground(Color.cyan);
JPanel p1=new JPanel();
p1.setLayout(new GridLayout(3, 3)); //設置表格布局
//添加組件
p1.add(button_input_txt);
p1.add(button_compiler_text);
p1.add(button_see_doswin);
p1.add(new JLabel("輸入編譯文件名(.java):"));
p1.add(input_file_name_text);
p1.add(button_compiler);
p1.add(new JLabel("輸入應用程序主類名"));
p1.add(run_file_name_text);
p1.add(button_run_prom);
add(p1,"North");
//定義事件
button_input_txt.addActionListener(this);
button_compiler.addActionListener(this);
button_compiler_text.addActionListener(this);
button_run_prom.addActionListener(this);
button_see_doswin.addActionListener(this);
}
public void actionPerformed(ActionEvent e)
{
//實現方法
}
@Override
public void run() {
//實現方法
}
}
到此,我們的 GUI 界面就算做好了!
3.4 測試類的實現
下面,我們趕緊做個測試類,測試一下我們的界面。
Main.java:
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
FileWindow win=new FileWindow();
win.pack();
win.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
win.setBounds(200, 180,550,360);
win.setVisible(true);
}
}
界面和測試類就完成了。
四、實驗總結
在本節實驗中,我們完成了項目的創建以及 GUI 界面,下節實驗我們將完善本節的遺留問題,實現界面組件事件響應邏輯和Java文件的編輯、編譯及運行。主要包括兩個方法:
public void actionPerformed(ActionEvent e)
public void run()
No2、實現功能
一、實驗介紹
1.1 實驗內容
在上節實驗中我們完成了編輯器的界面,可是按鈕的響應功能並未完成,在本節實驗中我們將實現界面組件事件響應邏輯和Java文件的編輯、編譯及運行。主要包括兩個方法:
public void actionPerformed(ActionEvent e)
public void run()
1.2 實驗知識點
Java Swing 編程
IO 流操作
Runtime 類
Thread 的使用
1.3 實驗環境
本實驗環境采用帶桌面的 Ubuntu Linux 環境,實驗中會用到的環境或軟件:
JDK1.7
Eclipse。
1.4 適合人群
本節課程難度較難,屬於中級課程,適合對 Java 核心 API 有較深入理解的同學學習。
1.5 代碼獲取
你可以在Xfce終端下通過下面命令將實驗的完整工程項目下載到實驗樓環境中,作為參照對比進行學習。
$ wget http://labfile.oss.aliyuncs.com/courses/287/MyEdit.tar.gz
二、項目文件結構
三、實驗步驟
3.1 actionPerformed 方法的實現
首先咱們實現 public void actionPerformed(ActionEvent e) 這個方法。代碼中的註釋進行了詳細的講解。
public void actionPerformed(ActionEvent e)
{
if(e.getSource()==button_input_txt)
{ //顯示程序輸入區
mycard.show(p,"input");
}
else if(e.getSource()==button_compiler_text)
{ //顯示編譯結果顯示區
mycard.show(p,"compiler");
}
else if(e.getSource()==button_see_doswin)
{ //顯示程序運行結果區
mycard.show(p,"dos");
}
else if(e.getSource()==button_compiler)
{ //如果是編譯按鈕,執行編譯文件的方法
if(!(compiler.isAlive()))
{
compiler=new Thread(this);
}
try {
compiler.start();
} catch (Exception e2) {
// TODO: handle exception
e2.printStackTrace();
}
mycard.show(p,"compiler");
}
else if(e.getSource()==button_run_prom)
{ //如果是運行按鈕,執行運行文件的方法
if(!(run_prom.isAlive()))
{
run_prom=new Thread(this);
}
try {
run_prom.start();
} catch (Exception e2) {
// TODO: handle exception
e2.printStackTrace();
}
mycard.show(p,"dos");
}
}
以上的代碼就是通過比較來判斷需要處理哪些事件。
3.2 run 方法的實現
然後就剩一個 run() 方法,也是最重要的一個方法。在這個方法裏會判斷是編譯還是運行:
如果當前Thread是編譯,那麽會將程序輸入區中的代碼以.java文件的形式保存到項目的當前目錄下,並通過javac命令執行剛才保存的.java文件生成.class文件,編譯後的信息會輸出到編譯結果顯示區。
如果當前Thread是運行,那麽會通過java命令執行編譯生成的.class文件,並將程序結果顯示到程序運行結果區中。
public void run() {
//TODO Auto-generated method stub
if(Thread.currentThread()==compiler)
{
compiler_text.setText(null);
String temp=input_text.getText().trim();
byte [] buffer=temp.getBytes();
int b=buffer.length;
String file_name=null;
file_name=input_file_name_text.getText().trim();
try {
file_saved=new File(file_name);
FileOutputStream writefile=null;
writefile=new FileOutputStream(file_saved);
writefile.write(buffer, 0, b);
writefile.close();
} catch (Exception e) {
// TODO: handle exception
System.out.println("ERROR");
}
try {
//獲得該進程的錯誤流,才可以知道運行結果到底是失敗了還是成功。
Runtime rt=Runtime.getRuntime();
InputStream in=rt.exec("javac "+file_name).getErrorStream(); //通過Runtime調用javac命令。註意:“javac ”這個字符串是有一個空格的!!
BufferedInputStream bufIn=new BufferedInputStream(in);
byte[] shuzu=new byte[100];
int n=0;
boolean flag=true;
//輸入錯誤信息
while((n=bufIn.read(shuzu, 0,shuzu.length))!=-1)
{
String s=null;
s=new String(shuzu,0,n);
compiler_text.append(s);
if(s!=null)
{
flag=false;
}
}
//判斷是否編譯成功
if(flag)
{
compiler_text.append("Compile Succeed!");
}
} catch (Exception e) {
// TODO: handle exception
}
}
else if(Thread.currentThread()==run_prom)
{
//運行文件,並將結果輸出到dos_out_text
dos_out_text.setText(null);
try {
Runtime rt=Runtime.getRuntime();
String path=run_file_name_text.getText().trim();
Process stream=rt.exec("java "+path);//調用java命令
InputStream in=stream.getInputStream();
BufferedInputStream bisErr=new BufferedInputStream(stream.getErrorStream());
BufferedInputStream bisIn=new BufferedInputStream(in);
byte[] buf=new byte[150];
byte[] err_buf=new byte[150];
@SuppressWarnings("unused")
int m=0;
@SuppressWarnings("unused")
int i=0;
String s=null;
String err=null;
//打印編譯信息及錯誤信息
while((m=bisIn.read(buf, 0, 150))!=-1)
{
s=new String(buf,0,150);
dos_out_text.append(s);
}
while((i=bisErr.read(err_buf))!=-1)
{
err=new String(err_buf,0,150);
dos_out_text.append(err);
}
}
catch (Exception e) {
// TODO: handle exception
}
}
}
3.3 進行簡單測試
點擊編譯按鈕會出現錯誤信息,證明距離成功不遠了。
運行按鈕錯誤:
點擊按鈕在程序輸入區(白色),寫一個簡單的測試小程序吧!代碼如下:
class a
{
public static void main(String [] args)
{
System.out.println("Hello ShiYanLou");
}
}
接著在輸入編譯文件名(.java)後面的文本框裏填入與類名相同的.java文件,如a.java,點擊編譯程序。
如果程序沒有出錯,那麽編譯結果顯示如下:
在輸入應用程序主類名後面的文本框裏填入類名,如a,點擊運行程序。
程序的運行結果將會顯示在淺藍色的文本域中。
重新編輯的時候需要點擊按鈕在程序輸入區(白色),在白色文本域進行輸入。
三、實驗總結
至此,我們終於完成了整個程序,實現了編輯Java代碼、編譯和運行Java文件的功能。本次課程涉及的知識點比較復雜,特別是 Runtime 類和 Thread 的使用,希望同學們下來能夠對這些知識點進行鞏固。
五、課後習題
同學們下來考慮如何豐富其功能,例如 “代碼高亮”、“代碼自動補全” 等等。這些功能有的比較難,不一定要實現,但要勤於思考。
具體代碼:
FileWindow.java
package com.hijackykun.myedit; import java.awt.CardLayout; import java.awt.Color; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextArea; import javax.swing.JTextField; @SuppressWarnings("serial") //壓制信息,不會的同學可以不理會。 public class FileWindow extends JFrame implements ActionListener,Runnable{ /*註意:因為實現了ActionListener 和Runnable接口,所以必須要實現這兩個接口的方法。 * 這裏我們先把這兩個方法簡單實現以下。下節課將徹底完成這兩個方法。*/ Thread compiler = null; Thread run_prom = null; boolean bn = true; CardLayout mycard; //聲明布局,以後會用到 File file_saved = null; JButton button_input_txt, //按鈕的定義 button_compiler_text, button_compiler, button_run_prom, button_see_doswin; JPanel p = new JPanel(); JTextArea input_text = new JTextArea(); // 程序輸入區 JTextArea compiler_text = new JTextArea();// 編譯錯誤顯示區 JTextArea dos_out_text = new JTextArea();// 程序的輸出信息 JTextField input_file_name_text = new JTextField(); JTextField run_file_name_text = new JTextField(); public FileWindow() { // TODO Auto-generated constructor stub super("Java語言編譯器"); mycard = new CardLayout(); compiler=new Thread(this); run_prom=new Thread(this); button_input_txt=new JButton("程序輸入區(白色)"); button_compiler_text=new JButton("編譯結果區(粉紅色)"); button_see_doswin=new JButton("程序運行結果(淺藍色)"); button_compiler=new JButton("編譯程序"); button_run_prom=new JButton("運行程序"); p.setLayout(mycard);//設置卡片布局 p.add("input",input_text);//定義卡片名稱 p.add("compiler", compiler_text); p.add("dos",dos_out_text); add(p,"Center"); compiler_text.setBackground(Color.pink); //設置顏色 dos_out_text.setBackground(Color.cyan); JPanel p1=new JPanel(); p1.setLayout(new GridLayout(3, 3)); //設置表格布局 //添加組件 p1.add(button_input_txt); p1.add(button_compiler_text); p1.add(button_see_doswin); p1.add(new JLabel("輸入編譯文件名(.java):")); p1.add(input_file_name_text); p1.add(button_compiler); p1.add(new JLabel("輸入應用程序主類名")); p1.add(run_file_name_text); p1.add(button_run_prom); add(p1,"North"); //定義事件 button_input_txt.addActionListener(this); button_compiler.addActionListener(this); button_compiler_text.addActionListener(this); button_run_prom.addActionListener(this); button_see_doswin.addActionListener(this); } @Override public void actionPerformed(ActionEvent e) { // TODO Auto-generated method stub if(e.getSource()==button_input_txt) { //顯示程序輸入區 mycard.show(p,"input"); } else if(e.getSource()==button_compiler_text) { //顯示編譯結果顯示區 mycard.show(p,"compiler"); } else if(e.getSource()==button_see_doswin) { //顯示程序運行結果區 mycard.show(p,"dos"); } else if(e.getSource()==button_compiler) { //如果是編譯按鈕,執行編譯文件的方法 if(!(compiler.isAlive())) { compiler=new Thread(this); } try { compiler.start(); } catch (Exception e2) { // TODO: handle exception e2.printStackTrace(); } mycard.show(p,"compiler"); } else if(e.getSource()==button_run_prom) { //如果是運行按鈕,執行運行文件的方法 if(!(run_prom.isAlive())) { run_prom=new Thread(this); } try { run_prom.start(); } catch (Exception e2) { // TODO: handle exception e2.printStackTrace(); } mycard.show(p,"dos"); } } @Override public void run() { // TODO Auto-generated method stub if (Thread.currentThread() == compiler) { compiler_text.setText(null); String temp = input_text.getText().trim(); byte[] buffer = temp.getBytes(); int b = buffer.length; String file_name = null; file_name = input_file_name_text.getText().trim(); try { file_saved = new File(file_name); FileOutputStream writefile = null; writefile = new FileOutputStream(file_saved); writefile.write(buffer, 0, b); writefile.close(); } catch (Exception e) { // TODO: handle exception System.out.println("ERROR"); } try { // 獲得該進程的錯誤流,才可以知道運行結果到底是失敗了還是成功。 Runtime rt = Runtime.getRuntime(); InputStream in = rt.exec("javac " + file_name).getErrorStream(); // 通過Runtime調用javac命令。註意:“javac ”這個字符串是有一個空格的!! BufferedInputStream bufIn = new BufferedInputStream(in); byte[] shuzu = new byte[100]; int n = 0; boolean flag = true; // 輸入錯誤信息 while ((n = bufIn.read(shuzu, 0, shuzu.length)) != -1) { String s = null; s = new String(shuzu, 0, n); compiler_text.append(s); if (s != null) { flag = false; } } // 判斷是否編譯成功 if (flag) { compiler_text.append("Compile Succeed!"); } } catch (Exception e) { // TODO: handle exception } } else if (Thread.currentThread() == run_prom) { // 運行文件,並將結果輸出到dos_out_text dos_out_text.setText(null); try { Runtime rt = Runtime.getRuntime(); String path = run_file_name_text.getText().trim(); Process stream = rt.exec("java " + path);// 調用java命令 InputStream in = stream.getInputStream(); BufferedInputStream bisErr = new BufferedInputStream( stream.getErrorStream()); BufferedInputStream bisIn = new BufferedInputStream(in); byte[] buf = new byte[150]; byte[] err_buf = new byte[150]; @SuppressWarnings("unused") int m = 0; @SuppressWarnings("unused") int i = 0; String s = null; String err = null; // 打印編譯信息及錯誤信息 while ((m = bisIn.read(buf, 0, 150)) != -1) { s = new String(buf, 0, 150); dos_out_text.append(s); } while ((i = bisErr.read(err_buf)) != -1) { err = new String(err_buf, 0, 150); dos_out_text.append(err); } } catch (Exception e) { // TODO: handle exception } } } }View Code
Main.java
package com.hijackykun.myedit; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; public class Main { public static void main(String[] args) { FileWindow win =new FileWindow(); win.pack();//根據窗口裏面的布局及組件的preferredSize來確定frame的最佳大小 win.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { System.exit(0); } }); //setBounds(x,y,width,height); //x:組件在容器X軸上的起點 y:組件在容器Y軸上的起點 width:組件的長度 height:組件的高度 win.setBounds(200, 180, 550, 360); win.setVisible(true); } }View Code
參考來源:https://www.shiyanlou.com/courses/287
制作一個自己的 Java 編輯器