Java原生的GUI设计: Swing AWT
1. 特点:
- Java的GUI界面不美观。
- 需要JRE环境。
- 它是MVC的基础,可以写出自己想要的小工具。
- 可以了解MVC架构,了解监听。
2. AWT
-
包含很多类和接口,属于GUI编程。
-
元素: 窗口,按钮,文本框。
-
java.awt: Component,Button,TextArea,Label.
Container, Window, Panel,Frame,Dialog,Applet
java
Frame : 相当于Delphi中的 Form可以单独存在。
.setVisible(true);
.setBounds(x,y,w,h);
.setBackground(color);
Panel: 不可以单独存在。
.setBounds(x,y,w,h);
.setBackground(color);
frame.add(panel); //设置容器。
一个简单的画矩形和画圆程序
java
import java.awt.*;
public class Demo1 {
public static void main(String[] args) {
new MyPaint().loadFrame();
}
}
class MyPaint extends Frame {
public void loadFrame(){
setBounds(400,200,800,600);
setVisible(true);
}
@Override
public void paint(Graphics g) {
//super.paint(g); //这个应该相当于Delphi中的herited;
g.setColor(Color.red);
g.drawOval(100,100,100,100);
g.fillOval(100,100,100,100);
g.setColor(Color.blue);
g.fillRect(200,200,400,300);
}
public MyPaint() {
}
}
3. 布局管理器:Layout
-
流式布局。 类似delphi的AlCenter之类的.就是所有控件在容器中的排列对齐模式。
常用有:FlowLayout.LEFT 和 FlowLayout.CENTER(默认)
-
东西南北中布局。BorderLayout,直译是边界布局,类似DelphiRX开发界面,需要设置5个
方向上的控件。程序运行时,各个方向上的控件都会自动铺满的效果。
在Frame中布局的时候,可以直接用 frame.add(childComp,BorderLayout.WEST)来指定布局,
但是在Panel中布局的时候,必须创建的时候new Panel(BorderLayout()); 否则直接指定失效。
-
表格布局。GridLayout( rows,cols); 表格布局中,控件排版的顺序是先排满行,再往列(下)延伸。
如果控件的总数超过表格设置,则会自动增加列来排多余的控件,但总体顺序还是满行再下延列。
下面是一个比较复杂的布局例子
这个例子实现了在界面上布局10按钮的模式,上部4个,下部6个。整个模式为:
|------|------|-------|------|
| btn1 | btn5 || btn2 |
| btn1 | btn6 || btn2 |
| btn3 | btn7 | btn8 | btn4 |
| btn3 | btn9 | btn10 | btn4 |
设计思路是先对整体Frame进行表格布局,分成上下部分; 然后上下部分分别都使用:东南西北中布局,不过只用西,中,东,其中"中"是panel。最后再在中部panel里再次使用表格布局。核心语句:setLayout和add(xxx, BorderLayout.yyy) ```java import java.awt.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.event.WindowListener;
public class HomeWork {
public static void main(String\[\] args) {
Frame frm = new Frame("家庭作业");
Panel pnl1 = new Panel(new BorderLayout());
Panel pnl2 = new Panel(new BorderLayout());
Panel pnl3 = new Panel();
Panel pnl4 = new Panel();
pnl1.setBackground(Color.blue);
pnl2.setBackground(Color.yellow);
pnl3.setBackground(Color.lightGray);
pnl4.setBackground(Color.pink);
//试试表格布局
frm.setLayout(new GridLayout(2,1));
frm.add(pnl1);
frm.add(pnl2);
Button btn1 = new Button("btn1");
Button btn2 = new Button("btn2");
Button btn3 = new Button("btn3");
Button btn4 = new Button("btn4");
Button btn5 = new Button("btn5");
Button btn6 = new Button("btn6");
Button btn7 = new Button("btn7");
Button btn8 = new Button("btn8");
Button btn9 = new Button("btn9");
Button btn10 = new Button("btn10");
//在第一个panel上布局东中西
pnl1.add(btn1,BorderLayout.WEST); //边界布局失败
pnl1.add(pnl3,BorderLayout.CENTER);
pnl1.add(btn2,BorderLayout.EAST);
//在第二个panel上布局东中西
pnl2.add(btn3,BorderLayout.WEST);
pnl2.add(pnl4,BorderLayout.CENTER);
pnl2.add(btn4,BorderLayout.EAST);
//在第三个panel上进行表格布局
pnl3.setLayout(new GridLayout(2,1));
pnl3.add(btn5);
pnl3.add(btn6);
//在第四个panel上布局2x2表格
pnl4.setLayout(new GridLayout(2,2));
pnl4.add(btn7);
pnl4.add(btn8);
pnl4.add(btn9);
pnl4.add(btn10);
frm.setBounds(500,200,800,600);
<//frm.pack>();
frm.setVisible(true);
frm.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}
### 4. 监听事件:来自java.awt.event
相当于给特定Form增加一个OnClose时间。java窗体点关闭时,默认无动作的。
```java
frame.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
//super.windowClosing(e);
System.exit(0); //这里在初始化触发器时,就重写了事件内容。
}
}) //这里才是窗口监听器参数结束。
关闭事件可以分开代码写方法:
java
windowsClose(frm);
...
private static void windowsClose(Frame afrm){
afrm.addWindowListener(new WindowAdapter(){
@Override
public void windowsClosing(WindowEvent e){
System.exit(0);
}
});
} //这样写也行,跟下面的ActionListener写法还不一样,这里没有用到WindowAdapter的变量。
监听,就相当于Delphi的事件代码,从接口接收一个消息。WindowsAdapter 是一个抽象类, 继承三个接口:
public abstract class WindowAdapter implements WindowListener, WindowStateListener, WindowFocusListener
也就是说,通常的窗体触发事件,来自三个方向:窗体消息,窗体状态信息,窗体焦点信息。
按钮的监听:
java
button.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e){
System.out.println("Button Click");
}
})
当然,为了层次分明,也可以分开写:
java
MyActionListener myAct = new MyActionListener();
button.addActionListener(myAct);
....
class MyActionListener implements ActionListener{ //注意,这里是用类来继承一个接口。
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button Click");
if (e.getActionCommand().equals(AStr){ //这里AStr默认为按钮getCaption. //执行某个按钮别点击的事件;我们可以提前用btn.setActionCommand(AStr);指定按钮信息
}
}
}
以上两种效果是一样的。ActionEvent 和ActionListener都是来自 java.awt.event。ActionEvent是类,ActionListener是接口。由于接口需要实例化方法,所以直接new的话,就必须带方法体。ActionListener继承于另一个接口: java.util.EventListener.目前只具备一个方法: public void actionPerformed(ActionEvent e);
5. 了解文本编辑器:TextField
文本编辑器同按钮一样,通过接口ActionListener来实现事件,由回车触发。
java
class MyAct implements ActionListener{
@Override
public void actionPerformed(ActionEvent e){
//这里注意,用强行转换操作了e.getSource(),我们可以认为,它的意义就是Delphi中的Sender
TextField field=(TextField)e.getSource();
System.out.println(field.getText());
field.setText("");
}
}
textField.setEchoChar('*'); //可以通过这个方法实现掩码输入。
textField.setText(Astr); //直接设置文本。
构造方法的参数是文本容纳的字符数: new TextField( N );
6. 简易计算器的实现,以及组合+内部类的了解。
OOP原则,能使用组合,就不使用继承。
继承语法:
class A extends B{
}
组合语法:
class A {
public B b;
//这里所有B类的方法由b带出。
}
计算器的设计源码
这里保留了原本的独立监听事件MyCalculaterListener,并改为使用内部类InnerListener。这使得几个TextField参数传输数据更方便。
java
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class TestCalc {
public static void main(String[] args) {
new MyCalculator().loadFrame();
}
}
class MyCalculator extends Frame {
TextField fd1,fd2,fd3;
public void loadFrame(){
fd1 =new TextField(10);
fd2 =new TextField(10);
fd3 =new TextField(10);
Button btn1 = new Button("=");
//btn1.addActionListener(new MyCalculaterListener(fd1,fd2,fd3));原有的非常繁琐。
btn1.addActionListener(new InnerListener());
Label lbl1= new Label("+");
setLayout(new FlowLayout());
add(fd1);
add(lbl1);
add(fd2);
add(btn1);
add(fd3);
pack();
setVisible(true);
}
public MyCalculator() {
}
//用内部类实现监听
private class InnerListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
int n1 = 0,n2=0;
try {
n1 = Integer.parseInt(fd1.getText());
n2 = Integer.parseInt(fd2.getText());
}catch (NumberFormatException excp){
System.out.println("非法字符"+e);
}catch (Exception excp){
throw excp;
}finally {
}
fd3.setText(" "+(n1+n2));
fd1.setText("");
fd2.setText("");
}
public InnerListener() {
super();
//this.mycalc=mycalc;
}
}
}
class MyCalculaterListener implements ActionListener{
//private TextField fd1,fd2,fd3;
MyCalculator mycalc;
@Override
public void actionPerformed(ActionEvent e) {
int n1 = 0,n2=0;
try {
n1 = Integer.parseInt(mycalc.fd1.getText());
n2 = Integer.parseInt(mycalc.fd2.getText());
}catch (NumberFormatException excp){
System.out.println("非法字符"+e);
}catch (Exception excp){
throw excp;
}finally {
}
mycalc.fd3.setText(" "+(n1+n2));
mycalc.fd1.setText("");
mycalc.fd2.setText("");
}
//用构造方法传参数
//public MyCalculaterListener(TextField fd1, TextField fd2,TextField fd3) {
public MyCalculaterListener(MyCalculator mycalc) {
this.mycalc=mycalc;
//this.fd1=mycalc.fd1; this.fd2=mycalc.fd2; this.fd3=mycalc.fd3;
}
}
- 从计算器的设计上我们能感悟到的东西:
- 对于重要的功能,应该独立建立类来处理。
2.对于对象的属性,我们需要定义在类根下。然后初始化的过程可以单独定义一个方法。
3.对于事件的处理,我们应该尽量使用内部类 private class来处理,这样方便在事件中对对象的各种属性方法进行调用。
4.对于方法参数,尽量使用引用类型参数,可以避免重复开辟空间。
5.对于基础数据类型变量,系统要求必须初始化才能参与加减乘除。但是try{..}内容中的初始化,会被编译器忽略。
6.main方法中尽量少写东西,只写必要的类和方法调用即可,没必要在main方法中初始化一些变量。
- 对于重要的功能,应该独立建立类来处理。
7. 重视内部类的使用:
-
java内部类是定义在java类内部的嵌套类,主要分为:成员内部类,静态嵌套类,方法内部类,匿名内部类。4种。
-
核心特征包括编译后生成独立的.class文件,格式为:外部类名 $内部类名。 以及直接访问外部类成员的权限
-
差异,无论外部类成员是否private。内部类是为多重继承问题提供补充方案,减少代码冗余。
-
由于静态,只能位于类的外部一级,所以内部类中是不可能拥有静态数据或者静态内部类。
-
成员内部类:依附于外部类的实例,可访问外部类所有成员。
-
静态嵌套类独立存在仅能访问静态成员。这个属于顶级嵌套类技术,与外部类无实例关联。
-
方法内部类受限于局部变量的final修饰和作用域。定义时,会出现在一个方法内。只能在该方法内实例化,而
不可以在此方法外对其实例化。 方法内部类对象,不能使用内部类所在方法的非final变量(权限很小)。
原因:方法局部变量是在栈上,方法周期结束栈数据删除。但方法结束后,方法内部类对象依然可能存在,
因为生命周期的问题,所以内部类对象无法访问外部非final成员。
-
匿名内部类,无显示类名,常用于接口实现或继承扩展。
-
8 . java.awt包中的控件和常用类:
Button; Canvas; Checkbox; CheckboxGroup, CheckboxMenuItem, Choice, Color, ColorPaintContext(富文本),Component, Container, Cursor, Desktop, Dialog, Dimension, FileDalog, Font, Frame, Graphics, Image,Label, Menu, MenuBar, MenuItem, Panel, Point, Queue, PopupMenu, Rectangle, Scrollbar, TextArea(Memo),TextField
比较新的: FontMetrics 字体信息对象,含坡度(ascent/descent),行间距(leading)等信息,用于画布绘制文字。
另外,java.awt.event 包内,也包含绝大部分事件定义。 其中Event 和 Adapter结尾的一般是继承接口的抽象方法,而Listener结尾的,全部都是接口。
java
常用的组件 以及支持的事件
Component Type | Events supported by this component
Adjustable AdjustmentEvent
Applet ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
Button ActionEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
Canvas FocusEvent, KeyEvent, MouseEvent, ComponentEvent
Checkbox itemEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
CheckboxMenultem ActionEvent, ItemEvent
Choice ItemEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
Component FocusEvent, KeyEvent, MouseEvent, ComponentEvent
Container ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
Dialog ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
FileDialog ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
Frame ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
Label FocusEvent, KeyEvent, MouseEvent, ComponentEvent
List ActionEvent, FocusEvent, KeyEvent, MouseEvent, ItemEvent, ComponentEvent
Menu ActionEvent
MenuItem ActionEvent
Panel ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
PopupMenu ActionEvent
Scrollbar AdjustmentEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
ScrollPane ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
TextArea TextEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
TextComponent TextEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
TextField ActionEvent, TextEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
Window ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
-
了解组件的事件,只需要取得对应事件的名字,然后把Event改成Listener,就可以在内部类里实现对应的 接收器接口。
-
对象只需要使用add前缀+接收器,就可以来增加对应的事件,参数看情况选择Listener系列接口,或者对应
触发器Adapter,这是一个抽象类。在多于1个的事件中,选择对应触发器(或者叫适配器),可以简化代码。
我们可以了解一下,哪些接收器(监听器),包含多个方法。如下表:
java
Listener interface/adapter | Methods in interface |
ActionListener actionPerformed(ActionEvent)
AdjustmentListener adjustmentValueChanged(AdjustmentEvent)
ComponentListener componentHidden(ComponentEvent)
&ComponentAdapter componentShown(ComponentEvent)
componetMoved(ComponetEvent)
componetResized(ComponentEvent)
ContainerListener componentAdded(ContainerEvent)
&ContainerAdapter componentRemoved(ContainerEvent)
FocusListener focusGained(FocusEvent)
&FocusAdapter focusLost(FocusEvent)
KeyListener keyPressed(KeyEvent)
&KeyAdapter keyRelease(KeyEvent)
keyTyped(KeyEvent)
MouseListener mouseClicked(MouseEvent)
&MouseAdapter mouseEnterd(MouseEvent)
mouseExited(MouseEvent)
mousePressed(MouseEvent)
mouseReleased(MouseEvent)
MouseMotionListener mouseDragged(MouseEvent)
@MouseMotionAdapter mouseMoved(MouseEvent)
WindowsListener windowOpend(WindowsEvent)
@WindowsAdapter windowClosing(WindowEvent)
windowClosed(WindowEvent)
windowActivated(WindowEvent)
windowDeactivated(WindowEvent)
windowIconified(WindowEvent)
windowDeiconified(WindowEvent)
ItemListener itemStateChanged(ItemEvent)
TextListener textValueChanged(TextEvent)
9. 代码中吸取营养:GoodTechnique
java
package com.Murphy;
/*
这个程序的作用是捕获各个区域的鼠标坐标
*/
// GoodTechnique.java
// Your first choice when overriding components
// should be to install listeners. The code is
// much safer, more modular and maintainable.
import java.awt.*;
import java.awt.event.*;
class Display {
public static final int //定义消息类型
EVENT = 0, COMPONENT = 1,
MOUSE = 2, MOUSE_MOVE = 3,
FOCUS = 4, KEY = 5, ACTION = 6,
LAST = 7;
public String[] evnt; //事件的本质,其实就是一个String,这里定义的是事件组,下标对应消息类型存放一组消息。
Display() {
evnt = new String[LAST];
for(int i = 0; i < LAST; i++)
evnt[i] = new String();
}
//这里是精髓,用一个方法,输出7行信息,对应每一种事件。
public void show(Graphics g) {
for(int i = 0; i < LAST; i++)
g.drawString(evnt[i], 0, 10 * i + 10); //直接用画布输出字串
}
}
//定义一个可以接收鼠标位置信息的panel类
class EnabledPanel extends Panel {
Color c;
int id;
Display display = new Display(); //这里利用Display类,在画布中显示事件信息。
public EnabledPanel(int i, Color mc) { //用panel的ID和底色来初始化
id = i;
c = mc; //颜色放入成员变量
setLayout(new BorderLayout());
add(new MyButton(), BorderLayout.SOUTH);
addComponentListener(new CL());
addFocusListener(new FL());
addKeyListener(new KL());
addMouseListener(new ML());
addMouseMotionListener(new MML());
}
// To eliminate flicker:
@Override
public void update(Graphics g) { //更新画布
paint(g);
}
@Override
public void paint(Graphics g) { //画布作图:颜色,宽高,填充区域,带事件字符串显示区域
g.setColor(c);
Dimension s = getSize();
g.fillRect(0, 0, s.width, s.height);
g.setColor(Color.black);
display.show(g);
}
// Don't need to enable anything for this:
@Override
public void processEvent(AWTEvent e) {
display.evnt[Display.EVENT] = e.toString();
repaint();
super.processEvent(e); //保持父类消息事件
}
//实现组件监听,得到各种事件的消息串
class CL implements ComponentListener {
public void componentMoved(ComponentEvent e) {
display.evnt[Display.COMPONENT] =
"Component moved";
repaint();
}
public void
componentResized(ComponentEvent e) {
display.evnt[Display.COMPONENT] =
"Component resized";
repaint();
}
public void
componentHidden(ComponentEvent e) {
display.evnt[Display.COMPONENT] =
"Component hidden";
repaint();
}
public void componentShown(ComponentEvent e) {
display.evnt[Display.COMPONENT] =
"Component shown";
repaint();
}
}
class FL implements FocusListener {
public void focusGained(FocusEvent e) {
display.evnt[Display.FOCUS] =
"FOCUS gained";
repaint();
}
public void focusLost(FocusEvent e) {
display.evnt[Display.FOCUS] =
"FOCUS lost";
repaint();
}
}
//得到键盘消息,键盘的消息值e ,其实就是每个按键的ASCII码值的字符表示
class KL implements KeyListener {
public void keyPressed(KeyEvent e) {
display.evnt[Display.KEY] =
"KEY pressed: ";
showCode(e);
}
public void keyReleased (KeyEvent e){
display.evnt[Display.KEY] =
"KEY released: ";
showCode(e);
}
public void keyTyped (KeyEvent e){
display.evnt[Display.KEY] =
"KEY typed: ";
showCode(e);
}
void showCode (KeyEvent e){
int code = e.getKeyCode();
display.evnt[Display.KEY] +=
KeyEvent.getKeyText(code);
repaint();
}
}
class ML implements MouseListener {
public void mouseClicked(MouseEvent e) {
requestFocus(); // Get FOCUS on click
display.evnt[Display.MOUSE] =
"MOUSE clicked";
showMouse(e);
}
public void mousePressed(MouseEvent e) {
display.evnt[Display.MOUSE] =
"MOUSE pressed";
showMouse(e);
}
public void mouseReleased(MouseEvent e) {
display.evnt[Display.MOUSE] =
"MOUSE released";
showMouse(e);
}
public void mouseEntered(MouseEvent e) {
display.evnt[Display.MOUSE] =
"MOUSE entered";
showMouse(e);
}
public void mouseExited(MouseEvent e) {
display.evnt[Display.MOUSE] =
"MOUSE exited";
showMouse(e);
}
void showMouse(MouseEvent e) {
display.evnt[Display.MOUSE] +=
", x = " + e.getX() +
", y = " + e.getY();
repaint();
}
}
class MML implements MouseMotionListener {
public void mouseDragged(MouseEvent e) {
display.evnt[Display.MOUSE_MOVE] =
"MOUSE dragged";
showMouse(e);
}
public void mouseMoved(MouseEvent e) {
display.evnt[Display.MOUSE_MOVE] =
"MOUSE moved";
showMouse(e);
}
//鼠标消息用画布字串输入,记录当前的鼠标坐标,鼠标消息对应的就是坐标可以用e.getX()和e.getY()得到
void showMouse(MouseEvent e) {
display.evnt[Display.MOUSE_MOVE] +=
", x = " + e.getX() +
", y = " + e.getY();
repaint();
}
}
}
class MyButton extends Button {
int clickCounter;
String label = "";
public MyButton() {
addActionListener(new AL());
}
public void paint(Graphics g) { //绿色按钮,南北布局南,黑色边框,
g.setColor(Color.green);
Dimension s = getSize();
g.fillRect(0, 0, s.width, s.height);
g.setColor(Color.black);
g.drawRect(0, 0, s.width - 1, s.height - 1);
drawLabel(g);
}
private void drawLabel(Graphics g) {
FontMetrics fm = g.getFontMetrics();
int width = fm.stringWidth(label);
int height = fm.getHeight();
int ascent = fm.getAscent(); //上坡度,基线到字符顶的距离
int leading = fm.getLeading(); //行间距
int horizMargin =
(getSize().width - width) / 2; //水平缩进: 按钮宽度减去字符串宽度除以2,这样基本居中
int verMargin =
(getSize().height - height) / 2;
g.setColor(Color.red);
g.drawString(label, horizMargin, //这个很经典,要学会这些参数。
verMargin + ascent + leading);
}
class AL implements ActionListener {
public void actionPerformed(ActionEvent e) {
//这里可以把按钮上的点击次数记录显示出来。
clickCounter++;
label = "click #" + clickCounter +
" " + e.toString();
repaint();
}
}
}
public class GoodTechnique extends Frame {
GoodTechnique() {
//用表格模式增加3个记录事件带button的Panel,这样右下角会空出一个区域。
setLayout(new GridLayout(2,2));
add(new EnabledPanel(1, Color.cyan));
add(new EnabledPanel(2, Color.lightGray));
add(new EnabledPanel(3, Color.yellow));
}
public static void main(String[] args) {
Frame f = new GoodTechnique();
f.setTitle("Good Technique");
f.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e){
System.out.println(e);
System.out.println("Window Closing");
System.exit(0);
}
});
f.setSize(700,700);
f.setVisible(true);
}
}
-
学会用Graphics类的对象方法drawString输出文本参数列表:(strText, horizMargin, verMargin+ascent+leading)
其中:horizMargin和verMargin可以通过FontMetrics的高度,宽度算出。 而ascent和leading来自FontMetrics的
方法graphics.getFontMetrics.getAscent 或者getLeading.
-
在键盘按键事件中,可以得到按键ASCII码值: KeyEvent e; code = e.getKeyCode();
而ASCII码值,可以用KeyEvent类方法转为文本显示: KeyEvent.getKeyText(code); 注意,这里控制字符也能显示出来。
-
在鼠标按键的事件中,可以得到鼠标的当前坐标: MouseEvent e ; e.getX()和e.getY();这个坐标返回的是当前监听者的
相对坐标。 可能需要requestFocus();这个属于Component对象方法,监听者都自带。
-
画布要想生效的话,记得在其所在组件中,使用update(Graphics g)重写内容添加paint(g);
画刷方法paint(Graphics g)也需要重写,里面需要包含诸如drawString方法才能让新画布内容生效。还需要重写过程事件processEvent(AWTEvent e) 里面增加repaint(); super.processEvent(e);
虚拟键小程序
java
package UsePaint;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
public class VirtualKeyTest {
public static void main(String[] args) {
new KeyFrame();
}
}
class KeyFrame extends Frame{
public KeyFrame(){
setBounds(200,200,800,600);
setVisible(true);
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
//super.keyPressed(e);
int keycode = e.getKeyCode();
if (keycode==KeyEvent.VK_UP){ //直接VK_UP-->Goto就可以看到所有键值
System.out.println("你按了上键");
}else{//这里显示的是10进制值,除了Tab键其他所有键都有键值
//这种虚拟键并不支持组合键或切换键,如NumLock
System.out.println(keycode);
}
}
});
}
}
10. 用反射机制追溯控件源头
-
我们可以用getSuperclass来追踪每一个组件的父类源头,下面是Frame 的类源头。
class java.awt.Frame
class java.awt.Window
class java.awt.Container
class java.awt.Component
class java.lang.Object
null //Object 的父类是NULL,如果用变量引用了Null,再继续getSupercass则会: NullPointerException.javaButton<--Component<--Object<--null Canvas<--Component<--Object<--null Checkbox<--Component<--Object<--null CheckboxMenuItem<--MenuItem<--MenuComponent<--Object<--null Choice<--Component<--Object<--null Dialog<--Window<--Container<--Component<--Object<--null FileDialog<-Dialog<--Window<--Container<--Component<--Object<--null Label<--Component<--Object<--null List<--Component<--Object<--null Menu<--MenuItem<--MenuComponent<--Object<--null MenuItem<--MenuComponent<--Object<--null Panel<--Container<--Component<--Object<--null PopupMenu<--Menu<--MenuItem<--MenuComponent<--Object<--null Scrollbar<--Component<--Object<--null ScrollPane<--Container<--Component<--Object<--null TextArea<--TextComponent<--Component<--Object<--null TextField<--TextComponent<--Component<--Object<--null String<--Object<--null Integer<--Number<--Object<--null所有的基础数据类型类,都没有父类。而对应的方法类,如:Integer, Double,Float.都来自Number类。
Boolean,String方法类除外,它们是直接来自Object.所有数组的类,也是来自Object.
byte , short, int , long, float, double, char, boolean <--null
11. 几个针对控件的小示例
Choice按钮的使用
java
package com.Murphy;
//: ChoiceNew.java
// Drop-down lists with Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class ChoiceNew extends Applet {
String[] description = { "Ebullient", "Obtuse",
"Recalcitrant", "Brilliant", "Somnescent",
"Timorous", "Florid", "Putrescent" };
TextField t = new TextField(100);
Choice c = new Choice();
Button b = new Button("Add items");
int count = 0;
public void init() {
t.setEditable(false);
for(int i = 0; i < 4; i++)
c.addItem(description[count++]);
add(t);
add(c);
add(b);
c.addItemListener(new CL());
b.addActionListener(new BL());
}
class CL implements ItemListener {
public void itemStateChanged(ItemEvent e) {
t.setText("index: " + c.getSelectedIndex()
+ " " + e.toString());
}
}
class BL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(count < description.length)
c.addItem(description[count++]);
}
}
public static void main(String[] args) {
ChoiceNew applet = new ChoiceNew();
Frame aFrame = new Frame("ChoiceNew");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(750,100);
applet.init();
applet.start();
aFrame.setVisible(true);
}
}
FileDialog与文件的读写
这段代码实现了用文件打开控件对一个文本文件内容的读写,保存在TextArea内容中,然后保存按钮点击时在把保存内容生成对应文件。
java
import java.awt.*;
import java.awt.event.*;
import java.io.*;
public class FileDialogNew extends Frame {
TextField filename = new TextField();
TextField directory = new TextField();
Button open = new Button("Open");
Button save = new Button("Save");
// 新增文本区域:用于显示/编辑文件内容
TextArea contentArea = new TextArea();
public FileDialogNew() {
setTitle("File Dialog Test");
// 底部按钮面板
Panel p = new Panel();
p.setLayout(new FlowLayout());
open.addActionListener(new OpenL());
p.add(open);
save.addActionListener(new SaveL());
p.add(save);
add(p, BorderLayout.SOUTH);
// 顶部路径显示面板
directory.setEditable(false);
filename.setEditable(false);
p = new Panel();
p.setLayout(new GridLayout(2, 1));
p.add(filename);
p.add(directory);
add(p, BorderLayout.NORTH);
// 中间文件内容编辑区域
add(contentArea, BorderLayout.CENTER);
}
// 打开文件监听器:新增读取文件内容的逻辑
class OpenL implements ActionListener {
public void actionPerformed(ActionEvent e) {
FileDialog d = new FileDialog(FileDialogNew.this, "What file do you want to open?");
d.setFile("*.java");
d.setDirectory(".");
d.show();
String selectedFile = d.getFile();
if (selectedFile != null) {
filename.setText(selectedFile);
directory.setText(d.getDirectory());
// 拼接完整文件路径
String fullPath = d.getDirectory() + File.separator + selectedFile;
// 读取文件内容并显示到文本区域
try (BufferedReader br = new BufferedReader(new FileReader(fullPath))) {
contentArea.setText(""); // 清空原有内容
String line;
while ((line = br.readLine()) != null) {
contentArea.append(line + "\n");
}
} catch (IOException ex) {
contentArea.setText("读取文件失败:" + ex.getMessage());
}
} else {
filename.setText("You pressed cancel");
directory.setText("");
contentArea.setText("");
}
}
}
// 保存文件监听器:新增写入文件内容的逻辑
class SaveL implements ActionListener {
public void actionPerformed(ActionEvent e) {
FileDialog d = new FileDialog(FileDialogNew.this, "What file do you want to save?", FileDialog.SAVE);
d.setFile("*.java");
d.setDirectory(".");
d.show();
String saveFile = d.getFile();
if (saveFile != null) {
filename.setText(saveFile);
directory.setText(d.getDirectory());
// 拼接完整保存路径
String fullPath = d.getDirectory() + File.separator + saveFile;
// 将文本区域的内容写入文件
try (BufferedWriter bw = new BufferedWriter(new FileWriter(fullPath))) {
bw.write(contentArea.getText());
contentArea.append("\n\n文件已保存到:" + fullPath);
} catch (IOException ex) {
contentArea.setText("保存文件失败:" + ex.getMessage());
}
} else {
filename.setText("You pressed cancel");
directory.setText("");
}
}
}
public static void main(String[] args) {
Frame f = new FileDialogNew();
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.setSize(500, 400); // 放大窗口以显示文本区域
f.setVisible(true);
}
}
好方法与坏方法
好方法不会让主类去继承接口,从而不得不重写各种抽象方法。下面是标准的建立控件及对应事件的写法,堪称范本。
java
package com.Murphy;
//用内部类实现各种监听事件。
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class GoodIdea extends Frame {
Button
b1 = new Button("Button 1"),
b2 = new Button("Button 2");
public GoodIdea() {
setLayout(new FlowLayout());
b1.addActionListener(new B1L());
b2.addActionListener(new B2L());
add(b1);
add(b2);
}
public class B1L implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("Button 1 pressed");
}
}
public class B2L implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("Button 2 pressed");
}
}
public static void main(String[] args) {
Frame f = new GoodIdea();
f.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e){
System.out.println("Window Closing");
System.exit(0);
}
});
f.setSize(300,200);
f.setVisible(true);
}
}
坏方法,不但让主类继承各种接口,还在类中为主类写了各种监听事件,这导致整个程序写死了,极难改动。
java
package com.Murphy;
//这种写法很容易造成主类的监听实例的指针丢失。
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class BadIdea1 extends Frame
//在主类中继承接口,是一种很垃圾的做法。我们不得已要重写所有的接口方法实现。并且方法跟按钮逻辑松散。
implements ActionListener, WindowListener {
Button
b1 = new Button("Button 1"),
b2 = new Button("Button 2");
public BadIdea1() {
setLayout(new FlowLayout());
addWindowListener(this);
b1.addActionListener(this);
b2.addActionListener(this);
add(b1);
add(b2);
}
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if(source == b1)
System.out.println("Button 1 pressed");
else if(source == b2)
System.out.println("Button 2 pressed");
else
System.out.println("Something else");
}
public void windowClosing(WindowEvent e) {
System.out.println("Window Closing");
System.exit(0);
}
public void windowClosed(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
public void windowActivated(WindowEvent e) {}
public void windowDeactivated(WindowEvent e) {}
public void windowOpened(WindowEvent e) {}
public static void main(String[] args) {
Frame f = new BadIdea1();
f.setSize(300,200);
f.setVisible(true);
}
}
java
package com.Murphy;
//不应该在主类的内部类中继承触发器Adapt,因为触发器全部都是抽象方法,只有监听器Listener才是标准接口。
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class BadIdea2 extends Frame
implements ActionListener {
Button
b1 = new Button("Button 1"),
b2 = new Button("Button 2");
public BadIdea2() {
setLayout(new FlowLayout());
addWindowListener(new WL());
b1.addActionListener(this);
b2.addActionListener(this);
add(b1);
add(b2);
}
//这个地方是最烂的,不能在主类中写触发器的执行过程,一个是难以修改和扩展,另一个是杂乱,看不到跟按钮的紧密联系
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if(source == b1)
System.out.println("Button 1 pressed");
else if(source == b2)
System.out.println("Button 2 pressed");
else
System.out.println("Something else");
}
class WL extends WindowAdapter {
public void windowClosing(WindowEvent e) {
System.out.println("Window Closing");
System.exit(0);
}
}
public static void main(String[] args) {
Frame f = new BadIdea2();
f.setSize(300,200);
f.setVisible(true);
}
}
一个弹窗生成表格,并用按钮填满表格可以点击的程序
这个程序有点像棋类游戏,设计棋类时可以参考它。
java
package com.Murphy;
/*
子窗口+Grid表格测试
*/
import java.awt.*;
import java.awt.event.*;
public class ToeTestNew extends Frame {
TextField rows = new TextField("3");
TextField cols = new TextField("3");
public ToeTestNew() {
setTitle("Toe Test");
Panel p = new Panel();
p.setLayout(new GridLayout(2,2));
p.add(new Label("Rows", Label.CENTER));
p.add(rows);
p.add(new Label("Columns", Label.CENTER));
p.add(cols);
add(p, BorderLayout.NORTH);
Button b = new Button("go");
b.addActionListener(new BL()); //弹窗事件
add(b, BorderLayout.SOUTH);
}
static final int BLANK = 0;
static final int XX = 1;
static final int OO = 2;
class ToeDialog extends Dialog {//Dialog继承Window,跟Frame是平级的。
// w = number of cells wide
// h = number of cells high
int turn = XX; // Start with x's turn
public ToeDialog(int w, int h) {
super(ToeTestNew.this,
"The game itself", false);
setLayout(new GridLayout(w, h));
for(int i = 0; i < w * h; i++)
add(new ToeButton());
setSize(w * 50, h * 50);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e){
dispose();
}
});
}
class ToeButton extends Canvas {//填充表格的按钮实际是Canvas
int state = BLANK;
ToeButton() {
addMouseListener(new ML()); //添加画圆画X事件。
}
public void paint(Graphics g) {
int x1 = 0;
int y1 = 0;
int x2 = getSize().width - 1;
int y2 = getSize().height - 1;
g.drawRect(x1, y1, x2, y2);
x1 = x2/4;
y1 = y2/4;
int wide = x2/2;
int high = y2/2;
if(state == XX) {
g.drawLine(x1, y1,
x1 + wide, y1 + high);
g.drawLine(x1, y1 + high,
x1 + wide, y1);
}
if(state == OO) {
g.drawOval(x1, y1,
x1 + wide/2, y1 + high/2);
}
}
class ML extends MouseAdapter {
public void mousePressed(MouseEvent e) {
if(state == BLANK) {//如果是空白,就参照历史turn.
state = turn;
turn = (turn == XX ? OO : XX);
}
else
state = (state == XX ? OO : XX);
repaint();
}
}
}
}
class BL implements ActionListener {
public void actionPerformed(ActionEvent e) {
Dialog d = new ToeDialog(
Integer.parseInt(rows.getText()),
Integer.parseInt(cols.getText()));
d.show();
}
}
public static void main(String[] args) {
Frame f = new ToeTestNew();
f.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.setSize(200,100);
f.setVisible(true);
}
}