1

All of the tutorials I have seen deomonstrate the tkinter filedialog.askopenfilename function by only using the information collected within the function that is linked to the tkinter button. I can pass information in the function, but I would like to pass variables (filepath, images, etc.) outside the function and have them update variables in my GUI. I have commented out the location I would like to call the variables in main_gui_setup function below. Any help will be appreciated, as it has been very demoralizing not being able to open a file. If this problem persists, my future as a programmer may be limited to creating tic-tac-toe games or instructional videos for Youtube.

from tkinter import *
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog, messagebox
from PIL import ImageTk, Image # was import PIL.Image, PIL.ImageTk
import cv2


def main():
    root = Tk()
    window1 = Window(root, "X-ray Assist", "Give up")
    return None


# can't pass by reference in python

class Window():

    n = 0
    file_path = ""
    img1_info = ""

    def __init__(self, root, title, message):
        self.root = root
        self.root.title(title)
        #self.root.geometry(geometry)
        self.screen_width = root.winfo_screenwidth()
        self.screen_height = root.winfo_screenheight()
        #self.root.attributes('-topmost', 1)

        # SET APP WINDOW SIZE
        scr_size_main = self.scr_size() # create instance of scr_size
        self.root.geometry("%dx%d+%d+%d" % (self.root_width, self.root_height, self.root_x, self.root_y))

        # CREATE MAIN WINDOW GUI
        create_gui = self.main_gui_setup()

        self.root.mainloop()
        
        pass

    def scr_size(self):
        '''Reads monitor size and adjusts GUI frame sizes'''        

        self.root_width = int(self.screen_width*0.52)
        self.root_height = int(self.screen_height*0.9)
        self.root_x = int(self.screen_width*0.23)
        self.root_y = int(self.screen_height*0.02)        
        self.img_ht_full = int(self.screen_height*0.82)   
        self.tools_nb_width = int(self.screen_width*0.22)
        self.tools_nb_height = int(self.screen_height*0.48)     
        self.hist_nb_width = int(self.screen_width*0.22)
        self.hist_nb_height = int(self.screen_height*0.23)

    def open_image(self):
        
        main_win = ttk.Frame(self.root)
        main_win.grid(column=0, row=0)  
        
        self.file_path = filedialog.askopenfilename(initialdir='/', title='Open File', 
            filetypes=(('tif files', '*.tif'), ('all files', '*.*')))
        self.file_path_label = ttk.Label(main_win, text=self.file_path)
        self.file_path_label.grid(column=0, row=0, columnspan=1, sticky="nw", padx=(5,0), pady=1)    
        self.img1_8bit = cv2.imread(self.file_path, 0) #, cv2.IMREAD_ANYDEPTH | cv2.IMREAD_GRAYSCALE)
        #self.img1_8bit_resize = cv2.resize(self.img1_8bit, (self.img_ht_full, self.img_ht_full)) #, interpolation = cv2.INTER_CUBIC)
        #self.img1_height, self.img1_width = self.img1_8bit.shape # not resized for screen
        #img1_info = text = f"{self.img1_height} {self.img1_8bit.dtype} {self.img1_16bit.dtype}"
        #print(self.img1_width, " x ", self.img1_height, " bitdepth = ", self.img1_8bit.dtype)
        
        #img1_info = ttk.Label
        #print(f"{self.img1_height} {self.img1_width} {self.img1_8bit.dtype}")
        #img1_info.grid(column=3, row=1, columnspan=1, sticky="w", padx=(5,0), pady=1)
        
        #img = io.imread(main_win.filename) #scikit
        
        self.img1_16bit = cv2.imread(self.file_path, cv2.IMREAD_ANYDEPTH | cv2.IMREAD_GRAYSCALE)

        #self.img_canvas = tk.Canvas(self.root, width=self.img_ht_full, height=self.img_ht_full)
        #self.img_canvas.grid(column=1, row=2, columnspan=10, rowspan=10, sticky="nw")

        #self.img_canvas.image = ImageTk.PhotoImage(image=Image.fromarray(self.img1_8bit_resize))
        #self.img_canvas.create_image(0,0, image=self.img_canvas.image, anchor="nw")
        
        # .create_line(x1, y1, x2, y2, fill="color")
        #self.img_canvas.create_line((self.img_ht_full/2), 0, (self.img_ht_full/2), (self.img_ht_full), fill="yellow")
        #self.img_canvas.create_line(0, (self.img_ht_full/2), (self.img_ht_full), (self.img_ht_full/2), fill="yellow")
        
    def main_gui_setup(self):
        main_win = ttk.Frame(self.root)
        main_win.grid(column=0, row=0)
        image_win = ttk.Frame(main_win, borderwidth=25, relief="groove", width=self.img_ht_full, height=self.img_ht_full)                 
        image_win.grid(column=1, row=2, columnspan=10, rowspan=10, sticky="nw")
        toolbar = ttk.Frame(main_win, borderwidth=5) #, width=1100, height=15)
        toolbar.grid(column=0, row=0, columnspan=10, rowspan=1, sticky="nw", padx=20)
        hist_win = ttk.Frame(main_win, borderwidth=25, relief="groove", width=300, height=200)
        panel_info = ttk.Label(main_win, text=f"{self.screen_width} x {self.screen_height}")
        panel_info.grid(column=5, row=1, columnspan=1, sticky="e", pady=1)
        # SCROLL SLIDER AT BOTTOM
        slider = ttk.Scrollbar(main_win, orient="horizontal")
        slider.grid(column=1, row=13, columnspan=7, padx=5, pady=5, sticky="ew")
        #X-RAY AND DETECTOR SETTINGS - will input these from separate class
        kv = ttk.Label(main_win, text="125kV") 
        kv.grid(column=0, row=2, columnspan=1, padx=15, pady=5)
        file_path_label = ttk.Label(main_win, text="No image loaded")
        file_path_label.grid(column=1, row=1, columnspan=1, sticky="nw", padx=(5,0), pady=1)  
        
        # CREATE BUTTONS
        open = ttk.Button(toolbar, text="Open", width=10, command=self.open_image)
        open.grid(column=0, row=0)
        save = ttk.Button(toolbar, text="Save", width=10)
        save.grid(column=1, row=0)
        b1 = ttk.Button(toolbar, text="1", width=10)
        b1.grid(column=2, row=0)
        b2 = ttk.Button(toolbar, text="2", width=10)
        b2.grid(column=3, row=0)

    pass

main()
Bert
  • 33
  • 3
  • did You try using `self.` when retrieving path? since a `self.` variable is accessible throughout the class (tho define it in `__init__` at least as a None or sth) – Matiiss Apr 10 '21 at 20:29
  • @Matiiss OP's problem is that he/she is trying to access `self.path_label` just after they create the button. – TheLizzard Apr 10 '21 at 20:33
  • You should define all `self.` variables in the `__init__` method if You don't have a value for them yet just set them to `None` – Matiiss Apr 10 '21 at 20:33
  • What are you trying to do on the `main_label = ttk.Label(...)` line? – Henry Apr 10 '21 at 20:35
  • Oh, I see. You should then create another method that will get executed by that button and then it will create the button – Matiiss Apr 10 '21 at 20:35
  • @Henry as it seems he is trying to create a label with his file path or sth with the path he just got, more like the path he thinks he will get and then it will go back to the function that made it get the path and assign it to a label inside that function – Matiiss Apr 10 '21 at 20:37
  • @Matiiss You got it. Do you want to write the answer? – TheLizzard Apr 10 '21 at 20:39
  • @TheLizzard that is an interesting question to ask. I mean I could write a sample or edit his code or sth, ok will try – Matiiss Apr 10 '21 at 20:41
  • wait, what is this supposed to do? `main_label = ttk.Label(main_win, self.path_label)` is it even possible? why are You trying to put...what? – Matiiss Apr 10 '21 at 20:44
  • @Matiiss That's what I was asking – Henry Apr 10 '21 at 20:53
  • Yes, I did try using self., but as I am trying to load info to the user interface after opening an image, it doesn't update. I have added the actual code I'm working on, rather than the pared down version I posted. Somehow I thought I was making it easier to understand, but I think I mainly obscured what I am trying to do. – Bert Apr 11 '21 at 01:10

2 Answers2

0

You aren't thinking of event driven programming correctly. In event driven programming you have callbacks to the functions you defined. Let's look at your code:

def get_path(self):
    ...
    self.path_label = ...
    ...
    
def main_gui_setup(self):
    main_win = ttk.Frame(self.root)
    main_win.pack()
    open = ttk.Button(main_win, text="Open", width=10, command=self.get_path)
    open.pack()

    # Problematic code:
    # main_label = ttk.Label(main_win, self.path_label)
    # main_label.pack()

When main_gui_setup is called it creates a frame and a button inside it. When the button is clicked it calls get_path which sets up the path_label variable. The problem that you were facing is that that as soon as you create your button (without waiting for the button to be pressed), you create the label called main_label.

For a simple fix to your problem try this:

def get_path(self):
    ...
    self.file_path = ...
    self.path_label = ...
    ...

def button_callback(self, main_win):
    # set up
    self.get_path()
    # My guess is that you wanted `self.file_path` here instead of `self.path_label`
    main_label = ttk.Label(main_win, self.file_path)
    main_label.pack()

def main_gui_setup(self):
    main_win = ttk.Frame(self.root)
    main_win.pack()
    # I am using a lambda and passing in `main_win` because you haven't
    # assigned it to `self` using `self.main_win = ...`
    open = ttk.Button(main_win, text="Open", width=10, command=lambda: self.button_callback(main_win))
    open.pack()
TheLizzard
  • 7,248
  • 2
  • 11
  • 31
  • that `open` variable name should be avoided tho since it is a reserved python keyword. – Matiiss Apr 10 '21 at 21:32
  • @Matiiss if `open` isn't going to be called in that function I don't get what the problem is. Also it isn't a key word. `is`, `for`, `in` are python keywords. `open`, `print`, `input` are builtin python functions. – TheLizzard Apr 10 '21 at 21:38
  • It sounds like the callback is the way to go. I will look into it tomorrow. Thank you everyone for your comments. I'll try to be less confusing next time! – Bert Apr 11 '21 at 01:11
0

I am still confused by what You are trying to accomplish:

from tkinter import Tk, Button, Label, filedialog


class MainWindow(Tk):
    def __init__(self):
        Tk.__init__(self)

        self.open_file_btn = Button(self, text='Open', command=self.open_file)
        self.open_file_btn.pack()
        
        self.file_name = None

    def open_file(self):
        self.file_name = filedialog.askopenfilename()
        Label(self, text=self.file_name).pack()


root = MainWindow()
root.mainloop()

I will explain what will happen here! (You can change the .askopenfilename() attributes obviously).

When the program opens the filedialog and You select a file, that file name will now get assigned to self.file_name and that variable can be used anywhere in the class.

also from what I have seen You should learn more about classes and PEP 8

Matiiss
  • 5,970
  • 2
  • 12
  • 29
  • That sentence is very long, mind breaking it down? Also in this case `self.file_name = None` is useless because before it's accessed by `Label(self, text=self.file_name)` it is set to `filedialog.askopenfilename()`. – TheLizzard Apr 10 '21 at 21:01
  • I did shorten the sentence. About that `self.file_name`: I always thought it was a PEP 8 violation to not define a variable inside `__init__` which it isn't as I just looked up on why my editor may throw that "warning" and found this https://stackoverflow.com/questions/19284857/instance-attribute-attribute-name-defined-outside-init , also this https://en.wikipedia.org/wiki/Principle_of_least_astonishment so really I guess it is not that important but I am too lazy to turn off that "warning" in my editor and I am already used to it – Matiiss Apr 10 '21 at 21:38
  • By that logic all python classes should start with `__slots__ = ...` For more info on `__slots__` read [this](https://stackoverflow.com/questions/472000/usage-of-slots). I think that if your documentation is good enough and you know what is happening in your code you should be fine defining class attributes outside of the `__init__` method. That is the reason why I program in python. – TheLizzard Apr 10 '21 at 21:43