Related
I have 3 modules (small, dont worry).
main_module = it has a combobox and a button. Comobobox list must be update each time a list (in module2) increases in number of names (combo values). Button calls the second window (module2)-->
myapp_second_window.py which has a entry box and another button. We write a name in the entry, push the button...voila..the list increases. In the origina app the list is created automatically when (2) is called.
Now I pass the list to a Pages.variable that is in -->
my_pages_to_connect_modules.
So, when app start I can populate combobox calling (2) to generate a Pages.variable list or populate combobox with json previously written.
The problem? --> how populate combobox while app is running. I mean, we go to (2) create a new name in entry come back to (1) and it is already there.
main_module
import tkinter as tk
from tkinter import*
from tkinter import ttk
import myapp_second_window
from myapp_second_window import SecondClass
root= Tk()
root.geometry("500x500")
root.title('myAPP_Main_Window')
class MainClass:
def __init__(self, parent,myapp_second_window):
self.parent = parent
self.my_widgets1()
def call_second_page (self):
Window2 = tk.Toplevel(root)
Window2.geometry('400x300')
myapp_second_window.SecondClass(Window2)
def my_widgets1(self):
self.field1_value = StringVar()
self.field1 = ttk.Combobox(self.parent, textvariable=self.field1_value)
self.field1['values'] = [1,2] # Pages.variable comes Here
self.field1.grid( row=0, column=0)
self.myButton = tk.Button(self.parent, text = "Call Second module", command = self.call_second_page)
self.myButton.grid(row=2, column=0)
if __name__ == '__main__':
app = MainClass(root, myapp_second_window)
root.mainloop()
myapp_second_window.py
import tkinter as tk
from tkinter import*
from tkinter import ttk
root= Tk()
root.minsize(550,450)
root.maxsize(560,460)
root.title('myAPP_Second_Window')
class SecondClass:
def init(self, parent):
self.parent = parent
self.my_widgets()
self.names = []
def my_widgets(self):
mylabel = Label(self.parent, text='Insert new name in next widget:')
mylabel.grid(column=0, row=0, sticky=W, pady=3)
button1 = tk.Button(self.parent, text="Click to enter Names in list", command=self.addToList)
button1.grid(column=3, row=0, sticky=W, pady=3)
self.name = StringVar()
valueEntry = tk.Entry(self.parent, textvariable= self.name)
valueEntry.grid(row=1, column=0, sticky=W, pady=3)
def addToList(self):
self.names.append(self.name.get())
print('listentries', self.names)
Pages.list_of_names = self.names
my_pages_to_connect_modules.
class Pages():
list_of_names = " "
It`s been challenging to me, every help is welcome. But please dont say just that I must update main window, I need to know how. Thanks to all of you.
Hi there (this is my first question)
I am building an app with Tkinter as the GUI. I want multiple frames to expand to fill out the entire root window.
With the code below, I expected the bottom (green) frame to expand all the way up to the top (cyan) frame. Instead, it stays at the bottom, and there is a "frame-less" white area between the two frames.
screenshot of an actual result when code is run
This is the code, I am executing (methods that do not mess with frame layout has been shortened out):
class CreateWindow:
def __init__(self, master, screen):
self.master = master
self.master.geometry('300x400')
self.master.title("THE PROGRAM")
self.screen = screen
self.menu_bar = Menu(self.master)
self.setup_menu = Menu(self.menu_bar)
self.setup_bar()
self.main_menu = Menu(self.menu_bar)
self.main_bar()
self.diary_menu = Menu(self.menu_bar)
self.diary_bar()
self.master.config(menu=self.menu_bar)
# self.master.grid_columnconfigure(0, weight=1) # What is difference between these two and the two below?
# self.master.grid_rowconfigure(0, weight=1)
self.master.columnconfigure(0, weight=1)
self.master.rowconfigure(0, weight=1)
self.top_menu(self.master) # TODO: Make this menu actively do stuff
if self.screen == "setup":
setup = SetupScreen(self.master)
elif self.screen == "main":
setup = MainScreen(self.master)
elif self.screen == "diary":
setup = DiaryScreen(self.master)
else:
raise TypeError("wrong screen")
def setup_bar(self): ...
def main_bar(self): ...
def diary_bar(self): ...
def top_menu(self, window): # Defines top frame : placeholder for future menu
top = tk.Frame(window, bg='cyan', pady=5)
top.grid(row=0, sticky='new')
button = tk.Button(top, text="Setup", command=self.do_nothing)
button.grid(row=0, column=0)
button = tk.Button(top, text="Main", command=self.do_nothing)
button.grid(row=0, column=1)
button = tk.Button(top, text="Diary", command=self.do_nothing)
button.grid(row=0, column=2)
top.columnconfigure(0, weight=1)
top.columnconfigure(1, weight=1)
top.columnconfigure(2, weight=1)
def do_nothing(self): ...
def b_exit(self): ...
"""This class contains methods, that create and manage the setup screen.
I want the green frame to expand all the way up to the cyan (top menu) """
class SetupScreen(CreateWindow):
def __init__(self, master):
self.master = master
self.menu = tk.Frame(self.master, bg='green')
self.menu.grid(row=1, sticky='new')
self.menu.columnconfigure(0, weight=1) # Again, what is difference between 'grid_'or not?
self.menu.grid_rowconfigure(1, weight=1) #I have tried setting index to both 0 and 1, no difference
self.create_buttons()
def create_buttons(self): ...
def personal_details(self): ...
def start_new(self):
pass
if __name__ == "__main__":
files = FileHandler() #Class meant to be handling file operations - currently only sets a boolean to false, that makes the app start with setup screen
ap = files.active_program
print(ap)
root = tk.Tk()
if not files.active_program: #based on the boolean from FileHandler class, this starts the setup screen
top_menu = CreateWindow(root, "setup")
else:
top_menu = CreateWindow(root, "main")
root.mainloop()
It looks like you're trying to create a notebook widget with several tabs.
So I would suggest you use ttk.Notebook instead of re-inventing it yourself.
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?
Click "Upload a video" and select an MP4 file.
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()
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:
Use the same variable in both SelectROIPage and StartPage.
Update the variable when the button is clicked by extending readyForROI (a bit kludgy in my opinion)
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 ...
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.
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.
You can call .grid() and .pack() directly after declaring a label if you don't need access to the widget in the future.
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.
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.
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())
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)
My understanding is that the in_ keyword argument to pack/grid should allow me to specify the managing widget. I want to pack arbitrary widgets inside a Frame subclass, so I passed the widgets and packed them during intialization, but the widgets didn't appear (although space in the window appears to have been allocated...). If I create the widget internally using master which is root, there is no issue and the widgets are displayed as expected.
The following working example and its output demonstrate the issue:
import tkinter as tk
from tkinter import ttk
class ItemContainerExternal(ttk.Frame):
def __init__(self, master, input_label, input_object):
ttk.Frame.__init__(self, master)
self.label = input_label
self.label.pack(side=tk.LEFT, padx=5, pady=3, fill=tk.X, in_=self)
self.input_object = input_object
self.input_object.pack(side=tk.LEFT, padx=5, pady=3, fill=tk.X, in_=self)
def get(self):
return variable.get()
class ItemContainerInternal(ttk.Frame):
def __init__(self, master):
ttk.Frame.__init__(self, master)
ttk.Label(master, text='internal').pack(side=tk.LEFT, padx=5, pady=3, fill=tk.X, in_=self)
self.input_object = ttk.Entry(master)
self.input_object.pack(side=tk.LEFT, padx=5, pady=3, fill=tk.X, in_=self)
def get(self):
return variable.get()
if __name__ == '__main__':
root = tk.Tk()
inputobj = ttk.Entry(root)
inputlabel = ttk.Label(root, text='external')
ItemContainerExternal(root, inputlabel, inputobj).grid(row=0, column=0)
ItemContainerInternal(root).grid(row=1, column=0)
root.mainloop()
The problem is that you're creating the entry and label before you're creating the frame, so they have a lower stacking order. That means the frame will be on top of the entry and label and thus, obscuring them from view.
A simple fix is to call lift() on the entry and label:
class ItemContainerExternal(tk.Frame):
def __init__(self, master, input_label, input_object):
...
self.input_object.lift()
self.label.lift()
The order in which widgets get created matters. Newer widgets are "on top of" previous widgets.
Call .lower() on the Frame after you create it, assuming it's created after all the widgets that you will pack into it. If not, you'll need to either call .lower() again on the Frame after creating a new widget to go inside it, or you'll have to raise the new widget via .lift() as per Bryan's answer.
I'm writing a python code with tkinter (python3) but I have some problems. I have two classes _MainScreen and _RegisterScreen (this last is nested in _MainScreen). In _RegisterScreen I had implemented a simple question with tkinter.Radiobutton (choose your sex: male, female). The idea is to catch the user selection, but when I run the script, the value assigned to the variable is empty (""). However, if I run the class _RegisterScreen alone, it works. I hope you can show me where is my error. Thanks in advance.
Here is an abstraction (32 lines) of my code (250 lines):
import tkinter
class _MainScreen(tkinter.Frame):
def __init__(self):
self.root = tkinter.Tk()
self.new_account(self.root)
self.root.mainloop()
def new_account(self, frame):
tkinter.Button(frame, text="Create new account",
command=self.create_new_account).pack(anchor="center", pady=(0,15))
def create_new_account(self):
_RegisterScreen()
class _RegisterScreen(tkinter.Frame):
def __init__(self):
self.root = tkinter.Tk()
tkinter.Label(self.root, text="Sex").grid(row=1, padx=(0,10), sticky="w")
self.sex_option = tkinter.StringVar()
tkinter.Radiobutton(self.root, text="Male", variable=self.sex_option,
value="Male", command=self._selected).grid(row=1, column=1)
tkinter.Radiobutton(self.root, text="Female", variable=self.sex_option,
value="Female", command=self._selected).grid(row=1, column=2)
tkinter.Button(self.root, text="Submit",
command=self._login_btn_clickked).grid(row=3, columnspan=4, pady=20)
self.root.mainloop()
def _login_btn_clickked(self):
sex = self._selected()
print(sex)
def _selected(self):
return self.sex_option.get()
_MainScreen()
#_RegisterScreen() # comment the above line and uncomment this line
# to test the _RegisterScreen object alone.
After doing some research on how tkinter's RadioButton widget works, I believe I have a solution to your problem:
Here's your new _RegisterScreen function:
class _RegisterScreen(tkinter.Frame):
def __init__(self):
self.gender = "NA" #Variable to be changed upon user selection
self.root = tkinter.Tk()
tkinter.Label(self.root, text="Sex").grid(row=1, padx=(0,10), sticky="w")
self.sex_option = tkinter.StringVar()
#This Radiobutton runs the setMale function when pressed
tkinter.Radiobutton(self.root, text="Male", variable=self.sex_option,
value="Male", command=self.setMale).grid(row=1, column=1)
#This Radiobutton runs the setFemale function when pressed
tkinter.Radiobutton(self.root, text="Female", variable=self.sex_option,
value="Female", command=self.setFemale).grid(row=1, column=2)
tkinter.Button(self.root, text="Submit",
command=self._login_btn_clickked).grid(row=3, columnspan=4, pady=20)
self.root.mainloop()
def _login_btn_clickked(self):
sex = self.gender #gets the value stored in gender and assigns it to sex
print(sex)
def setMale(self):
self.gender="Male" #sets gender to Male
def setFemale(self):
self.gender="Female" #sets gender to Female
Ultimately, you want to run 2 separate functions for either RadioButton.
When the Male Radiobutton gets clicked, it runs the setMale function.
When the Female Radiobutton gets clicked, it runs the setFemale function.
I believe you were confused about what RadioButton's variable and value attributes actually are (as was I before looking further into this).
I learned more about what those those do in this video: https://www.youtube.com/watch?v=XNF-y0QFNcM
I hope this helps! ~Gunner