52.java编程思想——创建窗口和程序片 程序片限制
出于安全缘故,程序片十分受到限制,并且有很多的事我们都不能做。您一般会问:程序片看起来能做什么,传闻它又能做什么:扩展浏览器中WEB 页的功能。自从作为一个网上冲浪者,我们从未真正想了解是否一个WEB 页来自友好的或者不友好的站点,我们想要一些可以安全地行动的代码。所以我们可能会注意到大量的限制:
(1) 一个程序片不能接触到本地的磁盘。这意味着不能在本地磁盘上写和读,我们不想一个程序片通过WEB页面阅读和传送重要的信息。写是被禁止的,当然,因为那将会引起病毒的侵入。当数字签名生效时,这些限制会被解除。
(2) 程序片不能拥有菜单。(注意:这是规定在Swing 中的)这可能会减少关于安全和关于程序简化的麻烦。我们可能会接到有关程序片协调利益以作为WEB 页面的一部分的通知;而我们通常不去注意程序片的范围。这儿没有帧和标题条从菜单处弹出,出现的帧和标题条是属于WEB 浏览器的。也许将来设计能被改变成允许我们将浏览器菜单和程序片菜单相结合起来——程序片可以影响它的环境将导致太危及整个系统的安全并使程序片过于的复杂。
(3) 对话框是不被信任的。在Java中,对话框存在一些令人难解的地方。首先,它们不能正确地拒绝程序片,这实在是令人沮丧。如果我们从程序片弹出一个对话框,我们会在对话框上看到一个附上的消息框“不被信任的程序片”。这是因为在理论上,它有可能欺骗用户去考虑他们在通过WEB 同一个老顾客的本地应用程序交易并且让他们输入他们的信用卡号。在看到AWT 开发的那种GUI 后,我们可能会难过地相信任何人都会被那种方法所愚弄。但程序片是一直附着在一个Web页面上的,并可以在浏览器中看到,而对话框没有这种依附关系,所以理论上是可能的。因此,我们很少会见到一个使用对话框的程序片。
在较新的浏览器中,对受到信任的程序片来说,许多限制都被放宽了(受信任程序片由一个信任源认证)。
涉及程序片的开发时,还有另一些问题需要考虑:
■程序片不停地从一个适合不同类的单独的服务器上下载。我们的浏览器能够缓存程序片,但这没有保证。在Java 1.1 版中的一个改进是JAR(Java ARchive)文件,它允许将所有的程序片组件(包括其它的类文件、图像、声音)一起打包到一个的能被单个服务器处理下载的压缩文件。“数字签字”(能校验类创建器)可有效地加入每个单独的JAR 文件。
■因为安全方面的缘故,我们做某些工作更加困难,例如访问数据库和发送电子邮件。另外,安全限制规则使访问多个主机变得非常的困难,因为每一件事都必须通过WEB 服务器路由,形成一个性能瓶颈,并且单一环节的出错都会导致整个处理的停止。
■浏览器里的程序片不会拥有同样的本地应用程序运行的控件类型。例如,自从用户可以开关页面以来,在程序片中不会拥有一个形式上的对话框。当用户对一个WEB 页面进行改变或退出浏览器时,对我们的程序片而言简直是一场灾难——这时没有办法保存状态,所以如果我们在处理和操作中时,信息会被丢失。另外,当我们离开一个WEB 页面时,不同的浏览器会对我们的程序片做不同的操作,因此结果本来就是不确定的。
如果能容忍那些限制,那么程序片的一些优点也是非常突出的,尤其是在我们构建客户/服务器应用或者其它网络应用时:
■没有安装方面的争议。程序片拥有真正的平台独立性(包括容易地播放声音文件等能力)所以我们不需要针对不同的平台修改代码也不需要任何人根据安装运行任何的“tweaking ”。事实上,安装每次自动地将WEB 页连同程序片一起,因此安静、自动地更新。在传统的客户机/服务器系统中,建立和安装一个新版本的客户端软件简直就是一场恶梦。
■因为安全的原因创建在核心Java 语言和程序片结构中,我们不必担心坏的代码而导致毁坏某人的系统。这样,连同前面的优点,可使用Java (可从JavaScript 和VBScript中选择客户端的WEB 编程工具)为所谓的Intrant(在公司内部使用而不向Internet 转移的企业内部网络)客户机/服务器开发应用程序。
■由于程序片是自动同HTML 集成的,所以我们有一个内建的独立平台文件系统去支持程序片。这是一个很有趣的方法,因为我们惯于拥有程序文件的一部分而不是相反的拥有文件系统。
出于安全的缘故,我们会看到在程序片我们的行为非常的受到限制。我们真实地感到,程序片是被临时地加入在WEB 浏览器中的,因此,它的功能连同它的相关知识,控件都必须加以限制。但是,我们希望Java能制造一个开窗口的程序去运行一些事物,否则宁愿安放在一个WEB 页面上,并且也许我们希望它可以运行一些可靠的应用程序,以及夸张的实时便携性。在这本书前面的章节中我们制造了一些命令行应用程序,但在一些操作环境中(例如:Macintosh)没有命令行。所以我们有很多的理由去利用Java 创建一个设置窗口,非程序片的程序。这当然是一个十分合理的要求。
一个Java 设置窗口应用程序可以拥有菜单和对话框(这对一个程序片来说是不可能的和很困难的),可是如果我们使用一个老版本的Java,我们将会牺牲本地操作系统环境的外观和感受。JFC/Swing 库允许我们制造一个保持原来操作系统环境的外观和感受的应用程序。如果我们想建立一个设置窗口应用程序,它会合理地运作,同样,如果我们可以使用最新版本的Java 并且集合所有的工具,我们就可以发布不会使用户困惑的应用程序。如果因为一些原因,我们被迫使用老版本的Java,请在毁坏以建立重要的设置窗口的应用程序前仔细地考虑。
直接在程序片中安放一个菜单是不可能的(Java 1.0,Java1.1 和Swing 库不允许),因为它们是针对应用程序的。继续,如果您不相信我并且确定在程序片中可以合理地拥有菜单,那么您可以去试验一下。程序片中没有setMenuBar()方法,而这种方法是附在菜单中的(我们会看到它可以合理地在程序片产生一个帧,并且帧包含菜单)。
有四种不同类型的MenuComponent (菜单组件),所有的菜单组件起源于抽象类:菜单条(我们可以在一个事件帧里拥有一个菜单条),菜单去支配一个单独的下拉菜单或者子菜单、菜单项来说明菜单里一个单个的元素,以及起源于MenuItem,产生检查标志(checkmark)去显示菜单项是否被选择的CheckBoxMenuItem。不同的系统使用不同的资源,对Java 和AWT 而言,我们必须在源代码中手工汇编所有的菜单。
import java.awt.*;
public class Menu1 extends Frame {
String[] flavors = { "Chocolate", "Strawberry","Vanilla Fudge Swirl", "Mint Chip", "Mocha Almond Fudge",
"Rum Raisin", "Praline Cream","Mud Pie" };
TextField t = new TextField("No flavor", 30);
MenuBar mb1 = new MenuBar();
Menu f = new Menu("File");
Menu m = new Menu("Flavors");
Menu s = new Menu("Safety");
// Alternativeapproach:
CheckboxMenuItem[] safety = { new CheckboxMenuItem("Guard"), new CheckboxMenuItem("Hide")};
MenuItem[] file = { new MenuItem("Open"), new MenuItem("Exit")};
// A secondmenu bar to swap to:
MenuBar mb2 = new MenuBar();
Menu fooBar = new Menu("fooBar");
MenuItem[] other = { new MenuItem("Foo"), new MenuItem("Bar"), new MenuItem("Baz"), };
Button b = new Button("Swap Menus");
public Menu1() {
for (int i = 0; i < flavors.length; i++) {
m.add(new MenuItem(flavors[i]));
// Add separators at intervals:
if ((i + 1) % 3 == 0)
m.addSeparator();
}
for (int i = 0; i < safety.length; i++)
s.add(safety[i]);
f.add(s);
for (int i = 0; i < file.length; i++)
f.add(file[i]);
mb1.add(f);
mb1.add(m);
setMenuBar(mb1);
t.setEditable(false);
add("Center", t);
// Set up the system for swapping menus:
add("North", b);
for (int i = 0; i < other.length; i++)
fooBar.add(other[i]);
mb2.add(fooBar);
}
public booleanhandleEvent(Event evt){
if (evt.id== Event.WINDOW_DESTROY)
System.exit(0);
else
return super.handleEvent(evt);
return true;
}
public booleanaction(Event evt,Object arg){
if (evt.target.equals(b)) {
MenuBar m = getMenuBar();
if (m == mb1)
setMenuBar(mb2);
else if (m == mb2)
setMenuBar(mb1);
} else if (evt.targetinstanceofMenuItem) {
if (arg.equals("Open")){
String s = t.getText();
boolean chosen = false;
for (int i = 0; i < flavors.length; i++)
if (s.equals(flavors[i]))
chosen = true;
if (!chosen)
t.setText("Choose a flavor first!");
else
t.setText("Opening " + s + ". Mmm, mm!");
} else if (evt.target.equals(file[1]))
System.exit(0);
// CheckboxMenuItems cannot use String
// matching; you must match the target:
else if (evt.target.equals(safety[0]))
t.setText("Guard the Ice Cream! "+ "Guarding is " + safety[0].getState());
else if (evt.target.equals(safety[1]))
t.setText("Hide the Ice Cream! " + "Is it cold? "+ safety[1].getState());
else
t.setText(arg.toString());
} else
return super.action(evt, arg);
return true;
}
public staticvoidmain(String[] args){
Menu1 f = new Menu1();
f.resize(300, 200);
f.show();
}
}/// :~
避免了为每个菜单编写典型的冗长的add()列表调用,因为那看起来像许多的无用的标志。取而代之的是,我安放菜单项到数组中,然后在一个for 的循环中通过每个数组调用add()简单地跳过。这样的话,增加和减少菜单项变得没那么讨厌了。
作为一个可选择的方法(我发现这很难令我满意,因为它需要更多的分配)CheckboxMenuItems 在数组的句柄中被创建是被称为安全创建;这对数组文件和其它的文件而言是真正的安全。程序中创建了不是一个而是二个的菜单条来证明菜单条在程序运行时能被交换激活。我们可以看到菜单条怎样组成菜单,每个菜单怎样组成菜单项(MenuItems),chenkboxMenuItems 或者其它的菜单(产生子菜单)。当菜单组合后,可以用setMenuBar()方法安装到现在的程序中。值得注意的是当按钮被压下时,它将检查当前的菜单安装使用getMenuBar(),然后安放其它的菜单条在它的位置上。
当测试是“open”(即开始)时,注意拼写和大写,如果开始时没有对象,Java 发出no error(没有错误)的信号。这种字符串比较是一个明显的程序设计错误源。校验和非校验的菜单项自动地运行,与之相关的CheckBoxMenuItems 着实令人吃惊,这是因为一些原因它们不允许字符串匹配。(这似乎是自相矛盾的,尽管字符串匹配并不是一种很好的办法。)因此,我们可以匹配一个目标对象而不是它们的标签。当演示时,getState()方法用来显示状态。我们同样可以用setState()改变CheckboxMenuItem 的状态。
我们可能会认为一个菜单可以合理地置入超过一个的菜单条中。这看似合理,因为所有我们忽略的菜单条的add()方法都是一个句柄。然而,如果我们试图这样做,这个结果将会变得非常的别扭,而远非我们所希望得到的结果。(很难知道这是一个编程中的错误或者说是他们试图使它以这种方法去运行所产生的。)这个例子同样向我们展示了为什么我们需要建立一个应用程序以替代程序片。(这是因为应用程序能支持菜单,而程序片是不能直接使用菜单的。)我们从帧处继承代替从程序片处继承。另外,我们为类建一个构建器以取代init()安装事件。最后,我们创建一个main()方法并且在我们建的新型对象里,调整它的大小,然后调用show()。它与程序片只在很小的地方有不同之处,然而这时它已经是一个独立的设置窗口应用程序并且我们可以使用菜单。
对话框是一个从其它窗口弹出的窗口。它的目的是处理一些特殊的争议和它们的细节而不使原来的窗口陷入混乱之中。对话框大量在设置窗口的编程环境中使用,但就像前面提到的一样,鲜于在程序片中使用。我们需要从对话类处继承以创建其它类型的窗口、像帧一样的对话框。和窗框不同,对话框不能拥有菜单条也不能改变光标,但除此之外它们十分的相似。一个对话框拥有布局管理器(默认的是BorderLayout 布局管理器)和过载action()等等,或用handleEvent() 去处理事件。我们会注意到handleEvent()的一个重要差异:当WINDOW_DESTORY 事件发生时,我们并不希望关闭正在运行的应用程序!
相反,我们可以使用对话窗口通过调用dispace()释放资源。在下面的例子中,对话框是由定义在那儿作为类的ToeButton的特殊按钮组成的网格构成的(利用GridLayout 布局管理器)。ToeButton 按钮围绕它自已画了一个帧,并且依赖它的状态:在空的中的“X”或者“O”。它从空白开始,然后依靠使用者的选择,转换成“X”或“O”。但是,当我们单击在按钮上时,它会在“X”和“O”之间来回交换。(这产生了
一种类似填字游戏的感觉,当然比它更令人讨厌。)另外,这个对话框可以被设置为在主应用程序窗口中为很多的行和列变更号码。
import java.awt.*;
class ToeButton extends Canvas {
int state= ToeDialog.BLANK;
ToeDialog parent;
ToeButton(ToeDialog parent) {
this.parent = parent;
}
public voidpaint(Graphics g){
int x1 = 0;
int y1 = 0;
int x2 = size().width - 1;
int y2 = size().height - 1;
g.drawRect(x1, y1, x2,y2);
x1 = x2 / 4;
y1 = y2 / 4;
int wide = x2/ 2;
int high = y2/ 2;
if (state == ToeDialog.XX) {
g.drawLine(x1, y1, x1+ wide,y1+ high);
g.drawLine(x1, y1 + high,x1+ wide,y1);
}
if (state == ToeDialog.OO) {
g.drawOval(x1, y1, x1+ wide/ 2, y1+ high/ 2);
}
}
public booleanmouseDown(Event evt,intx, int y) {
if (state == ToeDialog.BLANK) {
state = parent.turn;
parent.turn = (parent.turn == ToeDialog.XX ? ToeDialog.OO : ToeDialog.XX);
} else
state = (state == ToeDialog.XX ? ToeDialog.OO : ToeDialog.XX);
repaint();
return true;
}
}
class ToeDialog extends Dialog {
// w = numberof cells wide
// h = numberof cells high
static finalintBLANK= 0;
static finalintXX= 1;
static finalintOO= 2;
int turn= XX;// Start with x's turn
public ToeDialog(Frame parent, intw, int h) {
super(parent, "The gameitself", false);
setLayout(new GridLayout(w, h));
for (int i = 0; i < w * h; i++)
add(new ToeButton(this));
resize(w * 50, h * 50);
}
public booleanhandleEvent(Event evt){
if (evt.id== Event.WINDOW_DESTROY)
dispose();
else
return super.handleEvent(evt);
return true;
}
}
public class ToeTest extends Frame {
TextField rows = new TextField("3");
TextField cols = new TextField("3");
public ToeTest() {
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("North", p);
add("South", new Button("go"));
}
public booleanhandleEvent(Event evt){
if (evt.id== Event.WINDOW_DESTROY)
System.exit(0);
else
return super.handleEvent(evt);
return true;
}
public booleanaction(Event evt,Object arg){
if (arg.equals("go")){
Dialog d = new ToeDialog(this, Integer.parseInt(rows.getText()), Integer.parseInt(cols.getText()));
d.show();
} else
return super.action(evt, arg);
return true;
}
public staticvoidmain(String[] args){
Frame f = new ToeTest();
f.resize(200, 100);
f.show();
}
} /// :~
ToeButton 类保留了一个句柄到它ToeDialog 型的父类中。正如前面所述,ToeButton 和ToeDialog 高度的结合因为一个ToeButton 只能被一个ToeDialog 所使用,但它却解决了一系列的问题,事实上这实在不是一个糟糕的解决方案因为没有另外的可以记录用户选择的对话类。当然我们可以使用其它的制造ToeDialog.turn(ToeButton 的静态的一部分)方法。这种方法消除了它们的紧密联系,但却阻止了我们一次拥有多个ToeDialog(无论如何,至少有一个正常地运行)。
paint()是一种与图形有关的方法:它围绕按钮画出矩形并画出“X”或“O”。这完全是冗长的计算,但却十分的直观。
一个鼠标单击被过载的mouseDown()方法所俘获,最要紧的是检查是否有事件写在按钮上。如果没有,父窗口会被询问以找出谁选择了它并用来确定按钮的状态。值得注意的是按钮随后交回到父类中并且改变它的选择。如果按钮已经显示这为“X”和“O”,那么它们会被改变状态。我们能注意到本书第三章中描述的在这些计算中方便的使用的三个一组的If-else。当一个按钮的状态改变后,按钮会被重画。
ToeDialog 的构建器十分的简单:它像我们所需要的一样增加一些按钮到GridLayout 布局管理器中,然后调整每个按钮每边大小为50 个像素(如果我们不调整窗口,那么它就不会显示出来)。注意handleEvent()正好为WINDOW_DESTROY 调用dispose(),因此整个应用程序不会被关闭。
ToeTest 设置整个应用程序以创建TextField(为输入按钮网格的行和列)和“go”按钮。我们会领会action()在这个程序中使用不太令人满意的“字符串匹配”技术来测试按钮的按下(请确定我们拼写和大写都是正确的!)。当按钮按下时,TextField 中的数据将被取出,并且,因为它们在字符串结构中,所以需要利用静态的Integer.paresInt()方法来转变成中断。一旦对话类被建立,我们就必须调用show()方法来显示和激活它。
我们会注意到ToeDialog 对象赋值给一个对话句柄 d。这是一个上溯造型的例子,尽管它没有真正地产生重要的差异,因为所有的事件都是show()调用的。但是,如果我们想调用ToeDialog 中已经存在的一些方法,我们需要对ToeDialog 句柄赋值,就不会在一个上溯中丢失信息。
在一些操作系统中拥有许多的特殊内建对话框去处理选择的事件,例如:字库,颜色,打印机以及类似的事件。几乎所有的操作系统都支持打开和保存文件,但是,Java 的FileDialog 包更容易使用。当然这会不再检测所有使用的程序片,因为程序片在本地磁盘上既不能读也不能写文件。(这会在新的浏览器中交换程序片的信任关系。)
下面的应用程序运用了两个文件对话类的窗体,一个是打开,一个是保存。大多数的代码到如今已为我们所熟悉,而所有这些有趣的活动发生在两个不同按钮单击事件的action()方法中。
import java.awt.*;
public class FileDialogTest extends Frame {
TextField filename = new TextField();
TextField directory= newTextField();
Button open = new Button("Open");
Button save = new Button("Save");
public FileDialogTest() {
setTitle("File Dialog Test");
Panel p = new Panel();
p.setLayout(new FlowLayout());
p.add(open);
p.add(save);
add("South", p);
directory.setEditable(false);
filename.setEditable(false);
p = new Panel();
p.setLayout(new GridLayout(2, 1));
p.add(filename);
p.add(directory);
add("North", p);
}
public booleanhandleEvent(Event evt){
if (evt.id== Event.WINDOW_DESTROY)
System.exit(0);
else
return super.handleEvent(evt);
return true;
}
public booleanaction(Event evt,Object arg){
if (evt.target.equals(open)) {
// Two arguments, defaults to open file:
FileDialog d = new FileDialog(this, "Whatfile do you want to open?");
d.setFile("*.java"); // Filename filter
d.setDirectory("."); // Current directory
d.show();
String openFile;
if ((openFile = d.getFile()) != null) {
filename.setText(openFile);
directory.setText(d.getDirectory());
} else {
filename.setText("You pressed cancel");
directory.setText("");
}
} else if (evt.target.equals(save)) {
FileDialog d = new FileDialog(this, "Whatfile do you want to save?", FileDialog.SAVE);
d.setFile("*.java");
d.setDirectory(".");
d.show();
String saveFile;
if ((saveFile = d.getFile()) != null) {
filename.setText(saveFile);
directory.setText(d.getDirectory());
} else {
filename.setText("You pressed cancel");
directory.setText("");
}
} else
return super.action(evt, arg);
return true;
}
public staticvoidmain(String[] args){
Frame f = new FileDialogTest();
f.resize(250, 110);
f.show();
}
} /// :~
对一个“打开文件”对话框,我们使用构建器设置两个自变量;首先是父窗口句柄,其次是FileDialog 标题条的标题。setFile()方法提供一个初始文件名--也许本地操作系统支持通配符,因此在这个例子中所有的.java 文件最开头会被显示出来。setDirectory()方法选择文件决定开始的目录(一般而言,操作系统允许用户改变目录)。
show()命令直到对话类关闭才返回。FileDialog 对象一直存在,因此我们可以从它那里读取数据。如果我们调用getFile()并且它返回空,这意味着用户退出了对话类。文件名和调用getDirectory()方法的结果都显示在TextFields 里。
按钮的保存工作使用同样的方法,除了因为FileDialog 而使用不同的构建器。这个构建器设置了三个自变量并且第三的一个自变量必须为FileDialog.SAVE 或FileDialog.OPEN。