I have a little app that I am working on with wxPython.
I have a scrolled window using wx.ScrolledWindow. It seems to refuse to repaint the contents when it is scrolled.
Example:
Code that created above example:
import wx
class SaveEdFrame(wx.Frame):
def __init__(self, *args, **kwds):
kwds["style"] = wx.DEFAULT_FRAME_STYLE|wx.EXPAND
wx.Frame.__init__(self, *args, **kwds)
self.__do_layout()
self.Bind(wx.EVT_SIZE, self.onSize)
def __mainSizer(self):
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
for key in xrange(30):
self.headerLabel = wx.StaticText(self, -1, "TestStr %s" % key)
self.mainSizer.Add(self.headerLabel)
return self.mainSizer
def __do_layout(self):
## begin wxGlade: SaveEdFrame.__do_layout
self.scroll = wx.ScrolledWindow(self, style=wx.FULL_REPAINT_ON_RESIZE)
self.scroll.SetScrollbars(1, 10, 1, 10)
self.scroll.SetSizer(self.__mainSizer())
def onSize(self, event):
self.scroll.SetSize(self.GetClientSize())
self.Refresh()
if __name__ == "__main__":
app = wx.App(0)
mainFrame = SaveEdFrame(None)
app.SetTopWindow(mainFrame)
mainFrame.Show(True)
app.MainLoop()
I've been digging through the wxDocs, and it seems to me that one solution would be to subclass wx.ScrolledWindow, manually catch wx.EVT_SCROLLWIN events, and then explicitly redraw the window, but my attempts to do that failed when calling self.Refresh() did not cause the interior of the wx.ScrolledWindow to repaint.
Anyways, it seems to me that the whole point of the wx.ScrolledWindow object is that it should handle repainting itself when scrolled.
What am I doing wrong?
Platform is W7-x64, python 2.7 32 bit, wxPython 2.8.11.0
I think the problem there is that your wx.StaticText widgets are children of the SaveEdFrame, not the ScrolledWindow. The ScrolledWindow is being redrawn over them as you scroll it. Try:
headerLabel = wx.StaticText(self.scroll, -1, "TestStr %s" % key)
self.mainSizer.Add(headerLabel)
Related
With the code snippet below, which produces something like this:
... I want to have the blue (subclassed Panel) widget turn red, every time the user left clicks with the mouse anywhere in the application frame window. As it is currently, it reacts only when the widget itself is clicked - but it doesn't react to, say, clicks on the button or elsewhere in the frame.
So my question is:
Is this widget-listening-to-"global"-clicks possible at all in wxPython?
If it is - how do I do it in the example below?
(Just changing the parent, e.g. self.parent.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseEvents) does not work at all; I also found wx.lib.evtmgr, but I simply cannot find the right syntax to set it up so it works).
The code:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# generated by wxGlade 0.6.3 on Mon Jan 12 21:20:22 2015
import wx
# begin wxGlade: extracode
# end wxGlade
class MyPanel(wx.Panel):
def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, name="MyPanel"):
super(MyPanel, self).__init__(parent, id, pos, size, style, name)
self.parent = parent
self.lmb = False
self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseEvents) # only reacts on its own clicks, not globally!
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnMouseEvents(self, event):
print("OnMouseEvents")
self.lmb = event.LeftDown() or event.LeftIsDown()
self.Refresh(False)
def OnPaint(self, event):
w, h = self.GetSize()
if self.lmb: mybrush = wx.Brush(wx.NamedColour("red"))
else: mybrush = wx.Brush(wx.NamedColour("blue"))
dc = wx.PaintDC(self)
dc.SetPen(wx.TRANSPARENT_PEN)
dc.SetBrush(mybrush)
dc.DrawRectangle(0, 0, w, h)
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
# begin wxGlade: MyFrame.__init__
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.button_1 = wx.Button(self, -1, "button_1")
self.panel_1 = MyPanel(self, -1)
self.__set_properties()
self.__do_layout()
# end wxGlade
def __set_properties(self):
# begin wxGlade: MyFrame.__set_properties
self.SetTitle("frame_1")
self.SetSize((200, 200))
# end wxGlade
def __do_layout(self):
# begin wxGlade: MyFrame.__do_layout
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_1.Add(self.button_1, 0, 0, 0)
sizer_1.Add(self.panel_1, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
self.Layout()
# end wxGlade
# end of class MyFrame
if __name__ == "__main__":
app = wx.PySimpleApp(0)
wx.InitAllImageHandlers()
frame_1 = MyFrame(None, -1, "")
app.SetTopWindow(frame_1)
frame_1.Show()
app.MainLoop()
Ok, I think I got this to work; note however that:
"wxMouseEvent does NOT propagate" wxWidgets Discussion Forum • View topic - Problem with mouse events
"Various sources say it is not possible (to catch mouse events in the actual wxFrame element) and wxPanels should be used to capture events" ; "Some mouse events go to the frame, like wxEVT_ENTER (or whatever it is called). Others go to the panel, e.g. wxEVT_RIGHT_UP. You can forward those events to the frame." ; "Mouse events are not propagated upwards the window hierarchy, so if your frame is entirely covered by other windows, then it doesn't get any mouse events in the first place and hence you can't catch them there." Is it possible to catch mouse events in wxFrame
"whenever you have one child of a wxFrame, wxWidgets automatically assumes you want it to cover the entire area of the wxFrame. Because of this, there is no portion of the frame visible - hence none of the events make it to your handler." Not catching all mouse events with wxWidgets
All this considered, the solution is to:
recursively walk through all of the (child) widgets in the frame;
connect individually their mouse left down/up events to a single event handler;
Make sure that single event handler ends with event.Skip() so the event is propagated to the original button etc, so its original click handlers work too.
With that, now with the code below I get this:
Note that the area where the mouse is on the screenshot is "covered" by a sizer, but sizers do not handle clicks - so basically it is the frame itself that responds to the click there (turning the monitoring square red). So basically each widget on the surface of the frame window handles its own area; and whatever is not covered - even if there are sizers present in that area - is actually handled by the frame.
The code:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# generated by wxGlade 0.6.3 on Mon Jan 12 21:20:22 2015
import wx
# begin wxGlade: extracode
# end wxGlade
class MyPanel(wx.Panel):
def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, name="MyPanel"):
super(MyPanel, self).__init__(parent, id, pos, size, style, name)
self.lmb = False
#self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseEvents) # only reacts on its own clicks, not globally! # use RecursiveSetListeners now; should be called from parent class after all init layout is done
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnMouseEvents(self, event):
print("OnMouseEvents")
self.lmb = event.LeftDown() or event.LeftIsDown()
self.Refresh(False)
event.Skip() # propagate, in case button is clicked
def OnPaint(self, event):
w, h = self.GetSize()
if self.lmb: mybrush = wx.Brush(wx.NamedColour("red"))
else: mybrush = wx.Brush(wx.NamedColour("blue"))
dc = wx.PaintDC(self)
dc.SetPen(wx.TRANSPARENT_PEN)
dc.SetBrush(mybrush)
dc.DrawRectangle(0, 0, w, h)
def RecursiveSetListeners(self, inwidget):
print("setting listeners: " + inwidget.GetName())
inwidget.Connect(wx.ID_ANY, -1, wx.wxEVT_LEFT_DOWN, self.OnMouseEvents)
inwidget.Connect(wx.ID_ANY, -1, wx.wxEVT_LEFT_UP, self.OnMouseEvents)
for child in inwidget.GetChildren():
self.RecursiveSetListeners(child)
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
# begin wxGlade: MyFrame.__init__
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.button_1 = wx.Button(self, -1, "button_1", name="button_1")
self.panel_1 = MyPanel(self, -1, name="panel_1")
self.button_2 = wx.Button(self.panel_1, -1, "button_2", name="button_1")
#self.Connect(wx.ID_ANY, -1, wx.EVT_MOUSE_EVENTS, self.panel_1.OnMouseEvents) # 'wx.wxEVT_MOUSE_EVENTS: AttributeError: 'module' has no attribute 'wxEVT_MOUSE_EVENTS';; wx.EVT_MOUSE_EVENTS: TypeError: in method 'EvtHandler_Connect', expected argument 4 of type 'int'
# these work:
#self.Connect(wx.ID_ANY, -1, wx.wxEVT_LEFT_DOWN, self.panel_1.OnMouseEvents)
#self.Connect(wx.ID_ANY, -1, wx.wxEVT_LEFT_UP, self.panel_1.OnMouseEvents)
#self.button_1.Connect(wx.ID_ANY, -1, wx.wxEVT_LEFT_DOWN, self.panel_1.OnMouseEvents)
#self.button_1.Connect(wx.ID_ANY, -1, wx.wxEVT_LEFT_UP, self.panel_1.OnMouseEvents)
self.__set_properties()
self.__do_layout()
print(self.GetChildren())
self.panel_1.RecursiveSetListeners(self)
# end wxGlade
def __set_properties(self):
# begin wxGlade: MyFrame.__set_properties
self.SetTitle("frame_1")
self.SetSize((200, 200))
# end wxGlade
def __do_layout(self):
# begin wxGlade: MyFrame.__do_layout
sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
sizer_2.Add(self.button_2, 0,0,0)
self.panel_1.SetSizer(sizer_2)
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_1.Add(self.button_1, 0, 0, 0)
sizer_1.Add(self.panel_1, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
self.Layout()
# end wxGlade
# end of class MyFrame
if __name__ == "__main__":
app = wx.PySimpleApp(0)
wx.InitAllImageHandlers()
frame_1 = MyFrame(None, -1, "")
app.SetTopWindow(frame_1)
frame_1.Show()
app.MainLoop()
The following code makes a window with a grey gradient bar.
import wx
class GradientFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, -1, title)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_ERASE_BACKGROUND, lambda event: None)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Center()
self.Show()
def OnSize(self, event):
event.Skip()
self.Refresh()
def OnPaint(self, event):
dc = wx.BufferedPaintDC(self)
rect = self.GetClientRect()
dc.SetBackground(wx.Brush("white"))
dc.Clear()
rect.SetHeight(30)
dc.GradientFillLinear(rect, '#fbfbfb', '#efefef', wx.SOUTH)
rect.SetTop(30)
rect.SetHeight(2)
dc.GradientFillLinear(rect, '#dbdbdb', '#c1c1c1', wx.SOUTH)
app = wx.App(0)
frame = GradientFrame(None, 'Test')
app.MainLoop()
I would like to add toogle buttons like the following screenshot, that allows to access to different pages / panels of the GUI (each of them containing their own widgets, etc.)
What is the good framework for that : should these buttons be created manually in OnPaint (this would be very 'low-level') or somewhere else? Is there a ready-to-use way to use buttons linked to different pages ?
There is no good framework for creating custom widgets. However, there are some good recipes out there:
https://stackoverflow.com/questions/1351448/how-to-make-custom-buttons-in-wx
http://wiki.wxpython.org/CreatingCustomControls
Those two links should get you started. You can also take a look at the source for GenericButtons, AquaButton or PlateButton for additional ideas.
Alternatively, you could also just create a panel that's a specific size and put some of the custom buttons or just regular buttons on it instead.
Here's an example of how to use PlateButtons that should get you started:
import wx
import wx.lib.platebtn as platebtn
class GradientFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, -1, title)
self.panel = wx.Panel(self)
self.panel.Bind(wx.EVT_PAINT, self.OnPaint)
self.panel.Bind(wx.EVT_ERASE_BACKGROUND, lambda event: None)
self.panel.Bind(wx.EVT_SIZE, self.OnSize)
# add plate buttons
top_sizer = wx.BoxSizer(wx.VERTICAL)
btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
labels = ["Morceaux", "Albums", "Artistes", "Genres"]
style = platebtn.PB_STYLE_GRADIENT
for label in labels:
btn = platebtn.PlateButton(self.panel, label=label, style=style)
btn.SetPressColor(wx.Colour(208,208,208))
btn_sizer.Add(btn, 0, wx.RIGHT|wx.LEFT|wx.CENTER, 5)
top_sizer.Add(btn_sizer, 0, wx.ALL|wx.CENTER, 5)
top_sizer.Add((1,1), 1, wx.EXPAND)
self.panel.SetSizer(top_sizer)
self.Center()
self.Show()
def OnSize(self, event):
event.Skip()
self.Refresh()
def OnPaint(self, event):
dc = wx.BufferedPaintDC(self.panel)
rect = self.panel.GetClientRect()
dc.SetBackground(wx.Brush("white"))
dc.Clear()
rect.SetHeight(30)
dc.GradientFillLinear(rect, '#fbfbfb', '#efefef', wx.SOUTH)
rect.SetTop(30)
rect.SetHeight(2)
dc.GradientFillLinear(rect, '#dbdbdb', '#c1c1c1', wx.SOUTH)
app = wx.App(0)
frame = GradientFrame(None, 'Test')
app.MainLoop()
I've got a wxPython program that uses a ScrolledPanel and it's doing something really strange on some resizes. If you drag the resize around a bit, one of the buttons will start looking messed up.
Before:
http://s7.postimage.org/stoc27517/Good_Window.png
After:
http://s9.postimage.org/s2apf38m7/Bad_Window.png
Here's the code:
import wx
import wx.lib.scrolledpanel as scrolled
class TestApp(wx.Frame):
def __init__(self, *args, **kwargs):
super(TestApp,self).__init__(*args,**kwargs)
self.setupGUI()
def setupGUI(self):
#Fires on window resize
self.Bind(wx.EVT_SIZE, self.OnSize)
self.box = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self.box)
self.scrolling_window = scrolled.ScrolledPanel( self )
self.scrolling_window.SetAutoLayout(1)
self.scrolling_window.SetupScrolling()
self.gb = wx.GridBagSizer(5,5)
self.scrolling_window.SetSizer(self.gb)
self.btnOne = wx.Button(self.scrolling_window,label='One')
self.btnTwo = wx.Button(self.scrolling_window,label='Two')
self.gb.Add(self.btnOne, pos=(0,0), flag=wx.EXPAND)
self.gb.Add(self.btnTwo, pos=(1,1), flag=wx.ALIGN_BOTTOM)
self.gb.AddGrowableCol(0)
self.gb.AddGrowableRow(1)
self.box.Add(self.scrolling_window,1,wx.EXPAND)
self.Show(True)
def OnSize(self, e):
#This refresh shouldn't be necessary
#self.Refresh()
#Pass event up the chain so window still resizes
e.Skip()
if __name__ == '__main__':
app = wx.App(redirect=False)
TestApp(None)
app.MainLoop()
The only way I could prevent this behavior was to uncomment the self.Refresh() in the OnSize method. This would essentially force a repaint on every window resize. But I don't think this should be necessary. Is this a bug, or am I doing something wrong?
Hi, everyone.
I have a little python GUI program based on wxPython 2.8.x.
It create a wx.Dialog widget and show the dialog by calling ShowModal().
At some situation, the wx.Dialog will show a wx.PopupWindow with a wx.ListCtrl locate inside.
The wx.PopupWindow appears correctly, but trouble comes.
wx.ListCtrl, and its parent widget, wx.PopupWindow, couldn't receive any mouse event,
this cause wx.ListCtrl and wx.PopupWindow have no response while user generate any mouse action.
If wx.Dialog is opened by calling Show(),the above situation won't happen, wx.PopupWindow and wx.ListCtrl work correctly.
However, the above situation won't happen in windows version of wxPython 2.8.x even if we show wx.Dialog by calling ShowModal(),it happen in linux only.
Any suggestion?
Thanks.
Here is my source code.
It may be too long but is easy enough to test the above situation (just copy and paste).
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import wx
class TestPopupWindow(wx.PopupWindow): # modify here to change different Widget
def __init__(self, *args, **kwargs):
super(TestPopupWindow, self).__init__(*args, **kwargs)
self.SetSize((200, 200))
self.testButton = wx.Button(self, label='Test')
self.testButton.Bind(wx.EVT_BUTTON, self.__onEvtButton, self.testButton)
self.Show()
def __onEvtButton(self, event):
event.Skip()
print 'PopupWindow: test button pushed!'
self.Hide()
class TestDialog(wx.Dialog):
def __init__(self, *args, **kwargs):
wx.Dialog.__init__(self, *args, **kwargs)
panel = wx.Panel(self)
panel.SetAutoLayout(True)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Fit(panel)
sizer.SetSizeHints(panel)
panel.SetSizer(sizer)
for i in xrange(2):
ctrl = wx.TextCtrl(panel)
sizer.Add(ctrl, 0, wx.ADJUST_MINSIZE|wx.EXPAND, 0)
for i in xrange(2):
ctrl = wx.ComboBox(panel)
sizer.Add(ctrl, 0, wx.ADJUST_MINSIZE|wx.EXPAND, 0)
self.openPopupWindowButton = wx.Button(panel, label='Open PopupWindow')
sizer.Add(self.openPopupWindowButton, 0, 0, 0)
standardDialogButtonSizer = wx.StdDialogButtonSizer()
self.standardDialogButtonSizerOK = wx.Button(panel, wx.ID_OK)
standardDialogButtonSizer.AddButton(self.standardDialogButtonSizerOK)
self.standardDialogButtonSizerCancel = wx.Button(panel, wx.ID_CANCEL)
standardDialogButtonSizer.AddButton(self.standardDialogButtonSizerCancel)
standardDialogButtonSizer.Realize()
sizer.Add(standardDialogButtonSizer, 0, wx.EXPAND, 0)
panel.Layout()
# event binding
self.openPopupWindowButton.Bind(wx.EVT_BUTTON, self.__onEvtButtonOpenPopupWindow, self.openPopupWindowButton)
self.popupWindow = None
# event handler
def __onEvtButtonOpenPopupWindow(self, event):
event.Skip()
if self.popupWindow is not None:
self.popupWindow.Close()
self.popupWindow = TestPopupWindow(self)
class TestFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
panel = wx.Panel(self)
panel.SetAutoLayout(True)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Fit(panel)
sizer.SetSizeHints(panel)
panel.SetSizer(sizer)
self.openButton = wx.Button(panel, label='Open Dialog')
sizer.Add(self.openButton, 0, 0, 0)
self.openPopupWindowButton = wx.Button(panel, label='Open PopupWindow')
sizer.Add(self.openPopupWindowButton, 0, 0, 0)
panel.Layout()
# event binding
self.openButton.Bind(wx.EVT_BUTTON, self.__onEvtButtonOpen, self.openButton)
self.openPopupWindowButton.Bind(wx.EVT_BUTTON, self.__onEvtButtonOpenPopupWindow, self.openPopupWindowButton)
self.Show()
self.popupWindow = None
# event handler
def __onEvtButtonOpenPopupWindow(self, event):
event.Skip()
if self.popupWindow is not None:
self.popupWindow.Close()
self.popupWindow = TestPopupWindow(self)
def __onEvtButtonOpen(self, event):
event.Skip()
dialog = TestDialog(self, title='TestDialog')
result = dialog.ShowModal()
if result == wx.ID_OK:
print >>sys.stderr, 'OK!'
else:
print >>sys.stderr, 'Cancel!'
def main(argv=sys.argv[:]):
app = wx.PySimpleApp()
frame = TestFrame(None, title='TestFrame', style=wx.TAB_TRAVERSAL|wx.DEFAULT_FRAME_STYLE)
app.SetTopWindow(frame)
app.MainLoop()
return 0
if __name__ == '__main__':
sys.exit(main())
Instead of using a PopupWindow, just show a second wx.Dialog. Or open a frame. PopupWindows can be kind of weird with complex widgets like the ListCtrl.
What event is used when I close a tab in an auinotebook? I tested with
EVT_AUINOTEBOOK_PAGE_CLOSE(D). It didn't work.
I would also like to fire a right click on the tab itself event.
Where can I find all the events that can be used with the aui manager/notebook? Might just be my poor searching skills, but I can't find any lists over the different events that exist, not for mouse/window events either. It would be really handy to have a complete list.
#!/usr/bin/python
#12_aui_notebook1.py
import wx
import wx.lib.inspection
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
self.nb = wx.aui.AuiNotebook(self)
self.new_panel('Page 1')
self.new_panel('Page 2')
self.new_panel('Page 3')
self.nb.Bind(wx.EVT_AUINOTEBOOK_PAGE_CLOSED, self.close)
def new_panel(self, nm):
pnl = wx.Panel(self)
pnl.identifierTag = nm
self.nb.AddPage(pnl, nm)
self.sizer = wx.BoxSizer()
self.sizer.Add(self.nb, 1, wx.EXPAND)
self.SetSizer(self.sizer)
def close(self, event):
print 'closed'
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, -1, '12_aui_notebook1.py')
frame.Show()
self.SetTopWindow(frame)
return 1
if __name__ == "__main__":
app = MyApp(0)
# wx.lib.inspection.InspectionTool().Show()
app.MainLoop()
Oerjan Pettersen
This is the bind command you want:
self.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CLOSED, self.close, self.nb)
To detect a right click on the tab (e.g. to show a custom context menu):
self.Bind(wx.aui.EVT_AUINOTEBOOK_TAB_RIGHT_DOWN, self.right, self.nb)
Here's a list of the aui notebook events:
EVT_AUINOTEBOOK_PAGE_CLOSE
EVT_AUINOTEBOOK_PAGE_CLOSED
EVT_AUINOTEBOOK_PAGE_CHANGED
EVT_AUINOTEBOOK_PAGE_CHANGING
EVT_AUINOTEBOOK_BUTTON
EVT_AUINOTEBOOK_BEGIN_DRAG
EVT_AUINOTEBOOK_END_DRAG
EVT_AUINOTEBOOK_DRAG_MOTION
EVT_AUINOTEBOOK_ALLOW_DND
EVT_AUINOTEBOOK_DRAG_DONE
EVT_AUINOTEBOOK_BG_DCLICK
EVT_AUINOTEBOOK_TAB_MIDDLE_DOWN
EVT_AUINOTEBOOK_TAB_MIDDLE_UP
EVT_AUINOTEBOOK_TAB_RIGHT_DOWN
EVT_AUINOTEBOOK_TAB_RIGHT_UP
From: {python folder}/Lib/site-packages/{wxpython folder}/wx/aui.py