0
votes

I have several GUIs which open progressively that are all experiencing the same issue. When I open them, the first object which is selected is a TextCtrl object. These GUIs are rather large and have scroll bars. Since a TextCtrl object is selected, scrolling with the mouse wheel does nothing and makes it appear as if the scroll bars are broken. To demonstrate this, I made the following code:

import wx

class Tester(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, "Window", size=(500, 500))
        self.panel = wx.ScrolledWindow(self,wx.ID_ANY) 
        self.panel.SetScrollbars(30,30,600,400)

        textBox = wx.TextCtrl(panel, -1, "", size=(200, 150), style=wx.TE_MULTILINE|wx.TE_LEFT)
        textStuff = wx.StaticText(panel, -1, "A\nbunch\nof\nlines\nto\nmake\nthis\nlong\nenough\nto\nhave\nscroll\nbars\n\n\n\n\n\n\n\n\n\nIts lonely down here\n\n\n\n:(")
        lonelyBtn = wx.Button(panel, -1, "So Lonely")


        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox.Add(textBox, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, border=10)
        vbox.Add(textStuff, flag=wx.LEFT|wx.RIGHT|wx.TOP, border=10)
        vbox.Add(lonelyBtn, flag=wx.LEFT|wx.RIGHT|wx.TOP, border=10)

        panel.SetSizer(vbox)
        panel.Layout()

app = wx.PySimpleApp()
Tester().Show()
app.MainLoop()

When you run this and try to scroll down, you'll notice you cannot scroll with the mouse wheel.

So far, this all makes sense. Here's where it gets a little weird. If the selected object is a Button, using the mouse wheel engages the scroll bars. You can test this by pressing the button, then using the mouse wheel. Also, clicking on the panel, or even the scroll bar itself, doesn't allow the mouse wheel to work.

What I'm looking for is a way to make sure that if there are scroll bars, they can be used with the mouse wheel upon displaying the GUI (they are selected by default). I can accept that the mouse wheel will not function once a user clicks into the text control.

Additionally, if you have an explanation for why the mouse wheel works for buttons and not text controls, I'd love to hear it

EDIT: I know I can add a listener (thanks to Mr. Joran Beasley), but this means that the scroll bars within a multi-line text control can never be used with the mouse wheel. The ideal solution (which I'm not sure is possible), is to have clicking on anywhere outside the text control (panel or scroll bar) allows the mouse wheel to scroll the panel. Is this possible?

Additionally, I've switched over to using ScrolledWindow instead of ScrolledPanel

EDIT 2: The fix was to use the following:

self.panel.Bind(wx.EVT_MOTION, self.onMouseMove)

def onMouseMove(self, event):
        self.panel.SetFocusIgnoringChildren()

EDIT 3: The actual fix was to do something a little tricky. Using the code below I bound only multiline text controls to EVT_ENTER_WINDOW and EVT_LEAVE_WINDOW as well as binding every item (and the panel itself) to EVT_MOUSEWHEEL. Then a logical self.inMLTxtCtrl tracks if the mouse if over any of the multiline text controls

self.panel.Bind(wx.EVT_MOUSEWHEEL, self.onWheel)
for sizerItem in self.panel.GetSizer().GetChildren():
    try:
        if sizerItem.GetWindow().IsMultiLine():
            sizerItem.GetWindow().Bind(wx.EVT_ENTER_WINDOW, self.onMouseEnter)
            sizerItem.GetWindow().Bind(wx.EVT_LEAVE_WINDOW, self.onMouseLeave)
            sizerItem.GetWindow().Bind(wx.EVT_MOUSEWHEEL, self.onWheel)
    except:
        sizerItem.GetWindow().Bind(wx.EVT_MOUSEWHEEL, self.onWheel)

Then a logical self.inMLTxtCtrl tracks if the mouse if over any of the multiline text controls as shown below.

def onMouseEnter(self, event):
    print "entering"
    self.inMLTxtCtrl = True

def onMouseLeave(self, event):
    print "leaving"
    self.inMLTxtCtrl = False

Finally, the onWheel() function uses this logical flag to determine where to scroll. If the mouse is in a multiline text control when the scroll wheel is turned, it attempts to scroll in that text control. Othewise, the SetFocusIgnoringChildren() function is called and the panel is scrolled. Since the panel and text control use different scrolling methods, a try...except is needed.

def onWheel(self, event):
    if self.inMLTxtCtrl:
        print "in", event.GetWheelRotation()
    else:
        print "out", event.GetWheelRotation()
        self.panel.SetFocusIgnoringChildren()

    try:
        currScroll = self.panel.FindFocus().GetViewStart()
        newScroll = (currScroll[0],currScroll[1]- event.GetWheelRotation()/60)
        self.panel.FindFocus().Scroll(*newScroll)
    except:
        self.panel.FindFocus().ScrollLines(event.GetWheelRotation()/-60)
1
... you can certainly add listeners for wx.EVT_MOUSEWHEEL to text fields to redirect the scroll to the scrollbars - Joran Beasley
That sounds ideal, how would I do that? - wnnmaw
updated my answer to make it more in line with what you want .... still not exactly what you asked for but meh :/ (its based on the ScrolledPanel version still , although Im sure you can tweak it easy enough to the ScrolledWindow) - Joran Beasley

1 Answers

1
votes
class Tester(wx.Frame):
    def OnTxtScroll(self,e):
        currScroll = self.panel.GetViewStart()
        newScroll = (currScroll[0],currScroll[1]- e.GetWheelRotation()/120)
        self.panel.Scroll(*newScroll)

    def __init__(self):
        ....
        #your code
        ....
        self.panel = panel
        textBox.Bind(wx.EVT_MOUSEWHEEL,self.OnTxtScroll)

after your clarification ... I think that this would work (its a bit hacky and doesnt do exactly what you describe ... but it might work)

 def OnTxtScroll(self,e):
        print dir(e)
        target = e.GetEventObject()
        p1 = target.GetScrollPos(wx.VERTICAL)
        e.Skip()
        self.Update()
        def updateScroll(p1,target,scroll_amt):
            p2 = target.GetScrollPos(wx.VERTICAL)
            if p1 ==p2:#scroll did not effect target object so lets scroll our main panel
                currScroll = self.panel.GetViewStart()
                newScroll = (currScroll[0],currScroll[1]- scroll_amt)
                self.panel.Scroll(*newScroll)
        wx.CallAfter(updateScroll,p1,target,e.GetWheelRotation()/120)