Tkinter frame shrinks and then grows when width and height is given - python

I have created a frame that holds a specialized text widget. In my app I have a button to change the color mode from light to dark and vice versa. everything works quite well with the color mode change except the specialized text widget. I have narrowed down the problem and have found that it is the width=134, height=30 that is causing the problem. No idea why though. Any ideas?
Code to reproduce problem:
from tkinter import *
class ultra_text(Frame):
def __init__(self, *args, **kwargs):
self.window = kwargs.pop("window", None)
self.color_mode = kwargs.pop("color_mode")
Frame.__init__(self, *args, **kwargs)
if "height" and "width" in kwargs:
self.text = Text(self, height=kwargs["height"], width=kwargs["width"], borderwidth=2, relief=RIDGE, wrap=NONE, undo=True, font=("Courier New bold", 15))
else:
self.text = Text(self, font=("Courier New bold", 15), wrap="none", undo=True, borderwidth=2, relief=RIDGE, width=120, height=30)
self.scrollbar = Scrollbar(self, orient=VERTICAL, command=self.text.yview)
self.text.configure(yscrollcommand=self.scrollbar.set)
self.numberLines = TextLineNumbers(self, width=40)
self.numberLines.attach(self.text)
self.scrollbar.pack(side=RIGHT, fill=Y)
self.numberLines.pack(side=LEFT, fill=Y, padx=(5, 0))
self.text.pack(side=RIGHT, fill=BOTH, expand=True)
self.text.bind("<Key>", self.onPressDelay)
self.text.bind("<Button-1>", self.numberLines.redraw)
self.scrollbar.bind("<Button-1>", self.onScrollPress)
self.text.bind("<MouseWheel>", self.onPressDelay)
self.window.bind("<KeyRelease>", self.redraw())
#Place tag_config here
def change_color(self, new_color):
if new_color == "Dark":
bg = "black"
fg = "white"
else:
bg = "white"
fg = "black"
self.text.config(bg=bg, fg=fg, insertbackground=fg)
self.numberLines.config(bg=bg)
def onScrollPress(self, *args):
self.scrollbar.bind("<B1-Motion>", self.numberLines.redraw)
def onScrollRelease(self, *args):
self.scrollbar.unbind("<B1-Motion>", self.numberLines.redraw)
def onPressDelay(self, *args):
self.after(2, self.numberLines.redraw)
def get(self, *args, **kwargs):
return self.text.get(*args, **kwargs)
def insert(self, *args, **kwargs):
return self.text.insert(*args, **kwargs)
def delete(self, *args, **kwargs):
return self.text.delete(*args, **kwargs)
def index(self, *args, **kwargs):
return self.text.index(*args, **kwargs)
def redraw(self):
self.numberLines.redraw()
class TextLineNumbers(Canvas):
def __init__(self, *args, **kwargs):
Canvas.__init__(self, *args, **kwargs, highlightthickness=0)
self.textwidget = None
def attach(self, text_widget):
self.textwidget = text_widget
def redraw(self, *args):
'''redraw line numbers'''
self.delete("all")
i = self.textwidget.index("#0,0")
while True :
dline= self.textwidget.dlineinfo(i)
if dline is None: break
y = dline[1]
linenum = str(i).split(".")[0]
self.create_text(2, y, anchor="nw", text=linenum, fill="#808090") #606366
i = self.textwidget.index("%s+1line" % i)
if __name__ == "__main__":
window = Tk()
color_mode = "Light"
window.geometry("%dx%d+0+0" % (window.winfo_screenwidth(), window.winfo_screenheight()))
my_text = ultra_text(window, window = window, color_mode="Dark", width=134, height=30)#Problem is here <- get rid of ", width=134, height=30" and it works. Keep it and it has the bug. How do I get rid of the bug while keeping the width and height?
my_text.place(relx=.5, rely=.5, anchor=CENTER)
def change_color_mode():
global my_text
global color_mode
if color_mode == "Dark":
color_mode = "Light"
else:
color_mode = "Dark"
if color_mode == "Dark":
bg = "black"
else:
bg = "white"
my_text.change_color(color_mode) #This part is fine and does the bulk of the color_changing
my_text.configure(bg=bg)
change_color_mode_button.config(highlightbackground=bg)
window.configure(bg=bg)
change_color_mode_button = Button(window, text="Change Color Mode", command=change_color_mode)
change_color_mode_button.pack()
window.mainloop()

I figured out the solution; if we remove the width and height from the main part it keeps its size because of the else statement but gets rid of the bug.
Code:
from tkinter import *
class ultra_text(Frame):
def __init__(self, *args, **kwargs):
self.window = kwargs.pop("window", None)
self.color_mode = kwargs.pop("color_mode")
Frame.__init__(self, *args, **kwargs)
if "height" and "width" in kwargs:
self.text = Text(self, height=kwargs["height"], width=kwargs["width"], borderwidth=2, relief=RIDGE, wrap=NONE, undo=True, font=("Courier New bold", 15))
else:
self.text = Text(self, font=("Courier New bold", 15), wrap="none", undo=True, borderwidth=2, relief=RIDGE, width=120, height=30)
self.scrollbar = Scrollbar(self, orient=VERTICAL, command=self.text.yview)
self.text.configure(yscrollcommand=self.scrollbar.set)
self.numberLines = TextLineNumbers(self, width=40)
self.numberLines.attach(self.text)
self.scrollbar.pack(side=RIGHT, fill=Y)
self.numberLines.pack(side=LEFT, fill=Y, padx=(5, 0))
self.text.pack(side=RIGHT, fill=BOTH, expand=True)
self.text.bind("<Key>", self.onPressDelay)
self.text.bind("<Button-1>", self.numberLines.redraw)
self.scrollbar.bind("<Button-1>", self.onScrollPress)
self.text.bind("<MouseWheel>", self.onPressDelay)
self.window.bind("<KeyRelease>", self.redraw())
#Place tag_config here
def change_color(self, new_color):
if new_color == "Dark":
bg = "black"
fg = "white"
else:
bg = "white"
fg = "black"
self.text.config(bg=bg, fg=fg, insertbackground=fg)
self.numberLines.config(bg=bg)
def onScrollPress(self, *args):
self.scrollbar.bind("<B1-Motion>", self.numberLines.redraw)
def onScrollRelease(self, *args):
self.scrollbar.unbind("<B1-Motion>", self.numberLines.redraw)
def onPressDelay(self, *args):
self.after(2, self.numberLines.redraw)
def get(self, *args, **kwargs):
return self.text.get(*args, **kwargs)
def insert(self, *args, **kwargs):
return self.text.insert(*args, **kwargs)
def delete(self, *args, **kwargs):
return self.text.delete(*args, **kwargs)
def index(self, *args, **kwargs):
return self.text.index(*args, **kwargs)
def redraw(self):
self.numberLines.redraw()
class TextLineNumbers(Canvas):
def __init__(self, *args, **kwargs):
Canvas.__init__(self, *args, **kwargs, highlightthickness=0)
self.textwidget = None
def attach(self, text_widget):
self.textwidget = text_widget
def redraw(self, *args):
'''redraw line numbers'''
self.delete("all")
i = self.textwidget.index("#0,0")
while True :
dline= self.textwidget.dlineinfo(i)
if dline is None: break
y = dline[1]
linenum = str(i).split(".")[0]
self.create_text(2, y, anchor="nw", text=linenum, fill="#808090") #606366
i = self.textwidget.index("%s+1line" % i)
if __name__ == "__main__":
window = Tk()
color_mode = "Light"
window.geometry("%dx%d+0+0" % (window.winfo_screenwidth(), window.winfo_screenheight()))
my_text = ultra_text(window, window = window, color_mode="Dark")
my_text.place(relx=.5, rely=.5, anchor=CENTER)
def change_color_mode():
global my_text
global color_mode
if color_mode == "Dark":
color_mode = "Light"
else:
color_mode = "Dark"
if color_mode == "Dark":
bg = "black"
else:
bg = "white"
my_text.change_color(color_mode) #This part is fine and does the bulk of the color_changing
my_text.configure(bg=bg)
change_color_mode_button.config(highlightbackground=bg)
window.configure(bg=bg)
change_color_mode_button = Button(window, text="Change Color Mode", command=change_color_mode)
change_color_mode_button.pack()
window.mainloop()
It seems that because it is inputting the width and height from kwargs that it alters the width and height variables to have a problem but I can't be quite sure.

Related

Grey overlay in the middle of my GUI - TKinter

I'm having trouble to understand why there is a grey row in my GUI. It's like a overlay, you can write "behind" it but it's is bothering me. Anyone any idea where it comes from?
I'm trying out classes for the first time, when I write the code without classes the problem doesn't occur.
Code:
class LabelInput(tk.Frame):
def __init__(self, parent, label, var, input_class=ttk.Entry,input_args=None, label_args=None, **kwargs):
super().__init__(parent, **kwargs)
input_args = input_args or {}
label_args = label_args or {}
self.variable = var
self.variable.label_widget = self
if input_class in (ttk.Checkbutton, ttk.Button):
input_args["text"] = label
else:
self.label = ttk.Label(self, text=label, **label_args)
self.label.grid(row=0, column=0, sticky=(tk.W + tk.E))
if input_class in (ttk.Checkbutton, ttk.Button, ttk.Radiobutton):
input_args["variable"] = self.variable
else:
input_args["textvariable"] = self.variable
if input_class == ttk.Radiobutton:
self.input = tk.Frame(self)
for v in input_args.pop('values', []):
button = ttk.Radiobutton(self.input, value=v, text=v, **input_args)
button.pack(side=tk.LEFT, ipadx=10, ipady=2, expand=True, fill='x')
else:
self.input = input_class(self, **input_args)
self.input.grid(row=1, column=0, sticky=(tk.W+tk.E))
self.columnconfigure(0, weight=1)
def grid(self, sticky=(tk.E + tk.W), **kwargs):
super().grid(sticky=sticky, **kwargs)
Problem:

Pop up message to appear when text is highlighted using python?

I want to highlight a given token inside a input text field (We can have several token highlighted) and when the user has the mouse over this token we get up dialogox.
I tried the following:
import tkinter as tk
from tkinter import *
class Example(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.l1 = tk.Label(self, text="Hover over me")
self.l2 = tk.Label(self, text="", width=40)
self.l1.pack(side="top")
self.l2.pack(side="top", fill="x")
self.inputText = tk.Text(root, height = 10, width = 70, bg = "light yellow")
self.inputText.insert('1.0', "token1 token2 token3 etc.")
self.inputText.pack()
self.display_annotate = tk.Button(self, height = 2, width = 20, text ="Annotate text", command = lambda: self.add_highlighter())
self.display_annotate.place(x = 750, y = 20)
print(self.__dict__.keys())
self.l1.bind("<Enter>", lambda event, text="text": self.on_enter(text=text))
self.l1.bind("<Leave>", self.on_leave)
def take_input(self,):
text_to_annotate = self.inputText.get("1.0", "end-1c")
print(text_to_annotate)
return text_to_annotate
def on_enter(self, text):
self.l2.configure(text=text)
def on_leave(self, event):
self.l2.configure(text="")
def add_highlighter(self):
self.inputText.tag_add("start", "1.0", "1.5")
self.inputText.bind("<Enter>", lambda event, text="ali": self.on_enter(text=text))
self.inputText.tag_config("start", background= "black", foreground= "white")
if __name__ == "__main__":
root = tk.Tk()
scrollb = tk.Scrollbar(root)
scrollb.pack(side = tk.RIGHT, fill=tk.Y)
var1 = tk.IntVar()
var2 = tk.IntVar()
root.geometry("900x500+10+10")
root.title('Annotation page')
Example(root).pack(side="top", fill="both", expand="true")
root.mainloop()
It works to highlight the concerned token 1 between the caracters 0 and 5. But it is not working when I haveover the mouse over token 1. noting that it is working for the label.
Any suggestion?
If I got you right, I've added a label when you hover over a token (together with rotating tokens)
import tkinter as tk
from tkinter import *
def on_enter(text, obj, e=None):
obj.float_lbl = Label(root, text=text)
obj.float_lbl.place(x=e.x + obj.winfo_x(), y=e.y + obj.winfo_y())
def on_leave(obj, event=None):
obj.float_lbl.destroy()
class Example(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.l1 = tk.Label(self, text="Hover over me")
self.l1.pack(side="top")
# creating new Text object with new methods and properties
self.inputText = InputText()
# creating 15 new tokens
for i in range(15):
self.inputText.add_token(f"token{i + 1}")
self.inputText.pack()
# binding rotate_tag method to button
self.display_annotate = tk.Button(self, height=2, width=20, text="Annotate text",
command=self.inputText.rotate_tag)
self.display_annotate.place(x=750, y=20)
self.l1.bind("<Enter>", lambda event, text="text": on_enter(text=text, e=event, obj=self.l1))
self.l1.bind("<Leave>", lambda event: on_leave(event=event, obj=self.l1))
class Token:
"""
Token object for InputText
"""
def __init__(self, name: str, pos: int, tag_parent: Text):
self.token_name = name
self.begin = "1." + str(pos)
self.end = "1." + str(pos + len(name))
self.parent = tag_parent
self.add_tag()
self.bind_tag()
def add_tag(self):
self.parent.tag_add(self.token_name, self.begin, self.end)
def bind_tag(self):
self.parent.tag_bind(self.token_name, "<Enter>",
lambda event, text="text": on_enter(obj=self.parent, e=event, text=self.token_name))
self.parent.tag_bind(self.token_name, "<Leave>",
lambda event, text="text": on_leave(obj=self.parent, event=event))
def highlight_tag(self):
self.parent.tag_config(self.token_name, background='red', foreground='white')
def disable_tag(self):
self.parent.tag_config(self.token_name, background='gray', foreground='black')
class InputText(Text):
"""
Text object with methods to add tokens and rotate tags
"""
def __init__(self):
super(InputText, self).__init__(root, height=10, width=70, bg="light yellow")
self.tokens = []
self.last = 0
self.current_tag = None
def add_token(self, name):
self.insert(END, name + " ")
self.tokens.append(Token(name, self.last, self))
self.last += (1 + len(name))
def rotate_tag(self):
if self.current_tag is None:
self.current_tag = 0
else:
self.tokens[self.current_tag - 1].disable_tag()
self.tokens[self.current_tag].highlight_tag()
self.current_tag = min(self.current_tag + 1, len(self.tokens) - 1)
if __name__ == "__main__":
root = tk.Tk()
scrollb = tk.Scrollbar(root)
scrollb.pack(side=tk.RIGHT, fill=tk.Y)
var1 = tk.IntVar()
var2 = tk.IntVar()
root.geometry("900x500+10+10")
root.title('Annotation page')
Example().pack(side="top", fill="both", expand="true")
root.mainloop()

Adding line numbers to text widget using Tkinter

I have text box my_text on gui at location
my_text = Text(root, width=98, height=35, font=("Helvetica", 10), bg="cyan", fg="black")
my_text.grid(row=4, column=0, padx=(20, 50), pady=(20, 0), rowspan=3, sticky="e")
I want to add lines numbers to this text box using Bryan Oakley code here.
My code:
import tkinter as tk
from tkinter import *
root = Tk()
my_text = Text(root, width=98, height=35, font=("Helvetica", 10), bg="cyan", fg="black")
my_text.grid(row=4, column=0, padx=(20, 50), pady=(20, 0), rowspan=3, sticky="e")
text_file = open("sample.xml", 'r')
s = text_file.read()
my_text.delete("1.0", "end")
my_text.insert(END, s)
class TextLineNumbers(tk.Canvas):
def __init__(self, *args, **kwargs):
tk.Canvas.__init__(self, *args, **kwargs)
self.root = Tk()
self.textwidget = None
def attach(self, text_widget):
self.textwidget = text_widget
def redraw(self, *args):
'''redraw line numbers'''
self.delete("all")
i = self.textwidget.index("#0,0")
while True :
dline= self.textwidget.dlineinfo(i)
if dline is None: break
y = dline[1]
linenum = str(i).split(".")[0]
self.create_text(2,y,anchor="nw", text=linenum)
i = self.textwidget.index("%s+1line" % i)
def run(self):
self.root.mainloop()
linenos = TextLineNumbers()
linenos.attach(my_text)
linenos.redraw()
root.mainloop()
Code is not displaying line number. It is displaying just text. How to display linenumbers in text box? Thanks in advance.
You mentioned this great example, why not just modify it to suit your needs? I was curious and modified the Example class from given link by adding a button to call a function load_xml which loads files via filechooser, deletes the previous data in the CustomText widget and inserts the new data:
import tkinter as tk
from tkinter import filedialog
import os
class TextLineNumbers(tk.Canvas):
def __init__(self, *args, **kwargs):
tk.Canvas.__init__(self, *args, **kwargs)
self.textwidget = None
def attach(self, text_widget):
self.textwidget = text_widget
def redraw(self, *args):
'''redraw line numbers'''
self.delete("all")
i = self.textwidget.index("#0,0")
while True:
dline = self.textwidget.dlineinfo(i)
if dline is None:
break
y = dline[1]
linenum = str(i).split(".")[0]
self.create_text(2, y, anchor="nw", text=linenum)
i = self.textwidget.index("%s+1line" % i)
class CustomText(tk.Text):
def __init__(self, *args, **kwargs):
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, *args):
# let the actual widget perform the requested action
cmd = (self._orig,) + args
result = self.tk.call(cmd)
# generate an event if something was added or deleted,
# or the cursor position changed
if (args[0] in ("insert", "replace", "delete") or
args[0:3] == ("mark", "set", "insert") or
args[0:2] == ("xview", "moveto") or
args[0:2] == ("xview", "scroll") or
args[0:2] == ("yview", "moveto") or
args[0:2] == ("yview", "scroll")):
self.event_generate("<<Change>>", when="tail")
# return what the actual widget returned
return result
class Example(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.text = CustomText(self)
self.vsb = tk.Scrollbar(self, orient="vertical", command=self.text.yview)
self.text.configure(yscrollcommand=self.vsb.set)
self.text.tag_configure("bigfont", font=("Helvetica", "24", "bold"))
self.linenumbers = TextLineNumbers(self, width=30)
self.linenumbers.attach(self.text)
self.vsb.pack(side="right", fill="y")
self.linenumbers.pack(side="left", fill="y")
self.text.pack(side="right", fill="both", expand=True)
# new button to call load_xml and show status
self.load_button = tk.Button(root, text="Load file", command=self.load_xml)
self.load_button.pack(side="top")
self.text.bind("<<Change>>", self._on_change)
self.text.bind("<Configure>", self._on_change)
self.text.insert("end", "one\ntwo\nthree\n")
self.text.insert("end", "four\n", ("bigfont",))
self.text.insert("end", "five\n")
def _on_change(self, event):
self.linenumbers.redraw()
def load_xml(self):
"""Load any file, delete current text and insert new data"""
input_file = filedialog.askopenfilename(title="Load a textfile",
filetypes=(("XML", "*.xml"),
("Text", "*.txt"),
("All files", "*.*")),
initialdir=os.getcwd())
if input_file:
self.text.delete("1.0", "end")
self.load_button.config(text=f"Currently loaded: {input_file.split(os.sep)[-1]}")
with open(input_file, 'r') as f:
self.text.insert("end", f.read())
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()

How to duplicate tkinter widgets?

How to duplicate tkinter text widget so that you can add it in every Notebook tab? I am writing an editor using tkinter, and I have also added undo functions. The problem is, when I add a new tab, the undo function works only for that tab. When I delete that tab, it is not working for other tabs either.
from tkinter.ttk import Notebook
import tkinter.messagebox
class TextClass(Frame):
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
self.text = Text(self, bg='white', foreground="black", undo=True,
insertbackground='black', height=35, width=135,
selectbackground="blue")
self.scrollbar = Scrollbar(self, orient=VERTICAL, command=self.text.yview)
self.text.configure(yscrollcommand=self.scrollbar.set)
self.numberLines = TextLineNumbers(self, width=40, bg='#2A2A2A')
self.numberLines.attach(self.text)
self.scrollbar.pack(side=RIGHT, fill=Y)
self.numberLines.pack(side=LEFT, fill=Y, padx=(5, 0))
self.text.pack(fill='both', expand=True)
self.text.bind("<Key>", self.onPressDelay)
self.text.bind("<Button-1>", self.numberLines.redraw)
self.scrollbar.bind("<Button-1>", self.onScrollPress)
self.text.bind("<MouseWheel>", self.onPressDelay)
def undo():
try:
self.text.edit_undo()
except TclError:
tkinter.messagebox.showerror(
"Nothing to Undo",
"You have not typed anything to undo. Please type and try again!"
)
undo_button = Button(toolbar, text='Undo', relief='ridge', command=undo, bg='black', fg='white',
width=6)
undo_button.place(x=170, y=5)
hover(undo_button, on_entrance='white', on_exit='black', entrance_foreground='black', exit_fg='white')
def redo():
try:
self.text.edit_redo()
except TclError:
tkinter.messagebox.showerror(
"Nothing to Redo",
"You have not done an undo to redo. Please type and try again!"
)
redo_button = Button(toolbar, text='Redo', relief='ridge', command=redo, bg='black', fg='white',
width=6)
redo_button.place(x=230, y=5)
hover(redo_button, on_entrance='white', on_exit='black', entrance_foreground='black', exit_fg='white')
def onScrollPress(self, *args):
self.scrollbar.bind("<B1-Motion>", self.numberLines.redraw)
def onScrollRelease(self, *args):
self.scrollbar.unbind("<B1-Motion>", self.numberLines.redraw)
def onPressDelay(self, *args):
self.after(2, self.numberLines.redraw)
def get(self, *args, **kwargs):
return self.text.get(*args, **kwargs)
def insert(self, *args, **kwargs):
return self.text.insert(*args, **kwargs)
def delete(self, *args, **kwargs):
return self.text.delete(*args, **kwargs)
def index(self, *args, **kwargs):
return self.text.index(*args, **kwargs)
def redraw(self):
self.numberLines.redraw()
class TextLineNumbers(Canvas):
Canvas.text_widget = None
def attach(self, text_widget):
self.text_widget = text_widget
def redraw(self, *args):
self.delete("all")
i = self.text_widget.index("#0,0")
while True:
d_line = self.text_widget.dlineinfo(i)
if d_line is None:
break
y = d_line[1]
line_numbers = str(i).split(".")[0]
self.create_text(2, y, anchor="nw", text=line_numbers, fill="white")
i = self.text_widget.index("%s+1line" % i)
def add_tab():
global tab
tab = Frame(notebook)
notebook.add(tab, text=f'{"Untitled1.txt": ^20}')
global text
text = TextClass(tab, bg='white')
text.pack()
text.text.focus()
def close_tab():
notebook.forget('current')
def hover(widget, entrance_foreground, exit_fg, on_entrance, on_exit):
widget.bind("<Enter>", func=lambda e: widget.config(
bg=on_entrance,
fg=entrance_foreground
))
widget.bind("<Leave>", func=lambda e: widget.config(
bg=on_exit,
fg=exit_fg
))
root = Tk()
root.config(bg='white')
root.geometry("1260x680")
toolbar = Frame(root, bg='white', height=45)
toolbar.pack(expand=False, fill='x')
notebook = Notebook(root)
tab = Frame(notebook)
notebook.add(tab, text=f'{"Untitled.txt": ^20}')
notebook.place(x=50, y=60)
text = TextClass(tab, bg='white')
text.pack()
add_tab_btn = Button(toolbar, text='Add new tab', command=add_tab, relief='ridge', bg='black', fg='white')
hover(add_tab_btn, on_entrance='white', on_exit='black', entrance_foreground='black', exit_fg='white')
add_tab_btn.place(x=15, y=5)
delete_tab_btn = Button(toolbar, text='Delete tab', command=close_tab, relief='ridge', bg='black', fg='white')
hover(delete_tab_btn, on_entrance='white', on_exit='black', entrance_foreground='black', exit_fg='white')
delete_tab_btn.place(x=100, y=5)
root.after(200, text.redraw())
scr_menu()
root.mainloop()
As I suspected you keep making new undo/redo buttons over the old ones each time you create a new tab. This means that the button only works for the last tab. To fix that problem you have to move the undo/redo buttons inside the notebook. Just as a proof of concept change this line:
undo_button = Button(toolbar, text='Undo', relief='ridge', command=undo, bg='black', fg='white',
width=6)
to
undo_button = Button(self, text='Undo', relief='ridge', command=undo, bg='black', fg='white',
width=6)

How to scroll a GUI with tkinter in python using the mouse wheel on the center of the window

This code is fine, but I would like to scroll the page using the mousewheel just like we do with Chrome, when the pointer of the mouse is in the middle of the page.
import tkinter as tk
from random import randint
class ScrolledFrame(tk.Frame):
def __init__(self, parent, vertical=True, horizontal=False):
super().__init__(parent)
self._canvas = tk.Canvas(self)
self._canvas.grid(row=0, column=0, sticky='news') # changed
self._vertical_bar = tk.Scrollbar(self, orient='vertical', command=self._canvas.yview)
if vertical:
self._vertical_bar.grid(row=0, column=1, sticky='ns')
self._canvas.configure(yscrollcommand=self._vertical_bar.set)
self._horizontal_bar = tk.Scrollbar(self, orient='horizontal', command=self._canvas.xview)
if horizontal:
self._horizontal_bar.grid(row=1, column=0, sticky='we')
self._canvas.configure(xscrollcommand=self._horizontal_bar.set)
self._vertical_bar.config(command=self._canvas.yview)
self.inner = tk.Frame(self._canvas, bg='red')
self._window = self._canvas.create_window((0, 0), window=self.inner, anchor='nw')
self.columnconfigure(0, weight=1) # changed
self.rowconfigure(0, weight=1) # changed
self.inner.bind('<Configure>', self.resize)
self._canvas.bind('<Configure>', self.frame_width)
def frame_width(self, event):
canvas_width = event.width
self._canvas.itemconfig(self._window, width=canvas_width)
def resize(self, event=None):
self._canvas.configure(scrollregion=self._canvas.bbox('all'))
class Question:
def __init__(self, parent, question, answer):
self.parent = parent
self.question = question
self.answer = answer
self.create_widgets()
def get_input(self):
value = self.entry.get()
print('value:', value)
if value == self.answer:
print("Right it's " + self.answer)
self.label['text'] = self.question + "Right it's " + self.answer
else:
self.label['text'] = "Sorry, it was " + self.answer
def create_widgets(self):
self.labelframe = tk.LabelFrame(self.parent, text="Domanda:")
self.labelframe.pack(fill="both", expand=True)
self.label = tk.Label(self.labelframe, text=self.question)
self.label.pack(expand=True, fill='both')
self.entry = tk.Entry(self.labelframe)
self.entry.pack()
self.entry.bind("<Return>", lambda x: self.get_input())
root = tk.Tk()
root.title("Quiz")
root.geometry("400x300")
window = ScrolledFrame(root)
window.pack(expand=True, fill='both')
for i in range(10):
one = randint(1, 10)
two = randint(1, 10)
Question(window.inner, "How is the result of {} + {} ?".format(one, two), str(one + two))
root.mainloop()
I found how to make it
Ok, I had some problems asking how to make it, but thanks to https://code.activestate.com/recipes/580640-scrolling-frame-with-mouse-wheel-support/ I succeded in making the mousewheel work as I intended. If it can help, the example is here.
import tkinter as tk
from random import randint
# --- classes ---
try:
from Tkinter import Canvas, Frame
from ttk import Scrollbar
from Tkconstants import *
except ImportError:
from tkinter import Canvas, Frame
from tkinter.ttk import Scrollbar
from tkinter.constants import *
import platform
OS = platform.system()
class Mousewheel_Support(object):
# implemetation of singleton pattern
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = object.__new__(cls)
return cls._instance
def __init__(self, root, horizontal_factor=2, vertical_factor=2):
self._active_area = None
if isinstance(horizontal_factor, int):
self.horizontal_factor = horizontal_factor
else:
raise Exception("Vertical factor must be an integer.")
if isinstance(vertical_factor, int):
self.vertical_factor = vertical_factor
else:
raise Exception("Horizontal factor must be an integer.")
if OS == "Linux":
root.bind_all('<4>', self._on_mousewheel, add='+')
root.bind_all('<5>', self._on_mousewheel, add='+')
else:
# Windows and MacOS
root.bind_all("<MouseWheel>", self._on_mousewheel, add='+')
def _on_mousewheel(self, event):
if self._active_area:
self._active_area.onMouseWheel(event)
def _mousewheel_bind(self, widget):
self._active_area = widget
def _mousewheel_unbind(self):
self._active_area = None
def add_support_to(self, widget=None, xscrollbar=None, yscrollbar=None, what="units", horizontal_factor=None, vertical_factor=None):
if xscrollbar is None and yscrollbar is None:
return
if xscrollbar is not None:
horizontal_factor = horizontal_factor or self.horizontal_factor
xscrollbar.onMouseWheel = self._make_mouse_wheel_handler(widget, 'x', self.horizontal_factor, what)
xscrollbar.bind('<Enter>', lambda event, scrollbar=xscrollbar: self._mousewheel_bind(scrollbar))
xscrollbar.bind('<Leave>', lambda event: self._mousewheel_unbind())
if yscrollbar is not None:
vertical_factor = vertical_factor or self.vertical_factor
yscrollbar.onMouseWheel = self._make_mouse_wheel_handler(widget, 'y', self.vertical_factor, what)
yscrollbar.bind('<Enter>', lambda event, scrollbar=yscrollbar: self._mousewheel_bind(scrollbar))
yscrollbar.bind('<Leave>', lambda event: self._mousewheel_unbind())
main_scrollbar = yscrollbar if yscrollbar is not None else xscrollbar
if widget is not None:
if isinstance(widget, list) or isinstance(widget, tuple):
list_of_widgets = widget
for widget in list_of_widgets:
widget.bind('<Enter>', lambda event: self._mousewheel_bind(widget))
widget.bind('<Leave>', lambda event: self._mousewheel_unbind())
widget.onMouseWheel = main_scrollbar.onMouseWheel
else:
widget.bind('<Enter>', lambda event: self._mousewheel_bind(widget))
widget.bind('<Leave>', lambda event: self._mousewheel_unbind())
widget.onMouseWheel = main_scrollbar.onMouseWheel
#staticmethod
def _make_mouse_wheel_handler(widget, orient, factor=1, what="units"):
view_command = getattr(widget, orient + 'view')
if OS == 'Linux':
def onMouseWheel(event):
if event.num == 4:
view_command("scroll", (-1) * factor, what)
elif event.num == 5:
view_command("scroll", factor, what)
elif OS == 'Windows':
def onMouseWheel(event):
view_command("scroll", (-1) * int((event.delta / 120) * factor), what)
elif OS == 'Darwin':
def onMouseWheel(event):
view_command("scroll", event.delta, what)
return onMouseWheel
class Scrolling_Area(Frame, object):
def __init__(self, master, width=None, anchor=N, height=None, mousewheel_speed=2, scroll_horizontally=True, xscrollbar=None, scroll_vertically=True, yscrollbar=None, background=None, inner_frame=Frame, **kw):
Frame.__init__(self, master, class_="Scrolling_Area", background=background)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
self._width = width
self._height = height
self.canvas = Canvas(self, background=background, highlightthickness=0, width=width, height=height)
self.canvas.grid(row=0, column=0, sticky=N + E + W + S)
if scroll_vertically:
if yscrollbar is not None:
self.yscrollbar = yscrollbar
else:
self.yscrollbar = Scrollbar(self, orient=VERTICAL)
self.yscrollbar.grid(row=0, column=1, sticky=N + S)
self.canvas.configure(yscrollcommand=self.yscrollbar.set)
self.yscrollbar['command'] = self.canvas.yview
else:
self.yscrollbar = None
if scroll_horizontally:
if xscrollbar is not None:
self.xscrollbar = xscrollbar
else:
self.xscrollbar = Scrollbar(self, orient=HORIZONTAL)
self.xscrollbar.grid(row=1, column=0, sticky=E + W)
self.canvas.configure(xscrollcommand=self.xscrollbar.set)
self.xscrollbar['command'] = self.canvas.xview
else:
self.xscrollbar = None
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
self.innerframe = inner_frame(self.canvas, **kw)
self.innerframe.pack(anchor=anchor)
self.canvas.create_window(0, 0, window=self.innerframe, anchor='nw', tags="inner_frame")
self.canvas.bind('<Configure>', self._on_canvas_configure)
Mousewheel_Support(self).add_support_to(self.canvas, xscrollbar=self.xscrollbar, yscrollbar=self.yscrollbar)
#property
def width(self):
return self.canvas.winfo_width()
#width.setter
def width(self, width):
self.canvas.configure(width=width)
#property
def height(self):
return self.canvas.winfo_height()
#height.setter
def height(self, height):
self.canvas.configure(height=height)
def set_size(self, width, height):
self.canvas.configure(width=width, height=height)
def _on_canvas_configure(self, event):
width = max(self.innerframe.winfo_reqwidth(), event.width)
height = max(self.innerframe.winfo_reqheight(), event.height)
self.canvas.configure(scrollregion="0 0 %s %s" % (width, height))
self.canvas.itemconfigure("inner_frame", width=width, height=height)
def update_viewport(self):
self.update()
window_width = self.innerframe.winfo_reqwidth()
window_height = self.innerframe.winfo_reqheight()
if self._width is None:
canvas_width = window_width
else:
canvas_width = min(self._width, window_width)
if self._height is None:
canvas_height = window_height
else:
canvas_height = min(self._height, window_height)
self.canvas.configure(scrollregion="0 0 %s %s" % (window_width, window_height), width=canvas_width, height=canvas_height)
self.canvas.itemconfigure("inner_frame", width=window_width, height=window_height)
class Question:
def __init__(self, parent, question, answer):
self.parent = parent
self.question = question
self.answer = answer
self.create_widgets()
def get_input(self):
value = self.entry.get()
print('value:', value)
if value == self.answer:
print("Right it's " + self.answer)
self.label['text'] = self.question + "Right it's " + self.answer
else:
self.label['text'] = "Sorry, it was " + self.answer
def create_widgets(self):
self.labelframe = tk.LabelFrame(self.parent, text="Domanda:")
self.labelframe.pack(fill="both", expand=True)
self.label = tk.Label(self.labelframe, text=self.question)
self.label.pack(expand=True, fill='both')
self.entry = tk.Entry(self.labelframe)
self.entry.pack()
self.entry.bind("<Return>", lambda x: self.get_input())
# self.button = tk.Button(self.labelframe, text="Click", command=self.get_input)
# self.button.pack()
# --- main ---
root = tk.Tk()
root.title("Quiz")
root.geometry("400x300")
window = Scrolling_Area(root)
window.pack(expand=True, fill='both')
for i in range(10):
one = randint(1, 10)
two = randint(1, 10)
Question(window.innerframe, "How is the result of {} + {} ?".format(one, two), str(one + two))
domande = [("Qual รจ la prima leva del marketing mix? (prodotto o prezzo?", "prodotto")]
for d, r in domande:
Question(window.innerframe, d, r)
root.mainloop()

Categories

Resources