I need to highlight a specific word in a text within a tkinter frame. In order to find the word, I put a balise like in html. So in a text like "hello i'm in the |house|" I want to highlight the word "house".
My frame is defined like that:
class FrameCodage(Frame):
self.t2Codage = Text(self, height=20, width=50)
and I insert my text with this code: fenetre.fCodage.t2Codage.insert(END, res) , res being a variable containing my text.
I saw this code on an other post:
class CustomText(tk.Text):
'''A text widget with a new method, highlight_pattern()
example:
text = CustomText()
text.tag_configure("red", foreground="#ff0000")
text.highlight_pattern("this should be red", "red")
The highlight_pattern method is a simplified python
version of the tcl code at http://wiki.tcl.tk/3246
'''
def __init__(self, *args, **kwargs):
tk.Text.__init__(self, *args, **kwargs)
def highlight_pattern(self, pattern, tag, start="1.0", end="end",
regexp=False):
'''Apply the given tag to all text that matches the given pattern
If 'regexp' is set to True, pattern will be treated as a regular
expression.
'''
start = self.index(start)
end = self.index(end)
self.mark_set("matchStart", start)
self.mark_set("matchEnd", start)
self.mark_set("searchLimit", end)
count = tk.IntVar()
while True:
index = self.search(pattern, "matchEnd","searchLimit",
count=count, regexp=regexp)
if index == "": break
self.mark_set("matchStart", index)
self.mark_set("matchEnd", "%s+%sc" % (index, count.get()))
self.tag_add(tag, "matchStart", "matchEnd")
But they are few things that I don't understand: how can I apply this function to my case? When did I call this function? What's the pattern and the tag in my case? I'm a beginner in Tkinter so don't hesitate to explain to me this code, or another.
Instead of this:
class FrameCodage(Frame):
self.t2Codage = Text(self, height=20, width=50)
... do this:
class FrameCodage(Frame):
self.t2Codage = CustomText(self, height=20, width=50)
Next, create a "highlight" tag, and configure it however you want:
self.t2Codage.tag_configure("highlight", foreground="red")
Finally, you can call the highlight_pattern method as if it were a standard method:
self.t2Codage.highlight_pattern(r"\|.*?\|", "red", regexp=True)
Below is widget I created to deal with this, hope it helps.
try: import tkinter as tk
except ImportError: import Tkinter as tk
class code_editor(tk.Text):
def __init__(self, parent, case_insensetive = True, current_line_colour = '', word_end_at = r""" .,{}[]()=+-*/\|<>%""", tags = {}, *args, **kwargs):
tk.Text.__init__(self, *args, **kwargs)
self.bind("<KeyRelease>", lambda e: self.highlight())
self.case_insensetive = case_insensetive
self.highlight_current_line = current_line_colour != ''
self.word_end = word_end_at
self.tags = tags
if self.case_insensetive:
for tag in self.tags:
self.tags[tag]['words'] = [word.lower() for word in self.tags[tag]['words']]
#loops through the syntax dictionary to creat tags for each type
for tag in self.tags:
self.tag_config(tag, **self.tags[tag]['style'])
if self.highlight_current_line:
self.tag_configure("current_line", background = current_line_colour)
self.tag_add("current_line", "insert linestart", "insert lineend+1c")
self.tag_raise("sel")
#find what is the last word thats being typed.
def last_word(self):
line, last = self.index(tk.INSERT).split('.')
last=int(last)
#this limit issues when user is a fast typer
last_char = self.get(f'{line}.{int(last)-1}', f'{line}.{last}')
while last_char in self.word_end and last > 0:
last-=1
last_char = self.get(f'{line}.{int(last)-1}', f'{line}.{last}')
first = int(last)
while True:
first-=1
if first<0: break
if self.get(f"{line}.{first}", f"{line}.{first+1}") in self.word_end:
break
return {'word': self.get(f"{line}.{first+1}", f"{line}.{last}"), 'first': f"{line}.{first+1}", 'last': f"{line}.{last}"}
#highlight the last word if its a syntax, See: syntax dictionary on the top.
#this runs on every key release which is why it fails when the user is too fast.
#it also highlights the current line
def highlight(self):
if self.highlight_current_line:
self.tag_remove("current_line", 1.0, "end")
self.tag_add("current_line", "insert linestart", "insert lineend+1c")
lastword = self.last_word()
wrd = lastword['word'].lower() if self.case_insensetive else lastword['word']
for tag in self.tags:
if wrd in self.tags[tag]['words']:
self.tag_add(tag, lastword['first'], lastword['last'])
else:
self.tag_remove(tag, lastword['first'], lastword['last'])
self.tag_raise("sel")
#### example ####
if __name__ == '__main__':
# from pyFilename import code_editor
ms = tk.Tk()
example_text = code_editor(
parent = ms,
case_insensetive = True, #True by default.
current_line_colour = 'grey10', #'' by default which will not highlight the current line.
word_end_at = r""" .,{}[]()=+-*/\|<>%""", #<< by default, this will till the class where is word ending.
tags = {#'SomeTagName': {'style': {'someStyle': 'someValue', ... etc}, 'words': ['word1', 'word2' ... etc]}} this tells it to apply this style to these words
"failSynonyms": {'style': {'foreground': 'red', 'font': 'helvetica 8'}, 'words': ['fail', 'bad']},
"passSynonyms":{'style': {'foreground': 'green', 'font': 'helvetica 12'}, 'words': ['Pass', 'ok']},
"sqlSyntax":{'style': {'foreground': 'blue', 'font': 'italic'}, 'words': ['select', 'from']},
},
font='helvetica 10 bold', #Sandard tkinter text arguments
background = 'black', #Sandard tkinter text arguments
foreground = 'white' #Sandard tkinter text arguments
)
example_text.pack()
ms.mainloop()
Related
I have dict[str, Any] = {} (populated) coming from this function:
def get_exif():
global image_object
try:
exif = image_object.getexif()
except AttributeError:
return {}
exif_table = {}
for tag_id, value in exif.items():
tag = TAGS.get(tag_id, tag_id)
exif_table[tag] = value
exif_lbl.configure(text=exif_table)
Shown like this on my GUI:
How can I make it show as this:
ResolutionUnit: 2,
ExifOffset: 146,
Software: GIMP 2.4.5
etc..
My exif_lbl is:
exif_lbl = Message(exif_frame, font=("helvetica", 18), aspect=200)
exif_lbl.grid(row=1, column=0)
Maybe I should change it to ListBox?
Thanks
It is not problem with widget.
If you want expected result then you have to write code which will convert it to single string with \n - and it will need for-loop
data = {
'ResolutionUnit': '2',
'ExifOffset': '146',
'Software': 'GIMP 2.4.5'
}
# --- more readable
lines = []
for key, value in data.items():
lines.append( f"{key}: {value}" )
text = "\n".join(lines)
# --- shorter
text = "\n".join(f"{key}: {value}" for key, value in data.items())
# ---
print(text)
import tkinter as tk
root = tk.Tk()
lb = tk.Message(root, text=text, width=500)
lb.pack()
root.mainloop()
I have the following code:
def find_and_replace(self, *args):
findandreplace = tk.Toplevel(master)
findandreplace.title('Find & Replace')
find_label = tk.Label(findandreplace, text='Find')
find_label.pack(side = tk.LEFT)
find_words = tk.StringVar()
find_entry = tk.Entry(findandreplace, textvariable=find_words)
find_entry.pack(side = tk.LEFT, fill = tk.BOTH, expand = 1)
find_button = tk.Button(findandreplace, text='Find', command=self.find)
find_button.pack(side = tk.LEFT)
replace_label = tk.Label(findandreplace, text='Replace')
replace_label.pack(side = tk.LEFT)
replace_words = tk.StringVar()
replace_entry = tk.Entry(findandreplace, textvariable=replace_words)
replace_entry.pack(side = tk.LEFT, fill = tk.BOTH, expand = 1)
replace_button = tk.Button(findandreplace, text='Replace', command=self.replace)
replace_button.pack(side = tk.LEFT)
find_string = find_words.get()
replace_string = replace_words.get()
return find_string, replace_string
def find(self, *args):
self.textarea.tag_remove('found', '1.0', tk.END)
find_word = self.find_and_replace()[0]
if find_word:
idx = '1.0'
while True:
idx = self.textarea.search(find_word, idx, nocase=1,
stopindex=tk.END)
if not idx:
break
lastidx = '% s+% dc' % (idx, len(find_word))
idx = lastidx
self.textarea.tag_config('found', foreground='red')
def replace(self, *args):
self.textarea.tag_remove('found', '1.0', tk.END)
find_word = self.find_and_replace()[0]
replace_word = self.find_and_replace()[1]
if find_word and replace_word:
idx = '1.0'
while True:
idx = self.textarea.search(find_word, idx, nocase=1,
stopindex=tk.END)
if not idx:
break
lastidx = '% s+% dc' % (idx, len(find_word))
self.textarea.delete(idx, lastidx)
self.textarea.insert(idx, replace_word)
lastidx = '% s+% dc' % (idx, len(replace_word))
idx = lastidx
self.textarea.tag_config('found', foreground='green', background='yellow')
And I am using a menubar in a different class to access this:
edit_dropdown.add_command(label="Find",
command=parent.find)
edit_dropdown.add_command(label="Replace",
command=parent.find_and_replace)
So, find_and_replace() creates a new tk.Toplevel widget where I can access the find() and replace() functions.
However, when I press on the respective buttons, all I get is two more windows created. I want to highlight the find_words string and then have it replaced by the replace_words string.
I feel I'm messing up by accessing variables of one method in another and in the opposite manner.
The root of your problem are these lines:
find_word = self.find_and_replace()[0]
replace_word = self.find_and_replace()[1]
self.find_and_replace creates the dialog. You don't want to be creating new dialogs. Instead, you need to access the widgets in the current dialog. That means you need to save references to the widgets as instance attributes, and then use those instance attributes.
Note: you can reduce the complexity by not using the textvariable attribute. You're just adding overhead since you aren't taking advantage of any features of the variable that you can't do by just calling the entry widget directly
For example:
def find_and_replace(self, *args):
...
self.find_entry = tk.Entry(findandreplace)
self.replace_entry = tk.Entry(findandreplace)
...
def replace(self, *args):
...
find_word = self.find_entry.get()
replace_word = self.replace_entry.get()
...
Something like this should do the trick:
from tkinter import *
# to create a window
root = Tk()
# root window is the parent window
fram = Frame(root)
# Creating Label, Entry Box, Button
# and packing them adding label to
# search box
Label(fram, text ='Find').pack(side = LEFT)
# adding of single line text box
edit = Entry(fram)
# positioning of text box
edit.pack(side = LEFT, fill = BOTH, expand = 1)
# setting focus
edit.focus_set()
# adding of search button
Find = Button(fram, text ='Find')
Find.pack(side = LEFT)
Label(fram, text = "Replace With ").pack(side = LEFT)
edit2 = Entry(fram)
edit2.pack(side = LEFT, fill = BOTH, expand = 1)
edit2.focus_set()
replace = Button(fram, text = 'FindNReplace')
replace.pack(side = LEFT)
fram.pack(side = TOP)
# text box in root window
text = Text(root)
# text input area at index 1 in text window
text.insert('1.0', '''Type your text here''')
text.pack(side = BOTTOM)
# function to search string in text
def find():
# remove tag 'found' from index 1 to END
text.tag_remove('found', '1.0', END)
# returns to widget currently in focus
s = edit.get()
if (s):
idx = '1.0'
while 1:
# searches for desried string from index 1
idx = text.search(s, idx, nocase = 1,
stopindex = END)
if not idx: break
# last index sum of current index and
# length of text
lastidx = '% s+% dc' % (idx, len(s))
# overwrite 'Found' at idx
text.tag_add('found', idx, lastidx)
idx = lastidx
# mark located string as red
text.tag_config('found', foreground ='red')
edit.focus_set()
def findNreplace():
# remove tag 'found' from index 1 to END
text.tag_remove('found', '1.0', END)
# returns to widget currently in focus
s = edit.get()
r = edit2.get()
if (s and r):
idx = '1.0'
while 1:
# searches for desried string from index 1
idx = text.search(s, idx, nocase = 1,
stopindex = END)
print(idx)
if not idx: break
# last index sum of current index and
# length of text
lastidx = '% s+% dc' % (idx, len(s))
text.delete(idx, lastidx)
text.insert(idx, r)
lastidx = '% s+% dc' % (idx, len(r))
# overwrite 'Found' at idx
text.tag_add('found', idx, lastidx)
idx = lastidx
# mark located string as red
text.tag_config('found', foreground ='green', background = 'yellow')
edit.focus_set()
Find.config(command = find)
replace.config(command = findNreplace)
# mainloop function calls the endless
# loop of the window, so the window will
# wait for any user interaction till we
# close it
root.mainloop()
I have written this code and for some reason it refuses to return any sort of value or input for slef.REV when used in the function post(self) however it will return a value when I try and return a value in the getlen() function which is used to reurn the number of characters in the review.I dont have this problem for any other variables that I retrieve data from within this class. Below is the relevant code, any help would be appreciated. the lines where this problem occures is the first functio calld post(lines 1-5) and 4 lines up from the bottom
def post(self):
MovieID = self.MovID
REV = self.REV
AddReview(conn,cursor,Add_Review,MovieID,REV)
print(REV)
def shrek_film(self):
self.title = "Shrek"
self.MovID = 1
self.root4 = tk.Toplevel()
self.root4.title("Watch Shreck")
self.root4.geometry("1400x800")
frame_4 = tk.Frame(self.root4, bg = "black")
frame_4.pack(fill = tk.BOTH, expand = True, padx=0 , pady=0)
frame_4.grid_columnconfigure(1,weight=1)
self.Create_canvas = tk.Canvas(frame_4, width=2000, height=1080)
self.Create_canvas.place(x=-50, y=-50)
self.Create_img = PhotoImage(file="shrek-landscape.gif")
self.Create_canvas.create_image(20, 20, anchor = NW, image=self.Create_img)
play_button= tk.Button(frame_4,bg="orange",text="play", command = self.addHistory)
play_button.place(x=700,y=400)
play_button.config(font=("Ariel","30"))
def gtelen():
Review = reviewbox.get('1.0',END)
REVLEN = len(Review)
REVLENLEFT = (231-len(Review))
if REVLEN >=230:
lenbox = tk.Label(frame_4 ,text="No words left",bg="orange")
lenbox.place(x=360,y=460)
lenbox.config(font=("Ariel","15"))
else:
lenbox = tk.Label(frame_4 ,text=REVLENLEFT,bg="orange")
lenbox.place(x=360,y=460)
lenbox.config(font=("Ariel","15"))
print(Review)
Words_button = tk.Button(frame_4, bg="orange",text="check number of words remaining", command=gtelen)
Words_button.place(x=150,y=460)
Words_button.config(font=("Ariel","10"))
reviewlable=tk.Label(frame_4,text="Write a review",bg="orange")
reviewlable.place(x=10,y=460)
reviewlable.config(font=("ariel","15"))
Review_button= tk.Button(frame_4,bg="orange",text="See Reviews")#, command = self.ViewReviews)
Review_button.place(x=490,y=450)
Review_button.config(font=("Ariel","15"))
reviewbox= Text(frame_4,width=100,height=12)
reviewbox.place(x=10,y=500)
self.REV = reviewbox.get('1.0',END)
post_button = tk.Button(frame_4,bg="orange",text="Post Review", command = self.post)
post_button.place(x=830,y=650)
post_button.config(font=("Ariel","15"))
You can use Entry instead and use a StringVar
v = StringVar() # Create StringVar
reviewbox = Entry(frame_4, width = 100, height = 12, textvariable = v) # Create Entry widget
reviewbox.place(x = 10, y = 500) # Place Entry widget
self.REV = v.get() # Get contents of StringVar
The line self.REV = reviewbox.get('1.0',END) is being called about a millisecond after creating the text widget. The user will not even have seen the widget yet, much less have had time to type in it.
You can't call the get() method until after the user has had a chance to enter data, such as inside the post method.
def post(self):
MovieID = self.MovID
REV = reviewbox.get("1.0", "end")
AddReview(conn,cursor,Add_Review,MovieID,REV)
print(REV)
I am writing a simple app to calculate some values according to entered value by user. I almost accomplished it!
Now I want to know:
1. How can I clear my txtFixedIncome text box every time I click on it?
2. How can it be implemented to display the content of txtFixedIncome text box with thousands separator (digit group separator)? i.e. displaying 27659 as 27,659.
import tkinter
mainForm = tkinter.Tk()
mainForm.title('Shahr Fixed Income Fund')
def btnCalculatePressed():
txtCalculationResult.delete('1.0', 'end')
#txtCalculationResult.insert(tkinter.INSERT, "Button was pressed")
#txtCalculationResult.pack()
yourIncomePortion = txtFixedIncome.get('1.0', 'end')
print(yourIncomePortion)
txtCalculationResult.insert(tkinter.INSERT, yourIncomePortion)
btnCalculate = tkinter.Button(mainForm , text = "Calculate", command= btnCalculatePressed)
txtCalculationResult = tkinter.Text(mainForm )
txtCalculationResult.insert(tkinter.INSERT, "CalculationResults")
txtFixedIncome = tkinter.Text(mainForm, height = 1, width = 30 )
txtFixedIncome.insert(tkinter.INSERT, "your income portion")
txtFixedIncome.pack();
txtCalculationResult.pack()
btnCalculate.pack()
mainForm.mainloop()
Solution of first question:
bind focus in and focus out of your widget. Like:
txtFixedIncome.bind("<FocusIn>",lambda _:txtFixedIncome.delete('1.0', 'end'))
txtFixedIncome.bind("<FocusOut>",lambda _:txtFixedIncome.insert("1.0","your income portion" if str(txtFixedIncome.get("1.0","end")) == "\n" else ""))
This will erase all data in entry when focused out and enter information data when focused in.
Solution of second question:
You can make a function to update your values and format them like:
def updat(text):
try:txtCalculationResult.insert(tkinter.INSERT,"{:,.0f}".format(float(text)))
except:txtCalculationResult.insert(tkinter.INSERT,"CalculationResults")
As:
>>> "{:,.0f}".format(23442)
'23,442'
>>>
Full code:
Here is a full example code that shows the behavior you are expecting:
import tkinter
mainForm = tkinter.Tk()
mainForm.title('Shahr Fixed Income Fund')
def updat(text):
try:txtCalculationResult.insert(tkinter.INSERT,"{:,.0f}".format(float(text)))
except:txtCalculationResult.insert(tkinter.INSERT,"CalculationResults")
def btnCalculatePressed():
txtCalculationResult.delete('1.0', 'end')
yourIncomePortion = txtFixedIncome.get('1.0', 'end')
updat(yourIncomePortion)
btnCalculate = tkinter.Button(mainForm , text = "Calculate", command= btnCalculatePressed)
txtCalculationResult = tkinter.Text(mainForm)
txtCalculationResult.insert(tkinter.INSERT, "CalculationResults")
txtFixedIncome = tkinter.Text(mainForm, height = 1, width = 30 )
txtFixedIncome.insert(tkinter.INSERT, "your income portion")
txtFixedIncome.bind("<FocusIn>",lambda _:txtFixedIncome.delete('1.0', 'end'))
txtFixedIncome.bind("<FocusOut>",lambda _:txtFixedIncome.insert("1.0","your income portion" if str(txtFixedIncome.get("1.0","end")) == "\n" else ""))
txtFixedIncome.pack();
txtCalculationResult.pack()
btnCalculate.pack()
mainForm.mainloop()
Format during typing:
If you want to format the string during typing as you commented then use the following code:
import tkinter
mainForm = tkinter.Tk()
mainForm.title('Shahr Fixed Income Fund')
def updat(text):
try:txtCalculationResult.insert(tkinter.INSERT,"{:,.0f}".format(float(text.replace(",",""))))
except:txtCalculationResult.insert(tkinter.INSERT,"CalculationResults")
def btnCalculatePressed():
txtCalculationResult.delete('1.0', 'end')
yourIncomePortion = txtFixedIncome.get('1.0', 'end')
updat(yourIncomePortion)
btnCalculate = tkinter.Button(mainForm , text = "Calculate", command= btnCalculatePressed)
txtCalculationResult = tkinter.Text(mainForm)
txtCalculationResult.insert(tkinter.INSERT, "CalculationResults")
txtFixedIncome = tkinter.Text(mainForm, height = 1, width = 30 )
txtFixedIncome.insert(tkinter.INSERT, "your income portion")
def updat2():
text = txtFixedIncome.get("1.0","end")
txtFixedIncome.delete("1.0","end")
try:
txtFixedIncome.insert(tkinter.INSERT,"{:,.0f}".format(float(text.replace(",",""))))
except:
txtFixedIncome.insert(tkinter.INSERT,text[:-1])
txtFixedIncome.bind("<FocusIn>",lambda _:txtFixedIncome.delete('1.0', 'end'))
txtFixedIncome.bind("<FocusOut>",lambda _:txtFixedIncome.insert("1.0","your income portion" if str(txtFixedIncome.get("1.0","end")) == "\n" else ""))
txtFixedIncome.bind("<Key>",lambda _:mainForm.after(50,updat2))
txtFixedIncome.pack();
txtCalculationResult.pack()
btnCalculate.pack()
mainForm.mainloop()
How can I use the following code to delete a selection from a listbox and removing it from the list the contains it also? The selections in the listbox are dictionaries which I store in a list.
.................code..............................
self.frame_verDatabase = Listbox(master, selectmode = EXTENDED)
self.frame_verDatabase.bind("<<ListboxSelect>>", self.OnDouble)
self.frame_verDatabase.insert(END, *Database.xoomDatabase)
self.frame_verDatabase.pack()
self.frame_verDatabase.config(height = 70, width = 150)
def OnDouble(self, event):
widget = event.widget
selection=widget.curselection()
value = widget.get(selection[0])
print ("selection:", selection, ": '%s'" % value)
Example:
When I make a selection in the listbox, this data gets returned:
selection: (2,) : '{'Fecha de Entrega': '', 'Num Tel/Cel': 'test3', 'Nombre': 'test3', 'Num Orden': '3', 'Orden Creada:': ' Tuesday, June 23, 2015', 'Email': 'test3'}'
from tkinter import *
things = [{"dictionaryItem":"value"}, {"anotherDict":"itsValue"}, 3, "foo", ["bar", "baz"]]
root = Tk()
f = Frame(root).pack()
l = Listbox(root)
b = Button(root, text = "delete selection", command = lambda: delete(l))
b.pack()
l.pack()
for i in range(5):
l.insert(END, things[i])
def delete(listbox):
global things
# Delete from Listbox
selection = l.curselection()
l.delete(selection[0])
# Delete from list that provided it
value = eval(l.get(selection[0]))
ind = things.index(value)
del(things[ind])
print(things)
root.mainloop()
Edited for clarity. Since the listbox in this case only included dict objects I simply eval the value that is pulled from the listbox, get its index inside the list object, and delete it.
Everything from the second comment up to the print statement can be accomplished in one line as follows:
del(things[things.index(eval(l.get(selection[0])))])
If you feel like being creative.