2

I am attempting to having a frame display an image that's opened through filedialog. My current method initializes the frame in Step1 with "image1path", and then I'd like to update it to some "image2path" specified by the user. I am attempting to do this through the Data.currentJPEG attribute.

  1. Is there a better way that involves not initializing with a "dummy" image?
  2. If this is the correct way, why do my commented out lines at the bottom return: " 'str' object has no attribute '_bind' " when Step1 is not a string. I thought Step1 was what allowed self.bind to work on the startPage. Is this not the correct method of updating the image in the frame?
  3. Bonus side question: if I change weight=100 to weight=1 in the start page lines, why does the open .jpg button's location scale at all?

This code structure can be found here

Resizing an image this way can be found here

The method I attempt to use follows this answer, where a new function is called when the button is pressed that updates the image. However, I believe I am running into issues because my image uses the resizing function as well.

            import os
            import tkinter as tk
            from PIL import Image, ImageTk
            from tkinter import filedialog

            image1path='mainGraphic.jpg'

            cwd = os.getcwd()

            # where opened file data with be stored
            class Data():
                # this sets initial values for the class attributes 
                def __init__(self):
                    self.currentJPEG=image1path

            class program(tk.Tk):
                def __init__(self,*args,**kwargs):
                    tk.Tk.__init__(self,*args,**kwargs)

                    self.title('program name')

                    container = tk.Frame(self)
                    container.pack(side="top", fill="both", expand=True)
                    container.grid_rowconfigure(0, weight=1)
                    container.grid_columnconfigure(0, weight=1)

                    # frames are laid ontop of each other, startPage shown first
                    self.frames = {}
                    for Frame in (StartPage, Step1):
                        frame = Frame(container, self)
                        self.frames[Frame] = frame
                        frame.grid(row = 0, column = 0, sticky="nsew")
                        frame.columnconfigure(0, weight=1)
                        frame.rowconfigure(0, weight=1)

                    self.show_frame(StartPage)

                def show_frame(self,cont):
                    frame = self.frames[cont]
                    frame.tkraise()

            class StartPage(tk.Frame):
                def __init__(self, parent, controller):
                    tk.Frame.__init__(self,parent)

                    # scale rows and columns (make button scaling negligible) 
                    self.rowconfigure(0,weight=0)
                    self.rowconfigure(1,weight=100) #bonus question refers to these 2 lines
                    self.columnconfigure(0,weight=100) 

                    # button to open an image in Step 1,
                         # must pass program so app.showframe is available,
                         # must pass Step1 so the image attributes are available?
                    button = tk.Button(self, text='Open .jpg File in new frame',
                                        command=lambda: Step1.openJPEG(program))
                    button.grid(row=0, column=0, sticky='ew')

                    # add the main graphic
                    self.canvas = tk.Canvas(self,bg='black')
                    self.canvas.grid(row=1, column=0, sticky='nsew')
                    self.img_copy = Image.open(image1path)
                    self.image = None #this is overriden every time the image is redrawn so there is no need to make it yet
                    self.bind("<Configure>",self.resizeImage)

                def resizeImage(self,event):
                    origin = (0,0)
                    size = (event.width, event.height)
                    if self.bbox("bg") != origin + size:
                        self.canvas.delete("bg")
                        self.image = self.img_copy.resize(size)
                        self.background_image = ImageTk.PhotoImage(self.image)
                        self.canvas.create_image(*origin,anchor="nw",image=self.background_image,tags="bg")
                        self.canvas.tag_lower("bg","all")

            class Step1(tk.Frame):

                def __init__(self, parent, controller):
                    tk.Frame.__init__(self, parent)

                    # give even weight to each subframe
                    self.rowconfigure(0, weight=1)
                    self.columnconfigure(0, weight=100)
                    self.columnconfigure(1, weight=100)

                    # Frame1, this is on the left and empty
                    frame1 = tk.Frame(self, bg="grey")
                    frame1.grid(row=0, column=0, sticky='nsew')

                    # Frame2, this is where image preview is
                    frame2 = tk.Frame(self, bg="grey")
                    frame2.grid(row=0, column=1, sticky='nsew')    

                    # scale rows and columns with equal weight
                    frame2.rowconfigure(0,weight=1)
                    frame2.columnconfigure(0,weight=100)
                    frame2.columnconfigure(1,weight=100)

                    # initialize where image preview will be in frame2
                    self.canvas = tk.Canvas(frame2,bg='black')
                    self.canvas.grid(row=0, column=1, sticky='nsew')
                    self.img_copy = Image.open(Data.currentJPEG)
                    self.image = None 
                    self.bind("<Configure>",self.resizeImage)

                def resizeImage(self,event):
                    origin = (0,0)
                    size = (event.width, event.height) # these need to get height/width of their frame, not Step1
                    if self.bbox("bg") != origin + size:
                        self.canvas.delete("bg")
                        self.image = self.img_copy.resize(size)
                        self.background_image = ImageTk.PhotoImage(self.image)
                        self.canvas.create_image(*origin,anchor="nw",image=self.background_image,tags="bg")
                        self.canvas.tag_lower("bg","all")

                # update the image in frame2 using opened jpeg image
                def openJPEG(program):
                    filePath = filedialog.askopenfilename(initialdir=cwd,
                                                          filetypes=(("JPEG",".jpg"),("All Files","*.*")),
                                                          title="Open JPEG")
                    try:
                        with open(filePath) as image: #***need a better 'try' statement for only jpeg is opened'
                            app.show_frame(Step1)
                    except:
                        print('could not open image')
                        return

                    ## update image preview from mainGraphic to opened JPEG; these lines aren't working
                    Data.currentJPEG=filePath
                    #Step1.img_copy = Image.open(Data.currentJPEG)
                    #Step1.image = None
                    #Step1.bind("<Configure>",Step1.resizeImage)

            # initalize data class
            Data=Data()
            # run program
            app = program()
Community
  • 1
  • 1

1 Answers1

1

If all you want to do is to let your user manually select a file, and then have your tkinter GUI display it, you just need a Button and a Canvas.

Code:

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.image as mpimg
from tkinter import filedialog
import tkinter as tk
import os

class program(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)

        # Create a button to load a jpg
        self.button = tk.Button(self, text="Load image", command=self.openJPEG)
        self.button.pack()

        # Create a canvas on which to show the jpg
        self.frame = tk.Frame(self)
        self.frame.pack(fill=tk.BOTH, expand=1)
        self.create_canvas()

    def openJPEG(self):
        # Display a dialog for the user to select a jpg file, 
        filePath = filedialog.askopenfilename(initialdir=os.getcwd(),
                                              filetypes=(("JPEG",".jpg"),
                                                         ("All Files","*.*")),
                                              title="Open JPEG")
        # Show the selected jpg on the canvas
        img = mpimg.imread(filePath)
        self.ax1.imshow(img)
        self.canvas1.draw()

    def create_canvas(self):
        """ Add a canvas to plot images """        
        self.fig1 = Figure(frameon=False, figsize=(6, 4.5))
        self.canvas1 = FigureCanvasTkAgg(self.fig1, master=self.frame)
        self.canvas1.get_tk_widget().pack(fill=tk.BOTH, expand=1)
        self.ax1 = self.fig1.add_axes([0, 0, 1, 1])
        self.ax1.axis('off')

# Run program
app = program()
app.mainloop()

If you want to keep your multiple classes structure, you will have to make better use of the root part of your GUI (called controller in your subframes) to communicate between the different frames. See the code below to get you started:

import os
import tkinter as tk
from PIL import Image, ImageTk
from tkinter import filedialog

# where opened file data with be stored
class Data():
    # this sets initial values for the class attributes 
    def __init__(self):
        self.currentJPEG=""

class program(tk.Tk):
    def __init__(self, Data):
        tk.Tk.__init__(self)
        self.Data = Data
        container = tk.Frame(self)
        container.pack(fill="both", expand=True)

        # frames are laid ontop of each other, startPage shown first
        self.frames = {}
        for Frame in (StartPage, Step1):
            frame = Frame(container, self)
            self.frames[Frame] = frame
            frame.grid(row = 0, column = 0, sticky="nsew")

        self.show_frame(StartPage)

    def show_frame(self,cont):
        frame = self.frames[cont]
        frame.tkraise()

class StartPage(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller

        # button to open an image in Step 1,
        button = tk.Button(self, text='Open .jpg File in new frame',
                           command= self.openJPEG)
        button.grid(row=0, column=0, sticky='ew')

        # add empty canvas
        self.canvas = tk.Canvas(self,bg='black')
        self.canvas.grid(row=1, column=0, sticky='nsew')

    def openJPEG(self):
        self.openJPEG
        filePath = filedialog.askopenfilename(initialdir=os.getcwd(),
                                              filetypes=(("JPEG",".jpg"),
                                                         ("All Files","*.*")),
                                              title="Open JPEG")
        # Update the image in Step1 frame
        self.controller.Data.currentJPEG = filePath
        self.controller.frames[Step1].show_current_image()
        # Show Step1 frame
        self.controller.show_frame(Step1)

class Step1(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller

        # Frame2, this is where image preview is
        frame2 = tk.Frame(self, bg="grey")
        frame2.grid(row=0, column=1, sticky='nsew')    

        # initialize where image preview will be in frame2
        self.canvas = tk.Canvas(frame2,bg='black')
        self.canvas.grid(row=0, column=1, sticky='nsew')

    def show_current_image(self):
        self.image = Image.open(self.controller.Data.currentJPEG)
        self.background_image = ImageTk.PhotoImage(self.image)
        self.canvas.create_image(0, 0, anchor="nw", image=self.background_image, 
                                 tags="bg")

# initalize data class
Data = Data()
# run program
app = program(Data)
app.mainloop()
Josselin
  • 2,593
  • 2
  • 22
  • 35
  • This works great, but can you show how to use a button to run openJPEG from a different class? In my case, that open image button exists on the startPage, but the image should be opened in the Step1 frame. I would think you would use: command=Step1.openJPEG, however this doesn't seem to be the case. I also need controller to be passed in for the frame switch. – theMostVersatileSnake May 03 '17 at 19:38
  • I added a reworked version of your code to my answer to show you how to communicate between different parts of your GUI in different classes, using a common `controller` class. – Josselin May 04 '17 at 12:07
  • 1
    I'm starting to understand this structure a bit better. Could you elaborate on "self.controller = controller" under Step1? I thought controller was inherited just by using __init__(self, parent, controller). Thanks a bunch for your help! – theMostVersatileSnake May 04 '17 at 21:02
  • In the Step1 class, `controller` represent your `program` instance, and is passed as an argument during creation in `__init__(self, parent, controller)`, but it's only available in the `__init__()` method. Writing `self.controller = controller` is a way of storing the `controller` in Step1's instance to make it available in other methods of Step1 like `show_current_image()` where you want to access the main program's Data. Hope it helps! ;) – Josselin May 05 '17 at 09:02