I'd like to keep focus on the entry text widget, which will pass whatever's entered into a separate display text widget. I have that part working.
I can't figure out how to make it so that when someone clicks on the display text widget the line clicked is highlighted (or the line changes background color) but focus is returned to the entry widget. I also need to store a reference to that line so that i can manipulate it with other Widgets.
Here's some sample code so you can see how I have it so far. I have a lot more widgets and code in GUI right now but I only posted the relevant code to my issue:
from Tkinter import *
class GUI:
def __init__(self,root):
Window = Frame(root)
self.OutWidget = Text(Window, state='disabled')
self.InWidget = Text(Window,bg='black',bd=3,fg='white',exportselection=0,height=1,wrap=WORD,insertofftime=0,insertbackground="white")
self.OutWidget.pack()
self.InWidget.pack()
Window.pack()
self.InWidget.focus_set()
self.OutWidget.bind("<Button 1>",self.Select)
self.InWidget.bind("<Return>", self.Post)
def Post(self,event):
text = self.InWidget.get(1.0,2.0)
self.InWidget.delete(1.0,2.0)
self.OutWidget['state'] = ['normal']
self.OutWidget.insert('end',text)
self.OutWidget['state'] = ['disabled']
return ("break")
def Select(self,event):
#highlight the CURRENT line
#store a reference to the line
#return focus to InWidget
self.InWidget.focus()
return ("break")
if __name__ == '__main__':
root = Tk()
App = GUI(root)
root.mainloop()
You can get the index of the start of the line where you clicked by using something like this:
line_start = self.OutWidget.index("#%s,%s linestart" % (event.x, event.y))
You can add highlighting by applying a tag to that line with something like this:
line_end = self.OutWidget.index("%s lineend" % line_start)
self.OutWidget.tag_remove("highlight", 1.0, "end")
self.OutWidget.tag_add("highlight", line_start, line_end)
You can set the color for the item with the "highlight" tag with something like this:
self.OutWidget.tag_configure("highlight", background="bisque")
You can move the focus back to the other window with something like this:
self.InWidget.focus_set()
Related
I am making a simple tkinter popup where you can type a message.
In the textbox itself, I inserted the text "Type your message here" with a grey colour and when clicked, the inserted text is deleted so the user can type in their own message. In addition, the colour of the text typed by the user is set to black.
However, when I was testing I realised that this will only happen if they click the textbox with a mouse button. My question is, is there a way for tkinter to automatically run a command when a condition is changed? For example, if the textbox is empty, the font colour should be set to black.
I tried putting if-statements in the tk.mainloop, but sadly that didn't work.
Any ideas?
this is my (hopefully) simplified version of the code:
from tkinter import *
def changecolor(event):
if textbox.get("1.0", "end-1c") == "Type your message here":
textbox.delete("1.0", "end")
textbox.config(fg='black')
root = Tk()
canvas = Canvas(root, height=400, width=600)
canvas.pack()
textbox = Text(canvas, font=40, fg="grey")
textbox.insert(1.0, "Type your message here")
textbox.bind("<Button-1>", changecolor)
textbox.pack()
root.mainloop()
~finally found out how to format code here.
Take a look at this class I created that does similar to what your code does, do try it on.
from tkinter import *
class PlaceholderText(Text):
def __init__(self,master,placeholder,placeholdercolor='black',fg='grey',**kwargs):
Text.__init__(self,master,**kwargs) #init the text widget
self.placeholder = placeholder
self.fgcolor = fg
self.placeholdercolor = placeholdercolor
self.has_placeholder = False #make flag
self.add() #run the function to add placeholder
self.bind('<FocusIn>',self.clear) #binding to focusin and not button-1
self.bind('<FocusOut>',self.add) #function wil get triggered when widget loses focus
def clear(self,event=None):
if self.get('1.0','end-1c') == self.placeholder and self.has_placeholder: #condition to clear a placeholder
self.delete('1.0','end-1c') #delete the placeholder
self.config(fg=self.fgcolor) #change the color
self.has_placeholder = False #set flag to flase
def add(self,event=None):
if self.get('1.0','end-1c') == '' and not self.has_placeholder: #condition to add placeholder
self.insert('1.0',self.placeholder) #add placeholder
self.has_placeholder = True #set flag to true
self.config(fg=self.placeholdercolor) #change text color to what you specify?
def ret(self,index1,index2):
if self.get('1.0','end-1c') == self.placeholder and self.has_placeholder: #gives none if there is nothing in the widget
return 'None'
else:
return self.get(index1,index2) #else gives the text
root = Tk()
pl = PlaceholderText(root,placeholder='Type something here...')
pl.pack()
e = Entry(root) #dummy widget to switch focus and check
e.pack(padx=10,pady=10)
root.mainloop()
I've explained to through the comments. But keep in mind its not the best of classes yet, you have to do add a lot more methods in to make it more efficient.
Just if your wondering on how to do this without classes, then:
from tkinter import *
has_placeholder = False #make flag
placeholder = 'Type Something Here...' #the text to be inserted
def clear(event=None):
global has_placeholder
if a.get('1.0','end-1c') == placeholder and has_placeholder: #condition to clear a placeholder
a.delete('1.0','end-1c') #delete the placeholder
a.config(fg='grey') #change the color
has_placeholder = False #set flag to flase
def add(event=None):
global has_placeholder
if a.get('1.0','end-1c') == '' and not has_placeholder: #condition to add placeholder
a.insert('1.0',placeholder) #add placeholder
has_placeholder = True #set flag to true
a.config(fg='black') #change text color to normal
root = Tk()
a = Text(root)
a.pack()
add() #add the placeholder initially
a.bind('<FocusIn>',clear) #binding to focus and not button-1
a.bind('<FocusOut>',add)
e = Entry(root) #dummy widget to show focus loss
e.pack()
root.mainloop()
Why not to use classes if the latter method is more easier? This is not reusable, say you want to add one more Text widget, that cannot have such property, while using a custom class with custom class you can have as many as text widgets with same properties you like.
Do let me know if any doubts.
You can simply add a <Key> binding to your text widget and use your changecolor function to determine what state your textbox is in.
#Give a hoot. Don't pollute. :D
import tkinter as tk
txtmsg = "Type your message here"
def changecolor(event):
text = textbox.get("1.0", "end-1c")
#customize accordingly
if text:
if text == txtmsg:
print("text is txtmsg")
else:
print("text is user text")
else:
print("text is empty")
#FYI:
#whether this was a button press or key press DOES NOT have string equality
#if you need to create button vs key conditions
#use tk.EventType.ButtonPress and tk.EventType.KeyPress
#or learn the .value and compare that
print(event.type, type(event.type), event.type.value)
root = tk.Tk()
textbox = tk.Text(root, font=40, fg="grey")
textbox.insert(1.0, txtmsg)
textbox.pack()
#add events
for ev in ['<Key>', '<1>']:
textbox.bind(ev, changecolor)
root.mainloop()
I am trying to keep the label text value: "This is the subtotal" next to subtotal value. Meaning:
If I were to click on the "Calulate Subtotal" Button the text "This is the subtotal" should be to the right and the actual subtotal should be to the left. Currently, If I were to click on the "Calulate Subtotal" Button the text "this is the subtotal" disappears. Can someone steer me in the right direction?
try:
import Tkinter as tk
except:
import tkinter as tk
class GetInterfaceValues():
def __init__(self):
self.root = tk.Tk()
self.root.geometry('500x200')
self.button = tk.Button(self.root,
text='Calculate Subtotal',
command=self.getSubtotals)
self.button.pack()
self.firstLabel = tk.Label(self.root, text="This is the subtotal:")
self.firstLabel.pack()
self.root.mainloop()
def getSubtotals(self):
self.firstLabel["text"] = 55*10
app = GetInterfaceValues()
You can simply change your getSubtotals method to retain the current text of firstLabel as the following:
def getSubtotals(self):
self.firstLabel["text"] = self.firstLabel["text"] + str(55 * 10)
Couple of suggestions:
You might want to create another widget to show subtotal value other than firstLabel.
You might want to restructure your class so that you only initialize the attributes in the init method
Please check the indentations and code formatting when asking questions to make it easier for others to inspect your code
I want to add a Drag Text Feature in canvas to change the position of text using mouse.
from PIL import Image,ImageFont,ImageDraw,ImageTk
import tkinter as tk
root = tk.Tk()
root.title('Demo')
root.geometry('400x50')
def func_image():
img_window = tk.Toplevel()
img_window.grab_set()
photo = Image.open(r'E:\side_300.png')
wi,hi = photo.size
fonty = ImageFont.truetype('arial.ttf',18)
draw = ImageDraw.Draw(photo)
draw.text((50,50),text=text.get(),fill='red',font=fonty)
new_photo = photo
can_photo = ImageTk.PhotoImage(new_photo)
canvas = tk.Canvas(img_window,height=500,width=500)
canvas.pack(anchor='n')
canvas.create_image(wi/2,hi/2,image=can_photo,anchor='center')
img_window.mainloop()
lbl_text = tk.Label(root,text='Enter Text :')
lbl_text.grid(row=0,column=0)
text = tk.Entry()
text.grid(row=0,column=1)
btn = tk.Button(root,text='Click Me',command=func_image)
btn.grid(row=0,column=2)
root.mainloop()
When you run the code it will firstly open a window with name 'Demo' which contains one entry box and a button.
When you click on a Button 'Click Me' after entering some text into entry box it will go to a function func_image and opens a new window which contain a canvas filled with new_image.
Quick Disclaimer: I don't have a lot of experience with PIL, so i don't know how to remove text that has already been drawn. Maybe you can figure that one out yourself. But apart from that, i know some things about tkinter. My idea would be the following:
Bind a function to the <B1-motion> event (Button 1 is being held down and moved) that will constantly get the position of the mouse inside the window and draw new text at that position, while deleting the previous text.
import...
...
def func_image():
img_window = tk.Toplevel()
...
...
draw = ImageDraw.Draw(photo)
draw.text((50,50),text=text.get(),fill='red',font=fonty)
...
def move_text(event):
# here you would delete your previous text
x = event.x
y = event.y
draw.text((x,y),text=text.get(),fill='red',font=fonty
img_window.bind('<B1-Motion>', move_text)
That being said, i think it would be a better idea to use Canvas.create_text (more on effbot.org) in order to write your text on the image. It's really easy to drag around text on a Canvas, here's a little example:
import tkinter as tk
root = tk.Tk()
def change_position(event):
x = event.x
y = event.y
# 20x20 square around mouse to make sure text only gets targeted if the mouse is near it
if text in c.find_overlapping(str(x-10), str(y-10), str(x+10), str(y+10)):
c.coords(text, x, y) # move text to mouse position
c = tk.Canvas(root)
c.pack(anchor='n')
text = c.create_text('10', '10', text='test', fill='red', font=('arial', 18)) # you can define all kinds of text options here
c.bind("<B1-Motion>", change_position)
root.mainloop()
I have this few lines of code which print the selected content in the Listbox when double clicked but i want to display text beside the cursor like double click the selected content to print to prompt the user to take such action before it can be printed.
I search the listbox documentation and found the is an attribute cursor which you set the cursor type to display when the widget has focus but didn't find something like cursor-text to do that.Is the a way i can work around to achieve that, your suggestions are welcomed.
from tkinter import *
def test(event=None):
print("woow test works")
print(l.get(ACTIVE))
root = Tk()
l = Listbox(root, cursor="tcross")
l.pack()
l.insert(END, ("today"),("tomorrow"))
l.bind("<Double-Button-1>", test)
root.mainloop()
I don't quite understand your English, but you can see this page for an example of using tooltips, which make text appear on mouse hover.
Yes, it's possible to add specific info or comments to listbox lines using Hovertip from idlelib.tooltip. In the example below, Mark Lutz's customizable scrolled listbox works normally, except that right-clicking on any line opens a tip with info/comments for that line.
Ref. : Programming Python 4th ed. Example 9-9
from tkinter import *
from idlelib.tooltip import Hovertip
class ScrolledList(Frame):
def __init__(self, options, parent=None):
Frame.__init__(self, parent)
self.pack(expand=YES, fill=BOTH)
botfrm = Frame(parent)
botfrm.pack(side=BOTTOM)
Label(botfrm,
text="Select a line and right-click for info").pack()
self.makeWidgets(options)
self.myTip = None
def handleList(self, event):
if self.myTip:
self.myTip.__del__()
label = self.lstbx.get(ACTIVE)
self.runCommand(label)
def overflyLine(self, event):
if self.myTip:
self.myTip.__del__()
self.myTip = Hovertip(self.lstbx,f"Comments for {self.lstbx.get(ACTIVE)}")
self.myTip.showtip()
def makeWidgets(self, options):
sbar = Scrollbar(self)
list = Listbox(self, relief=SUNKEN, bg='misty rose')
sbar.config(command=list.yview)
list.config(yscrollcommand=sbar.set)
sbar.pack(side=RIGHT, fill=Y)
list.pack(side=LEFT, expand=YES, fill=BOTH)
for label in options:
list.insert(END, label)
self.lstbx = list
list.bind('<Button-3>', self.overflyLine)
list.bind('<Double-1>', self.handleList)
list.bind('<Return>', self.handleList)
def runCommand(self, selection):
print('You selected:', selection)
if __name__ == '__main__':
options = (('Lumberjack-%s' % x) for x in range(20))
scr = ScrolledList(options).mainloop()
i am going to create an tkinter gui app, and i know how i want it to look. but after playing around with tkinter, i found no way to toggle between screens when you press buttons down at the bottom. i know it does nothing but below is the simple layout i want to have, and switch between "myframe1" and "myframe2" kind of like the Apple App Store layout. is this possible?
from tkinter import *
tk = Tk()
tk.geometry("300x300")
myframe1 = Frame(tk,background="green",width=300,height=275)
myframe1.pack()
myframe2 = Frame(tk,background="cyan",width=300,height=275)
myframe2.pack()
btnframe = Frame(tk)
btn1 = Button(btnframe,text="screen1",width=9)
btn1.pack(side=LEFT)
btn2 = Button(btnframe,text="screen2",width=9)
btn2.pack(side=LEFT)
btn3 = Button(btnframe,text="screen3",width=9)
btn3.pack(side=LEFT)
btn4 = Button(btnframe,text="screen4",width=9)
btn4.pack(side=LEFT)
myframe1.pack()
btnframe.pack()
tk.mainloop()
something for you to get started with:
def toggle(fshow,fhide):
fhide.pack_forget()
fshow.pack()
btn1 = Button(btnframe,text="screen1", command=lambda:toggle(myframe1,myframe2),width=9)
btn1.pack(side=LEFT)
btn2 = Button(btnframe,text="screen2",command=lambda:toggle(myframe2,myframe1),width=9)
btn2.pack(side=LEFT)
Are you looking for something like a tabbed widget? You could use forget and pack as suggested here
Here is a class that I use in my code that works:
class MultiPanel():
"""We want to setup a pseudo tabbed widget with three treeviews. One showing the disk, one the pile and
the third the search results. All three treeviews should be hooked up to exactly the same event handlers
but only one of them should be visible at any time.
Based off http://code.activestate.com/recipes/188537/
"""
def __init__(self, parent):
#This is the frame that we display
self.fr = tki.Frame(parent, bg='black')
self.fr.pack(side='top', expand=True, fill='both')
self.widget_list = []
self.active_widget = None #Is an integer
def __call__(self):
"""This returns a reference to the frame, which can be used as a parent for the widgets you push in."""
return self.fr
def add_widget(self, wd):
if wd not in self.widget_list:
self.widget_list.append(wd)
if self.active_widget is None:
self.set_active_widget(0)
return len(self.widget_list) - 1 #Return the index of this widget
def set_active_widget(self, wdn):
if wdn >= len(self.widget_list) or wdn < 0:
logger.error('Widget index out of range')
return
if self.widget_list[wdn] == self.active_widget: return
if self.active_widget is not None: self.active_widget.forget()
self.widget_list[wdn].pack(fill='both', expand=True)
self.active_widget = self.widget_list[wdn]