get value from entry to be inside frame - python

I have frames generated automatically. these frames contain objects such as labels and only 1 entry.
I manage to identify the Entry with the following command:
for widget in FrameCalc.winfo_children():
print("widget.winfo_children()[4]", widget.winfo_children()[4])
which gives me this
.! toplevel.labels.! frame2.! entry
How can I get the value contained in the target Entry?
thank you in advance for your time

Welcome to Stack Overflow community!
In your case, you can use any of SrtingVar()(holing string), IntVar()(holing integer), DoubleVar()(holding float) or BooleanVar()(holding boolean values) depending on your requirement and assign a textvariable to the entry widget. You can then append these variables into a list and use .get() method to retrieve it's contents when required. Here's an example, using a loop to create many entries with StringVar() and getting their values later.
from tkinter import *
root = Tk()
def display(ent):
global disp, var_list
disp.set(var_list[ent].get())
var_list = []
for i in range (0, 5):
var = StringVar()
entry = Entry(root, textvariable = var)
var_list.append(var)
entry.pack()
button = Button(root, text = "Show", command = lambda ent = i: display(ent))
button.pack()
disp = StringVar()
label = Label(root, textvariable = disp)
label.pack()
root.mainloop()

I believe this is the answer you are looking for.
use isinstance() to check for the widget type
use get() to return the value
import tkinter as tk
#a dummy widget for example purposes
class DummyWidget(tk.Frame):
def __init__(self, master, t, e, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
tk.Label(self, text=t).grid(row=0, column=0)
ent = tk.Entry(self)
ent.grid(row=0, column=1)
ent.insert(0, e)
#extend root
class App(tk.Tk):
#application constants
TITLE = 'Application'
WIDTH, HEIGHT, X, Y = 800, 600, 50, 50
def __init__(self):
tk.Tk.__init__(self)
DummyWidget(self, "label 1", "entry 1").grid(row=0, column=0)
DummyWidget(self, "label 2", "entry 2").grid(row=1, column=0)
DummyWidget(self, "label 3", "entry 3").grid(row=2, column=0)
#this is the answer portion of the example
for widget in self.winfo_children():
for i, subwidget in enumerate(widget.winfo_children()):
if isinstance(subwidget, tk.Entry):
print(f'child {i} of widget', subwidget.get())
#properly initialize your app
if __name__ == '__main__':
app = App()
app.title(App.TITLE)
app.geometry(f'{App.WIDTH}x{App.HEIGHT}+{App.X}+{App.Y}')
#app.resizable(width=False, height=False)
app.mainloop()
This concept could also be turned into a utility, so you have a dynamic system of finding whatever you want, starting from wherever you want. I would definitely consider this preferable to rewriting the above multi-dimensional loop (that stops at grandchildren) every time you need to find specific instance types.
import tkinter as tk
from dataclasses import dataclass
from typing import Type
#a dummy widget for example purposes
class DummyWidget(tk.Frame):
def __init__(self, master, t, e, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
tk.Label(self, text=t).grid(row=0, column=0)
ent = tk.Entry(self)
ent.grid(row=0, column=1)
ent.insert(0, e)
#to illustrate inheritance
class DummyEntry(tk.Entry):
def __init__(self, master, text, **kwargs):
tk.Entry.__init__(self, master, **kwargs)
self.insert(0, text)
#used in Utils.GetInstancesAsDataFrom(...) to store individual widget data
#dataclass
class WidgetData_dc:
type: Type
parent: tk.Widget
childindex: int
path: str
class Utils:
""" GetInstancesFrom
deep search of every child, grandchild, etc.. for a specific widget type
#start ~ parent widget to start the search from
#wtype ~ the type of widget to find
#inst ~ used internally to pass the dictionary to this method's internal calls of itself
returns a dictionary of all found instances
"""
#staticmethod
def GetInstancesFrom(start, wtype, inst=None):
instances = dict() if inst is None else inst
for widget in start.winfo_children():
if isinstance(widget, wtype):
instances[f'{widget}'] = widget
Utils.GetInstancesFrom(widget, wtype, instances)
return instances
""" GetInstancesAsDataFrom
deep search of every child, grandchild, etc.. for a specific widget type
#start ~ parent widget to start the search from
#wtype ~ the type of widget to find
#inst ~ used internally to pass the dictionary to this method's internal calls of itself
returns a dictionary of all found instances
"""
#staticmethod
def GetInstancesAsDataFrom(start, wtype, inst=None):
instances = dict() if inst is None else inst
for i, widget in enumerate(start.winfo_children()):
if isinstance(widget, wtype):
instances[widget] = WidgetData_dc(type(widget), start, i, f'{widget}')
Utils.GetInstancesAsDataFrom(widget, wtype, instances)
return instances
#extend root
class App(tk.Tk):
#application constants
TITLE = 'Application'
WIDTH, HEIGHT, X, Y = 800, 600, 50, 50
def __init__(self):
tk.Tk.__init__(self)
#a bunch of junk instances for example purposes
DummyWidget(self, "label 1", "entry 1").grid(column=0)
DummyWidget(self, "label 2", "entry 2").grid(column=0)
DummyWidget(self, "label 3", "entry 3").grid(column=0)
DummyEntry(self, text='entry 4').grid(column=0) #this extends tk.Entry so it qualifies as a tk.Entry
#answer portion of the example
for path, widget in Utils.GetInstancesFrom(self, tk.Entry).items():
print(f'{path}: {widget.get()}')
print('') #skip a line
#alternate implementation
for widget, data in Utils.GetInstancesAsDataFrom(self, tk.Entry).items():
print(f'{data.parent}[{data.childindex}]:{data.type} has value "{widget.get()}"')
#properly initialize your app
if __name__ == '__main__':
app = App()
app.title(App.TITLE)
app.geometry(f'{App.WIDTH}x{App.HEIGHT}+{App.X}+{App.Y}')
#app.resizable(width=False, height=False)
app.mainloop()

Related

Creat and access dictionary within class

I have created a class within tkinter that builds a frame and buttons (example below).
I am also trying to create a series of radiobuttons each with a command, and rather than having to type three times the same, I am trying to use a dictionary that stores the function name and the function (again see below):
import tkinter as tk
from tkinter import ttk
from tkinter import *
class EnoxDoseCalculator:
def __init__(self, root):
root.title('Enoxaparin Dose Calculator')
indication = {'VTE': self.vte, 'Atrial Fibrilation': self.af, 'Mechanical Heart valve': self.mech}
mainframe = ttk.Frame(root)
mainframe.grid(column = 0, row = 0, sticky='nsew')
test_button = ttk.Button(mainframe, text= 'Push Mew!!', command = self.format)
test_button.grid(row = 0, column = 0)
def vte(self):
pass
def af(self):
pass
def mech(self):
pass
def format(self):
self.var3 = tk.StringVar()
for key, value in self.indication.items():
ttk.Radiobutton(mainframe, text = self.indication.key, variable = self.var3, value = self.indication.key, command = self.indication.value).grid(row = n+1, column =0, sticky = 'nswe')
root = Tk()
EnoxDoseCalculator(root)
print(dir(EnoxDoseCalculator))
root.mainloop()
However, I keep getting this message:
I get this is to do with the dictionary functions being listed before they are created, so my question is, where do I place said dictionary, because I am sure this is possible. or is it that I am using the wrong names for the function?
Try this:
class EnoxDoseCalculator:
def __init__(self, root):
self.root = root # add this (mostly for consistency/best practice)
self.root.title('Enoxaparin Dose Calculator')
self.indication = {'VTE': self.vte, 'Atrial Fibrilation': self.af, 'Mechanical Heart valve': self.mech}
self.mainframe = ttk.Frame(self.root) # update this to include 'self'
self.mainframe.grid(column=0, row=0, sticky='nsew')
# update this to include 'self'
self.test_button = ttk.Button(self.mainframe, text='Push Mew!!', command=self.format)
self.test_button.grid(row=0, column=0)
Using self to namespace your class variables to EnoxDoseCalculator

Changing one OptionMenu changes the second one

In my code below I have two option menus which are populated with the same list. In the final application the list is generated by importing a .csv file.
The user should be able to select two entries from the list.
Now the problem is, that changing the first option menu, will change instead the second one.
The second one, however, works as expected.
I guess the function update_file_list_selection() and lambda function is implemented badly.
import tkinter as tk
from tkinter import ttk
class File_Selection():
def __init__(self, frame, text):
self.frame = frame
self.text = text
self.label_file = tk.Label(self.frame, text=text)
self.label_file.pack()
self.variable_file = tk.StringVar(self.frame)
self.option_list = ["no file loaded"]
self.variable_file.set(self.option_list[0])
self.optionmenu_file = tk.OptionMenu(self.frame, self.variable_file,
*self.option_list)
self.optionmenu_file.pack()
class View:
def __init__(self, view, update_list):
self.view = view
self.view.title("Test")
self.view.geometry("320x240")
self.view.resizable(False, False)
self.frame = tk.Frame(self.view)
self.frame.pack()
self.button = tk.Button(self.frame, text="Update", command=update_list)
self.button.pack()
self.file_one = File_Selection(self.frame, "File 1")
self.file_two = File_Selection(self.frame, "File 2")
class Controller:
def __init__(self):
self.root = tk.Tk()
self.view = View(self.root, lambda: self.update_file_list_selection())
self.files = ["File 1", "File 2", "File 3", "File 4"]
def run(self):
self.root.mainloop()
def update_file_list_selection(self):
self.active_file_selection = [self.view.file_one, self.view.file_two]
for file_selection in self.active_file_selection:
self.menu = file_selection.optionmenu_file["menu"]
self.menu.delete(0, "end")
for x in self.files:
file_selection.option_list.append(x)
self.menu.add_command(label=x,
command=lambda value=x: file_selection.variable_file.set(value))
file_selection.variable_file.set(self.files[0])
if __name__ == "__main__":
c = Controller()
c.run()
I guess the function update_file_list_selection() and lambda function is implemented badly.
That is a correct guess.
The reason is a common problem with using lambda - when you do command=lambda value=x: file_selection.variable_file.set(value), the value of file_selection won't be the value from the loop, it will end up being the value of the final time that variable was set. You can solve this by binding the value to the lambda as a default argument:
self.menu.add_command(label=x, command=lambda value=x, fs=file_selection: fs.variable_file.set(value))
The above will make sure that inside the lambda body, fs will be set to the value of file_selection at the time the menu item is made rather than the value at the time the item is selected.
You'll still end up with OptionMenu items that don't behave exactly the same as normal OptionMenu items, but in this specific example that doesn't seem to matter since you don't have a command associated with the OptionMenu as a whole.

Creating dynamically named gui objects in Python with tkinter

I'm learning to use tkinter in Python 3.6.4. I am creating a GUI with multiple instances of buttons. Two such instances are:
def createWidgets(self):
# first button
self.QUIT = Button(self)
self.QUIT["text"] = "Quit"
self.QUIT["command"] = self.quit
self.QUIT.pack()
# second button
self.Reset = Button(self)
self.Reset["text"] = "Reset"
self.Reset["command"] = "some other function, tbd"
What I want to learn is how to abstract the instantiation of buttons such that each instance in the createWidgets method is based on a method something like this:
createButton( self, text, command, fg, bg, hgt, wth, cursor ):
What I don't know is how to control the naming of the button as:
self.QUIT
self.Reset
where the property or name following the "." operator can be passed to the createButton as a property by which the button is created and named.
Simply expanding on what Brian said, this code will get you going. The button objects are stored in a widget dictionary. Here is one way to put this together:
import tkinter as tk
import sys
root = tk.Tk()
class CustomButton(tk.Button):
def __init__(self, parent, **kwargs):
tk.Button.__init__(self, parent)
for attribute,value in kwargs.items():
try:
self[attribute] = value
except:
raise
def doReset():
print("doRest not yet implemented")
if __name__ == '__main__':
widget = {}
widget['quit'] = CustomButton(root, text='Quit', command=sys.exit)
widget['reset'] = CustomButton(root, text='Reset', command=doReset)
for button in widget.keys():
widget[button].pack()
root.mainloop()

Can you config a widget after it has been created dynamically?

So I have been looking for the answer to this but can't find any examples.
I want to know if you can create several buttons or labels or whatever widget in tkinter with all the same variable name and then be able to target that widget directly after its created.
Here is an example of some code that will create 5 buttons with the same variable name and if you press the button it will print the text on said button.
import tkinter as tk
btn_names = ["1st Button", "2nd Button", "3rd Button", "4th Button", "5th Button"]
class MyButton(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.createButtons()
def createButtons(self):
row_count = 0
for n in range(5):
someButton = tk.Button(self.parent, text=btn_names[n], command= lambda t=btn_names[n]: self.getText(t))
someButton.grid(row = row_count, column = 0)
row_count += 1
def getText(self, text):
print(text)
if __name__ == "__main__":
root = tk.Tk()
myApp = MyButton(root)
root.mainloop()
Now what I can't figure out is if it is possible to also make changes to said button. Like I now want to change the buttons background and foreground colors but I have no way of targeting the button I want to edit.
I can't just do this:
someButton.config(background = "black", foreground = "white")
as all the buttons are named someButton.
So is it possible to be able to edit a widget created in this manor after it has been created?
I'm not sure this is the best way to do it, but it is possible.
Instead of passing a command to your button when you originally create it, add a line where you configure the command to your lambda function and pass someButton as an argument. Then in your callback function, ensure you configure the button passed to change its background color.
import tkinter as tk
btn_names = ["1st Button", "2nd Button", "3rd Button", "4th Button", "5th
Button"]
class MyButton(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.createButtons()
def createButtons(self):
row_count = 0
for n in range(5):
someButton = tk.Button(self.parent, text=btn_names[n])
someButton.configure(command=lambda t=btn_names[n], btn = someButton: self.getText(t, btn))
someButton.grid(row = row_count, column = 0)
row_count += 1
def getText(self, text, btn):
print(text)
btn.configure(background = 'black')
if __name__ == "__main__":
root = tk.Tk()
myApp = MyButton(root)
root.mainloop()

Visible textvariable in tkinter Entry widget

I'm having trouble with using the Entry box widget in tkinter. I want to have a new window to open when the user selects Edit -> Backbone... . In this window there will be a number of Entry widgets (just one coded for below for simplicity) that show default Strings stored in various instances the class Elements. The user should be able to edit this string and save it by clicking OK and returning it to its default value by clicking default. The entry box should always show the current value of the variable each time the backbone editor is reopened (If the whole program is restarted it does not need to remember the user input).
Upon opening the 'Backbone Editor' window the Entry box should show the string text variable but I can't make it appear.
from tkinter import *
from tkinter import ttk
class View(ttk.Frame):
"""Main GUI class"""
def __init__(self, master = None):
self.WIDTH = 450
self.HEIGHT = 500
self.lib = MolecularLibrary()
# Set up the main window
ttk.Frame.__init__(self, master, borderwidth=5, width=self.WIDTH, height=self.WIDTH)
self.master.resizable(FALSE, FALSE)
self.grid(column=0, row=0, sticky=(N, S, E, W))
self.columnconfigure(0, weight=1)
self.create_menus()
def create_menus(self):
"""Produces the menu layout for the main window"""
self.master.option_add('*tearOff', FALSE)
self.menubar = Menu(self.master)
self.master['menu'] = self.menubar
# Menu Variables
menu_edit = Menu(self.menubar)
# Add the menus to the menubar and assign their variables
self.menubar.add_cascade(menu=menu_edit, label = "Edit")
### Edit ###
menu_edit.add_command(label="Backbone...", command=lambda : self.edit_backbone())
def edit_backbone(self):
"""Shows a window where the backbone constituents can be edited"""
backbone_window = Toplevel(borderwidth = 5)
backbone_window.title("Backbone Editor")
backbone_window.resizable(FALSE, FALSE)
print("sugar value", self.lib.sugar_var)
# LABELS FOR BACKBONE #
# Phosphate annotations and input
sugar_label = ttk.Label(backbone_window, text = "Sugar")
#inputs
sugar = ttk.Entry(backbone_window, textvariable = self.lib.sugar_var, justify = 'center', width=10)
### Buttons ###
default = ttk.Button(backbone_window, text = "Defaults", command=lambda : defaults())
okay = ttk.Button(backbone_window, text = "Okay", command=lambda : okay())
cancel = ttk.Button(backbone_window, text = "Cancel", command=lambda : backbone_window.destroy())
#content.grid(column=0, row=0)
sugar_label.grid(column=2, row=1)
sugar.grid(column=1, row=2, columnspan=3)
default.grid(column=0, row=12, columnspan=3, pady=2)
okay.grid(column=6, row=12, columnspan=3, pady=2)
cancel.grid(column=9, row=12, columnspan=4, pady=2)
backbone_window.focus()
def defaults():
"""Reset the backbone and bases to their defaults."""
self.lib.set_molecules()
def okay():
"""Set the backbone and base variables to the user set values."""
self.lib.sugar_var.new_formula(sugar.get())
backbone_window.destroy()
class MolecularLibrary:
"""
"""
def __init__(self, atom_file = r'C:\MyPyProgs\OSeq\resources\ATOMS.txt',
precision = 4):
self.molecules = {}
self.atom_file = atom_file
# self.molecule_file = molecule_file
# Variables
self.set_molecules()
def set_molecules(self):
"""
Set all of the molecules for the backbone and bases to their default values and forumlae.
"""
### Sugar ###
self.sugar_var = Element('C5H8O3', 'A')
def add_molecule(self, molecule):
"""(MolecularLibrary, list) -> None
Returns a dictionary of the molecule name as an Element
{molecule[0]: Element}
"""
print(molecule)
tmp = self.get_mass(molecule[1])
return {molecule[0]: Element(molecule[1], molecule[0], tmp[0], tmp[0])}
class Element:
"""
Creates an element with the following construct:
[symbol, name, monoisotopic, average]
"""
def __init__(self, symbol, name):
self.symbol = symbol
self.name = name
def __str__(self):
return str([self.symbol, self.name])
def get_name(self):
"""Returns the name of the Element"""
return self.name
def get_symbol(self):
"""Returns the symbol of the Element"""
return self.symbol
def new_formula(self, new_formula):
"""replace the formula with new_formaula and recalculate the
average and monoisotopic masses."""
self.symbol = new_formula
if __name__ == "__main__":
root = Tk()
root.title("Sequencer")
view = View(root)
root.mainloop()
The code above is a heavily stripped version of my program but has the same basic architecture. I'm afraid there is still quite a lot of code, I would normally try strip it down more, but I'm not sure if the issue I'm having is from the architecture or not.
In order to use the textvariable attribute, you must give it an instance of a tkinter variable: StringVar, IntVar, BooleanVar, or DoubleVar
A good starting point to learn more about these variables is here: http://effbot.org/tkinterbook/variable.htm

Categories