Обучение Python: RuntimeError: обернутый объект C / C ++ типа StaticBitmap был удален - PullRequest
0 голосов
/ 30 мая 2020

В настоящее время я учусь программировать с помощью 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: обернутый объект C / C ++ был удален , Понимание ошибки «базовый объект C / C ++ был удален» и Может ли PyQt4 QObject должен быть запрошен, чтобы определить, был ли уничтожен базовый экземпляр C ++? , но я не понимаю, что вызывает ошибку в приведенном выше примере кода. Я имею в виду, что в сообщении об ошибке указано, что объект растрового изображения удален, но я понятия не имею, как и почему.

Я ценю любую продуктивную помощь, которую кто-либо может мне оказать. Я хотел бы понять, что именно вызывает ошибку в коде и как ее избежать.

Вот код:

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])

1 Ответ

0 голосов
/ 30 мая 2020

Это не сразу очевидно, но я заметил, что вы не отменяете подписку 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()

Посмотрите, имеет ли это значение.

Чтобы гарантировать, что onQuit также выполняется при закрытии приложения с помощью X в правом верхнем углу кадра, используйте следующее (в MainFrame):

    self.Bind(wx.EVT_CLOSE, self.onQuit)

Это должно охватывать все базы.

...