如何让我的 wxPython scrolledPanel 滚动到 window 的底部?

How do I make my wxPython scrolledPanel scroll to the bottom of the window?

问题:我希望在调用 createNewRow() 时能够强制滚动条到底部。我可以看到 self.Scroll(0, self.scrollRange) 发生在 OnKeyTyped() 中,滚动条移动到 window 的底部,但随后滚动条移动到 window 再一次。我试图通过将 wx.EVT_SCROLLWIN 绑定到调用 event.Skip() 的 OnScroll 来阻止此操作,但似乎这没有用。我不知道如何继续,因为我对 wxPython 中的事件处理程序和滚动事件了解不够。任何有关如何进行的帮助将不胜感激。完整代码如下。

import os
import wx
import datetime as dt
import wx.lib.scrolledpanel as scrolled


class MyFrame(wx.Frame):
    width = 1000
    height = 600
    today = dt.date.today()
    today_date = f"{today:%A - %d %B %Y}"
    filename = f"Worklog {today_date}"
    wxTHICK_LINE_BORDER = 3

    def __init__(self, parent=None, title=filename, size=(width,height - 1)):
        wx.Frame.__init__(self, parent=parent, title=title, size=size)
        self.parent = parent
        self.title = title
        self.size = size
        self.BuildMenuBar()

    def BuildMenuBar(self):
        # Menu bar
        self.menuBar = wx.MenuBar()
        self.fileMenu = wx.Menu()
        self.NewOpt = wx.MenuItem(self.fileMenu, wx.ID_NEW, '&New\tCtrl+N')
        self.OpenOpt = wx.MenuItem(self.fileMenu, wx.ID_OPEN, '&Open\tCtrl+O')
        self.SaveOpt = wx.MenuItem(self.fileMenu, wx.ID_SAVE, '&Save\tCtrl+S')
        self.QuitOpt = wx.MenuItem(self.fileMenu, wx.ID_EXIT, '&Quit\tCtrl+Q')
        self.fileMenu.Append(self.NewOpt)
        self.fileMenu.Append(self.OpenOpt)
        self.fileMenu.Append(self.SaveOpt)
        self.fileMenu.Append(self.QuitOpt)
        self.Bind(wx.EVT_MENU, self.OnQuit, self.QuitOpt)
        self.menuBar.Append(self.fileMenu, '&File')
        self.SetMenuBar(self.menuBar)


    def OnQuit(self, e):
        self.Close()


class MyPanel(wx.Panel):
    def __init__(self,parent):
        wx.Panel.__init__(self, parent=parent)
        self.parent = parent
        self.size = parent.size
        panel_colour = wx.Colour(240, 240, 240, 255)
        self.SetBackgroundColour(panel_colour)
        self.Refresh()


class MyScrolledPanel(scrolled.ScrolledPanel):
    def __init__(self, parent):
        scrolled.ScrolledPanel.__init__(self, parent=parent, style = wx.TAB_TRAVERSAL | wx.TB_BOTTOM)
        self.parent = parent
        # self.size = parent.size
        self.width = parent.size[0]
        self.height = parent.size[1]
        scrollpanel_colour = wx.Colour(255, 255, 255, 255)
        self.SetBackgroundColour(scrollpanel_colour)
        # Call a refresh to update the UI
        self.Refresh()
        self.SetAutoLayout(True)
        self.SetupScrolling()
        self.InitUI()
        self.Bind(wx.EVT_SCROLLWIN, self.OnScroll, self)
        self.Bind(wx.EVT_SIZE, self.OnSize, self)


    def OnScroll(self, e):
        e.Skip()


    def InitUI(self):
        vgap = 0
        hgap = 0

        self.rowList = []

        self.n = 0

        self.scrollSizer = wx.GridBagSizer(vgap + 10, hgap + 10)    

        self.row = self.CreateNewRow(self.n)

        self.rowList.append(self.row)
        print(f"Row List: {self.rowList[-1]}")

        self.scrollSizer.Add(self.row[0], pos = (self.i, 0), flag = wx.ALL | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL, border = 10)
        self.scrollSizer.Add(self.row[1], pos = (self.i, 1), flag = wx.EXPAND | wx.TOP | wx.RIGHT | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL , border = 10)

        self.scrollSizer.AddGrowableCol(1)
        self.SetSizer(self.scrollSizer)

        self.panelSizer = wx.GridBagSizer(vgap, hgap)
        self.panelSizer.AddGrowableRow(0)
        self.panelSizer.AddGrowableCol(0)
        self.panelSizer.Add(self, pos = (0, 0), flag = wx.EXPAND, border = 0) # Add wx.Window not wx.Sizer
        self.parent.SetSizer(self.panelSizer)


    def CreateNewRow(self, number):
        self.i = number
        self.txtStr = "%02d" % (self.i+1) + ". "
        self.staticText = wx.StaticText(self, wx.ID_ANY, self.txtStr)
                        #pos = (x, y)
        #self.staticText.SetForegroundColour(wx.Colour(0,0,0))

        self.control = wx.TextCtrl(self, self.i)
        self.control.SetMaxLength(256)
        self.text_history_length = 0
        self.control.Bind(wx.EVT_TEXT, self.OnKeyTyped, id = self.i)
        #self.control = wx.TextCtrl(self, -1, pos = (x + w + 5,y) )
                        #style = wx.TE_MULTILINE
        elems = [self.staticText, self.control]

        return elems


    def OnSize(self, e):
        self.width, self.height = e.GetSize()
        self.SetSize((self.width, self.height))
        self.OnSizeChange()
        self.Refresh()


    def OnSizeChange(self):
        # Fit child elements
        self.scrollSizer.FitInside(self)
        # Resize layout
        self.Layout()
        # Resize scrolling
        self.SetupScrolling()


    def OnKeyTyped(self, e):
        self.text_length = len(e.GetString())
        if (self.text_history_length == 1 and self.text_length == 0):
            print(f"History length: {self.text_history_length}")
            print(f"Text length: {self.text_length}")
            self.text_history_length = self.text_length
            pass
        elif (self.text_history_length == 0 and self.text_length == 1):
            print(f"History length: {self.text_history_length}")
            print(f"Text length: {self.text_length}")
            self.n += 1
            self.row = self.CreateNewRow(self.n)
            print(f"Action: {self.row}")
            print(f"Row List: {self.rowList[-1]}")
            self.rowList.append(self.row)
            self.scrollSizer.Add(self.row[0], pos = (self.n, 0), flag = wx.ALL | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL, border = 10)
            self.scrollSizer.Add(self.row[1], pos = (self.n, 1), flag = wx.EXPAND | wx.TOP | wx.RIGHT | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL , border = 10)
            self.SetupScrolling()
            self.text_history_length = self.text_length
            self.rowList[self.n-1][1].Bind(wx.EVT_TEXT, None, id = self.n-1)
            self.text_history_length = 0
        else:
            print(f"History length: {self.text_history_length}")
            print(f"Text length: {self.text_length}")
            self.text_history_length = self.text_length
        self.rowList[-1][1].SetFocus()
        self.scrolledPanelChild = self.GetChildren()[-1] # [ scrollPanel ]
        self.ScrollChildIntoView(self.scrolledPanelChild)
        self.OnSizeChange()
        self.scrollRange = self.GetScrollRange(wx.VERTICAL)
        print(f"ScrollRange: {self.scrollRange}")
        #self.scrollUnits = self.GetScrollPixelsPerUnit()
        #print(f"ScrollUnit: {self.scrollUnits}")
        #self.scrollThumb = self.GetScrollThumb(wx.VERTICAL)
        #print(f"ScrollThumb: {self.scrollThumb}")
        self.Scroll(0, self.scrollRange)


def main():
    app = wx.App(False)
    app.locale = wx.Locale(wx.Locale.GetSystemLanguage())
    frame = MyFrame()
    panel = MyPanel(frame)
    scrolledPanel = MyScrolledPanel(panel)
    frame.Show(True)
    app.MainLoop()


if __name__ == "__main__":
    main()

在:

def OnSizeChange(self):
        # Fit child elements
        self.scrollSizer.FitInside(self)
        # Resize layout
        self.Layout()
        # Resize scrolling
        self.SetupScrolling()

SetupScrolling() 事件导致滚动条重置到页面顶部。因此,如果您对此进行评论,则 Scroll(0, self.scrollRange) 会将滚动条滚动到页面底部。改进是让这个调用在 SetupScrolling 之后发生,这样它无论如何都会发生。也许在任何情况下都不需要调用 SetupScrolling,Layout() 就足够了。

老问题,但仍然如此。这可以使用以下方法解决: wx.CallAfter(self._scrolled_panel.ScrollChildIntoView, new_text) 或者从 CallAfter 调用任何其他滚动方法。

SetupScrolling(scrollIntoView=True, scrollToTop=False)