什么是Curses?
curses库为基于文本的终端提供独立于终端的屏幕绘制和键盘处理设施;这些终端包括VT100s、Linux控制台和各种程序提供的模拟终端。显示终端支持各种控制代码来执行常见的操作,如移动光标、滚动屏幕和擦除区域。不同的终端使用的代码差别很大,而且常常有自己的小毛病。
在图形显示的世界中,有人可能会问“为什么要这么麻烦”?诚然,字符单元显示终端是一种过时的技术,但在某些特定领域,能够用它们做一些新奇的事情仍然很有价值。其中一个利基市场是不运行X服务器的占用空间小或嵌入式unix。另一个是OS安装程序和内核配置器等工具,它们可能必须在任何图形支持可用之前运行。
curses库提供了相当基本的功能,为程序员提供了一个包含多个非重叠文本窗口的显示抽象。窗口的内容可以通过各种方式进行更改——添加文本、擦除文本、更改外观——curses库将计算出需要将哪些控制代码发送到终端才能生成正确的输出。curses没有提供许多用户界面概念,比如按钮、复选框或对话框;如果需要这些特性,可以考虑使用用户界面库,比如Urwid。
curses库最初是为BSD Unix编写的;AT&T后来的System V版本Unix增加了许多增强功能和新功能。不再维护BSD curses,取而代之的是ncurses,它是AT&T接口的一个开源实现。如果您使用的是开源Unix,比如Linux或FreeBSD,那么您的系统几乎肯定使用了ncurses。由于目前大多数商业Unix版本都基于System V代码,所以这里描述的所有函数可能都是可用的。不过,一些专有的Unixes携带的旧版本的咒语可能并不支持所有的功能。
Windows版本的Python不包含curses模块。一个名为UniCurses的移植版本是可用的。您还可以尝试Fredrik Lundh编写的控制台模块,它不使用与curses相同的API,但提供了可通过指针寻址的文本输出,并完全支持鼠标和键盘输入。
Python Curses模块
本教程介绍如何使用curses和Python编写文本模式的程序。它并不是curses API的完整指南;为此,请参阅Python库指南中关于ncurses的部分,以及关于ncurses的C手册页面。不过,它会给你一些基本的概念。
开始我们的编程吧(启动与结束)
初始化curses:
import curses
stdscr = curses.initscr()
- 1
- 2
通常使用noecho()使屏幕停止输出无关内容:
curses.noecho()
- 1
应用程序通常也需要立即响应键,而不需要按Enter键;这称为cbreak模式,与通常的缓冲输入模式相反。
curses.cbreak()
- 1
终端通常返回特殊的键,如光标键或导航键,如Page Up和Home,作为多字节转义序列。虽然您可以编写应用程序来期望这样的序列并相应地处理它们,但是curses可以为您做到这一点,返回一个特殊的值,比如curss . key_left。要让Curses生效,你必须启用键盘模式。
stdscr.keypad(True)
- 1
终止curses应用程序要比启动curses应用程序容易得多。你需要调用:
相对于,启动,终止只需要反向操作:
curses.nocbreak()
stdscr.keypad(False)
curses.echo()
- 1
- 2
- 3
反转对curses友好的终端设置。然后调用endwin()函数将终端恢复到原来的操作模式。
curses.endwin()
- 1
在调试一个cruses应用程序时,一个常见的问题是,当应用程序死亡而没有将终端恢复到以前的状态时,您的终端就会陷入混乱。在Python中,这通常发生在代码有bug并引发未捕获异常时。例如,当您键入键时,键不再回显到屏幕,这使得使用shell变得困难。
重要使用方法(包装用法)
在Python中,您可以通过导入curses .wrapper()函数并像这样使用它来避免这些复杂性,并使调试变得更加容易:
from curses import wrapper
def main(stdscr):
# Clear screen
stdscr.clear()
<span class="token comment"># This raises ZeroDivisionError when i == 10.</span>
<span class="token keyword">for</span> i <span class="token keyword">in</span> <span class="token builtin">range</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">11</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
v <span class="token operator">=</span> i<span class="token operator">-</span><span class="token number">10</span>
stdscr<span class="token punctuation">.</span>addstr<span class="token punctuation">(</span>i<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token string">'10 divided by {} is {}'</span><span class="token punctuation">.</span><span class="token builtin">format</span><span class="token punctuation">(</span>v<span class="token punctuation">,</span> <span class="token number">10</span><span class="token operator">/</span>v<span class="token punctuation">)</span><span class="token punctuation">)</span>
stdscr<span class="token punctuation">.</span>refresh<span class="token punctuation">(</span><span class="token punctuation">)</span>
stdscr<span class="token punctuation">.</span>getkey<span class="token punctuation">(</span><span class="token punctuation">)</span>
wrapper(main)
包装器()函数接受一个可调用对象并执行上面描述的初始化,如果有颜色支持,还初始化颜色。然后运行您提供的可调用的wrapper()。一旦可调用返回,wrapper()将恢复终端的原始状态。可调用的对象在try中被调用,但它捕获异常,恢复终端的状态,然后重新引发异常。因此,您的终端将不会在异常上处于奇怪的状态,您将能够读取异常的消息并进行回溯。
Windows and Pads
窗口的cruses的基本抽象。窗口对象表示屏幕的矩形区域,并支持显示文本、擦除文本、允许用户输入字符串等方法。
函数initscr()返回的stdscr对象是一个覆盖整个屏幕的窗口对象。许多程序可能只需要这个窗口,但是您可能希望将屏幕分割成更小的窗口,以便分别重新绘制或清除它们。newwin()函数的作用是:创建一个给定大小的新窗口,返回新窗口对象。
begin_x = 20; begin_y = 7
height = 5; width = 40
win = curses.newwin(height, width, begin_y, begin_x)
注意,在cruses中使用的坐标系是不寻常的。坐标总是以y,x的顺序传递,窗口的左上角是坐标(0,0)。这打破了处理首先出现x坐标的坐标的常规。这与大多数其他计算机应用程序有一个不幸的不同之处,但自从它最初被编写以来,它就一直是cruses的一部分,现在要改变已经太迟了。
当您调用一个方法来显示或擦除文本时,效果不会立即显示在屏幕上。
相反,您必须调用window对象的refresh()方法来更新屏幕。
这是因为cruses最初是在300波特的终端连接速度较慢的情况下编写的;使用这些终端,最小化重绘屏幕所需的时间是非常重要的。相反,curses会累积对屏幕的更改,并在调用refresh()时以最有效的方式显示更改。例如,如果您的程序在窗口中显示了一些文本,然后清除了该窗口,那么就不需要发送原始文本,因为它们永远不可见
实际上,显式地告诉curses重新绘制一个窗口并不会使使用curses进行编程变得非常复杂。大多数程序都进入忙乱状态,然后暂停,等待用户的一个按键或其他动作。在暂停等待用户输入之前,您所要做的就是确保屏幕已经重新绘制,方法是首先调用stdscr.refresh()或其他相关窗口的refresh()方法。
pad是Window的一种特殊情况;它可以比实际的显示屏大,而且一次只能显示pad的一部分。创建一个pad需要pad的高度和宽度,而刷新pad则需要给出屏幕上显示pad的一个小节的区域的坐标。
pad = curses.newpad(100, 100)
# These loops fill the pad with letters; addch() is
# explained in the next section
for y in range(0, 99):
for x in range(0, 99):
pad.addch(y,x, ord('a') + (x*x+y*y) % 26)
# Displays a section of the pad in the middle of the screen.
# (0,0) : coordinate of upper-left corner of pad area to display.
# (5,5) : coordinate of upper-left corner of window area to be filled
# with pad content.
# (20, 75) : coordinate of lower-right corner of window area to be
# : filled with pad content.
pad.refresh( 0,0, 5,5, 20,75)
refresh()调用在屏幕上显示从坐标(5,5)扩展到坐标(20,75)的矩形中pad的一部分;显示部分的左上角是pad上的坐标(0,0)。除此之外,pad与普通窗口完全一样,支持相同的方法。
如果你在屏幕上有多个窗口和垫子,有一个更有效的方法来更新屏幕,防止烦人的屏幕闪烁,因为屏幕的每个部分都得到了更新。refresh()实际上做两件事:
- 调用每个窗口的noutrefresh()方法来更新表示屏幕所需状态的底层数据结构。
- 调用函数doupdate()函数来更改物理屏幕以匹配数据结构中记录的所需状态。
相反,您可以在多个窗口上调用noutrefresh()来更新数据结构,然后调用doupdate()来更新屏幕。
显示文本
在python中,只有一个函数addstr():
**参数格式:**一共有4种
- str or ch 显示string str 或者ch
- str or ch, attr 显示string 或者 ch,attr 在当前位置
- y, x, str or ch 一共位置到y,x然后打印出相应内容
- y, x, str or ch, attr 移动到y,x,打印内容,使用属性。
属性允许以突出显示的形式显示文本,如黑体、下划线、反向代码或颜色。它们将在下一小节中得到更详细的解释。
addstr()方法接受一个Python字符串或bytestring作为要显示的值。字节串的内容按原样发送到终端。使用窗口的编码属性值将字符串编码为字节;这默认为local .getpreferredencoding()返回的默认系统编码。
addch()方法接受一个字符,它可以是长度为1的字符串、长度为1的字节字符串或整数。
为扩展字符提供常量;这些常数是大于255的整数。例如,ACS_PLMINUS是+/-符号,ACS_ULCORNER是框的左上角(方便绘制边框)。您还可以使用适当的Unicode字符
Windows会记住上次操作后光标的位置,所以如果您省略了y、x坐标,则字符串或字符将显示在上次操作结束的位置。您还可以使用move(y,x)方法移动光标。因为一些终端总是显示闪烁的光标,所以您可能希望确保光标位于不会分散注意力的位置;光标在某些明显随机的位置闪烁可能会让人感到困惑。
leaveok(bool)/curs_set():函数用于关闭游标闪烁
如果应用程序根本不需要闪烁的光标,可以调用curs_set(False)使其不可见。为了与旧的curses版本兼容,有一个leaveok(bool)函数,它是curs_set()的同义词。当bool为真时,curses库将尝试抑制闪烁的光标,您无需担心将其放在奇怪的位置。
属性和颜色
字符可以以不同的方式显示。基于文本的应用程序中的状态行通常以反向视频的形式显示,或者文本查看器可能需要突出显示某些单词。curses支持这一点,它允许您为屏幕上的每个单元格指定一个属性。
属性是一个整数,每个位代表一个不同的属性。您可以尝试显示具有多个属性位集的文本,但是curses不能保证所有可能的组合都可用,或者它们在视觉上都是不同的。这取决于正在使用的终端的能力,因此最安全的方法是使用这里列出的最常用的属性。
- A_BLINK Blinking text
- A_BOLD Extra bright or bold text
- A_DIM Half bright text
- A_REVERSE Reverse-video text
- A_STANDOUT The best highlighting mode available
- A_UNDERLINE Underlined text
因此,要在屏幕的顶部显示一个反向视频状态行,您可以编写代码:
stdscr.addstr(0, 0, "Current mode: Typing mode",
curses.A_REVERSE)
stdscr.refresh()
curses库还支持在提供它的终端上使用颜色。最常见的此类终端可能是Linux控制台,其次是color xterms。
要使用color,必须在调用initscr()后不久调用start_color()函数,以初始化默认的颜色集(curses .wrapper()函数会自动执行此操作)。完成后,如果使用的终端可以显示颜色,那么has_colors()函数将返回TRUE。(注:curses使用美式拼写“color”,而不是加拿大/英国拼写“colour”。如果你已经习惯了英式拼写,那么为了这些功能,你就不得不承认自己拼错了。
curses库维护有限数量的颜色对,包括前景(或文本)颜色和背景颜色。可以使用color_pair()函数获得与颜色对对应的属性值;这可以与其他属性(如A_REVERSE)进行位操作或’ed,但是同样,这种组合不能保证在所有终端上都有效。
一个例子,它显示一行文本使用颜色对1:
stdscr.addstr("Pretty text", curses.color_pair(1))
stdscr.refresh()
init_pair(n, f, b)改变颜色对前景色、背景色。
如前所述,一对颜色由前景和背景颜色组成。init_pair(n, f, b)函数将颜色对n的定义更改为前景色f和背景色b。
让我们把这些放在一起。要将颜色1更改为白色背景上的红色文本,您可以调用:
stdscr.addstr(0,0, "RED ALERT!", curses.color_pair(1)
非常漂亮的终端可以将实际颜色的定义更改为给定的RGB值。这可以让你改变颜色1,通常是红色,紫色或蓝色或任何其他你喜欢的颜色。不幸的是,Linux控制台不支持这一点,所以我无法尝试它,也无法提供任何示例。您可以通过调用can_change_color()来检查您的终端是否可以做到这一点,如果功能存在,can_change_color()将返回True。如果您有幸拥有这样一个优秀的终端,请参考系统的手册页以获得更多信息。
用户输入
Python的curses模块添加了一个基本的文本输入小部件。
有两种方法:
- getch()刷新屏幕,然后等待用户敲一个键, 如果之前调用echo()时显示键值。您可以选择在暂停之前指定光标应该移动到的坐标。
- getkey() 执行相同的操作,但将整数转换为字符串。单个字符返回为1个字符的字符串,特殊的键(如函数键)返回包含键名(如KEY_UP或^G)的更长的字符串。
可以不等待用户输入
可以不使用nodelay()窗口方法等待用户。在nodelay(True)之后,窗口的getch()和getkey()变为非阻塞。为了表示没有准备好输入,getch()返回curses。ERR(值为-1)和getkey()引发异常。还有一个halfdelay()函数,它可以用来(实际上)为每个getch()设置一个计时器;如果在指定的延迟内没有可用的输入(以十分之一秒为单位),curses将引发异常。
getch()返回整数
getch()方法返回一个整数;如果在0到255之间,则表示按下的键的ASCII码。大于255的值是特殊键,如Page Up、Home或光标键。您可以将返回的值与常量(如curses)进行比较。KEY_PPAGE,curses。KEY_HOME或curses.KEY_LEFT。程序的主循环可能是这样的:
while True:
c = stdscr.getch()
if c == ord('p'):
PrintDocument()
elif c == ord('q'):
break # Exit the while loop
elif c == curses.KEY_HOME:
x = y = 0
curses.ascii模块提供ascii类成员函数,这些函数接受整数或1个字符串参数;在为这样的循环编写更具可读性的测试时,这些方法可能很有用。它还提供了转换函数,该函数接受整数或1个字符字符串参数,并返回相同的类型。例如,curses .ascii.ctrl()返回与其参数对应的控制字符。
还有一个方法可以检索整个字符串getstr()。它并不经常使用,因为它的功能非常有限;惟一可用的编辑键是退格键和回车键,回车键终止字符串。它可以选择限制为固定数量的字符。
curses.echo() # Enable echoing of characters
# Get a 15-character string, with the cursor on the top line
s = stdscr.getstr(0,0, 15)
cruses.textpad模块提供一个文本框,该文本框支持一组类似于emacs的键绑定。Textbox类的各种方法都支持使用输入验证进行编辑,并收集带有或不带尾随空格的编辑结果。这里有一个例
import curses
from curses.textpad import Textbox, rectangle
def main(stdscr):
stdscr.addstr(0, 0, "Enter IM message: (hit Ctrl-G to send)")
editwin <span class="token operator">=</span> curses<span class="token punctuation">.</span>newwin<span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">,</span><span class="token number">30</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">)</span>
rectangle<span class="token punctuation">(</span>stdscr<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token operator">+</span><span class="token number">5</span><span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token operator">+</span><span class="token number">30</span><span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">)</span>
stdscr<span class="token punctuation">.</span>refresh<span class="token punctuation">(</span><span class="token punctuation">)</span>
box <span class="token operator">=</span> Textbox<span class="token punctuation">(</span>editwin<span class="token punctuation">)</span>
<span class="token comment"># Let the user edit until Ctrl-G is struck.</span>
box<span class="token punctuation">.</span>edit<span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token comment"># Get resulting contents</span>
message <span class="token operator">=</span> box<span class="token punctuation">.</span>gather<span class="token punctuation">(</span><span class="token punctuation">)</span>
不懂啊