简体   繁体   English

如何在 Python/Curses 子窗口中滚动文本?

[英]How to scroll text in Python/Curses subwindow?

In my Python script which uses Curses, I have a subwin to which some text is assigned.在我使用 Curses 的 Python 脚本中,我有一个 subwin,其中分配了一些文本。 Because the text length may be longer than the window size, the text should be scrollable.因为文本长度可能比窗口大小长,所以文本应该是可滚动的。

It doesn't seem that there is any CSS-"overflow" like attribute for Curses windows. Curses 窗口似乎没有任何 CSS-“溢出”之类的属性。 The Python/Curses docs are also rather cryptic on this aspect. Python/Curses 文档在这方面也相当神秘。

Does anybody here have an idea how I can code a scrollable Curses subwindow using Python and actually scroll through it?这里有人知道如何使用 Python 编写可滚动的 Curses 子窗口并实际滚动它吗?

\\edit: more precise question \\编辑:更精确的问题

OK with window.scroll it was too complicated to move the content of the window.好的 window.scroll 移动窗口的内容太复杂了。 Instead, curses.newpad did it for me.相反,curses.newpad 为我做了。

Create a pad:创建垫:

mypad = curses.newpad(40,60)
mypad_pos = 0
mypad.refresh(mypad_pos, 0, 5, 5, 10, 60)

Then you can scroll by increasing/decreasing mypad_pos depending on the input from window.getch() in cmd:然后,您可以根据 cmd 中 window.getch() 的输入通过增加/减少 mypad_pos 来滚动:

if  cmd == curses.KEY_DOWN:
    mypad_pos += 1
    mypad.refresh(mypad_pos, 0, 5, 5, 10, 60)
elif cmd == curses.KEY_UP:
    mypad_pos -= 1
    mypad.refresh(mypad_pos, 0, 5, 5, 10, 60)

Right, I was a bit confused on how to utilize pads (in order to scroll text), and still couldn't figure it out after reading this post;是的,我对如何使用pads(为了滚动文本)有点困惑,阅读这篇文章后仍然无法弄清楚; especially since I wanted to use it in a context of the content being an existing "array of lines".特别是因为我想在内容是现有“行数组”的上下文中使用它。 So I prepared a small example, that shows similarities (and differences) between newpad and subpad :因此,我制备了一个小例子,即示出了相似性(和差异)之间newpadsubpad

#!/usr/bin/env python2.7
import curses

# content - array of lines (list)
mylines = ["Line {0} ".format(id)*3 for id in range(1,11)]

import pprint
pprint.pprint(mylines)

def main(stdscr):
  hlines = begin_y = begin_x = 5 ; wcols = 10
  # calculate total content size
  padhlines = len(mylines)
  padwcols = 0
  for line in mylines:
    if len(line) > padwcols: padwcols = len(line)
  padhlines += 2 ; padwcols += 2 # allow border
  stdscr.addstr("padhlines "+str(padhlines)+" padwcols "+str(padwcols)+"; ")
  # both newpad and subpad are <class '_curses.curses window'>:
  mypadn = curses.newpad(padhlines, padwcols)
  mypads = stdscr.subpad(padhlines, padwcols, begin_y, begin_x+padwcols+4)
  stdscr.addstr(str(type(mypadn))+" "+str(type(mypads)) + "\n")
  mypadn.scrollok(1)
  mypadn.idlok(1)
  mypads.scrollok(1)
  mypads.idlok(1)
  mypadn.border(0) # first ...
  mypads.border(0) # ... border
  for line in mylines:
    mypadn.addstr(padhlines-1,1, line)
    mypadn.scroll(1)
    mypads.addstr(padhlines-1,1, line)
    mypads.scroll(1)
  mypadn.border(0) # second ...
  mypads.border(0) # ... border
  # refresh parent first, to render the texts on top
  #~ stdscr.refresh()
  # refresh the pads next
  mypadn.refresh(0,0, begin_y,begin_x, begin_y+hlines, begin_x+padwcols)
  mypads.refresh()
  mypads.touchwin()
  mypadn.touchwin()
  stdscr.touchwin() # no real effect here
  #stdscr.refresh() # not here! overwrites newpad!
  mypadn.getch()
  # even THIS command erases newpad!
  # (unless stdscr.refresh() previously):
  stdscr.getch()

curses.wrapper(main)

When you run this, at first you will get something like ( newpad left, subpad right):当你运行它,首先你会得到类似的信息( newpad左, subpad右):

 ┌────────────────────────┐    ┌────────────────────────┐
 │Line 1 Line 1 Line 1 ───│    │Line 1 Line 1 Line 1 ───│
 │Line 2 Line 2 Line 2    │    │Line 2 Line 2 Line 2    │
 │Line 3 Line 3 Line 3    │    │Line 3 Line 3 Line 3    │
 │Line 4 Line 4 Line 4    │    │Line 4 Line 4 Line 4    │
 │Line 5 Line 5 Line 5    │    │Line 5 Line 5 Line 5    │
                               │Line 6 Line 6 Line 6    │
                               │Line 7 Line 7 Line 7    │
                               │Line 8 Line 8 Line 8    │
                               │Line 9 Line 9 Line 9    │
                               │Line 10 Line 10 Line 10 │
                               └────────────────────────┘

Some notes:一些注意事项:

  • Both newpad and subpad should have their width/height sized to the content (num lines/max line width of the array of lines) + eventual border spacenewpadsubpad应具有它们的宽度/高度尺寸含量(NUM线/线的阵列的最大线宽度)+最终边界空间
  • In both cases, you could allow extra lines with scrollok() - but not extra width在这两种情况下,您都可以使用scrollok()允许额外的行 - 但不允许额外的宽度
  • In both cases, you basically "push" a line at the bottom of the pad;在这两种情况下,您基本上都是在垫子底部“推”一条线; and then scroll() up to make room for the next然后scroll()为下一个腾出空间
  • The special refresh method that newpad has, then allows for just a region of this "whole content" to be shown on screen; newpad具有的特殊refresh方法,然后只允许在屏幕上显示此“整个内容”的一个区域; subpad more-less has to be shown in the size it was instantiated in subpad more-less 必须以实例化时的大小显示
  • If you draw the borders of the pads before adding content strings - then the borders will scroll up too (that is the ─── piece shown at the ...Line 1 ───│ part).如果在添加内容字符串之前绘制焊盘的边框 - 那么边框也会向上滚动(即显示在...Line 1 ───│部分的───部分)。

Useful links:有用的链接:

Set the window.scrollok(True).设置 window.scrollok(True)。

Documentation 文档

I wanted to use a scrolling pad to display content of some large text files but this didn't work well because texts can have line breaks and it was pretty hard to figure out how many characters to display at a time to fit the good number of columns and rows.我想使用滚动板来显示一些大型文本文件的内容,但这效果不佳,因为文本可能会有换行符,而且很难确定一次显示多少字符以适应大量文本列和行。

So I decided to first split my text files in lines of exactly COLUMNS characters, padding with spaces when lines were too short.所以我决定首先将我的文本文件分割成一行正好是 COLUMNS 字符的行,当行太短时用空格填充。 Then scrolling the text become more easy.然后滚动文本变得更加容易。

Here is a sample code to display any text file:这是显示任何文本文件的示例代码:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import curses
import locale
import sys

def main(filename, filecontent, encoding="utf-8"):
    try:
        stdscr = curses.initscr()
        curses.noecho()
        curses.cbreak()
        curses.curs_set(0)
        stdscr.keypad(1)
        rows, columns = stdscr.getmaxyx()
        stdscr.border()
        bottom_menu = u"(↓) Next line | (↑) Previous line | (→) Next page | (←) Previous page | (q) Quit".encode(encoding).center(columns - 4)
        stdscr.addstr(rows - 1, 2, bottom_menu, curses.A_REVERSE)
        out = stdscr.subwin(rows - 2, columns - 2, 1, 1)
        out_rows, out_columns = out.getmaxyx()
        out_rows -= 1
        lines = map(lambda x: x + " " * (out_columns - len(x)), reduce(lambda x, y: x + y, [[x[i:i+out_columns] for i in xrange(0, len(x), out_columns)] for x in filecontent.expandtabs(4).splitlines()]))
        stdscr.refresh()
        line = 0
        while 1:
            top_menu = (u"Lines %d to %d of %d of %s" % (line + 1, min(len(lines), line + out_rows), len(lines), filename)).encode(encoding).center(columns - 4)
            stdscr.addstr(0, 2, top_menu, curses.A_REVERSE)
            out.addstr(0, 0, "".join(lines[line:line+out_rows]))
            stdscr.refresh()
            out.refresh()
            c = stdscr.getch()
            if c == ord("q"):
                break
            elif c == curses.KEY_DOWN:
                if len(lines) - line > out_rows:
                    line += 1
            elif c == curses.KEY_UP:
                if line > 0:
                    line -= 1
            elif c == curses.KEY_RIGHT:
                if len(lines) - line >= 2 * out_rows:
                    line += out_rows
            elif c == curses.KEY_LEFT:
                if line >= out_rows:
                    line -= out_rows
    finally:
        curses.nocbreak(); stdscr.keypad(0); curses.echo(); curses.curs_set(1)
        curses.endwin()

if __name__ == '__main__':
    locale.setlocale(locale.LC_ALL, '')
    encoding = locale.getpreferredencoding()
    try:
        filename = sys.argv[1]
    except:
        print "Usage: python %s FILENAME" % __file__
    else:
        try:
            with open(filename) as f:
                filecontent = f.read()
        except:
            print "Unable to open file %s" % filename
        else:
            main(filename, filecontent, encoding)

The main trick is the line:主要技巧是这一行:

lines = map(lambda x: x + " " * (out_columns - len(x)), reduce(lambda x, y: x + y, [[x[i:i+out_columns] for i in xrange(0, len(x), out_columns)] for x in filecontent.expandtabs(4).splitlines()]))

First, tabulations in the text are converted to spaces, then I used splitlines() method to convert my text in array of lines.首先,文本中的表格被转换为空格,然后我使用 splitlines() 方法将我的文本转换为行数组。 But some lines may be longer than our COLUMNS number, so I splitted each line in chunk of COLUMNS characters and then used reduce to transform the resulting list in a list of lines.但是有些行可能比我们的 COLUMNS 号长,所以我将每一行分成 COLUMNS 字符块,然后使用 reduce 将结果列表转换为行列表。 Finally, I used map to pad each line with trailing spaces so that its length is exactly COLUMNS characters.最后,我使用 map 用尾随空格填充每一行,使其长度正好是 COLUMNS 个字符。

Hope this helps.希望这可以帮助。

This is the answer of this question: How to make a scrolling menu in python-curses这是这个问题的答案: How to make a scrolling menu in python-curses

This code allows you to create a little scrolling menu in a box from a list of strings.此代码允许您从字符串列表的框中创建一个小的滚动菜单。
You can also use this code getting the list of strings from a sqlite query or from a csv file.您还可以使用此代码从 sqlite 查询或 csv 文件中获取字符串列表。
To edit the max number of rows of the menu you just have to edit max_row .要编辑菜单的最大行数,您只需编辑max_row
If you press enter the program will print the selected string value and its position.如果按 Enter,程序将打印选定的字符串值及其位置。

from __future__ import division  #You don't need this in Python3
import curses
from math import *



screen = curses.initscr()
curses.noecho()
curses.cbreak()
curses.start_color()
screen.keypad( 1 )
curses.init_pair(1,curses.COLOR_BLACK, curses.COLOR_CYAN)
highlightText = curses.color_pair( 1 )
normalText = curses.A_NORMAL
screen.border( 0 )
curses.curs_set( 0 )
max_row = 10 #max number of rows
box = curses.newwin( max_row + 2, 64, 1, 1 )
box.box()


strings = [ "a", "b", "c", "d", "e", "f", "g", "h", "i", "l", "m", "n" ] #list of strings
row_num = len( strings )

pages = int( ceil( row_num / max_row ) )
position = 1
page = 1
for i in range( 1, max_row + 1 ):
    if row_num == 0:
        box.addstr( 1, 1, "There aren't strings", highlightText )
    else:
        if (i == position):
            box.addstr( i, 2, str( i ) + " - " + strings[ i - 1 ], highlightText )
        else:
            box.addstr( i, 2, str( i ) + " - " + strings[ i - 1 ], normalText )
        if i == row_num:
            break

screen.refresh()
box.refresh()

x = screen.getch()
while x != 27:
    if x == curses.KEY_DOWN:
        if page == 1:
            if position < i:
                position = position + 1
            else:
                if pages > 1:
                    page = page + 1
                    position = 1 + ( max_row * ( page - 1 ) )
        elif page == pages:
            if position < row_num:
                position = position + 1
        else:
            if position < max_row + ( max_row * ( page - 1 ) ):
                position = position + 1
            else:
                page = page + 1
                position = 1 + ( max_row * ( page - 1 ) )
    if x == curses.KEY_UP:
        if page == 1:
            if position > 1:
                position = position - 1
        else:
            if position > ( 1 + ( max_row * ( page - 1 ) ) ):
                position = position - 1
            else:
                page = page - 1
                position = max_row + ( max_row * ( page - 1 ) )
    if x == curses.KEY_LEFT:
        if page > 1:
            page = page - 1
            position = 1 + ( max_row * ( page - 1 ) )

    if x == curses.KEY_RIGHT:
        if page < pages:
            page = page + 1
            position = ( 1 + ( max_row * ( page - 1 ) ) )
    if x == ord( "\n" ) and row_num != 0:
        screen.erase()
        screen.border( 0 )
        screen.addstr( 14, 3, "YOU HAVE PRESSED '" + strings[ position - 1 ] + "' ON POSITION " + str( position ) )

    box.erase()
    screen.border( 0 )
    box.border( 0 )

    for i in range( 1 + ( max_row * ( page - 1 ) ), max_row + 1 + ( max_row * ( page - 1 ) ) ):
        if row_num == 0:
            box.addstr( 1, 1, "There aren't strings",  highlightText )
        else:
            if ( i + ( max_row * ( page - 1 ) ) == position + ( max_row * ( page - 1 ) ) ):
                box.addstr( i - ( max_row * ( page - 1 ) ), 2, str( i ) + " - " + strings[ i - 1 ], highlightText )
            else:
                box.addstr( i - ( max_row * ( page - 1 ) ), 2, str( i ) + " - " + strings[ i - 1 ], normalText )
            if i == row_num:
                break



    screen.refresh()
    box.refresh()
    x = screen.getch()

curses.endwin()
exit()

Another way is to print the visible items using for-loop on list with slice notation.另一种方法是使用带有切片符号的列表上的 for 循环打印可见项。 That is, you choose a specific part of the list to display, then add or subtract an increment to change the range every time you press a key up or down.也就是说,您选择要显示的列表的特定部分,然后每次向上或向下按一个键时增加或减少一个增量以更改范围。
like list[ y : y + coverage ] where y is the start and coverage determines how many items in the list you'd like to display.list[ y : y + coverage ]其中y是开始, coverage决定了你想要显示的列表中有多少项目。

from curses import wrapper
import curses

def main(stdscr):
    mY = curses.LINES
    win = curses.newwin(100,50,0,50)
    win.keypad(True)
    numbers = [n for n in range(0,1001)]
    ylen = len(numbers)
    iny = 0
    border_y = mY-5
    def scroll(window):
        [window.addstr(y, 0, f'{b} \n') for y, b in enumerate(numbers[iny:iny+border_y])]
        window.refresh()
    scroll(win)

    
    ###    KEY PRESS    ###
    while(True):
        ch = win.getkey()
        if ch == 'KEY_UP':
            if(iny>0):
                iny-=1
                scroll(win) 
        elif ch == 'KEY_DOWN':
            if(iny<ylen-border_y):
                iny+=1
                scroll(win)
        elif ch == 'q':
            break
wrapper(main)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM