Автор — Грэг Валтерс (Greg Walters)
В этом месяце мы поговорим об использовании Curses в Python. Нет, мы не собираемся использовать Python для ругательств грязными словечками, однако и такое возможно, если вы действительно захотите. Мы говорим об использовании библиотеки Curses с целью приукрасить окно вывода.
Если вы застали времена первых компьютеров, вы вспомните, что в бизнесе всегда использовались мейнфреймы — с простейшими терминалами ввода-вывода (экраны и клавиатуры). К одному компьютеру могли быть подключены несколько терминалов. Проблема в том, что терминалы были достаточно «скромными» устройствами. У них не было ни окон, ни цветов, ни многого другого — всего лишь 24 строчки по 80 символов (это в лучшем случае). Когда же персональные компьютеры стали популярными, в старые добрые времена DOS и CPM, с чем вы наверняка знакомы, программисты работали за продвинутыми (для тех дней) терминалами, используя их для ввода и вывода данных на экран, а для проектирования экрана использовалась бумага в клетку. Каждая клетка была эквивалентна одному символу. Когда же мы имеем дело с программами на Python, запущенными в терминале, у нас по-прежнему остаётся экран 24×80. Однако с этим ограничением можно легко справиться, заранее подготовившись и всё продумав. Так что сходите в ближайший магазин канцтоваров и обзаведитесь парой тетрадей в клетку.
В любом случае, давайте сразу начнём и создадим нашу первую программу с использованием Curses. (Справа сверху). Просмотрите код, а затем я вам всё объясню.
Коротко, но просто. Давайте просмотрим код строчку за строчкой. Первым делом мы импортируем пакеты, с кото-рыми вы уже знакомы. Далее, создаём новый объект Curses, инициализируем и называем его myscreen (myscreen = curses.initscr()). Это — наш «холст», на котором мы будем рисовать. Далее мы используем команду myscreen.border(0), чтобы нарисовать контур вокруг «холста». Это делать не обязательно, но так выглядит посимпатичнее. Добавляем метод addstr для «написания» некоторого текста на поле начиная с 25 позиции 12 строчки. Воспринимайте метод .addstr как функцию print для Curses. Наконец метод .refresh() делает нашу работу видимой. Если не обновить экран, то мы не увидим внесённых изменений. Далее ждём, когда пользователь нажмёт любую клавишу (.getch), и передаём управление экраном системе (.endwin) для продолжения нормальной работы в терминале. Функция curses.endwin() ОЧЕНЬ важна и, если её не вызвать, то терминал останется в большом беспорядке. Так что удостоверьтесь, что вызвали эту функцию в конце вашего приложения.
Сохраните эту программу как «CursesExample1.py» и запустите в терминале. Один нюанс: всякий раз при добавле-нии контура он занимает по одной «доступной» клетке на каждый символ контура. Кроме того, отсчёт позиций линий и символов начинается с НУЛЯ. Это означает, что первая линия границы — это нулевая строчка на экране, а последняя линия — это 23 строчка. Следовательно, крайняя левая верхняя пози-ция — это (0,0), а крайняя пра-вая нижняя — (23,79). Давайте покажем это на примере (cправа сверху). Всё достаточно просто, за исключением блоков try/finally. Помните, я говорил, что curses.endwin() — это ОЧЕНЬ важная функция и её нужно обязательно вызывать в конце кода? Так вот, в данном случае, если дела пойдут очень плохо, будет вызвана функция endwin. Существует множество спосо-бов вызвать эту функцию, но этот мне кажется достаточно простым.
Теперь давайте создадим красивое меню. Если вы припом-ните, мы писали программу «Поваренная книга», у которой было меню (Программа на Python — Часть 8). Когда мы что-то набирали, всё содержимое просто прокручивалось вверх. Сейчас мы, используя вышеска-занное, сделаем модель меню, чтобы приукрасить «Поваренную книгу». Ниже приведён старый кусок кода.
На этот раз мы будем использовать Curses. Начнём со следующего шаблона. Вероятно вам захочется сохранить этот фрагмент (справа снизу) для использования в своих будущих программах.
Сохраните шаблон как «cursesmenu1.py». Теперь мы можем работать с файлом, сохраняя шаблон.
Прежде чем мы пойдём дальше по нашему коду, давайте рассмотрим его по блокам. Здесь на псевдокоде написано то, что мы хотим сделать.
Конечно, этот псевдокод всего лишь… псевдо. Но он даёт нам представление о том, что мы хотим сделать. Поскольку это всего лишь пример, мы дойдём только до этого момента, но вы можете взять шаблон целиком. Давайте начнём с основного цикла (посередине справа).
Кода здесь немного. Мы имеем только блоки try/finally, так же, как они представлены в шаблоне. Инициализируем экран Curses и вызываем функцию LogicLoop. Этот код показан в нижнем правом углу.
И вновь, кода немного, но это только образец. Здесь мы вызываем две функции: DoMainMenu и MainInKey. DoMainMenu (справа) выводит наше меню на экран, а MainInKey управляет всем остальным.
Заметьте, что эта функция не делает ничего кроме очистки экрана (myscreen.erase), и затем выводит на экран то, что нам нужно. Обработкой событий ввода с клавиатуры мы тут не занимаемся. Этим занимается функция MainInKey, которая показана на следующей странице.
Эта функция действительно проста. Цикл while выполняется до тех пор, пока пользователь не нажал клавишу «0» (ноль). Внутри цикла мы сравниваем её с различными значениями. В зависимости от результата выполняются определённые действия и вызывается главное меню. Эти действия вы уже и сами можете добавить в код. Давайте лучше посмотрим на пункт 2, «Search for a Recipe». С самим меню всё понятно, а вот с функцией Inkey2 (справа) посложнее.
Вновь мы используем стан-дартный цикл while. Используем выражение doloop = 1 для обеспечения бесконечного цикла while и команду break для выхода из цикла. Три элемента выбора очень похожи. Главное их отличие в том, что мы начинаем с переменной tmpstr и затем добавляем к ней текст из выбранного пункта, делая меню более дружелюбным. Далее вызываем функцию GetSearchLine для получения текста поиска. Функция getstr используется для получения строки целиком, а не только отдельных символов. Полученную строку мы в дальнейшем используем.
Полный код доступен по адресу: http://pastebin.com/EluZ3T4P
И напоследок, если вы заинтересованы в дальнейшем изучении Curses, то знайте, что кроме использованных в этой статье методов, есть ещё много других. Помимо поиска в Google, наилучшей отправной точкой станет официальная документация на http://docs.python.org/library/curses.html
До скорых встреч.
Коды
#!/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() #!/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() #!/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() =================================================== 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.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 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() # MAIN LOOP try: myscreen = curses.initscr() LogicLoop() finally: curses.endwin() def LogicLoop(): DoMainMenu() 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() 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) tmpstr = "Enter text to search in " if key == ord('1'): sstr = "'Recipe Name' for -> " tmpstr = tmpstr + sstr retstring = GetSearchLine(13,1,tmpstr) break elif key == ord('2'): sstr = "'Recipe Source' for -> " tmpstr = tmpstr + sstr retstring = GetSearchLine(13,1,tmpstr) break elif key == ord('3'): sstr = "'Ingredients' for -> " tmpstr = tmpstr + sstr retstring = GetSearchLine(13,1,tmpstr) break else: retstring = "" break if retstring != "": myscreen.addstr(15,1,"You entered - " + retstring) else: myscreen.addstr(15,1,"You entered a blank string") myscreen.refresh() myscreen.addstr(20,1,"Press a key") myscreen.getch() def GetSearchLine(row,col,strng): myscreen.addstr(row,col,strng) myscreen.refresh() instring = myscreen.getstr(row,len(strng)+1) myscreen.addstr(row,len(strng)+1,instring) myscreen.refresh() return instring