wxPython Manual Scroll of panel ScrolledPanel failing with wx.Grid - python

I've created a panel thats derived from wx.lib.scrolledpanel. I could scroll on it fine with my mousewheel until I put a grid into the panel. Now when the mouse cursor is on top of the grid, the scroll stopped working, and would start working again if i moved the cursor outside of the grid.
I figured the easiest solution after searching and searching was to just manually capture the mousewheel event and scroll the panel manually. I bound this handler to wx.EVT_MOUSEWHEEL inside my wx.App object
class Wx_app(wx.App):
def __init__(self):
super().__init__(clearSigInt=True)
self.frame = MyFrame(None, pos=(0,0), size=(1900, 1100))
self.Bind(wx.EVT_MOUSEWHEEL, self.on_mouse_wheel)
def on_mouse_wheel(self, e):
# get the current scroll pos, is tuple with x as first val, y as second val
pos = self.frame.panel.CalcUnscrolledPosition(0, 0)
y_pos = pos[1]
# detemrine if user is scrolling up or down
if e.GetWheelRotation() > 0:
# user is scrolling up
print("UP")
self.frame.panel.Scroll(0, y_pos + 10)
else:
# user is scrolling down
print("DOWN")
self.frame.panel.Scroll(0, y_pos - 10)
This code works when i try to scroll down, but when i try to scroll back up with the the mousewheel nothing happens, even though "UP" registers in my terminal. Also i would think up should be y_pos - 10 and not y_pos + 10, but then the wheel scrolls in the opposite direction you would expect. What am I doing wrong? Perhaps i'm not getting the correct existing position in the first place, but CalcUnscrolledPosition is the only thing I could find that could maybe do that. I'm new to Python please explain it like I'm a 5 year old. thanks

Your immediate issue is that Up would be Y - 10 and Down would be Y + 10.
You need to go further up (less y) or further down (more y).
You also may well be comparing Apples with Oranges, when relying on the position as returned from the mouse event and how that relates to the position within the scrolled window.
The crux of this is that both the scrolledpanel and the grid are scrollable widgets.
Your issue seems to be that your grid is not of sufficent size to show the grid's scrollbars, thus causing confusion.
You can force the scrollbars on the grid and size it so that it is obvious that there are two sets of scrollbars and hopefully your users will work it out.
Here is a sample grid in a scrolled panel to play with (ignore the bound functions, they were there because initially I misunderstood your issue, thinking that you wanted manual scrolling within the grid)
import wx
import wx.lib.scrolledpanel
import wx.grid
class MyPanel(wx.Panel):
def __init__(self, *args, **kwargs):
wx.Panel.__init__(self, *args, **kwargs)
self.sizer = wx.BoxSizer(wx.HORIZONTAL)
self.fileFormPanel = FileFormPanel(self)
self.sizer.Add(self.fileFormPanel, 1, wx.EXPAND)
self.SetSizer(self.sizer)
class FileFormPanel(wx.lib.scrolledpanel.ScrolledPanel):
def __init__(self, parent):
wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent)
sizer = wx.BoxSizer(wx.VERTICAL)
# Create a wxGrid object
self.grid = wx.grid.Grid(self, -1, size=(300,480))
self.grid.CreateGrid(5, 10)
self.grid.SetRowLabelSize(1)
self.grid.SetDefaultColSize(120)
self.grid.SetColSize(0, 50)
self.grid.ShowScrollbars(True, True)
for j in range(5):
for k in range(10):
self.grid.SetCellValue(j, k, str(k))
text = wx.TextCtrl(self, wx.ID_ANY, value="Some text")
sizer.Add(self.grid)
sizer.Add(text)
self.SetSizer(sizer)
self.SetupScrolling()
#self.grid.Bind(wx.EVT_MOUSEWHEEL, self.OnGrid)
#self.Bind(wx.EVT_MOUSEWHEEL, self.OnScroll)
#self.grid.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.OnSelect)
# def OnGrid(self, event):
# obj = event.GetEventObject()
# if event.WheelRotation > 0:
# self.grid.MoveCursorUp(False)
# else:
# self.grid.MoveCursorDown(False)
#
# def OnSelect(self, event):
# obj = event.GetEventObject()
# r = event.GetRow()
# c = event.GetCol()
# self.grid.MakeCellVisible(r,c)
# event.Skip()
#
# def OnScroll(self, event):
# print("window scroll")
# event.Skip()
class DemoFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent = None, size = (400, 400))
MyPanel(self)
class App(wx.App):
def OnInit(self):
self.frame = DemoFrame()
self.frame.Show()
self.SetTopWindow(self.frame)
return True
app = App()
app.MainLoop()

Related

Using wx.EVT_PAINT and wx.PaintDC

You use wx.EVT_PAINT and wx.PaintDC to draw shapes, so that when window is resized (redrawn) shapes will not be lost. This works when the window is created. But, how will I preserve the shapes that I create after window is created?
Below, I present you a code, when the app first starts, a rectangle is drawn on the window. When user double clicks somewhere on the window, another rectangle is created. The initial rectangle is always preserved because it is bind to wx.EVT_PAINT event, so that it will be redrawn every time the window is redrawn.
But the second rectangle is not associated to the wx.EVT_PAINT, therefore it is lost when window is redrawn. How do I preserve the second rectangle as well?
import wx
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_LEFT_DCLICK, self.on_left_double_click)
def OnPaint(self, evt):
dc = wx.PaintDC(self)
dc.DrawRectangle(50, 60, 90, 40)
def on_left_double_click(self, evt):
x = evt.GetX()
y = evt.GetY()
dc = wx.ClientDC(self)
dc.SetBrush(wx.Brush("yellow"))
dc.DrawRectangle(x, y, 90, 40)
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Test",style=wx.DEFAULT_FRAME_STYLE,size=wx.Size(400, 300))
self.main_panel = MyPanel(self)
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
There is no universal solution to this except handling every drawing operation in a wx.PaintDC. You would do something along the lines of the following:
def __init__(self, parent):
# ...
self.show_yellow_box = False
self.box_pos = None
def OnPaint(self, evt):
dc = wx.PaintDC(self)
dc.DrawRectangle(50, 60, 90, 40)
if self.show_yellow_box:
x, y = self.box_pos
dc.SetBrush(wx.Brush("yellow"))
dc.DrawRectangle(x, y, 90, 40)
def on_left_double_click(self, evt):
x = evt.GetX()
y = evt.GetY()
self.box_pos = (x, y)
self.show_yellow_box = True
self.Refresh() # important, to trigger EVT_PAINT on panel
If the operations in the paint event are more expensive, you probably will end up collecting the expensive drawing operations on the DC in a wx.MemoryDC and blit the bitmap content back onto the panel in the MyPanel.OnPaint.
There is a temporary DC (wx.Overlay/wx.OverlayDC), which is however only useful to apply temporary changes between paint events.

wxPython Paint Damaged, Clipped area

I have the following simple code (click the pink box and you can move it around with your mouse while holding down the left mouse button).
import wx
class AppPanel(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
p = MovablePanel(self, -1)
self.i = 0
self.Bind(wx.EVT_PAINT, self.OnPaint, self)
def OnPaint(self, event):
dc = wx.PaintDC(self)
self.i = self.i+10
c = self.i % 255
c = (0, 0, c)
dc.SetPen(wx.Pen(c))
dc.SetBrush(wx.Brush(c))
dc.DrawRectangle(0, 0, 10000,10000)
class MovablePanel(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
self.SetMinSize((500,500))
self.SetSize((500,500))
self.SetBackgroundColour("PINK")
self.LEFT_DOWN = False
self.Bind(wx.EVT_MOTION, self.OnMove, self)
self.Bind(wx.EVT_LEFT_DOWN,
self.OnClickDown,
self)
self.Bind(wx.EVT_LEFT_UP,
self.OnClickUp,
self)
def OnClickUp(self, event):
self.LEFT_DOWN = False
self.Refresh()
def OnClickDown(self, event):
self.LEFT_DOWN = True
self.Refresh()
def OnMove(self, event):
if self.LEFT_DOWN:
p = self.GetTopLevelParent().ScreenToClient(wx.GetMousePosition())
self.SetPosition(p)
if __name__ == "__main__":
app = wx.App(False)
f = wx.Frame(None, -1, size = (700, 700))
p = AppPanel(f, -1)
f.Show()
f.Maximize()
app.MainLoop()
and it is suppose to look like the following (simply resize the frame)
However after moving the pink box around you will see it really looks like this
I have tried the following
dc.Clear()
dc.DestroyClippingRegion()
wx.FULL_REPAINT_ON_RESIZE
wx.EVT_ERASE_BACKGROUND
I'm pretty sure it has to do with it being a panel, and therefore the PaintEvent only marking it partially damaged. This part is colored differently making the 'ghosting' or 'smearing' obvious. Perhaps I'm using the wrong words because I was unable to find a solution (and I this seems to be a non complex issue simply having to do with the 'damaged' region).
Ok I found the problem, but I'll try to post more details later.
Basically the goal of this code is to move a panel around and then update the parent panel. SetPosition calls Move which going through the wxWidget code calls DoMoveWindow, all of this leads to a change in position and a repaint call (not sure what calls the repaint yet). Great. However the repaint only marks a certain 'area' as it tries to be efficient. That is why some of the issue can be solved by having the panel go over the 'ghosted' area. What you have to do is after the SetPosition, call GetParent().Refresh(), which will send a 'full' paint without any excluded area.
Another thing to note is there are TWO terms for this 'damaged' or 'clipped' area. One is 'damage' however there is another, 'dirty'. Damage is used in the wx PaintDC information
Using wx.PaintDC within EVT_PAINT handlers is important because it
automatically sets the clipping area to the damaged area of the
window. Attempts to draw outside this area do not appear.
Trusting the documentation you will be mostly lost. However in one of the wxPython DoubleBuffer how to's the lingo changes (but it is the same thing as 'damage')
Now the OnPaint() method. It's called whenever ther is a pain event
sent by the system: i.e. whenever part of the window gets dirty.
Knowing this if you Google wx Window dirty you will get the following
Mark the specified rectangle (or the whole window) as "dirty" so it
will be repainted. Causes an EVT_PAINT event to be generated and sent
to the window.
Take the following three print cycles where an EVT_PAINT was fired after a SetPosition call (this is WITHOUT the GetParent().Refresh() call)
# first EVT_PAINT
Drawing
Panel Size (1440, 851)
Clipping Rect (0, 0, 1440, 851)
Client Update Rect (x=0, y=6, w=500, h=501) # the only place getting update is
# directly below the panel
# (that is (500, 500) )
# second
Drawing
Panel Size (1440, 851)
Clipping Rect (0, 0, 1440, 851)
Client Update Rect (x=0, y=6, w=910, h=845) # however this time the update area is
# bigger, this is also right before
# the move
# i believe what it is doing is
# drawing from (0,6) to (910, 851)
# why? because the panel is moving to
# (410, 390) and the bottom right
# corner of the panel (after moved)
# is (410+500, 390+461) = (910, 851)
# or about where the edge of the panel
# will be
# third
Drawing
Panel Size (1440, 851)
Clipping Rect (0, 0, 1440, 851)
Client Update Rect (x=410, y=390, w=500, h=461)
Here is the update code to play around with, hopefully this will help others.
import wx
instructions = """
How to use.
1) Hover your mouse over the pink panel.
2) Click down (left click)
3) While holding down drag mouse around
4) Release mouse button to stop.
"""
class AppPanel(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.settings_sizer = wx.BoxSizer(wx.HORIZONTAL)
p = MovablePanel(self, -1)
self.c = wx.CheckBox(self, -1, label = "Ghosting On?")
self.p = p
self.i = 0
self.settings_sizer.Add(self.c)
self.sizer.Add(self.settings_sizer)
self.sizer.Add(self.p)
self.SetSizer(self.sizer)
self.Layout()
self.Bind(wx.EVT_CHECKBOX, self.OnCheck, self.c)
self.Bind(wx.EVT_PAINT, self.OnPaint, self)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnErase, self)
def OnCheck(self, event):
print "CHECK\n\n\n\n\n"
v = self.c.GetValue()
self.p.r = v
print v
def OnErase(self, event):
pass
def OnPaint(self, event):
print "Drawing"
dc = wx.PaintDC(self)
print "Panel Rect, ", self.p.GetPosition(),
print self.p.GetSize()
print "Clipping Rect", dc.GetClippingBox()
print "Client Update Rect", self.GetUpdateClientRect()
print "----------------------------"
self.i = self.i+10
c = self.i % 255
c = (0, 0, c)
dc.SetPen(wx.Pen(c))
dc.SetBrush(wx.Brush(c))
dc.DrawRectangle(0, 0, 10000,10000)
self.SetBackgroundColour(c)
dc.SetPen(wx.Pen("WHITE"))
dc.SetBrush(wx.Brush("WHITE"))
dc.DrawRectangle(0, 0, self.GetSize()[0], self.c.GetSize()[1])
class MovablePanel(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
self.SetMinSize((300,300))
self.SetSize((300,300))
txt = wx.StaticText(self, -1, label = "CLICK AND DRAG ME!")
inst = wx.StaticText(self, -1, label = instructions)
font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)
txt.SetFont(font)
inst.SetFont(font)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(txt, flag = wx.ALIGN_CENTRE_VERTICAL | wx.ALIGN_CENTRE_HORIZONTAL)
sizer.Add(inst, flag = wx.ALIGN_CENTRE_VERTICAL | wx.ALIGN_CENTRE_HORIZONTAL)
self.SetSizer(sizer)
self.SetBackgroundColour("PINK")
self.LEFT_DOWN = False
self.r = False
self.Bind(wx.EVT_MOTION, self.OnMove, self)
self.Bind(wx.EVT_LEFT_DOWN,
self.OnClickDown,
self)
self.Bind(wx.EVT_LEFT_UP,
self.OnClickUp,
self)
def OnClickUp(self, event):
self.LEFT_DOWN = False
self.Refresh()
def OnClickDown(self, event):
self.LEFT_DOWN = True
self.Refresh()
def OnMove(self, event):
if self.LEFT_DOWN:
p = self.GetTopLevelParent().ScreenToClient(wx.GetMousePosition())
self.SetPosition(p)
if not self.r:
self.GetParent().Refresh()
if __name__ == "__main__":
app = wx.App(False)
f = wx.Frame(None, -1, size = (700, 700))
p = AppPanel(f, -1)
f.Show()
app.MainLoop()

wxpython - Erase background erases non-background components

In wxpython, I want to have a window with a picture that changes based on use of toolbar buttons with text controls on top of the picture. When I click the toolbar buttons, I am posting an erase background event, then capturing the erase event, and redrawing the new background from there (base on this).
Mostly works well, except that the text controls cease to be drawn once I redraw the background. They're still there, just not drawn.
Here is a simplified code that demonstrates the problem. If you run this code and click the button to toggle drawing the background image or not, the text controls disappear.:
import wx
import wx.lib.inspection
class PanelWithDrawing(wx.Panel):
def __init__(self, parent):
super(PanelWithDrawing, self).__init__(parent, size=(100, 40))
self.showbmp = False
self.txt = wx.TextCtrl(self, pos=(10, 10))
def onErase(self, dc):
if self.showbmp:
# dc.DrawBitmap(wx.Bitmap('background.png', 0, 0)
dc.DrawRectangle(0, 0, 40, 40) # use a drawing instead so you don't have to find a png
class Toolbar(wx.ToolBar):
def __init__(self, parent):
super(Toolbar, self).__init__(parent, -1)
self.AddLabelTool(wx.ID_SAVE, "Record", wx.Bitmap("picture.png", wx.BITMAP_TYPE_ANY), wx.NullBitmap, wx.ITEM_NORMAL, "", "")
class Example(wx.Frame):
def __init__(self, parent, title):
super(Example, self).__init__(parent, title=title)
self.toolbar = Toolbar(self)
self.SetToolBar(self.toolbar)
self.toolbar.Realize()
self.panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
self.panel1 = PanelWithDrawing(self.panel)
vbox.Add(self.panel1)
# self.panel2 = PanelWithText(self.panel)
# vbox.Add(self.panel2)
self.panel.SetSizer(vbox)
self.Centre()
self.Show()
self.toolbar.Bind(wx.EVT_TOOL, self.onButton)
self.panel1.Bind(wx.EVT_ERASE_BACKGROUND, self.onErase)
def onErase(self, evt):
try:
dc = evt.GetDC()
except:
dc = wx.ClientDC(self)
rect = self.GetUpdateRegion().GetBox()
dc.SetClippingRect(rect)
dc.Clear()
self.panel1.onErase(dc)
def onButton(self, evt):
self.panel1.showbmp = not self.panel1.showbmp
wx.PostEvent(self.panel1, wx.PyCommandEvent(wx.wxEVT_ERASE_BACKGROUND))
if __name__ == '__main__':
app = wx.App()
Example(None, title='Example')
wx.lib.inspection.InspectionTool().Show() # use this for debugging GUI design
app.MainLoop()
How do I tell wxpython to draw all the non-background stuff again? Alternatively, how do I not un-draw it in the first place?
After working on it for a few days, I got it! And the answer is trivially simple (as usual).
wx.PostEvent(self.panel1, wx.PyCommandEvent(wx.wxEVT_ERASE_BACKGROUND)) should be replaced with self.Refresh() to refresh the whole frame and not just force a specific (and apparently unsafe) redraw.

How can I add and remove controls in the page of a Notebook in wxPython?

Based on user interaction, I would like to dynamically add and remove controls to a panel in a wxPython notebook. The approach I've tried most thoroughly is to call .Clear() on the panel's sizer and add all new controls. However, on both Windows 7 and Linux desktops, rendering artifacts and stale controls remain visible under the new contents. How can I completely remove the old controls and add new controls without these artifacts?
Below is a sample program that reproduces the issue on Windows 7. Note the two different .update() methods of StaticPanel and DynamicPanel:
#!/usr/bin/python
import wx
import sys
class StaticPane(wx.Panel):
"""A panel that contains simple text that is updated
when the .update() method is called. The text is updated
using .SetText(), and the text control sticks around
between calls to .update()."""
def __init__(self, *args, **kwargs):
super(StaticPane, self).__init__(*args, **kwargs)
self._sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self._sizer)
self._counter = 0
self._base_text = "Some Text"
self._text = wx.TextCtrl(self, -1,
self._base_text + "!" * self._counter,
style=wx.TE_READONLY)
self._sizer.Add(self._text, -1, wx.EXPAND)
def update(self):
self._counter += 1
self._text.SetValue(self._base_text + "!" * self._counter)
class DynamicPane(wx.Panel):
"""A panel that contains simple text that is updated
when the .update() method is called. The text is updated
by removing the existing text control, and adding a new one
with the updated text string."""
def __init__(self, *args, **kwargs):
super(DynamicPane, self).__init__(*args, **kwargs)
self._sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self._sizer)
self._counter = 0
self._base_text = "Some Text"
self._text = wx.TextCtrl(self, -1,
self._base_text + "!" * self._counter,
style=wx.TE_READONLY)
self._sizer.Add(self._text, -1, wx.EXPAND)
def update(self):
self._counter += 1
self._sizer.Clear()
self._text = wx.TextCtrl(self, -1,
self._base_text + "!" * self._counter,
style=wx.TE_READONLY)
self._sizer.Add(self._text, -1, wx.EXPAND)
self.Layout()
class TestViewer(wx.Frame):
"""A Frame with a button and a notebook. When the button is pressed,
each of the two pages in the notebook recieve a call to .update().
"""
def __init__(self, parent):
super(TestViewer, self).__init__(parent, -1, "Test Viewer")
self.Bind(wx.EVT_CLOSE, self.OnClose)
self._panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
self._panel.SetSizer(vbox)
update_button = wx.Button(self._panel, wx.ID_CLOSE, "Update")
update_button.Bind(wx.EVT_BUTTON, self.update)
vbox.Add(update_button, 0, wx.EXPAND)
self._nb = wx.Notebook(self._panel)
self._view_one = StaticPane(self._nb, -1)
self._view_two = DynamicPane(self._nb, -1)
self._nb.AddPage(self._view_one, "One")
self._nb.AddPage(self._view_two, "Two")
vbox.Add(self._nb, 1, wx.EXPAND | wx.ALL)
self.Layout()
def update(self, e):
self._view_one.update()
self._view_two.update()
def OnClose(self, event):
sys.exit(0)
if __name__ == "__main__":
app = wx.App(False)
frame = TestViewer(None)
frame.Show()
app.MainLoop()
Click the button "Update" to update the text in the two panels. The first panel updates the text control by using .SetText(), while the second panel replaces the TextCtrl with a new one. Note that as you resize the window or mouseover the second panel after a few button clicks, there are overlapping controls and other artifacts.
Here are screenshots showing the stacked controls. Both images were taken after the same number of button clicks, they just show the two different panels at the same state. I expected the text to be exactly the same.
Simply clearing the sizer will only remove the references to its contents, not the widgets. When you create a widget, it will register with the parent window you supplied. Consider the following code:
class Frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,None)
self.textCtrl = wx.TextCtrl(self) # create with Frame as parent
self.textCtrl = None # has no effect on the TextCtrl
The parent window (Frame in this example) will take ownership of the TextCtrl and even if you set it to None, the Frame will keep it alive until it's is destroyed. To remove the TextCtrl, you have to destroy it explicitly:
self.textCtrl.Destroy()
If you want to remove all child widgets at once, you can use:
self.DestroyChildren()

Scrolling through a `wx.ScrolledPanel` with the mouse wheel and arrow keys

In my wxPython application I've created a wx.ScrolledPanel, in which there is a big wx.StaticBitmap that needs to be scrolled.
The scroll bars do appear and I can scroll with them, but I'd also like to be able to scroll with the mouse wheel and the arrow keys on the keyboard. It would be nice if the "Home", "Page Up", and those other keys would also function as expected.
How do I do this?
UPDATE:
I see the problem. The ScrolledPanel is able to scroll, but only when it is under focus. Problem is, how do I get to be under focus? Even clicking on it doesn't do it. Only if I put a text control inside of it I can focus on it and thus scroll with the wheel. But I don't want to have a text control in it. So how do I make it focus?
UPDATE 2:
Here is a code sample that shows this phenomena. Uncomment to see how a text control makes the mouse wheel work.
import wx, wx.lib.scrolledpanel
class MyFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
scrolled_panel = \
wx.lib.scrolledpanel.ScrolledPanel(parent=self, id=-1)
scrolled_panel.SetupScrolling()
text = "Ooga booga\n" * 50
static_text=wx.StaticText(scrolled_panel, -1, text)
sizer=wx.BoxSizer(wx.VERTICAL)
sizer.Add(static_text, wx.EXPAND, 0)
# Uncomment the following 2 lines to see how adding
# a text control to the scrolled panel makes the
# mouse wheel work.
#
#text_control=wx.TextCtrl(scrolled_panel, -1)
#sizer.Add(text_control, wx.EXPAND, 0)
scrolled_panel.SetSizer(sizer)
self.Show()
if __name__=="__main__":
app = wx.PySimpleApp()
my_frame=MyFrame(None, -1)
#import cProfile; cProfile.run("app.MainLoop()")
app.MainLoop()
Problem is on window Frame gets the focus and child panel is not getting the Focus (on ubuntu linux it is working fine). Workaround can be as simple as to redirect Frame focus event to set focus to panel e.g.
import wx, wx.lib.scrolledpanel
class MyFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.panel = scrolled_panel = \
wx.lib.scrolledpanel.ScrolledPanel(parent=self, id=-1)
scrolled_panel.SetupScrolling()
text = "Ooga booga\n" * 50
static_text=wx.StaticText(scrolled_panel, -1, text)
sizer=wx.BoxSizer(wx.VERTICAL)
sizer.Add(static_text, wx.EXPAND, 0)
scrolled_panel.SetSizer(sizer)
self.Show()
self.panel.SetFocus()
scrolled_panel.Bind(wx.EVT_SET_FOCUS, self.onFocus)
def onFocus(self, event):
self.panel.SetFocus()
if __name__=="__main__":
app = wx.PySimpleApp()
my_frame=MyFrame(None, -1)
app.MainLoop()
or onmouse move over panel, set focus to it, and all keys + mousewheeel will start working e.g.
import wx, wx.lib.scrolledpanel
class MyFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.panel = scrolled_panel = \
wx.lib.scrolledpanel.ScrolledPanel(parent=self, id=-1)
scrolled_panel.SetupScrolling()
scrolled_panel.Bind(wx.EVT_MOTION, self.onMouseMove)
text = "Ooga booga\n" * 50
static_text=wx.StaticText(scrolled_panel, -1, text)
sizer=wx.BoxSizer(wx.VERTICAL)
sizer.Add(static_text, wx.EXPAND, 0)
scrolled_panel.SetSizer(sizer)
self.Show()
def onMouseMove(self, event):
self.panel.SetFocus()
if __name__=="__main__":
app = wx.PySimpleApp()
my_frame=MyFrame(None, -1)
app.MainLoop()
Here's an example that should do what you want, I hope. (Edit: In retrospect, this doesnt' quite work, for example, when there are two scrolled panels... I'll leave it up here though so peole can downvote it or whatever.) Basically I put everything in a panel inside the frame (generally a good idea), and then set the focus to this main panel.
import wx
import wx, wx.lib.scrolledpanel
class MyFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
main_panel = wx.Panel(self, -1)
main_panel.SetBackgroundColour((150, 100, 100))
self.main_panel = main_panel
scrolled_panel = \
wx.lib.scrolledpanel.ScrolledPanel(parent=main_panel, id=-1)
scrolled_panel.SetupScrolling()
self.scrolled_panel = scrolled_panel
cpanel = wx.Panel(main_panel, -1)
cpanel.SetBackgroundColour((100, 150, 100))
b = wx.Button(cpanel, -1, size=(40,40))
self.Bind(wx.EVT_BUTTON, self.OnClick, b)
self.b = b
text = "Ooga booga\n" * 50
static_text=wx.StaticText(scrolled_panel, -1, text)
main_sizer=wx.BoxSizer(wx.VERTICAL)
main_sizer.Add(scrolled_panel, 1, wx.EXPAND)
main_sizer.Add(cpanel, 1, wx.EXPAND)
main_panel.SetSizer(main_sizer)
text_sizer=wx.BoxSizer(wx.VERTICAL)
text_sizer.Add(static_text, 1, wx.EXPAND)
scrolled_panel.SetSizer(text_sizer)
self.main_panel.SetFocus()
self.Show()
def OnClick(self, evt):
print "click"
if __name__=="__main__":
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, -1)
frame.Show(True)
self.SetTopWindow(frame)
return True
app = MyApp(0)
app.MainLoop()
For keyboard control, like setting action from the home key, I think you'll need to bind to those events, and respond appropriately, such as using mypanel.Scroll(0,0) for the home key (and remember to call evt.Skip() for the keyboard events you don't act on). (Edit: I don't think there are any default key bindings for scrolling. I'm not sure I'd want any either, for example, what should happen if there's a scrolled panel within a scrolled panel?)

Categories