Tkinter Text Tag Config not persisting - python

I'm trying to colour every odd line in my Application, however I just have a line that is moving down the screen. Is this because I don't persist the tag?
Here is some of the code:
def addTextToRaw(self, text, changeColour=False, numberOfLines=0):
self.rawText.config(state=NORMAL)
self.rawText.insert(END,text)
self.rawText.config(state=DISABLED)
if changeColour is True:
print "Changing Coloiur" + str(self.numberOfObjects())
lastLine = int(self.rawText.index('end-1c').split('.')[0])
start = str(lastLine)+".0"
end = str(lastLine)+".0+"+str(1+numberOfLines)+"lines"
self.rawText.tag_add("oddObject"+str(self.numberOfObjects()), start, end)
self.rawText.tag_config("oddObject"+str(self.numberOfObjects()), background="blue", foreground="white")
This method is called using the after method which it gets data from a queue which is being processed.
None of the colour lines stay theres just one line moving down the screen. So what can i do to make the colours persist?

If you want to color every other line, you merely need to add the tag once for each line, and you only need to configure the tag once when you create the widget.
Here's a working example:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.text = tk.Text(self, wrap="word")
self.vsb = tk.Scrollbar(self, orient="vertical", command=self.text.yview)
self.text.configure(yscrollcommand=self.vsb.set)
self.vsb.pack(side="right", fill="y")
self.text.pack(side="left", fill="both", expand=True)
self.text.tag_configure("odd", background="white", foreground="black")
self.text.tag_configure("even", background="blue", foreground="white")
self.addText(tk.__doc__)
def addText(self, text):
# get the starting line number
index = int(self.text.index("end-1c").split(".")[0])
# insert each line, adding a odd or even tag
tag = "even" if (index%2 == 0) else "odd"
for line in text.split("\n"):
self.text.insert("end", line+"\n", tag)
tag = "even" if tag == "odd" else "odd"
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()

Call the tag_config method in your update method this will then call it. Also you can give it the same tag so this method can be simplified like so:
def addTextToRaw(self, text, changeColour=False):
self.rawText.config(state=NORMAL)
if changeColour is True:
self.rawText.insert(END,text ,'oddObject')
else:
self.rawText.insert(END,text)
self.rawText.config(state=DISABLED)
Then in the update method just call:
self.rawText.tag_config("oddObject", background="blue", foreground="white")

Related

A question about 'pack' or 'grid' for tkinter widgets error

I am learning python.I follow the book example code. This code make SumGrid class in order to be attachable to containers where other widgets are being gridded or packed. The author said that it leaves its own geometry management ambiguous and requires callers to pack or grid its instances. It's OK for containers to pick either scheme for their own children because they effectively seal off the pack-or-grid choice. But attachable component classes that aim to be reused under both geometry managers cannot manage themselves because they cannot predict their parent's policy.
from tkinter import *
from tkinter.filedialog import askopenfilename
from PP4E.Gui.Tour.quitter import Quitter # reuse, pack, and grid
class SumGrid(Frame):
def __init__(self, parent=None, numrow=5, numcol=5):
Frame.__init__(self, parent)
self.numrow = numrow # I am a frame container
self.numcol = numcol # caller packs or grids me
self.makeWidgets(numrow, numcol) # else only usable one way
def makeWidgets(self, numrow, numcol):
self.rows = []
for i in range(numrow):
cols = []
for j in range(numcol):
ent = Entry(self, relief=RIDGE)
ent.grid(row=i+1, column=j, sticky=NSEW)
ent.insert(END, '%d.%d' % (i, j))
cols.append(ent)
self.rows.append(cols)
self.sums = []
for i in range(numcol):
lab = Label(self, text='?', relief=SUNKEN)
lab.grid(row=numrow+1, column=i, sticky=NSEW)
self.sums.append(lab)
Button(self, text='Sum', command=self.onSum).grid(row=0, column=0)
Button(self, text='Print', command=self.onPrint).grid(row=0, column=1)
Button(self, text='Clear', command=self.onClear).grid(row=0, column=2)
Button(self, text='Load', command=self.onLoad).grid(row=0, column=3)
Quitter(self).grid(row=0, column=4) # fails: Quitter(self).pack()
def onPrint(self):
for row in self.rows:
for col in row:
print(col.get(), end=' ')
print()
print()
def onSum(self):
tots = [0] * self.numcol
for i in range(self.numcol):
for j in range(self.numrow):
tots[i] += eval(self.rows[j][i].get()) # sum current data
for i in range(self.numcol):
self.sums[i].config(text=str(tots[i]))
def onClear(self):
for row in self.rows:
for col in row:
col.delete('0', END) # delete content
col.insert(END, '0.0') # preserve display
for sum in self.sums:
sum.config(text='?')
def onLoad(self):
file = askopenfilename()
if file:
for row in self.rows:
for col in row: col.grid_forget() # erase current gui
for sum in self.sums:
sum.grid_forget()
filelines = open(file, 'r').readlines() # load file data
self.numrow = len(filelines) # resize to data
self.numcol = len(filelines[0].split())
self.makeWidgets(self.numrow, self.numcol)
for (row, line) in enumerate(filelines): # load into gui
fields = line.split()
for col in range(self.numcol):
self.rows[row][col].delete('0', END)
self.rows[row][col].insert(END, fields[col])
if __name__ == '__main__':
import sys
root = Tk()
root.title('Summer Grid')
if len(sys.argv) != 3:
SumGrid(root).pack() # .grid() works here too
else:
rows, cols = eval(sys.argv[1]), eval(sys.argv[2])
SumGrid(root, rows, cols).pack()
root.mainloop()
When I run this .py file in my window shell. It can't work. The err message like below. I think SumGrid is a Frame. The caller can decide to pack or grid this Frame. But it can't work on my computer. Why? Thank you.
Traceback (most recent call last):
File "C:\Users\hp\PP4E-Examples-1.4\Examples\PP4E\Gui\Tour\Grid\grid5c.py", line 84, in <module>
SumGrid(root).pack() # .grid() works here too
File "C:\Users\hp\PP4E-Examples-1.4\Examples\PP4E\Gui\Tour\Grid\grid5c.py", line 12, in __init__
self.makeWidgets(numrow, numcol) # else only usable one way
File "C:\Users\hp\PP4E-Examples-1.4\Examples\PP4E\Gui\Tour\Grid\grid5c.py", line 35, in makeWidgets
Quitter(self).grid(row=0, column=4) # fails: Quitter(self).pack()
File "C:\Users\hp\PP4E-Examples-1.4\Examples\PP4E\Gui\Tour\quitter.py", line 12, in __init__
self.pack()
File "C:\Users\hp\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 2425, in pack_configure
self.tk.call(
_tkinter.TclError: cannot use geometry manager pack inside .!sumgrid which already has slaves managed by grid
Like the error says, you can't use both grid and pack on widgets that have the same parent.
Let's look at these two lines of code:
Button(self, text='Load', command=self.onLoad).grid(row=0, column=3)
Quitter(self).grid(row=0, column=4) # fails: Quitter(self).pack()
Notice how both Quitter and Button are using self as the parent. That means that you must only use one of grid or pack for both widgets (and the other widgets that have self as the parent). If you use grid for the button, you cannot use pack for Quitter, which is exactly what the error is telling you.

How to return string value of Console output to put it into a label in Tkinter

I made a python script that has a lot of print statements showing what the program is doing rather than just having it just sit there and the python script works fine now I am creating a front end with tkinter.
What I used to send the print statements to return them is some thing like this:
test.py
def PrintX():
X = [1,2,3,4,5]
for x in X:
print(x)
My plan is to have a tkinter frame in that I put a label and set the text variable to my function in my script. My tkinter page script looks like this so far:
class TradingBotapp(tk.Tk):
def __init__(self,*args,**kwargs):
tk.Tk.__init__(self,*args,**kwargs)
container = tk.Frame(self)
container.pack(side='top',fill='both',expand= True)
container.grid_rowconfigure(0,weight = 1)
container.grid_columnconfigure(0,weight = 1)
self.frames = {}
for F in (InitalSetup):
frame = F(container,self)
self.frames[F] = frame
frame.grid(row=0,column=0,sticky='nsew')
self.show_frame(InitalSetup)
def show_frame(self,cont):
frame = self.frames[cont]
frame.tkraise()
class InitalSetup(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
label = tk.Label(self, text='Setup', font = LARGE_FONT).pack()
Frame = tk.Frame(self,width=768,height=576).pack()
lbl = tk.Message(Frame, text='').pack()
button1 = ttk.Button(self, text='Start Setup',command=lambda:callback2(lbl)).pack()
def callback2(object):
old_stdout = sys.stdout
sys.stdout = StdoutRedirectorLabel(lbl)
setup()
#lbl['text'] = sys.stdout.result.strip()
sys.stdout = old_stdout
class StdoutRedirectorLabel(object):
def __init__(self,widget):
self.widget = widget
self.widget['text'] = ''
def write(self, text):
self.widget['text'] += text
app = TradingBotapp()
app.mainloop()
But nothing is showing up, but when I press the button I get self.widget['text'] = ''
TypeError: 'NoneType' object does not support item assignment any help would be much appreciated
Haha... You've fallen victim to one of the classic blunders!! (In all seriousness, I've done this many times before, you're not alone) I believe you need to pack your labels with label1.pack() after their creation.
The problem was that lbl was set and packed at the same time.
what was wrong: lbl = tk.Label(Frame, text='').pack()
to fix it I had to do this instead:
lbl = tk.Label(Frame, text='')
lbl.pack()
But it is not working the way I want it to so I have to find another way

Tkinter TreeView binding left click to the current tree and selected item

I am trying to bind this function self.copyTextToClipboard(self,t) to multiple different trees to make it more flexible (please see binding below).
from tkinter.ttk import Treeview
from tkinter import *
class App:
def __init__(self, master):
self.master = master
frame = Frame(master)
master.geometry("{}x{}".format(master.winfo_screenwidth() - 100, master.winfo_screenheight() - 100))
master.resizable(False, False)
self.leftFrame = Frame(master, bg="#DADADA", width=375, relief=SUNKEN)
self.leftFrame.pack_propagate(0)
self.leftFrame.pack(side=LEFT, fill=Y, padx=1)
# This table (TreeView) will display the partitions in the tab
self.partitionsOpenDiskTree = Treeview(self.leftFrame, columns=("#"), show="headings", selectmode="browse", height=23)
yscrollB = Scrollbar(self.leftFrame)
yscrollB.pack(side=RIGHT, fill=Y)
self.partitionsOpenDiskTree.column("#", width=50)
self.partitionsOpenDiskTree.heading("#", text="#")
self.partitionsOpenDiskTree.configure(yscrollcommand=yscrollB.set)
# Bind left click on text widget to copy_text_to_clipboard() function
self.partitionsOpenDiskTree.bind("<ButtonRelease-1>", lambda t=self.partitionsOpenDiskTree: self.copyTextToClipboard(self,t))
# Adding the entries to the TreeView
for i in range(3):
self.partitionsOpenDiskTree.insert("", "end", i, values=(i), tags=str(i))
self.partitionsOpenDiskTree.pack(anchor=NW, fill=Y)
#todo: figure out where this is getting called and put in tree
def copyTextToClipboard(self, tree, event=None):
print(type(tree))
# triggered off left button click on text_field
root.clipboard_clear() # clear clipboard contents
textList = tree.item(tree.focus())["values"]
line = ""
for text in textList:
if line != "":
line += ", " + str(text)
else:
line += str(text)
root.clipboard_append(line) # append new value to clipbaord
root = Tk()
app = App(root)
root.mainloop()
However, I am unable to bind it to a TreeView object it seems; when I run the code, I get:
Exception in Tkinter callback
<class '__main__.App'>
Traceback (most recent call last):
File "C:\Users\user1\Anaconda3\lib\tkinter\__init__.py", line 1699, in __call__
return self.func(*args)
File "C:/Users/user1/main_merged.py", line 56, in <lambda>
lambda t=self.partitionsOpenDiskTree: self.copyTextToClipboard(self,t))
File "C:/Users/user1/main_merged.py", line 70, in copyTextToClipboard
textList = tree.item(tree.focus())["values"]
AttributeError: 'App' object has no attribute 'item'
If I try to print out tree type, I get that it's a not a TreeView object. Any ideas on how I can get a TreeView object, so that I can figure out which item was selected?
Thanks!
-FF
So, apparently, taking out the self call seemed to work:
from tkinter.ttk import Treeview
from tkinter import *
class App:
def __init__(self, master):
self.master = master
frame = Frame(master)
master.geometry("{}x{}".format(master.winfo_screenwidth() - 100, master.winfo_screenheight() - 100))
master.resizable(False, False)
self.leftFrame = Frame(master, bg="#DADADA", width=375, relief=SUNKEN)
self.leftFrame.pack_propagate(0)
self.leftFrame.pack(side=LEFT, fill=Y, padx=1)
# This table (TreeView) will display the partitions in the tab
self.partitionsOpenDiskTree = Treeview(self.leftFrame, columns=("#"), show="headings", selectmode="browse", height=23)
yscrollB = Scrollbar(self.leftFrame)
yscrollB.pack(side=RIGHT, fill=Y)
self.partitionsOpenDiskTree.column("#", width=50)
self.partitionsOpenDiskTree.heading("#", text="#")
self.partitionsOpenDiskTree.configure(yscrollcommand=yscrollB.set)
# Bind left click on text widget to copy_text_to_clipboard() function
self.partitionsOpenDiskTree.bind("<ButtonRelease-1>", lambda event, t=self.partitionsOpenDiskTree: self.copyTextToClipboard(t))
# Adding the entries to the TreeView
for i in range(3):
self.partitionsOpenDiskTree.insert("", "end", i, values=(i), tags=str(i))
self.partitionsOpenDiskTree.pack(anchor=NW, fill=Y)
#todo: figure out where this is getting called and put in tree
def copyTextToClipboard(self, tree, event=None):
print(type(tree))
# print(type(tree.partitionsOpenDiskTree))
# triggered off left button click on text_field
root.clipboard_clear() # clear clipboard contents
textList = tree.item(tree.focus())["values"]
line = ""
for text in textList:
if line != "":
line += ", " + str(text)
else:
line += str(text)
root.clipboard_append(line) # append new value to clipbaord
print(line)
root = Tk()
app = App(root)
root.mainloop()
Output:
0
When you use bind, the callback function must have an event as its first argument, custom arguments should be put after. But as your callback does not need the event parameters, you may mask it with your lambda. So you have to change both the binding and the def of your callback:
self.partitionsOpenDiskTree.bind("<ButtonRelease-1>", lambda event, t=self.partitionsOpenDiskTree: self.copyTextToClipboard(t))
...
def copyTextToClipboard(self, tree):
should solve the problem

How to display text beside cursor in tkinter Listbox

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

Match Tkinter Text tag to string?

I'm wondering if it's possible to bind a tag, such as 'keyword', to a string, such as 'print' to my Text widget in Tkinter (Python).
I'd like to know this because I'm not sure how I can use regular expressions to connect to my Text widget, and it seems extremely over-complicated, so I was wondering if there was any normal way.
My current code (SyntaxHighlighting.py):
global lang
lang = 'py'
def highlighter_bind(obj):
# Bind a highlighting language to a text widget within obj.
# Notice that obj must be a Frame with a .root property being
# the root it is binded to, and a .text widget being where I
# can highlight the text.
#
# The lang variable must be specified as a string that
# contains the file extension of whatever you want to
# highlight.
#
# Supported languages:
# - py
def command(self):
# Sub command for highlighting, it's what will
# be "binded" to <Key>.
if lang == 'py':
print 'imported language py'
# This is to get all the contents from the Text widget (property).
t = o.text.get('1.0', END).split('\n')[0:len(o.text.get('1.0', END).split('\n')) - 1]
# Now loop through the text and find + add a tag to each of the line's contents, assuming that there is something to be found.
for i in range(0, len(t)):
# We use the try / except because search.group will return an error if it's a NoneType.
try:
# Now we need to search through the line and see if we can find print.
if search('print ', t[i]).group(0):
# Now, WHERE did we find print? I get confused there,
# because I have no idea where to find the index(es) of
# the string I found within a line. I use a regex to find the
# string, but how do I find where?
print "Ok!" # Temporary, just stating that I found it. It works.
except:
pass
else:
print 'unrecognized language:', lang
o.root.bind('<Key>', command)
Ideal code were it to actually work:
...
def command(self):
if lang == 'py':
obj.text.tag_match("print", 'keyword')
# And even better, using regex to match..
obj.text.tag_match("(if|elif|else)", 'keyword')
obj.text.tag_match("(not|and|or)", 'keyword')
obj.text.tag_match("(\+|-|*|/|**|%%)", 'keyword') # %% is to put a modulus in.
The way to apply highlighting according to a regular expression or fixed string is to use the text widget search method. Your method of searching the raw text and then trying to determine the index is not the right approach.
Here is an example of applying the tag "keyword" to all instances of the word "print":
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.text = tk.Text(self, wrap="none")
xsb = tk.Scrollbar(self, orient="horizontal", command=self.text.xview)
ysb = tk.Scrollbar(self, orient="vertical", command=self.text.yview)
self.text.configure(yscrollcommand=ysb.set, xscrollcommand=xsb.set)
ysb.grid(row=0, column=1, sticky="ns")
xsb.grid(row=1, column=0, sticky="ew")
self.text.grid(row=0, column=0, sticky="nsew")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
self.text.tag_configure("keyword", foreground="#b22222")
# this probably isn't what you want to do in production,
# but it's good enough for illustrative purposes
self.text.bind("<Any-KeyRelease>", self.highlight)
self.text.bind("<Any-ButtonRelease>", self.highlight)
def highlight(self, event=None):
self.text.tag_remove("keyword", "1.0", "end")
count = tk.IntVar()
self.text.mark_set("matchStart", "1.0")
self.text.mark_set("matchEnd", "1.0")
while True:
index = self.text.search("print", "matchEnd","end", count=count)
if index == "": break # no match was found
self.text.mark_set("matchStart", index)
self.text.mark_set("matchEnd", "%s+%sc" % (index, count.get()))
self.text.tag_add("keyword", "matchStart", "matchEnd")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
For an example that subclasses the text widget and adds a method for highlighting any regular expression, see this answer to the question How to highlight text in a tkinter Text widget

Categories

Resources