How to make a ttk.Combobox callback - python

I was wondering if there was a way to invoke a call back from a ttk.Combobox when the user selects an item from the drop-down list. I want to check to see what the value of the combobox is when an item is clicked so that I can display the associated dictionary value given the combobox key.
import Tkinter
import ttk
FriendMap = {}
UI = Tkinter.Tk()
UI.geometry("%dx%d+%d+%d" % (330, 80, 500, 450))
UI.title("User Friend List Lookup")
def TextBoxUpdate():
if not FriendListComboBox.get() == "":
FriendList = FriendMap[FriendListComboBox.get()]
FriendListBox.insert(0,FriendMap[FriendListComboBox.get()])`
#Imports the data from the FriendList.txt file
with open("C:\Users\me\Documents\PythonTest\FriendList.txt", "r+") as file:
for line in file:
items = line.rstrip().lower().split(":")
FriendMap[items[0]] = items[1]
#Creates a dropdown box with all of the keys in the FriendList file
FriendListKeys = FriendMap.keys()
FriendListKeys.sort()
FriendListComboBox = ttk.Combobox(UI,values=FriendListKeys,command=TextBoxUpdate)`
The last line obviously doesn't work since there is no 'command' for Comboboxes but I am not really sure what I need to do here to get that to work. Any help would be appreciated.

You can bind to the <<ComboboxSelected>> event which will fire whenever the value of the combobox changes.
def TextBoxUpdate(event):
...
FriendListComboBox.bind("<<ComboboxSelected>>", TextBoxUpdate)

Use IntVar and StringVar .
You can use the trace method to attach “observer” callbacks to the variable. The callback is called whenever the contents change:
import Tkinter
import ttk
FriendMap = {}
UI = Tkinter.Tk()
UI.geometry("%dx%d+%d+%d" % (330, 80, 500, 450))
UI.title("User Friend List Lookup")
def TextBoxUpdate():
if not FriendListComboBox.get() == "":
FriendList = FriendMap[FriendListComboBox.get()]
FriendListBox.insert(0,FriendMap[UserListComboBox.get()])`
def calback():
print("do something")
#Imports the data from the FriendList.txt file
with open("C:\Users\me\Documents\PythonTest\FriendList.txt", "r+") as file:
for line in file:
items = line.rstrip().lower().split(":")
FriendMap[items[0]] = items[1]
#Creates a dropdown box with all of the keys in the FriendList file
value = StringVar()
value.trace('w', calback)
FriendListKeys = FriendMap.keys()
FriendListKeys.sort()
FriendListComboBox = ttk.Combobox(UI,values=FriendListKeys,command=TextBoxUpdate,textvariable=value)`
the callback function will be called when the comobox changes

Related

Is there anyway that I can assign different name to Labels using Loop? [duplicate]

I am trying to create a framework to create a label, text box and button as an object, I can extend it easily.
Idea:
Original
after extend
and it could extend to have 3, 4, 5 or more if it have more file, just declare in dictionary list, it will extend automatically.
The code I have:
def getpath(entry_box, type):
# path or file
if type == 'path':
entry_box.set(filedial.askdirectory())
elif type == 'file':
entry_box.set(filedial.askopenfilename())
MainWin = tk.Tk() # Create main windows
MainWin.title('Get file and path') # App name
# Create root container to hold Frames
mainFrame = ttk.Frame(MainWin)
mainFrame.grid(column=1, row=1)
# define the object to create in dictionary, format:
# {object name:[title name, default path, button name, file or folder path]}
obj2create ={'file1':['ABC Location: ', r'C:/', 'Get ABC','file'],
'file2': ['DEF Location:', r'C:/1/', 'Get DEF', 'file']}
ttl_obj = 0
for key in obj2create:
ttl_obj +=1
vir = obj2create[key]
# Module for get file:
obj_name = key
title = vir[0]
default_path = vir[1]
btn_name = vir[2]
get_type = vir[3]
# Create main container
pa_frame = ttk.Frame(mainFrame)
pa_frame.grid(column=1, row=10*ttl_obj, sticky=tk.W)
pa_frame.config(width = 100)
# Row 1: Label
frame_name = obj_name + '_name'
print(frame_name)
frame_name = ttk.Label(pa_frame, text= title).grid(column=1, row=10*ttl_obj,sticky=tk.W)
# Row 2: path and button
# assign type
path_loc = obj_name + '_path'
path_loc = tk.StringVar()
path_loc.set(default_path)
# put in frame
fileLocPath = obj_name + '_loc_path'
fileLocPath = ttk.Entry(pa_frame, width=70, textvariable=path_loc)
fileLocPath.grid(column=1, row=30*ttl_obj) # Assign position
# Get file button
# define button display text and assign the command / function
bt_get_file_path = obj_name + '_btn'
bt_get_file_path = ttk.Button(pa_frame, text= btn_name,
command=lambda: getpath(path_loc, get_type))
# Position Button in second row, second column (zero-based)
bt_get_file_path.grid(column=2, row=30*ttl_obj)
# Auto popup when open
MainWin.mainloop() # Let the window keep running until close
Issue:
Default file path appear in second text box only.
All the button location point to second box.
I also not sure how could I get the value in different box, the "path_loc = obj_name + '_path'" not able to get the correct object.
How should I make it work? Or the way I use is wrong?
Tkinter widgets are no different than any other python objects with respect to creating them in a loop. Your problem seems to be that you don't know how to create a unique variable for an unknown number of widgets.
The important thing to know is that you don't need a unique variable for each widget. Instead, you can use a list or dictionary, either of which can be easily extended at runtime.
Saving widget references in a dictionary
Here is a solution using a dictionary:
entries = {}
for key in obj2create:
...
entries[key] = ttk.Entry(...)
...
...
print("the value for file1 is", entries["file1"].get()
Creating a custom compound widget
If you're wanting to create sets of widgets that are to be treated as a group, it may be better to create a class. In effect, you're creating your own custom widget. This type of class is often called a "compound widget" or "megawidget".
For example:
class FileWidget(ttk.Frame):
def __init__(self, parent, name):
ttk.Frame.__init__(self, parent)
self.label = ttk.Label(self, text="%s Location" % name)
self.fileLocPath = ttk.Entry(self)
self.bt_get_file_path = ttk.Button(self, text=name, command=self.get_path)
...
def get_path(self):
return self.fileLocPath.get()
You can then create each row in your GUI like this:
widgets = {}
for key in obj2create:
widgets[key] = FileWidget(mainFrame, key)
widgets[key].pack(side="top", fill="x")
Later, you can get back the values like this:
for key in obj2create:
widget = widgets[key]
print("%s: %s" % (key, widget.get_path())

How to change a list depending on tkinter checkbutton

I have been trying to use tkinter CheckButton widget to edit items in a list - each item of the list is a new checkbutton. I want a save method to save the data to a text file, and a load method to load the info from the text file and mark the checkbutton boxes depending on the items in the list.
Here is my code so far, but the list doesn't seem to change when I check the buttons and update the list/file
Here is my code, I need to know why the list isn't updating when I check the boxes:
import tkinter.messagebox as box
modulesMain = Tk()
moduleChecks = []
def SaveChanges():
# Clear the text file
modules = open("modules.txt", "w") #Write mode to overwrite the whole file
modules.write("") # Put in blank text
modules.close()
modules = open("modules.txt", "a") # Append mode to append the file
for item in moduleChecks:
modules.write(item + "\n")
print(moduleChecks)
appButton = Checkbutton(modulesMain, text = "Test", variable = moduleChecks[0]).grid()
searchButton = Checkbutton(modulesMain, text = "Test", variable = moduleChecks[1]).grid()
Save = Button(modulesMain, text = "Save Changes", command = SaveChanges).grid()
The variable for each of your checkboxes needs to be an IntVar. Your moduleCheck list is currently un-initialised so when you try to access the elements inside it, you'll get an error.
In the below code (modified from yours), I've initialised the moduleCheck to contain two IntVars.
When you press the save button, it will print to the console the current state of the check boxes.
import tkinter as tk
modulesMain = tk.Tk()
moduleChecks = [tk.IntVar() for i in range(2)]
def SaveChanges():
for idx,item in enumerate(moduleChecks):
print(f"{idx} = {item.get()}")
appCheck = tk.Checkbutton(modulesMain, text = "App", variable = moduleChecks[0])
appCheck.grid()
searchCheck = tk.Checkbutton(modulesMain, text = "Check", variable = moduleChecks[1])
searchCheck.grid()
saveButton = tk.Button(modulesMain, text = "Save Changes", command = SaveChanges)
saveButton.grid()
modulesMain.mainloop()
Here is a basic solution.
import tkinter as tk
root = tk.Tk()
#list of options. doubles as checkbox label text
#appending to this list is all that is necessary to create more options
opts = ['App', 'Search']
#create an StringVar for every option
#this way there doesn't have to be type conversions for reading/writing files
vars = [tk.StringVar(value='0') for _ in opts]
#create checkboxes for every option
for opt, var in zip(opts, vars):
tk.Checkbutton(root, text=opt, variable=var, onvalue='1', offvalue='0').grid(sticky='w')
#write the checkbox values to file
def save_options_state():
with open("modules.txt", "w") as file:
file.write(''.join([v.get() for v in vars]))
#set checkbox values from file
def load_options_state():
with open("modules.txt", "r") as file:
for n, var in zip(list(file.read().strip()), vars):
var.set(n)
#save/load buttons
tk.Button(root, text="save", command=save_options_state).grid()
tk.Button(root, text="load", command=load_options_state).grid(row=2, column=1)
root.mainloop()
Here's a runnable example which I think shows how to do everything you've asked about. It assumes the modules.txt file contains data in the following format, where each line consists of a module's name and whether it's checked or not.
Mod1,0
Mod2,0
Mod3,0
The data in the file is used to create a list of Checkbuttons — one for each module — and initially sets to its current status as indicated in the file.
Here's the sample code:
from tkinter import *
import tkinter.messagebox as box
dataFilename = "modules.txt"
moduleChecks = []
def ReadModules():
with open(dataFilename, "r") as file:
for line in (line.strip() for line in file):
moduleName, moduleState = line.split(',')
moduleChecks.append((moduleName, int(moduleState)))
print('read:', moduleChecks)
def SaveChanges():
print('writing:', moduleChecks)
with open(dataFilename, "w") as file:
for checkButton in moduleCheckbuttons:
moduleName = checkButton.cget('text')
moduleState = checkButton.var.get()
file.write(','.join((moduleName, str(moduleState))) + '\n')
ReadModules()
modulesMain = Tk()
moduleCheckbuttons = []
# Create a Checkbutton for each module based on moduleChecks values.
for moduleName, moduleState in moduleChecks:
intVar = IntVar(value=moduleState)
checkButton = Checkbutton(modulesMain, text=moduleName, variable=intVar)
checkButton.var = intVar # Attach reference to variable.
checkButton.grid()
moduleCheckbuttons.append(checkButton)
saveButton = Button(modulesMain, text="Save Changes", command=SaveChanges)
saveButton.grid()
modulesMain.mainloop()

Saving and searching data with Tkinter entry boxes

This might be a strange question because I am new to Python.
I am trying to create form in Python which data can be entered into boxes and saved, then opened again. I'm currently using Tkinter to create a Gui which has entry boxes and buttons:
import sys
from tkinter import *
def mstore():
pass
return
def msearch():
file_path = filedialog.askopenfilename()
return
mGui=Tk()
mGui.geometry('450x450+200+200')
mGui.title('Form Test')
#Top
mTitle = Label (mGui,text='Heading Text',bg='white').grid(row=1,column=1)
mDetail = Label (mGui,text='Flavour you can see',bg='white').grid(row=2,column=1)
#Entry Boxes
mFName = Label (mGui,text='Barcode',bg='white').grid(row=3,column=1)
mEntryname = Entry().grid(row=3,column=2)
#Buttons
mSave = Button (mGui,text='Save',bg='white', command = mstore).grid(row=4,column=1)
mSearch = Button (mGui,text='Search',bg='white', command = msearch).grid(row=5,column=1)
mGui.mainloop()
The search was going to be used to open up a file which has been saved before and fill in the boxes with that data, however before that I need help saving the data in a way it will be retrievable - All the information I find is about web-forms. I have also tried saving information with SQLite3 but I found that to not be quite what I was looking for.
Any help/guidance will be appreciated.
Thanks,
Hello Gregulimy!
I have simplified your code and made it do what you want it to do. I have left comments explaining what the code does. If you have any questions about what I have done feel free to ask!
from tkinter import *
def mstore(text):
file = open("file.txt", "w") # Create file.txt
file.write(text) # Write contents of mEntryname to file
file.close() # Closes text file
def msearch():
file = filedialog.askopenfilename() # Stores file directory that user chose
open_file = open(file, 'r') # Opens file user chose
print(open_file.read()) # Displays contents in console
open_file.close() # Closes text file
# Window Creation and Settings
window = Tk()
window.geometry('450x500')
window.title('Form Test')
# Create Widgets
mTitle = Label (window,text='Heading Text',bg='white')
mDetail = Label (window,text='Flavour you can see',bg='white')
mFName = Label (window,text='Barcode',bg='white')
mEntryname = Entry(window)
# Runs mstore function when pressed (passing the contents of the entry box)
mSave = Button (window,text='Save',bg='white', command = lambda: mstore(mEntryname.get()))
# Runs msearch function when pressed
mSearch = Button (window,text='Search',bg='white', command = lambda: msearch())
# Render Widgets
mTitle.pack()
mDetail.pack()
mFName.pack()
mEntryname.pack()
mSave.pack()
mSearch.pack()
window.mainloop()

Is it possible to display hyperlinks in a Tkinter message widget?

I am creating a news feed program that uses the Feedparser module to read the Yahoo! RSS API, write key data to a text file, and then display the data organised in a Tkinter GUI.
I was enquiring if it is possible to have clickable hyperlinks in a text file/Tkinter message widget.
My current thinking is that you could write code that runs in the following fashion:
If item in the text file includes 'http', make it a hyperlink.
If anyone knows of a Pythonic way to achieve this, or knows if it is not in fact possible, please contribute.
Thank you for your time, here is my code:
def news_feed(event):
''' This function creates a new window within the main window, passes an event(left mouse click), and creates a text heading'''
root = Toplevel(window)
# Create a text heading and define its placement within the grid
menu_heading = Label(root, text = 'News feed', font = 'bold')
menu_heading.grid(row = 0, column = 0, columnspan = 3, pady = 4)
# Create a variable of the selected radio button
button_choice = IntVar()
def selection():
''' This function gets the activated radio button and calls its corresponding function.'''
# Get the value of the activated radio button, and call its corresponding function
news_choice = button_choice.get()
# If the user's choice is industry news, ask them which feed they would like (E.g. Stock market),
if news_choice == 0:
# grab the corresponding url segment to the user's feed choice from the dictionary,
news_choice_url = news_areas[news_feed]
# set the url variable using by inserting this segment into the API url,
rss_url = feedparser.parse('https://au.finance.yahoo.com/news/' + news_choice_url + '/?format=rss')
# and call the feed parsing function.
parse_feed()
# If the user's choice is the second button, call the company news function
elif news_choice == 1:
company_news()
def read_news_file(news_feed_message):
'''This function opens the companyNews text file and reads its contents, line by line'''
with open('C:\\Users\\nicks_000\\PycharmProjects\\untitled\\SAT\\GUI\\Text Files\\companyNews.txt', mode='r') as inFile:
news_data_read = inFile.read()
print('\n')
news_feed_message.configure(text = news_data_read)
def parse_feed(news_feed_message, rss_url):
''' This function parses the Yahoo! RSS API for data of the latest five articles, and writes it to the company news text file'''
# Define the RSS feed to parse from, as the url passed in of the company the user chose
feed = feedparser.parse(rss_url)
try:
# Define the file to write the news data to the company news text file
with open('C:\\Users\\nicks_000\\PycharmProjects\\untitled\\SAT\\GUI\\Text Files\\companyNews.txt', mode='w') as outFile:
# Create a list to store the news data parsed from the Yahoo! RSS
news_data_write = []
# Initialise a count
count = 0
# For the number of articles to append to the file, append the article's title, link, and published date to the news_elements list
for count in range(10):
news_data_write.append(feed['entries'][count].title)
news_data_write.append(feed['entries'][count].published)
article_link = (feed['entries'][count].link)
article_link = article_link.split('*')[1]
news_data_write.append(article_link)
# Add one to the count, so that the next article is parsed
count+=1
# For each item in the news_elements list, convert it to a string and write it to the company news text file
for item in news_data_write:
item = str(item)
outFile.write(item+'\n')
# For each article, write a new line to the company news text file, so that each article's data is on its own line
outFile.write('\n')
# Clear the news_elements list so that data is not written to the file more than once
del(news_data_write[:])
finally:
outFile.close()
read_news_file(news_feed_message)
def industry_news():
''' This function creates a new window within the main window, and displays industry news'''
industry_window = Toplevel(root)
Label(industry_window, text = 'Industry news').grid()
def company_news():
''' This function creates a new window within the main window, and displays company news'''
company_window = Toplevel(root)
company_label = Label(company_window, text = 'Company news')
company_label.grid(row = 0, column = 0, columnspan = 6)
def company_news_handling(company_ticker):
''' This function gets the input from the entry widget (stock ticker) to be graphed.'''
# set the url variable by inserting the stock ticker into the API url,
rss_url = ('http://finance.yahoo.com/rss/headline?s={0}'.format(company_ticker))
# and call the feed parsing function.
parse_feed(news_feed_message, rss_url)
# Create the entry widget where the user enters a stock ticker, and define its location within the grid
company_ticker_entry = Entry(company_window)
company_ticker_entry.grid(row = 1, column = 0, columnspan = 6, padx = 10)
def entry_handling():
'''This function validates the input of the entry box, and if there is nothing entered, an error is outputted until a value is'''
# Create a variable that equals the input from the entry widget
company_ticker = company_ticker_entry.get()
# Convert the input into a string
company_ticker = str(company_ticker)
if company_ticker == '':
news_feed_message.configure(text = 'Please input a stock ticker in the entry box.')
else:
company_news_handling(company_ticker)
# Create the button that the user presses when they wish to graph the data of the stock ticker they inputted in the entry widget
graph_button = Button(company_window, text = 'SHOW', command = entry_handling, width = 10).grid(row = 2, column = 0, columnspan = 6)
news_feed_message = Message(company_window, text='', width=500, borderwidth=5, justify=LEFT, relief=RAISED)
news_feed_message.grid(row=3, column=0, columnspan=6)
Most uses of hyperlinks in a tkinter application i have seen involved using the webbrowser and attaching events to your tkinter object to trigger callbacks, but there may be simpler ways, but heres what i mean :
from tkinter import *
import webbrowser
def callback(event):
webbrowser.open_new(r"http://www.google.com")
root = Tk()
link = Label(root, text="Google Hyperlink", fg="blue", cursor="hand2")
link.pack()
link.bind("<Button-1>", callback)
root.mainloop()
From this source
You could do as you said and read from a text file, and if the line contains "http" create a new label, and event, attaching the hyper link from the file to the event.
import re
with open(fname) as f:
content = f.readlines()
urls = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_#.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', content)
Access the url's after this and generate your label's or whatever widget you attach the url's too and you can then have all of them open the web page when clicked.
Hope this helps in some way, let me know if you need more help :)
I think it is easy to create hyperlink in tkinter using following link and its easy for modifying as per your requirement
Updated Hyperlink in tkinter
hope this works for you.
regards Midhun
(Answer taken from effbot)
Support module for Text hyperlinks (File: tkHyperlinkManager.py)
from Tkinter import *
class HyperlinkManager:
def __init__(self, text):
self.text = text
self.text.tag_config("hyper", foreground="blue", underline=1)
self.text.tag_bind("hyper", "<Enter>", self._enter)
self.text.tag_bind("hyper", "<Leave>", self._leave)
self.text.tag_bind("hyper", "<Button-1>", self._click)
self.reset()
def reset(self):
self.links = {}
def add(self, action):
# add an action to the manager. returns tags to use in
# associated text widget
tag = "hyper-%d" % len(self.links)
self.links[tag] = action
return "hyper", tag
def _enter(self, event):
self.text.config(cursor="hand2")
def _leave(self, event):
self.text.config(cursor="")
def _click(self, event):
for tag in self.text.tag_names(CURRENT):
if tag[:6] == "hyper-":
self.links[tag]()
return
And here’s an example:
# File: hyperlink-1.py
import tkHyperlinkManager
from Tkinter import *
root = Tk()
root.title("hyperlink-1")
text = Text(root)
text.pack()
hyperlink = tkHyperlinkManager.HyperlinkManager(text)
def click1():
print "click 1"
text.insert(INSERT, "this is a ")
text.insert(INSERT, "link", hyperlink.add(click1))
text.insert(INSERT, "\n\n")
def click2():
print "click 2"
text.insert(INSERT, "this is another ")
text.insert(INSERT, "link", hyperlink.add(click2))
text.insert(INSERT, "\n\n")
mainloop()

Dynamic Widget creation based of configparser file

I am trying a piece of python based tkinter code with following objective:
(Please go through the objective, then I will take an example to explain what exactly i require and then in the end will post the script I have written)
Reads from a config file, which is implemented using configparser module.
Based on options read from this file it automatically generates widget.
These widgets are restricted to only labels and entry box as of now.
Every entry box is associated with a variable. It is hence needed to generate a variable
automatically whenever a entry box is declared.
Now when the user enters any value in the entry box, and presses calculate button a list is
generated with combination of values entered by user( in a specific format).
Example:
Let the configparser file has following content:
[widget]
label = ani_label,sham_label
entry = ani,sham
The list generated for this case will be like this:
out_list = ['-ani','< ani >','-sham','< sham >']
< ani > means value stored in ani variable
And below is the code that i have tried.
from Tkinter import *
from Tkinter import Tk
import Tkinter as tk
import ttk
import ConfigParser
import sys
############ Initialize ###############################
parser_read = ConfigParser.ConfigParser()
parser_read.read('option_read.config')
config_list = {}
config_list['label'] = parser_read.get('widget','label').split(',')
config_list['entry'] = parser_read.get('widget','entry').split(',')
######
def calculate():
#Will include the list generation part
pass
#######
root = Tk()
root.title("NRUNTEST GUI VERSION 1")
#
menuframe = ttk.Frame(root)
menuframe.grid(column=0,row=0)
menuframe.columnconfigure(0,weight=1)
menuframe.rowconfigure(0,weight=1)
#
mainframe_label = ttk.Frame(root)
mainframe_label.grid(column=1,row=0)
mainframe_label.columnconfigure(0,weight=1)
mainframe_label.rowconfigure(0,weight=1)
#
mainframe_entry = ttk.Frame(root)
mainframe_entry.grid(column=2,row=0)
mainframe_entry.columnconfigure(0,weight=1)
mainframe_entry.rowconfigure(0,weight=1)
#
general_label= Label(menuframe,text="Please Enter the Values Below").grid(column=1,row=0,sticky=(E))
compiler_label= ttk.Label(menuframe,text="Compiler")
compiler_label.grid(column=1,row=1,sticky=W)
#
calculate_button = ttk.Button(menuframe, text="Calculate", command=calculate).grid(column=1,row=2,sticky=(W,E))
#Automatic Widget declaration ###
for x in config_list['label']:
x = ttk.Label(mainframe_label,text=x).grid()
for x in config_list['entry']:
#print x
var = lambda: sys.stdout.write(x)
x = ttk.Entry(mainframe_entry,textvariable = x).grid()
root.mainloop()
The content of option_read.config is
[widget]
label : animesh_label,sharma
entry : animesh_entry,sharma
STATUS:
I can create the required widgets automatically. But I am not able to create the variables dynamically to store the entry box values.
Once the variable has been calculated, I can write the calculate function on my own.
Please advice how i can proceed.
If you have any better way to meet my requirements, please do suggest.
Also do ping me if you require any more inputs or my query is not clear.
The easiest way to do this, IMO, is to use a dict to store the references to the dynamically created variables. You could use the label as the key. For example:
vars = {}
for x in config_list['entry']:
vars[x] = StringVar()
entry = ttk.Entry(mainframe_entry, textvariable=vars[x])
entry.grid()
By the way... are you aware that if you do something like x=ttk.Entry(...).grid(...), x does not contain a reference to the widget? It contains the result of the call to grid, which is None.

Categories

Resources