如何在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
相关产品推荐
相关产品推荐

