第 7 章 图形程序设计
从现在开始Swing是指
被绘制的
用户界面类;AWT指像 事件处理 这样的窗口工具箱的 底层机制。
矢量图不依赖于屏幕的分辨率。
创建框架
在java中,顶层窗口(就是没有包含在其他窗口中的窗口)被称为框架(frame)。在AWT库中有一个称为Frame的类,用于描述顶层窗口。这个类的Swing版本名为JFrame,它扩展于Frame类。JFrame是极少数几个不绘制在画布上的Swing组件之一。因此,它的修饰部件(按钮,标题栏,图标)由用户的窗口系统绘制,而不是Swing绘制。
在文档中查阅一个类继承的方法是一件比较令人烦恼的事情。对于子类来说,API文档只解释了覆盖的方法。如果认为应该有一个能够完成某项操作的方法,而在处理的类文档中又没有解释,就应该查看这个类的超类API文档。每页API上面都有一个对超类的超链接,继承方法被列在新方法和覆盖方法的汇总下面。
Component类是所有(GUI对象的祖先)
针对get/set约定有一个例外:对于boolean的属性,获取方法由is开头,例如,下面两个方法定义了locationByPlatform属性:
public boolean isLocationByPlatform()
public void setLocationPlatform(boolean b)
在Eclipse中定义一个boolean类型的域,生成set/get,实验结果证明是这样。
Swing程序员最关心的是内容窗格(content pane)。在设计框架的时候,要使用下列代码将所有的组件添加到内容窗格中。
Container contentPane = frame.getContentPane();
Component c = . . .;
contentPane.add(c);
读取图像的方法:Image image = new ImageIcon(filename
).getImage();
第八章 事件处理
像java这样的面向对象的语言,都将事件的相关信息封装在一个
事件对象
中。
所有的事件对象都最终派生于java.util.EventObject类。每个事件类型还有子类,例如,ActionEvent和WindowEvent。
下面给出AWT事件处理机制的概要:
--
监听器对象是一个实现了特定
监听器接口的类的实例。
--
事件源是一个
能够注册监听器对象并
发送事件对象的
对象。
--当事件发生时,
事件源将
事件对象 传递给所有注册的
监听器。
--
监听器对象将利用
事件对象中的信息决定如何对事件
做出响应
下图显示了事件源、事件监听器和事件对象之间的协作关系。箭头被指向的为方法的拥有者。
事件监听器对象通常需要执行一些对其他对象可能产生影响的操作。可以策略性的将监听器类放置在需要修改状态的那个类中。
程序清单8-1包含了完整的框架类。无论何时点击任何一个按钮,对应的动作监听器就会修改面板的背景颜色
ButtonFrame.java
/**
* A frame with a button panel
*/
public class ButtonFrame extends JFrame
{
private JPanel buttonPanel;
private static final int DEFAULT_WIDTH = 300;
private static final int DEFAULT_HEIGHT = 200;
public ButtonFrame()
{
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
// create buttons
JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton("Blue");
JButton redButton = new JButton("Red");
buttonPanel = new JPanel();//在构造方法中构造一个Jpanel对象赋给实例域buttonPanel
// add buttons to panel
buttonPanel.add(yellowButton);
buttonPanel.add(blueButton);
buttonPanel.add(redButton);
// add panel to frame
add(buttonPanel);
// create button actions
ColorAction yellowAction = new ColorAction(Color.YELLOW);
ColorAction blueAction = new ColorAction(Color.BLUE);
ColorAction redAction = new ColorAction(Color.RED);
// associate actions with buttons
yellowButton.addActionListener(yellowAction);
blueButton.addActionListener(blueAction);
redButton.addActionListener(redAction);
}
/**
* An action listener that sets the panel's background color.
*/
private class ColorAction implements ActionListener
{
private Color backgroundColor;
public ColorAction(Color c)
{
backgroundColor = c;
}
public void actionPerformed(ActionEvent event)
{
buttonPanel.setBackground(backgroundColor);//内部类访问外部类的实例域buttonPanel
}
}
}
ButtonTest.java
public class ButtonTest
{
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
JFrame frame = new ButtonFrame();
frame.setTitle("ButtonTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}
(System.exit(0);将虚拟机的内容都停掉,可以强制停止程序运行)
适配器类
在Java中,实现一个接口的任何类都必须实现其中的所有方法;如果有7个方法,那么意味着要实现这7个方法。然而我们只对其中一个方法感兴趣。我们可以将另外6个方法的方法体为空,即{}。但显然很乏味。
鉴于简化的目的,每个含有多个方法的AWT监听器接口都配有一个 适配器(adapter) 类。这个类实现了接口的所有方法,但每个方法没有做任何事情。这意味着适配器类自动地满足了Java实现监听器接口的技术需求。可以通过扩展(extend)适配器类来指定对某些事件的响应动作,而不必实现接口中的每个方法。
匿名内部类的语法需要人们适应一段时间,但得到的是更加简练的代码。
第九章 Swing用户界面组件
在 模型--视图--控制器模式中,背景是显示信息和接收用户输入的用户界面系统。这里有几个冲突因素。对于同一数据来说,可能需要同时更新多个可视化表示。解决方案是将这些功能分布到三个独立的交互组件:模型,视图,控制器。
容器和组件是:组合(composite)模式
带滚动条的面板是:装饰器模式。
布局管理器是:策略模式
布局管理器是:策略模式
模型--视图--控制器模式这种 设计模式同其他许多设计模式一样,都遵循面向对象设计中的一个基本原则:限制一个对象拥有的功能数量。不要用
一个按钮类完成所有的事情,而是应该让一个对象负责组件的观感,另一个对象负责存储内容。MVC模式告诉我们如何实现这种设计,实现三个独立的类:
--模型:存储内容
--视图:显示内容
--控制器:处理用户输入
这个模式明确的规定了三个对象如何进行交互。模型
存储内容,它 没有用户界面。按钮的内容非常简单,只有几个用来表示当前按钮是否按下,是否处于活动状态的
标志等。
模型
必须实现改变内容和查找内容的方法。例如一个文本模型中的方法有:在当前文本中添加或者删除字符以及把当前文本作为一个字符串返回等。记住:模型是完全不可见的。显示存储在模型中的数据是
视图的工作。
模型--视图--控制器模式的一个优点是一个模型可以有多个视图,其中每个视图可以显示全部内容的不同部分或不同形式。例如,一个HTML编辑器常常为同一内容在同一时刻提供两个视图:一个所见即所得视图和一个原始标记视图。
控制器
负责处理用户输入事件,如点击鼠标和敲击键盘。然后决定是否把这些事件转化成对模型或视图的改变。例如,如果用户在一个文本框中按下了一个字符键,
控制器调用
模型中的“插入字符”命令,然后模型告诉视图进行更新,而视图永远不会知道文本为什么改变了。但是如果用户按下了一个光标键,那么控制器会通知视图进行卷屏。卷动视图对实际文本不会有任何影响,因此模型永远不会知道这个事件的发生。
下图给出了模型、视图、控制器对象之间的交互
面板的默认管理器是流布局管理器。当一行的空间不够时,会将显示在新的一行上。另外按钮总是位于面板的中央,即使用户对框架进行缩放也是如此。
第 10 章 部署应用程序和applet
JAR 文件
在将应用程序打包时,使用者一定希望仅提供给一个单独的文件,而不是一个含有大量类文件的目录,Java归档(JAR)文件就是为此目的设计的。一个JAR文件
既可以包含类文件,也可以
包含诸如图像和声音这些其他类型的文件。此外,JAR文件是压缩的,它使用了大家熟悉的ZIP压缩格式。
可以使用jar工具制作JAR文件(在默认的JDK安装中,位于jdk/bin目录下)。创建一个新的JAR文件应该使用的常见命令格式为:
jar cvf JARFileName File1 File2
除了类文件、图像和其他资源外,每个JAR文件还包含一个用于描述归档特征的 清单文件(mainfest)。
清单文件被命名为MAINFEST.MF,它位于JAR文件的一个特殊META-INF子目录中。
资源
在应用程序中通常需要使用一些相关的数据文件,例如:
--图像和声音文件
--带有消息字符串和按钮的文本文件
--二进制数据文件
在Java中,这些关联的文件被称为资源(resource)
我们有时候希望将文本放置在一个文件中,而不是以字符串的形式硬写到代码中。但是应该将about.txt这样的文件放在哪里呢?显然将它与其他程序文件一起放在JAR文件中是最方便的。
类加载器知道如何搜索类文件,直到在类路径、存档文件或Web服务器上找到为止。利用
资源机制,对于非类文件也可以同样方便地操作。下面是必要的步骤:
1)获得具有资源的Class对象,例如,AboutPanel.class
2)如果资源是一个图像或声音文件,那么就需要调用
getresource(filename)获得作为URL的资源位置,然后利用getImage或getAudioClip方法读取。
3)与图像或声音不同,其他资源可以使用
getResourceAsStream方法读取文件中。
重点在于
类加载器 可以记住 如何定位类,然后在同一位置查找关联的资源。
例如,要想利用about.gif图像制作图标,可以使用下列代码:
URL url = ResoutceTest.class.getResource("about.gif");
Image img = new ImageIcon(url).getImage();
这段代码的含义是在找到ResourceTest类的地方法查找about.gif文件。
要想读取about.txt文件,可以使用下列命令:
InputStream stream = ResourceTest.class.getResourceAsStream("about.txt");
Scanner in = new Scanner(stream);
除了可以将资源文件与类文件放在同一个目录中外,还可以将它放在子目录中。可以使用下面所示的层级资源名:data/text/about.txt
这是一个
相对的资源名,它会被解释为相对于加载这个资源的类所在的包。必须使用“/”作为分隔符。
一个以 “/” 开头的资源名被称为绝对资源名。它的定位方式与类在包中的定位方式一样。例如,资源/corejava/title.txt定位于corejava目录下(它可能是类路径的一个子目录)
文件的自动装载,是
利用资源的自动加载特性来完成的。没有标准的方法来解释资源文件的内容。
每个程序必须拥有解释资源文件的方法。
(Outline窗口显示当前的java文件的结构信息)
ResourceTest.java
public class ResourceTest
{
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
JFrame frame = new ResourceTestFrame();
frame.setTitle("ResourceTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}
/**
* A frame that loads image and text resources.
*/
@SuppressWarnings("serial")
class ResourceTestFrame extends JFrame
{
private static final int DEFAULT_WIDTH = 300;
private static final int DEFAULT_HEIGHT = 300;
public ResourceTestFrame()
{
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
URL aboutURL = getClass().getResource("about.gif");
Image img = new ImageIcon(aboutURL).getImage();
setIconImage(img);
JTextArea textArea = new JTextArea();
InputStream stream = getClass().getResourceAsStream("about.txt");
Scanner in = new Scanner(stream);
while (in.hasNext())
textArea.append(in.nextLine() + "\n");//textArea对象必须以“\n”作为结束标志
add(textArea);
in.close();
}
}
密封
可以将Java包密封,以保证不会有其他的类加入到其中。如果在代码中使用了包可见的类,方法和域,就可能希望密封包。如果不密封,其他类就可能放在这个包中,进而访问包可见的特性。
例如,如果密封了com.mycompany.util包,就不能用下面的语句顶替密封包之外的类:
package com.mycompany.util;
要想密封一个包,需要将包中的所有类放到一个JAR文件中。在默认的情况下,
JAR文件中的包是没有密封的。可以在清单文件的主节中加入下面一行:
Sealed:true
来改变全局的默认设定。对于每个单独的包,可以通过在JAR文件的清单中增加一节,来指定是否想要密封这个包。例如:
Name:com/mycompany/util
Sealed:true
Name:com/mycompany/misc
Sealed:false
要想密封一个包,需要创建一个包含清单指令的文本文件。然后用常规的方式运行jar命令:
jar cvfm MyArchive.jar
manifest.mf files to add
浏览器利用MIME类型确定启动哪一种辅助应用程序。
沙箱
无论何时加载远程网站上代码并在本地执行,安全都是至关重要的问题。如果点击一个链接或访问一个网页时,在用户的机器上能够安装任意的代码,那么犯罪分子可能在此时窃取机密信息,读取财务数据或接管用户机器来发送广告。
java的安全管理器将检查有权使用所有系统的资源。在默认情况下,只允许执行哪些无害的操作,要想允许执行其他的操作,代码必须得到数字签名,用户必须通过数字验证。
在所有平台上,远程代码(相对于本地代码而言)?它可以显示图像、播放音乐、获得用户的键盘输入和鼠标点击、以及将用户的输入送回加载代码所在的主机。这些功能足以能够显示信息和图片,或者获得用户为订单所输入的信息。这种
受限制的执行环境称为 沙箱(sandbox),在沙箱中运行的代码不能修改或查看用户系统。
在
沙箱中的程序有下列限制:
--不能运行任何本地的可执行程序
--不能从本地计算机系统中读取任何信息,也不能往本地计算机系统中写入任何信息
--不能查看 除 Java版本信息和少数几个无害的操作系统详细信息外的任何有关本地计算机的信息。特别是,在沙箱中的代码不能查看用户名、e-mail地址等信息
--远程加载的程序不能与除下载程序所在的服务器之外的任何主机通信,这个服务器被称为源主机。这条规则通常称为“远程代码只能和家人通话。”
这条规则将确保用户不会被代码探查到内部网络资源。
--所有弹出式窗口都会带一个警告信息。
应用程序首选项存储
应用程序通常期待能够自行对应用程序进行配置,并能够将其保存起来。日后再次运行这个程序时能够读取这些配置。
属性映射(property map)是一种存储键/值对的数据结构。属性映射经常被用来存放配置信息。它有三个特性:
--键和值都是字符串
--键/值对都可以很容易地写入文件或从文件读出
--用二级表存放默认值
实现属性映射的Java类被称为
Properties。
属性映射对指定程序的配置选项非常有用。例如:
Properties settings = new Properties();
settings.put("width","200");
settings.put("title","Hello,World"):
可以使用stroe方法将这个属性映射列表保存到文件中。在这里,将属性映射保存在Myprog.properties文件中。第二个参数是包含到这个文件的注释。
FileOutputStream out = new FileOutputStream("program.properties");
settings.
put("width","200");
settings.put("title","Hello,World");
上述示例将会输出如下结果:
#Program Properties
#Mon Apr 30 07:22:52 2007
width=200
title=Hello,World
要想从文件中加载这些属性,可以使用:
FileInputStream in = new
FileInputStream("program.properties");
settings.load(in);
习惯上,将程序存储在用户主目录的某个子目录下。目录名通常选择由一个圆点开始。这个约定表示来自用户的一个隐藏的系统目录。在实例程序中将遵守这个约定。
要想查看
用户的主目录,可以调用
System.getProperties方法。很巧,还可以使用Properties对象描述系统信息。
主目录包含键user.home。还有一个很有用的方法,,它用于读取单键:
String userDir = System.getProperty(" user.home");// C:\Users\Administrator-- C:\Users\Administrator>
查看当前目录
String nowDir =
System.
getProperty
(
"user.dir"
);//
D:\学习资料\源码\corejavabook\v1ch02\Welcome
一旦用户手工地编辑文件,那么为应用程序提供默认值就是一种很好的想法。Properties类有两种提供默认值的机制。第一种是在试图获得字符串值时指定默认值。当键值不存在时,就会自动调用它。
String title = setting.getProperty("title","Default title");
如果在属性映射中有title属性,则title将设置成字符串。否则title将设置成Default title。如果觉得在每次调用getProperty方法时指定默认值太麻烦,那么就可以将所有的默认值放在一个二级属性映射中。用它来构造查询表。
如果在属性映射中有title属性,则title将设置成字符串。否则title将设置成Default title。如果觉得在每次调用getProperty方法时指定默认值太麻烦,那么就可以将所有的默认值放在一个二级属性映射中。用它来构造查询表。
Properties defaultSettings = new Properties();
defaultSettings.put("width","300");
defaultSettings.put("height","200");
defaultSettings.put("title","Default title");
. . .
Propertyies settings = new Properties(defaultSettings);
PropertiesTest.java
public class PropertiesTest
{
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
PropertiesFrame frame = new PropertiesFrame();
frame.setVisible(true);
}
});
}
}
/**
* A frame that restores position and size from a properties file and updates the properties upon
* exit.
*/
class PropertiesFrame extends JFrame
{
private static final int DEFAULT_WIDTH = 300;
private static final int DEFAULT_HEIGHT = 200;
private File propertiesFile;
private Properties settings;
public PropertiesFrame()
{
// get position, size, title from properties
String userDir = System.getProperty("user.home");//获得用户的主目录
//定义一个.corejava文件夹
File propertiesDir = new File(userDir, ".corejava");
if (!propertiesDir.exists()) propertiesDir.mkdir();
//在.corejava文件夹下生成一个program.properties属性映射文件
propertiesFile = new File(propertiesDir, "program.properties");
//设置属性映射文件的默认值
Properties defaultSettings = new Properties();
defaultSettings.put("left", "0");
defaultSettings.put("top", "0");
defaultSettings.put("width", "" + DEFAULT_WIDTH);
defaultSettings.put("height", "" + DEFAULT_HEIGHT);
defaultSettings.put("title", "");
//主属性映射中提供二级属性映射。
settings = new Properties(defaultSettings);
if (propertiesFile.exists()) try
{
FileInputStream in = new FileInputStream(propertiesFile);
settings.load(in);
}
catch (IOException ex)
{
ex.printStackTrace();
}
int left = Integer.parseInt(settings.getProperty("left"));
int top = Integer.parseInt(settings.getProperty("top"));
int width = Integer.parseInt(settings.getProperty("width"));
int height = Integer.parseInt(settings.getProperty("height"));
setBounds(left, top, width, height);
// if no title given, ask user
String title = settings.getProperty("title");
if (title.equals("")) title = JOptionPane.showInputDialog("Please supply a frame title:");
if (title == null) title = "";
setTitle(title);
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent event)
{
settings.put("left", "" + getX());
settings.put("top", "" + getY());
settings.put("width", "" + getWidth());
settings.put("height", "" + getHeight());
settings.put("title", getTitle());
try
{
FileOutputStream out = new FileOutputStream(propertiesFile);
settings.store(out, "Program Properties");
}
catch (IOException ex)
{
ex.printStackTrace();
}
System.exit(0);
}
});
}
}