- 浏览: 45434 次
- 性别:
- 来自: 北京
最新评论
-
alienj:
ifNotBegunOutcome 我这里要报错,不知道为啥子 ...
非常人贩 2---整个电影只能用一个字来形容---“扯”! -
ouspec:
欢迎加入
等了三天终于被我等到
Java 的乐趣与游戏:Java grab 包的技术提示
一些 Java SE 技术提示
作者:Jeff Friesen, JavaWorld.com, 01/02/07
翻译:suli原文地址:http://www.javaworld.com/javaworld/jw-01-2007/jw-0102-games.html
开发 Java 平台十年之久,我已经积累了一些使用 Java SE 的 grab 包加强游戏及其他 Java 平台开发的宝贵经验。 本期的 Java Fun and Games 将与您分享一些技术提示。 在文章的后半部分,将介绍如何将这些技术提示应用到一个网页抓图应用程序。
最简单的 API
不管计算机运行得有多快,我们却总是在等待某个任务的完成,比如,下载大个的文件、执行彻底搜索或者进行复杂的数学计算。 在这些费时的任务完成时,许多 Java 程序都会用一些花哨的方式提示用户,普遍方法是使用可以听得见的警告。
Java 提供了许多声音 API 可以用于创建有声警告。 可以使用 Java Speech API 告诉用户任务已经结束。 如果您希望任务完成时播放音效或音乐,Java Sound API 是一个不错的选择。 然而,因为 Java Speech 需要额外的分发文件,而 Java Sound 需要相当复杂的代码,您可能就希望使用 Audio Clip API 了。
Audio Clip API 基于 java.applet.AudioClip
和 java.applet.Applet
方法,例如:public static final AudioClip newAudioClip(URL url)
。 虽然此 API 比 Java Speech 和 Java Sound 更易于使用,但只用它来播放一段简单的声音也太过大材小用了。 对于这种简单的任务,还是考虑使用 Java 最简单的声音 API 吧。
最简单的声音 API 由 java.awt.Toolkit
的 public abstract void beep()
方法构成。 当调用此方法时,将发出简单的“哔跸”声。 为了展示 beep()
的用法,我创建了一个 CalcPi
应用程序,为 Pi 计数。 请看列表 1。
列表 1 CalcPi.java
// CalcPi.java
import java.awt.Toolkit;
import java.math.BigDecimal;
public class CalcPi
{
/* constants used in pi computation */
private static final BigDecimal ZERO = BigDecimal.valueOf (0);
private static final BigDecimal ONE = BigDecimal.valueOf (1);
private static final BigDecimal FOUR = BigDecimal.valueOf (4);
/* rounding mode to use during pi computation */
private static final int roundingMode = BigDecimal.ROUND_HALF_EVEN;
/* digits of precision after the decimal point */
private static int digits;
public static void main (String [] args)
{
if (args.length != 1)
{
System.err.println ("usage: java CalcPi digits");
return;
}
int digits = 0;
try
{
digits = Integer.parseInt (args [0]);
}
catch (NumberFormatException e)
{
System.err.println (args [0] + " is not a valid integer");
return;
}
System.out.println (computePi (digits));
Toolkit.getDefaultToolkit ().beep ();
}
/*
* Compute the value of pi to the specified number of
* digits after the decimal point. The value is
* computed using Machin's formula:
*
* pi/4 = 4*arctan(1/5) - arctan(1/239)
*
* and a power series expansion of arctan(x) to
* sufficient precision.
*/
public static BigDecimal computePi (int digits)
{
int scale = digits + 5;
BigDecimal arctan1_5 = arctan (5, scale);
BigDecimal arctan1_239 = arctan (239, scale);
BigDecimal pi = arctan1_5.multiply (FOUR).
subtract (arctan1_239).multiply (FOUR);
return pi.setScale (digits, BigDecimal.ROUND_HALF_UP);
}
/*
* Compute the value, in radians, of the arctangent of
* the inverse of the supplied integer to the specified
* number of digits after the decimal point. The value
* is computed using the power series expansion for the
* arc tangent:
*
* arctan(x) = x - (x^3)/3 + (x^5)/5 - (x^7)/7 +
* (x^9)/9 ...
*/
public static BigDecimal arctan (int inverseX, int scale)
{
BigDecimal result, numer, term;
BigDecimal invX = BigDecimal.valueOf (inverseX);
BigDecimal invX2 = BigDecimal.valueOf (inverseX * inverseX);
numer = ONE.divide (invX, scale, roundingMode);
result = numer;
int i = 1;
do
{
numer = numer.divide (invX2, scale, roundingMode);
int denom = 2 * i + 1;
term = numer.divide (BigDecimal.valueOf (denom), scale, roundingMode);
if ((i % 2) != 0)
result = result.subtract (term); else
result = result.add (term);
i++;
}
while (term.compareTo (ZERO) != 0);
return result;
}
}
列表 1 使用一种算法来计算 pi,该算法是早在 18 世纪初期由英国数学家 John Machin 发明的。 算法首先计算 pi/4 = 4*arctan(1/5)-arctan(1/239),然后将结果乘以 4 得出 pi 的值。 由于 arc (inverse) tangent 是使用一系列庞大的 term 来计算的, term 的数量越大得出的 pi 值越准确(小数点后显示的位数)
列表 1 的大部分代码引用自 Sun 的远程方法调用教程的“创建一个客户端程序”部分。 |
此算法的实现依赖于 java.math.BigDecimal
和一个 arc-tangent 方法。 虽然 Java SE 5.0 等高级版本的 BigDecimal
包括常量 ZERO
和 ONE
,这些常量在 Java 1.4 中是不存在的。 同样,number-of-digits 命令行参数用于确定 arc-tangent 的数量和 pi 的精确度。
java CalcPi 0
3
java CalcPi 1
3.1
java CalcPi 2
3.14
java CalcPi 3
3.142
java CalcPi 4
3.1416
java CalcPi 5
3.14159
本文中更重要的部分是 Toolkit.getDefaultToolkit ().beep ();
,该语句用于在计算结束时发出“哗哗”的声音。 由于数字参数越大造成的计算时间越长,此声音可以让您知道 pi 的计算何时结束。 如果一声“哗”响不够用,可以按照如下方法创建其他的音效:
Toolkit tk = Toolkit.getDefaultToolkit ();
for (int i = 0; i < NUMBER_OF_BEEPS; i++)
{
tk.beep ();
// On Windows platforms, beep() typically
// plays a WAVE file. If beep() is called
// before the WAVE sound finishes, the
// second WAVE sound will not be heard. A
// suitable delay solves this problem.
// (I'm not sure if this problem occurs
// on other platforms.)
try
{
Thread.sleep (BEEP_DELAY);
}
catch (InterruptedException e)
{
}
}
窗口居中
使您的 Java 程序看起来更加专业的一种方法就是使其对话框窗口(例如,一个 "about" 窗口)位于父窗口的中间。 可以使用 java.awt.Window
的 public void setLocationRelativeTo(Component c)
方法完成此任务,该方法将相对于组件参数 —null
来创建一个居于屏幕中间的窗口。 请看列表 2。
列表 2 AboutBox1.java
// AboutBox1.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
public class AboutBox1
{
public static void main (String [] args)
{
final JFrame frame = new JFrame ("AboutBox1");
frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel ()
{
{
JButton btnWindow = new JButton ("Window center");
ActionListener l = new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
new AboutBox (frame, "W").setVisible (true);
}
};
btnWindow.addActionListener (l);
add (btnWindow);
JButton btnScreen = new JButton ("Screen center");
l = new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
new AboutBox (frame, "S").setVisible (true);
}
};
btnScreen.addActionListener (l);
add (btnScreen);
}
};
frame.getContentPane ().add (panel);
// frame.setLocationRelativeTo (null);
frame.pack ();
// frame.setLocationRelativeTo (null);
frame.setVisible (true);
}
}
class AboutBox extends JDialog
{
AboutBox (JFrame frame, String centerMode)
{
super (frame, "AboutBox", true /* modal */);
final JButton btnOk = new JButton ("Ok");
btnOk.addActionListener (new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
dispose ();
}
});
getContentPane ().add (new JPanel () {{ add (btnOk); }});
pack ();
setLocationRelativeTo (centerMode.equals ("W") ? frame : null);
}
}
Listing 2's AboutBox1
创建了一个 GUI,它的两个按钮建立了一个 "about" 对话框,该对话框通过 setLocationRelativeTo()
方法位于应用程序主窗口或屏幕的中心位置。 frame.pack ();
之前被注释掉的一行无法起到使主窗口居中的作用,因为主窗口的大小还没有确定。 但是,被注释掉的第二行起到了使窗口居中的作用。
getContentPane ().add (new JPanel () {{ add (btnOk); }});
也许看起来有点奇怪,因为它嵌套了许多括号。 本质上该语句可以理解为,创建一个内部匿名类(该类扩展自 javax.swing.JPanel
)的对象,通过由内层括号对标识的 object block initializer 为此对象添加一个按钮,然后将对象添加到对话框的 content pane 中。
添加阴影
如 果您想突出显示一个 "about" 对话框的标题文字,可以考虑以一定的偏移量和指定的颜色绘制背景文字以达到投放“阴影”的效果。 选择一个适当的颜色做为背景字的颜色,注意与前景文字和背景的颜色搭配。 然后使用反失真技术使边缘上的小锯齿变得平滑。 效果如图 1 所示:
图 1:阴影可以强调对话框的标题
图 1 展示了一个具有蓝色文字、黑色阴影和白色背景的 "about" 对话框。 该对话框是在 AboutBox2
程序的 AboutBox(JFrame frame, String centerMode)
构造函数内创建的。 由于该程序的代码与 AboutBox1.java
极为相似,所以我只给出其构造函数:
AboutBox (JFrame frame, String centerMode)
{
super (frame, "AboutBox", true /* modal */);
// Add a panel that presents some text to the dialog box's content pane.
getContentPane ().add (new JPanel ()
{
final static int SHADOW_OFFSET = 3;
{
// Establish the drawing panel's preferred
// size.
setPreferredSize (new Dimension (250, 100));
// Create a solid color border that both
// surrounds and is part of the drawing
// panel. Select the panel background
// color that is appropriate to this look
// and feel.
Color c =
UIManager.getColor ("Panel.background");
setBorder (new MatteBorder (5, 5, 5, 5, c));
}
public void paintComponent (Graphics g)
{
// Prevent jagged text.
((Graphics2D) g).setRenderingHint
(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// Because the border is part of the panel,
// we need to make sure that we don't draw
// over it.
Insets insets = getInsets ();
// Paint everything but the border white.
g.setColor (Color.white);
g.fillRect (insets.left, insets.top,
getWidth ()-insets.left-
insets.right,
getHeight ()-insets.top-
insets.bottom);
// Select an appropriate text font and
// obtain the dimensions of the text to be
// drawn (for centering purposes). The
// getStringBounds() method is used instead
// of stringWidth() because antialiasing is
// in effect -- and the documentation for
// stringWidth() recommends use of this
// method whenever the antialiasing or
// fractional metrics hints are in effect.
g.setFont (new Font ("Verdana",
Font.BOLD,
32));
FontMetrics fm = g.getFontMetrics ();
Rectangle2D r2d;
r2d = fm.getStringBounds ("About Box", g);
int width = (int)((Rectangle2D.Float) r2d)
.width;
int height = fm.getHeight ();
// Draw shadow text that is almost
// horizontally and vertically (the
// baseline) centered within the panel.
g.setColor (Color.black);
g.drawString ("About Box",
(getWidth ()-width)/2+
SHADOW_OFFSET,
insets.top+(getHeight()-
insets.bottom-insets.top)/2+
SHADOW_OFFSET);
// Draw blue text that is horizontally and
// vertically (the baseline) centered
// within the panel.
g.setColor (Color.blue);
g.drawString ("About Box",
(getWidth ()-width)/2,
insets.top+(getHeight()-
insets.bottom-insets.top)/2);
}
}, BorderLayout.NORTH);
final JButton btnOk = new JButton ("Ok");
btnOk.addActionListener (new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
dispose ();
}
});
getContentPane ().add (new JPanel () {{ add (btnOk); }},
BorderLayout.SOUTH);
pack ();
setLocationRelativeTo (centerMode.equals ("W") ? frame : null);
}
除了向您介绍如何在 JPanel
子类组件的 public void paintComponent(Graphics g)
方法中呈现阴影以外,构造函数还揭示了一个技巧:使用 UIManager.getColor("Panel.background")
获取与对话框背景色匹配的组件边框的颜色(即现在的外观)。
超级链接和启动浏览器
许多程序都会在 "about" 对话框中呈现超级链接。单击超级链接时,程序将启动默认浏览器,并为用户打开应用程序的网站。我想 "about" 对话框中的超级链接是可以用类来描述的,所以我创建了一个 AboutBox3
应用程序来说明其可行性。请阅读以下代码:
AboutBox (JFrame frame, String centerMode)
{
super (frame, "AboutBox", true /* modal */);
// Create a pane that presents this dialog box's text. Surround the pane
// with a 5-pixel empty border.
Pane pane = new Pane (5);
pane.setPreferredSize (new Dimension (250, 100));
// Create a title with a drop shadow for the pane.
Font font = new Font ("Verdana", Font.BOLD, 32);
Pane.TextNode tn = pane.new TextNode ("About Box", font, Color.blue,
Pane.TextNode.CENTERX,
Pane.TextNode.CENTERY, Color.black);
pane.add (tn);
// Create a link for the pane.
font = new Font ("Verdana", Font.BOLD, 12);
tn = pane.new TextNode ("Jeff Friesen", font, Color.blue,
Pane.TextNode.CENTERX, 80,
null, "http://www.javajeff.mb.ca", Color.red);
pane.add (tn);
// Add pane to the center region of the dialog box's content pane.
getContentPane ().add (pane);
// Create a button for disposing the dialog box.
final JButton btnOk = new JButton ("Ok");
btnOk.addActionListener (new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
dispose ();
}
});
// Add button via an intermediate panel that causes button to be laid
// out at its preferred size to the south region of the dialog box's
// content pane.
getContentPane ().add (new JPanel () {{ add (btnOk); }},
BorderLayout.SOUTH);
// Resize all components to their preferred sizes.
pack ();
// Center the dialog box with respect to the frame window or the screen.
setLocationRelativeTo (centerMode.equals ("W") ? frame : null);
}
AboutBox(JFrame frame, String centerMode)
构造函数创建了一个 Pane
组件,来描述一个用于绘制文字的区域。该组件的 Pane(int borderSize)
构造函数使用 borderSize
参数,来识别组件边框的大小(以像素为单位)-- 绘制区域的大小等于 Pane 的大小减去边框大小:
Pane (int borderSize)
{
// Create a solid color border that both surrounds and is part of the
// this component. Select the panel background color that is appropriate
// to this look and feel.
setBorder (new MatteBorder (borderSize, borderSize, borderSize,
borderSize,
UIManager.getColor ("Panel.background")));
}
该组件将文字存储为 Pane.TextNode
对象的数组列表。每个 TextNode
描述一个文字条目,并且创建自三个构造函数之一。最简单的构造函数是 TextNode(String text, Font font, Color color, int x, int y)
,它用于创建一个非超级链接且没有阴影的文字节点。使用的五个参数是:
text
指定要绘制的文字font
指定要使用的字体font
指定要使用的文字颜色x
指定第一个字符的起始列y
指定每个字符基线所在的行
第二简单的构造函数是:TextNode(String text, Font font, Color color, int x, int y, Color shadowColor)
.除了上述参数外,还需要为该函数指定 shadowColor
,即阴影的颜色。如果传递 null
,则不呈现阴影,这样一来该构造函数就与前一构造函数一样了。这两个构造函数都调用下面的第三个构造函数:
TextNode (String text, Font font, Color color, int x, int y,
Color shadowColor, String url, Color activeLinkColor)
{
this.text = text;
this.font = font;
this.color = color;
this.x = x;
this.y = y;
this.shadowColor = shadowColor; this.url = url;
this.activeLinkColor = activeLinkColor;
if (url != null)
{
addMouseListener (new MouseAdapter ()
{
public void mousePressed (MouseEvent e)
{
int mx = e.getX ();
int my = e.getY ();
if (mx >= TextNode.this.x &&
mx < TextNode.this.x+width &&
my > TextNode.this.y-height &&
my <= TextNode.this.y)
{
active = false;
repaint ();
}
}
public void mouseReleased (MouseEvent e)
{
int mx = e.getX ();
int my = e.getY ();
if (mx >= TextNode.this.x &&
mx < TextNode.this.x+width &&
my > TextNode.this.y-height &&
my <= TextNode.this.y)
{
active = true;
repaint ();
Launcher.
launchBrowser (TextNode.this.url);
}
}
});
addMouseMotionListener (new MouseMotionListener ()
{
public void mouseMoved (MouseEvent e)
{
int mx = e.getX ();
int my = e.getY ();
if (mx >= TextNode.this.x &&
mx < TextNode.this.x+width &&
my > TextNode.this.y-height &&
my <= TextNode.this.y)
{
if (!active)
{
active = true;
repaint ();
}
}
else
{
if (active)
{
active = false;
repaint ();
}
}
}
public void mouseDragged (MouseEvent e)
{
}
});
}
}
保存参数后,该构造函数会在 Pane 中注册一个鼠标监听器(假设 url
不为 null
)。这些侦听器将判断鼠标指针是否位于超级链接文本上。如果是,则操纵 active
变量,将呈现该节点及其他节点,并启动浏览器。
class Launcher
{
static void launchBrowser (String url)
{
try
{
// Identify the operating system.
String os = System.getProperty ("os.name");
// Launch browser with URL if Windows. Otherwise, just output the url
// to the standard output device.
if (os.startsWith ("Windows"))
Runtime.getRuntime ()
.exec ("rundll32 url.dll,FileProtocolHandler " + url);
else
System.out.println (url);
}
catch (IOException e)
{
System.err.println ("unable to launch browser");
}
}
}
Pane
的 public void paintComponent(Graphics g)
方法将在该组件及其文本节点呈现的时候被调用。该方法启用反失真技术(防止文字出现锯齿),获取组件的插入位置(所以文本不会落在边框上),清理绘制区域使其成为白色,并呈现数组列表存储的每个文字节点。
public void paintComponent (Graphics g)
{
// Prevent jagged text.
((Graphics2D) g).setRenderingHint (RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// Because the border is part of the panel, we need to make sure that we
// don't draw over it.
Insets insets = getInsets ();
// Paint everything but the border white.
g.setColor (Color.white);
g.fillRect (insets.left, insets.top, getWidth ()-insets.left-insets.right,
getHeight ()-insets.top-insets.bottom);
// Render all nodes.
Iterator iter = nodes.iterator ();
while (iter.hasNext ())
{
TextNode tn = (TextNode) iter.next ();
tn.render (g, insets);
}
}
每个文本节点都是由 TextNode
的 void render(Graphics g, Insets insets)
方法呈现的。该方法首先确定了字体,然后调用私有的 strDim()
方法来获取要绘制文字的规格尺寸等。然后呈现文本(可以选择阴影、超级链接等属性):
void render (Graphics g, Insets insets)
{
g.setFont (font);
Dimension d = strDim (g, text);
width = (int) d.width;
height = (int) d.height;
// Always drop the drop shadow (if specified) first.
if (shadowColor != null)
{
g.setColor (shadowColor);
if (x == CENTERX)
x = (getWidth ()-d.width)/2;
if (y == CENTERY)
y = insets.top+(getHeight ()-insets.bottom-insets.top)/2;
// Draw the drop shadow.
g.drawString (text, x+SHADOW_OFFSET, y+SHADOW_OFFSET);
}
// If the text is not a link, active can never be true -- the mouse
// listeners are not installed.
g.setColor ((active) ? activeLinkColor: color);
// If a drop shadow was drawn, x and y will never equal CENTERX and
// CENTERY (respectively). This is okay because x and y must contain
// the same values as specified when drawing the drop shadow.
if (x == CENTERX)
x = (getWidth ()-d.width)/2;
if (y == CENTERY)
y = insets.top+(getHeight ()-insets.bottom-insets.top)/2;
// Draw the text.
g.drawString (text, x, y);
}
g.setColor ((active) ? activeLinkColor: color);
决定是否绘制有效的超级链接文本(使用由activeLinkColor
指定的有效链接颜色),或者使用由 color
指定的颜色绘制无效超级链接文本(或非超级链接文本)。图 2 显示了此决定的结果:
图 2 当鼠标指针移动到文本上时,超级链接的文本变成了红色。单击可查看大图。
状态栏
许多应用程序都会呈现状态栏,以显示程序的名称和版本、相应于菜单的帮助文字、当前时间以及一些其他信息。由于状态栏如此有用,您可能认为 Java 包含了一个 javax.swing.JStatusBar
组件。然而,事实并非如此。幸运的是,创建自己的状态栏还算容易,如图 3 所示。
图 3. 状态栏中当前菜单项的帮助文字
图 3 显示了一个由 StatBar
应用程序创建的状态栏。该应用程序将一个 javax.swing.JLabel
组件和一个 javax.swing.event.MenuListener
结合在一起,然后使用 java.awt.event.MouseListener
显示相应于菜单的菜单项帮助文字 -- 或者在未选择菜单或菜单项时显示默认文字。请看列表 3。
列表 3. StatBar.java
// StatBar.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class StatBar extends JFrame
{
// The status label serves as this application's status bar. A description
// of the currently highlighted menu/item appears on the status bar.
JLabel status;
// The default text appears on the status bar at program startup, and when
// no other menu/item text appears.
String defaultStatusText = "Welcome to StatBar 1.0!";
// The MenuItem helper class conveniently organizes the menu items for each
// of the File and Edit menus. This organization reduces the amount of
// source code that appears in the StatBar() constructor, which hopefully
// makes it easier to study the constructor, and facilitates adding extra
// menu items in the future.
class MenuItem
{
String label; // menu text
ActionListener al; String desc; // menu description for status bar
MenuItem (String label, ActionListener al, String desc)
{
this.label = label;
this.al = al;
this.desc = desc;
}
}
// Construct StatBar's GUI and indirectly start AWT helper threads.
public StatBar (String title)
{
// Pass application title to superclass, so that it appears on the title
// bar.
super (title);
// When the user initiates a close operation from the System menu or by
// clicking the tiny x window on a Microsoft Windows' window title bar,
// terminate this application.
setDefaultCloseOperation (EXIT_ON_CLOSE);
// Construct the application's menu bar.
JMenuBar mb = new JMenuBar ();
// Create a menu listener shared by all menus on the menu bar. This menu
// listener either displays default text or menu-specific text on the
// status bar.
MenuListener menul;
menul = new MenuListener ()
{
public void menuCanceled (MenuEvent e)
{
}
public void menuDeselected (MenuEvent e)
{
status.setText (defaultStatusText);
}
public void menuSelected (MenuEvent e)
{
JMenu m = (JMenu) e.getSource ();
status.setText (m.getActionCommand ());
}
};
// Create a mouse listener shared by all menu items on all menus. This
// mouse listener displays menu-item specific text on the status bar
// whenever the mouse pointer enters the menu item. It displays default
// text when the mouse pointer exits a menu item.
MouseListener statusl = new MouseAdapter ()
{
public void mouseEntered (MouseEvent e)
{
JMenuItem mi = (JMenuItem) e.getSource ();
status.setText (mi.getActionCommand ());
}
public void mouseExited (MouseEvent e)
{
status.setText (defaultStatusText);
}
};
// The first menu to appear on the menu bar is File. The user invokes
// menu items on this menu to open, save, and print documents, and to
// terminate the application.
JMenu menuFile = new JMenu ("File");
menuFile.addMenuListener (menul);
menuFile.setActionCommand ("Open document, save changes, print document "
+ "and terminate StatBar.");
// Create a listener for each menu item on the File menu.
ActionListener openl;
openl = new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
System.out.println ("Open listener invoked.");
}
};
ActionListener saveasl;
saveasl = new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
System.out.println ("Save as listener invoked.");
}
};
ActionListener savel;
savel = new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
System.out.println ("Save listener invoked.");
}
};
ActionListener printl;
printl = new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
System.out.println ("Print listener invoked.");
}
};
ActionListener exitl;
exitl = new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
System.exit (0);
}
};
// Identify menu items to be installed on the File menu.
MenuItem [] itemsFile =
{
new MenuItem ("Open...", openl, "Open a document."),
new MenuItem ("Save", savel, "Save changes to current document."),
new MenuItem ("Save as...", saveasl, "Save current document to new "
+ "document."),
new MenuItem ("Print...", printl, "Print current document."),
new MenuItem (null, null, null),
new MenuItem ("Exit", exitl, "Terminate StatBar.")
};
// Install all of the previous menu items on the File menu.
for (int i = 0; i < itemsFile.length; i++)
{
if (itemsFile [i].label == null)
{
menuFile.addSeparator ();
continue;
}
JMenuItem mi = new JMenuItem (itemsFile [i].label);
mi.addActionListener (itemsFile [i].al);
mi.setActionCommand (itemsFile [i].desc);
mi.addMouseListener (statusl);
menuFile.add (mi);
}
// Add the file menu to the menu bar.
mb.add (menuFile);
// The second menu to appear on the menu bar is Edit. The user invokes
// menu items on this menu to undo any changes and perform copy/cut/paste
// operations on the current document.
JMenu menuEdit = new JMenu ("Edit");
menuEdit.addMenuListener (menul);
menuEdit.setActionCommand ("Perform various editing tasks and undo " +
"changes.");
// Create a listener for each menu item on the Edit menu.
ActionListener undol;
undol = new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
System.out.println ("Undo listener invoked.");
}
};
ActionListener copyl;
copyl = new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
System.out.println ("Copy listener invoked.");
}
};
ActionListener cutl;
cutl = new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
System.out.println ("Cut listener invoked.");
}
};
ActionListener pastel;
pastel = new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
System.out.println ("Paste listener invoked.");
}
};
// Identify menu items to be installed on the Edit menu.
MenuItem [] itemsEdit =
{
new MenuItem ("Undo", undol, "Restore document."),
new MenuItem (null, null, null),
new MenuItem ("Copy", copyl, "Copy text to clipboard."),
new MenuItem ("Cut", cutl, "Cut text to clipboard."),
new MenuItem ("Paste", pastel, "Paste text from clipboard.")
};
// Install all of the previous menu items on the Edit menu.
for (int i = 0; i < itemsEdit.length; i++)
{
if (itemsEdit [i].label == null)
{
menuEdit.addSeparator ();
continue;
}
JMenuItem mi = new JMenuItem (itemsEdit [i].label);
mi.addActionListener (itemsEdit [i].al);
mi.setActionCommand (itemsEdit [i].desc);
mi.addMouseListener (statusl);
menuEdit.add (mi);
}
// Add the edit menu to the menu bar.
mb.add (menuEdit);
// Install StatBar's menu bar.
setJMenuBar (mb);
// Create a status bar for displaying help text associated with the menus
// and their items.
status = new JLabel (defaultStatusText);
status.setBorder (BorderFactory.createEtchedBorder ());
// Add the status bar to the bottom of the application's contentpane.
getContentPane ().add (status, BorderLayout.SOUTH);
// Establish a suitable initial size for displaying a document.
setSize (450, 300);
// Display GUI and start GUI processing.
setVisible (true);
}
// Application entry point.
public static void main (String [] args)
{
// Create the application's GUI and start the application.
new StatBar ("StatBar");
}
}
在使用默认状态栏文字创建了 status
JLabel
之后,StatBar
刻画了一个边框 (通过 status.setBorder (BorderFactory.createEtchedBorder ());
)以使状态栏标签与 GUI 的其他部分区别开来。然后,标签被添加到框架窗口内容窗格的南侧区域,这是状态栏的常见位置。
如果不希望状态栏显示任何默认文字,则需要至少输入一个空格来代替文字。如果使用空字符串 ("" ) 来代替,则不会显示状态栏(虽然会显示其边框)。必须处理状态栏标签的首选字体大小,因为这涉及到当前的字体和那个不可缺少的字符。如果状态栏标签不包含任何字符(空字符串),其首选字体大小就会是 0,造成状态栏无法显示。 |
MenuItemListener
接口描述了一个 "File" 和 "Edit" 菜单的侦听程序。此接口的 public void menuSelected(MenuEvent e)
方法将在选择这些菜单时被调用,然后会显示菜单的帮助文字。选中一个菜单时,调用 public void menuDeselected(MenuEvent e)
显示文字。
MouseListener
接口描述每个菜单项的侦听程序。其 public void mouseEntered(MouseEvent e)
在鼠标进入一个菜单项时被调用。然后,菜单项的帮助文字会显示在状态栏中。鼠标指针移动到菜单项外时,调用 public void mouseExited(MouseEvent e)
,然后显示默认文字。
每个侦听程序都依赖于 javax.swing.JMenu
或 javax.swing.JMenuItem
的继承 public void setActionCommand(String command)
方法,此方法曾在指定每个菜单或菜单项的状态栏文字时被调用。文字是在侦听程序内部进行检索的,方法是调用相关的 public String getActionCommand()
方法。
抓图程序
几年前,我构建过一个基于命令行的 GetImages
应用程序 -- 请看列表 4 -- 来获取网页中的图像,并将它们存放到我的磁盘上。此应用程序只有一个用于连接到 HTML 文档的 URL 参数。解析 HTML 文档,将 标记的
src
属性值提取为可识别的图像文件,然后下载这些文件。
列表 4 GetImages.java
// GetImages.java
import java.io.*;
import java.net.*;
import java.util.regex.*;
import javax.swing.text.*;
import javax.swing.text.html.*;
import javax.swing.text.html.parser.ParserDelegator;
public class GetImages
{
public static void main (String [] args)
{
// Validate number of command-line arguments.
if (args.length != 1)
{
System.err.println ("usage: java GetImages URL");
return;
}
// Create a Base URI from the solitary command-line argument. This URI
// will be used in the handleSimpleTag() callback method to convert a
// potentially relative URI in an tag's src attribute to an
// absolute URI.
final URI uriBase;
try
{
uriBase = new URI (args [0]);
}
catch (URISyntaxException e)
{
System.err.println ("URI is improperly formed");
return;
}
// Convert the URI to a URL, so that the HTML document can be read and
// parsed.
URL url;
try
{
url = new URL (args [0]);
}
catch (MalformedURLException e)
{
System.err.println ("URL is improperly formed");
return;
}
// Establish a callback whose handleSimpleTag() method is invoked for
// each tag that does not have an end tag. The tag is an example.
HTMLEditorKit.ParserCallback callback;
callback = new HTMLEditorKit.ParserCallback ()
{
public void handleSimpleTag (HTML.Tag tag,
MutableAttributeSet aset,
int pos)
{
// If an tag is encountered ...
if (tag == HTML.Tag.IMG)
{
// Get the value of the src attribute.
String src = (String)
aset.getAttribute (HTML.Attribute.SRC);
// Create a URI based on the src value, and then
// resolve this potentially relative URI against
// the document's base URI, to obtain an absolute
// URI.
URI uri = null;
try
{
// Handle this situation:
//
// 1) http://www.javajeff.mb.ca
//
// There is no trailing forward slash.
//
// 2) common/logo.jpg
//
// There is no leading forward slash.
//
// 3) http://www.javajeff.mb.cacommon/logo.jpg
//
// The resolved URI is not valid.
if (!uriBase.toString ().endsWith ("/") &&
!src.startsWith ("/"))
src = "/" + src;
uri = new URI (src);
uri = uriBase.resolve (uri);
System.out.println ("uri being " +
"processed ... " + uri);
}
catch (URISyntaxException e) {
System.err.println ("Bad URI");
return;
}
// Convert the URI to a URL so that its input
// stream can be obtained.
URL url = null;
try
{
url = uri.toURL ();
}
catch (MalformedURLException e)
{
System.err.println ("Bad URL");
return;
}
// Open the URL's input stream.
InputStream is;
try
{
is = url.openStream ();
}
catch (IOException e)
{
System.err.println ("Unable to open input " +
"stream");
return;
}
// Extract URL's file component and remove path
// information -- only the filename and its
// extension are wanted.
String filename = url.getFile ();
int i = filename.lastIndexOf ('/');
if (i != -1)
filename = filename.substring (i+1);
// Save image to file.
saveImage (is, filename);
}
}
};
// Read and parse HTML document.
try
{
// Read HTML document via an input stream reader that assumes the
// default character set for decoding bytes into characters.
Reader reader = new InputStreamReader (url.openStream ());
// Establish a ParserDelegator whose parse() method causes the
// document to be parsed. Various callback methods are called and
// the document's character set is not ignored. The parse() method
// throws a ChangedCharSetException if it encounters a tag
// with a charset attribute that specifies a character set other
// than the default.
new ParserDelegator ().parse (reader, callback, false);
}
catch (ChangedCharSetException e)
{
// Reparse the entire file using the specified charset. A regexp
// pattern is specified to extract the charset name.
String csspec = e.getCharSetSpec ();
Pattern p = Pattern.compile ("charset=\"?(.+)\"?\\s*;?",
Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher (csspec);
String charset = m.find () ? m.group (1) : "ISO-8859-1";
// Read and parse HTML document using appropriate character set.
try
{
// Read HTML document via an input stream reader that uses the
// specified character set to decode bytes into characters.
Reader reader;
reader = new InputStreamReader (url.openStream (), charset);
// This time, pass true to ignore the tag with its charset
// attribute.
new ParserDelegator ().parse (reader, callback, true);
}
catch (UnsupportedEncodingException e2)
{
System.err.println ("Invalid charset");
}
catch (IOException e2)
{
System.err.println ("Input/Output problem");
e.printStackTrace ();
}
}
catch (IOException e)
{
System.err.println ("Input/Output problem");
e.printStackTrace ();
}
}
public static void saveImage (InputStream is, String filename)
{
FileOutputStream fos = null;
try
{
fos = new FileOutputStream (filename);
int bYte;
while ((bYte = is.read ()) != -1)
fos.write (bYte);
}
catch (IOException e)
{
System.err.println ("Unable to save stream to file");
}
finally
{
if (fos != null)
try
{
fos.close ();
}
catch (IOException e)
{
}
}
}
}
列表 4 使用 javax.swing.text.html.parser.ParserDelegator
类创建一个 HTML 文档解析器,调用解析器对象的 public void parse(Reader r, HTMLEditorKit.ParserCallback cb, boolean ignoreCharSet)
实行解析。此方法使用了三个参数:
r
标识一个用于读取 HTML 文档的java.io.Reader
对象。cb
标识一个处理所解析的标记及其属性的javax.swing.text.html.HTMLEditorKit.ParserCallback
对象。ignoreCharSet
标识是否忽略文档的标记(如果存在)中的
charset
属性。
解析器在解析过程中调用了各种各样的 ParserCallback
方法。但只有一个方法是 GetImages
需要的:即 public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos)
方法。这是一个为没有结束标记的标记(例如,)调用的方法,它使用了三个参数:
t
通过HTML.Tag
对象对标记进行标识。a
通过javax.swing.text.MutableAttributeSet
对象标识标记内的属性。pos
标识当前解析到的位置
现在您已经清楚地认识到 GetImages
的工作原理,应该试着运行一下了。例如,您可以指定 java GetImages http://www.javajeff.mb.ca
将该网页上的图像下载到自己的 Web 站点的主页上。结果应该与以下类似(当前目录下出现了一些新图像文件):
uri being processed ... http://www.javajeff.mb.ca/common/logo.jpg
uri being processed ... http://www.javajeff.mb.ca/common/logo.gif
uri being processed ... http://www.javajeff.mb.ca/na/images/wom.jpg
但是 GetImages
在命令行模式下再多么有用,也不及使用 GUI 抓取和查看图像来得方便。我的 IG
应用程序,结合 GetImages
源代码和前面的一些技巧以及一些 GUI 代码正是为此而生。图 4 展示了 GetImages
的 GUI,建议您查看本文的代码归档文件(资源)以获取源代码。
图 4 使用抓图程序的 GUI 方便地抓取和查看图像
结束语
此图像抓取程序将文章中介绍的技巧很好地结合在一起,但是还遗留了一些问题,关于这些问题的解决方法就当成家庭作业吧。问题 1:抓图程序下载图像,图像的标记需要指明相对 URL (例如,),但是无法下载指明绝对 URL 的图像(例如,
)。
问题 2:抓图程序无法下载动态生成 的
src
属性的图像。作为例子,以下 标记的
src
属性是我最近在 JavaWorld 主页上发现的:src="http://ad.doubleclick.net/ad/idg.us.nwf.jw_home/;abr=!ie;pos=top;sz=728x90;ptile=1;type=;ord=063423?"
。
作者简介
Jeff Friesen 是一位自由职业的软件开发者,擅长的领域是 C、C++ 和 Java 技术。资源
<externallinks no-escaping="yes"></externallinks>
- 下载文章中使用的源代码:
http://www.javaworld.com/javaworld/jw-01-2007/games/jw-01-games.zip - 您可以使用 Java Fun and Games 中的 DevSquare 在线开发工具来生成和运行 Applet。请阅读此入门指南:
相关推荐
通常,你可以通过包管理器(如Ubuntu的`apt-get`或Fedora的`dnf`)来安装FFmpeg,但可能需要额外的步骤来确保`x11grab`被包含。如果默认安装不包含`x11grab`,你可以手动编译FFmpeg源代码并确保在配置时启用该模块。...
JavaCV(Java Computer Vision)是一个Java接口,它提供了与多个流行的计算机视觉库的无缝集成,如OpenCV、FFmpeg、ImageIO等。这个框架的主要目的是让Java开发者能够更轻松地利用这些强大的计算机视觉库,无需深入...
compile 'com.github.sarxos:javacv:0.7' // 如果需要其他依赖,如opencv,可以单独添加 compile 'org.opencv:opencv:2.4.3' } ``` - 如果是传统的Java项目,将jar文件添加到项目的类路径中,或者在IDE中设置...
`go-grab`(或简称为`grab`)就是一个专门为此设计的包,它为开发者提供了强大的功能来管理文件的下载过程。这个包允许你监控下载进度、设置限速、断点续传,并且提供错误处理机制,确保了下载过程的稳定性和可靠性...
GrabCut算法是一种在计算机视觉领域中用于图像分割的高级技术,尤其适用于前景与背景分离的任务。这个算法由Rother、Kolmogorov和Blake在2004年提出,它结合了图形模型(马尔科夫随机场)和交互式的方法,使得用户...
GRABIT工具箱与MATLAB的结合,使得用户能够在熟悉的MATLAB环境中,轻松处理特定领域的专业问题。 GRABIT的主要功能包括: 1. 数据导入:GRABIT支持多种数据格式,如ASCII、CSV、BINARY等,方便用户将来自不同来源...
GrabCut是一种在图像分割领域广泛应用的交互式算法,它结合了图形割(Graph Cut)方法和高斯混合模型(Gaussian Mixture Models, GMM)。该算法允许用户通过选择初始的前景和背景区域来指导分割过程,从而得到更加...
`javacv`是Java版本的OpenCV库,它提供了与计算机视觉相关的各种功能,包括图像处理、视频捕获和分析等。本篇文章将深入探讨如何使用`javacv`库来实现摄像头的显示。 首先,`javacv`依赖于多个其他的库,如OpenCV、...
Grab Cut是一种基于交互式的图像分割算法,它在计算机视觉领域有着广泛的应用。图像分割是将图像中的不同区域根据其颜色、纹理、亮度等特征进行区分,以便进一步分析和理解图像内容。Grab Cut算法由Rother、...
在本文中,我们将深入探讨如何在Visual Studio 2017中使用OpenCV库的GrabCut算法进行图像分割。OpenCV是一个广泛使用的计算机视觉库,它提供了丰富的图像处理和计算机视觉功能,包括图像分割。GrabCut算法是OpenCV...
在OpenCV中,GrabCut算法是一种交互式的图像分割方法,用于从复杂背景中提取前景对象,比如从一张照片中抠出人物或者物体。这个算法结合了马尔科夫随机场(Markov Random Field, MRF)模型和交互式用户输入,能够...
【标题】"myGrabcut:我的grabcut实现" 涉及的是计算机视觉领域中的一种图像分割技术——GrabCut算法的个人实现。GrabCut是一种交互式图像分割方法,由Rother、Kolmogorov和Blake在2004年提出,它结合了图形模型和...
### Java读取SHP文件及DBF属性的关键技术解析 #### 概述 在地理信息系统(GIS)领域,Shapefile是一种常见的矢量数据格式,用于存储地理位置信息及相关属性数据。一个完整的Shapefile由多个文件组成,包括.shp、....
ebson 是一个可扩展的 BSON 文档 Java 开发包。Maven: <groupId>com.github.kohanyirobert</groupId> <artifactId>ebson <version>... 示例代码:// create documents to serialize BsonDocument ...
在IT行业中,获取视频的第一帧是一项常见的任务,特别是在多媒体处理、视频分析或视频预览等应用场景。...通过这个工具包,开发者可以轻松地在Java应用程序中实现视频处理功能,无需深入理解复杂的底层多媒体处理细节。
GrabCut分割算法是一种在计算机视觉领域中用于图像分割的技术,由Rother、Kolmogorov和Blake在2004年提出。OpenCV库提供了对GrabCut算法的实现,使得开发者能够轻松地在实际项目中应用这一算法。本文将深入探讨Grab...
2. **初始化FFmpegFrameGrabber**:JAVACV提供了`FFmpegFrameGrabber`类,用于从摄像头或其他视频源抓取帧。通过构造函数传入摄像头设备ID,初始化一个`FFmpegFrameGrabber`实例。 3. **开始抓取**:调用`start()`...
IplImage frame = FFmpegFrameGrabber.createDefault(imgPath).grabImage(); recorder.record(frame); } recorder.stop(); ``` 7. 性能优化: 在处理大量图片时,需要注意性能优化,例如使用多线程批量处理...
【图像分割与GrabCut算法详解】 图像分割是计算机视觉领域中的关键步骤,它将图像划分为不同的区域,每个区域代表图像中的一个特定对象或背景。OpenCV是一个强大的开源计算机视觉库,提供了许多图像处理和机器学习...
JavaCV(Java Computer Vision)是一个基于Java的计算机视觉库,它为Java开发者提供了一系列与图像处理、视频捕获和分析相关的API。这个库是多个开源计算机视觉库的封装,包括OpenCV、FFmpeg等,因此它能让我们在...