GUI2:下篇
5. 其他组件简介
(1) 对话框——Dialog
在介绍对话框组件之前,我们首先做一个简单的练习。
需求:根据用户指定的路径,列出并显示该路径下的所有文件夹和文件路径及名称。
思路:
在说明思路以前,我们首先简单了解以下文本区域——TextArea。TextArea是显示文本的多行区域。可以将它设置为允许编辑或只读。文本区域和文本框有些类似,区别就是能够显示的文本的行数不同。关于该组件的更为具体的用法和所具备的方法,大家可以直接参考下面的演示代码。
首先,定义所有所需组件对象的引用,包括主窗体、文本框、按钮和文本区域。其中,文本框用于输入用户指定的路径,按钮用于触发路径查找方法,文本区域用于显示所有路径名和文件名。
第二,创建各个组件的对象,并为其设置各种参数,最后将所有组件按顺序添加到主窗体中。
第三,开始定义各个组件的事件监听代码。为按钮添加活动监听器,当接收到活动事件后,首先获取文本框中输入的文本(也即地址),然后通过递归的方式获取到所有文件夹和其中文件的地址及名称的字符串,并将其显示在文本区域中。再为文本框添加键盘事件监听器,当检测到“Enter”被按下后,执行与以上相同的动作。
代码:
代码1:
import java.awt.*;
import java.awt.event.*;
import java.io.*;
class MyFrame
{
private Frame f;
private Button but;
private TextField tf;
private TextArea ta;
MyFrame()
{
init();
}
public void init()
{
f = new Frame("Prime");
f.setBounds(450,300,600,463);
f.setLayout(newFlowLayout());
tf = new TextField(45);
tf.setFont(newFont(Font.DIALOG,Font.PLAIN,20));
f.add(tf);
but = new Button("查找");
but.setFont(newFont(Font.DIALOG,Font.PLAIN,20));
f.add(but);
//创建文本区域对象,并为其制定行数和列数
ta = new TextArea(15,51);
ta.setFont(newFont(Font.DIALOG,Font.PLAIN,20));
f.add(ta);
myEvent();
f.setVisible(true);
}
private void myEvent()
{
f.addWindowListener(newWindowAdapter(){
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
but.addActionListener(newActionListener(){
public void actionPerformed(ActionEvent e)
{
/*
获取到文本框中用户指定的文件路径字符串以后
分别将文本框和文本区域中的内容设置为空
*/
String path = tf.getText();
tf.setText("");
listFile(path);
}
});
//为文本框添加键盘事件监听器
tf.addKeyListener(newKeyAdapter(){
public void keyPressed(KeyEvent e)
{
/*
当监听器监听到的键盘事件中存储的键值为“Enter”时
同样触发显示指定路径中文件夹和文件名的方法
*/
if(e.getKeyCode() == KeyEvent.VK_ENTER)
{
String path = tf.getText();
tf.setText("");
listFile(path);
}
}
});
ta.addKeyListener(newKeyAdapter(){
public void keyPressed(KeyEvent e)
{
e.consume();
}
});
}
//显示指定路径下的所有文件
private void listFile(String path)
{
File dir = new File(path);
if(dir.exists() && dir.isDirectory())
{
ta.setText("");
String[] files = dir.list();
for(Stringfile : files)
{
ta.append(file+"\n");
}
}
}
}
class AwtDemo
{
public static void main(String[] args)
{
MyFramem f = new MyFrame();
}
}
代码说明:
(1) 以上代码中,为文本框、按钮和文本区域设置了字体,关于这一部分代码,请大家自行查阅Font类的说明和使用方法。
(2) 我们以向按钮添加的活动监听器为例,当接收到活动事件对象后,首先通过文本框的getText()方法(继承自父类TextComponent),获取输入到文本框中的路径字符串对象,然后将文本框中的内容清空,方便用户下一次的输入,最后将该字符串传递给了专门用于列出并显示指定路径下所有文件夹和文件名的方法listFile(String path)。该方法中,通过调用TestArea的setText方法将文本区域中的内容清空,这样可以使显示内容不致太过臃肿。另外,需要注意的是,将文件名显示在文本区域时,应使用TextArea的append()方法,作用为追加内容,而不能是setText(),否则之前的显示内容将被覆盖。
(3) 代码1中还为文本框添加了键盘事件监听器,当检测到“Enter”键被按下时,同样能够触发列出指定路径中文件名的功能。
代码1中,当输入错误路径时,应该给出适当地错误提示,但这个错误提示不能再像以前那样显示在命令行中,因为面向普通用户的软件一般是没有命令行的,因此我们就可以使用对话框(Dialog)组件来实现错误提示功能。
查阅Dialog组件类的API文档,该组件中定义了大量的构造方法重载形式。其中的一种形式为Dialog(Frame owner),意思是将该对话框所属的窗体组件对象作为参数传递进来。这么定义的原因是,对话框通常不会单独存在的,是因为窗体中发生了一些事情,需要弹出一个对话框来提示用户,因此对话框总是依赖于一个窗体存在。
想要实现弹窗提示信息,除了需要对话框组件以外,还需要显示在对话框中的提示文本以及用于关闭对话框的按钮,因此还需要一个Lable组件和Button组件。代码如下,
代码2:
import java.awt.*;
import java.awt.event.*;
import java.io.*;
class MyFrame
{
private Frame f;
private Button but;
private TextField tf;
private TextArea ta;
MyFrame()
{
init();
}
public void init()
{
f = new Frame("Prime");
f.setBounds(450,300,600,463);
f.setLayout(newFlowLayout());
tf = new TextField(45);
tf.setFont(newFont(Font.DIALOG,Font.PLAIN,20));
f.add(tf);
but = new Button("查找");
but.setFont(newFont(Font.DIALOG,Font.PLAIN,20));
f.add(but);
//创建文本区域对象,并为其制定行数和列数
ta = new TextArea(15,51);
ta.setFont(newFont(Font.DIALOG,Font.PLAIN,20));
f.add(ta);
myEvent();
f.setVisible(true);
}
private void myEvent()
{
f.addWindowListener(newWindowAdapter(){
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
but.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{
/*
获取到文本框中用户指定的文件路径字符串以后
分别将文本框和文本区域中的内容设置为空
*/
String path = tf.getText();
tf.setText("");
listFile(path);
}
});
//为文本框添加键盘事件监听器
tf.addKeyListener(newKeyAdapter(){
public void keyPressed(KeyEvent e)
{
/*
当监听器监听到的键盘事件中存储的键值为“Enter”时
同样触发显示指定路径中文件夹和文件名的方法
*/
if(e.getKeyCode() == KeyEvent.VK_ENTER)
{
String path = tf.getText();
tf.setText("");
listFile(path);
}
}
});
ta.addKeyListener(new KeyAdapter(){
public void keyPressed(KeyEvent e)
{
e.consume();
}
});
}
//显示指定路径下的所有文件
private void listFile(String path)
{
File dir = new File(path);
if(dir.exists() && dir.isDirectory())
{
ta.setText("");
String[] files = dir.list();
for(String file : files)
{
ta.append(file+"\n");
}
}
else
//当输入错误路径时,调用提示错误信息的方法
errInfo(f,path);
}
private void errInfo(Frame f, String path)
{
//创建对话框所需的各个组件
final Dialog dia = new Dialog(f,"错误提示信息",true);
dia.setBounds(350,400,300,120);
dia.setLayout(newFlowLayout(FlowLayout.CENTER));
Button but = new Button("确定");
but.setFont(newFont(Font.DIALOG,Font.PLAIN,16));
Label lb = new Label();
lb.setFont(newFont(Font.DIALOG,Font.PLAIN,16));
//为Label设定提示文本
lb.setText("找不到:"+path+"。 请重新输入。");
dia.add(lb);
dia.add(but);
dia.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
//不显示对话框等同于将其关闭
dia.setVisible(false);
}
});
but.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
dia.setVisible(false);
}
});
dia.setVisible(true);
}
}
class AwtDemo2
{
public static void main(String[] args)
{
MyFrame mf = new MyFrame();
}
}
代码说明:
(1) 代码2专门为弹出错误提示功能定义了一个方法,并在方法内完成了各个组件的创建、初始化,设置参数,以及添加事件监听机制等动作。这是因为,在没有出现错误操作的时候,没有必要在打开主窗体时就完成以上所有步骤,这实际上是对内存的一个优化。
(2) 创建对话框组件对象时,我们除了为其初始化了所属主窗体、对话框标题,还向构造方法中传递了布尔值true,它其实代表了一种模式,表示如果不关闭本对话框,将不能操作主窗体,传递false时作用相反。并且,对话框既然作为窗体的子类,同样需要设置初始位置、大小以及布局。
(3) 为提供多样化的操作方式,分别为“转到”按钮和文本框添加了活动监听器和键盘事件监听,鼠标按下按钮或者在文本框中按下回车键,均可以触发目录列出功能。
(4) Label组件用于在窗体中显示文本,可调用setText方法为其设定提示信息。
(5) 分别为对话框本身和按钮添加活动监听器和窗口事件监听器,两者的触发动作是相同的,将对话框设置为不可见,相当于将其关闭。但不能像关闭窗体那样直接退出虚拟机,否则主窗体本身也将被关闭。
(2) 菜单——Menu
在以上演示的代码中,我们用到了窗体、按钮、文本框、文本区域以及标签等组件,下面我们来演示GUI中非常常见的组件——菜单的创建以及设置方法。
大家如果去观察一个软件的菜单就会发现,菜单首先是由一个菜单栏组成,然后在菜单栏中有很多菜单,每个菜单中又有若干菜单项,也有可能其中的某个菜单项本身也是一个菜单,其中也包含了一些菜单项。那么总结起来,一个菜单的结构从大到小包含了菜单栏、菜单以及菜单项。
按照这一顺序,我们首先来看表示菜单栏组件的MenuBar类的API文档。MenuBar类封装绑定到窗体的菜单栏的平台概念。为了将该菜单栏与Frame对象关联,可以调用该窗体的setMenuBar方法,也就是说,若要向窗体添加菜单栏,应调用窗体的setMenuBar方法而不是一般的add方法。MenuBar类的第一个方法就是add(Menum),向菜单栏中添加菜单Menu。而Menu组件的第一个方法也是添加,只不过是添加菜单项——add(MenuItem mi),参数类型MenuItem表示的就是菜单项。查看菜单组件的继承体系可知,Menu本身是MenuItem的子类,这就表示可以向一个菜单中添加菜单项,也可以多态地添加菜单——子菜单。以下代码3演示了菜单最基本的创建及设置过程,
代码2:
import java.awt.*;
import java.awt.event.*;
class MyFrame
{
private Frame f;
private MenuBar mb;
private Menu m;
private MenuItem closeItem;
MyFrame()
{
init();
}
private void init()
{
f = new Frame("Prime");
f.setBounds(450,200,600,450);
//分别创建菜单栏、菜单以及菜单项
mb = new MenuBar();
m = new Menu("文件");
closeItem = new MenuItem("退出");
//按照从小到大的顺序将菜单组件“组装”好
m.add(closeItem);
mb.add(m);
f.setMenuBar(mb);
event();
f.setVisible(true);
}
private void event()
{
f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
//为“退出”菜单项添加活动监听
closeItem.addActionListener(newActionListener()
{
public void actionPerformed(ActionEvent e)
{
System.exit(0);
}
});
}
}
class MenuDemo
{
public static void main(String[] args)
{
new MyFrame();
}
}
通过以上代码就创建了最为基本的菜单结构,并为“退出”菜单项添加了活动监听器,可以实现窗口的退出功能。查阅MenuItem组件类的API文档方法摘要可知,该组件仅可以添加活动监听器,对应方法为addActionListener(ActionListener l),因此通过键盘也可以操作菜单。
在代码3的基础上,向文件菜单中继续添加子菜单,而在子菜单中又包含一个子条目,形成嵌套的菜单结构,代码如下,
代码3:
import java.awt.*;
import java.awt.event.*;
class MyFrame
{
private Frame f;
private MenuBar mb;
private Menu m, subMenu;
private MenuItem closeItem, subItem;
MyFrame()
{
init();
}
private void init()
{
//---
mb = new MenuBar();//菜单栏
m = new Menu("文件");//添加到菜单栏的菜单
subMenu = new Menu("子菜单");//添加到菜单中的子菜单
subItem = new MenuItem("子条目");//添加到子菜单中的子条目
closeItem = new MenuItem("退出");//添加到菜单中的子条目
//按照从小到大的顺序将菜单组件“组装”好
subMenu.add(subItem);
m.add(subMenu);
m.add(closeItem);
mb.add(m);
f.setMenuBar(mb);
//---
}
private void event()
{
//---
}
}
class MenuDemo2
{
//---
}
以上代码中只呈现了菜单的创建以及设置相关代码,其余部分与代码3相同。可以结合下面的菜单组件关系图来理解各菜单组件的组合过程,
最终的效果图如下,
当然菜单的嵌套结构可以定义多层,如下所示,
以上多层嵌套菜单结构在实际应用中也是很常见的,大家可以按照代码4的思路自行构建多层嵌套菜单结构。
(3) 文件对话框——FileDialog
有些软件比如记事本、Office系列软件可以实现打开指定路径下的文件,以及保存当前文件到指定路径下的功能。那么这些功能的实现就需要在创建菜单的基础上结合文件对话框——FileDialog来实现了。
查阅FileDialog组件类的API文档:FileDialog组件类显示一个对话框窗口,用户可以从中选择文件。那么该组件就是我们在打开\保存文件时常见的那个对话框,这个对话框已经基于平台设计好了外观以及内部的功能,程序员不必再重复设计。
FileDialog的构造方法摘要与Dialog基本相同,都需要为它指定一个所属的窗体或者对话框,其中有一个构造方法FileDialog(Frame parent, String title, intmode),再次涉及到了模式的问题。FileDialog组件的模式与Dialog不同,它是用于指定该文件对话框的功能是用于打开文件还是保存文件的,因此FileDialog类中定义了两个对应的字段——FileDialog.LOAD和FileDialog.SAVE,如果不指定模式,默认设置打开功能。以下代码演示了最简单的创建并显示具有“打开”和“保存”功能的文件对话框的方法,
代码4:
import java.awt.*;
import java.awt.event.*;
class MyFrame
{
private Frame f;
private MenuBar mb;
private Menu fileMenu;
private MenuItem open, save, close;
MyFrame()
{
init();
}
private void init()
{
f = new Frame("Prime");
f.setBounds(450,200,600,450);
mb = new MenuBar();
fileMenu = new Menu("文件");
open = new MenuItem("打开");
save = new MenuItem("保存");
close = new MenuItem("退出");
fileMenu.add(open);
fileMenu.add(save);
fileMenu.add(close);
mb.add(fileMenu);
f.setMenuBar(mb);
event();
f.setVisible(true);
}
//创建并显示具有“打开”功能的文件对话框
private void load()
{
FileDialog open = new FileDialog(f, "打开", FileDialog.LOAD);
open.setVisible(true);
//打开功能待定义
}
//创建并显示具有“保存”功能的文件对话框
private void save()
{
FileDialog save = new FileDialog(f, "保存", FileDialog.SAVE);
save.setVisible(true);
//保存功能待定义
}
private void event()
{
f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
close.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
System.exit(0);
}
});
open.addActionListener(newActionListener()
{
publicvoid actionPerformed(ActionEvent e)
{
load();
}
});
save.addActionListener(newActionListener()
{
publicvoid actionPerformed(ActionEvent e)
{
save();
}
});
}
}
class MenuDemo2
{
public static void main(String[] args)
{
new MyFrame();
}
}
代码4中,为了优化内存,为两种文件对话框分别定义了初始化方法,当“打开”和“保存”菜单项触发了活动事件时,才会创建并显示两个文件对话框。当然,仅仅是实现两种文件对话框的弹出功能是不够的,我们应真正实现对文件的打开和保存功能。
1) 打开文件
那么以文本文件为例,我们可以将打开的文本文件内容显示在一个文本区域中,可以参考“记事本”软件的设计思路。我们首先实现文件的打开功能。
代码5:
/*
首先在MyFrame类的成员位置上定义一个File类类型变量file,用于指向封装有用户操作文件的File对象
然后将窗体的布局设置为边界式布局,这样文本区域可以完全填充窗体,显示效果较好
其次再在MyFrame类的成员位置上定义TextArea类类型变量
并在init方法中初始化TextArea组件对象,并将其添加到窗体中
*/
//创建并显示具有“打开”功能的文件对话框
private void load()
{
FileDialog open = new FileDialog(f, "打开", FileDialog.LOAD);
open.setVisible(true);
//获取“打开”文件对话框所选取文件的路径与文件名,以创建对应的File对象
String dir = open.getDirectory();
String fileName = open.getFile();
//判断用户是否选择了任何文件
if(dir == null || fileName == null)
return;
//当用户确实选择了一个文件时,将当前显示文件清空
ta.setText("");
file = new File(dir, fileName);
BufferedReader bufr = null;
String line = null;
//按照常规的方式读取文本文件,并将每行文本追加到文本区域中
try
{
bufr = new BufferedReader(new FileReader(file));
while((line = bufr.readLine()) != null)
{
ta.append(line+'\n');
}
}
catch(IOException e)
{
throw new RuntimeException("文件读取失败!");
}
finally
{
try
{
bufr.close();
}
catch(IOException e)
{
throw new RuntimeException("读取流关闭失败!");
}
}
}
以上代码通过具有“打开”功能的文件对话框实现了对文本文件的打开操作,而关于文件的保存功能还未定义,将在下面的内容中介绍。
代码说明:
(1) 文件对话框类FileDialog对外提供了用于获取用户所选文件的路径和文件名的方法——getDirectory()以及getFile(),以上代码就通过这两个方法,创建了对应的File对象。
(2) 在创建用户所选文件的File对象前,需要判断getDirectory()和getFile()方法返回的字符串是否为空,因为有可能用户并没有选择任何文件,并通过“取消”按钮退出了文件对话框,此时getFile()方法的返回值为空,那么也就无法成功创建File对象,而抛出异常。相对应的处理方式是,当路径字符串或文件名字符串中任意一个为空时,直接通过“return”关键字退出load方法即可。
(3) 为了避免文本区域中的显示内容过多,当确定用户选定了一个文件以后,将当前显示文本内容通过TextArea类的setText()方法清空掉。
2) 保存文件
通过以上代码实现了对文本文件的打开功能,那么相应的,还应提供保存功能。保存分为三种情况:
第一种,如果用户新建了一个本不存在的文件,那么点击保存时必须要弹出“保存”文件对话框,提示用户选择保存文件的路径和并设置文件名;
第二种,若用户打开的是已经存在的文件,对其进行修改后,点击保存时就没有必要再弹出“保存”文件对话框了,因为文件所在路径和文件名是已知信息;
第三种,如果定义了“另存为”功能,那么“保存”文件对话框一定要弹出,此时用户必须重新设定文件保存路径和文件名。
我们首先针对前两种情况编写代码,代码如下,
代码6:
private void save()
{
/*
判断文件对象是否为空,若不为空
则表示file变量已指向了当前显示文件
因此无需打开“保存”文件对话框,直接进行保存动作即可
*/
if(file == null)
{
FileDialog save = new FileDialog(f, "保存", FileDialog.SAVE);
save.setVisible(true);
//获取代表用户指定的路径和文件名的字符串
String dir = save.getDirectory();
String fileName = save.getFile();
if(dir == null || fileName == null)
return;
file = new File(dir, fileName);
}
BufferedWriter bufw = null;
String line = null;
try
{
bufw = new BufferedWriter(new FileWriter(file));
//获取文件区域内的文本字符串,并写入到写入流中
bufw.write(ta.getText());
bufw.flush();
}
catch(IOException e)
{
throw new RuntimeException("文件写入失败!");
}
finally
{
try
{
if(bufw != null)
bufw.close();
}
catch(IOException e)
{
throw new RuntimeException("写入流关闭失败!");
}
}
}
代码说明:
(1) 以上代码中,在弹出“保存”文件对话框以前,首先要对file变量指向是否为空进行判断。这一动作的目的在于,如果该变量指向为空,则表示该程序在此前的操作中用户未指定过任何路径下的任何文件,那么此时如果用户点击保存按钮,就需要弹出“保存”对话框,并提示用户指定保存路径和文件名,完成保存动作。如果file变量不为空,那么它所指向的File对象必然是当前文本区域所显示的文本文件,这是因为无论用户是新建了一个文件还是打开了一个已有文件,都会创建对应的File对象并被file变量所指向。
(2) 保存文件的原理实际就是将文本区域的字符串写入到与字符写入流关联起来的文件中,因此创建一个BufferedWriter,并将调用文本区域getText()方法的返回值写入到其中即可。
(3) “另存为”功能无需判断file变量是否有所指向,因为无论当前显示文本是用户打开的文件还是新建的,均要重新定义保存路径和文件名,因此“保存”文件对话框一定要弹出。所以,“另存为”的实现只需将save方法中判断file变量是否指向为null的语句删除即可。
3) 创建可执行文件
将代码4、5、6合并起来就完成了一个简易版本的“记事本”软件,具备对文本文件的打开、保存以及另存为功能。虽然功能都具备了,但是每次执行该程序都需要通过命令行对其进行操作,非常麻烦,那么根据以前介绍过的方法,我们可以通过命令行的“jar”命令,将一个包内的字节码文件打包为一个“.jar”可执行文件,就能够直接双击执行了。
那么首先,需要在代码的头部通过“package”关键字指定包名,这一部分曾经进行介绍,这里不再赘述,假设包名为“mynotepad”。
其次,不能直接通过“jar”命令将“mynotepad”包中的类文件封装为“.jar”可执行文件,因为封装其内的字节码文件很多,但其中只有一个字节码文件中包含主函数,也就是可以独立运行的字节码文件,这里就是代码4中的MenuDemo2.class”文件。因此需要通过一个配置信息文件,告诉系统该“.jar”文件的应用程序入口是哪个字节码文件。
第三,在包所在目录下创建配置信息文件,文件名及格式任意,配置信息为:
Main-Class: mynotepad.MenuDemo2
\r\n
以上配置信息表示,主类,也即具备主函数的类是包含在mynotepad包中的MenuDemo2。通过这一配置信息系统就确定了应用程序的入口。该配置信息的书写格式非常严格,大小写严格区分,第一行冒号后必须要有一个空格,并且写完一行配置信息以后后面一定要换行,第二行的“\r\n”就表示回车符。
第四,通过命令行命令将配置信息文件和包含有可执行字节码文件的包,封装一个“.jar”文件,命令为“jar –cvfm MyNotepad.jar config.txtmynotepad”,-cvfm是jar命令参数,其中m的作用是用于导入用户指定的配置信息。通过该命令在当前目录下创建了名为“MyNotepad.jar”的可执行文件。可能有的朋友双击该文件还是不能执行,这就需要进行注册表的设置,只需在注册表“HKEY_CLASSES_ROOT\Applications\javaw.exe\shell\open\command”目录中项的值设置为”javaw.exe所在目录\javaw.exe”–jar “%1”即可。
本文详细介绍了GUI编程中的其他组件,包括对话框(Dialog)、菜单(Menu)和文件对话框(FileDialog)。对话框用于错误提示,菜单栏结构包括菜单和菜单项,文件对话框用于打开和保存文件。文中通过示例代码展示了如何创建和使用这些组件,包括文件的打开、保存和创建可执行文件的功能。
3903

被折叠的 条评论
为什么被折叠?



