【问题标题】:Python curses - resize terminal while inputting textPython curses - 在输入文本时调整终端大小
【发布时间】:2018-04-25 02:20:28
【问题描述】:

我正在尝试使用 curses 做一个简单的 GUI,但在调整屏幕大小和重绘屏幕时遇到了一些问题。 这是我的代码:

import curses
import curses.textpad as textpad

class editor (object):
    def __init__(self, stdscreen):
        self.screen = stdscreen
        self.height,self.width = self.screen.getmaxyx()
        self.draw_screen()
        while True:
            # Check if the screen has been resized
            resize = curses.is_term_resized(self.height, self.width)
            if resize:
                self.height,self.width = self.screen.getmaxyx()
                self.draw_screen()
            curses.noecho()
            text = textpad.Textbox(self.text).edit(validate=self.validate)
            self.body.addstr(0,0, str(text), curses.A_NORMAL)
            self.body.refresh()
            self.text.erase()
    def draw_screen (self):
        self.screen.clear()
        self.header=self.screen.subwin(1,self.width,0,0)
        self.body=self.screen.subwin(self.height-3,self.width,1,0)
        self.text=self.screen.subwin(1,self.width,self.height-2,0)
        self.footer=self.screen.subwin(self.height-1,0)
        header_text = "{0:<{n}}".format("Header", n=self.width-1)
        self.header.addstr(0,0, header_text, curses.A_REVERSE)
        self.footer.addstr(0,0, "^W", curses.A_REVERSE)
        self.footer.addstr(0,2, " Quit", curses.A_NORMAL)
        self.header.noutrefresh()
        self.body.noutrefresh()
        self.footer.noutrefresh()
        self.text.noutrefresh()
        curses.doupdate()
    def validate (self, ch):
        # If resize event redraw screen
        if ch==curses.KEY_RESIZE:
            self.height,self.width = self.screen.getmaxyx()
            self.draw_screen()
        if ch==23: # ^w quit
            exit()
        else:
            return ch

if __name__=="__main__":
    curses.wrapper(editor)

当终端没有被调整大小时,它会像预期的那样工作,但是当我放大终端窗口时,当页眉和页脚分别移动到顶行和底行时,光标保持在前一个位置,直到字符串被接受按 Enter。 缩小时相同,只是在这种情况下光标移到页脚上并返回页脚的内容。 试图调试问题,我发现子窗口似乎可以移动,但光标并没有随着它们移动。我也试过了

self.text.move(0,0)

将光标移回正确的位置,但是没有用。

奖励: 如何保留在调整大小操作之前插入的文本并将其添加到调整大小的显示屏幕?

编辑: 经过更多调试后,我想出了一个“有点”可行的伪解决方案(但丑陋和hacky)。

import curses
import curses.textpad as textpad

class my_textbox(textpad.Textbox):

    def edit(self, validate=None):
        while 1:
            ch = self.win.getch()
            # Return a list instead of a string if the last key was a resize
            if ch==curses.KEY_RESIZE:
                return[self.gather(),True]
            if validate:
                ch = validate(ch)
            if not ch:
                continue
            if not self.do_command(ch):
                break
            self.win.refresh()
        return self.gather()

class editor (object):
    def __init__(self, stdscreen):
        self.screen = stdscreen
        self.height,self.width = self.screen.getmaxyx()
        # Initialize a variable to store the previous text
        self.ptext=None
        curses.noecho()
        self.draw_screen()
        while True:
            # Check if the screen has been resized
            resize = curses.is_term_resized(self.height, self.width)
            if resize:
                self.height,self.width = self.screen.getmaxyx()
                self.draw_screen()
            text = my_textbox(self.text).edit(validate=self.validate)
            # If the window has been resized (aka textbox returned a list)
            if isinstance (text, list):
                text=text[0]
                # Store the previous text
                self.ptext=text.strip()
                continue
            self.body.addstr(0,0, str(text), curses.A_NORMAL)
            self.body.refresh()
            self.text.erase()
    def draw_screen (self):
        self.screen.clear()
        self.header=self.screen.subwin(1,self.width,0,0)
        self.body=self.screen.subwin(self.height-3,self.width,1,0)
        self.text=self.screen.subwin(1,self.width,self.height-2,0)
        self.footer=self.screen.subwin(self.height-1,0)
        header_text = "{0:<{n}}".format("Header", n=self.width-1)
        self.header.addstr(0,0, header_text, curses.A_REVERSE)
        # If there was text previous to resize
        if self.ptext:
            # To prevent _curses.error: addwstr() returned ERR
            # when shrinking window cut out the last character
            # (!) Raises ERR anyway when shrinking with double click from
            # titlebar, also, when resizing is too fast, ptext become a series of ^?
            if len(self.ptext)<self.width-1:
                self.text.addstr(0,0,self.ptext.strip())
            else:
                self.ptext=self.ptext[:self.width-1]
                self.text.addstr(0,0,self.ptext.strip())
        self.footer.addstr(0,0, "^W", curses.A_REVERSE)
        self.footer.addstr(0,2, " Quit", curses.A_NORMAL)
        self.header.noutrefresh()
        self.body.noutrefresh()
        self.footer.noutrefresh()
        self.text.noutrefresh()
        curses.doupdate()

    def validate (self, ch):
        if ch==23: # ^w quit
            exit()
        else:
            return ch

if __name__=="__main__":
    curses.wrapper(editor)

我做了什么

我重新定义了 Textbox 类的 edit 方法,以便在调整窗口大小时返回一个列表而不是字符串。

在调整窗口大小时的输入循环中,先前的文本被存储并开始一个新的循环。

如果之前的文本存在,绘图函数会将其添加到子窗口中。

注释:

通过双击标题栏来调整大小会引发错误,如果调整大小“太快”,则字符串将被垃圾替换(“^?”,实际上是 2^64-1 在被诅咒转换之前)。

我想因为 (n)curses 的大部分功能都是在考虑不可调整大小的终端创建的,所以实现确实有意义,但至少对于像我这样对 python 相对较新的人来说,根本不直观.

【问题讨论】:

    标签: python ncurses curses


    【解决方案1】:

    描述听起来不错:您的应用程序必须重新布局文本并重新绘制屏幕内容。

    为此,它必须保留信息记录。其中一些是预定义的,例如这一行中的"Header"

    header_text = "{0:<{n}}".format("Header", n=self.width-1)
    

    但是如果屏幕变小,窗口的内容可能会丢失(因为 ncurses 中的resizeterm 会截断这些窗口):

    self.header=self.screen.subwin(1,self.width,0,0)
    

    以及任何窗口,例如,使用newwin 创建的窗口。 Python 的 textpad 类可以做到这一点。

    curses(特别是 Python 使用的 ncurses)支持一个名为 "pad" 的准窗口,您可以在某些情况下使用它。但是它的 textpad 类并不容易使用它(因为刷新 pad 的底层调用与 window 的调用不同)。

    但是(这需要一些实际的工作),您可以创建一个与您想要的 屏幕 一样大的 pad管理,并使 textpad 成为它的子元素,然后独立于resizeterm 进行自己的重新布局。相关resize_term 注释的Python 文档(复制自ncurses manual):

    resizeterm()使用的后端函数,执行大部分工作;调整窗口大小时,resize_term() 空白填充扩展的区域。调用应用程序应使用适当的数据填充这些区域。 resize_term() 函数尝试调整所有窗口的大小。但是,由于 pads 的调用约定,如果不与应用程序进行额外交互,则无法调整它们的大小。

    【讨论】:

    • 感谢您的建议,我又做了一些实验,但不确定问题出在截断的窗口上。我已经用我发现的内容更新了这个问题。我想我可以用 pad 做一些测试,但这似乎真的需要做很多工作。
    猜你喜欢
    • 2011-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-01
    • 2017-08-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多