Tkinter class method and accessing class members - python

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)

Related

I can't organize interaction between two classes, belonging to different frames in tkinter

The two classes belong to different frames. The challenge is
to read data from the window ʻent_dataclass a parent ofForLeftFrame in a descendant class of ForRightFrameChild`.
When calling the parent class, a message appears in the console:
"name 'left_frame' is not defined". Can't figure out why?
Everything works in one frame. Please help me figure it out.
The code is as follows:
import tkinter as tk
#-----------
class ForLeftFrame():
def __init__(self, left_frame):
self.left_frame = left_frame
self.ent_data = tk.Entry(left_frame, width=8, bg='#3de',
fg='#dff')
self.ent_data.grid(column=0, row=1)
#-----------
class ForRightFrameChild(ForLeftFrame):
def __init__(self, right_frame):
self.right_frame = right_frame
super().__init__(self, left_frame)
self.left_frame = left_frame
self.transf_button = tk.Button(right_frame, text="Transfer to...",
bg='#489', fg='#dff', command=self.transferTo)
self.transf_button.grid(column=0, row=1)
def transferTo(self):
self.ent_data_st = self.ent_data.get()
print('Transfer to...', self.ent_data_st)
#-----------
class Application(tk.Frame):
"""Главный класс приложения"""
def __init__(self, master):
super().__init__()
left_frame = tk.Frame(master, bg='tan', relief='groove', bd=3)
left_frame.pack(side='left', fill='both', expand=1)
righr_frame = tk.Frame(master, bg='aqua', relief='groove', bd=3)
righr_frame.pack(side='right', fill='both', expand=1)
self.for_left_frame = ForLeftFrame(left_frame)
self.for_right_frame_child = ForRightFrameChild(righr_frame)
#-----------------------------
root = tk.Tk()
app = Application(root)
root.mainloop()
To achive this you need to have references and that is exactly what the error suggests.
So what I did in the code below is first to write your application as a subclass of tk.Tk() so the root parameter or you window is now your own class to modify, also it becomes the controller/director in this ensemble.
Also I created your frames as a subclass of frames, how you did it first with only the application class.
Now, all the magic is in this line and I will explain what happens here for a better understanding.
self.master.left_frame.ent_data.get()
So self reference to the instance of the class which we had bound to self.right_frame in our class named Application.
The class Application is also the master of self/right_frame.
The Application has the attribute left_frame which we did by self.left_frame in the Application class.
self.left_frame was bound to a reference of an instance from the class LeftFrame were we have defined the attribute ent_data which is an tk.Entry that has the method get.
I know it seems confusing in the beginning and you may need to read this text more then onnce, but this is how it works. It is more or less a straight way to go.
import tkinter as tk
#-----------
class LeftFrame(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.ent_data = tk.Entry(self, width=8, bg='#3de',fg='#dff')
self.ent_data.grid(column=0, row=1)
#-----------
class RightFrame(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self,master)
self.transf_button = tk.Button(self, text="Transfer to...",
bg='#489', fg='#dff',
command=self.transferTo)
self.transf_button.grid(column=0, row=1)
def transferTo(self):
self.ent_data_st = self.master.left_frame.ent_data.get()
print('Transfer to...', self.ent_data_st)
#-----------
class Application(tk.Tk):
def __init__(self):
super().__init__()
self.left_frame = LeftFrame(self)
self.right_frame = RightFrame(self)
self.left_frame.pack(side='left', fill='both', expand=1)
self.right_frame.pack(side='right', fill='both', expand=1)
#-----------------------------
app = Application()
app.mainloop()
EDIT:
import tkinter as tk
#-----------
class LeftFrame(object):
def __init__(self, frame):
self.f = frame
self.ent_data = tk.Entry(self.f, width=8, bg='#3de',fg='#dff')
self.ent_data.grid(column=0, row=1)
#-----------
class RightFrame(object):
def __init__(self, frame):
self.f = frame
self.transf_button = tk.Button(self.f, text="Transfer to...",
bg='#489', fg='#dff',
command=self.transferTo)
self.transf_button.grid(column=0, row=1)
def transferTo(self):
self.ent_data_st = self.f.master.for_left_frame.ent_data.get()
print('Transfer to...', self.ent_data_st)
#-----------
class Application(tk.Tk):
def __init__(self):
super().__init__()
frame1 = tk.Frame(self)
frame2 = tk.Frame(self)
self.for_left_frame = LeftFrame(frame1)
self.for_right_frame = RightFrame(frame2)
frame1.pack()
frame2.pack()
#-----------------------------
app = Application()
app.mainloop()

How to switch between different tkinter canvases from a start-up page and return back to start-up page in sub-canvas

I have created a start-up canvas, which contains buttoms to shfit to two other sub-canvases. In addition, in those two sub-canvases, buttom to return to start-up canvas is created. However, after I enter the sub-canvas, I fail to return to start-up canvas. That is to say, when I click the buttom to return to the start-up canvas, it will create a start-up canvas beside the sub-canvas instead of closing the subcanvas and shifting to the start-up canvas. Is there any way to switch between canvases and return to the main canvas? Thank you!
import tkinter as tk
from tkinter import ttk
import re
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self._Canvas= None
self.switch_Canvas(StartUpPage)
def switch_Canvas(self, Canvas_class):
new_Canvas = Canvas_class(self)
if self._Canvas is not None:
self._Canvas.destroy()
self._Canvas = new_Canvas
self._Canvas.pack()
class StartUpPage(tk.Canvas):
def __init__(self, master, *args, **kwargs):
tk.Canvas.__init__(self, master, *args, **kwargs)
tk.Frame(self)
tk.Label(self, text="Example").grid(column = 0, row = 0)
tk.Button(self, text="Canvas1",
command=lambda: master.switch_Canvas(PageOne)).grid(column = 0, row = 1)
tk.Button(self, text="Canvas2",
command=lambda: master.switch_Canvas(PageTwo)).grid(column = 0, row = 2)
class PageOne(tk.Canvas, tk.Tk):
def __init__(self, master, *args, **kwargs):
root = tk.Canvas.__init__(self, *args, **kwargs)
self.frame1 = tk.Frame(root, width=430)
self.frame1.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
tk.Label(self.frame1, text="First Canvas").pack(side="top", fill="x", pady=5)
tk.Button(self.frame1, text="Back to start-up page",
command=lambda: master.switch_Canvas(StartUpPage)).pack()
class PageTwo(tk.Canvas, tk.Tk):
def __init__(self, master, *args, **kwargs):
root = tk.Canvas.__init__(self, *args, **kwargs)
self.frame2 = tk.Frame(root, width=430)
self.frame2.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
tk.Label(self.frame2, text="Second Canvas").pack(side="top", fill="x", pady=5)
tk.Button(self.frame2, text="Back to start-up page",
command=lambda: master.switch_Canvas(StartUpPage)).pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
So, few things:
First: There's the wrong parent/master relationship in PageOne and PageTwo in
def __init__(self, master, *args, **kwargs):
Since you're inheriting from tk.Canvas, the new instance of the class has parent master which in itself get's passed from
SampleApp's switchCanvas as Canvas_class(self) meaning master is . or the (main/root) window.
But then (we're still inside PageOne) you're making root = tk.Canvas.__init__(self, *args, **kwargs) which becomes None since initializers return None.
Then you're creating a new Frame whose parent is root which is None.
In short
PageOne and PageTwo are (also) new instances of Canvas whose parent is the main window, or the instance made by SampleApp.
root aka the Frame's parent is None (Which I even don't know how that affects it when you position it byself.frame1.pack(fill=tk.BOTH, side=tk.LEFT, expand=True))
Hence I assume you're trying to making PageOne and PageTwo instances of Frame and put a Canvas inside them.
These are the few changes I made to the two classes: Their instances are now both of (type) tk.Frame that contains the other widgets.
class PageTwo(tk.Frame): # Sub-lcassing tk.Frame
def __init__(self, master, *args, **kwargs):
# self is now an istance of tk.Frame
tk.Frame.__init__(self,master, *args, **kwargs)
# make a new Canvas whose parent is self.
self.canvas = tk.Canvas(self,bg='yellow', width=430)
self.label = tk.Label(self, text="Second Canvas").pack(side="top", fill="x", pady=5)
self.button = tk.Button(self, text="Back to start-up page",
command=lambda: master.switch_Canvas(StartUpPage))
self.button.pack()
# pack the canvas inside the self (frame).
self.canvas.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
#print('is instance',isinstance(self,tk.Frame))
Second: Keeping track of which instances/objects are created, instead of continuously spawning new ones. I decided to keep it simple and create an internal dictionary whose keys are the classes of StratUpPage,PageOne or PageTwo meaning each class gets to have one instance that can be switched to.
def switch_Canvas(self, Canvas_class):
# Unless the dictionary is empty, hide the current Frame (_mainCanvas is a frame)
if self._mainCanvas:
self._mainCanvas.pack_forget()
# Modification 2: is the Class type passed is a one we have seen before?
canvas = self._allCanvases.get(Canvas_class, False)
# if Canvas_class is a new class type, canvas is False
if not canvas:
# Instantiate the new class
canvas = Canvas_class(self)
# Store it's type in the dictionary
self._allCanvases[Canvas_class] = canvas
# Pack the canvas or self._mainCanvas (these are all frames)
canvas.pack(pady = 60)
# and make it the 'default' or current one.
self._mainCanvas = canvas
Entire Code
import tkinter as tk
from tkinter import ttk
import re
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self._mainCanvas= None
# The dictionary to hold the class type to switch to
# Each new class passed here, will only have instance or object associated with it (i.e the result of the Key)
self._allCanvases = dict()
# Switch (and create) the single instance of StartUpPage
self.switch_Canvas(StartUpPage)
def switch_Canvas(self, Canvas_class):
# Unless the dictionary is empty, hide the current Frame (_mainCanvas is a frame)
if self._mainCanvas:
self._mainCanvas.pack_forget()
# is the Class type passed one we have seen before?
canvas = self._allCanvases.get(Canvas_class, False)
# if Canvas_class is a new class type, canvas is False
if not canvas:
# Instantiate the new class
canvas = Canvas_class(self)
# Store it's type in the dictionary
self._allCanvases[Canvas_class] = canvas
# Pack the canvas or self._mainCanvas (these are all frames)
canvas.pack(pady = 60)
# and make it the 'default' or current one.
self._mainCanvas = canvas
class StartUpPage(tk.Canvas):
def __init__(self, master, *args, **kwargs):
tk.Canvas.__init__(self, master, *args, **kwargs)
tk.Frame(self) # Here the parent of the frame is the self instance of type tk.Canvas
tk.Label(self, text="Example").grid(column = 0, row = 0)
tk.Button(self, text="Canvas1",
command=lambda: master.switch_Canvas(PageOne)).grid(column = 0, row = 1)
tk.Button(self, text="Canvas2",
command=lambda: master.switch_Canvas(PageTwo)).grid(column = 0, row = 2)
class PageOne(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self,master, *args, **kwargs)
self.canvas = tk.Canvas(self,bg='blue', width=430)
print('got',self,master,args,kwargs)
tk.Label(self, text="First Canvas").pack(side="top", fill="x", pady=5)
tk.Button(self, text="Back to start-up page",
command=lambda: master.switch_Canvas(StartUpPage)).pack()
self.canvas.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
class PageTwo(tk.Frame): # Sub-lcassing tk.Frame
def __init__(self, master, *args, **kwargs):
# self is now an istance of tk.Frame
tk.Frame.__init__(self,master, *args, **kwargs)
# make a new Canvas whose parent is self.
self.canvas = tk.Canvas(self,bg='yellow', width=430)
self.label = tk.Label(self, text="Second Canvas").pack(side="top", fill="x", pady=5)
self.button = tk.Button(self, text="Back to start-up page",
command=lambda: master.switch_Canvas(StartUpPage))
self.button.pack()
# pack the canvas inside the self (frame).
self.canvas.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
#print('is instance',isinstance(self,tk.Frame))
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
EDIT: Visual Demo

Tkinter widget after method

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[:])

Not using grid anywhere in class still gives error 'cannot use geometry manager pack inside . which already has slaves managed by grid' in tkinter

class QuadEQPlot:
width=0;height=0;centerh=0;centerw=0;root=None
def __init__(self):
root = Tk()
root.title("Quadratic Equation Plot")
width = 1200
height = 800
centerh = height/2
centerw=width/2
def init_widgets(self,a,b,c):
canvas_frame = Canvas(QuadEQPlot.root,width=QuadEQPlot.width, height=QuadEQPlot.height, bg='white')
self.plot_axis(QuadEQPlot.root,QuadEQPlot.width,QuadEQPlot.height,QuadEQPlot.centerh,QuadEQPlot.centerw,canvas_frame)
#start point
x=-5.00
xy = [] #array of points
while (x<6):
# x coordinates
xy.append(x*9 + QuadEQPlot.centerw)
# y coordinates
xy.append(QuadEQPlot.centerh - (a*(x**2) +b*x +c)*9 )
x+=0.01
#plot all accumulated points
quad_line = canvas_frame.create_line(QuadEQPlot.root,xy, fill='blue')
canvas_frame.pack()
root.mainloop()
def plot_axis(self,root,width,height,centerh,centerw,canvas_frame):
# x and y axis plot
center_line = canvas_frame.create_line(root,0, centerh, width, centerh, fill='black')
center_line = canvas_frame.create_line(root,centerw, 0, centerw, height, fill='black')
diffx=9
markno=1
# marking on x axis
while(diffx<=centerw and markno<7):
xrmarking=canvas_frame.create_line(root,centerw+diffx,centerh,centerw+diffx,centerh+5,fill='black')
# marking text on rhs of x axis
xrtext=canvas_frame.create_text(root,centerw+diffx,centerh+12,text="%d" %markno,fill='black')
xlmarking=canvas_frame.create_line(root,centerw-diffx,centerh,centerw-diffx,centerh+5,fill='black')
#marking text on lhs of x axis
xltext=canvas_frame.create_text(root,centerw-diffx,centerh+12,text="-%d" %markno,fill='black')
markno+=1
diffx+=9
#zerotext=canvas_frame.create_text(centerw+5,centerh+9,text="0",fill="black")
diffy=0
markno=0
# marking on y axis
while(diffy<=centerh):
yrmarking=canvas_frame.create_line(root,centerw,centerh+diffy,centerw+5,centerh+diffy,fill='black')
#yrtext=canvas_frame.create_text(centerw+12,centerh+diffy,text="%d" %markno,fill='black')
ylmarking=canvas_frame.create_line(root,centerw,centerh-diffy,centerw+5,centerh-diffy,fill='black')
#yltext=canvas_frame.create_text(centerw+12,centerh-diffy,text="%d" %markno,fill='black')
#markno+=1
diffy+=9
I am calling this class from another class with a tkinter frame that uses grid..however this class doesn't use it and on line canvas_frame.pack() it gives the error 'cannot use geometry manager pack inside . which already has slaves managed by grid '
code for calling function:
def submit(self,a,b,c):
obj=QuadEQPlot()
obj.init_widgets(a,b,c)
code for calling class:
class CoefficientsDialog:
def __init__(self):
master = Tk()
master.minsize(width=200, height=100)
#master.pack()
Label(master, text="X^2 +").grid(row=2,column=5)
Label(master, text="X +").grid(row=4,column=5)
e1 = Entry(master)
e2 = Entry(master)
e3 = Entry(master)
a=0;b=0;c=0
e1.grid(row=2, column=3)
e2.grid(row=4, column=3)
e3.grid(row=6, column=3)
try:
a=int(e1.get())
b=int(e2.get())
c=int(e3.get())
except ValueError as e:
a=0;b=1;c=0
#Button(master, text='Quit', command=master.quit).grid(row=3, column=0, sticky=W, pady=4)
Button(master, text='Submit', command=lambda : self.submit(a,b,c)).grid(row=8, column=3, sticky=W, pady=4)
master.mainloop( )
def submit(self,a,b,c):
obj=QuadEQPlot()
obj.init_widgets(a,b,c)
If you use this structure, then it is very easy to keep on top of the different views that you have. Also, it allows you to have different root windows running at the same time:
import tkinter as tk
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.container = Frame(self)
self.container.pack(side="top", expand=True)
self.container.grid_rowconfigure(0, weight=1)
self.container.grid_columnconfigure(0, weight=1)
for F in (pageOne, pageTwo):
self.frame = F(self.container, self)
self.frames[F] = self.frame
self.frame.grid(row=0, column=0, sticky="nsew", pady=5)
self.show_frame(pageOne)
def show_frame(self, frame):
self.frame = self.frames[frame]
self.frame.tkraise()
class pageOne(tk.Frame):
def __init__(self, controller, parent):
tk.Frame.__init__(self, parent)
self.button1 = tk.Button(self, text="button")
self.button1.pack()
class pageTwo(tk.Frame):
def __init__(self, controller, parent):
tk.Frame.__init__(self, parent)
self.button1 = tk.Button(self, text="button")
self.button1.pack()
app = App()
This shows a simple app that uses three classes, one being the root, the others being sample pages. To flip to a different Frame with the same root class, all you have to do is call controller.show_frame(FRAMENAME), which will switch the frames that are on the top. To have multiple different roots, you can just make a new root class, and structure and populate it in the same way.
I just wanted to comment because i got this Error and tried figuring out why, in my case it was due to using super.init() in my window/page classes..

Python tkinter text modified callback

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()

Categories