0

New to Tkinter. I've been at this for a few days now. I'm looking to pass the file path of an Mp4 video (retrieved using askopenfilename and a button) to another frame (where I grab the first frame and display it, so the user can select a ROI).

UPDATED! MINIMAL, VIABLE EXAMPLE: RUNNING THIS CODE, THE FILENAME CHOSEN DOES NOT DISPLAY ON THE SECOND FRAME (PROBLEM):

LARGE_FONT=("Verdana",12)

import tkinter as tk
from tkinter import filedialog 
from tkinter import *


filename = ''


class HRDetectorApp(tk.Tk): #in brackets, what the class inherits

    def __init__(self,*args,**kwargs): #this will always load when we run the program. self is implied args = unlimited vars. kwargs are keywords arguments (dictionaries)

        tk.Tk.__init__(self,*args,**kwargs)
        container = tk.Frame(self)

        container.pack(side="top", fill="both", expand=True) #pack into top, fill into entire top space, and expand will expand into the whole window. fill into the area you packed.

        container.grid_rowconfigure(0, weight=1) #min size zero, weight is priority
        container.grid_columnconfigure(0,weight=1)

        self.frames = {}
        self.shared_data = {"filename": tk.StringVar()}

        for F in (StartPage, SelectROIPage):

            frame = F(container,self)

            self.frames[F] = frame

            frame.grid(row=0,column=0, sticky="nsew") #sticky = alignment + stretch, north south east west

        self.show_frame(StartPage)
        self.title("Heart Rate Detection")


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

    def get_page(self, page_class):
        return self.frames[page_class]



def openFile():
    root = tk.Tk()
    global filename 
    filename = filedialog.askopenfilename(title="Select an Mp4 Video", filetypes =(("Mp4 Files", "*.mp4"),))
    root.update_idletasks()
    print(filename)


class StartPage(tk.Frame):


    def __init__(self,parent,controller):
        self.controller = controller
        #global filename
        #filename = ""
        tk.Frame.__init__(self,parent)
        label = tk.Label(self,text="Start Page", font=LARGE_FONT)
        label.pack(pady=10,padx=10)
        self.filename = tk.StringVar()

        chooseFileButton = tk.Button(self,text="Upload a Video",command=openFile)
        chooseFileButton.pack()

        goButton = tk.Button(self,text ="Click me after you've uploaded a video!", command = lambda: controller.show_frame(SelectROIPage))
        goButton.pack()



class SelectROIPage(tk.Frame):

    def __init__(self,parent,controller):

        tk.Frame.__init__(self,parent)
        label = tk.Label(self,text="Select a Region of Interest (R.O.I)", font=LARGE_FONT)
        label.pack(pady=10,padx=10)
        label = tk.Label(self, text = "selected file : " + filename)
        label.pack()



app = HRDetectorApp()
app.mainloop()

How to reproduce?

  1. Click "Upload a video" and select an MP4 file.
  2. Click "Click me after you've uploaded a video"

For some reason, the variable doesn't update after calling askopenfilename. I've tried to use global variables, using root.update, nothing has worked (see my attempts commented out)

Any help is greatly appreciated, thank you!

ORIGINAL CODE : thanks for your suggestions to simplify it :)

LARGE_FONT=("Verdana",12)

import tkinter as tk
from tkinter import filedialog 
from tkinter import *
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.widgets import RectangleSelector
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import cv2
import numpy as np
import sys
import time
from scipy import signal
import scipy.signal as signal
import selectinwindow


class HRDetectorApp(tk.Tk): #in brackets, what the class inherits

    def __init__(self,*args,**kwargs): #this will always load when we run the program. self is implied args = unlimited vars. kwargs are keywords arguments (dictionaries)

        tk.Tk.__init__(self,*args,**kwargs)
        container = tk.Frame(self)

        container.pack(side="top", fill="both", expand=True) #pack into top, fill into entire top space, and expand will expand into the whole window. fill into the area you packed.

        container.grid_rowconfigure(0, weight=1) #min size zero, weight is priority
        container.grid_columnconfigure(0,weight=1)

        self.frames = {}
        self.shared_data = {"filename": tk.StringVar()}

        for F in (StartPage, SelectROIPage):

            frame = F(container,self)

            self.frames[F] = frame

            frame.grid(row=0,column=0, sticky="nsew") #sticky = alignment + stretch, north south east west

        self.show_frame(StartPage)
        self.title("Heart Rate Detection")


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

    def get_page(self, page_class):
        return self.frames[page_class]


#
#def openFile():
#    root = tk.Tk()
#    global filename 
#    root.filename = filedialog.askopenfilename(title="Select an Mp4 Video", filetypes =(("Mp4 Files", "*.mp4"),))
#    filename = root.filename 
#    root.update_idletasks()
#    #name = root.filename
#    
#    print(filename)


class StartPage(tk.Frame):


    def __init__(self,parent,controller):
        self.controller = controller
#        global filename
#        filename = ""
        tk.Frame.__init__(self,parent)
        label = tk.Label(self,text="Start Page", font=LARGE_FONT)
        label.pack(pady=10,padx=10)
        self.filename = tk.StringVar()

        chooseFileButton = tk.Button(self,text="Upload a Video",command=self.openFile)
        chooseFileButton.pack()

        goButton = tk.Button(self,text ="Click me after you've uploaded a video!", command = lambda: controller.show_frame(SelectROIPage))
        goButton.pack()

    def openFile(self):
        #root = tk.Tk()
        #global filename 
        #root.filename = filedialog.askopenfilename(title="Select an Mp4 Video", filetypes =(("Mp4 Files", "*.mp4"),))
       # filename = root.filename 
        self.filename.set(filedialog.askopenfilename(title="Select an Mp4 Video", filetypes =(("Mp4 Files", "*.mp4"),)))
        #root.update()
        #if filename == "":
            #root.after(1000,openFile(self))
        #name = root.filename

        print(self.filename.get())

**code to use rectangle selector for selecting ROI**

class SelectROIPage(tk.Frame):

    def __init__(self,parent,controller):


        tk.Frame.__init__(self,parent)
        self.controller = controller
        startpg = self.controller.get_page(StartPage)
        file = startpg.filename.get() **THIS IS NOT UPDATING**
        label = tk.Label(self,text="Select a Region of Interest (R.O.I)", font=LARGE_FONT)
        label.pack(pady=10,padx=10)

        #file=filename

        **code to read image and display it (confirmed no errors here!)**



app = HRDetectorApp()
app.mainloop()
mimss
  • 1
  • 1
  • I know it's extra work, but please try to create a minimal reproducible example (https://stackoverflow.com/help/minimal-reproducible-example) rather than posting all of your code. It's easier to provide help, and you may solve your own problem in the process. – Daniel B. Feb 02 '20 at 20:03

3 Answers3

0

In your methods you reference the global filename. But in the global scope you don't create filename.

If you were to do that (say just before app = HRDetectorApp()), it should work.

Update

Based on your MVE, this is what happens.

You are creating the SelectROIPage object in the HRDetectorApp.__init__ method. At that time, the global variable filename points to an empty string.

What I would suggest it to create a synthetic event named e.g. <<Update>>. In the SelectROIPage class, you define an on_update method that sets the label text. In SelectROIPage.__init__, you bind the on_update method to the <<Update>> event.

In the handler for clicking the goButton in StartPage, you call the event_generate method on the SelectROIPage object to send it the message. (This requires that the StartPage has an attribute for the SelectROIPage object)

Roland Smith
  • 42,427
  • 3
  • 64
  • 94
  • Hey! Thanks so much for your suggestion. Gave that a shot and it didn't work :/ To clarify... I had SOME success using the global variable approach, however the variable would only update AFTER I close the app. – mimss Feb 02 '20 at 20:29
  • Judging by the imports, your code seems to be part of a larger program, most of which we don't see. Try to create a minimum viable example that exhibits the problematic behavior. – Roland Smith Feb 02 '20 at 20:44
  • Hi just updated it with an example that shows the issue. Please have a look. thank you :) – mimss Feb 03 '20 at 00:14
0

You are never updating the value in SelectROIPage. The page is instantiated once, and in that instantiation you initialize file to '' with file = startpg.filename.get(), because this occurs before the button has been clicked when you create HRDetectorApp at frame = F(container, self)

When you click the button, it doesn't create a new frame, thus does not call so it is not updating, and the value is an empty string. You must somehow update the value when the first button is clicked.

I see a few options:

  1. Use the same variable in both SelectROIPage and StartPage.
  2. Update the variable when the button is clicked by extending readyForROI (a bit kludgy in my opinion)
  3. Update the variable when the ROI frame is shown using a binding (overkill, but see: How would I make a method which is run every time a frame is shown in tkinter)

If it were me, I would choose the first.

UPDATE It's a bit trimmed but that's still not what I'd call a "minimum reproducible example".

I've edited your code a bit to accomplish what you're trying to do. I wanted to be able to point at a few other issues as well, so it's not quite minimal.

import tkinter as tk
from tkinter import filedialog 
from tkinter import *


class HRDetectorApp(tk.Tk): #in brackets, what the class inherits
    def __init__(self,*args,**kwargs): #this will always load when we run the program. self is implied args = unlimited vars. kwargs are keywords arguments (dictionaries)

        tk.Tk.__init__(self,*args,**kwargs)
        container = tk.Frame(self)

        container.pack(side="top", fill="both", expand=True) #pack into top, fill into entire top space, and expand will expand into the whole window. fill into the area you packed.

        container.grid_rowconfigure(0, weight=1) #min size zero, weight is priority
        container.grid_columnconfigure(0,weight=1)

        self.frames = {}
        self.shared_data = {"filename": tk.StringVar()}

        for F in (StartPage, SelectROIPage):

            frame = F(container,self)

            self.frames[F] = frame

            frame.grid(row=0,column=0, sticky="nsew") #sticky = alignment + stretch, north south east west

        self.show_frame(StartPage)

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

    def get_page(self, page_class):
        return self.frames[page_class]


class StartPage(tk.Frame):
    def __init__(self,  parent: tk.Widget, controller: HRDetectorApp):
        tk.Frame.__init__(self,parent)
        self.filename = controller.shared_data['filename'] # use the same object for filename here as in SelectROIPage
        tk.Button(self,text="Click me to pick a file", command=self.openFile).pack()
        tk.Button(self,text ="Click me after you've picked a file", command = lambda: controller.show_frame(SelectROIPage)).pack()

    def openFile(self):
        self.filename.set(filedialog.askopenfilename(title="Select a file"))
        print('filename in StartPage: {}'.format(self.filename.get()))


class SelectROIPage(tk.Frame):
    def __init__(self,  parent: tk.Widget, controller: HRDetectorApp):
        tk.Frame.__init__(self,parent)
        self.filename = controller.shared_data['filename']
        tk.Label(self, text = "selected file : ").pack() # text assigns a permanent value
        tk.Label(self, textvariable=self.filename).pack()


app = HRDetectorApp()
app.mainloop()

So let's discuss this a little ...

  1. First, you don't need to declare a variable global for this to work; that's more relevant to threading, i.e. a global variable can be accessed by all threads in the program.
  2. You also instantiated a second root with root=tk.Tk(). It's important that you only ever have one root (instance of tk.Tk()) in your program.
  3. You can call .grid() and .pack() directly after declaring a label if you don't need access to the widget in the future.
  4. The actual issue you are having. What I meant was to provide a reference to the same variable in both classes. You were close with shared_data['filename'], but you never called or assigned it! Both classes have access to the variable (through controller), and thus they can access the value of the variable.
  5. The benefit of using tkinter variables rather than pythong variables is callbacks and traces. If you use the text property of the label, it will assign a static string to the label. The second label iv'e created uses the textvariable property. When you assign a tkinter variable to this property, the text of the label will automatically update whenever the variable changes. You could get more complicated in what happens by using the .trace() method of tkinter variables to call a function whenever it is written.

Regarding minimum reproducible examples ... what I would have expected is a program which creates two frames, where clicking a button in one frame (StartPage) updates a string variable in the other frame (SelectROIPage), and , each with the same parent frame (HRDetectorApp). I wouldn't expect it to be more than 20-30 lines.

Daniel B.
  • 1,254
  • 1
  • 16
  • 35
  • Hey, I updated my example - I used the same , global variable in both (as you suggested) and still no luck.. any idea what I'm still doing wrong? – mimss Feb 03 '20 at 00:14
  • @mimss I've updated my answer, hopefully that clarifies things. You don't need a global variable in the C/C++ sense. You need something more akin to a reference or pointer, so that both classes see the same data. – Daniel B. Feb 03 '20 at 04:19
  • Great! One small issue - you said that if I use textvariable, the label will automatically update (as it does). However, I need to now pass this varible to OpenCv for a series of other tasks, BUT if I call the filename in SelectROIPage by calling: filename = self.file.get() & it doesn't update. Is there a way I can force the variable to update the same way that it does when used in a label? THANK YOU! :) – mimss Feb 09 '20 at 20:19
  • I don't understand. In the example code I posted, self.filename will update, and self.filename.get() WILL return the updated filename. It is doing what you describe. In your code, you have multiple StringVariables, it's possible you're referencing the wrong one; without seeing updated code I can't say what's wrong. – Daniel B. Feb 14 '20 at 17:24
0

You can bind <Expose> event to the SelectROIPage class and update a label in the event callback (which is called when SelectROIPage is shown):

class SelectROIPage(tk.Frame):
    def __init__(self,parent,controller):
        tk.Frame.__init__(self,parent)
        self.controller = controller
        self.startpg = self.controller.get_page(StartPage)
        tk.Label(self,text="Select a Region of Interest (R.O.I)", font=LARGE_FONT).pack(pady=10,padx=10)
        self.label = tk.Label(self) # label used to show the selected filename
        self.label.pack()
        self.bind('<Expose>', self.on_expose)

    def on_expose(self, event):
        self.label.config(text='Selected file: '+self.startpg.filename.get())
acw1668
  • 40,144
  • 5
  • 22
  • 34
  • Would that trigger when raising above another frame? "This event occurs whenever at least some part of your application or widget becomes visible after having been covered up by another window." It's not another window covering the widget, but another widget... – Daniel B. Feb 03 '20 at 17:07
  • Yes, it will be triggered when it is raising above another frame as long as it is covered by another frame before. If it is already on the top, then no. – acw1668 Feb 04 '20 at 00:50
  • I tried this solution and when running my code, it just prompts the askopenfile dialog to popup BEFORE I click the button - any idea why? – mimss Feb 09 '20 at 20:10
  • @mimss I used your updated code with my solution and did not have the mentioned issue. – acw1668 Feb 10 '20 at 04:14
  • @mimss Also you should not create `Tk()` instance inside `openFile()` function. Remove `root = tk.Tk()` and `root.update_idletasks()` from `openFile()`. – acw1668 Feb 10 '20 at 04:19