====== 13、 Curses ====== 本月我们讨论在Python中使用Curses。我的意思不是谈论用Python来说脏话,但是当你感觉需要的时候也是可以的。我们的重点是使用Curses库来输出一些非常炫的屏幕效果。 如果你的年纪足够大且有机会接触早期计算机的话,你应该记得在商用领域使用的计算机都是带有哑终端(只有键盘和显示器)的大型机。你可以用很多终端连接到同一台主机上。问题是终端相当愚蠢,没有窗口,没有彩色,大部分东西都没有,唯一有的东西就是24行80个字符(最多)的显示能力。在个人电脑开始流行的时候,操作系统还是古老的DOS和CPM,不过这已经是当时最好的了。 当程序员们为了得到漂亮的显示效果(当时的情况),特别是用于数据输入和显示的目的,他们使用绘图纸设计屏幕。图纸上的每个块代表显示器上一个字符的位置。对于在终端中运行的Python程序来说,我们要处理的还是40*80的屏幕。然而,这样的限制可以通过合理地规划和准备来解决。那么现在到离你最近的办公用品店买些绘图纸回来吧。 好了,现在我们开始创建第一个Curses程序。看下面的代码,稍后我会进行解释的: --> #!/usr/bin/env python # CursesExample1 #------------------------------- # Curses Programming Sample 1 #------------------------------- import curses myscreen = curses.initscr() myscreen.border(0) myscreen.addstr(12, 25, "See Curses, See Curses Run!") myscreen.refresh() myscreen.getch() curses.endwin() 非常简单吧,我们一行一行来看。首先我们先导入,现在这个你应该已经非常熟悉了。接下来我们创建一个Curses屏幕对象,进行初始化后将其命名为 myscreen.(myscreen=curses.initscr())。这就是我们进行创作的画布了。然后使用 myscreen.border(0)命令在花布周围画上边框。这虽然不是必须的,但有了之后却可以让我们的窗口看起来更漂亮。接下来再使用addstr 方法在画布上12行25字符处写一些自编字。想想Curses打印表达式的.addstr方法。最后,.refresh()可以让我们的修改生效。如果不刷新屏幕的话,我们做的更改是看不到的。然后我们等待用户按键(.getch)之后释放屏幕对象(.endwin)使终端可以回到一般状态。 curses.endwin()命令非常重要,如果没有执行的话,你的终端就会一团糟。所以在程序结束之前一定记得调用这个函数。 将程序保存成CursesExample1.py并在终端中执行。注意事项:每次使用边界时都会消耗掉一个可用的字符位置。还有行和字符位置都是从0开始的。这就意味着屏幕的第一行是0,最后一行是23。所以,相对0,0的最左上方位置是23,79。我们举一个小例子说明一下这个问题: --> #!/usr/bin/env python # CursesExample2 import curses #========================================================== # MAIN LOOP #========================================================== try: myscreen = curses.initscr() myscreen.clear() myscreen.addstr(0,0,"0 1 2 3 4 5 6 7") myscreen.addstr(1,0,"12345678901234567890123456789012345678901234567890123456789012345678901234567890") myscreen.addstr(10,0,"10") myscreen.addstr(20,0,"20") myscreen.addstr(23,0, "23 - Press Any Key to Continue") myscreen.refresh() myscreen.getch() finally: curses.endwin() 除try/finally之外的语句都很简单。记住,curses.endwin()非常重要,你需要在每次程序结束的时候执行。这样,即使程序运行非常糟糕,endwin()例程都会被调用。还有很多方法可以实现这个,但是对我来说这个是最简单的。 现在我们开始建立漂亮的菜单。不知道你还记不记得,在第8部分的时候,我们创建过一个有菜单的食谱。我们打印一些东西的时候,终端就会向上滚动。这次我们还用这个想法来做一个菜单,这样你可以让你的食谱更加美观。 看下面是我们当时的编码吧: --> =================================================== RECIPE DATABASE =================================================== 1 - Show All Recipes 2 - Search for a recipe 3 - Show a Recipe 4 - Delete a recipe 5 - Add a recipe 6 - Print a recipe 0 - Exit =================================================== Enter a selection -> 这次我们用Curses来实现。先看下面的模板。你可能想将其保存下来并在以后的程序中使用。 --> #!/usr/bin/env python #------------------------------- # Curses Programming Template #------------------------------- import curses def InitScreen(Border): if Border == 1: myscreen.border(0) #========================================================== # MAIN LOOP #========================================================== myscreen = curses.initscr() InitScreen(1) try: myscreen.refresh() # Your Code Stuff Here... myscreen.addstr(1,1, "Press Any Key to Continue") myscreen.getch() finally: curses.endwin() 现在将你的模板保存为cursesmenu1.py以便我们可以将原来的模板保留下来。 继续编码之前,我们使用模块方法。 下面是我们要将要进行的部分的伪代码: --> curses.initscreen LogicLoop ShowMainMenu # Show the main menu MainInKey # This is our main input handling routine While Key != 0: If Key == 1: ShowAllRecipesMenu # Show the All Recipes Menu Inkey1 # Do the input routines for this ShowMainMenu # Show the main menu If Key == 2: SearchForARecipeMenu # Show the Search for a Recipe Menu InKey2 # Do the input routines for this option ShowMainMenu # Show the main menu again If Key == 3: ShowARecipeMenu # Show the Show a recipe menu routine InKey3 # Do the input routine for this routine ShowMainMenu # Show the main menu again … # And so on and so on curses.endwin() # Restore the terminal 当然这些代码只是伪代码。但它却能让你了解整件事情的发展。由于这仅仅是个例子,我们就到此为止,不过你也可以自己进行完善。 现在开始主循环…… --> # MAIN LOOP try: myscreen = curses.initscr() LogicLoop() finally: curses.endwin() 这里还没有开始正式编码。我们现在有和模板中一样的try|finally块。初始化Curses屏幕之后调用叫做LogicLoop的例程。代码如下: --> def LogicLoop(): DoMainMenu() MainInKey() 同样,代码量也很少,但是这仅仅只是一个样本。这里我们要调用两个例程。DoMainMenu和MainInKey。DoMainMenu显示主菜单(真的会吗?),MainKey处理所有跟菜单有关的东西。下面是DoMainMenu的代码: --> def DoMainMenu(): myscreen.erase() myscreen.addstr(1,1, "========================================") myscreen.addstr(2,1, " Recipe Database") myscreen.addstr(3,1, "========================================") myscreen.addstr(4,1, " 1 - Show All Recipes") myscreen.addstr(5,1, " 2 - Search for a recipe") myscreen.addstr(6,1, " 3 - Show a recipe") myscreen.addstr(7,1, " 4 - Delete a recipe") myscreen.addstr(8,1, " 5 - Add a recipe") myscreen.addstr(9,1, " 6 - Print a recipe") myscreen.addstr(10,1, " 0 - Exit") myscreen.addstr(11,1, "========================================") myscreen.addstr(12,1, " Enter a selection: ") myscreen.refresh() 注意这段例程除了清空屏幕(myscrees.erase)然后在屏幕上显示我们想要的东西之外什么都没做。这里还没有牵扯到键盘动作处理。 那是下面MainInKey的事: --> def MainInKey(): key = 'X' while key != ord('0'): key = myscreen.getch(12,22) myscreen.addch(12,22,key) if key == ord('1'): ShowAllRecipesMenu() DoMainMenu() elif key == ord('2'): SearchForARecipeMenu() InKey2() DoMainMenu() elif key == ord('3'): ShowARecipeMenu() DoMainMenu() elif key == ord('4'): NotReady("'Delete A Recipe'") DoMainMenu() elif key == ord('5'): NotReady("'Add A Recipe'") DoMainMenu() elif key == ord('6'): NotReady("'Print A Recipe'") DoMainMenu() myscreen.refresh() 这段例程确实很简单。进入while循环直到用户的输入等于0。循环中检查输入是否等于各个值,如果等于的话就执行相应的例程,执行完成之后调用主菜单。现在你可以自己填写大部分例程了,但我们还要看看选项2,搜索菜单。菜单很简单,但是InKey2例程就有些复杂了。 --> def SearchForARecipeMenu(): myscreen.addstr(4,1, "-------------------------------") myscreen.addstr(5,1, " Search in") myscreen.addstr(6,1, "-------------------------------") myscreen.addstr(7,1, " 1 - Recipe Name") myscreen.addstr(8,1, " 2 - Recipe Source") myscreen.addstr(9,1, " 3 - Ingredients") myscreen.addstr(10,1," 0 - Exit") myscreen.addstr(11,1,"Enter Search Type -> ") myscreen.refresh() def InKey2(): key = 'X' doloop = 1 while doloop == 1: key = myscreen.getch(11,22) myscreen.addch(11,22,key) ===== 参考 ===== * http://hougeubuntu.blogspot.com/search/label/Python