0
votes

I'm a beginner at python and programming for that matter and trying to implement a rudimentary image viewer and having trouble to update the sub panel, in which the image should be shown. When I feed an image to the panel at the start of the program, it shows correctly. However, when I try to update the panel with another image via the open file- or open directory-dialog it does not display correctly (see screenshot).

From what I read so far about updating panels (wxpython refresh window on button press,wxPython - change panel by button,How do you force refresh of a wx.Panel?) it is not enough to just recall the function responsible for creating the sub panel, but I seem to oversee or miss something elementary because I cannot get any of the proposed solutions to work in my code.

I also had a look at this photo viewer tutorial at Mouse vs Python, but I could't get it to work in my program.

Below is the code I have so far. It also reproduces the faulty result which you can see on the screenshot. I appreciate any constructive help and feedback you can give me.

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()
2

2 Answers

0
votes

First of all, anytime wxpython puts something in the top left it means it doesn't know where to put it, I think you have worked that out, but it is useful to know. It does mean your wxpython object exists and can be rendered which is at least something.

The reason it doesn't work second time around is that this code :

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)

creates a new comicPagePanel object (in the last line), but it is a new one, completely separate from the one already created. The new one doesn't relate to any of the sizers, it is just a child of the frame. So it appears at the top left as there is no layout associated with it.

Your code as it is structured cannot do what you want. In order to update the image in the sizers you need to get this object

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

and perform the following operation on it

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

Note, you should also change the name of this variable. It is not a sizer, it is a StaticBitmap. You can check this by asking python what its type is

print(type(comPageBoxSizer))

It is quite reasonable to have the type in the name and can be helpful, but it is decidedly unhelpful if the type is wrong.

A working solution would have a new method in your comicPagePanel class that has a parameter of the new image and then updates the bitmap that is embedded in the layout structure.

I hope from this description you can see why it works if you declare the image at the start of the run. The image is placed in the layout structure appropriately. If you add it later it is completely separate from that layout.

0
votes

First of all, thanks again to pyrrhoofcam for pointing me into the right direction. I worked out a solution that works, but I don't know if it is "good" code. I simply defined the image as an static class attribute which can be changed through the additional updateImage() method within the same class. The code of the panel class comicPagePanel() looks like this now:

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

Now, if the user wants to open a new image via the file dialog, the updateImage() method is called. This call looks like this:

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