|
下面的是这个界面的一个原始草图:
正如你所看到的,最终的结果看上去和计划的想法完全一样。
你应该能看到在草图里有一些线,这些线是用来把总界面分成若干行和列的,这样你就很清楚每一个组件放置的格子位置。这就是GridBagLayout里"格"的那一部分,而图上的数字就是格的号码。
在某种意义上说, 我们可以把GridBagLayout想象成为早些年的HTML3和4,它们都是基于表的布局,Grid的概念就类似rowspan和colspan的意思,只不过换了个名字罢了。
随着我们的界面和表格的设置完成,是时候该进行界面布局并开始写代码了。
工作过程
这一节我假定你已经了解了基本的窗口和组件创建知识。
通过这篇文章我们最终能在一个frame中布局组件,我们将在以后的文章对界面进行改进使它更适用。因此,为了了解这整个工作的过程,我们列出了所有的目标代码。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GridBagWindow extends JFrame {
private JButton searchBtn;
private JComboBox modeCombo;
private JLabel tagLbl;
private JLabel tagModeLbl;
private JLabel previewLbl;
private JTable resTable;
private JTextField tagTxt;
public GridBagWindow() {
Container contentPane = getContentPane();
GridBagLayout gridbag = new GridBagLayout();
contentPane.setLayout(gridbag);
GridBagConstraints c = new GridBagConstraints();
//setting a default constraint value
c.fill =GridBagConstraints.HORIZONTAL;
tagLbl = new JLabel("Tags");
c.gridx = 0; //x grid position
c.gridy = 0; //y grid position
gridbag.setConstraints(tagLbl, c); //associate the label with a constraint object
contentPane.add(tagLbl); //add it to content pane
tagModeLbl = new JLabel("Tag Mode");
c.gridx = 0;
c.gridy = 1;
gridbag.setConstraints(tagModeLbl, c);
contentPane.add(tagModeLbl);
tagTxt = new JTextField("plinth");
c.gridx = 1;
c.gridy = 0;
c.gridwidth = 2;
gridbag.setConstraints(tagTxt, c);
contentPane.add(tagTxt);
String[] options = {"all", "any"};
modeCombo = new JComboBox(options);
c.gridx = 1;
c.gridy = 1;
c.gridwidth = 1;
gridbag.setConstraints(modeCombo, c);
contentPane.add(modeCombo);
searchBtn = new JButton("Search");
c.gridx = 1;
c.gridy = 2;
gridbag.setConstraints(searchBtn, c);
contentPane.add(searchBtn);
resTable = new JTable(5,3);
c.gridx = 0;
c.gridy = 3;
c.gridwidth = 3;
gridbag.setConstraints(resTable, c);
contentPane.add(resTable);
previewLbl = new JLabel("Preview goes here");
c.gridx = 0;
c.gridy = 4;
gridbag.setConstraints(previewLbl, c);
contentPane.add(previewLbl);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public static void main(String args[]) {
GridBagWindow window = new GridBagWindow();
window.setTitle("GridBagWindow");
window.pack();
window.setVisible(true);
}
}
构造方法前的代码都不是很特殊,都是一些相当标准的import和变量定义。但是进入构造方法后,事情就变得有趣了。
Container contentPane = getContentPane();
GridBagLayout gridbag = new GridBagLayout();
contentPane.setLayout(gridbag);
我们以GridBagWindow的内容面板作为开始来创建一个GridBagLayout对象,准确地说,这个方法与过去我们所创建 GridLayout对象和BorderLayout对象的方法是一样的。那么,现在我们就开始来设置GridBagLayout对象使它作为内容面板的 布局。
GridBagConstraints c = new GridBagConstraints();
然后我要提到这整个进程中的一个独特的对象,那就是GridBagConstraints。这个对象在GridBagLayout中控制所 有被安置在其中组件的约束。为了把一个组件增加到你的GridBagLayout中去,你首先必须将它与一个GridBagConstraints对象建 立连接。
GridBagConstraints可以从11个方面来进行控制和操纵,也可以给你提供一些帮助。这些内容是:
- Gridx——组件的横向坐标
- Girdy——组件的纵向坐标
- Gridwidth——组件的横向宽度,也就是指组件占用的列数,这与HTML的colspan类似
- Gridheight——组件的纵向长度,也就是指组件占用的行数,这与HTML的rowspan类似
- Weightx——指行的权重,告诉布局管理器如何分配额外的水平空间
- Weighty——指列的权重,告诉布局管理器如何分配额外的垂直空间
- Anchor——告诉布局管理器组件在表格空间中的位置
- Fill——如果显示区域比组件的区域大的时候,可以用来控制组件的行为。控制组件是垂直填充,还是水平填充,或者两个方向一起填充
- Insets——指组件与表格空间四周边缘的空白区域的大小
- Ipadx—— 组件间的横向间距,组件的宽度就是这个组件的最小宽度加上ipadx值
- ipady—— 组件间的纵向间距,组件的高度就是这个组件的最小高度加上ipady值
可能对于一个组件的每一个实例你都需要为它建立一个单独的GridBagConstraints;然而,这种方法我们并不推荐使用。最好的方法是,当你调用它的时候把对象设置为默认值,然后针对于每一个组件改变其相应的域。
这个方法具有通用性,因为在一些域中,比如insets、padx、pady和fill这些域,对于每一个组件来说一般都是相同的,因此这样对一个域进行设置就会更轻松了,也能更轻松的在另外的组件中改变某些域的值。
如果在改变了某些域值之后,你想回到原始的域值的话,你应该在增加下一个组件之前进行改变。这种方法使你更容易明白你正在修改的内容,也能使你更容易明白在一连串对象中的这11个参数的作用。
也许你现在对这些内容还是一知半解,不过事实上一旦你理解了GridBagConstraints,值得安慰的是你以后做再困难的工作都会游刃有余了。
所以,如果我们已经明白了GridBagConstraints的详细用法了,那么现在就让我们来看看在实际应用中应该如何来实现它:
tagLbl = new JLabel("Tags");
c.gridx = 0; //x grid position
c.gridy = 0; //y grid position
gridbag.setConstraints(tagLbl, c); //设置标签的限制
contentPane.add(tagLbl); //增加到内容面板
我们所做的是示例我们的标签、分配给它一个格位置,将它与一个约束对象联系起来并把它增加到我们的内容面板中。
tagModeLbl = new JLabel("Tag Mode");
c.gridx = 0;
c.gridy = 1;
gridbag.setConstraints(tagModeLbl, c);
contentPane.add(tagModeLbl);
请注意,虽然我们已经在我们的约束对象中把gridx的值设置为0,但是在这里我们仍然要对它进行重新设置——这样做没有其它原因,只是为了增加可读性。
下面,我们增加一个文本域以便能存储我们希望能搜索到的关键字,再增加一个组合框以便用来搜索多个关键字。除了我们希望的文本域有两列之外,这个概念其他的方面都与上面所说的是相同的,所以,我们需要在增加组合框之前重新设置文本域的值。
tagTxt = new JTextField("plinth");
c.gridx = 1;
c.gridy = 0;
c.gridwidth = 2;
gridbag.setConstraints(tagTxt, c);
contentPane.add(tagTxt);
String[] options = {"all", "any"};
modeCombo = new JComboBox(options);
c.gridx = 1;
c.gridy = 1;
c.gridwidth = 1;
gridbag.setConstraints(modeCombo, c);
contentPane.add(modeCombo);
做了这些之后,我们再在内容面板中增加一些其余的简单组件,这时候我们就能够浏览它了;其余的代码应该不会出现任何问题了。
到这个阶段,我们应该已经得到了一个类似于我们先前所设计的界面了。
7-4:JComboBox的使用:
类层次结构图:
java.lang.Object
--java.awt.Component
--java.awt.Container
--javax.swing.JComponent
--javax.swing.JComboBox
构造函数:
JComboBox():建立一个新的JComboBox组件。
JComboBox(ComboBoxModel aModel):用ListModel建立一个新的JComboBox组件。
JComboBox(Object[] items):利用Array对象建立一个新的JComboBox组件。
JComboBox(Vector items):利用Vector对象建立一个新的JComboBox组件。
7-4-1:建立一般的JComboBox:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Vector;
public class JComboBox1{
public static void main(String[] args){
JFrame f=new JFrame("JComboBox1");
Container contentPane=f.getContentPane();
contentPane.setLayout(new GridLayout(1,2));
String[] s = {"美国","日本","大陆","英国","法国","意大利","澳洲","韩国"};
Vector v=new Vector();
v.addElement("Nokia 8850");
v.addElement("Nokia 8250");
v.addElement("Motorola v8088");
v.addElement("Motorola v3850");
v.addElement("Panasonic 8850");
v.addElement("其它");
JComboBox combo1=new JComboBox(s);
combo1.addItem("中国");//利用JComboBox类所提供的addItem()方法,加入一个项目到此JComboBox中。
combo1.setBorder(BorderFactory.createTitledBorder("你最喜欢到哪个国家玩呢?"));
JComboBox combo2=new JComboBox(v);
combo2.setBorder(BorderFactory.createTitledBorder("你最喜欢哪一种手机呢?"));
contentPane.add(combo1);
contentPane.add(combo2);
f.pack();
f.show();
f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
}
}
7-4-2:利用ComboModel构造JComboBox:
如同JList一般,在JComboBox中也有一个构造函数是利用某种Model来构造。如下所示:
JComboBox(COmboBoxModel aModel)
ComboBoxModel是一个interface,里面定义了两个方法,分别是setSelectedItem()与getSelectedItem().这两个方法目的是让用
户选取某个项目后,可正确地显示出用户所选取的项目。下面是这两个方法的详细定义:
ComboBoxModel interface定义的方法:
Object getSelectedItem():返回所选取的项目值。
Void setSelectedItem(Object anItem):设置所选取的项目值.
与JList不同的是,JComboBox是利用ComboBoxModel,而不是ListModel.不过ComboBoxModel interface是继承ListModel interface
,因此若我们要利用ComboBoxModel来构造JComboBox,除了要实作ComboBoxModel的两个方法外,还必须实作ListModel的所定义的4个
方法,这样的做法可说相当麻烦。
在介绍JList时我们曾经提到AbstractListModel这个抽象类。这个抽象类实作了ListModel interface中的addListDataListener
()、removeListDataListener()这两个方法。因此若我们继承AbstractListModel,则可少掉实作这两个方法,只需要实作
getElementAt()、getSize()、setSelectedItem()与getSelectedItem()这4个方法。这样的作法就显得比较简单一点.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JComboBox2{
String[] s= {"美国","日本","大陆","英国","法国","意大利","澳洲","韩国"};
public JComboBox2(){
JFrame f=new JFrame("JComboBox2");
Container contentPane=f.getContentPane();
ComboBoxModel mode=new UserDefineComboBoxModel();
JComboBox combo=new JComboBox(mode);
combo.setBorder(BorderFactory.createTitledBorder("你最喜欢到哪个国家去玩?"));
contentPane.add(combo);
f.pack();
f.show();
f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
}
public static void main(String[] args){
new JComboBox2();
}
class UserDefineComboBoxModel extends AbstractListModel implements ComboBoxModel{
String item=null;
public Object getElementAt(int index){
return s[index++];
}
//由于继承AbstractListModel抽象类。因此我们分别在程序中实作了getElementAt()与getSize()方法。
public int getSize(){
return s.length;
}
//由于我们实现了ComboBoxModel interface.因此我们必须在程序中实作setSelectedItem()与getSelectedItem()方法.
public void setSelectedItem(Object anItem){
item=(String)anItem;
}
public Object getSelectedItem(){
return item;
}
}
}
当程序要show出JComboBox时,系统会先自动调用getSize()方法,看看这个JComboBox长度有多少,然后再调用getElementAt()
方法,将String Array s中的值填入JComboBox中。当用户选择项目时,系统会调用getSelectedItem()方法,返回所选取的项目,并
利用setSelectedItem()方法,将选取项目放在JComboBox最前端。
getElementAt()方法中的“index”参数,系统会自动由0计算,不过要自己作累加的操作,如程序中:
return s[index++];
如同JList一般,java对于JComboBox也提供了另一个类,DefaultComboBoxModel实体类。此类继承了AbstractListModel抽象类,也
实作了ComboBoxModel interface.因此你不需要再实作getSize()、getElementAt()、setSelectedItem()与getSelectedItem()方法。
利用DefaultComboBoxModel这个类我们可以很方便地做到动态更改JComboBox的项目值。当你没有必要自己定义特殊的ComboBoxModel
时,使用DefaultComboBoxModel就显得非常的方便,我们来看下面的例子:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JComboBox3{
String[] s = {"美国","日本","大陆","英国","法国","意大利","澳洲","韩国"};
public JComboBox3(){
JFrame f=new JFrame("JComboBox3");
Container contentPane=f.getContentPane();
ComboBoxModel mode=new AModel();
JComboBox combo=new JComboBox(mode);
combo.setBorder(BorderFactory.createTitledBorder("您最喜欢到哪个国家玩呢?"));
contentPane.add(combo);
f.pack();
f.show();
f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
}
public static void main(String[] args){
new JComboBox3();
}
class AModel extends DefaultComboBoxModel{
AModel(){
for (int i=0;i<s.length;i++)
addElement(s[i]);
}
}
}
1.由于AModel继承DefaultComboBoxModel实体类,由AModel可得到一个ComboBoxModel实体对象。
2.我们使AModel继承DefaultComboBoxModel实体类,因此就不需要再实作getElementAt()、getSize()、setSelectedItem()与
getSelectedItem()这4个方法,直接将所要的项目用addElement()方法加入即可。系统会自动将所加入的项目放进一个Vector
中,并在输出JComboBox时自动调用getSize()与getElementAt()方法。
7-4-3:建立有图像的JComboBox:
在上一节中我们利用ListCellRenderer interface在JList中加入Icon图像,而要在JComboBox中加入图像的方法也是一样的。
我们必须实作ListCellRenderer interface所定义的方法getListCellRendererComponent.以下为这个方法的定义:
要先了解ListCellRenderer interface.我们必须由这个interface所定义的方法,将图像画在JComboBox中的每个项目。
ListCellRenderer interface里只定义了一个方法,那就是getListCellRendererComponent,不过这个参数有点多,我们把它列出来
看看:
public Component getListCellRendererComponent(JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus)
list:即所要画上的图像的JComboBox组件。
value:JComboBox项目值,如JComboBox.getModel().getElementAt(index)所返回的值。
index:为JComboBox项目的索引值,由0开始。
isSelected与cellHasFocus:判断JComboBox中的项目是否有被选取或是有焦点置入。
上面这4个参数会在你设置JComboBox的绘图样式(setCellRenderer())时自动的由JComboBox组件提供,你只要关心怎么控制
getListCellRendererComponent()方法中的4个参数,而无需担心怎么参数传入。
要在JList中加入Icon图像的技巧就是将JComboBox中的每一个项目当作是JLabel,因为JLabel在使用文字与图像上非常的方便,要设置JComboBox的图像,
必须使用setRenderer(ListCellRenderer cellRenderer){注:我们在JList中画上图像是利用JList所提供的setCellRenderer(ListCellRenderer
cellRenderer)方法,读者请小心}这个方法。我们来看下面这个范例,你就能明白了!
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JComboBox4{
String[] s={"西瓜","苹果","草莓","香蕉","葡萄"};
public JComboBox4(){
JFrame f=new JFrame("JComboBox");
Container contentPane=f.getContentPane();
JComboBox combo=new JComboBox(s);
combo.setBorder(BorderFactory.createTitledBorder("你最喜欢吃哪些水果?"));
combo.setRenderer(new ACellRenderer());
combo.setMaximumRowCount(3);
contentPane.add(combo);
f.pack();
f.show();
f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
}
public static void main(String[] args){
new JComboBox4();
}
}
class ACellRenderer extends JLabel implements ListCellRenderer{
ACellRenderer(){
setOpaque(true);
}
public Component getListCellRendererComponent(JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus){
if (value!=null){
setText(value.toString());
setIcon(new ImageIcon(".\\icons\\fruit"+(index+1)+".jpg"));
}
if (isSelected){
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
}else{
setBackground(list.getBackground());
setForeground(list.getForeground());
}
return this;
}
}
各们读者在运行这个程序时会发现,即使JComboBox的选项中有图标,但在选后图标却不会显示在显示列中,原因是在上面程序中
我们以String Array s建立JComboBox:
JComboBox combo=new JComboBox(s);
String Array s里面放的只是水果名称,而并没有图标。当我们使用setRenderer()方法来JComboBox时,只会绘制JComboBox的
选项部份,而最后显示在JComboBox上的值还是以String Array s为依据。因此JComboBox显示列就只会显示文字而已,而不会显示出
图形。要解决这个问题,我们必须改变JComboBox所传入的参数内容,也就是将原来的String Array s更改成具有图形的数据项。在
此我们是利用JComboBox(Object[] items)来建立有图像的JComboBox,我们所传进去的Object Array不应该只有文字,而必须连图标一
并传入。我们修改上个范例修改如下:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JComboBox5
{
String[] s = {"西瓜","苹果","草莓","香蕉","葡萄"};
ImageIcon[] icons = new ImageIcon[5];;
public JComboBox5()
{
JFrame f = new JFrame("JComboBox");
Container contentPane = f.getContentPane();
ItemObj[] obj = new ItemObj[5];
for(int i=0; i < 5; i++)
{
icons[i] = new ImageIcon(".\\icons\\fruit"+(i+1)+".jpg");
obj[i] = new ItemObj(s[i],icons[i]);
}
JComboBox combo = new JComboBox(obj);//利用ItemObj Array obj当作是JComboBox的参数传入,构造出JComboBox.
combo.setBorder(BorderFactory.createTitledBorder("您喜欢吃哪些水果?"));
combo.setRenderer(new ACellRenderer());
combo.setMaximumRowCount(3);
contentPane.add(combo);
f.pack();
f.show();
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public static void main(String args[])
{
new JComboBox5();
}
}
class ItemObj
{
String name;
ImageIcon icon;
public ItemObj(String name, ImageIcon icon){
this.name = name;
this.icon = icon;
}
}
class ACellRenderer extends JLabel implements ListCellRenderer
{
ACellRenderer()
{
setOpaque(true);
}
public Component getListCellRendererComponent(JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus)
{
if (value != null)
{
setText(((ItemObj)value).name);
setIcon(((ItemObj)value).icon);
}
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
}
else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
return this;
}
}
你可以发现,第一栏显示有图标显示出来了。当然你也可以利用ComboBoxModel方式来构造出有图标的JComboBox.我们来看下面
的例子:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JComboBox6{
String[] s={"西瓜","苹果","草莓","香蕉","葡萄"};
ImageIcon[] icons=new ImageIcon[5];
public JComboBox6(){
JFrame f=new JFrame("JComboBox");
Container contentPane=f.getContentPane();
for(int i=0; i < 5; i++)
{
icons[i] = new ImageIcon(".\\icons\\fruit"+(i+1)+".jpg");
}
ComboBoxModel mode=new AModel();
JComboBox combo=new JComboBox(mode);
combo.setBorder(BorderFactory.createTitledBorder("您喜欢吃哪些水果?"));
combo.setRenderer(new ACellRenderer());
combo.setMaximumRowCount(3);
contentPane.add(combo);
f.pack();
f.show();
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public static void main(String[] args){
new JComboBox6();
}
/*我们用JComboBox(ComboBoxModel aModel)来构造图标的JComboBox,因此我们在程序中编写一个继承DefaultComboBoxModel的
ComboBoxModel.
*/
class AModel extends DefaultComboBoxModel{
AModel(){
for (int i=0;i<s.length;i++){
ItemObj obj=new ItemObj(s[i],icons[i]);
addElement(obj);
}
}
}
}
class ItemObj
{
String name;
ImageIcon icon;
public ItemObj(String name, ImageIcon icon){
this.name = name;
this.icon = icon;
}
}
class ACellRenderer extends JLabel implements ListCellRenderer
{
ACellRenderer()
{
setOpaque(true);
}
public Component getListCellRendererComponent(JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus)
{
if (value != null)
{
setText(((ItemObj)value).name);
setIcon(((ItemObj)value).icon);
}
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
}
else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
return this;
}
}
我们用JComboBox(ComboBoxModel aModel)来构造图标的JComboBox,因此我们在程序中编写一个继承DefaultComboBoxModel的
ComboBoxModel.
7-4-4:建立可自行输入的JComboBox:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JComboBox7
{
String[] fontsize = {"12","14","16","18","20","22","24","26","28"};
String defaultMessage = "请选择或直接输入文字大小!";
public JComboBox7()
{
JFrame f = new JFrame("JComboBox");
Container contentPane = f.getContentPane();
JComboBox combo = new JComboBox(fontsize);
combo.setBorder(BorderFactory.createTitledBorder("请选择你要的文字大小"));
combo.setEditable(true);//将JComboBox设成是可编辑的.
ComboBoxEditor editor = combo.getEditor();//getEditor()方法返回ComboBoxEditor对象,如果你查看手册,你就会发
//现ComboBoxEditor是个接口(interface),因此你可以自行实作这个接口,制作自己想要的ComboBoxEditor组件。但通常
//我们不需要这么做,因为默认的ComboBoxEditor是使用JTextField,这已经足够应付大部份的情况了。
//configureEditor()方法会初始化JComboBox的显示项目。例如例子中一开始就出现:"请选择或直接输入文字大小!"这个
//字符串。
combo.configureEditor(editor, defaultMessage);
contentPane.add(combo);
f.pack();
f.show();
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public static void main(String args[])
{
new JComboBox7();
}
}
7-4-5:JComboBox的事件处理:
JComboBox的事件处理亦可分为两种,一种是取得用户选取的项目;另一种是用户在JComboBox上自行输入完毕后按下[Enter]键,
运作相对应的工作。对于第一种事件的处理,我们使用ItemListener.对于第二种事件的处理,我们使用ActionListener.
这个范例用户可以选取所要的字号,字号的变化会呈现在JLabel上,并可让用户自行输入字体的大小。当用户按下[Enter]键后
,若用户输入的值不在选项上时,此输入值会增加至JComboBox中,并将输入字体的大小显示在JLabel上。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JComboBox8 implements ItemListener,ActionListener{
String[] fontsize={"12","14","16","18","20","22","24","26","28"};
String defaultMessage="请选择或直接输入文字大小!";
Font font=null;
JComboBox combo=null;
JLabel label=null;
public JComboBox8(){
JFrame f=new JFrame("JComboBox");
Container contentPane=f.getContentPane();
contentPane.setLayout(new GridLayout(2,1));
label=new JLabel("Swing",JLabel.CENTER);
font=new Font("SansSerif",Font.PLAIN,12);
label.setFont(font);
combo=new JComboBox(fontsize);
combo.setBorder(BorderFactory.createTitledBorder("请选择你要的文字大小:"));
combo.setEditable(true);
ComboBoxEditor editor=combo.getEditor();
combo.configureEditor(editor,defaultMessage);
combo.addItemListener(this);0
combo.addActionListener(this);
contentPane.add(label);
contentPane.add(combo);
f.pack();
f.show();
f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
}
public static void main(String[] args){
new JComboBox8();
}
public void actionPerformed(ActionEvent e){
boolean isaddItem=true;
int fontsize=0;
String tmp=(String)combo.getSelectedItem();
//判断用户所输入的项目是否有重复,若有重复则不增加到JComboBox中。
try{
fontsize=Integer.parseInt(tmp);
for(int i=0;i<combo.getItemCount();i++){
if (combo.getItemAt(i).equals(tmp)){
isaddItem=false;
break;
}
}
if (isaddItem){
combo.insertItemAt(tmp,0);//插入项目tmp到0索引位置(第一列中).
}
font=new Font("SansSerif",Font.PLAIN,fontsize);
label.setFont(font);
}catch(NumberFormatException ne){
combo.getEditor().setItem("你输入的值不是整数值,请重新输入!");
}
}
public void itemStateChanged(ItemEvent e){//ItemListener界面只有itemStateChanged()一个方法,在此实作它。
if (e.getStateChange()==ItemEvent.SELECTED){//当用户的选择改变时,则在JLabel上会显示出Swing目前字形大小信息.
int fontsize=0;
try{
fontsize=Integer.parseInt((String)e.getItem());
label.setText("Swing 目前字形大小:"+fontsize);
}catch(NumberFormatException ne){//若所输入的值不是整数,则不作任何的操作.
}
}
}
}
HttpClient 是 Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。本文首先介绍 HTTPClient,然后根据作者实际工作经验给出了一些常见问题的解决方法。 HttpClient简介 HTTP 协议可能是现在 Internet 上使用得最多、最重要的协议了,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源。虽然在 JDK 的 java.net 包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。HttpClient 已经应用在很多的项目中,比如 Apache Jakarta 上很著名的另外两个开源项目 Cactus 和 HTMLUnit 都使用了 HttpClient,更多使用 HttpClient 的应用可以参见http://wiki.apache.org/jakarta-httpclient/HttpClientPowered。HttpClient 项目非常活跃,使用的人还是非常多的。目前 HttpClient 版本是在 2005.10.11 发布的 3.0 RC4 。
HttpClient 功能介绍
以下列出的是 HttpClient 提供的主要的功能,要知道更多详细的功能可以参见 HttpClient 的主页。
- 实现了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)
- 支持自动转向
- 支持 HTTPS 协议
- 支持代理服务器等
下面将逐一介绍怎样使用这些功能。首先,我们必须安装好 HttpClient。
HttpClient 基本功能的使用
GET 方法
使用 HttpClient 需要以下 6 个步骤:
1. 创建 HttpClient 的实例
2. 创建某种连接方法的实例,在这里是 GetMethod。在 GetMethod 的构造函数中传入待连接的地址
3. 调用第一步中创建好的实例的 execute 方法来执行第二步中创建好的 method 实例
4. 读 response
5. 释放连接。无论执行方法是否成功,都必须释放连接
6. 对得到后的内容进行处理
根据以上步骤,我们来编写用GET方法来取得某网页内容的代码。
大部分情况下 HttpClient 默认的构造函数已经足够使用。
HttpClient httpClient = new HttpClient();
创建GET方法的实例。在GET方法的构造函数中传入待连接的地址即可。用GetMethod将会自动处理转发过程,如果想要把自动处理转发过程去掉的话,可以调用方法setFollowRedirects(false)。
GetMethod getMethod = new GetMethod("http://www.ibm.com/");
调用实例httpClient的executeMethod方法来执行getMethod。由于是执行在网络上的程序,在运行executeMethod方法的时候,需要处理两个异常,分别是HttpException和IOException。引起第一种异常的原因主要可能是在构造getMethod的时候传入的协议不对,比如不小心将"http"写成"htp",或者服务器端返回的内容不正常等,并且该异常发生是不可恢复的;第二种异常一般是由于网络原因引起的异常,对于这种异常 (IOException),HttpClient会根据你指定的恢复策略自动试着重新执行executeMethod方法。HttpClient的恢复策略可以自定义(通过实现接口HttpMethodRetryHandler来实现)。通过httpClient的方法setParameter设置你实现的恢复策略,本文中使用的是系统提供的默认恢复策略,该策略在碰到第二类异常的时候将自动重试3次。executeMethod返回值是一个整数,表示了执行该方法后服务器返回的状态码,该状态码能表示出该方法执行是否成功、需要认证或者页面发生了跳转(默认状态下GetMethod的实例是自动处理跳转的)等。
//设置成了默认的恢复策略,在发生异常时候将自动重试3次,在这里你也可以设置成自定义的恢复策略
getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler());
//执行getMethod
int statusCode = client.executeMethod(getMethod);
if (statusCode != HttpStatus.SC_OK) {
System.err.println("Method failed: " + getMethod.getStatusLine());
}
在返回的状态码正确后,即可取得内容。取得目标地址的内容有三种方法:第一种,getResponseBody,该方法返回的是目标的二进制的byte流;第二种,getResponseBodyAsString,这个方法返回的是String类型,值得注意的是该方法返回的String的编码是根据系统默认的编码方式,所以返回的String值可能编码类型有误,在本文的"字符编码"部分中将对此做详细介绍;第三种,getResponseBodyAsStream,这个方法对于目标地址中有大量数据需要传输是最佳的。在这里我们使用了最简单的getResponseBody方法。
byte[] responseBody = method.getResponseBody();
释放连接。无论执行方法是否成功,都必须释放连接
method.releaseConnection();
处理内容。在这一步中根据你的需要处理内容,在例子中只是简单的将内容打印到控制台
System.out.println(new String(responseBody));
import java.io.IOException;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
public class GetSample{
public static void main(String[] args) {
//构造HttpClient的实例
HttpClient httpClient = new HttpClient();
//创建GET方法的实例
GetMethod getMethod = new GetMethod("http://www.ibm.com");
//使用系统提供的默认的恢复策略
getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler());
try {
//执行getMethod
int statusCode = httpClient.executeMethod(getMethod);
if (statusCode != HttpStatus.SC_OK) {
System.err.println("Method failed: "
+ getMethod.getStatusLine());
}
//读取内容
byte[] responseBody = getMethod.getResponseBody();
//处理内容
System.out.println(new String(responseBody));
} catch (HttpException e) {
//发生致命的异常,可能是协议不对或者返回的内容有问题
System.out.println("Please check your provided http address!");
e.printStackTrace();
} catch (IOException e) {
//发生网络异常
e.printStackTrace();
} finally {
//释放连接
getMethod.releaseConnection();
}
}
}
POST方法
根据RFC2616,对POST的解释如下:POST方法用来向目的服务器发出请求,要求它接受被附在请求后的实体,并把它当作请求队列(Request-Line)中请求URI所指定资源的附加新子项。POST被设计成用统一的方法实现下列功能:
- 对现有资源的注释(Annotation of existing resources)
- 向电子公告栏、新闻组,邮件列表或类似讨论组发送消息
- 提交数据块,如将表单的结果提交给数据处理过程
- 通过附加操作来扩展数据库
调用HttpClient中的PostMethod与GetMethod类似,除了设置PostMethod的实例与GetMethod有些不同之外,剩下的步骤都差不多。在下面的例子中,省去了与GetMethod相同的步骤,只说明与上面不同的地方,并以登录清华大学BBS为例子进行说明。
构造PostMethod之前的步骤都相同,与GetMethod一样,构造PostMethod也需要一个URI参数,在本例中,登录的地址是http://www.newsmth.net/bbslogin2.php。在创建了PostMethod的实例之后,需要给method实例填充表单的值,在BBS的登录表单中需要有两个域,第一个是用户名(域名叫id),第二个是密码(域名叫passwd)。表单中的域用类NameValuePair来表示,该类的构造函数第一个参数是域名,第二参数是该域的值;将表单所有的值设置到PostMethod中用方法setRequestBody。另外由于BBS登录成功后会转向另外一个页面,但是HttpClient对于要求接受后继服务的请求,比如POST和PUT,不支持自动转发,因此需要自己对页面转向做处理。具体的页面转向处理请参见下面的"自动转向"部分。代码如下
String url = "http://www.newsmth.net/bbslogin2.php";
PostMethod postMethod = new PostMethod(url);
// 填入各个表单域的值
NameValuePair[] data = { new NameValuePair("id", "youUserName"),
new NameValuePair("passwd", "yourPwd") };
// 将表单的值放入postMethod中
postMethod.setRequestBody(data);
// 执行postMethod
int statusCode = httpClient.executeMethod(postMethod);
// HttpClient对于要求接受后继服务的请求,象POST和PUT等不能自动处理转发
// 301或者302
if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
// 从头中取出转向的地址
Header locationHeader = postMethod.getResponseHeader("location");
String location = null;
if (locationHeader != null) {
location = locationHeader.getValue();
System.out.println("The page was redirected to:" + location);
} else {
System.err.println("Location field value is null.");
}
return;
}
字符编码
某目标页的编码可能出现在两个地方,第一个地方是服务器返回的http头中,另外一个地方是得到的html/xml页面中。
在http头的Content-Type字段可能会包含字符编码信息。例如可能返回的头会包含这样子的信息:Content-Type: text/html; charset=UTF-8。这个头信息表明该页的编码是UTF-8,但是服务器返回的头信息未必与内容能匹配上。比如对于一些双字节语言国家,可能服务器返回的编码类型是UTF-8,但真正的内容却不是UTF-8编码的,因此需要在另外的地方去得到页面的编码信息;但是如果服务器返回的编码不是UTF-8,而是具体的一些编码,比如gb2312等,那服务器返回的可能是正确的编码信息。通过method对象的getResponseCharSet()方法就可以得到http头中的编码信息。
对于象xml或者html这样的文件,允许作者在页面中直接指定编码类型。比如在html中会有<meta http-equiv="Content-Type" content="text/html; charset=gb2312"/>这样的标签;或者在xml中会有<?xml version="1.0" encoding="gb2312"?>这样的标签,在这些情况下,可能与http头中返回的编码信息冲突,需要用户自己判断到底那种编码类型应该是真正的编码。
自动转向
根据RFC2616中对自动转向的定义,主要有两种:301和302。301表示永久的移走(Moved Permanently),当返回的是301,则表示请求的资源已经被移到一个固定的新地方,任何向该地址发起请求都会被转到新的地址上。302表示暂时的转向,比如在服务器端的servlet程序调用了sendRedirect方法,则在客户端就会得到一个302的代码,这时服务器返回的头信息中location的值就是sendRedirect转向的目标地址。
HttpClient支持自动转向处理,但是象POST和PUT方式这种要求接受后继服务的请求方式,暂时不支持自动转向,因此如果碰到POST方式提交后返回的是301或者302的话需要自己处理。就像刚才在POSTMethod中举的例子:如果想进入登录BBS后的页面,必须重新发起登录的请求,请求的地址可以在头字段location中得到。不过需要注意的是,有时候location返回的可能是相对路径,因此需要对location返回的值做一些处理才可以发起向新地址的请求。
另外除了在头中包含的信息可能使页面发生重定向外,在页面中也有可能会发生页面的重定向。引起页面自动转发的标签是:<meta http-equiv="refresh" content="5; url=http://www.ibm.com/us">。如果你想在程序中也处理这种情况的话得自己分析页面来实现转向。需要注意的是,在上面那个标签中url的值也可以是一个相对地址,如果是这样的话,需要对它做一些处理后才可以转发
处理HTTPS协议
HttpClient提供了对SSL的支持,在使用SSL之前必须安装JSSE。在Sun提供的1.4以后的版本中,JSSE已经集成到JDK中,如果你使用的是JDK1.4以前的版本则必须安装JSSE。JSSE不同的厂家有不同的实现。下面介绍怎么使用HttpClient来打开Https连接。这里有两种方法可以打开https连接,第一种就是得到服务器颁发的证书,然后导入到本地的keystore中;另外一种办法就是通过扩展HttpClient的类来实现自动接受证书。
方法1,取得证书,并导入本地的keystore:
- 安装JSSE (如果你使用的JDK版本是1.4或者1.4以上就可以跳过这一步)。本文以IBM的JSSE为例子说明。先到IBM网站上下载JSSE的安装包。然后解压开之后将ibmjsse.jar包拷贝到<java-home>\lib\ext\目录下。
- 取得并且导入证书。证书可以通过IE来获得:
1. 用IE打开需要连接的https网址,会弹出如下对话框:
2. 单击"View Certificate",在弹出的对话框中选择"Details",然后再单击"Copy to File",根据提供的向导生成待访问网页的证书文件
3. 向导第一步,欢迎界面,直接单击"Next",
4. 向导第二步,选择导出的文件格式,默认,单击"Next",
5. 向导第三步,输入导出的文件名,输入后,单击"Next",
6. 向导第四步,单击"Finish",完成向导
7. 最后弹出一个对话框,显示导出成功
-
用keytool工具把刚才导出的证书倒入本地keystore。Keytool命令在<java-home>\bin\下,打开命令行窗口,并到<java-home>\lib\security\目录下,运行下面的命令:
- keytool -import -noprompt -keystore cacerts -storepass changeit -alias yourEntry1 -file your.cer
其中参数alias后跟的值是当前证书在keystore中的唯一标识符,但是大小写不区分;参数file后跟的是刚才通过IE导出的证书所在的路径和文件名;如果你想删除刚才导入到keystore的证书,可以用命令:
- keytool -delete -keystore cacerts -storepass changeit -alias yourEntry1
- 写程序访问https地址。如果想测试是否能连上https,只需要稍改一下GetSample例子,把请求的目标变成一个https地址。
GetMethod getMethod = new GetMethod(https://www.yourdomain.com);
运行该程序可能出现的问题:
-
1. 抛出异常java.net.SocketException: Algorithm SSL not available。出现这个异常可能是因为没有加JSSEProvider,如果用的是IBM的JSSE Provider,在程序中加入这样的一行:
if(Security.getProvider("com.ibm.jsse.IBMJSSEProvider") == null)
Security.addProvider(new IBMJSSEProvider());
或者也可以打开<java-home>\lib\security\java.security,在行
-
security.provider.1=sun.security.provider.Sun
security.provider.2=com.ibm.crypto.provider.IBMJCE
后面加入security.provider.3=com.ibm.jsse.IBMJSSEProvider
2. 抛出异常java.net.SocketException: SSL implementation not available。出现这个异常可能是你没有把ibmjsse.jar拷贝到<java-home>\lib\ext\目录下。
3. 抛出异常javax.net.ssl.SSLHandshakeException: unknown certificate。出现这个异常表明你的JSSE应该已经安装正确,但是可能因为你没有把证书导入到当前运行JRE的keystore中,请按照前面介绍的步骤来导入你的证书。
方法2,扩展HttpClient类实现自动接受证书
因为这种方法自动接收所有证书,因此存在一定的安全问题,所以在使用这种方法前请仔细考虑您的系统的安全需求。具体的步骤如下:
- 提供一个自定义的socket factory(test.MySecureProtocolSocketFactory)。这个自定义的类必须实现接口org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory,在实现接口的类中调用自定义的X509TrustManager(test.MyX509TrustManager),这两个类可以在随本文带的附件中得到
- 创建一个org.apache.commons.httpclient.protocol.Protocol的实例,指定协议名称和默认的端口号
Protocol myhttps = new Protocol("https", new MySecureProtocolSocketFactory (), 443);
- 注册刚才创建的https协议对象
Protocol.registerProtocol("https ", myhttps);
-
- 然后按照普通编程方式打开https的目标地址,代码请参见test.NoCertificationHttpsGetSample
处理代理服务器
HttpClient中使用代理服务器非常简单,调用HttpClient中setProxy方法就可以,方法的第一个参数是代理服务器地址,第二个参数是端口号。另外HttpClient也支持SOCKS代理。
httpClient.getHostConfiguration().setProxy(hostName,port);
结论
从上面的介绍中,可以知道HttpClient对http协议支持非常好,使用起来很简单,版本更新快,功能也很强大,具有足够的灵活性和扩展性。对于想在Java应用中直接访问http资源的编程人员来说,HttpClient是一个不可多得的好工具。
package client.test;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.htmlparser.Node;
import org.htmlparser.NodeFilter;
import org.htmlparser.Parser;
import org.htmlparser.filters.AndFilter;
import org.htmlparser.filters.HasAttributeFilter;
import org.htmlparser.filters.TagNameFilter;
import org.htmlparser.nodes.TagNode;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
public class TIPOTest {
private static String mainPageUrl = "http://twpat.tipo.gov.tw/twcgi/ttsweb?@0:0:1:twpat2@@"
+ Math.random();
public static String strEncoding = "utf-8"; //strEncoding = "iso-8859-1";
final static String userAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705)";
public static void main(String[] args) throws Exception {
// HttpClient client = new HttpClient();
// client.getHostConfiguration().setProxy("10.41.16.98", 808);
// client.getHostConfiguration().setHost(
// "http://twpat3.tipo.gov.tw/tipotwoc", 80, "http");
// String strLegalStatus = null;
String mainPage = doGetMethod(mainPageUrl);
String searchPageUrl = parserMainPage(mainPage);
String searchPage = doGetMethod(searchPageUrl);
String queryPageUrl = getQueryPageUrl(searchPage);
String queryPage = doGetMethod(queryPageUrl);
String searchResultUrl = parserPageForPostURL(queryPage);
Map parameterMap = parseParameters(queryPage);
System.out.println("searchResultUrl : " + searchResultUrl);
String response = getResultPage(searchResultUrl, "087121847",
parameterMap);
// HttpMethod method = getPostMethod(); // 使用 POST 方式提交数据
// // HttpMethod method = getGetMethod(); // 使用 GET 方式提交数据
// client.executeMethod(method); // 打印服务器返回的状态
// System.out.println(method.getStatusLine()); // 打印结果页面
// String response = new
// String(method.getResponseBodyAsString().getBytes(
// "8859_1"));
// 打印返回的信息
System.out.println(response);
// 释放连接
// method.releaseConnection();
}
private static String doGetMethod(String url) throws Exception {
try {
HttpClient httpClient = new HttpClient();
HostConfiguration hc = httpClient.getHostConfiguration();
hc.setProxy("10.41.16.98", 808);
httpClient.getState().setAuthenticationPreemptive(true);
GetMethod getMethod = new GetMethod(url);
getMethod.setRequestCharSet(strEncoding);
getMethod.setResponseCharSet(strEncoding);
getMethod.setRequestHeader("User-Agent", userAgent);
getMethod.setTimeout(1000 * 60 * 2);
int StatusCode = httpClient.executeMethod(getMethod);
if (StatusCode >= 400) {
getMethod.getConnection().close();
throw new Exception("Can't get information.");
}
String str = getMethod.getResponseBodyAsString();
getMethod.releaseConnection();
writeStrToTxt(str);// wuwen add for test
return str;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
static int i = 1;
// wuwen add for test
public static void writeStrToTxt(String str) {
i++;
BufferedWriter output;
try {
output = new BufferedWriter(new FileWriter(
"C:\\Documents and Settings\\F3223114\\桌面\\" + i + ".txt"));
output.write(str);
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static String parserMainPage(String body) {
String parserSearchUrl = "";
/*
* String regEx = "href=(.+?)\\s*class=menu"; Pattern pattern =
* Pattern.compile(regEx, Pattern.CASE_INSENSITIVE); Matcher matcher =
* pattern.matcher(body); while (matcher.find()) { if
* (matcher.group(1).contains(":80:::0")) { parserSearchUrl =
* matcher.group(1); } }
*/
int i = body.indexOf("http");
if (i != -1) {
parserSearchUrl = body.substring(i, body.indexOf(">", i));
}
return parserSearchUrl;
}
public String getMidString(String sInput, String sStartTag, String sEndTag) {
int i, j = -1;
if ((i = sInput.indexOf(sStartTag)) < 0) {
return null;
} else {
if ((j = sInput.indexOf(sEndTag, i + sStartTag.length())) < 0) {
return null;
} else {
if (i + sStartTag.length() == j) {
return null;
} else {
return sInput.substring(i + sStartTag.length(), j);
}
}
}
}
private static String doPostMethod(String url, NameValuePair[] nameValuePair)
throws Exception {
try {
HttpClient httpClient = new HttpClient();
HostConfiguration hc = httpClient.getHostConfiguration();
hc.setProxy("10.41.16.98", 808);
httpClient.getState().setAuthenticationPreemptive(true);
PostMethod postMethod = new PostMethod(url);
postMethod.setRequestCharSet(strEncoding);
postMethod.setResponseCharSet(strEncoding);
postMethod.setRequestHeader("User-Agent", userAgent);
postMethod.setTimeout(1000 * 60 * 2);
postMethod.setRequestBody(nameValuePair);
int StatusCode = httpClient.executeMethod(postMethod);
if (StatusCode >= 400) {
postMethod.getConnection().close();
throw new Exception("Can't get information.");
}
String str = postMethod.getResponseBodyAsString();
postMethod.releaseConnection();
return str;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
// modify by zhuoluo 2008.12.09
private static String getResultPage(String url, String strAppNo,
Map parameterMap) throws Exception {
try {
NameValuePair _0_1_T = new NameValuePair("@_0_1_T", "T_UID");
_0_1_T.setHeadNeedEncode(false);
NameValuePair _0_1_T_1 = new NameValuePair("_0_1_T", "");
_0_1_T_1.setHeadNeedEncode(false);
NameValuePair _0_2_P = new NameValuePair("@_0_2_P", "P_PWD");
_0_2_P.setHeadNeedEncode(false);
NameValuePair _0_2_P_1 = new NameValuePair("_0_2_P", "");
_0_2_P_1.setHeadNeedEncode(false);
NameValuePair _20_0_T = new NameValuePair("@_20_0_T", "T_AN");
_20_0_T.setHeadNeedEncode(false);
NameValuePair appNo = new NameValuePair("_20_0_T", strAppNo);
appNo.setHeadNeedEncode(false);
NameValuePair _20_1_K = new NameValuePair("@_20_1_K", "K_NE");
_20_1_K.setHeadNeedEncode(false);
NameValuePair _20_1_K_1 = new NameValuePair("_20_1_K", "");
_20_1_K_1.setHeadNeedEncode(false);
NameValuePair JPAGE = new NameValuePair("JPAGE", "");
JPAGE.setHeadNeedEncode(false);
NameValuePair INFO = new NameValuePair("INFO", parameterMap.get(
"INFO").toString());
INFO.setHeadNeedEncode(false);
// NameValuePair x = new NameValuePair("_IMG_%E6%AA%A2%E7%B4%A2.x",
// "39");
// x.setHeadNeedEncode(false);
// NameValuePair y = new NameValuePair("_IMG_%E6%AA%A2%E7%B4%A2.y",
// "10");
// y.setHeadNeedEncode(false);
NameValuePair x = new NameValuePair("_IMG_%E6%AA%A2%E7%B4%A2%25z.x",
"39");
x.setHeadNeedEncode(false);
NameValuePair y = new NameValuePair("_IMG_%E6%AA%A2%E7%B4%A2%25z.y",
"10");
y.setHeadNeedEncode(false);
NameValuePair[] nameValuePair = new NameValuePair[] { _0_1_T,
_0_1_T_1, _0_2_P, _0_2_P_1, _20_0_T, appNo, _20_1_K,
_20_1_K_1, x, y , INFO };
// NameValuePair[] nameValuePair = new NameValuePair[] { _0_1_T,
// _0_1_T_1, _0_2_P, _0_2_P_1, _20_0_T, appNo, _20_1_K,
// _20_1_K_1, JPAGE, INFO };//x, y ,
String str = doPostMethod(url, nameValuePair);
writeStrToTxt(str);// wuwen add for test
return str;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
private static String getQueryPageUrl(String body) {
Parser parser = Parser.createParser(body, "utf-8");
NodeFilter filter_constellation_summart = new AndFilter(
(new TagNameFilter("a")), (new HasAttributeFilter("class",
"menu")));
NodeList nodeList = null;
String href = "";
try {
nodeList = parser
.extractAllNodesThatMatch(filter_constellation_summart);
Node node = null;
node = nodeList.elementAt(3);
href = ((TagNode) node).getAttribute("href");
} catch (ParserException ex) {
}
return href;
}
private static String parserPageForPostURL(String body)
throws ParserException {
String searchResultUrl = "";
NodeFilter tagFilter = new TagNameFilter("FORM");
Parser parser = Parser.createParser(body, "UTF-8");
NodeList list = parser.extractAllNodesThatMatch(tagFilter);
for (int i = 0; i < list.size(); i++) {
Node node = list.elementAt(i);
if (node instanceof TagNode) {
String methodValue = ((TagNode) node).getAttribute("METHOD");
if (methodValue != null) {
String actionPath = ((TagNode) node).getAttribute("ACTION");
searchResultUrl = actionPath;
}
}
}
return searchResultUrl;
}
private static Map<String, String> parseParameters(String pageBody)
throws ParserException {
Map<String, String> parameterMap = new HashMap<String, String>();
NodeFilter tagFilter = new TagNameFilter("input");
Parser parser = Parser.createParser(pageBody, "UTF-8");
NodeList list = parser.extractAllNodesThatMatch(tagFilter);
for (int i = 0; i < list.size(); i++) {
Node node = list.elementAt(i);
if (node instanceof TagNode) {
String nodeName = ((TagNode) node).getAttribute("name");
String nodeValue = ((TagNode) node).getAttribute("value");
if (nodeName != null) {
if (nodeName.equals("INFO")) {
if (nodeValue == null) {
nodeValue = "";
}
parameterMap.put(nodeName, nodeValue);
}
}
}
}
return parameterMap;
}
}
TRUNC函数用于对值进行截断。
用法有两种:TRUNC(NUMBER)表示截断数字,TRUNC(date)表示截断日期。
(1)截断数字:
格式:TRUNC(n1,n2),n1表示被截断的数字,n2表示要截断到那一位。n2可以是负数,表示截断小数点前。注意,TRUNC截断不是四舍五入。
SQL> select TRUNC(15.79) from dual;
TRUNC(15.79)
------------
15
SQL> select TRUNC(15.79,1) from dual;
TRUNC(15.79,1)
--------------
15.7
SQL> select trunc(15.79,-1) from dual;
TRUNC(15.79,-1)
---------------
10
(2)截断日期:
先执行命令:alter session set nls_date_format='yyyy-mm-dd hh24:mi:hh';
截取今天:
SQL> select sysdate,trunc(sysdate,'dd') from dual;
SYSDATE TRUNC(SYSDATE,'DD')
------------------- -------------------
2009-03-24 21:31:17 2009-03-24 00:00:00
截取本周第一天:
SQL> select sysdate,trunc(sysdate,'d') from dual;
SYSDATE TRUNC(SYSDATE,'D')
------------------- -------------------
2009-03-24 21:29:32 2009-03-22 00:00:00
截取本月第一天:
SQL> select sysdate,trunc(sysdate,'mm') from dual;
SYSDATE TRUNC(SYSDATE,'MM')
------------------- -------------------
2009-03-24 21:30:30 2009-03-01 00:00:00
截取本年第一天:
SQL> select sysdate,trunc(sysdate,'y') from dual;
SYSDATE TRUNC(SYSDATE,'Y')
------------------- -------------------
2009-03-24 21:31:57 2009-01-01 00:00:00
截取到小时:
SQL> select sysdate,trunc(sysdate,'hh') from dual;
SYSDATE TRUNC(SYSDATE,'HH')
------------------- -------------------
2009-03-24 21:32:59 2009-03-24 21:00:00
截取到分钟:
SQL> select sysdate,trunc(sysdate,'mi') from dual;
SYSDATE TRUNC(SYSDATE,'MI')
------------------- -------------------
2009-03-24 21:33:32 2009-03-24 21:33:00
获取上月第一天:
SQL> select TRUNC(add_months(SYSDATE,-1),'MM') from dual
RMI原理及实现
简介
RMI是远程方法调用的简称,象其名称暗示的那样,它能够帮助我们查找并执行远程对象的方法。通俗地说,远程调用就象将一个class放在A机器上,然后在B机器中调用这
个class的方法。
我个人认为,尽管RMI不是唯一的企业级远程对象访问方案,但它却是最容易实现的。与能够使不同编程语言开发的CORBA不同的是,RMI是一种纯Java解决方案。在RMI
中,程序的所有部分都由Java编写。
在看本篇文章时,我假定读者都已经具备了较扎实的Java基础知识,在这方面有欠缺的读者请自行阅读有关资料。
概念
我在前面已经提到,RMI是一种远程方法调用机制,其过程对于最终用户是透明的:在进行现场演示时,如果我不说它使用了RNI,其他人不可能知道调用的方法存储在其他
机器上。当然了,二台机器上必须都安装有Java虚拟机(JVM)。
其他机器需要调用的对象必须被导出到远程注册服务器,这样才能被其他机器调用。因此,如果机器A要调用机器B上的方法,则机器B必须将该对象导出到其远程注册服务器
。注册服务器是服务器上运行的一种服务,它帮助客户端远程地查找和访问服务器上的对象。一个对象只有导出来后,然后才能实现RMI包中的远程接口。例如,如果想使机器A
中的Xyz对象能够被远程调用,它就必须实现远程接口。
RMI需要使用占位程序和框架,占位程序在客户端,框架在服务器端。在调用远程方法时,我们无需直接面对存储有该方法的机器。
在进行数据通讯前,还必须做一些准备工作。占位程序就象客户端机器上的一个本机对象,它就象服务器上的对象的代理,向客户端提供能够被服务器调用的方法。然
后,Stub就会向服务器端的Skeleton发送方法调用,Skeleton就会在服务器端执行接收到的方法。
Stub和Skeleton之间通过远程调用层进行相互通讯,远程调用层遵循TCP/IP协议收发数据。下面我们来大致了解一种称为为“绑定”的技术。
客户端无论何时要调用服务器端的对象,你可曾想过他是如何告诉服务器他想创建什么样的对象吗?这正是“绑定”的的用武之地。在服务器端,我们将一个字符串变量与一个
对象联系在一起(可以通过方法来实现),客户端通过将那个字符串传递给服务器来告诉服务器它要创建的对象,这样服务器就可以准确地知道客户端需要使用哪一个对象了。所
有这些字符串和对象都存储在的远程注册服务器中。
在编程中需要解决的问题
在研究代码之前,我们来看看必须编写哪些代码:
远程对象:这个接口只定义了一个方法。我们应当明白的是,这个接口并非总是不包括方法的代码而只包括方法的定义。远程对象包含要导出的每个方法的定义,它还实
现Java.rmi中的远程接口。
远程对象实现:这是一个实现远程对象的类。如果实现了远程对象,就能够覆盖该对象中的所有方法,因此,远程对象的实现类将真正包含我们希望导出的方法的代码。
远程服务器:这是一个作为服务器使用的类,它是相对于要访问远程方法的客户端而言的。它存储着绑定的字符串和对象。
远程客户端:这是一个帮助我们访问远程方法提供帮助的类,它也是最终用户。我们将使用查找和调用远程方法的方法在该类中调用远程方法。
编程
我们将首先编写远程对象,并将代码保存为名字为AddServer.Java的文件:
import Java.rmi.*;
public interface AddServer extends Remote {
public int AddNumbers(int firstnumber,int secondnumber) throws RemoteException;
}
我们来看看上面的代码。首先,为了使用其内容,我们导入rmi包。然后,我们创建一个扩展了Java.rmi中远程接口的接口。所有的远程对象必须扩展该远程接口,我们将该
远程接口称为AddServer。在该远程对象中,有一个名字为AddNumbers的方法,客户端可以调用这一方法。我们必须记住的是,所有的远程方法都需要启
动RemoteException方法,有错误发生时就会调用该方法。
下面我们开始编写远程对象的实现。这是一个实现远程对象并包含有所有方法代码的类,将下面的代码保存为名字为AddServerImpl.Java的文件:
import Java.rmi.*;
public class AddServerImpl extends UnicastRemoteObject implements AddServer {
public AddServerImpl() {
super();
}
public int AddNumbers(int firstnumber,int secondnumber) throws RemoteException {
return firstnumber + secondnumber;
}
}
首先,我们导入rmi包,然后创建一个扩展UnicastRemoteObject和实现创建的远程对象的类;其次,我们可以为类创建一个缺省的构建器。我们还了解了AddNumbers方
法的代码,它启动RemoteException。这样我们就覆盖了创建的远程对象中的方法。AddNumbers方法的代码非常好理解,它接受2个整型参数,然后相加并返回它们的和。
至此,我们已经有了二个Java文件:远程对象和远程对象的实现。下面我们将使用Javac命令编译这二个文件:
编译远程对象:
C:\jdk\bin\Javac workingdir\AddServer.Java
编译远程对象实现:
C:\jdk\bin\Javac workingdir\AddServerImpl.Java 这样,就会达到二个Java文件和二个类文件,下面我们将创建stub和skeleton。为了创建stub和skeleton文件,
我们必须使用rmic编译器编译远程对象实现文件。
用Rmic编译远程对象实现文件:
C:\jdk\bin\rmic workingdir\AddServerImpl.Java 然后,我们就会发现多了2个新建的类文件,它们分别是AddServerImpl_Stub.class
和AddServerImpl_Skel.class 。
The Coding (Contd.)
我们已经编译了所有的源代码,下面我们来创建客户端和服务器端,将下面的代码保存为名字为RmiServer.Java的文件:
import Java.rmi.*;
import Java.net.*;
public class RmiServer {
public static void main (String args[]) throws RemoteException, MalformedURLException {
AddServerImpl add = new AddServerImpl();
Naming.rebind("addnumbers",add);
}
}
首先,我们导入Java.rmi包和Java.net包。另外,我们还使用throws从句捕获任何异常。我们从对象中得出远程对象实现,使用rebind方法将字符串addnumbers与该对
象绑定。下面的例子显示了绑定的含义:
从现在开始,无论何时客户端要调用远程对象,使用字符串addnumbers就可以实现。rebind方法有二个参数:第一个参数是字符串变量,第二个参数是远程对象实现类的
对象。
下面我们来创建客户端,将下面的代码保存为名字为RmiClient.Java的文件:
import Java.rmi.*;
import Java.net.*;
public class RmiClient {
public static void main(String args[]) throws RemoteException, MalformedURLException {
String url="rmi://127.0.0.1/addnumbers";
AddServer add;
add = (AddServer)Naming.lookup(url);
int result = add.AddNumbers(10,5);
System.out.println(result);
}
}
首先,我们导入Java.rmi包和Java.net包,并使用throws从句捕获所有必要的异常。然后通过利用Naming类中的静态lookup方法从远程对象中得到一个对象。(这也是我
们无需从Naming类中得到一个对象并调用它。而只使用类名字的原因。)
lookup方法接受远程对象的完整的URL名字,该URL由完整的机器IP地址以及与对象绑定的字符串(也誻对象的绑定名)组成。在调用远程对象时,我们使用了RMI协
议。lookup方法向我们返回一个对象,在能够使用它前,我们必须将它的数据类型转换为与远程对象的数据类型一致。
Since we have both our server and client source ready, let's compile them both:
至此,我们已经有了服务器端和客户端的源代码,下面我们来编译这二个源文件:
编译远程服务器:
C:\jdk\bin\Javac workingdir\RmiServer.Java
编译远程客户端:
C:\jdk\bin\Javac workingdir\RmiClient.Java
在对我们的代码进行测试前,还必须首先启动RMI Registry。RMI Registry存储有所有绑定的数据,没有它,RMI就不能正常地运行!
启动Rmi Registry服务器:
C:\jdk\bin\start rmiregistry
我们会注意到,这时会出现一个空白的DOS提示符窗口,这表明Rmi Registry服务器在运行,注意不要关闭该窗口。然后,我们首先在一个DOS提示符窗口中运行Rmi服务
器,然后在另一个DOS提示符窗口中运行Rmi客户端。
启动RMI服务器:
C:\jdk\bin\Java workingdir\RmiServer
启动RMI客户端:
C:\jdk\bin\Java workingdir\RmiClient
如果一切正常,我们应该能够得到15这个输出。我们向AddNumbers方法输入10和5二个数字,该方法将这二者加起来,并将其和15返回给我们。如果得到了15这个输出,
说明我们已经成功地执行了一个远程方法。当然,在这里,我们并没有执行真正意义上的远程方法,因为我们的计算机既是服务器,又是客户机。如果有计算机网络,我们就可以
方便地进行执行远程方法的试验了。
将JTable的单元格设置为不可编辑,有两种方法。
一种是自己写一个MyTable类继承DefaultTableModel,重写其中的isCellEditable方法;
还有一种是在创建JTable对象时, JTable treeTable = new JTable(tableModel){ public boolean isCellEditable(int row, int column) { return false; }};
一.创建表格控件的各种方式:
1) 调用无参构造函数.
JTable table = new JTable();
2) 以表头和表数据创建表格.
Object[][] cellData = {{"row1-col1", "row1-col2"},{"row2-col1", "row2-col2"}};
String[] columnNames = {"col1", "col2"};
JTable table = new JTable(cellData, columnNames);
3) 以表头和表数据创建表格,并且让表单元格不可改.
String[] headers = { "表头一", "表头二", "表头三" };
Object[][] cellData = null;
DefaultTableModel model = new DefaultTableModel(cellData, headers) {
public boolean isCellEditable(int row, int column) {
return false;
}
};
table = new JTable(model);
二.对表格列的控制
0)获取JTable中特定单元格的位置
table.addMouseListener(new MouseListener() {
public void mouseClicked(MouseEvent e) {
int row = jt.rowAtPoint(e.getPoint());
int col = jt.columnAtPoint(e.getPoint());
}
});
1) 设置列不可随容器组件大小变化自动调整宽度.
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
2) 限制某列的宽度.
TableColumn firsetColumn = table.getColumnModel().getColumn(0);
firsetColumn.setPreferredWidth(30);
firsetColumn.setMaxWidth(30);
firsetColumn.setMinWidth(30);
3) 设置当前列数.
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
int count=5;
tableModel.setColumnCount(count);
4) 取得表格列数
int cols = table.getColumnCount();
5) 添加列
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
tableModel.addColumn("新列名");
6) 删除列
table.removeColumn(table.getColumnModel().getColumn(columnIndex));// columnIndex是要删除的列序号
三.对表格行的控制
1) 设置行高
table.setRowHeight(20);
2) 设置当前航数
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
int n=5;
tableModel.setRowCount(n);
3) 取得表格行数
int rows = table.getRowCount();
4) 添加表格行
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
tableModel.addRow(new Object[]{"sitinspring", "35", "Boss"});
5) 删除表格行
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
model.removeRow(rowIndex);// rowIndex是要删除的行序号
四.存取表格单元格的数据
1) 取单元格数据
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
String cellValue=(String) tableModel.getValueAt(row, column);// 取单元格数据,row是行号,column是列号
2) 填充数据到表格.
注:数据是Member类型的链表,Member类如下:
public class Member{
// 名称
private String name;
// 年龄
private String age;
// 职务
private String title;
}
填充数据的代码:
public void fillTable(List<Member> members){
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
tableModel.setRowCount(0);// 清除原有行
// 填充数据
for(Member member:members){
String[] arr=new String[3];
arr[0]=member.getName();
arr[1]=member.getAge();
arr[2]=member.getTitle();
// 添加数据到表格
tableModel.addRow(arr);
}
// 更新表格
table.invalidate();
}
2) 取得表格中的数据
public List<Member> getShowMembers(){
List<Member> members=new ArrayList<Member>();
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
int rowCount=tableModel.getRowCount();
for(int i=0;i<rowCount;i++){
Member member=new Member();
member.setName((String)tableModel.getValueAt(i, 0));// 取得第i行第一列的数据
member.setAge((String)tableModel.getValueAt(i, 1));// 取得第i行第二列的数据
member.setTitle((String)tableModel.getValueAt(i, 2));// 取得第i行第三列的数据
members.add(member);
}
return members;
}
五.取得用户所选的行
1) 取得用户所选的单行
int selectRows=table.getSelectedRows().length;// 取得用户所选行的行数
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
if(selectRows==1){
int selectedRowIndex = table.getSelectedRow(); // 取得用户所选单行
.// 进行相关处理
}
2) 取得用户所选的多行
int selectRows=table.getSelectedRows().length;// 取得用户所选行的行数
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
if(selectRows>1)
int[] selRowIndexs=table.getSelectedRows();// 用户所选行的序列
for(int i=0;i<selRowIndexs.length;i++){
// 用tableModel.getValueAt(row, column)取单元格数据
String cellValue=(String) tableModel.getValueAt(i, 1);
}
}
六.添加表格的事件处理
view.getTable().addMouseListener(new MouseListener() {
public void mousePressed(MouseEvent e) {
// 鼠标按下时的处理
}
public void mouseReleased(MouseEvent e) {
// 鼠标松开时的处理
}
public void mouseEntered(MouseEvent e) {
// 鼠标进入表格时的处理
}
public void mouseExited(MouseEvent e) {
// 鼠标退出表格时的处理
}
public void mouseClicked(MouseEvent e) {
// 鼠标点击时的处理
}
});
GregorianCalendar cal = new GregorianCalendar();
Date now = new Date();
cal.setTime(now);
cal.setFirstDayOfWeek(GregorianCalendar.MONDAY); // 设置一个星期的第一天为星期1,默认是星期日
SimpleDateFormat dateutil = new SimpleDateFormat("yyyy-MM-dd");
System.out.println("now=" + dateutil.format(cal.getTime())); // 今天
cal.add(GregorianCalendar.DATE,- 1);
System.out.println("now=" + dateutil.format(cal.getTime())); // 昨天
cal.set(GregorianCalendar.DAY_OF_WEEK,GregorianCalendar.MONDAY);
System.out.println("now=" + dateutil.format(cal.getTime())); // 本周1
cal.set(GregorianCalendar.DAY_OF_MONTH,1);
System.out.println("now=" + dateutil.format(cal.getTime())); // 本月1日在此键入内容
Pixel length of String
import java.awt.FontMetrics;
import java.awt.geom.Rectangle2D;
1. awt
Font font = new Font("Verdana", Font.PLAIN, 10);
FontMetrics metrics = new FontMetrics(font) {
};
Rectangle2D bounds = metrics.getStringBounds("string", null);
int widthInPixels = (int) bounds.getWidth();
文章出处:飞诺网(www.firnow.com):http://dev.firnow.com/course/3_program/java/javajs/20091214/184739.html
|