如何用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()
关键说明
- 事件绑定:为两个文本框绑定
wx.EVT_SCROLLWIN事件,覆盖所有滚动操作(包括鼠标滚轮、滚动条拖动)。 - 同步逻辑:通过
GetScrollPos获取当前滚动位置,使用ScrollLines和ScrollColumns同步另一个文本框的位置。 - 避免循环触发:添加
self.syncing标记,在同步过程中忽略对方的滚动事件,防止无限循环。
内容的提问来源于stack exchange,提问作者PurpleRabbit75
相关产品推荐
相关产品推荐

