You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Python curses自定义文本框中实现纵向滚动功能

修改思路

要实现滚动效果,我们需要新增滚动偏移量记录当前隐藏的顶部行数,再根据光标位置自动调整偏移量,最后修改渲染逻辑适配偏移即可,以下是完整可运行的修改后代码:

import curses
from curses import textpad
import time

class notepad:
    def __init__(self, stdscr, topleft, bottomright, starttext = ""):
        self.topleft = topleft
        self.bottomright = bottomright
        self.stdscr = stdscr
        self.text = starttext
        self.curspos = 0
        # 新增:滚动偏移量,记录当前隐藏的顶部行数
        self.scroll_offset = 0
        # 新增:计算文本框内可显示的行数、列数
        self.visible_height = (self.bottomright[0] - 1) - (self.topleft[0] + 1) + 1
        self.width = (self.bottomright[1]-1) - (self.topleft[1]+1)

    def box(self):
        curses.textpad.rectangle(self.stdscr, self.topleft[0], self.topleft[1], self.bottomright[0], self.bottomright[1])

    # 新增:自动调整滚动偏移量的方法
    def adjust_scroll(self):
        # 计算光标当前所在的行号(全文的行号)
        cursor_line = self.curspos // self.width
        # 计算文本总行数
        total_lines = (len(self.text) + self.width - 1) // self.width

        # 如果光标在可视区域上方,向上滚动
        if cursor_line < self.scroll_offset:
            self.scroll_offset = cursor_line
        # 如果光标在可视区域下方,向下滚动
        elif cursor_line >= self.scroll_offset + self.visible_height:
            self.scroll_offset = cursor_line - self.visible_height + 1
        
        # 边界处理:滚动偏移不能为负,也不能超过最大可滚动值
        self.scroll_offset = max(0, min(self.scroll_offset, max(0, total_lines - self.visible_height)))

    def get_input(self, key):

        if not key == -1:

            if key == curses.KEY_LEFT:
                self.curspos = max(0, self.curspos - 1)

            elif key == curses.KEY_RIGHT:
                if self.curspos < len(self.text):
                    self.curspos += 1

            elif key == curses.KEY_UP:
                self.curspos = max(0, self.curspos - self.width)

            elif key == curses.KEY_DOWN:
                if self.curspos//self.width < (len(self.text) + self.width -1)//self.width -1:
                    self.curspos = min(len(self.text), self.curspos + self.width)

            elif key == curses.KEY_BACKSPACE:
                if self.curspos > 0:
                    self.text = self.text[:self.curspos][:-1] + self.text[self.curspos:]
                    self.curspos -= 1

            elif key == curses.KEY_ENTER or key == 10 or key == 13:
                pad = self.width - (len(self.text[:self.curspos]) % self.width)
                self.text = self.text[:self.curspos] + " "*pad + self.text[self.curspos:]
                self.curspos += pad

            else:
                self.text = self.text[:self.curspos] + chr(key) + self.text[self.curspos:]
                self.curspos += 1
        
        # 每次输入后调整滚动偏移
        self.adjust_scroll()

    def display(self):
        if self.curspos < 0:
            self.curspos = 0
        if self.curspos > len(self.text):
            self.curspos = len(self.text)

        # 显示文本:只渲染可视区域内的内容
        for i in range(len(self.text)):
            line_idx = i // self.width
            # 跳过不在可视区域的行
            if line_idx < self.scroll_offset or line_idx >= self.scroll_offset + self.visible_height:
                continue
            # 渲染行号要减去滚动偏移
            render_y = self.topleft[0] + 1 + (line_idx - self.scroll_offset)
            render_x = self.topleft[1] + 1 + i % self.width
            self.stdscr.addstr(render_y, render_x, self.text[i])
            
        # 显示模拟光标:位置也要对应减去滚动偏移
        cursor_line = self.curspos // self.width
        render_cursor_y = self.topleft[0] + 1 + (cursor_line - self.scroll_offset)
        render_cursor_x = self.topleft[1] + 1 + self.curspos % self.width
        if self.curspos < len(self.text):
            self.stdscr.addstr(render_cursor_y, render_cursor_x, self.text[self.curspos], curses.A_REVERSE)
        else:
            self.stdscr.addstr(render_cursor_y, render_cursor_x, " ", curses.A_REVERSE)
        
def main():
    run = True
    stdscr = curses.initscr()
    stdscr.nodelay(True)
    stdscr.keypad(True)
    curses.curs_set(False)
    curses.start_color()
    curses.noecho()
    curses.cbreak()

    # 这里设置小尺寸方便测试示例效果,可视区域刚好为5行
    notepad1 = notepad(stdscr, [0,0], [6, 10])

    try:
        while run:
            start = time.time()

            stdscr.erase()

            key = stdscr.getch()
            if key == 27: # 按ESC键退出
                run = False

            notepad1.box()
            notepad1.get_input(key)
            notepad1.display()

            stdscr.refresh()

            time.sleep(max(0.05 - (time.time() - start), 0))
    finally:
        curses.echo()
        curses.nocbreak()
        curses.curs_set(True)
        stdscr.keypad(False)
        stdscr.nodelay(False)
        curses.endwin()

main()

核心修改说明

  • 新增scroll_offset属性记录顶部隐藏的行数,新增visible_height记录文本框内可显示的最大行数
  • 新增adjust_scroll方法自动根据光标位置调整滚动偏移,保证光标始终在可视区域内
  • 重构文本渲染逻辑,只渲染可视范围内的行,渲染位置自动减去滚动偏移量
  • 修复光标显示的位置计算逻辑,适配滚动偏移
  • 优化了原有的上下键、回车、退格的边界判断逻辑,避免光标越界
  • 新增ESC键退出逻辑方便测试

内容的提问来源于stack exchange,提问作者RandomPersonOnline

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 16:30:06