我们必须要做的第一件事是清空屏幕。每次调用 paint(),Swing 会给您一个“白板”,MIDP 此时呈现出的屏幕与您先前对 paint() 的调用后所保持的屏幕状态相一致。当您预先正好知道已修改的内容时,这是一个极好的功能,但是我们无法严密地跟踪修改。通过每次清除和重绘整个屏幕,我们可以保持代码简单。
我们将背景绘为黑色并为了绘制文本而设置前景色为白色,这不仅是出于传统的原因。对于文本颜色,绿色或琥珀色可能是更好的选择,但至少我们知道黑背景上的白色在所有颜色、灰度和“1 位颜色”(黑和白)屏幕上都是是清晰的。为了方便和清楚起见,我们通过调用 setGrayScale(0) 来设置颜色;setColor( 0, 0, 0 ) 将实现相同的结果。
然后我们循环每个可见的行和列并从缓冲区描绘相应的字符到屏幕。虽然 drawChars() 可能是更快的操作,因为它以单次调用呈现多个字符,但我们还是要使用 drawChar(),因为实际上多数字符位置是空的,我们可以避免每次调用 paint() 时分配字符数组。
注意,我们确实有可滚动的偏移标记存储在 scrollX 和 scrollY 字段中。当我们尝试实现更复杂的终端时,这个功能在以后将变得更加重要,但是在此情况中,垂直滚动条是很有用的。我们让 scrollX 保留为 0。但是每次我们遇到一个换行或自动换行就将 scrollY 递增。这就使终端屏幕自动地滚动,在最新的输出到达屏幕时显示它,正如用户所期待的那样。
因为我们正在记录所有的进入数据,我们应该允许用户向后滚动并查看已经离开屏幕的内容。要实现该功能,我们执行 keyPressed() 和 keyRepeated() 来捕获 UP 和 DOWN 事件,相应地移动滚动偏移量和请求重绘。
public void keyPressed( int keyCode )
{
int gameAction = getGameAction( keyCode );
switch ( gameAction )
{
case DOWN:
// scroll down one row
scrollY++;
if ( scrollY > calcLastVisibleScreen() )
{
scrollY = calcLastVisibleScreen();
}
repaint();
break;
case UP:
// scroll up one row
scrollY--;
if ( scrollY < 0 ) scrollY = 0;
repaint();
break;
default:
// ignore
}
}
|
您应该记住一个细节:在测试它是设备上的 up 还是 down 按钮之前,您必须将键代码转换为游戏代码。某些设备上映射到 up 或 down 概念的键不只一个,有些有滚轴或其他专用的输入设备;使用 getGameAction() 能使该方法在所有情况下都能正确工作。keyRepeated() 也可做很多相同的工作,但是一次会移动滚动偏移量半个屏幕。
Telnet MIDlet
现在我们有了一个用于后端的前端,我们需要将他们与 MIDlet 结合成一体。Weather Underground 仍旧提供了一个免费的 telnet 服务,使用它编写一个 MIDlet 以检索最新的天气状况是一个有用的练习。而且,这个任务很有代表性,正是您希望用终端模拟 MIDlet 所做的那类事情:连接到远程服务器、登录和提取某类数据显示在屏幕上。
看一看 MIDTerm.java 的清单。它是一个极其标准的 MIDlet,从应用程序描述符中读取配置选项,然后设置显示和命令。在启动时,startApp() 调用 connect(),connect() 则生成调用 run() 的新线程:
public void run()
{
String connectString = "socket://" + host + ':' + port;
try
{
canvas.receive( toASCII( "Connecting...\n" ) );
connection = new TelnetConnection(
(StreamConnection) Connector.open(
connectString, Connector.READ_WRITE, true ) );
input = connection.openInputStream();
output = connection.openOutputStream();
// server interaction script
try
{
// suppress content until first "continue:"
waitUntil(
input, new String[] { "ontinue:" }, false );
output.write( toASCII( "\n" ) );
output.flush();
// show content until city code prompt
waitUntil(
input, new String[] { "code--" }, true );
output.write( toASCII( city + '\n' ) );
canvas.receive( toASCII( city + '\n' ) );
output.flush();
// keep advancing pages until "Selection:" prompt
while ( !"Selection:".equals(
waitUntil( input, new String[] {
"X to exit:", "Selection:" }, true ) ) )
{
output.write( toASCII( "\n" ) );
output.flush();
canvas.receive( toASCII( "\n" ) );
}
// exit will cause disconnect
output.write( toASCII( "X\n" ) );
output.flush();
canvas.receive( toASCII( "X\n" ) );
// keep reading until "Done" or disconnected
waitUntil( input, new String[] { "Done" }, true );
}
catch ( IOException ioe )
{
System.err.println(
"Error while communicating: "
+ ioe.toString() );
canvas.receive( toASCII( "\nLost connection." ) );
}
catch ( Throwable t )
{
System.err.println(
"Unexpected error while communicating: "
+ t.toString() );
canvas.receive( toASCII(
"\nUnexpected error: " + t.toString() ) );
}
}
catch ( IllegalArgumentException iae )
{
System.err.println( "Invalid host: " + host );
canvas.receive( toASCII( "Invalid host: " + host ) );
}
catch ( ConnectionNotFoundException cnfe )
{
System.err.println(
"Connection not found: " + connectString );
canvas.receive( toASCII(
"Connection not found: " + connectString ) );
}
catch ( IOException ioe )
{
System.err.println(
"Error on connect: " + ioe.toString() );
canvas.receive( toASCII(
"Error on connect: " + ioe.toString() ) );
}
catch ( Throwable t )
{
System.err.println(
"Unexpected error on connect: " + t.toString() );
canvas.receive( toASCII(
"Unexpected error on connect: " + t.toString() ) );
}
// clean up
disconnect();
canvas.receive( toASCII( "\nDisconnected.\n" ) );
} |