Side effects of handling EVT_PAINT event in wxPython
Whenever I handle EVT_PAINT event by binding it to a function some problem occures the frame/window opens up but it cannot be closed when I click on the close option on the top-right corner of the frame. Can any one tell me what can be the reason for this ??? Apart form that there is a lot of flicker that occures.
The purpose of the code is somewhat like this. I have a Main Frame class which holds a panel. In that panel I have a wxPython Notebook. There are two pages in the notebook. Each page has same structure having two panels, image panel and control panel. Image panel displays image and control panel has a file open button that opens the file to be displayed in the image panel. By default image panel shows a image called "default.png".
Just create some png image in the same folder as that of the program and name it as default.png so that the program works.
Here is the sample code.
import os
import wx
#===========================================================================
# This is how you pre-establish a file filter so that the dialog
# only shows the extension(s) you want it to.
wildcard = "Python source (*.py)|*.py|" \
"Compiled Python (*.pyc)|*.pyc|" \
"SPAM files (*.spam)|*.spam|" \
"Egg file (*.egg)|*.egg|" \
"All files (*.*)|*.*"
#===========================================================================
#========================================================================
# Set to 1 when new image is uploaded.
imageChangeFlag = [0];
#========================================================================
#========================================================================
# Set to 1 when new image is uploaded.
pageChangeFlag = [0];
#========================================================================
# Some classes to use for the notebook pages. Obviously you would
# want to use something more meaningful for your application, these
# are just for illustration.
class ImagePanel(wx.Panel):
'''
Create an image panel.
Sets all the controls of image panel
'''
def __init__(self,parent):
wx.Panel.__init__(self,parent,id=-1,
name="Image Panel",
style=wx.BORDER_THEME)
self.SetBackgroundColour(wx.WHITE)
class ControlPanel(wx.Panel):
'''
Create a control panel.
Sets all the controls of the control panel
'''
def __init__(self,parent):
wx.Panel.__init__(self,parent,id=-1,
name="Control Panel",
style=wx.BORDER_THEME)
self.SetBackgroundColour(wx.Colour(235,234,211))
class PageOne(wx.Panel):
'''
This panel is the first page of the notebook and sets all the widgets in the first page.
'''
def __init__(self, parent, id):
'''
Constructor for page 1 initializes the first page of the notebook
'''
wx.Panel.__init__(self, parent, id, name="Network Visualization")
#t = wx.StaticText(self, -1, "This is a PageOne object", (20,20))
self.path = "default.png"
#====================================================================
# Set the Network Visualization Page.
#====================================================================
self.imagePanel = ImagePanel(self)
self.controlPanel = ControlPanel(self)
fileOpenButton = wx.Button(self.controlPanel, -1, "Browse", (30,30))
self.Bind(wx.EVT_BUTTON, self.onFileOpen, fileOpenButton)
controlSizer = wx.BoxSizer()
controlSizer.Add(fileOpenButton,1)
self.controlPanel.SetSizer(controlSizer)
box = wx.BoxSizer()
box.Add(self.imagePanel,3,wx.EXPAND)
box.Add(self.controlPanel,1,wx.EXPAND)
self.SetSizer(box)
self.loadImage(self.path)
def onFileOpen(self,event):
fileOpenDlg = wx.FileDialog(self.controlPanel,
message="Select a file",
defaultDir=os.getcwd(),
defaultFile="",
wildcard=wildcard,
style=wx.OPEN |wx.CHANGE_DIR)
# Show the dialog and retrieve the user response. If it is the OK response,
# process the data.
if fileOpenDlg.ShowModal() == wx.ID_OK:
image = fileOpenDlg.GetPath()
# Load the new image and set the imageChangeFlag to 1.
self.loadImage(image)
imageChangeFlag[0] = 1;
# Destroy the dialog. Don't do this until you are done with it!
# BAD things can happen otherwise!
fileOpenDlg.Destroy()
def loadImage(self,image):
'''
Initializes the image.
Finds the properties of the image like the aspect ratio and bitmap.
'''
self.png = wx.Image(image, wx.BITMAP_TYPE_ANY)
imageHeight = self.png.GetHeight()
imageWidth = self.png.GetWidth()
self.aspectRatio = imageWidth/imageHeight
self.bitmap = wx.BitmapFromImage(self.png)
def getImagePanel(self):
return self.imagePanel
def getControlPanel(self):
return self.controlPanel
def getImage(self):
return self.png
def getImageBitmap(self):
return self.bitmap
def getImageAspectRatio(self):
return self.aspectRatio
class PageTwo(wx.Panel):
def __init__(self, parent, id):
'''
Constructor for page 1 initializes the first page of the notebook
'''
wx.Panel.__init__(self, parent, id, name="Graph Visualization")
#t = wx.StaticText(self, -1, "This is a PageTwo object", (40,40))
self.path = "default.png"
#====================================================================
# Set the Network Visualization Page.
#====================================================================
self.imagePanel = ImagePanel(self)
self.controlPanel = ControlPanel(self)
fileOpenButton = wx.Button(self.controlPanel, -1, "Browse", (30,30))
self.Bind(wx.EVT_BUTTON, self.onFileOpen, fileOpenButton)
controlSizer = wx.BoxSizer()
controlSizer.Add(fileOpenButton,1)
self.controlPanel.SetSizer(controlSizer)
box = wx.BoxSizer()
box.Add(self.imagePanel,3,wx.EXPAND)
box.Add(self.controlPanel,1,wx.EXPAND)
self.SetSizer(box)
self.loadImage(self.path)
def onFileOpen(self,event):
fileOpenDlg = wx.FileDialog(self.controlPanel,
message="Select a file",
defaultDir=os.getcwd(),
defaultFile="",
wildcard=wildcard,
style=wx.OPEN |wx.CHANGE_DIR)
# Show the dialog and retrieve the user response. If it is the OK response,
# process the data.
if fileOpenDlg.ShowModal() == wx.ID_OK:
image = fileOpenDlg.GetPath()
# Load the new image and set the imageChangeFlag to 1.
self.loadImage(image)
imageChangeFlag[0] = 1;
# Destroy the dialog. Don't do this until you are done with it!
# BAD things can happen otherwise!
fileOpenDlg.Destroy()
def loadImage(self,image):
'''
Initializes the image.
Finds the properties of the image like the aspect ratio and bitmap.
'''
self.png = wx.Image(image, wx.BITMAP_TYPE_ANY)
imageHeight = self.png.GetHeight()
imageWidth = self.png.GetWidth()
self.aspectRatio = imageWidth/imageHeight
self.bitmap = wx.BitmapFromImage(self.png)
def getImagePanel(self):
return self.imagePanel
def getControlPanel(self):
return self.controlPanel
def getImage(self):
return self.png
def getImageBitmap(self):
return self.bitmap
def getImageAspectRatio(self):
return self.aspectRatio
class Notebook(wx.Notebook):
'''
Creates a Notebook.
'''
def __init__(self,parent):
wx.Notebook.__init__(self,parent,size=parent.GetSizeTuple())
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None,
title="Social Network Analysis",
size=(900,700))
#======================================================================
# Create a panel and a notebook on the panel
#======================================================================
self.p = wx.Panel(self,size=self.GetSizeTuple())
self.nb = Notebook(self.p)
#====================================================================
# Create the page windows as children of the notebook
#====================================================================
self.networkVisualizationPage = PageOne(self.nb,id=1)
self.graphVisualizationPage = PageTwo(self.nb,id=2)
#=======================================================================================
# Initialize the page id to 1.
# By default Network Visualization Page will be selected first.
# Get the image panel from networkVisualization page.
# Then get the image to be displayed on the image panel of networkVisualization page.
#=======================================================================================
self.pageId = 1
self.pageSelect()
self.imageRefresh()
#======================================================================
# Add the pages to the notebook with the label to show on the tab
#======================================================================
self.nb.AddPage(self.networkVisualizationPage, "Network Visualization")
self.nb.AddPage(self.graphVisualizationPage, "Graph Visualization")
#======================================================================
# Finally, put the notebook in a sizer for the panel to manage the layout
#======================================================================
sizer = wx.BoxSizer()
sizer.Add(self.nb, 1, wx.EXPAND)
self.p.SetSizer(sizer)
self.Bind(wx.EVT_PAINT, self.onPaint)
self.Bind(wx.EVT_SIZE, self.onResize)
self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.onPageChange)
self.Bind(wx.EVT_ERASE_BACKGROUND , self.erase)
def erase(self,event):
pass
def onPageChange(self,event):
'''
Handles the EVT_NOTEBOOK_PAGE_CHANGED event
'''
pageChangeFlag[0] = 1
self.pageId = self.nb.GetCurrentPage().GetId()
self.pageSelect()
self.imageRefresh()
#print "Page Change"
#print self.pageId
def onPaint(self,event):
'''
Handles EVT_PAINT event.
'''
if(imageChangeFlag[0] == 1 or pageChangeFlag[0] == 1):
imageChangeFlag[0] = 0
pageChangeFlag[0] = 0
self.imageRefresh()
(w, h) = self.getBestSize()
self.scaledPNG = self.png.Scale(w,h,quality=wx.IMAGE_QUALITY_HIGH)
self.bitmap = wx.BitmapFromImage(self.scaledPNG)
self.Refresh()
imagePanelDC = wx.PaintDC(self.imagePanel)
imagePanelDC.DrawBitmap(self.bitmap, 0, 0, useMask=False)
#controlPanelDC = wx.PaintDC(self.controlPanel)
imagePanelDC.Destroy()
def onResize(self,event):
'''
Handles EVT_SIZE event.
'''
self.p.SetSize(self.GetSizeTuple())
(w, h) = self.getBestSize()
self.imageRefresh()
self.scaledPNG = self.png.Scale(w,h,quality=wx.IMAGE_QUALITY_HIGH)
self.bitmap = wx.BitmapFromImage(self.scaledPNG)
self.Refresh()
def imageRefresh(self):
#======================================================================
# Initialize the image parameters
#======================================================================
if(self.pageId == 1):
self.png = self.networkVisualizationPage.getImage()
self.bitmap = self.networkVisualizationPage.getImageBitmap()
self.aspectRatio = self.networkVisualizationPage.getImageAspectRatio()
elif(self.pageId == 2):
self.png = self.graphVisualizationPage.getImage()
self.bitmap = self.graphVisualizationPage.getImageBitmap()
self.aspectRatio = self.graphVisualizationPage.getImageAspectRatio()
def pageSelect(self):
#========================================================================
# Selects the image panel and control panel of appropriate page
#========================================================================
if(self.pageId == 1):
self.imagePanel = self.networkVisualizationPage.getImagePanel()
self.controlPanel = self.networkVisualizationPage.getControlPanel()
elif(self.pageId == 2):
self.imagePanel = self.graphVisualizationPage.getImagePanel()
self.controlPanel = self.graphVisualizationPage.getControlPanel()
def getBestSize(self):
'''
Returns the best size the image can have based on the aspect ratio of the image.
'''
(w,h) = self.imagePanel.GetSizeTuple()
#print "Image Panel Size = "
#print (w,h)
reductionFactor = 0.1
# Reduce the height by 20 units and change width of the image according to aspect ratio
newHeight = int(h - (h * reductionFactor))
newWidth = int (self.aspectRatio * newHeight)
newSize = (newWidth,newHeight)
#print "Image Size = "
#print newSize
return newSize
if __name__ == "__main__":
app = wx.App()
MainFrame().Show()
app.MainLoop()
I found why the problem was occuring. I was binding to paint event at wrong place i.e. in the outer frame. Now if I do the binding of the paint event in the inner image panel where the image is dispalyed it works fine. Thanks anyways...
But it seems like I am facing another problem. When I click the button "Browse" in my control panel and select a different image file to be displayed, the paint event is not called after this action. So the old image stays on the screen. But when I resize the window it paint event is called and new image is displayed.
Why is this happening ??? Is it because the paint event is called only for the first time and then after resizing?? In that case how can I call the paint event in the middle when I need...
Code is given below.
Damodar
import wx
import os
#===========================================================================
# This is how you pre-establish a file filter so that the dialog
# only shows the extension(s) you want it to.
wildcard = "Python source (*.py)|*.py|" \
"Compiled Python (*.pyc)|*.pyc|" \
"SPAM files (*.spam)|*.spam|" \
"Egg file (*.egg)|*.egg|" \
"All files (*.*)|*.*"
#===========================================================================
#========================================================================
# Set to 1 when new image is uploaded.
imageChangeFlag = [0];
#========================================================================
#========================================================================
# Set to 1 when page is changed.
pageChangeFlag = [0];
#========================================================================
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None,
title="Social Network Analysis",
size=(400,400),
style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE)
class MainPanel(wx.Panel):
def __init__(self,parent=None):
wx.Panel.__init__(self,parent,size=parent.GetSizeTuple())
#==========================================================
# Parent frame of the main panel
#==========================================================
self.parent = parent
#==========================================================
# Display the .png image in the panel
#==========================================================
#image = "default.png"
#self.loadImage(image)
class Notebook(wx.Notebook):
'''
Creates a Notebook.
'''
def __init__(self,parent):
wx.Notebook.__init__(self,parent,size=parent.GetSizeTuple())
class NewPage(wx.Panel):
'''
This panel is the first page of the notebook and sets all the widgets in the first page.
'''
def __init__(self, parent, parentPanel, parentFrame, id, title):
'''
Constructor for page 1 initializes the first page of the notebook
'''
wx.Panel.__init__(self, parent, id, name=title)
#====================================================================
# Set the Network Visualization Page.
#====================================================================
self.imagePanel = ImagePanel(self,parent,parentPanel,parentFrame)
self.controlPanel = ControlPanel(self,self.imagePanel)
box = wx.BoxSizer()
box.Add(self.imagePanel,3,wx.EXPAND)
box.Add(self.controlPanel,1,wx.EXPAND)
self.SetSizer(box)
class ImagePanel(wx.Panel):
'''
Create an image panel.
Sets all the controls of image panel
'''
def __init__(self,parent=None,parentBook=None,parentPanel=None,parentFrame=None):
wx.Panel.__init__(self,parent,id=-1,
name="Image Panel",
style=wx.BORDER_THEME)
self.SetBackgroundColour(wx.WHITE)
self.parent = parent
self.parentBook = parentBook
self.parentPanel = parentPanel
self.parentFrame = parentFrame
self.path = "default.png"
self.loadImage(self.path)
self.Bind(wx.EVT_PAINT, self.onPaint)
self.Bind(wx.EVT_SIZE, self.onResize)
def loadImage(self,image):
'''
Initializes the image.
Finds the properties of the image like the aspect ratio and bitmap.
'''
self.png = wx.Image(image, wx.BITMAP_TYPE_ANY)
imageHeight = self.png.GetHeight()
imageWidth = self.png.GetWidth()
self.aspectRatio = imageWidth/imageHeight
self.bitmap = wx.BitmapFromImage(self.png)
def onPaint(self,event):
'''
Handles EVT_PAINT event.
'''
if(imageChangeFlag[0] == 1):
imageChangeFlag[0] = 0
(w, h) = self.getBestSize()
self.scaledPNG = self开发者_开发知识库.png.Scale(w,h,quality=wx.IMAGE_QUALITY_HIGH)
self.bitmap = wx.BitmapFromImage(self.scaledPNG)
self.Refresh()
imagePanelDC = wx.PaintDC(self)
imagePanelDC.DrawBitmap(self.bitmap, 0, 0, useMask=False)
#imagePanelDC.Destroy()
def onResize(self,event):
'''
Handles EVT_SIZE event.
'''
self.parentPanel.SetSize(self.parentFrame.GetSizeTuple())
(w, h) = self.getBestSize()
self.scaledPNG = self.png.Scale(w,h,quality=wx.IMAGE_QUALITY_HIGH)
self.bitmap = wx.BitmapFromImage(self.scaledPNG)
self.Refresh()
def getBestSize(self):
'''
Returns the best size the image can have based on the aspect ratio of the image.
'''
(w,h) = self.GetSizeTuple()
#print "Image Panel Size = "
#print (w,h)
reductionFactor = 0.1
# Reduce the height by 20 units and change width of the image according to aspect ratio
newHeight = int(h - (h * reductionFactor))
newWidth = int (self.aspectRatio * newHeight)
newSize = (newWidth,newHeight)
#print "Image Size = "
#print newSize
return newSize
class ControlPanel(wx.Panel):
'''
Create a control panel.
Sets all the controls of the control panel
'''
def __init__(self,parent,imagePanel):
wx.Panel.__init__(self,parent,id=-1,
name="Control Panel",
style=wx.BORDER_THEME)
self.SetBackgroundColour(wx.Colour(235,234,211))
self.imagePanel = imagePanel
fileOpenButton = wx.Button(self, -1, "Browse", (30,30))
self.Bind(wx.EVT_BUTTON, self.onFileOpen, fileOpenButton)
controlSizer = wx.BoxSizer()
controlSizer.Add(fileOpenButton,1)
self.SetSizer(controlSizer)
def onFileOpen(self,event):
fileOpenDlg = wx.FileDialog(self,
message="Select a file",
defaultDir=os.getcwd(),
defaultFile="",
wildcard=wildcard,
style=wx.OPEN |wx.CHANGE_DIR)
# Show the dialog and retrieve the user response. If it is the OK response,
# process the data.
if fileOpenDlg.ShowModal() == wx.ID_OK:
image = fileOpenDlg.GetPath()
# Load the new image and set the imageChangeFlag to 1.
self.imagePanel.loadImage(image)
imageChangeFlag[0] = 1;
# Destroy the dialog. Don't do this until you are done with it!
# BAD things can happen otherwise!
fileOpenDlg.Destroy()
app = wx.PySimpleApp()
# Create Main Frame
frame = MainFrame()
# Create Main Panel inside Main Frame
panel = MainPanel(frame)
# Create a notebook inside the Main Panel
nb = Notebook(panel)
# Create the page windows as children of the notebook
networkVisualizationPage = NewPage(nb,panel,frame,id=1,title="Network Visualization")
graphVisualizationPage = NewPage(nb,panel,frame,id=2,title="Graph Visualization")
# Add the pages to the notebook with the label to show on the tab
nb.AddPage(networkVisualizationPage, "Network Visualization")
nb.AddPage(graphVisualizationPage, "Graph Visualization")
# Finally, put the notebook in a sizer for the panel to manage the layout
sizer = wx.BoxSizer()
sizer.Add(nb, 1, wx.EXPAND)
panel.SetSizer(sizer)
frame.Show(1)
app.MainLoop()
Just for the reference of anybody looking at this in the future as the answers so far don't clear explain that the problem is due to not creating wxPaintDC
for the window you handle EVT_PAINT
for. This must be done in current wx versions to avoid accumulation of an endless stream of WM_PAINT
messages under Windows.
FWIW the upcoming 2.9.1 release will handle this more gracefully and just warn you, via a debug message, about the problem in your code but will still validate the window to prevent the system from continuing to send you more WM_PAINT
s.
I found an answer to this one as well. I had to refresh the panel where the image was supposed to be displayed. self.imagePanel.refresh() would call the paint event.
Damodar
I've had that happen when hooking EVT_CLOSE, and I had to call event.Skip(1)
to get it to process the event normally. Maybe a similar side-effect is going on with the paint event.
精彩评论