2010-03-25 11 views
17

In meinem Python-Skript, das Curses verwendet, habe ich einen Subwin, dem ein Text zugewiesen ist. Da die Textlänge länger als die Fenstergröße sein kann, sollte der Text scrollbar sein.Wie scrollt man Text im Unterfenster Python/Curses?

Es scheint kein CSS- "Überlauf" -ähnliches Attribut für Curses-Fenster zu geben. Die Python/Curses-Dokumente sind zu diesem Aspekt auch eher kryptisch.

Hat jemand hier eine Idee, wie ich ein scrollbares Curses Subwindow mit Python codieren und tatsächlich scrollen kann?

\ edit: präzisere Frage

Antwort

22

OK mit window.scroll war es zu kompliziert, den Inhalt des Fensters zu bewegen. Stattdessen hat curses.newpad es für mich getan.

ein Pad erstellen:

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

Dann können Sie blättern durch Erhöhen/Vermindern mypad_pos am Eingang von window.getch abhängig() in cmd:

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) 
1

Stellen Sie die window.scrollok (True).

Documentation

+4

Das Fenster akzeptiert Texte, die die eigene Größe überschreiten. Es ist möglich, mit window.scroll (1) zu scrollen. Dann müssen die gescrollten Zeilen neu gezeichnet werden, was in den Dokumenten nicht erklärt wird. Das Scrollen von Curses-Fenstern erfordert also mehrere Schritte, von denen mir noch einige fehlen. – lecodesportif

4

Richtig, war ich ein bisschen verwirrt über die Verwendung von Pads (um Text zu scrollen) und konnte es nach dem Lesen dieses Beitrags immer noch nicht herausfinden; besonders, weil ich es in einem Kontext des Inhalts verwenden wollte, der ein existierendes "Array von Linien" ist. So bereitete ich ein kleines Beispiel, das zeigt Ähnlichkeiten (und Unterschiede) zwischen newpad und subpad:

#!/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) 

Wenn Sie dies ausführen, zunächst werden Sie so etwas wie (newpad links, subpad rechts) erhalten:

┌────────────────────────┐ ┌────────────────────────┐ 
│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 │ 
           └────────────────────────┘ 

Einige Anmerkungen:

  • Beide newpad und subpad sollten ihre Breite/Höhe Größe haben d auf den Inhalt (num Zeilen/max Linienbreite der Anordnung von Linien) + eventuelle Grenzraum
  • In beiden Fällen können Sie zusätzliche Linien mit scrollok() erlauben - aber nicht zusätzliche Breite
  • In beiden Fällen Sie im Grunde " Drücken Sie "eine Linie an der Unterseite des Pads; und dann scroll() bis zum Platz für die nächste
  • Die spezielle refresh Methode, die newpad hat, dann ermöglicht nur für eine Region von diesem "ganzen Inhalt" auf dem Bildschirm angezeigt werden; subpad mehr weniger hat in der Größe gezeigt werden, um es in
  • instanziiert wurde Wenn Sie die Grenzen der Pads ziehen, bevor Inhalt Strings Zugabe - dann werden die Grenzen zu scrollen (das ist das ─── Stück am ...Line 1 ───│ Teil gezeigt) .

Nützliche Links:

0

Dies ist die Antwort auf diese Frage: How to make a scrolling menu in python-curses

Mit diesem Code können Sie in einer Box aus einer Liste von Zeichenfolgen ein kleines Bildlaufmenü erstellen.
Sie können diesen Code auch verwenden, um die Liste der Zeichenfolgen aus einer SQLite-Abfrage oder aus einer CSV-Datei abzurufen.
Um die maximale Anzahl von Zeilen des Menüs zu bearbeiten, müssen Sie nur max_row bearbeiten.
Wenn Sie die Eingabetaste drücken, druckt das Programm den ausgewählten Zeichenfolgenwert und seine Position.

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() 
0

Ich wollte einen Scroll-Pad verwenden, um Inhalte von einigen großen Textdateien angezeigt werden, aber dies auch nicht funktioniert, weil Texte Zeilenumbrüche haben können und es war ziemlich hart, wie viele Zeichen, um herauszufinden, an einem anzuzeigen Zeit, um die gute Anzahl von Spalten und Zeilen anzupassen.

Also entschied ich mich, meine Textdateien zuerst in Zeilen mit exakt COLUMNS-Zeichen zu teilen und mit Leerzeichen zu füllen, wenn die Zeilen zu kurz waren. Dann wird das Scrollen des Textes einfacher.

Hier ist ein Beispielcode eine beliebige Textdatei anzuzeigen:

#!/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) 

Der wichtigste Trick ist die Zeile:

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()])) 

Zuerst Tabellierungen im Text in Leerzeichen umgewandelt werden, dann habe ich splitlines() -Methode, um meinen Text in ein Array von Zeilen zu konvertieren. Aber einige Zeilen können länger als unsere COLUMNS-Nummer sein, daher habe ich jede Zeile in einen Block von COLUMNS-Zeichen aufgeteilt und dann reduce verwendet, um die resultierende Liste in eine Liste von Zeilen umzuwandeln. Schließlich habe ich map verwendet, um jede Zeile mit abschließenden Leerzeichen aufzufüllen, so dass ihre Länge genau COLUMNS-Zeichen ist.

Hoffe, das hilft.

Verwandte Themen