Hello I think I have a python tkinter design issue. I have looked on this to structure my code better. I do not want to change the configuration of all my widgets individually and would like to use parent_widget.winfo_children() command that I found from this question.
I am wondering is there a better way to not individually configure the widgets and yet update their font and Style.
Here is my code and the current behavior:
class TabOne(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.tab1_note = ttk.Notebook(self,width=parent.winfo_screenwidth(), height=parent.winfo_screenheight())
tab1_open_observations = ttk.Frame(self.tab1_note)
tab1_closed_observations = ttk.Frame(self.tab1_note)
self.tab1_note.add(tab1_open_observations, text= "Open Projects")
self.tab1_note.add(tab1_closed_observations, text= "Closed/Deferred Projects")
self.tab1_note.pack()
self.tab_two_load(tab1_open_observations)
self.tab_three_load(tab1_closed_observations)
widget_list = []
widget_list.extend(tab1_open_observations.winfo_children())
widget_list.extend(tab1_closed_observations.winfo_children())
for wid in widget_list:
try:
wid.configure(font = 'helvetica 12')
except:
pass
def tab_one_load(self,tab1_refresh_db):
def tab_two_load(self,tab1_open_observations)
class TabTwo(Frame):
class TabThree(Frame):
class MainWindow(Frame):
def __init__(self, window, **kwargs):
Frame.__init__(self, window, **kwargs)
self.load_ui()
def load_ui(self):
self.note = ttk.Notebook(self,width=self.window.winfo_screenwidth()-(2*self.pad), height=self.window.winfo_screenheight()-(2*self.pad))
self.tab1 = TabOne(self.note)
self.tab2 = TabTwo(self.note)
self.tab3 = TabThree(self.note)
self.note.pack()
def main():
window = Tk()
window.title('Productivity Tool')
app = MainWindow(window)
app.pack(side="top", fill="both", expand=True)
window.mainloop()
if __name__ == '__main__':
main()
Current behavior for dependent drop down lists (code below):
The second list does not overlap if I add project_module_dropdown.configure(font='helvetica 12') below every dependent OptionsMenu, Here is more code for the function tab_two_load()
def tab_two_load(self,tab1_open_observations):
def update_modules(a,b,c):
proj_mo_names = [module[0] for module in project_modules]
proj_mod_select.set(proj_mo_names[0])
project_module_dropdown = OptionMenu(tab1_open_observations,proj_mod_select,*proj_mo_names)
project_module_dropdown.configure(font='helvetica 12')
project_module_dropdown.grid(row=2, column=1,padx=10, pady=10)
proj_select = StringVar(tab1_open_observations,value='Default Text')
proj_select.trace('w',update_modules)
proj_mod_select = StringVar(tab1_open_observations,value='Default Text')
proj_mod_select.trace('w',update_teams)
proj_mod_select.trace('w',update_artifacts)
proj_names = [project[1] for project in projects]
proj_select.set(proj_names[0])
project_dropdown = OptionMenu(tab1_open_observations,proj_select,*proj_names)
project_dropdown.grid(row=1,column=1,padx=10,pady=10)
I think the problem is with how I have structured my code but i believe i have compartmentalized the code well but I am open to suggestions. this is not exactly a code review problem. My problem is overlapping drowdowns, but I feel that I have some duplication in my code that I would like to avoid. Any help is great. Thanks.
I would like this as my desired behavior without adding project_module_dropdown.configure(font='helvetica 12'):
Related
I'm trying to program a application that carries over user inputs from one page to the other where the pages are separated by classes. A problem I'm having is that the array output on page 2 isn't updating. Basically the output is just [] which is just the starting initialized variable. I've tried some solutions I've found on stack overflow such as calling it a global variable again at the init bit of the PageTwo class as well as trying to use PageOne.overall_data and using self.overall_data but the problem persists although there weren't any errors. This is a part of the code before I tried anything. I tried to cut out the irrelevant bits but I'm not sure if I cut out too much, feedback is much appreciated. Thanks for reading!
import tkinter as tk
import tkinter_nav as tknav
from tkinter import ttk
import numpy as np
class App(tknav.Wrapper):
def __init__(self):
tknav.Wrapper.__init__(
self,
pages=[PageOne, PageTwo],
start_state={'previous_page': None}
)
self.geometry('450x450')
self.show_page('page_one')
class PageOne(tknav.Page):
def __init__(self, parent):
tknav.Page.__init__(self, parent, 'page_one')
player_details = []
global overall_data
overall_data = []
my_notebook = ttk.Notebook(self)
my_notebook.pack(pady = 15)
my_notebook.pack(fill="both", expand=True)
my_frame1 = tk.Frame(my_notebook, width = "500", height = "500")
def submit(): #called every time inputs are made to be appended to player_details then overall_data
player_details = []
global overall_data
player_details.append(name.get())
player_details.append(health.get())
player_details.append(ac.get())
player_details.append(initiative.get())
overall_data.append(player_details)
overall_data = sorted(overall_data, key = lambda x:x[3])
print(str(overall_data))
class PageTwo(tknav.Page): #second page of the application
def __init__(self, parent):
tknav.Page.__init__(self, parent, 'page_two')
tk.Label(
self,
text='Page Two'
).pack()
tk.Button(
self,
text='Navigate to Page One',
command=lambda: self.__navigate(),
).pack()
line21 = tk.Label(self, text = str(overall_data), font = ('Times New Roman', 12))
line21.place(x = 30, y = 30, width = 100, height = 25)
def __navigate(self):
print('navigating to page one')
self.navigate('page_one')
if __name__ == '__main__':
App().mainloop()
You can put your data at the class level in PageOne like this:
class PageOne(tknav.Page):
overall_data = []
and use it everywhere like this:
PageOne.overall_data.append(player_details)
Since you have used tkinter_nav, you can use its provided app_state to share data between pages:
class App(tknav.Wrapper):
def __init__(self):
tknav.Wrapper.__init__(
self,
pages=[PageOne, PageTwo],
start_state={'previous_page': None, 'overall_data': []} # added 'overall_data'
)
self.geometry('450x450')
self.show_page('page_one')
Then you can access this shared data in each page by:
self.app_state['overall_data']
In my tkinter project I have 2 classes namely input and search in my code. Both these classes are working well individually and contain a bunch of sub-pages under them through which I'm able to navigate. However I'm not able to switch between the 2 classes. As my project is rather large I have provided my approach as a general code below.
InputOrSearch = False
class Input: # class 1
[...]
class Search: # class 2
def __init__(self, screen):
self.screen = screen
def CheckPage(self, page, optmenu=None):
if page == 1:
self.Clear()
self.search_menu()
def Clear(self):
for widget in self.screen.winfo_children():
widget.destroy()
[...]
inputscreen = Input(gui)
searchscreen = Search(gui)
def inputorsearch():
if not InputOrSearch:
inputscreen.CheckPage(1)
else:
searchscreen.CheckPage(1)
while True:
inputorsearch()
gui.mainloop()
This is the approach I have used and although this leads correctly to Input it doesn't seem to be working for Search for some reason.
The easest solution is to make each of your classes a subclass of Frame. You can then easily switch between them by destroying one and creating an instance of the other, or creating them all at startup and then hiding one and showing the other.
import tkinter as tk
class Input(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
label = tk.Label(self, text="I am Input.")
label.pack(side="top", fill="both", expand=True)
class Search(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
label = tk.Label(self, text="I am Search.")
label.pack(side="top", fill="both", expand=True)
def inputorsearch():
if not InputOrSearch:
searchscreen.pack_forget()
inputscreen.pack(fill="both", expand=True)
else:
inputscreen.pack_forget()
searchscreen.pack(fill="both", expand=True)
gui = tk.Tk()
inputscreen = Input(gui)
searchscreen = Search(gui)
InputOrSearch = True
inputorsearch()
gui.mainloop()
I want to hide/remove all the buttons from my window (temporarily) with the "hide_widgets" function so I can put them back after but its just not working for me, I have tried using grid_hide() and destroy() and anything I have tried so for from searching stackoverflow as not worked either.
Here is my program so far:
from tkinter import *
class Application(Frame):
#GUI Application
def __init__(self, master):
#Initialize the Frame
Frame.__init__(self,master)
self.grid()
self.create_widgets()
def create_widgets(self):
#Create new game etc...
#Title
self.title = Label(self,text = "Gnome")
self.title.grid()
#New Game
self.new_game = Button(self,text = "New Game")
self.new_game ["command"] = self.create_new_game
self.new_game.grid()
#Load Game
self.load_game = Button(self,text = "Load Game")
self.load_game ["command"] = self.display_saves
self.load_game.grid()
#Settings
self.settings = Button(self,text = "Settings")
self.settings ["command"] = self.display_settings
self.settings.grid()
#Story
self.story = Button(self,text = "Story")
self.story ["command"] = self.display_story
self.story.grid()
#Credits
self.credits = Button(self,text = "Credits")
self.credits ["command"] = self.display_credits
self.credits.grid()
def hide_widgets(self):
#clear window
new_game.grid_forget()
def create_new_game(self):
#Create new game file
self.hide_widgets
self.instruction = Label(self, text = "Name World:")
self.instruction.grid()
self.world_name = Entry(self)
self.world_name.grid()
def display_saves(self):
#display saved games and allow to run
print("saves")
def display_settings(self):
#display settings and allow to alter
print("settings")
def display_story(self):
#display story
print("story")
def display_credits(self):
#display credits
print("credits")
root = Tk()
root.title("Welcome")
width, height = root.winfo_screenwidth(), root.winfo_screenheight()
root.geometry('%dx%d+0+0' % (width,height))
app = Application(root)
root.mainloop()
Thank you in advance.
You can hide the Buttons by calling each one's grid_forget() method.
To make that easier you might want to create a self.buttons list or dictionary that contains them all.
Alternatively there's also a grid_slaves() method you might be able to use on the Application instance that will give you a list of all the widgest it manages (or just the ones in a specified row or column). The Buttons should be in one of these lists. I've never used it, so I don't know how easy it would be to identify them in the list returned however.
Ok I got it working now, silly me forgot "()" in self.hide_widgets(), i just never thought about it because there was no error as it was creating a variable instead.
Have you tried replacing new_game.grid_forget() with self.new_game.grid_forget()?
Check this answer out for an explanation as to why self needs to be referenced explicitly. I ran a very simple script to test this behavior and it worked fine.
I was playing around with some Tkinter code that I found online:
from Tkinter import *
class ScrolledList(Frame):
def __init__(self, options, parent=None):
Frame.__init__(self, parent)
self.pack(expand=YES, fill=BOTH)
self.makeWidgets(options)
def handleList(self, event):
index = self.listbox.curselection()
label = self.listbox.get(index)
self.runCommand(label)
def makeWidgets(self, options):
sbar = Scrollbar(self)
list = Listbox(self, relief=SUNKEN)
sbar.config(command=list.yview)
list.config(yscrollcommand=sbar.set)
sbar.pack(side=RIGHT, fill=Y)
list.pack(side=LEFT, expand=YES, fill=BOTH)
pos = 0
for label in options:
list.insert(pos, label)
pos = pos + 1
list.bind('<Double-1>', self.handleList)
self.listbox = list
def runCommand(self, selection):
print 'You selected:', selection
if __name__ == '__main__':
options = map((lambda x: 'Lumberjack-' + str(x)), range(20))
ScrolledList(options).mainloop()
My question is: where is the frame created? I don't see anything like:
F1 = Tkinter.Frame()
Say if I wanted to add a label it would be:
label = Tkinter.Label(F1)
I'm looking into being able to add labels, and destroy the whole window when done (most likely add frame.destroy() line after print selection but I don't know what to address in that code).
Frames don't have titles; this works because Tkinter automagically creates a Tk instance the first time any widget is created and Tkinter detects that the root window hasn't been created yet (HT #BryanOakley). If you want to alter the window title, explicitly create a Tk instance and provide it as the parent to the ScrolledList:
if __name__ == '__main__':
options = map((lambda x: 'Lumberjack-' + str(x)), range(20))
app = Tk()
app.title('Demo')
ScrolledList(options, parent=app)
app.mainloop()
In many ways this is better, as it's easier to understand what's going on.
my question is were [sic] is the frame created?
A ScrolledList is a Frame, that's the whole point of inheritance (class ScrolledList(Frame): means "define a new class ScrolledList that inherits its behaviour from Frame"). So the frame is created by ScrolledList(...).
As #jonrsharpe points out, a ScrolledList is a Frame because the class is derived from it. The base Frame class is initialized in the first line of the ScrolledList.__init__() method:
class ScrolledList(Frame):
def __init__(self, options, parent=None):
Frame.__init__(self, parent) # <- calls base class constructor
...
Also, frames don't have a titles, so the closest way to have one is to add it to the window the frame is inside of. This can be done by explicitly creating the root window so you have a reference to it, use that to set its title, and then pass the window explicitly as the ScrolledList's parent:
if __name__ == '__main__':
root = Tk()
root.title('MyTitle')
root.minsize(200, 200) # also added so title is visible
options = map((lambda x: 'Lumberjack-' + str(x)), range(20))
ScrolledList(options, root) # <- Passes root window as the parent
root.mainloop()
The program I am working with currently requires input from the user to be displayed on the programs window. I have researched on both the internet and stackoverflow, discovering several solutions to my problem, but none seem to work. My goal is to receive input from the user via Python's tkinter entry widget and display the results in a new label, while taking out my initial one and the entry box, however, the program is rejecting my attempts at an answer.
What strategy, lines of code/library's, or pieces of advice do you have for me to accomplish my goal?
My existing solutions:
.get()
textvariable=self.entdat
Existing code is as follows:
from Tkinter import *
import time
class Input(Frame):
def __init__(self, parent=None, **kw):
Frame.__init__(self, parent, background="white")
self.parent = parent
self.initUI()
self.entdat = StringVar
self.timestr = StringVar()
self.makeWidgets()
def makeWidgets(self):
self.ol = Label(text="Objective:")
self.ol.pack(side=TOP)
self.ew = Entry()
self.ew.pack(side=TOP)
self.b = Button(text="OK", command=self.clicked)
self.b.pack(side=TOP)
def clicked(self):
self.entdat = self.ew.get()
self.dat = Label(textvariable=self.ew.get())
self.dat.pack(side=TOP)
self.hide_Widget()
def hide_Widget(event):
event.ew.pack_forget()
event.ol.pack_forget()
event.b.pack_forget()
def main():
root = Tk()
root.geometry("240x135+25+50")
tm = Input(root)
tm.pack(side=TOP)
root.mainloop()
if __name__ == '__main__':
main()
I amended your code, so that it executes at least, and hopefully in a way you want.
from Tkinter import *
class Input(Frame):
def __init__(self, parent=None, **kw):
Frame.__init__(self, parent, background="white")
self.parent = parent
self.entdat = StringVar()
self.makeWidgets()
def makeWidgets(self):
self.ol = Label(text="Objective:")
self.ol.pack(side=TOP)
self.ew = Entry(textvariable=self.entdat)
self.ew.pack(side=TOP)
self.b = Button(text="OK", command=self.clicked)
self.b.pack(side=TOP)
def clicked(self):
self.dat = Label(self, textvariable=self.entdat )
self.dat.pack(side=TOP)
self.distroy_Widget()
def distroy_Widget(self):
self.ew.destroy()
self.ol.destroy()
self.b.destroy()
def main():
root = Tk()
root.geometry("240x135+25+50")
tm = Input(root)
tm.pack(side=TOP)
root.mainloop()
if __name__ == '__main__':
main()
Hope it helps.