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

如何用wxPython实现两个多行文本框的滚动同步?

解决wxPython双文本框滚动同步问题

问题背景

使用Python 3.12 + wxPython开发的应用包含两个并排多行文本框:左侧用于用户输入,右侧显示行级反馈。当文本超出显示范围时,两个文本框的滚动条无法同步,导致反馈内容与输入行错位,难以定位问题。之前找到的Python 2版本的网格滚动同步方案无法适配当前环境。

最小可复现代码

### Pottersclay Writing Assistant 2.2a (MRE) ###
import wx
# Initialize the window
app = wx.App()
window = wx.Frame(None, title = "Pottersclay Writing Assistant 2.2a (MRE)", size = (1885, 1010))
window.Show()
bkg = wx.Panel(window, style = wx.WANTS_CHARS)
# Create the two main text boxes and set their styles
contents = wx.TextCtrl(bkg, pos = (0, 35), size = (500, 400), style = wx.TE_MULTILINE | wx.HSCROLL | wx.WANTS_CHARS)
outputbox = wx.TextCtrl(bkg, pos = (0, 35), size = (500, 400), style = wx.TE_MULTILINE | wx.HSCROLL | wx.WANTS_CHARS | wx.TE_READONLY)
contents.SetWindowStyle(contents.GetWindowStyle() & ~ wx.TAB_TRAVERSAL)
outputbox.SetWindowStyle(outputbox.GetWindowStyle() & ~ wx.TAB_TRAVERSAL)
EXAMPLE = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n"
contents.SetValue(EXAMPLE)
outputbox.SetValue(EXAMPLE)
#Add the two text boxes and the button box to a new, horizontal box sizer representing all of the window below the command line
lowerhbox = wx.BoxSizer(wx.HORIZONTAL)
lowerhbox.Add(contents, proportion = 1, flag = wx.EXPAND | wx.BOTTOM | wx.LEFT, border = 5)
lowerhbox.Add(outputbox, proportion = 1, flag = wx.EXPAND | wx.BOTTOM | wx.RIGHT, border = 5)
bkg.SetSizer(lowerhbox)
app.MainLoop()

解决方案

通过绑定文本框的wx.EVT_SCROLLWIN事件,在滚动触发时同步两个文本框的垂直和水平滚动位置,同时添加标记避免循环触发事件。

修改后的完整代码

### Pottersclay Writing Assistant 2.2a (Fixed) ###
import wx

class ScrollSyncFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title="Pottersclay Writing Assistant 2.2a (Fixed)", size=(1885, 1010))
        self.bkg = wx.Panel(self, style=wx.WANTS_CHARS)
        
        # 创建文本框
        self.contents = wx.TextCtrl(self.bkg, style=wx.TE_MULTILINE | wx.HSCROLL | wx.WANTS_CHARS)
        self.outputbox = wx.TextCtrl(self.bkg, style=wx.TE_MULTILINE | wx.HSCROLL | wx.WANTS_CHARS | wx.TE_READONLY)
        
        # 移除TAB_TRAVERSAL样式
        self.contents.SetWindowStyle(self.contents.GetWindowStyle() & ~wx.TAB_TRAVERSAL)
        self.outputbox.SetWindowStyle(self.outputbox.GetWindowStyle() & ~wx.TAB_TRAVERSAL)
        
        # 填充示例文本
        EXAMPLE = "\n".join(str(i) for i in range(50)) + "\n"
        self.contents.SetValue(EXAMPLE)
        self.outputbox.SetValue(EXAMPLE)
        
        # 布局设置
        lowerhbox = wx.BoxSizer(wx.HORIZONTAL)
        lowerhbox.Add(self.contents, proportion=1, flag=wx.EXPAND | wx.BOTTOM | wx.LEFT, border=5)
        lowerhbox.Add(self.outputbox, proportion=1, flag=wx.EXPAND | wx.BOTTOM | wx.RIGHT, border=5)
        self.bkg.SetSizer(lowerhbox)
        
        # 绑定滚动事件
        self.contents.Bind(wx.EVT_SCROLLWIN, self.on_scroll_contents)
        self.outputbox.Bind(wx.EVT_SCROLLWIN, self.on_scroll_output)
        
        # 标记是否正在同步,避免循环触发
        self.syncing = False

    def on_scroll_contents(self, event):
        if not self.syncing:
            self.syncing = True
            # 同步垂直滚动位置
            self.outputbox.ScrollLines(self.contents.GetScrollPos(wx.VERTICAL) - self.outputbox.GetScrollPos(wx.VERTICAL))
            # 同步水平滚动位置
            self.outputbox.ScrollColumns(self.contents.GetScrollPos(wx.HORIZONTAL) - self.outputbox.GetScrollPos(wx.HORIZONTAL))
            self.syncing = False
        event.Skip()

    def on_scroll_output(self, event):
        if not self.syncing:
            self.syncing = True
            # 同步垂直滚动位置
            self.contents.ScrollLines(self.outputbox.GetScrollPos(wx.VERTICAL) - self.contents.GetScrollPos(wx.VERTICAL))
            # 同步水平滚动位置
            self.contents.ScrollColumns(self.outputbox.GetScrollPos(wx.HORIZONTAL) - self.contents.GetScrollPos(wx.HORIZONTAL))
            self.syncing = False
        event.Skip()

if __name__ == "__main__":
    app = wx.App()
    frame = ScrollSyncFrame()
    frame.Show()
    app.MainLoop()

关键说明

  1. 事件绑定:为两个文本框绑定wx.EVT_SCROLLWIN事件,覆盖所有滚动操作(包括鼠标滚轮、滚动条拖动)。
  2. 同步逻辑:通过GetScrollPos获取当前滚动位置,使用ScrollLines和ScrollColumns同步另一个文本框的位置。
  3. 避免循环触发:添加self.syncing标记,在同步过程中忽略对方的滚动事件,防止无限循环。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 12:45:58