In python 2.7, I am trying to get a callback every time something is changed in the Tkinter Text widget.
The program uses multiple frames based on code found here: Switch between two frames in tkinter
The callback part is taken from the following example: http://code.activestate.com/recipes/464635-call-a-callback-when-a-tkintertext-is-modified/
Both codes work fine separately, but combining those two is difficult for me.
Here is my attempt with as bare bones code as possible.
import Tkinter as tk
class Texter(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack()
self.frames = {}
for F in (ConnectPage, EditorPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
page_name = EditorPage.__name__
self.frames[page_name] = frame
self.show_frame(ConnectPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def get_page(self, page_name):
return self.frames[page_name]
class ConnectPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
button1 = tk.Button(self, text="SecondPage",
command=lambda: controller.show_frame(EditorPage))
button1.grid(row=2, column=3, padx=15)
class EditorPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.text = tk.Text(self, height=25, width=80)
self.text.grid(column=0, row=0, sticky="nw")
button2 = tk.Button(self, text="FirstPage",
command=lambda: controller.show_frame(ConnectPage))
button2.grid(row=2, column=3, padx=15)
self.clearModifiedFlag()
self.bind_all('<<Modified>>', self._beenModified)
def _beenModified(self, event=None):
if self._resetting_modified_flag: return
self.clearModifiedFlag()
print("Hello!")
#self.beenModified(event)
def clearModifiedFlag(self):
self._resetting_modified_flag = True
try:
self.tk.call(self._w, 'edit', 'modified', 0)
finally:
self._resetting_modified_flag = False
if __name__ == '__main__':
gui = Texter()
gui.mainloop()
I tried taking only the necessary parts from the callback example.
The code does do a callback (if self.tk.call(self._w, 'edit', 'modified', 0) line is commented out) when the text is modified, but resetting the modified flag does not work, so only the first modification is registered.
At the moment I get the following error:
line 67, in clearModifiedFlag
self.tk.call(self._w, 'edit', 'modified', 0)
_tkinter.TclError: bad option "edit": must be cget or configure
In the callback example code "edit" works fine.
Edit: This is the working code
import Tkinter as tk
class Texter(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack()
self.frames = {}
for F in (ConnectPage, EditorPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
page_name = EditorPage.__name__
self.frames[page_name] = frame
self.show_frame(ConnectPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def get_page(self, page_name):
return self.frames[page_name]
class ConnectPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
button1 = tk.Button(self, text="SecondPage",
command=lambda: controller.show_frame(EditorPage))
button1.grid(row=2, column=3, padx=15)
class EditorPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.text = CustomText(self, height=25, width=80)
self.text.grid(column=0, row=0, sticky="nw")
self.text.bind("<<TextModified>>", self.onModification)
button2 = tk.Button(self, text="FirstPage",
command=lambda: controller.show_frame(ConnectPage))
button2.grid(row=2, column=3, padx=15)
def onModification(self, event):
print("Yellow!")
class CustomText(tk.Text):
def __init__(self, *args, **kwargs):
"""A text widget that report on internal widget commands"""
tk.Text.__init__(self, *args, **kwargs)
# create a proxy for the underlying widget
self._orig = self._w + "_orig"
self.tk.call("rename", self._w, self._orig)
self.tk.createcommand(self._w, self._proxy)
def _proxy(self, command, *args):
cmd = (self._orig, command) + args
result = self.tk.call(cmd)
if command in ("insert", "delete", "replace"):
self.event_generate("<<TextModified>>")
return result
if __name__ == '__main__':
gui = Texter()
gui.mainloop()
I suggest a simpler approach. You can set up a proxy for the widget, and within that proxy you can detect whenever anything was inserted or deleted. You can use that information to generate a virtual event, which can be bound to like any other event.
Let's start by creating a custom text widget class, which you will use like any other text widget:
import Tkinter as tk
class CustomText(tk.Text):
def __init__(self, *args, **kwargs):
"""A text widget that report on internal widget commands"""
tk.Text.__init__(self, *args, **kwargs)
# create a proxy for the underlying widget
self._orig = self._w + "_orig"
self.tk.call("rename", self._w, self._orig)
self.tk.createcommand(self._w, self._proxy)
def _proxy(self, command, *args):
cmd = (self._orig, command) + args
result = self.tk.call(cmd)
if command in ("insert", "delete", "replace"):
self.event_generate("<<TextModified>>")
return result
The proxy in this example does three things:
First it calls the actual widget command, passing in all of the arguments it received.
Next it generates an event for every insert and every delete
Then it then generates a virtual event
And finally it returns the results of the actual widget command
You can use this widget exactly like any other Text widget, with the added benefit that you can bind to <<TextModified>>.
For example, if you wanted to display the number of characters in the text widget you could do something like this:
root = tk.Tk()
label = tk.Label(root, anchor="w")
text = CustomText(root, width=40, height=4)
label.pack(side="bottom", fill="x")
text.pack(side="top", fill="both", expand=True)
def onModification(event):
chars = len(event.widget.get("1.0", "end-1c"))
label.configure(text="%s chars" % chars)
text.bind("<<TextModified>>", onModification)
root.mainloop()
I integrated the above <<TextModified>> example in my code and it
worked quite well, except that it was interfering with some
edit_modified() commands.
Fortunately, the tkinter Text window has a poorly documented feature
which is as good and is fully compatible with the edit_modified() get
or set commands: the predefined <<Modified>> tag. You don't even have
to create it, it works out-of-the-box.
Here are the relevant parts of my code:
The "self" prefixes were removed, some adjustments may be needed
Put that in your Text gadget code:
title = set_title(fname, numbr)
text.bind("<<Modified>>", lambda dummy: save_indicator(title))
Make sure these functions are visible:
def set_title(fname, numbr):
"Creates a window title showing the save indicator,"
"the file name and a window number"
fname = strip_path(fname)
if not fname:
fname = "(New Document)"
return "+ {} - Window no.{}".format(fname, numbr)
def strip_path(fname):
return os.path.split(fname)[-1]
def save_indicator(title, event=None):
"Update the window title"
titre = toggle_star(title)
text.winfo_toplevel().title(title)
def toggle_star(title):
"Change the first character of the title"
chr='+'; chr0='x'
if text.edit_modified():
title = chr0 + title[1:]
else:
title = chr + title[1:]
return title
Here is a complete working example with the predefined <<Modified>> tag:
def toggle_star(title):
"Change the color of the star in the title bar"
chr='+'; chr0='x'
if text.edit_modified():
title = chr0 + title[1:]
else:
title = chr + title[1:]
return title
def set_title(fname, winno):
"Put save indicator, file name and window number in the title"
if not fname:
fname = "(New Document)"
return "+ {} - Window no.{}".format(fname, winno)
def mockSave(title, event=None):
title = toggle_star(title)
root.winfo_toplevel().title(title)
text.edit_modified(0)
def read_ed_mod():
print("text.edit_modified()=", text.edit_modified())
def onModification(title, event=None):
title = toggle_star(title)
root.winfo_toplevel().title(title)
from tkinter import *
fname = 'blabla.txt'
winno = 1 ;
root = Tk()
label = Label(root, anchor="w")
text = Text(root, width=40, height=4)
label.pack(side="bottom", fill="x")
text.pack(side="top", fill="both", expand=True)
Button(root, text='Mock Save', command= lambda: mockSave(title)).pack(side=LEFT)
Button(root, text='Read ed_mod', command= lambda: read_ed_mod()).pack(side=RIGHT)
text.bind('<<Modified>>', lambda event: onModification(title))
title = set_title(fname, winno)
root.winfo_toplevel().title(title)
text.edit_modified(0)
root.mainloop()
Related
I'm building a desktop application that lets you insert some data into a form and then the data is displayed in a series (3) of Treeview widgets.
This is the form that I'm using to enter new data:
It's in a Toplevel widget. When the Add button is pressed the new data is stored in a file and it also should insert the new data in the corresponding Treeview Widget.
This is the root window:
It's comprised of 3 Treeview widgets. The purpose of the application is to give the user the opportunity to sort candidates into the right Treeview widget.
The issue that I'm facing is that when the Add button is pressed the new data is not shown in the Treeview widget and no errors are given. I think it may be an issue of class instantiation. This is an excerpt from my app, please see below a Minimal, Complete and Verifiable example
class MainApp(tk.Tk):
def __init__(self, *args, **kwargs):
...
# frame and menu classes are instantiated here
self.FrameList = {ViableCandidates: ViableCandidates(self),
NotViableCandidates: NotViableCandidates(self),
InProgressCandidates: InProgressCandidates(self)}
...
def InstanceLinker(self, frame):
link = self.FrameList[frame]
return link
class GUIMenu(tk.Menu):
def __init__(self, parent):
...
# menu code is here
addcandidates.add_command(label='Quick Add', command=lambda: QuickAdd(parent))
class QuickAdd(tk.Toplevel):
def __init__(self, parent):
...
# code for the small Toplevel window
...
# this is the code that I use to add the new item to Treeview when the Add button is pressed
if CandidateInfo['status'] == 'Viable':
app.InstanceLinker(ViableCandidates).AddtoList()
elif CandidateInfo['status'] == 'Not Viable':
app.InstanceLinker(NotViableCandidates).AddtoList()
else:
app.InstanceLinker(InProgressCandidates).AddtoList()
# ViableCandidates, NotViableCandidates, InProgressCandidates are created with the same pattern
class InProgressCandidates(tk.Frame):
def __init__(self, parent):
global Counter
tk.Frame.__init__(self, parent)
self.columnconfigure(0, weight=1)
self.rowconfigure(1, weight=1)
title = tk.Label(self, text="Candidates In Progress", font="Verdana 10 bold")
title.grid(row=0, column=0, sticky='nesw')
self.tree = ttk.Treeview(self)
self.tree.grid(row=1, column=0, sticky='nesw')
scrollbar = ttk.Scrollbar(self, orient='vertical', command=self.tree.yview)
scrollbar.grid(row=1, column=1, sticky='nws')
self.tree.config(columns=('Name', 'Date'), selectmode='browse', height=20, yscrollcommand=scrollbar.set)
self.tree.column('#0', width=20, minwidth=10, stretch=tk.YES)
self.tree.column('Name', width=150, minwidth=10, stretch=tk.YES)
self.tree.column('Date', width=80, minwidth=10, stretch=tk.YES)
self.tree.heading('#0', text='#', anchor=tk.W)
self.tree.heading('Name', text='Name', anchor=tk.W)
self.tree.heading('Date', text='Date', anchor=tk.W)
if Counter < 4:
Counter += 1
self.PopulateList()
def PopulateList(self):
selection = Database().SelectFromDB('name, date', "status = 'In progress'")
for i in range(len(selection)):
name = list(selection[i])[0]
date = adjusttotimezone(list(selection[i])[1])
self.tree.insert("", i, name, text=i + 1)
self.tree.set(name, 'Name', name)
self.tree.set(name, 'Date', date)
CandidateCounter['InProgressCandidates'] = i
def AddtoList(self):
CandidateCounter['InProgressCandidates'] += 1
print('I was here')
self.tree.insert("", CandidateCounter['InProgressCandidates'], CandidateInfo['name'],
text=CandidateCounter['InProgressCandidates'])
self.tree.set(CandidateInfo['name'], 'Name', CandidateInfo['name'])
selection = Database().SelectFromDB('date', "name = '" + CandidateInfo['name'] + "'")
date = adjusttotimezone(list(selection[0])[0])
self.tree.set(CandidateInfo['name'], 'Date', date)
app = MainApp()
app.mainloop()
When the "Add" button is pressed there are no errors and "I was here" is printed so the AddtoList method is instantiated, but there are no new items added to Treeview. I did check if the variables that I'm using to create the new Treeview item hold the correct data and they do.
EDIT: This is a Minimal, Complete and Verifiable example:
import tkinter as tk
from tkinter import ttk
Bigbadtext = ''
Counter = 0
class MainApp(tk.Tk):
def __init__(self, *args, **kwargs):
self.MainWindow = tk.Tk.__init__(self, *args, **kwargs)
menu = GUIMenu(self)
self.config(menu=menu)
frame = InProgressCandidates(self)
frame.grid(row=0, column=1, sticky='nesw')
self.FrameList = {InProgressCandidates:InProgressCandidates(self)}
def InstanceLinker(self, frame):
link = self.FrameList[frame]
return link
class GUIMenu(tk.Menu):
def __init__(self, parent):
tk.Menu.__init__(self, parent)
addcandidates = tk.Menu(self, tearoff=0)
self.add_cascade(label='Add Candidates', menu=addcandidates)
addcandidates.add_command(label='Quick Add', command=lambda: QuickAdd(parent))
class QuickAdd(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
saysomething = tk.Entry(self)
saysomething.grid(row=1, column=0)
def addbutton():
global Bigbadtext
Bigbadtext = saysomething.get()
app.InstanceLinker(InProgressCandidates).AddtoList()
okbutton = ttk.Button(self, text='Add', command=addbutton)
okbutton.grid(row=2, column=0)
class InProgressCandidates(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.tree = ttk.Treeview(self)
self.tree.grid(row=1, column=0, sticky='nesw')
scrollbar = ttk.Scrollbar(self, orient='vertical', command=self.tree.yview)
scrollbar.grid(row=1, column=1, sticky='nws')
self.tree.config(columns='something', selectmode='browse', height=20, yscrollcommand=scrollbar.set)
self.tree.column('#0', width=20, minwidth=10, stretch=tk.YES)
self.tree.column('something', width=150, minwidth=10, stretch=tk.YES)
self.tree.heading('#0', text='#', anchor=tk.W)
self.tree.heading('something', text='Say something', anchor=tk.W)
def AddtoList(self):
global Counter
Counter += 1
print('I was here')
self.tree.insert("", Counter, Bigbadtext, text=Counter)
self.tree.set(Bigbadtext, 'something', Bigbadtext)
app = MainApp()
app.mainloop()
The problem is that you are creating two treeview widgets, and then adding items to the one that is invisible.
You create one here:
frame = InProgressCandidates(self)
Then you create another one here:
self.FrameList = {InProgressCandidates:InProgressCandidates(self)}
Since you've already created one, the one you created should be what goes in self.FrameList:
self.FrameList = {InProgressCandidates:frame}
It is not really an answer but I up voted the question because it solved me a problem. I wanted to add items to the widget but did not want to show it to the user until I finished to populate the tree. But each insert showed right away. Now I create 2 identical widgets, one visible and the other is not, and once it is populated I change between them. Thus even a mistake can have a benefit.
I haven't found a thread to answer my question, so:
My GUI consists of 3 separate "windows" (Frames?) as classes: TextInput, TextOutput and Statistic. For performance reasons I only ran the Statistic.mainloop() but the other classes show up, too.
What I want my TextInput class to do is
iterate through a list with strings and insert them in a textwidget "self.ref_text".
class TextInput(tk.Frame):
LARGE_FONT = ("Arial Bold ", 18)
SMALL_FONT = ("Arial", 16)
BGC = '#CDCDC1'
FG = ['#000000', '#f44242']
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# print('415 TextInput instance: ', type(self))
self.controller = controller
self.reference = [] # some strings in here
self.createText()
def createText(self):
self.ref_text = tk.Text(self, height=30, width=100, font=TextInput.SMALL_FONT)
self.ref_text.config(background=TextInput.BGC)
self.ref_text.grid(column=0, columnspan=4, row=1, padx=5, sticky="W")
def display_ref(self, line):
print('line: ', line)
self.ref_text.insert('end', line)
def read_ref(self):
for line in self.reference:
self.ref_text.insert('end', line)
self.ref_text.after(1500, self.read_ref)
the after() method inserts all strings of "self.reference" instead of the intended FOR Loop. Also, the whole TextInput app seems to tilt (to much recursion?)
In another version I tried to call
self.ref_text.after(1500, self.display_ref, line)
which again puts all the text in the widget after 1500 ms.
What am I doing wrong?
Is it a problem that I only run
Statistik.mainloop()
at the bottom instead of TextInput.mainloop().
Thanks for your help
as for the minimal example:
import tkinter as tk
class Interface(tk.Tk):
def __init__(self, name, page, *kwargs):
tk.Tk.__init__(self, name, *kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
container.master.title(name)
self.frames = {}
self.windows = {}
self.windows[name] = page
self.window = page(container, self)
self.frames[name] = self.window
self.window.grid(row=0, column=0, sticky='nsew')
self.show_window(name)
def show_window(self, cont):
window = self.frames[cont]
window.tkraise()
class TextInput(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.reference = ['a is a sentence', 'b follows a', 'c closes the session']
self.createText()
self.read = True
self.read_ref()
def stop_read(self):
self.read = False
def createText(self):
self.ref_text = tk.Text(self, height=30, width=80,)
self.ref_text.grid(column=0, row=1, columnspan=3, padx=5, sticky="W")
def display_ref(self, line):
print('line: ', line)
self.ref_text.insert('end', line)
def read_ref(self):
'''
the goal is to loop through self.reference line by line
with a 1500 ms pause inbetween
'''
for line in self.reference:
if self.read:
self.ref_text.insert('end', line + '\n')
self.ref_text.after(1500, self.read_ref)
else:
return
class Statistik(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
'''does some other stuff'''
textinput_instance = Interface('TextInput', TextInput)
statistik_instance = Interface('Statistik', Statistik)
statistik_instance.mainloop()
If your goal is to loop through the list, displaying each line at 1500ms intervals, the easiest way is to have a function that does one iteration and then repeats itself using after
Something like this, perhaps:
def read_ref(self, lines):
# remove one item from the list
line = lines.pop(0)
# insert it
self.ref_text.insert("end", line + "\n")
# run again in 1500ms if there's still more work to do
if lines:
self.after(1500, self.read_ref, lines)
Then, call this function exactly once to start the process:
self.read_ref(self, self.reference)
If you want to be able to stop it, you can check for a flag in your function:
def read_ref(self):
...
if self.reference and not self.stop:
self.after(1500, self.read_ref)
The above code slowly removes items from self.reference. If you don't want that to happen, pass a copy of self.reference when you start so that the function will remove items from a copy of the original data.
self.read_ref(self, self.reference[:])
My question seems easy but after trying all methods, which should work, I could not forget or delete child frame.
Program is based on this ex: Switch between two frames in tkinter
My code:
import tkinter as tk
from tkinter import ttk
class myProgram(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.title(self, "myProgram")
framesForAllWindows = tk.Frame(self)
framesForAllWindows.pack(side="top")
framesForAllWindows.grid_rowconfigure(0)
framesForAllWindows.grid_columnconfigure(0)
self.frames = dict()
for pages in (checkPage, PageOne):
frame = pages(framesForAllWindows, self)
self.frames[pages] = frame
frame.grid(row=0, column=0, sticky="nsew")
print(framesForAllWindows.winfo_children())
self.show_frame(checkPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class checkPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.f = tk.Frame()
self.f.pack(side="top")
self.controller = controller
self.enterAppid = ttk.Label(text='Please enter your something: ', font=('Courier', 20))
self.enterAppid.pack(padx=50, pady=100)
self.appidVar = tk.StringVar()
self.appidEnter = ttk.Entry(width=60, textvariable=self.appidVar)
self.appidEnter.pack()
self.checkAppid = ttk.Button(text='check', command = self.checkInfo)
self.checkAppid.pack(pady=50, ipady=2)
def checkInfo(self):
self.appid = self.appidVar.get()
if(self.appid == "good"):
self.controller.show_frame(PageOne)
#self.f.pack_forget() doesn`t work
else:
print('Unknown Error')
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="You are on page One")
label.pack(pady=1,padx=10)
app = myProgram()
app.state('zoomed')
app.mainloop()
The goal is to forget all checkPage frame and move on to PageOne. When I execute the code that I currently have "You are on page one" appears in addition to all widgets from checkPage which I don`t want. Any ideas where I am making mistake?
You didn't specify your parent for the Label, Entry and Button. If you change these 3 lines it should work.
So like this:
self.enterAppid = ttk.Label(self.f, text='Please enter your something: ', font=('Courier', 20))
self.appidEnter = ttk.Entry(self.f, width=60, textvariable=self.appidVar)
self.checkAppid = ttk.Button(self.f, text='check', command = self.checkInfo)
I am trying to develop a Tkinter GUI which contains two pages, the first one to input stock name (counter_selection) and the second one to plot stock price data. However, when I tried to use the data input from the first class using controller.get_page function it does not return the value input by Entry. The code is as below:
class Application(Tk.Tk):
'''A GUI Application for FCN Demo'''
def __init__(self):
'''Initialization of frame'''
Tk.Tk.__init__(self)
container = Tk.Frame(self)
container.pack(side='top', fill = 'both', expand = True)
container.grid_rowconfigure(0,weight = 1)
container.grid_columnconfigure(0,weight = 1)
self.frames = {}
for F in (StartPage, counter_selection, plot_counter):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row= 0, column = 0, sticky = "nsew")
self.show_frame(StartPage)
def get_page(self,classname):
'''Returns an instace of page given it's class name as a string'''
for page in self.frames.values():
if str(page.__class__.__name__) == classname:
return page
return None
def show_frame(self,cont):
frame = self.frames[cont]
frame.tkraise()
class counter_selection(Tk.Frame):
def __init__(self, parent, controller):
'''This is to create the widgets to input stock'''
Tk.Frame.__init__(self,parent)
self.controller = controller
label1 = Tk.Label(self, text = "Please Enter 3 counter names")
label1.pack(padx = 10, pady = 10)
self.entry1 = Tk.Entry(self)
self.entry1.pack()
self.entry2 = Tk.Entry(self)
self.entry2.pack()
self.entry3 = Tk.Entry(self)
self.entry3.pack()
button1 = Tk.Button (self, text = 'Confirm', command = lambda: controller.show_frame(plot_counter))
button1.pack()
class plot_counter(Tk.Frame):
'''This is to plot the graph of three selected counters'''
def __init__(self,parent,controller):
Tk.Frame.__init__(self,parent)
self.controller = controller
counterPage = self.controller.get_page('counter_selection')
self.counter1 = counterPage.entry1.get()
self.counter2 = counterPage.entry2.get()
self.counter3 = counterPage.entry3.get()
label1 = Tk.Label(self, text = self.counter11)
label1.pack()
The label1 does not show anything on the Frame, suggesting that it seems failed to get the value from the class. What's my mistake?
(PS: I didn't put the StartPage in since it is irrelevant for the question)
Why is Tkinter Entry's get function returning nothing?
TL;DR: your call to entry1.get() is done only once while instancing the plot_counter object, and never called again. Try putting it in a function to be called when displaying the Frame.
In the attempt to make an editor with composite tkinter widgets I have stumbled upon an error or a bug?
The methods defined in the MyTextWidget class : fl() and fh() are to set the font size of the text widget belonging to the same class.
My understanding is that this should work, but when I have three instances of the same MyTextWidget class on a canvas using create_window() method, upon pressing the fl button and fh button, the text size in all three textwidgets changes simulataneously. I first tested it with one widget, when everything was working to my satisfaction, I added two more instances of the same class, but now it is not working as I expected it.
If this helps, the version is Python 2.7 and Tkinter version is Revision: 81008, debian linux.
Your help is appreciated, especially if you can guide me to a book or document that helps with the relevant information. Kindly enlighten.
import Tkinter as tk
import tkFont
class MyTextWidget(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent)
self.text = tk.Text(self, *args, **kwargs)
self.tbar = tk.Frame(self, *args, **kwargs)
self.tbar.pack(side=tk.TOP, padx=2, pady=2, fill="x", expand=True)
self.fl = tk.Button(self.tbar)
self.fl.pack(side=tk.LEFT, padx=2, pady=2)
self.fh = tk.Button(self.tbar, *args, **kwargs)
self.fh.pack(side=tk.LEFT, padx=2, pady=2)
def fl():
print "fl called"
self.text.configure(font=tkFont.Font(family="mytsmc", size=7), spacing1=2,spacing2=22,spacing3=2)
def fh():
print "fh called"
self.text.configure(font=tkFont.Font(family="mytsmc", size=9), spacing1=2,spacing2=22,spacing3=2)
self.fl.config(text="fl", width=1, command=fl)
self.fh.config(text="fh", width=1, command=fh)
self.vsb = tk.Scrollbar(self, orient="vertical", command=self.text.yview)
self.text.configure(yscrollcommand=self.vsb.set,
font=tkFont.Font(family="mytsmc", size=8),
spacing1=2,spacing2=32,spacing3=2)
self.vsb.pack(side="right", fill="y")
# self.text.pack(side="left", fill="both", expand=True)
self.text.pack(side="left", fill="both", expand=False)
self.insert = self.text.insert
self.delete = self.text.delete
self.mark_set = self.text.mark_set
self.get = self.text.get
self.index = self.text.index
self.search = self.text.search
class myEditor(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.w = tk.Canvas(self, width=1320,
height=740,
borderwidth=1,
background='white',
relief='raised')
self.w.pack(anchor='center')
#One
self.scrolled_text1 = MyTextWidget(self)
self.firstwidget = self.w.create_window(10, 10,
anchor=tk.NW,
width=400,
height=400,
window=self.scrolled_text1)
with open("/home/username/datafiles/1.txt", "r") as f:
self.scrolled_text1.insert("1.0", f.read())
#Two
self.scrolled_text2 = MyTextWidget(self)
self.firstwidget = self.w.create_window(420, 10,
anchor=tk.NW,
width=400,
height=400,
window=self.scrolled_text2)
with open("/home/username/datafiles/2.txt", "r") as f:
self.scrolled_text2.insert("1.0", f.read())
#Three
self.scrolled_text3 = MyTextWidget(self)
self.firstwidget = self.w.create_window(830, 10,
anchor=tk.NW,
width=400,
height=400,
window=self.scrolled_text3)
with open("/home/username/datafiles/3.txt", "r") as f:
self.scrolled_text3.insert("1.0", f.read())
def switchtob(event=None):
self.scrolled_text1.text.focus()
print "switched to b"
def switchton(event=None):
self.scrolled_text2.text.focus()
print "switched to n"
def switchtom(event=None):
self.scrolled_text3.text.focus()
print "switched to m"
root.bind('<Control-b>',switchtob)
root.bind('<Control-n>',switchton)
root.bind('<Control-m>',switchtom)
root = tk.Tk()
myEditor(root).pack(side="top", fill="both", expand=True)
def exit(event=None):
quit()
root.bind('<Control-q>',exit)
root.mainloop()
The simple fix is to create the fonts once, save a reference to them, and then use them instead of instantiating new fonts every time you click the button.
class MyTextWidget(tk.Frame):
def __init__(self, parent, *args, **kwargs):
...
self.font1 = tkFont.Font(family="mytsmc", size=7)
self.font2 = tkFont.Font(family="mytsmc", size=9)
...
def fl():
self.text.configure(font=self.font1, spacing1=2,spacing2=22,spacing3=2)
def fh():
self.text.configure(font=self.font2, spacing1=2,spacing2=22,spacing3=2)