How to maintain a scroll bar position after updating textctrl content?

Hi, mates, I am struggling to maintain a scroll bar position after updating textctrl content. Let me show my code firstly:

class MyFrame(rtmgr.MyFrame):
    def __init__(self, *args, **kwds):
        rtmgr.MyFrame.__init__(self, *args, **kwds)
        self.scroll_pos = 0
    
    def updateCtrlMsg(self, msg):
        self.scroll_pos = self.text_ctrl_1.GetScrollPos(wx.VERTICAL)
        self.text_ctrl_1.SetValue(msg)
        self.text_ctrl_1.SetScrollPos(wx.VERTICAL ,self.scroll_pos)

class MyApp(wx.App):
    def OnInit(self):
        self.frame = MyFrame(None, wx.ID_ANY, "")
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True

def main(args=None):
    
    app = MyApp(0)

    # Start wxPython main loop
    app.MainLoop()
  

if __name__ == '__main__':
    main()

In another thread, I use wx.CallAfter(self.app.frame.updateCtrlMsg, data) to update the content of textctrl.

but one thing confuses me a lot, why the scroll bar of textctrl automatically reset to the top all the time even though i have scrolled down.

could anyone give some suggestions to me to fix it out?

the following gif shows the problems:

Hi,

I modified your code to get a runnable app. Instead of using a separate thread to update the TextCtrl my code just uses a timer to call a function to get some random data.

When I used self.text_ctrl_1.SetScrollPos(wx.VERTICAL, self.scroll_pos) in the updateCtrlMsg() I got the same behaviour as you - the scrollbar kept jumping back to the top.

However, when I replaced that line with self.text_ctrl_1.ShowPosition(self.scroll_pos) the scrollbar didn’t jump.

import wx
from random import randint

def getMsg():
    msg = ''
    for i in range(20):
        msg += f"Line {i}: {randint(0, 9)}\n"
    return msg


class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        wx.Frame.__init__(self, *args, **kwds)
        sizer = wx.BoxSizer(wx.VERTICAL)
        self.text_ctrl_1 = wx.TextCtrl(self, -1, '', style=wx.TE_MULTILINE)
        sizer.Add(self.text_ctrl_1, 1, wx.EXPAND, 0)
        self.SetSizer(sizer)
        self.scroll_pos = 0

        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.OnTimer)
        self.timer.Start(1000)

    def OnTimer(self, _event):
        msg = getMsg()
        self.updateCtrlMsg(msg)

    def updateCtrlMsg(self, msg):
        self.scroll_pos = self.text_ctrl_1.GetScrollPos(wx.VERTICAL)
        self.text_ctrl_1.SetValue(msg)
        # self.text_ctrl_1.SetScrollPos(wx.VERTICAL, self.scroll_pos)
        self.text_ctrl_1.ShowPosition(self.scroll_pos)


class MyApp(wx.App):
    def OnInit(self):
        self.frame = MyFrame(None, wx.ID_ANY, "")
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True


def main():
    app = MyApp(0)
    app.MainLoop()


if __name__ == '__main__':
    main()

I don’t know why it behaves differently.

I am using wxPython 4.2.1 gtk3 (phoenix) wxWidgets 3.2.4 + Python 3.12.3 + Linux Mint 22

it’s nice try to use ShowPostion API and I will do it later. Thanks for your great support. But still I would keep focusing on why it behaves differently. Highly appreciate if anyone would share their ideas.

wx.TextCtrl inherits the SetScrollPos() method from its wx.Window ancestor class. In the documentation for the method it says:

Note: This function does not directly affect the contents of the window: it is up to the application to take note of scrollbar attributes and redraw contents accordingly.