购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

第4章
控制台应用程序

前面的许多示例都是基于控制台应用程序实现的。那么,什么是控制台应用程序呢?控制台应用程序运行时,开发者看到的是一个命令提示符窗口。它的输入输出类似于传统的DOS应用程序,只有一个简单的窗口,并且通过简单的字符串输入输出来与用户进行交互。

本章将介绍一些与控制台应用程序相关的知识。

● 向控制台窗口输出文本信息

● 读入用户输入的内容

● 接收和处理命令行参数

● 修改控制台应用程序窗口的标题

● 设置输出文本的颜色

● 更改控制台窗口大小与屏幕缓冲区大小

● 了解CancelKeyPress事件

4.1 输出文本信息

System.Console类公开了一系列静态成员,以帮助开发人员操作控制台应用程序。其中,向屏幕输出文本信息,Console类定义了两个方法。

(1)Write方法:将内容输出。该方法有多个重载,大体分为两种,一种是把传入方法参数的对象作为字符串输出,另一种是输出格式化的字符串(后面会详细介绍格式化字符串)。

(2)WriteLine方法:和Write方法相近,只是WriteLine方法在输出的文本后面自动加上换行符,也就是说,每调用WriteLine方法一次,就输出一整行。

例如,下面代码将在屏幕上输出字符串“test”

    Console.Write("test");

输出方法支持许多数据类型的输出,如下面代码将一个double类型的数值转换为字符串再输出。

    Console.WriteLine(122.315d);

Write/WriteLine方法在把传递的对象输出时,调用了对象的ToString方法,该方法是在Object类上定义的。由于所有类型都是从Object类派生的,并且ToString方法是个虚方法,因此在派生类中可以重写该方法,以自定义当前对象的字符串表示形式。

下面用一个示例演示如何自定义对象的字符串表示方式,并用控制台应用程序来输出。(完整的示例代码请参考\第4章\Example_1)。

首先,声明一个表示产品信息的Product类用于测试,并重写ToString方法。

在上面代码中,Product类重写了Object类的ToString方法,把类的三个属性连接成一个字符串输出。

其次,可以在Main方法中创建一个Product类的实例,并为其属性赋值。

最后,调用Console.Write方法把该对象实例直接以字符串的形式输出。

    Console.Write(p);

图4-1 对象的输出结果

运行应用程序后,得到如图4-1所示的结果。

Write/WriteLine方法还有一种输出形式,即通过格式化字符串来输出内容。比如,System.Console类的Write方法,有以下一种重载形式

    public static void Write(string format, params object[ ] arg)

第一个参数是要显示的字符串,但是该字符串中带有格式化控制符的索引,索引从0开始,用一对大括号({})括起来,如{0}、{1}等;后面的参数是一个object类型的数组,由于在定义arg参数时加了params关键字,因此在调用方法时,可以在第一个参数后直接把数组的元素传过去,每个元素用一个英文的逗号分隔,比如

    Console.Write("pages : {0}-{1}-{2}", 11, 2, 3);

上面代码给arg参数传递了三个元素,分别为11、2、3。从第二个参数起,每个元素都与第一个参数中的格式化控制符的索引一一对应,即用arg数组中的第一个元素替换字符串中的{0},用arg数组中的第二个元素替换字符串中的{1}……以此类推。

再举一例

    Console.Write("第{0}页/共{1}页", 2, 10);

在上面代码中,arg数组的第一个元素是2,替换字符串中的{0};arg数组的第二个元素是10,替换字符串中的{1}。最后输出的字符串为

    第2页/共10页

可以用图4-2表示这个格式化的过程。

图4-2 格式化字符串示意图

4.2 获取键盘输入

Console类公开了三个方法帮助开发者在应用程序中获取用户的键盘输入,它们分别是Read、ReadKey和ReadLine。

4.2.1 Read方法

Read方法每次只读入一个字符,如果没有可读的字符,则返回-1,对于键盘输入,一般不会返回-1,除非用户按下了Ctrl + Z快捷键才会返回-1。在用户输入内容时,Read方法不会马上读取,而是等到用户按下Enter键才会开始读取。由于Read方法每次只读取一个字符,所以通常会把Read方法放到一个循环体中调用。

下面演示Read方法的使用(完整的示例代码请参考\第4章\Example_2)。

图4-3 输出捕捉到的字符

上面代码中,通过while循环,反复调用Read方法读取输入的字符,只要Read方法不返回-1,循环体将不断执行。Read方法返回的是int数值,其实是字符的ASCII编码值,代码在输出时将它转换为char类型以输出字符。10和13分别代表的是换行符和回车符,输出这两个字符没有实际意义,因此在调用WriteLine方法输出之前,应当把10和13两个值过滤掉。示例的运行结果如图4-3所示。

按下组合键Ctrl + Z后,Read方法返回-1,跳出循环,应用程序退出。

4.2.2 ReadKey方法

使用ReadKey方法读取用户输入的字符,将返回一个ConsoleKeyInfo结构的实例,通过ConsoleKeyInfo结构的几个属性可以获取有关按键的信息。因此,ReadKey方法使用起来比Read方法更方便。

ConsoleKeyInfo结构公开了以下几个属性帮助获取按键信息。

(1)KeyChar属性:直接获取按键所表示的Unicode字符。

(2)Key属性:获取用户按下了哪个键,属性返回一个ConsoleKey枚举,该枚举已经把键盘上各个键的键码定义好了,开发者可以直接拿来进行判断。比如,要判断用户是否按下了Enter键,只要比较ReadKey方法返回的ConsoleKeyInfo实例中的Key属性是否与ConsoleKey.Enter相等即可。

(3)Modifiers属性:返回一个ConsoleModifiers枚举值,表示用户是否按下了Ctrl、Alt或者Shift键。因为ConsoleModifiers枚举应用了FlagsAttribute特性,所以它有可能是这三个键中的一个或多个的组合值。

下面用一个示例来演示ReadKey方法的使用。在Visual Studio开发环境中新建一个控制台应用程序项目,然后在Main方法中加入以下代码

图4-4 输出按键信息

由于do…while循环先执行一次循环再去验证条件是否成立,因此在上面代码中,先通过Console.ReadKey方法获取ConsoleKeyInfo实例,然后输出按键名。跳出循环的条件是用户按下了Esc键,一旦跳出循环,应用程序也将退出。

运行应用程序,然后在控制台窗口中随机按下键盘上的键,会得到如图4-4所示的结果。

完整的示例代码请参考\第4章\Example_3。

4.2.3 ReadLine方法

ReadLine方法每次读取一行,以字符串的形式返回,因此该方法可以一次性读取多个字符,遇到Enter键时返回,而读取到的一行字符串不包括最后的换行符和回车符。如果用户按下Ctrl + Z快捷键,则ReadLine方法返回null。

以下示例为运用ReadLine方法读取一行字符。

在Visual Studio开发环境中新建一个控制台应用程序项目(完整的示例代码请参考\第4章\Example_4),核心的代码如下:

图4-5 输出ReadLine方法读取的内容

调用Console.ReadLine方法读取一行,并把返回的string对象存放到变量line中,接着输出读取的内容。如果用户按下Ctrl + Z快捷键,再按Enter键,ReadLine方法将返回null,这样就跳出循环,应用程序也随之退出。

运行结果如图4-5所示。

4.3 命令行参数

所谓命令行参数,就是在启动应用程序时传递给应用程序的附加参数。例如,在操作系统的“任务管理器”窗口中,可以看到有些进程是带有命令行参数的,如图4-6所示。

图4-6 进程的命令行参数

在图4-6所列举的进程列表中,紧跟在.exe文件路径后面的就是命令行参数,即-k及其后面的内容,多个参数之间通常是以空格来隔开的,比如

    test.exe /s /t abc

其中,/s、/t、abc都是命令行参数。在开发Windows应用程序时,可能需要用到命令行参数。例如,一个应用程序有多种启动方式,可能不同的启动方式会向用户展示不同的内容,这样一来,可以考虑在启动应用程序时传递命令行参数,然后程序接收命令行参数,并通过分析该参数来决定如何向用户呈现特定的内容。

获取命令行参数的方法有两种。本节内容仅以控制台应用程序为例,获取命令行参数的方法在所有Windows应用程序项目中都通用。

接下来,将通过一个示例来对这两种方法进行逐一演示。

第一种方法也是最简单的方法。应用程序入口点Main方法有一个string数组类型的参数,比如这样

    static void Main(string[] args)

其实这个args参数就是用来获取传递给当前应用程序的命令行参数的,如果没有命令行参数传递,则数组的大小为0(0个元素)。可见,直接从args参数中获取命令行参数是最简单的做法,如下面代码所示

第二种方法就是通过调用Environment类的GetCommandLineArgs方法获取,这是一个静态方法,可以直接调用。为什么要提供这个方法呢?因为从Main方法的参数中提取命令行参数这种方法虽然简单,但是也有一定的局限性,如果开发者正在编写的代码不在Main方法中,那么就不能访问args参数了。所以,有了Environment.GetCommandLineArgs方法,在代码的其他地方也能获取到传递给当前应用程序的命令行参数。

GetCommandLineArgs方法返回一个string数组,其中包含了命令行参数,但是这个返回的数组和Main方法的args参数的数组不同,GetCommandLineArgs方法返回的数组多了一个元素,这个元素存储了当前可执行文件的路径,从第二个元素开始才是命令行参数。所以,如果只希望获取参数列表,应该忽略第一个元素。示例代码如下:

这里使用数组复制法,在GetCommandLineArgs方法返回的数组中,从第二个元素开始复制(即从索引1开始),目标数组的大小应该为源数组的长度减去1,因为少了一个元素未复制。这样一来,就把命令行参数都复制到新数组中了。

程序完成后,考虑如何在调试运行应用程序时传递参数。Visual Studio开发工具已经提供了这个功能,可以预先设置一些参数来测试。方法是在“解决方案资源管理器”窗口中,右击项目,从弹出的快捷菜单中选择【属性】,打开项目属性窗口。然后选中“调试”选项卡,在启动选项下的“命令行参数”右边的输入框中输入测试参数,每个参数用空格隔开。本示例输入a、b、c三个参数,如图4-7所示。

按下F5键,运行结果如图4-8所示。

图4-7 输入用于测试的命令行参数

图4-8 输出命令行参数

完整的示例代码请参考\第4章\Example_5。

4.4 控制台窗口的外观

由于控制台应用程序是基于命令提示符窗口的,故其窗口的外观和样式不会像平时看到的标准Windows窗口那么色彩斑斓。因此,这里所说的设置窗口的外观主要指设置窗口标题文本、文本颜色和文本的背景颜色。

要完成这几项设置也很简单,只需要在代码中修改以下几个属性就可以了。

(1)Title属性:控制台应用程序的标题栏文本,默认是当前可执行文件的路径。

(2)ForegroundColor属性:设置或获得窗口上文本的颜色。由ConsoleColor枚举进行规范,只需要在该枚举定义的值中选择一个即可。

(3)BackgroundColor属性:文本的背景色,即显示在文本下面的颜色,也是由ConsoleColor枚举来规范的。

以下示例为设置控制台应用程序窗口的外观。在Visual Studio开发环境中新建一个控制台应用程序项目,然后在Main方法中加入以下代码

图4-9 修改窗口外观后的效果

其实,上面代码只做了两件事:第一件事是修改窗口标题为“我的应用程序”,第二件事就是修改窗口中文本的前景色和背景色,并在每次修改后输出一行文本。最终的效果如图4-9所示。

不管是对背景色还是前景色做出的修改,都会应用到后面输出的文本上,直到ForegroundColor或BackgroundColor属性被再次修改为止。

完整的示例代码请参考\第4章\Example_6。

4.5 控制台窗口的大小和位置

控制台窗口的大小指的是窗口的宽度和高度,“缓冲区”指的是窗口内部用于显示文本的矩形区域,因此缓冲区也有宽度和高度。图4-10描述了窗口区域与缓冲区区域之间的关系。

从图4-10中可以看到,缓冲区域要比窗口区域大,所以窗口出现水平和垂直滚动条。知道了这两个区域,Console类的几个属性就好理解了。下面以分组的方式介绍这几个属性。

第一组是WindowWidth和WindowHeight属性,它们用来获取或者设置窗口的宽度和高度,WindowWidth属性所设置的值不能超过Console.LargestWindowWidth的值,WindowHeight属性所设置的值不能超过Console.LargestWindowHeight的值;第二组是BufferWidth和BufferHeight属性,用于获取或者设置窗口内缓冲区域的宽度和高度。为了便于开发者设置这些属性的值,Console类还公开了两个方法:SetWindowSize方法用来设置窗口区域的大小,SetBufferSize方法用来设置缓冲区域的大小。

控制台应用程序的窗口或缓冲区域的大小并不是用像素来度量的,而是以行和列来度量的,行与列的相交会形成多个单元格,如同在Excel中看到的表格一样。在控制台窗口中,每个字符占一个单元格,例如缓冲区域的宽度为5,高度为3,就表示在这个区域内,垂直方向上可排列3行,水平方向上(每一行)可以排列5个字符。整个区域可以容纳15个字符。可以用图4-11模拟这些字符的布局。

图4-10 窗口区域与缓冲区域的关系

图4-11 3行5列的字符布局示意图

下面是一个示例,完整的示例代码请参考\第4章\Example_7,其中核心代码如下:

    Console.Write("ABCDEFGHIJKLMNOPQRSTUVWXYZVAKLFIOXCNLWQKOWSFGHOSWOSAIELO");
    // 先将窗体缩小,以便于设置缓冲区域
    Console.SetWindowSize(1, 1);
    Console.SetBufferSize(70, 6);
    // 待缓冲区域大小设置完后再设置窗口区域大小
    Console.SetWindowSize(30, 3);

示例的代码不多,首先调用Console.Write方法输出一些字符串,然后用SetWindowSize方法把窗口区域设置为宽度和高度均为1。再去修改缓冲区域的大小,最后重新设置窗口区域大小。

那么,为什么要先将窗口区域的宽度和高度都改为1呢?把这行代码注释掉,然后再运行应用程序。

    //Console.SetWindowSize(1, 1);

如图4-12所示,程序运行后发生错误。

图4-12 运行时发生错误

由于缓冲区域的大小不能小于窗口区域的大小,而在程序运行时,窗口的默认区域远比代码要设置的值要大,即窗口区域远大于要设置的缓冲区域的大小,所以才会发生错误。因此,在修改缓冲区域大小前先把窗口区域的大小改为1,这样一来,所设置的缓冲区域肯定会比窗口区域要大,在成功设置了缓冲区域大小后,再去修改窗口区域的大小。这样一来,就可以避免错误的发生。

图4-13 窗口出现滚动条

示例的运行结果如图4-13所示。

由于设置的缓冲区域大于窗口区域,所以窗口会出现滚动条。

接下来再看控制台应用程序窗口的位置。这里所说的位置,与一般Windows窗口的位置不同。在常规的Windows窗口中,位置是指窗口在屏幕上的坐标位置,或者子窗口在父容器中的坐标位置。而对于控制台应用程序的窗口来说,窗口位置指的是窗口内部缓冲区域的位置。当缓冲区域大于窗口区域时,窗口会出现滚动条。因此,这里所说的位置是指窗口内部的缓冲区域在水平或垂直方向上所滚动的量。与前面所讲述的窗口区域大小一样,水平滚动条滑动的量以字符的列为单位,垂直滚动条滑动的量以字符的行为单位。比如,垂直滚动条向下滑动一个单位数值,则窗口内的文本将向上移动一行。

WindowTop属性表示窗口垂直方向上的位置,以行为单位,WindowLeft属性表示窗口在水平方向上的位置,以列为单位。这两个属性的设置是有条件限制的。

(1)两个属性的值不能小于0,也不能超过int的最大值。

(2)WindowLeft属性的值不能大于( BufferWidth – WindowWidth )。

(3)WindowTop属性的值不能大于( BufferHeight - WindowHeight )。

为了方便开发者设置窗口的位置,Console类提供了SetWindowPosition方法,当然也可以通过WindowTop和WindowLeft属性来分别设置。

下面将完成一个示例,在该示例中,文本缓冲区域比窗口区域要大,因此控制台应用程序的窗口出现滚动条。可以通过按下键盘上的上、下、左、右箭头键来移动窗口内的文本内容,实际上就是设置窗口的位置,按Esc键退出应用程序。

示例的核心代码如下:

在通过ReadKey方法获取到用户按键后,运用switch语句判断是否按下了上、下、左、右四个箭头键,并根据不同情况调用SetWindowPosition方法,设置窗口位置。当用户按下Esc键后跳出循环,结束应用程序。

运行示例程序,按左右箭头键可以水平滚动窗口中的文本(如图4-14所示),按上、下箭头键可以垂直滚动窗口中的文本(如图4-15所示)。

图4-14 水平移动内容

图4-15 垂直移动内容

完整的示例代码请参考\第4章\Example_8。

4.6 响应CancelKeyPress事件

在控制台应用程序中,支持使用快捷键Ctrl + C终止运行。当用户按下此快捷键后,会引发Console类的CancelKeyPress事件。如果应用程序是通过一个无限循环来工作的(比如循环读取网络上的数据),那么程序代码可以响应CancelKeyPress事件,以便做以下处理

(1)通知用户应用程序将要退出;

(2)修改某个条件,使应用程序能跳出循环;

(3)清理应用程序正在使用的资源(比如关闭已经打开的文件,删除临时数据等)。

接下来看一个例子。该示例首先在Program类中声明了一个名为running的静态字段,当进入Main方法后将其设置为true。然后执行while循环,只要running字段的值为true,那么程序就会无限地输出文本“正在运行……”。代码如下:

如果运行上述代码,应用程序会永远执行,除非内存资源耗尽,或者用户强行将其结束。

在进入while循环之前,处理Console类的CancelKeyPress事件,将running字段设置为false。

运行示例程序,当按下Ctrl + C快捷键后,while循环结束,程序就会退出。完整的示例代码请参考\第4章\Example_9。 aqGgHcel4EuaYx25C/gFNrnMJ0qp+hfoCX3b6rsNLtrOACFLfR3piOpP0yjzF+QO

点击中间区域
呼出菜单
上一章
目录
下一章
×