wxPython:更新子面板中的图像/位图

wxPython: Update image / bitmap in sub panel

我是 python 的初学者,我正在为此编程,并试图实现一个基本的图像查看器,但在更新应该显示图像的子面板时遇到了麻烦。当我在程序开始时向面板提供图像时,它可以正确显示。但是,当我尝试通过打开的文件或打开的目录对话框用另一个图像更新面板时,它无法正确显示(参见 screenshot)。

从我目前阅读的关于更新面板的内容来看 (wxpython refresh window on button press,,How do you force refresh of a wx.Panel?) 仅仅回忆起负责创建子面板的功能是不够的,但我似乎忽略或遗漏了一些基本的东西,因为我无法理解在我的代码中工作的任何建议解决方案。

我也在 Mouse vs Python 上看过这个 photo viewer tutorial,但我无法在我的程序中使用它。

下面是我目前的代码。它还会重现您在 screenshot 上看到的错误结果。感谢您给我的任何建设性帮助和反馈。

import glob
import wx
import os

#====================================================================
class textEditor(wx.Panel):
    #----------------------------------------------------------------------
    def __init__(self, parent):

        wx.Panel.__init__(self, parent)

        sizer = wx.BoxSizer(wx.VERTICAL)

        speeBubImg = wx.StaticBox(self, wx.ID_ANY, "Text Editor")
        sizer.Add(speeBubImg, wx.ID_ANY, flag=wx.EXPAND|wx.ALL)

        self.SetSizer(sizer)

#====================================================================
class comicPagePanel(wx.Panel):
    #----------------------------------------------------------------------
    def __init__(self, parent, imgsPath):

        wx.Panel.__init__(self, parent)

#        print('comicPagePanel', imgsPath)

        # create the static box with the panel description...
        comPageStatBox = wx.StaticBox(self, wx.ID_ANY, "Comic Page")
        # ...and asign a sizer to it
        comPageStatBoxSizer = wx.StaticBoxSizer(comPageStatBox, wx.VERTICAL)

         # Feeding the panel with an image when starting the program works
#        imgsPath.append('someImage.jpg')

        if imgsPath:

#            print('comicPagePanel_if-imgPath', imgsPath)

            # create the image box
            comPageBox = wx.Bitmap(wx.Image(imgsPath[0], wx.BITMAP_TYPE_ANY))

            img = comPageBox.ConvertToImage()

            iW = img.GetWidth()
            iH = img.GetHeight()
            imgMaxSize = 1000

            if iW > iH:
                NewW = imgMaxSize
                NewH = imgMaxSize * iH / iW
            else:
                NewH = imgMaxSize
                NewW = imgMaxSize * iW / iH

            img = wx.Bitmap(img.Scale(NewW,NewH))

            # create another sizer for the actual image box
            comPageBoxSizer = wx.StaticBitmap(self, wx.ID_ANY, img)

            # add the image box sizer to the sizer of the
            # static box with the panel description
            comPageStatBoxSizer.Add(comPageBoxSizer, wx.ID_ANY, wx.EXPAND|wx.ALL)

        # create a main sizer which stretches all other sizers to the
        # size of the subpanel
        main_sizer = wx.BoxSizer()

        # add the static box with the image box that is nested in it
        # to the main sizer
        main_sizer.Add(comPageStatBoxSizer, wx.ID_ANY, wx.EXPAND|wx.ALL)

        # fit the main sizer to the subpanel
        self.SetSizer(main_sizer)

#====================================================================
class comicPageViewer(wx.Panel):
    #----------------------------------------------------------------------
    def __init__(self, parent, imgsPath):

        wx.Panel.__init__(self, parent)

        # laying out the grid for the image panel and the ctrl-buttons
        sizer = wx.GridBagSizer(2, 4)

        # the image viewing panel
        comPage = comicPagePanel(self, imgsPath)
        sizer.Add(comPage, pos=(0, 0), span=(1, 4),
                  flag=wx.EXPAND|wx.ALL, border=5)

        # the ctrl-buttons
        butPrev = wx.Button(self, label="Previous Page")
        butNext = wx.Button(self, label="Next Page")
        butOCR = wx.Button(self, label="Find Text/OCR")
        butSaveTxt = wx.Button(self, label="Save Current Text(s)")


        sizer.Add(butPrev, pos=(1, 0), flag=wx.EXPAND)
        sizer.Add(butNext, pos=(1, 1), flag=wx.EXPAND)
        sizer.Add(butOCR, pos=(1, 2), flag=wx.EXPAND)
        sizer.Add(butSaveTxt, pos=(1, 3), flag=wx.EXPAND)


        sizer.AddGrowableCol(0)
        sizer.AddGrowableCol(1)
        sizer.AddGrowableCol(2)
        sizer.AddGrowableCol(3)

        sizer.AddGrowableRow(0)

        self.SetSizer(sizer)

#====================================================================
class mainPanel(wx.Panel):
    #----------------------------------------------------------------------
    def __init__(self, parent, imgsPath):

        wx.Panel.__init__(self, parent)

#        print(imgsPath)

        # Create a sub panel left and right
        lSubPan = comicPageViewer(self, imgsPath)
        rSubPan = textEditor(self)

        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(lSubPan, wx.ID_ANY, wx.EXPAND|wx.ALL)
        sizer.Add(rSubPan, wx.ID_ANY, wx.EXPAND|wx.ALL)
        self.SetSizer(sizer)

#====================================================================
class mainFrame(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)

        self.createPanel()
        self.Maximize(True)
        self.Show()

    #----------------------------------------------------------  
    def createPanel(self):

        imgsPath = []

        self.CreateStatusBar()
        self.createMenu()
        panel = mainPanel(self, imgsPath)
    #----------------------------------------------------------
    def createMenu(self):      

        # create the menu bar
        menuBar = wx.MenuBar()

        # create a file menu
        fileMenu = wx.Menu()

        opFileBut = wx.MenuItem(fileMenu, wx.ID_ANY, '&Open File', 'Open a Single File')
        fileMenu.Append(opFileBut)

        # bind the open button to the on_open_directory event
        fileMenu.Bind(wx.EVT_MENU, self.onOpenFile, opFileBut)

        # add an open directory Button to the file menu
        opDirBut = wx.MenuItem(fileMenu, wx.ID_ANY, 'Open &Directory', 'Open Working Directory')
        fileMenu.Append(opDirBut)

        # bind the open button to the on_open_directory event
        fileMenu.Bind(wx.EVT_MENU, self.onOpenDirectory, opDirBut)

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

        # call onQuit if the app is closed via x in title bar (in order to do some cleaning up)
        self.Bind(wx.EVT_CLOSE, self.onQuit)

        # give the menu a title
        menuBar.Append(fileMenu, "&File(s)")

        # connect the menu bar to the frame        
        self.SetMenuBar(menuBar)

    #----------------------------------------------------------
    def onOpenFile(self, event):

        with wx.FileDialog(self, "Choose a File",
                          style=wx.FD_DEFAULT_STYLE) as fDlg:
            if fDlg.ShowModal() == wx.ID_OK:

                imgPath = glob.glob(os.path.join(fDlg.GetPath()))

            if imgPath:
                comicPagePanel(self, imgPath)

    #----------------------------------------------------------
    def onOpenDirectory(self, event):

        with wx.DirDialog(self, "Choose a Directory",
                          style=wx.DD_DEFAULT_STYLE) as dDlg:
            if dDlg.ShowModal() == wx.ID_OK:

                imgsPath = glob.glob(os.path.join(dDlg.GetPath(), '*.jpg'))

            if imgsPath:
                comicPagePanel(self, imgsPath)

    #----------------------------------------------------------
    def onQuit(self, event):
        # get the frame's top level parent and close it
        wx.GetTopLevelParent(self).Destroy()


#======================
# Start GUI
#======================

if __name__ == '__main__':
    app = wx.App()
    mainFrame(None, title="Text from Comic Pages")
    app.MainLoop()

首先,任何时候 wxpython 把一些东西放在左上角,这意味着它不知道把它放在哪里,我想你已经解决了,但知道它很有用。它确实意味着你的 wxpython 对象存在并且可以被渲染,这至少是某种东西。

它第二次不起作用的原因是这段代码:

def onOpenFile(self, event):

    with wx.FileDialog(self, "Choose a File",
                      style=wx.FD_DEFAULT_STYLE) as fDlg:
        if fDlg.ShowModal() == wx.ID_OK:

            imgPath = glob.glob(os.path.join(fDlg.GetPath()))

        if imgPath:
            comicPagePanel(self, imgPath)

创建一个新的 comicPagePanel 对象(在最后一行),但它是一个新的,与已经创建的对象完全分开。新的与任何 sizer 无关,它只是框架的子项。所以它出现在左上角,因为没有与之关联的布局。

您的代码结构无法满足您的要求。为了更新 sizer 中的图像,您需要获取此对象

comPageBoxSizer = wx.StaticBitmap(self, wx.ID_ANY, img)

并对其进行如下操作

comPageBoxSizer.SetBitmap( ... new bitmap object here ... )

注意,您还应该更改此变量的名称。它不是一个 sizer,它是一个 StaticBitmap。您可以通过询问 python 它的类型是什么来检查

print(type(comPageBoxSizer))

名称中有类型是很合理的,可以提供帮助,但如果类型不正确,则毫无帮助。

一个有效的解决方案将在您的 comicPagePanel class 中有一个新方法,该方法具有新图像的参数,然后更新嵌入在布局结构中的位图。

我希望从这个描述中你能明白为什么如果你在 运行 的开头声明图像它会起作用。图像被适当地放置在布局结构中。如果您稍后添加它,它将完全独立于该布局。

首先,再次感谢 pyrrhoofcam 为我指明了正确的方向。我制定了一个可行的解决方案,但我不知道它是否是 "good" 代码。我只是将图像定义为静态 class 属性,可以通过同一 class 中的附加 updateImage() 方法对其进行更改。面板 class comicPagePanel() 的代码现在如下所示:

#====================================================================
# the upper left panel that shows the image of the comic page
class comicPagePanel(wx.Panel):

    # create an "empty" image as placeholder;
    # it will be replaced by an actual image via the open file dialog
    comPageImg = wx.Image(1,1)

    #----------------------------------------------------------------------
    def __init__(self, parent):
        # Constructor
        wx.Panel.__init__(self, parent)

        # create the static box with the panel description...
        comPageStatBox = wx.StaticBox(self, wx.ID_ANY, "Comic Page")
        # ...and asign a sizer to it
        comPageStatBoxSizer = wx.StaticBoxSizer(comPageStatBox, wx.VERTICAL)

        # get the background color of the panel
        [panR, panG, panB] = self.GetBackgroundColour()[:3]

        # set the color of the image placeholder to the background color of the panel
        comicPagePanel.comPageImg.Replace(0,0,0,panR,panG,panB)

        # convert the image placeholder to a static bitmap
        comicPagePanel.comPageImg = wx.StaticBitmap(self, wx.ID_ANY, wx.Bitmap(comicPagePanel.comPageImg))

        # add the image placeholder to the sizer of the
        # static box with the panel description; meaning, placing it within the static box
        comPageStatBoxSizer.Add(comicPagePanel.comPageImg, wx.ID_ANY, wx.EXPAND|wx.ALL)

        # create a main sizer which stretches all other sizers to the
        # size of the subpanel
        main_sizer = wx.BoxSizer()

        # add the static box with the image that is nested in it
        # to the main sizer
        main_sizer.Add(comPageStatBoxSizer, wx.ID_ANY, wx.EXPAND|wx.ALL)

        # fit the main sizer to the subpanel
        self.SetSizer(main_sizer)

    #----------------------------------------------------------------------
    # the update method of the comic page image viewer
    def updateImage(self, imgsPath):

        # get the new image
        newImg = wx.Image(imgsPath[0], wx.BITMAP_TYPE_ANY)

        # get the display resolution in order to fit the image into the panel
        [disX, disY] = wx.GetDisplaySize()

        # determine the approximate size of the image panel
        disX = disX/2-50
        disY = disY-175

        # get the size of the new image
        [iW, iH] = newImg.GetSize()

        # scale the image proportionally        
        if not iH < disY and iW > iH:
            NewW = disX
            NewH = disX * iH / iW
            # scaling the page image
            newImg = newImg.Scale(NewW,NewH)
        elif not iW < disX:
            NewH = disY
            NewW = disY * iW / iH
            # scaling the page image
            newImg = newImg.Scale(NewW,NewH)

        # replace the old image in the panel with the new one
        comicPagePanel.comPageImg.SetBitmap(wx.Bitmap(newImg))

现在,如果用户想通过文件对话框打开新图像,将调用 updateImage() 方法。此调用如下所示:

#----------------------------------------------------------
# open a file dialog
def onOpenFile(self, event):

    with wx.FileDialog(self, "Choose a File",
                      style=wx.FD_DEFAULT_STYLE) as fDlg:
        if fDlg.ShowModal() == wx.ID_OK:

            imgPath = glob.glob(os.path.join(fDlg.GetPath()))

        if imgPath:
            comicPagePanel.updateImage(self, imgPath)
        else:
            return