寫在前面:
前兩篇博客我們分別介紹了簡單java GUI的基本結構及事件監聽機制。這一次我們將介紹雙事件(多事件)監聽機制,並引入內部類。
1.設計任務
設計一個GUI,包含基本組件:按鈕(兩個),標籤(一個),隨機顏色圓-面板(一個),要求點擊其中一個按鈕可以改變標籤文字,點擊另一個按鈕可以改變圓的顏色,實現雙事件監聽。
2.任務分析
本任務的難度在於雙事件如何同時監聽。我們已經知道,要實現事件監聽,就必須實現ActionListener接口並具體實現actionPerformed方法。但是注意對於任何一個實現ActionListener接口的類而言,只能實現一個actionPerformed方法,那麼如何對兩個不同的按鈕實施監聽並且有不同的actionPerformed方法處理呢?我們使用內部類解決這個問題。內部類的形式如下所示:
class Outer {
int outer_int;
class Inner {
int inner_int;
}
使用內部類的一個好處是在內部類中,可以直接使用外部類中的屬性和方法。我們用以下代碼試圖實現本設計任務。
3.代碼Version1
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class newTwoButtons {
JFrame frame;
JLabel label;
public static void main(String[] args) {
newTwoButtons tb = new newTwoButtons();
tb.go();
}
public void go() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
label = new JLabel("I am waiting for you!");
JButton labelButton = new JButton("Change a label");
labelButton.addActionListener(new LabelListener());
JButton circleButton = new JButton("Change a circle");
circleButton.addActionListener(new CircleListener());
MyDrawPanel myPanel = new MyDrawPanel();
frame.getContentPane().add(BorderLayout.EAST, labelButton);
frame.getContentPane().add(BorderLayout.SOUTH, circleButton);
frame.getContentPane().add(BorderLayout.WEST, label);
frame.getContentPane().add(BorderLayout.CENTER, myPanel);
frame.setSize(300, 300);
frame.setVisible(true);
}
class LabelListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
label.setText("OhCh!");
}
}
class CircleListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
frame.repaint();
}
}
}
class MyDrawPanel extends JPanel {
public void paintComponent(Graphics g) { // this method is called every time the button is clicked
g.fillRect(0, 0, this.getWidth(), this.getHeight());
int red = (int) (Math.random() * 255);
int green = (int) (Math.random() * 255);
int blue = (int) (Math.random() * 255);
Color randomColor = new Color(red, green, blue);
g.setColor(randomColor); // Set random color
g.fillOval(70, 70, 100, 100); // Make a oval(circle)
}
}
4.結果測試
將窗口適當拉大以顯示所有組件,如下圖所示。
點擊“change a circle”按鈕,結果如下圖所示:
說明“change a circle”按鈕工作正常。
接下來點擊“change a label”按鈕,結果如下圖所示:
我們看到,屏幕左方的label文字改變了,說明“change a label”按鈕工作正常。不!等等!我們驚奇地發現圓形的顏色也改變了,可是我們並沒有點擊“change a circle”按鈕啊!
爲什麼會這樣呢?
5.問題在哪
其實,我們在嘗試拉動窗口調整大小時,圓形的顏色也會改變,附圖如下所示:
那麼這裏可以推斷出當panel組件大小發生變化時(比如點擊“change a label”按鈕導致panel左側區域展寬或者直接拉動窗口展寬),paintComponent函數就會執行。而我們希望的是,除了第一次生成圖形之外,當且僅當點擊“change a circle”按鈕,才執行paintComponent函數。
6.解決方案
由以上分析我們可以初步給出一個方案:設置flag,當flag爲false時不執行paintComponent函數,僅當其爲true時才執行。給出代碼如下所示:
7.代碼Version2
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class newTwoButtons {
JFrame frame;
JLabel label;
boolean flag = false;
public static void main(String[] args) {
newTwoButtons tb = new newTwoButtons();
tb.go();
}
public void go() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
label = new JLabel("I am waiting for you!");
JButton labelButton = new JButton("Change a label");
labelButton.addActionListener(new LabelListener());
JButton circleButton = new JButton("Change a circle");
circleButton.addActionListener(new CircleListener());
flag = true;
MyDrawPanel myPanel = new MyDrawPanel();
frame.getContentPane().add(BorderLayout.EAST, labelButton);
frame.getContentPane().add(BorderLayout.SOUTH, circleButton);
frame.getContentPane().add(BorderLayout.WEST, label);
frame.getContentPane().add(BorderLayout.CENTER, myPanel);
frame.setSize(300, 300);
frame.setVisible(true);
}
class LabelListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
label.setText("OhCh!");
}
}
class CircleListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
flag = true;
frame.repaint();
}
}
class MyDrawPanel extends JPanel {
public void paintComponent(Graphics g) { // this method is called every time the button is clicked
g.fillRect(0, 0, this.getWidth(), this.getHeight());
if (flag == true) {
int red = (int) (Math.random() * 255);
int green = (int) (Math.random() * 255);
int blue = (int) (Math.random() * 255);
Color randomColor = new Color(red, green, blue);
g.setColor(randomColor); // Set random color
g.fillOval(70, 70, 100, 100); // Make a oval(circle)
flag = false;
}
}
}
}
8.仍有問題?
測試發現,雖然現在只有點擊按鈕才能改變圓形的顏色,但是當改變窗口大小時,panel內圓形不顯示(即paintComponent函數只執行了一個語句),這也並不是我們想要的結果。
9.最終方案
爲了解決這個問題,我們的最終方案是使color成爲公共變量,這樣在擴展窗口時,依然執行paintComponent函數,但是繪圖的顏色與上一次沒有變化。代碼如下所示:
10.代碼Version3
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class newTwoButtons {
JFrame frame;
JLabel label;
boolean flag = false;
int red = 0, green = 0, blue = 0;
public static void main(String[] args) {
newTwoButtons tb = new newTwoButtons();
tb.go();
}
public void go() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
label = new JLabel("I am waiting for you!");
JButton labelButton = new JButton("Change a label");
labelButton.addActionListener(new LabelListener());
JButton circleButton = new JButton("Change a circle");
circleButton.addActionListener(new CircleListener());
flag = true;
MyDrawPanel myPanel = new MyDrawPanel();
frame.getContentPane().add(BorderLayout.EAST, labelButton);
frame.getContentPane().add(BorderLayout.SOUTH, circleButton);
frame.getContentPane().add(BorderLayout.WEST, label);
frame.getContentPane().add(BorderLayout.CENTER, myPanel);
frame.setSize(300, 300);
frame.setVisible(true);
}
class LabelListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
label.setText("OhCh!");
}
}
class CircleListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
flag = true;
frame.repaint();
}
}
class MyDrawPanel extends JPanel {
public void paintComponent(Graphics g) { // this method is called every time the button is clicked
g.fillRect(0, 0, this.getWidth(), this.getHeight());
if (flag == true) {
red = (int) (Math.random() * 255);
green = (int) (Math.random() * 255);
blue = (int) (Math.random() * 255);
Color randomColor = new Color(red, green, blue);
g.setColor(randomColor); // Set random color
g.fillOval(70, 70, 100, 100); // Make a oval(circle)
flag = false;
} else {
Color randomColor2 = new Color(red, green, blue);
g.setColor(randomColor2); // Set random color
g.fillOval(70, 70, 100, 100); // Make a oval(circle)
}
}
}
}