Removing a particular line in Text - python

I wrote a small program with Tkinter and user asked me to add a function in which user could able to delete a particular line.
My question is how to delete a particular line of text in Tkinter?
My code:
import tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.parent.state('zoomed')
self.Scrol = tk.Scrollbar(self.parent)
self.Text = tk.Text(self.parent, height=50, width=100)
self.Scrol.pack(side="right")
self.Text.pack()
self.Scrol.config(command=self.Text.yview)
self.Text.config(yscrollcommand=self.Scrol.set)
self.Text.tag_configure('red', foreground='red', underline=1)
self.count = 0
self.Text.insert('end', "{} test test test".format(str(self.count)) + "\n", 'red')
self.B = tk.Button(self.parent, text="add text", command=self.addText)
self.B.pack()
self.Text.bind("<Double-Button-1>", self.deleteLine)
def deleteLine(self, event):
self.Text.delete(1.0, 2.0)
def addText(self):
self.count += 1
self.Text.insert('end', "{} test test test".format(str(self.count)) + "\n", 'red')
root = tk.Tk()
app = MainApplication(root)
root.mainloop()
Now my code only deletes the first line every time when I click on any line. I would like to delete that line which I click on.

For deleting the current line the simplest way would probably be, as suggested in Bryan Oakley's comment, using 'current' argument in delete directly:
def deleteLine(self, event):
self.Text.delete('current linestart', 'current lineend+1c')
#return "break" # uncomment if you want to disable selection caused by double-clicking

Use the event.x and event.y attributes to get the line number the user clicked on and then use the Text widget to query what row that corresponds to. Finally delete the row. One side effect is that the text widget will select some text, because that is the default binding for the text widget when the double-button is clicked (not changeable unfortunately). You can remove the effects of that by returning "break" at the end of the deleteLine method.
def deleteLine(self, event):
point = '#' + str(event.x) + ',' + str(event.y)
line_number = self.Text.index(point).split('.')[0]
self.Text.delete(line_number + '.0', line_number + '.end + 1 char')
# Interrupt the default sel tag binding.
return "break"

Related

When I hide a button and unhide the button, another duplicate button is added in tkinter. How to fix this?

Functionality: When I click "Music theory" button, I want it to show another button ("Sight reading" button) and when I click "Music theory" button again, I want the "Sight reading button to hide.
Inside musictheorysubbtns() I'm able to hide the dropdown optionmenu when I click the "Sight reading" button. I tried doing the same for the "music theory button thats outside the function and it added two buttons on the window. I'm able to hide it but when I click "music theory button again, it adds 2 more duplicate buttons, now in total 4 buttons.
How to fix this?
...
tab6 = ttk.Frame(tabControl)
tabControl.add(tab6, text="Music")
tabControl.pack(expand=5, fill='both')
musicbtnsframe = tk.Frame(tab6)
musicbtnsframe.pack(side='top')
mtheorysubbtsframe = tk.Frame(tab6)
mtheorysubbtsframe.pack(anchor='center')
mtdropdownframe = tk.Frame(tab6)
mtdropdownframe.pack(anchor='center')
mrefreshingframe = tk.Frame(tab6)
mrefreshingframe.pack(anchor='center')
def musictheorysubbtns():
def mtheorydrops():
mtheory_opt = mtdropdown
mtheory_opt.config(width=50, font=('Helvetica', 12))
mtheory_opt.pack()
def mtheory_images(mt):
print(mt) # selected option
mt_label.config(image=mtimgz[mt])
mt_label = tk.Label(mtdropdownframe)
mt_label.pack(side = 'bottom', pady=padylength)
mtimgz = {}
for mtimgz_name in tradinglists.retracements:
mtimgz[mtimgz_name] = ImageTk.PhotoImage(Image.open("./Images/{}.png".format(mtimgz_name)))
mtvariable = tk.StringVar(tab2)
mtvariable.set(tradinglists.retracements[0])
mtdropdown = tk.OptionMenu(mtdropdownframe, mtvariable, *tradinglists.retracements, command=mtheory_images)
def refreshmtsub():
mtdropdownframe.pack_forget() if mtdropdownframe.winfo_manager() else mtdropdownframe.pack(anchor='center')
mtheory_k = tk.Button(mtheorysubbtsframe, text="Sight reading", width = artbtn_width, height = btnsize_height, command=lambda:[mtheorydrops(), refreshmtsub()])
mtheory_k.pack(side = 'left', padx=padxwidth, pady=padylength)
def refreshmt():
mtheorysubbtsframe.pack_forget() if mtheorysubbtsframe.winfo_manager() else mtheorysubbtsframe.pack(anchor='center')
theory_k = tk.Button(musicbtnsframe, text="Music theory", width = btnsize_width, height = btnsize_height, command=lambda:[musictheorysubbtns(), refreshmt()])
theory_k.pack(side='left', padx=padxwidth, pady=padylength)
v2:
tab6 = ttk.Frame(tabControl)
tabControl.add(tab6, text="Music")
tabControl.pack(expand=5, fill='both')
class ShowHideButton(tk.Button):
def __init__(self, parent, target_widget, *args, **kwargs):
self.target = target_widget
super().__init__(parent, *args, **kwargs)
self.config(command=self.toggle)
def toggle(self, force_off = False):
if force_off or self.target.winfo_manager():
self.target.pack_forget()
else:
self.target.pack()
if isinstance(self.target, ShowHideButton):
self.target.toggle(force_off=True)
musicbtnsframe = tk.Frame(tab6)
musicbtnsframe.pack(side='top')
mt_sub_frame = tk.Frame(tab6)
mt_sub_frame.pack(side='top')
mt_SRframe = tk.Frame(tab6)
mt_SRframe.pack(anchor='center')
mt_compframe = tk.Frame(tab6)
mt_compframe.pack(anchor='center')
def mt_images(m1t):
print(m1t) # selected option
mt_label.config(image=mtimgz[m1t])
mt_label = tk.Label(mt_SRframe)
mt_label.pack(side = 'bottom', pady=padylength)
mtimgz = {}
for mt_name in musiclists.sightReaing:
mtimgz[mt_name] = ImageTk.PhotoImage(importImageWithResize("./Music_images/Sightreading/{}.png".format(mt_name)))
mtvar = tk.StringVar(tab2)
mtvar.set(musiclists.sightReaing[0])
mt = tk.OptionMenu(mt_SRframe, mtvar, *musiclists.sightReaing, command=mt_images)
mt_opt = mt
mt_opt.config(width=50, font=('Helvetica', 12))
mt_opt.pack()
def mtcomp_images(mtcomp1t):
print(mtcomp1t) # selected option
mtcomp_label.config(image=mtcompimgz[mtcomp1t])
mtcomp_label = tk.Label(mt_compframe)
mtcomp_label.pack(side = 'bottom', pady=padylength)
mtcompimgz = {}
for mtcomp_name in musiclists.composition:
mtcompimgz[mtcomp_name] = ImageTk.PhotoImage(importImageWithResize("./Music_images/Composition/{}.png".format(mtcomp_name)))
mtcompvar = tk.StringVar(tab2)
mtcompvar.set(musiclists.composition[0])
mtcomp = tk.OptionMenu(mt_compframe, mtcompvar, *musiclists.composition, command=mtcomp_images)
mtcomp_opt = mtcomp
mtcomp_opt.config(width=50, font=('Helvetica', 12))
mtcomp_opt.pack()
def mt_sub_btns():
theory_k_sightreading_k = ShowHideButton(mt_sub_frame, mt_opt, text='Sight Reading')
theory_k_composition_k = ShowHideButton(mt_sub_frame, mtcomp_opt, text='Composition')
theory_k = ShowHideButton(musicbtnsframe, mt_sub_btns, text='Music theory')
theory_k.pack(side='left', padx=padxwidth, pady=padylength)
When you want functionality added to a feature in tkinter, it is usually best to customize the widgets to do what you need instead of stringing together functionality. Let me show you what I mean with an example. Since you want one button that show/hides another button, and another button that show/hides an optionmenu, it sounds like it would be best for you to have buttons that show/hide other widgets.
To do this, make your own version of a button:
import tkinter as tk
class ShowHideButton(tk.Button):
def __init__(self, parent, target_widget, *args, **kwargs):
self.target = target_widget
super().__init__(parent, *args, **kwargs)
self.config(command=self.toggle)
The ShowHideButton is our custom name of our new widget. The (tk.Button) says which widget we are basing it off of.
The __init__ method (that's init with two underscores on each side) is what happens right when you make this widget. The parameters we put in there basically all need to be there. self is just required for python reasons, parent and *args, **kwargs are things that tk.Button needs, but target_widget is just something I added to be the target of our show/hide tools.
self.target = target_widget saves the widget we pass through the parameters to the instance of our button.
The line that starts with 'super' makes python run tk.Button's init method so it will build everything that is needed to be a button. (Which is good because I am trying to take tkinter's button widget and adjust it to fit our needs.)
The last line sets the command of the button to a function called self.toggle. Since this happens, you will never need to set the command of our buttons. In fact, we want our buttons to hide/show some other widget, and the whole purpose is to have that functionality built in so it wouldn't make sense to manually set the command.
Put this under the init method to define what self.toggle does:
def toggle(self):
if or self.target.winfo_manager():
self.target.pack_forget()
else:
self.target.pack()
That should be the same indentation level as the init method. You can probably see what this does. It checks if self.target is packed or not and then changes it.
If you use this, you may notice something kind of strange: if you hide a widget, and that widget had opened something, it leaves that something packed. If you would like it to pass down hides, change it to be something like this:
def toggle(self, force_off = False):
if force_off or self.target.winfo_manager():
self.target.pack_forget()
else:
self.target.pack()
if isinstance(self.target, ShowHideButton):
self.target.toggle(force_off=True)
This will check if the target is itself a ShowHideButton and then turn it off as well if it is.
Here is my whole test script to demo the new button:
import tkinter as tk
root = tk.Tk()
class ShowHideButton(tk.Button):
def __init__(self, parent, target_widget, *args, **kwargs):
self.target = target_widget
super().__init__(parent, *args, **kwargs)
self.config(command=self.toggle)
def toggle(self, force_off = False):
if force_off or self.target.winfo_manager():
self.target.pack_forget()
else:
self.target.pack()
if isinstance(self.target, ShowHideButton):
self.target.toggle(force_off=True)
my_option_value = tk.StringVar()
my_option_value.set('opt1')
my_option_menu = tk.OptionMenu(root, my_option_value, 'opt1', 'opt2', 'etc')
s_r_button = ShowHideButton(root, my_option_menu, text='Sight Reading')
m_t_button = ShowHideButton(root, s_r_button, text='Music theory')
m_t_button.pack()
root.mainloop()
You can just copy and paste that class-block into your code and start using ShowHideButtons like they are any other widget. Note the order that I made the buttons in the demo above though. They need to have their targets when you make them so you have to make the ones that are targets before the ones that target them.
This could also be adapted to use grid or place instead of pack. Really, you can modify tkinter widgets in any way you like. Let us know if you have any other questions etc.
EDIT: if you would like a version of this where you can have more than one item being toggled by the button, here you go:
class ShowHideButton(tk.Button):
def __init__(self, parent, target_widgets, *args, **kwargs):
self.targets = target_widgets
super().__init__(parent, *args, **kwargs)
self.config(command=self.toggle)
def toggle(self, force_off = False):
for i in self.targets:
if force_off or i.winfo_manager():
i.pack_forget()
else:
i.pack()
if isinstance(i, ShowHideButton):
self.target.toggle(force_off=True)
Note that you must make target_widgets an iterable item (like a list or tuple) in this case. If you use this version but only want one widget to toggle, you can make a single-length list by surrounding the name of it by [ ].

Python tkinter how to get text from button I clicked on

I have code like this (just part of a code). I need when someone click on the button that is in list named buttonList then it gets buttons text.
This is code how I make render of those buttons. Its normally in class I put only the main part of code here.
So how can I get buttons text on click on him ?
def obsahOkna(self):
#vykresleni
radek = 0
bunka = 0
for i in range(100):
btn = Button(self.okno, text=seznamTextu[i], width="5", height="2", bg="black", command=self.getText)
btn.grid(row=radek, column=bunka)
bunka += 1
if bunka == 10 :
bunka = 0
radek +=1
def getText(self, udalost):
pass
Ok so here is an example using a class to perform what I think it is you are asking.
You want to use lambda in your command and assign the value of text to a variable. Then you pass that variable to the getTest(self, text) method to be able to print your button.
From your comment
Whole code is not need i just need way to get buttons text nothing else
I have created a bit of code to illustrate what you are wanting.
EDIT: I have added code that will allow you to change the configs of the button as well.
import tkinter as tk
# created this variable in order to test your code.
seznamTextu = ["1st Button", "2nd Button", "3rd Button", "4th Button", "5th Button"]
class MyButton(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.obsahOkna()
def obsahOkna(self):
radek = 0
bunka = 0
for i in range(5):
btn = tk.Button(self.parent, text=seznamTextu[i])
btn.config(command= lambda t=seznamTextu[i], btn = btn: self.getText(t, btn))
# in order for this to work you need to add the command in the config after the button is created.
# in the lambda you need to create the variables to be passed then pass them to the function you want.
btn.grid(row=radek, column=bunka)
bunka += 1
if bunka == 2 : # changed this variable to make it easier to test code.
bunka = 0
radek +=1
def getText(self, text, btn):
btn.configure(background = 'black', foreground = "white")
print("successfully called getText")
print(text)
if __name__ == "__main__":
root = tk.Tk()
myApp = MyButton(root)
root.mainloop()
Here is the result of running the program and pressing a couple buttons.

Python - Clearing and resetting Text widgit - Tkinter

I'm having a heck of time clearing this text wigit. I'll admit that I'm new to python and its GUI API, but I've read the documention and tried the suggestions on Stack Overflow to no avail.
I've seen many people suggest: self.text.delete(0.0, 'end')
this however is not working in the listener. Oddly it does work if I put it in the constructor. I get not stack trace from the listener either. Below is the code:
import tkinter
from tkinter import Text
def main():
CalculatorGUI(CalculatorController())
class CalculatorController:
def __init__(self):
self.ans = "0"
def calculate(self, textBox):
value = str("")
try:
inputValue = textBox.replace(",", "").replace(" ", "")
if inputValue[:1] in "-*/+%":
value = str(eval(self.ans + inputValue))[::-1]
else:
value = str(eval(inputValue))[::-1]
return self.makeHumanReadable(value)
except:
return "I cannot math that!"
def makeHumanReadable(self, stringValue):
if "." in stringValue:
decimal = stringValue[:stringValue.index(".")]
integer = stringValue[stringValue.index(".") + 1:]
self.ans = (decimal + "." + (','.join(integer[i:i+3] for i in range(0, len(integer), 3))))[::-1]\
.replace(",", "").replace(" ", "")
print("Current answer is: " + self.ans)
return (decimal + "." + (','.join(integer[i:i+3] for i in range(0, len(integer), 3))))[::-1]
else:
self.ans = ','.join(stringValue[i:i+3] for i in range(0, len(stringValue), 3))[::-1] \
.replace(",", "").replace(" ", "")
return ','.join(stringValue[i:i+3] for i in range(0, len(stringValue), 3))[::-1]
class CalculatorGUI:
def __init__(self, controller):
self.controller = controller
self.root = tkinter.Tk()
self.frame1 = tkinter.Frame(self.root)
self.frame2 = tkinter.Frame(self.frame1)
self.text = BetterText(self.frame1, height=1, borderwidth=0)
self.text.insert(1.0, "Enter a math statement:")
# self.text.delete(0.0, 'end') # If this is not commented out, it deletes
# the text but not when put in the listener
# self.text.clearAll() # Same here
self.text.configure(state="disabled")
self.entry = tkinter.Entry(self.frame2, width = 30)
self.calcButton = tkinter.Button(self.frame2, text="Calculate", \
command=self.calculate)
self.text.pack()
self.entry.pack()
self.calcButton.pack()
self.frame1.pack()
self.frame2.pack()
self.root.mainloop()
def calculate(self):
self.entry.delete(0, "end")
self.text.clearAll() # Does not work
if self.entry.get() != "":
self.text.insert("END", self.controller.calculate(self.entry.get()))
main()
Any ideas??? Using Python 3.4
EDIT: I even tried extending the Text widget and making a clearAll() method. Again it works in the constructor but not in the listener and it throws no errors. Its likely that there is a problem somewhere else in the code and I just don't see it.
class BetterText(Text):
def __init__(self, master=None, cnf={}, **kw):
Text.__init__(self, master, kw)
def clearAll(self):
self.delete(0.0, 'end')
The problem seems to be that you are using the text.configure(state="disabled") and then trying to write to the widget. In python, disabling the text box means that the user can't edit it, but it also means you can't. The reason your commented out code worked was that it came before the line you disabled the text widget. Try adding text.configure(state="normal") in your clear method before you attempt to change the text widget, and then set the state back when you are done. That seemed to fix the problem when I tried it out.

Display message when hovering over something with mouse cursor in Python

I have a GUI made with TKinter in Python. I would like to be able to display a message when my mouse cursor goes, for example, on top of a label or button. The purpose of this is to explain to the user what the button/label does or represents.
Is there a way to display text when hovering over a tkinter object in Python?
I think this would meet your requirements.
Here's what the output looks like:
First, A class named ToolTip which has methods showtip and hidetip is defined as follows:
from tkinter import *
class ToolTip(object):
def __init__(self, widget):
self.widget = widget
self.tipwindow = None
self.id = None
self.x = self.y = 0
def showtip(self, text):
"Display text in tooltip window"
self.text = text
if self.tipwindow or not self.text:
return
x, y, cx, cy = self.widget.bbox("insert")
x = x + self.widget.winfo_rootx() + 57
y = y + cy + self.widget.winfo_rooty() +27
self.tipwindow = tw = Toplevel(self.widget)
tw.wm_overrideredirect(1)
tw.wm_geometry("+%d+%d" % (x, y))
label = Label(tw, text=self.text, justify=LEFT,
background="#ffffe0", relief=SOLID, borderwidth=1,
font=("tahoma", "8", "normal"))
label.pack(ipadx=1)
def hidetip(self):
tw = self.tipwindow
self.tipwindow = None
if tw:
tw.destroy()
def CreateToolTip(widget, text):
toolTip = ToolTip(widget)
def enter(event):
toolTip.showtip(text)
def leave(event):
toolTip.hidetip()
widget.bind('<Enter>', enter)
widget.bind('<Leave>', leave)
The widget is where you want to add the tip. For example, if you want the tip when you hover over a button or entry or label, the instance of the same should be provided at the call time.
Quick note: the code above uses from tkinter import *
which is not suggested by some of the programmers out there, and they have valid points. You might want to make necessary changes in such case.
To move the tip to your desired location, you can change x and y in the code.
The function CreateToolTip() helps to create this tip easily. Just pass the widget and string you want to display in the tipbox to this function, and you're good to go.
This is how you call the above part:
button = Button(root, text = 'click mem')
button.pack()
CreateToolTip(button, text = 'Hello World\n'
'This is how tip looks like.'
'Best part is, it\'s not a menu.\n'
'Purely tipbox.')
Do not forget to import the module if you save the previous outline in different python file, and don't save the file as CreateToolTip or ToolTip to avoid confusion.
This post from Fuzzyman shares some similar thoughts, and worth checking out.
You need to set a binding on the <Enter> and <Leave> events.
Note: if you choose to pop up a window (ie: a tooltip) make sure you don't pop it up directly under the mouse. What will happen is that it will cause a leave event to fire because the cursor leaves the label and enters the popup. Then, your leave handler will dismiss the window, your cursor will enter the label, which causes an enter event, which pops up the window, which causes a leave event, which dismisses the window, which causes an enter event, ... ad infinitum.
For simplicity, here's an example that updates a label, similar to a statusbar that some apps use. Creating a tooltip or some other way of displaying the information still starts with the same core technique of binding to <Enter> and <Leave>.
import Tkinter as tk
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.l1.bind("<Enter>", self.on_enter)
self.l1.bind("<Leave>", self.on_leave)
def on_enter(self, event):
self.l2.configure(text="Hello world")
def on_leave(self, enter):
self.l2.configure(text="")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand="true")
root.mainloop()
You can refer to this- HoverClass
It is exactly what you need. Nothing more, nothing less
from Tkinter import *
import re
class HoverInfo(Menu):
def __init__(self, parent, text, command=None):
self._com = command
Menu.__init__(self,parent, tearoff=0)
if not isinstance(text, str):
raise TypeError('Trying to initialise a Hover Menu with a non string type: ' + text.__class__.__name__)
toktext=re.split('\n', text)
for t in toktext:
self.add_command(label = t)
self._displayed=False
self.master.bind("<Enter>",self.Display )
self.master.bind("<Leave>",self.Remove )
def __del__(self):
self.master.unbind("<Enter>")
self.master.unbind("<Leave>")
def Display(self,event):
if not self._displayed:
self._displayed=True
self.post(event.x_root, event.y_root)
if self._com != None:
self.master.unbind_all("<Return>")
self.master.bind_all("<Return>", self.Click)
def Remove(self, event):
if self._displayed:
self._displayed=False
self.unpost()
if self._com != None:
self.unbind_all("<Return>")
def Click(self, event):
self._com()
Example app using HoverInfo:
from Tkinter import *
from HoverInfo import HoverInfo
class MyApp(Frame):
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.grid()
self.lbl = Label(self, text='testing')
self.lbl.grid()
self.hover = HoverInfo(self, 'while hovering press return \n for an exciting msg', self.HelloWorld)
def HelloWorld(self):
print('Hello World')
app = MyApp()
app.master.title('test')
app.mainloop()
Screenshot:
I have a very hacky solution but it has some advantages over the current answers so I figured I would share it.
lab=Label(root,text="hover me")
lab.bind("<Enter>",popup)
def do_popup(event):
# display the popup menu
root.after(1000, self.check)
popup = Menu(root, tearoff=0)
popup.add_command(label="Next")
popup.tk_popup(event.x_root, event.y_root, 0)
def check(event=None):
x, y = root.winfo_pointerxy()
widget = root.winfo_containing(x, y)
if widget is None:
root.after(100, root.check)
else:
leave()
def leave():
popup.delete(0, END)
The only real issue with this is it leaves behind a small box that moves focus away from the main window
If anyone knows how to solve these issues let me know
If anyone is on Mac OSX and tool tip isn't working, check out the example in:
https://github.com/python/cpython/blob/master/Lib/idlelib/tooltip.py
Basically, the two lines that made it work for me on Mac OSX were:
tw.update_idletasks() # Needed on MacOS -- see #34275.
tw.lift() # work around bug in Tk 8.5.18+ (issue #24570)
Here is a simple solution to your problem that subclasses the tk.Button object. We make a special class that tk.Button inherits from, giving it tooltip functionality. The same for tk.Labels.
I don't know what would be cleanest and the easiest way to maintain code for keeping track of the text that goes into the tooltips. I present here one way, in which I pass unique widget IDs to MyButtons, and access a dictionary for storing the tooltip texts. You could store this file as a JSON, or as a class attribute, or as a global variable (as below). Alternatively, perhaps it would be better to define a setter method in MyButton, and just call this method every time you create a new widget that should have a tooltip. Although you would have to store the widget instance in a variable, adding one extra line for all widgets to include.
One drawback in the code below is that the self.master.master syntax relies on determining the "widget depth". A simple recursive function will catch most cases (only needed for entering a widget, since by definition you leave somewhere you once were).
Anyway, below is a working MWE for anyone interested.
import tkinter as tk
tooltips = {
'button_hello': 'Print a greeting message',
'button_quit': 'Quit the program',
'button_insult': 'Print an insult',
'idle': 'Hover over button for help',
'error': 'Widget ID not valid'
}
class ToolTipFunctionality:
def __init__(self, wid):
self.wid = wid
self.widet_depth = 1
self.widget_search_depth = 10
self.bind('<Enter>', lambda event, i=1: self.on_enter(event, i))
self.bind('<Leave>', lambda event: self.on_leave(event))
def on_enter(self, event, i):
if i > self.widget_search_depth:
return
try:
cmd = f'self{".master"*i}.show_tooltip(self.wid)'
eval(cmd)
self.widget_depth = i
except AttributeError:
return self.on_enter(event, i+1)
def on_leave(self, event):
cmd = f'self{".master" * self.widget_depth}.hide_tooltip()'
eval(cmd)
class MyButton(tk.Button, ToolTipFunctionality):
def __init__(self, parent, wid, **kwargs):
tk.Button.__init__(self, parent, **kwargs)
ToolTipFunctionality.__init__(self, wid)
class MyLabel(tk.Label, ToolTipFunctionality):
def __init__(self, parent, wid, **kwargs):
tk.Label.__init__(self, parent, **kwargs)
ToolTipFunctionality.__init__(self, wid)
class Application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.tooltip = tk.StringVar()
self.tooltip.set(tooltips['idle'])
self.frame = tk.Frame(self, width=50)
self.frame.pack(expand=True)
MyLabel(self.frame, '', text='One Cool Program').pack()
self.subframe = tk.Frame(self.frame, width=40)
self.subframe.pack()
MyButton(self.subframe, 'button_hello', text='Hello!', command=self.greet, width=20).pack()
MyButton(self.subframe, 'button_insutl', text='Insult', command=self.insult, width=20).pack()
MyButton(self.subframe, 'button_quit', text='Quit', command=self.destroy, width=20).pack()
tk.Label(self.subframe, textvar=self.tooltip, width=20).pack()
def show_tooltip(self, wid):
try:
self.tooltip.set(tooltips[wid])
except KeyError:
self.tooltip.set(tooltips['error'])
def hide_tooltip(self):
self.tooltip.set(tooltips['idle'])
def greet(self):
print('Welcome, Fine Sir!')
def insult(self):
print('You must be dead from the neck up')
if __name__ == '__main__':
app = Application()
app.mainloop()
The best way I have found to create a popup help window is to use the tix.Balloon. I have modified it below to make it look better and show an example (note the use of tix.Tk):
import tkinter as tk
import tkinter.tix as tix
class Balloon(tix.Balloon):
# A modified tix popup balloon (to change the default delay, bg and wraplength)
init_after = 1250 # Milliseconds
wraplength = 300 # Pixels
def __init__(self, master):
bg = root.cget("bg")
# Call the parent
super().__init__(master, initwait=self.init_after)
# Change background colour
for i in self.subwidgets_all():
i.config(bg=bg)
# Modify the balloon label
self.message.config(wraplength=self.wraplength)
root = tix.Tk()
l = tk.Label(root, text="\n".join(["text"] * 5))
l.pack()
b = Balloon(root.winfo_toplevel())
b.bind_widget(l, balloonmsg="Some random text")
root.mainloop()
OLD ANSWER:
Here is an example using <enter> and <leave> as #bryanoakley suggested with a toplevel (with overridedirect set to true). Use the hover_timer class for easy use of this. This needs the widget and help-text (with an optional delay argument - default 0.5s) and can be easily called just by initiating the class and then cancelling it.
import threading, time
from tkinter import *
class hover_window (Toplevel):
def __init__ (self, coords, text):
super ().__init__ ()
self.geometry ("+%d+%d" % (coords [0], coords [1]))
self.config (bg = "white")
Label (self, text = text, bg = "white", relief = "ridge", borderwidth = 3, wraplength = 400, justify = "left").grid ()
self.overrideredirect (True)
self.update ()
self.bind ("<Enter>", lambda event: self.destroy ())
class hover_timer:
def __init__ (self, widget, text, delay = 2):
self.wind, self.cancel_var, self.widget, self.text, self.active, self.delay = None, False, widget, text, False, delay
threading.Thread (target = self.start_timer).start ()
def start_timer (self):
self.active = True
time.sleep (self.delay)
if not self.cancel_var: self.wind = hover_window ((self.widget.winfo_rootx (), self.widget.winfo_rooty () + self.widget.winfo_height () + 20), self.text)
self.active = False
def delayed_stop (self):
while self.active: time.sleep (0.05)
if self.wind:
self.wind.destroy ()
self.wind = None
def cancel (self):
self.cancel_var = True
if not self.wind: threading.Thread (target = self.delayed_stop).start ()
else:
self.wind.destroy ()
self.wind = None
def start_help (event):
# Create a new help timer
global h
h = hover_timer (l, "This is some additional information.", 0.5)
def end_help (event):
# If therre is one, end the help timer
if h: h.cancel ()
if __name__ == "__main__":
# Create the tkinter window
root = Tk ()
root.title ("Hover example")
# Help class not created yet
h = None
# Padding round label
Frame (root, width = 50).grid (row = 1, column = 0)
Frame (root, height = 50).grid (row = 0, column = 1)
Frame (root, width = 50).grid (row = 1, column = 2)
Frame (root, height = 50).grid (row = 2, column = 1)
# Setup the label
l = Label (root, text = "Hover over me for information.", font = ("sans", 32))
l.grid (row = 1, column = 1)
l.bind ("<Enter>", start_help)
l.bind ("<Leave>", end_help)
# Tkinter mainloop
root.mainloop ()
I wanted to contribute to the answer of #squareRoot17 as he inspired me to shorten his code while providing the same functionality:
import tkinter as tk
class ToolTip(object):
def __init__(self, widget, text):
self.widget = widget
self.text = text
def enter(event):
self.showTooltip()
def leave(event):
self.hideTooltip()
widget.bind('<Enter>', enter)
widget.bind('<Leave>', leave)
def showTooltip(self):
self.tooltipwindow = tw = tk.Toplevel(self.widget)
tw.wm_overrideredirect(1) # window without border and no normal means of closing
tw.wm_geometry("+{}+{}".format(self.widget.winfo_rootx(), self.widget.winfo_rooty()))
label = tk.Label(tw, text = self.text, background = "#ffffe0", relief = 'solid', borderwidth = 1).pack()
def hideTooltip(self):
tw = self.tooltipwindow
tw.destroy()
self.tooltipwindow = None
This class can then be imported and used as:
import tkinter as tk
from tooltip import ToolTip
root = tk.Tk()
your_widget = tk.Button(root, text = "Hover me!")
ToolTip(widget = your_widget, text = "Hover text!")
root.mainloop()

Python Tkinter frame inside frame limitation or user error?

I've been building an app to track stock prices. The user should see a window with an entry widget and a button that creates a new frame with a label and a button. The label is the stock price and symbol, the button is a delete button, and should hide that frame if clicked.
I've re-written this program 4 times now, and it's been a great learning experience, but what I've learned is that I can't have the "mini-frames" being called from methods part of the main GUI class - this funks up the delete buttons, and updates the value behind frame.pack_forget() so it only deletes the last item ever.
I've moved my mini-frame widgets down into the class for the actual stock values. I've packed them (what I assume to be correct) but they don't show up. They also don't error out, which isn't very helpful. Here's my code, although I've omitted a lot of the functional parts to show what is happening with my frames. Keep in mind I need to keep it so that I can call my updater (self.update_stock_value) with a .after method against myapp.myContainer.
Is there a better way to do this?? Thanks in advance, my head hurts.
import re
import time
import urllib
from Tkinter import *
import threading
from thread import *
runningThreads = 0
# each object will be added to the gui parent frame
class MyApp(object):
def __init__(self, parent):
self.myParent = parent
self.myContainer = Canvas(parent)
self.myContainer.pack()
self.create_widgets()
# METHOD initiates basic GUI widgets
def create_widgets(self):
root.title("Stocker")
self.widgetFrame = Frame(self.myContainer)
self.widgetFrame.pack()
self.input = Entry(self.widgetFrame)
self.input.focus_set()
self.input.pack()
self.submitButton = Button(self.widgetFrame, command = self.onButtonClick)
self.submitButton.configure(text = "Add new stock")
self.submitButton.pack(fill = "x")
# METHOD called by each stock object
# returns the "symbol" in the entry widget
# clears the entry widget
def get_input_value(self):
var = self.input.get()
self.input.delete(0, END)
return var
# METHOD called when button is clicked
# starts new thread with instance of "Stock" class
def onButtonClick(self):
global runningThreads # shhhhhh im sorry just let it happen
runningThreads += 1 # count the threads open
threading.Thread(target = self.init_stock,).start() # force a tuple
if runningThreads == 1:
print runningThreads, "thread alive"
else:
print runningThreads, "threads alive"
def init_stock(self):
new = Stock()
class Stock(object):
def __init__(self):
# variable for the stock symbol
symb = self.stock_symbol()
# lets make a GUI
self.frame = Frame(myapp.myContainer)
self.frame.pack
# give the frame a label to update
self.testLabel = Label(self.frame)
self.testLabel.configure(text = self.update_stock_label(symb))
self.testLabel.pack(side = LEFT)
# create delete button to kill entire thread
self.killButton = Button(self.frame, command = self.kill_thread)
self.killButton.configure(text = "Delete")
self.killButton.pack(side = RIGHT)
# create stock label
# call updater
def kill_thread(self):
global runningThreads
runningThreads -= 1
self.stockFrame.pack_forget() # hide the frame
self.thread.exit() # kill the thread
def update_stock_label(self, symb):
self.testLabel.configure(text = str(symb) + str(get_quote(symb)))
myapp.myContainer.after(10000, self.update_stock_label(symb))
def stock_symbol(self):
symb = myapp.get_input_value()
print symb
# The most important part!
def get_quote(symbol):
try:
# go to google
base_url = "http://finance.google.com/finance?q="
# read the source code
content = urllib.urlopen(base_url + str(symbol)).read()
# set regex target
target = re.search('id="ref_\d*_l".*?>(.*?)<', content)
# if found, return.
if target:
print "found target"
quote = target.group(1)
print quote
else:
quote = "Not Found: "
return quote
# handling if no network connection
except IOError:
print "no network detected"
root = Tk()
root.geometry("280x200")
myapp = MyApp(root)
root.mainloop()
Your code won't run because of numerous errors, but this line is definitely not doing what you think it is doing:
self.frame.pack
For you to call the pack function you must include (), eg:
self.frame.pack()
You ask if your code is the best way to do this. I think you're on the right track, but I would change a few things. Here's how I would structure the code. This just creates the "miniframes", it doesn't do anything else:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.entry = tk.Entry(self)
self.submit = tk.Button(self, text="Submit", command=self.on_submit)
self.entry.pack(side="top", fill="x")
self.submit.pack(side="top")
def on_submit(self):
symbol = self.entry.get()
stock = Stock(self, symbol)
stock.pack(side="top", fill="x")
class Stock(tk.Frame):
def __init__(self, parent, symbol):
tk.Frame.__init__(self, parent)
self.symbol = tk.Label(self, text=symbol + ":")
self.value = tk.Label(self, text="123.45")
self.symbol.pack(side="left", fill="both")
self.value.pack(side="left", fill="both")
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()

Categories

Resources