Learning Python: RuntimeError: wrapped C/C++ object of type StaticBitmap has been deleted
Learning Python: RuntimeError: wrapped C/C++ object of type StaticBitmap has been deleted
我目前正在学习使用 Python 进行编程,我得到了一个简单图像查看器的示例代码(见下文),当我关闭应用程序时它会产生以下错误消息,运行 再次打开任意目录浏览其中的图片:
File "...\image_viewer4.py", line 103, in update_photo
self.image_ctrl.SetBitmap(wx.Bitmap(img))
RuntimeError: wrapped C/C++ object of type StaticBitmap has been
deleted.
除非我在 Spyder 中重新启动内核,否则每次都会发生这种情况。
我已经通读了PyQt: RuntimeError: wrapped C/C++ object has been deleted, Understanding the “underlying C/C++ object has been deleted” error, and Can a PyQt4 QObject be queried to determine if the underlying C++ instance has been destroyed?,但我不明白是什么导致了上面代码示例中的错误。我的意思是,错误指出位图对象已被删除,但我不知道如何或为何删除。
我感谢任何人能给我的任何富有成效的帮助。我想了解究竟是什么导致了代码中的错误以及如何避免它。
代码如下:
import glob
import os
import wx
from pubsub import pub
class ImagePanel(wx.Panel):
def __init__(self, parent):
super().__init__(parent)
disW, disH = wx.DisplaySize()
self.max_size = 800
self.photos = []
self.current_photo = 0
self.total_photos = 0
self.layout()
pub.subscribe(self.update_photos_via_pubsub, "update")
def layout(self):
"""
Layout the widgets on the panel
"""
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
img = wx.Image(self.max_size, self.max_size)
self.image_ctrl = wx.StaticBitmap(self, wx.ID_ANY,
wx.Bitmap(img))
self.main_sizer.Add(self.image_ctrl, 0, wx.ALL|wx.CENTER, 5)
self.image_label = wx.StaticText(self, label="")
self.main_sizer.Add(self.image_label, 0, wx.ALL|wx.CENTER, 5)
btn_data = [("Previous", btn_sizer, self.on_previous),
("Next", btn_sizer, self.on_next)]
for data in btn_data:
label, sizer, handler = data
self.btn_builder(label, sizer, handler)
self.main_sizer.Add(btn_sizer, 0, wx.CENTER)
self.SetSizer(self.main_sizer)
def btn_builder(self, label, sizer, handler):
"""
Builds a button, binds it to an event handler and adds it to a sizer
"""
btn = wx.Button(self, label=label)
btn.Bind(wx.EVT_BUTTON, handler)
sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
def on_next(self, event):
"""
Loads the next picture in the directory
"""
if not self.photos:
return
if self.current_photo == self.total_photos - 1:
self.current_photo = 0
else:
self.current_photo += 1
self.update_photo(self.photos[self.current_photo])
def on_previous(self, event):
"""
Displays the previous picture in the directory
"""
if not self.photos:
return
if self.current_photo == 0:
self.current_photo = self.total_photos - 1
else:
self.current_photo -= 1
self.update_photo(self.photos[self.current_photo])
def update_photos_via_pubsub(self, photos):
self.photos = photos
self.total_photos = len(self.photos)
self.update_photo(self.photos[0])
self.current_photo = 0
def update_photo(self, image):
"""
Update the currently shown photo
"""
img = wx.Image(image, wx.BITMAP_TYPE_ANY)
# scale the image, preserving the aspect ratio
W = img.GetWidth()
H = img.GetHeight()
if W > H:
NewW = self.max_size
NewH = self.max_size * H / W
else:
NewH = self.max_size
NewW = self.max_size * W / H
img = img.Scale(NewW, NewH)
self.image_ctrl.SetBitmap(wx.Bitmap(img))
self.Refresh()
def reset(self):
img = wx.Image(self.max_size,
self.max_size)
bmp = wx.Bitmap(img)
self.image_ctrl.SetBitmap(bmp)
self.current_photo = 0
self.photos = []
class MainFrame(wx.Frame):
def __init__(self):
disW, disH = wx.DisplaySize()
super().__init__(None, title='Image Viewer',
size=(disW-500, disH-100))
self.panel = ImagePanel(self)
# create status bar
self.CreateStatusBar()
# create the menu bar
menuBar = wx.MenuBar()
# create a menu
fileMenu = wx.Menu()
openBut = wx.MenuItem(fileMenu, wx.ID_OPEN, '&Open', 'Open Working Directory')
fileMenu.Append(openBut)
fileMenu.Bind(wx.EVT_MENU, self.on_open_directory, openBut)
# add a line separator to the file menu
fileMenu.AppendSeparator()
# add a quit button to fileMenu
quitBut = wx.MenuItem(fileMenu, wx.ID_EXIT, '&Quit', 'Exit the Programm')
fileMenu.Append(quitBut)
# connect the quit button to the actual event of quitting the app
fileMenu.Bind(wx.EVT_MENU, self.onQuit, quitBut)
# give the menu a title
menuBar.Append(fileMenu, "&File")
# connect the menu bar to the frame
self.SetMenuBar(menuBar)
self.create_toolbar()
self.Show()
def create_toolbar(self):
"""
Create a toolbar
"""
self.toolbar = self.CreateToolBar()
self.toolbar.SetToolBitmapSize((16,16))
open_ico = wx.ArtProvider.GetBitmap(
wx.ART_FILE_OPEN, wx.ART_TOOLBAR, (16,16))
openTool = self.toolbar.AddTool(
wx.ID_ANY, "Open", open_ico, "Open an Image Directory")
self.Bind(wx.EVT_MENU, self.on_open_directory, openTool)
self.toolbar.Realize()
def on_open_directory(self, event):
"""
Open a directory dialog
"""
with wx.DirDialog(self, "Choose a directory",
style=wx.DD_DEFAULT_STYLE) as dlg:
if dlg.ShowModal() == wx.ID_OK:
self.folder_path = dlg.GetPath()
photos = glob.glob(os.path.join(self.folder_path, '*.jpg'))
if photos:
pub.sendMessage("update", photos=photos)
else:
self.panel.reset()
def onQuit(self, event):
# get the frame's top level parent and close it
wx.GetTopLevelParent(self).Destroy()
if __name__ == '__main__':
app = wx.App(redirect=False)
frame = MainFrame()
app.MainLoop()
如果需要,这里是追溯:
File "...\image_viewer4.py", line 183, in on_open_directory
pub.sendMessage("update", photos=photos)
File "C:\Anaconda\lib\site-packages\pubsub\core\publisher.py", line 216, in sendMessage
topicObj.publish(**msgData)
File "C:\Anaconda\lib\site-packages\pubsub\core\topicobj.py", line 452, in publish
self.__sendMessage(msgData, topicObj, msgDataSubset)
File "C:\Anaconda\lib\site-packages\pubsub\core\topicobj.py", line 482, in __sendMessage
listener(data, self, allData)
File "C:\Anaconda\lib\site-packages\pubsub\core\listener.py", line 237, in __call__
cb(**kwargs)
File "...\image_viewer4.py", line 84, in update_photos_via_pubsub
self.update_photo(self.photos[0])
虽然不是很明显,但我注意到您没有取消 pubsub
订阅。
尝试:
def onQuit(self, event):
# get the frame's top level parent and close it
pub.unsubscribe(self.panel.update_photos_via_pubsub, "update")
wx.GetTopLevelParent(self).Destroy()
看看这是否有所作为。
为了确保在使用框架右上角的 X
关闭应用程序时也执行 onQuit
,请使用以下内容(在 MainFrame
中):
self.Bind(wx.EVT_CLOSE, self.onQuit)
这应该涵盖所有基地。
我目前正在学习使用 Python 进行编程,我得到了一个简单图像查看器的示例代码(见下文),当我关闭应用程序时它会产生以下错误消息,运行 再次打开任意目录浏览其中的图片:
File "...\image_viewer4.py", line 103, in update_photo self.image_ctrl.SetBitmap(wx.Bitmap(img)) RuntimeError: wrapped C/C++ object of type StaticBitmap has been deleted.
除非我在 Spyder 中重新启动内核,否则每次都会发生这种情况。 我已经通读了PyQt: RuntimeError: wrapped C/C++ object has been deleted, Understanding the “underlying C/C++ object has been deleted” error, and Can a PyQt4 QObject be queried to determine if the underlying C++ instance has been destroyed?,但我不明白是什么导致了上面代码示例中的错误。我的意思是,错误指出位图对象已被删除,但我不知道如何或为何删除。
我感谢任何人能给我的任何富有成效的帮助。我想了解究竟是什么导致了代码中的错误以及如何避免它。
代码如下:
import glob
import os
import wx
from pubsub import pub
class ImagePanel(wx.Panel):
def __init__(self, parent):
super().__init__(parent)
disW, disH = wx.DisplaySize()
self.max_size = 800
self.photos = []
self.current_photo = 0
self.total_photos = 0
self.layout()
pub.subscribe(self.update_photos_via_pubsub, "update")
def layout(self):
"""
Layout the widgets on the panel
"""
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
img = wx.Image(self.max_size, self.max_size)
self.image_ctrl = wx.StaticBitmap(self, wx.ID_ANY,
wx.Bitmap(img))
self.main_sizer.Add(self.image_ctrl, 0, wx.ALL|wx.CENTER, 5)
self.image_label = wx.StaticText(self, label="")
self.main_sizer.Add(self.image_label, 0, wx.ALL|wx.CENTER, 5)
btn_data = [("Previous", btn_sizer, self.on_previous),
("Next", btn_sizer, self.on_next)]
for data in btn_data:
label, sizer, handler = data
self.btn_builder(label, sizer, handler)
self.main_sizer.Add(btn_sizer, 0, wx.CENTER)
self.SetSizer(self.main_sizer)
def btn_builder(self, label, sizer, handler):
"""
Builds a button, binds it to an event handler and adds it to a sizer
"""
btn = wx.Button(self, label=label)
btn.Bind(wx.EVT_BUTTON, handler)
sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
def on_next(self, event):
"""
Loads the next picture in the directory
"""
if not self.photos:
return
if self.current_photo == self.total_photos - 1:
self.current_photo = 0
else:
self.current_photo += 1
self.update_photo(self.photos[self.current_photo])
def on_previous(self, event):
"""
Displays the previous picture in the directory
"""
if not self.photos:
return
if self.current_photo == 0:
self.current_photo = self.total_photos - 1
else:
self.current_photo -= 1
self.update_photo(self.photos[self.current_photo])
def update_photos_via_pubsub(self, photos):
self.photos = photos
self.total_photos = len(self.photos)
self.update_photo(self.photos[0])
self.current_photo = 0
def update_photo(self, image):
"""
Update the currently shown photo
"""
img = wx.Image(image, wx.BITMAP_TYPE_ANY)
# scale the image, preserving the aspect ratio
W = img.GetWidth()
H = img.GetHeight()
if W > H:
NewW = self.max_size
NewH = self.max_size * H / W
else:
NewH = self.max_size
NewW = self.max_size * W / H
img = img.Scale(NewW, NewH)
self.image_ctrl.SetBitmap(wx.Bitmap(img))
self.Refresh()
def reset(self):
img = wx.Image(self.max_size,
self.max_size)
bmp = wx.Bitmap(img)
self.image_ctrl.SetBitmap(bmp)
self.current_photo = 0
self.photos = []
class MainFrame(wx.Frame):
def __init__(self):
disW, disH = wx.DisplaySize()
super().__init__(None, title='Image Viewer',
size=(disW-500, disH-100))
self.panel = ImagePanel(self)
# create status bar
self.CreateStatusBar()
# create the menu bar
menuBar = wx.MenuBar()
# create a menu
fileMenu = wx.Menu()
openBut = wx.MenuItem(fileMenu, wx.ID_OPEN, '&Open', 'Open Working Directory')
fileMenu.Append(openBut)
fileMenu.Bind(wx.EVT_MENU, self.on_open_directory, openBut)
# add a line separator to the file menu
fileMenu.AppendSeparator()
# add a quit button to fileMenu
quitBut = wx.MenuItem(fileMenu, wx.ID_EXIT, '&Quit', 'Exit the Programm')
fileMenu.Append(quitBut)
# connect the quit button to the actual event of quitting the app
fileMenu.Bind(wx.EVT_MENU, self.onQuit, quitBut)
# give the menu a title
menuBar.Append(fileMenu, "&File")
# connect the menu bar to the frame
self.SetMenuBar(menuBar)
self.create_toolbar()
self.Show()
def create_toolbar(self):
"""
Create a toolbar
"""
self.toolbar = self.CreateToolBar()
self.toolbar.SetToolBitmapSize((16,16))
open_ico = wx.ArtProvider.GetBitmap(
wx.ART_FILE_OPEN, wx.ART_TOOLBAR, (16,16))
openTool = self.toolbar.AddTool(
wx.ID_ANY, "Open", open_ico, "Open an Image Directory")
self.Bind(wx.EVT_MENU, self.on_open_directory, openTool)
self.toolbar.Realize()
def on_open_directory(self, event):
"""
Open a directory dialog
"""
with wx.DirDialog(self, "Choose a directory",
style=wx.DD_DEFAULT_STYLE) as dlg:
if dlg.ShowModal() == wx.ID_OK:
self.folder_path = dlg.GetPath()
photos = glob.glob(os.path.join(self.folder_path, '*.jpg'))
if photos:
pub.sendMessage("update", photos=photos)
else:
self.panel.reset()
def onQuit(self, event):
# get the frame's top level parent and close it
wx.GetTopLevelParent(self).Destroy()
if __name__ == '__main__':
app = wx.App(redirect=False)
frame = MainFrame()
app.MainLoop()
如果需要,这里是追溯:
File "...\image_viewer4.py", line 183, in on_open_directory
pub.sendMessage("update", photos=photos)
File "C:\Anaconda\lib\site-packages\pubsub\core\publisher.py", line 216, in sendMessage
topicObj.publish(**msgData)
File "C:\Anaconda\lib\site-packages\pubsub\core\topicobj.py", line 452, in publish
self.__sendMessage(msgData, topicObj, msgDataSubset)
File "C:\Anaconda\lib\site-packages\pubsub\core\topicobj.py", line 482, in __sendMessage
listener(data, self, allData)
File "C:\Anaconda\lib\site-packages\pubsub\core\listener.py", line 237, in __call__
cb(**kwargs)
File "...\image_viewer4.py", line 84, in update_photos_via_pubsub
self.update_photo(self.photos[0])
虽然不是很明显,但我注意到您没有取消 pubsub
订阅。
尝试:
def onQuit(self, event):
# get the frame's top level parent and close it
pub.unsubscribe(self.panel.update_photos_via_pubsub, "update")
wx.GetTopLevelParent(self).Destroy()
看看这是否有所作为。
为了确保在使用框架右上角的 X
关闭应用程序时也执行 onQuit
,请使用以下内容(在 MainFrame
中):
self.Bind(wx.EVT_CLOSE, self.onQuit)
这应该涵盖所有基地。