Tkinter - Передача переменных между кадрами после askopenfilename? - PullRequest
0 голосов
/ 02 февраля 2020

Новый для Tkinter. Я был в этом в течение нескольких дней. Я пытаюсь передать путь к файлу в формате Mp4 (полученный с помощью askopenfilename и кнопки) в другой кадр (где я беру первый кадр и отображаю его, чтобы пользователь мог выбрать область интереса).

ОБНОВЛЕНО! МИНИМАЛЬНЫЙ, ДЕЙСТВУЮЩИЙ ПРИМЕР: ВЫПОЛНЯЯ ЭТОТ КОД, ВЫБРАННЫЙ ФИЛИАЛ НЕ ОТОБРАЖАЕТСЯ НА ВТОРОЙ ФРЕЙМЕ (ПРОБЛЕМА):

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

Как воспроизвести?

  1. Нажмите «Загрузить видео» и выберите файл MP4.
  2. Нажмите «Нажмите меня после загрузки видео»

Для некоторых причина, переменная не обновляется после вызова askopenfilename. Я пытался использовать глобальные переменные, используя root .update, ничего не помогло (см. Мои попытки закомментированы)

Любая помощь очень ценится, спасибо!

ОРИГИНАЛЬНЫЙ КОД: спасибо за ваши предложения по его упрощению :)

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

Ответы [ 3 ]

0 голосов
/ 02 февраля 2020

Вы никогда не обновляете значение в SelectROIPage. Страница создается один раз, и в этом случае вы инициализируете файл в '' с помощью file = startpg.filename.get(), потому что это происходит до нажатия кнопки при создании HRDetectorApp в frame = F(container, self)

Когда вы нажимаете кнопку , он не создает новый фрейм, поэтому не вызывает, поэтому он не обновляется, и значение является пустой строкой. Вы должны каким-то образом обновить значение при нажатии первой кнопки.

Я вижу несколько вариантов:

  1. Используйте одну и ту же переменную как в SelectROIPage, так и в StartPage.
  2. Обновите переменную при нажатии кнопки, расширив readyForROI (немного глупо, на мой взгляд)
  3. Обновите переменную, когда кадр ROI отображается с использованием привязки (перебор, но смотрите: Как бы я сделал метод, который запускается каждый раз, когда кадр отображается в tkinter )

Если бы это был я, я бы выбрал первый.

UPDATE Это немного урезано, но это все еще не то, что я бы назвал «минимально воспроизводимым примером».

Я немного отредактировал ваш код, чтобы выполнить sh то, что вы пытаетесь сделать. Я также хотел указать на несколько других вопросов, поэтому он не совсем минимален.

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

Итак, давайте обсудим это немного ...

  1. Во-первых, вам не нужно объявлять переменную global, чтобы это работало; это больше относится к многопоточности, то есть к глобальной переменной могут обращаться все потоки в программе.
  2. Вы также создали вторую root с root = tk.Tk (). Важно, чтобы в вашей программе был только один root (экземпляр tk.Tk ()).
  3. Вы можете вызывать .grid () и .pack () сразу после объявления метки, если вы не Вам не нужен доступ к виджету в будущем.
  4. Фактическая проблема, с которой вы столкнулись. Я имел в виду предоставление ссылки на одну и ту же переменную в обоих классах. Вы были близки с shared_data ['filename'], но вы никогда не звонили и не назначали его! Оба класса имеют доступ к переменной (через контроллер), и, следовательно, они могут получить доступ к значению переменной.
  5. Преимущество использования переменных tkinter, а не переменных pythong - это обратные вызовы и трассировки. Если вы используете свойство text метки, он присвоит метке строку stati c. Вторая созданная метка iv'e использует свойство textvariable. Когда вы присваиваете этому свойству переменную tkinter, текст метки будет автоматически обновляться при каждом изменении переменной . Вы можете усложнить то, что происходит, используя метод .trace() переменных tkinter для вызова функции всякий раз, когда она написана.

Что касается минимальных воспроизводимых примеров ... то, что я ожидал это программа, которая создает два фрейма, где нажатие кнопки в одном фрейме (StartPage) обновляет строковую переменную в другом фрейме (SelectROIPage), причем каждый из них имеет один и тот же родительский фрейм (HRDetectorApp). Я не ожидал бы, что это будет больше чем 20-30 линий.

0 голосов
/ 03 февраля 2020

Вы можете привязать событие <Expose> к классу SelectROIPage и обновить метку в обратном вызове события (которое вызывается, когда отображается SelectROIPage):

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())
0 голосов
/ 02 февраля 2020

В ваших методах вы ссылаетесь на глобальные filename. Но в глобальном масштабе вы не создаете filename.

Если бы вы это сделали (скажем, прямо перед app = HRDetectorApp()), это должно сработать.

Обновление

В зависимости от вашего MVE, вот что происходит.

Вы создаете объект SelectROIPage в методе HRDetectorApp.__init__. В то время , глобальная переменная filename указывает на пустую строку.

Что бы я предложил, чтобы создать syntheti c событие с именем eg <<Update>>. В классе SelectROIPage вы определяете метод on_update, который устанавливает текст метки. В SelectROIPage.__init__ вы bind метод on_update для события <<Update>>.

В обработчике для нажатия goButton в StartPage вы вызываете метод event_generate для SelectROIPage объект для отправки ему сообщения. (Для этого необходимо, чтобы StartPage имел атрибут для объекта SelectROIPage)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...