Java 控制台应用程序变得容易






4.83/5 (20投票s)
本文介绍了如何创建一个 Java 控制台应用程序。
目录
引言
不起眼的控制台应用程序:我一直在写它们,用于创建一次性的测试来探索新的编码想法。我甚至可以快速搭建基于菜单的测试应用程序,用于测试远程设备通信和命令集,在产品开发周期中,命令的数量可能会增加。当然,优点是,要扩展应用程序,我只需在代码中添加一个菜单项和一个事件处理程序。这比在已经拥挤的 Windows 窗体应用程序中塞入更多字段要好得多。有时,你直到失去才知道你拥有什么。
我决定今年学习 Java,并了解这种语言和工具。我通常通过将一个我已经写过的应用程序翻译成新语言来学习一门新语言(对我而言),并在此过程中遇到的棘手问题中摸索前进。令我惊讶的是,我在 C# 中理所当然的许多事情在 Java 中却很难实现;例如,控制台应用程序。
我选择移植到 Java 的应用程序恰好是一个基于控制台的菜单驱动应用程序。一旦我弄清楚如何用一个接口和嵌套的 Java 类来代替 C# 委托以获得回调行为,菜单代码的移植就相当直接了。真正的头疼之处在于应用程序的菜单代码在实际的 Windows 控制台实例中的性能。
Java 应用程序使用 STDIN
、STDOUT
和 STDERR
流,但不拥有显示这些流的控制台实例。由于 Java 旨在覆盖广泛的操作系统,它不包含清除屏幕的支持。我尝试了许多方法来让我的应用程序按预期运行,但都没有得到可接受的结果。
我正要放弃时,我改变了思路,考虑在 Java GUI 小部件中复制控制台功能。这似乎更有前景。有很多将 STDOUT
和 STDERR
重定向到 JtextArea
的例子,但我找不到一个也能成功地将键盘输入复制到 STDIN
的例子。我确实看到很多其他开发者表达了对这种组件的渴望。
我开始测试我找到的各种代码解决方案和代码片段,并选择了一个特别健壮的示例[^]。然后我添加了一些代码来将键盘字符管道传输到 STDIN
,添加了一个公共 clear()
方法,以及一些属性来定制控制台的外观。最后,我弄清楚了如何为 JavaConsole
创建一个自定义的闪烁块状光标来画龙点睛。
如何使用此 Java 控制台
最少来说,要在您的项目中实现此 Java 控制台,只需声明一个 JavaConsole
类的实例。
public static void main(String[] args) {
new JavaConsole();
scanner = new Scanner(System.in);
System.out.println("Hello World!");
scanner.nextLine();
System.exit(0);
}
这是一个如何自定义 Java 控制台外观的示例。
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
JavaConsole console = new JavaConsole();
//Customize the console text field
console.setBackground(Color.WHITE);
console.setForeground(Color.BLACK);
console.setFont(new Font ("Ariel", Font.BOLD, 12));
//Customize the console frame
console.setTitle("Hello World Program");
Image im = Toolkit.getDefaultToolkit().getImage("../images/MyIcon.png");
console.setIconImage(im);
System.out.println("Hello World!");
scanner.nextLine();
System.exit(0);
}
这是一个使用 JavaConsole.clear()
方法在文本块之间清除屏幕的示例。
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
JavaConsole console = new JavaConsole();
System.out.println(
"It was a dark and stormy night; the rain fell in torrents, except at " +
"occasional intervals, when it was checked by a violent gust of wind " +
"which swept up the streets (for it is in London that our scene lies), " +
"rattling along the house-tops, and fiercely agitating the scanty flame " +
"of the lamps that struggled against the darkness. Through one of the " +
"obscurest quarters of London, and among haunts little loved by the " +
"gentlemen of the police, a man, evidently of the lowest orders, was " +
"wending his solitary way.");
scanner.nextLine();
console.clear();
System.out.println(
"He stopped twice or thrice at different shops and houses of a description " +
"correspondent with the appearance of the quartier in which they were situated, " +
"and tended inquiry for some article or another which did not seem easily " +
"to be met with. All the answers he received were couched in the negative; " +
"and as he turned from each door he muttered to himself, in no very elegant " +
"phraseology, his disappointment and discontent. At length, at one house, " +
"the landlord, a sturdy butcher, after rendering the same reply the inquirer " +
"had hitherto received, added, \"But if this vill do as vell, Dummie, it is " +
"quite at your sarvice!\"");
scanner.nextLine();
System.exit(0);
}
最后,这里是 Java 控制台自定义功能的完整列表。
console.getBackground();
console.getForeground();
console.getFont();
console.setBackground(Color);
console.setForeground(Color);
console.setFont(Font);
console.setTitle(title);
console.setIconImage(Image);
console.clear();
如何连接一个控制台
我们需要做的第一件事是将 STDOUT
和 STDERR
重定向到我们的 JtextArea
。我开始使用的代码类以以下方式实现了这一点:
private final PipedInputStream pin=new PipedInputStream();
private final PipedInputStream pin2=new PipedInputStream();
PipedOutputStream pout=new PipedOutputStream(this.pin);
PipedOutputStream pout2=new PipedOutputStream(this.pin2);
System.setOut(new PrintStream(pout,true));
System.setErr(new PrintStream(pout2,true));
//Declarations
private Thread reader;
private Thread reader2;
//In the class constructor
reader=new Thread(this);
reader.setDaemon(true);
reader.start();
reader2=new Thread(this);
reader2.setDaemon(true);
reader2.start();
public synchronized void run()
{
try
{
while (Thread.currentThread()==reader)
{
try { this.wait(100);}catch(InterruptedException ie) {}
if (pin.available()!=0)
{
String input=this.readLine(pin);
textArea.append(input);
textArea.setCaretPosition(textArea.getDocument().getLength()); //DWM 02-07-2012
}
if (quit) return;
}
while (Thread.currentThread()==reader2)
{
try { this.wait(100);}catch(InterruptedException ie) {}
if (pin2.available()!=0)
{
String input=this.readLine(pin2);
textArea.append(input);
textArea.setCaretPosition(textArea.getDocument().getLength()); //DWM 02-07-2012
}
if (quit) return;
}
} catch (Exception e)
{
textArea.append("\nConsole reports an Internal error.");
textArea.append("The error is: "+e);
textArea.setCaretPosition(textArea.getDocument().getLength()); //DWM 02-07-2012
}
}
- 建立一些管道输入流。
- 将输入流连接到相应的输出流。
- 将系统
STDOUT
和STDERR
重定向到这些输出流。 - 启动一些线程从管道输入流读取。
- 监控
STDOUT
和STDERR
,并将文本追加到JtextArea
。
现在我们已经将 STDOUT
和 STDERR
管道传输到 JtextArea
,我们需要监控 JtextArea
的键盘输入并将字符复制到 STDIN
。我修改了原始代码,如下所示:
private final PipedOutputStream pout3=new PipedOutputStream();
System.setIn(new PipedInputStream(this.pout3));
textArea.addKeyListener(new KeyListener() {
public void keyPressed(KeyEvent e) {}
public void keyReleased(KeyEvent e) {}
public void keyTyped(KeyEvent e) {
try { pout3.write(e.getKeyChar()); } catch (IOException ex) {}
}});
- 建立一个用于读取键盘输入的管道输出流。
- 将输出流连接到一个相应的输入流,并将
STDIN
重定向到这个输入流。 - 添加一个
KeyListener
,并在键盘输入到达时将其复制出去。
制作一个自定义块状光标
JtextArea
的默认光标是一个垂直条,但我想要一个更醒目的提示符。事实证明这个功能是可定制的,我找到了一些关于如何实现的例子。请参考 自定义光标类 [^] 和 更花哨的自定义光标类 [^] 来了解它是如何完成的。这是我闪烁块状光标的代码。
public class BlockCaret extends DefaultCaret {
private static final long serialVersionUID = 1L;
/**
* @brief Class Constructor
*/
public BlockCaret() {
setBlinkRate(500); // half a second
}
/* (non-Javadoc)
* @see javax.swing.text.DefaultCaret#damage(java.awt.Rectangle)
*/
protected synchronized void damage(Rectangle r) {
if (r == null)
return;
// give values to x,y,width,height (inherited from java.awt.Rectangle)
x = r.x;
y = r.y;
height = r.height;
// A value for width was probably set by paint(), which we leave alone.
// But the first call to damage() precedes the first call to paint(), so
// in this case we must be prepared to set a valid width, or else
// paint()
// will receive a bogus clip area and caret will not get drawn properly.
if (width <= 0)
width = getComponent().getWidth();
repaint(); //Calls getComponent().repaint(x, y, width, height) to erase
repaint(); // previous location of caret. Sometimes one call isn't enough.
}
/* (non-Javadoc)
* @see javax.swing.text.DefaultCaret#paint(java.awt.Graphics)
*/
public void paint(Graphics g) {
JTextComponent comp = getComponent();
if (comp == null)
return;
int dot = getDot();
Rectangle r = null;
char dotChar;
try {
r = comp.modelToView(dot);
if (r == null)
return;
dotChar = comp.getText(dot, 1).charAt(0);
} catch (BadLocationException e) {
return;
}
if(Character.isWhitespace(dotChar)) dotChar = '_';
if ((x != r.x) || (y != r.y)) {
// paint() has been called directly, without a previous call to
// damage(), so do some cleanup. (This happens, for example, when
// the text component is resized.)
damage(r);
return;
}
g.setColor(comp.getCaretColor());
g.setXORMode(comp.getBackground()); // do this to draw in XOR mode
width = g.getFontMetrics().charWidth(dotChar);
if (isVisible())
g.fillRect(r.x, r.y, width, r.height);
}
}
特别报道!菜单系统
我已将我非常喜欢的菜单系统包含在此演示中。它是这个CodeProject 文章[^] 中易于使用的控制台菜单的 Java 版本。正如我在引言中提到的,在 Java 中处理回调与 C# 不同,Menu
类很好地说明了这一点。这是在 C# 中完成的方式。
delegate void MenuCallback();
private static void HandleItem1
{
Console.WriteLine("You chose item 1.\n");
Console.Read();
}
private static void HandleItem2
{
Console.WriteLine("You chose item 2.\n");
Console.Read();
}
menu = new Menu();
menu.Add("Item 1", new MenuCallback(HandleItem1));
menu.Add("Item 2", new MenuCallback(HandleItem2));
menu.Show();
- 我声明一个委托(函数指针)
- 接下来,我为菜单选择创建一些事件处理程序
- 然后,我像这样将它们添加到
Menu
中
Java 不允许传递函数指针,因此也不允许传递委托。相反,这种行为仅限于类型化对象指针,因此类接口成为委托的 Java 等价物。这是在 Java 中完成的方式。
public interface MenuCallback extends EventListener {
public void Invoke();
}
private static void HandleItem1 {
System.out.println("You chose item 1.\n.");
scanner.nextLine();
}
private static void HandleItem2 {
System.out.println("You chose item 2.\n.");
scanner.nextLine();
}
Menu menu = new Menu(console);
menu.add("Item 1", new MenuCallback() { public void Invoke() {
HandleItem1(); } });
menu.add("Item 2", new MenuCallback() { public void Invoke() {
HandleItem2(); } });
menu.show();
- 我声明一个接口(类型化对象指针)
- 接下来,我为菜单选择创建一些事件处理程序
- 然后我像这样将它们添加到
Menu
中
这有点冗长,因为我们实际上必须将函数指针包装在一个类中。
最后的评论
在 Java 应用程序的标准控制台中,菜单系统工作得不好。此处包含的 Menu
类版本只能在此 Java 控制台中 M使用。每当您想要一个由您的应用程序拥有的控制台窗口时,都可以使用此 Java 控制台。虽然它包含一个用于清除屏幕的 clear()
方法,但它不处理 DOS 命令或提供命令提示符。
历史
- 2012 年 2 月 9 日:版本 1.0.0.0