Changing the colour of text automatically inserted into tkinter widget - python

I have a text box widget that has three messages inserted into it. One is a start message, one an ending message, and one a message to alert when a 'unit' has been destroyed. I want the starting and ending messages to be black, but the destoyed messages (see where I have commented in the code) to be coloured red when inserted into the widget.
I'm not exactly sure how to go about doing this. I took a look at How to change the color of certain words in the tkinter text widget? and this would work, however it only changes the text properties if the text is selected using the mouse, whereas I want my text to be inserted in that colour automatically.
Any suggestions to point me in the right direction? I relatively new to Tkinter. I'm using Python 3.6.
import tkinter as tk
import random
class SimulationWindow():
def __init__(self, master):
#Initialise Simulation Window
self.master = master
self.master.geometry('1024x768') #w,h
#Initialise Mainframe
self.mainframe()
def mainframe(self):
#Initialise Frame
self.frame = tk.Frame(self.master)
self.frame.grid_columnconfigure(0, minsize = 100)
self.frame.grid(row = 1, sticky = 'w')
self.txtb_output = tk.Text(self.frame)
self.txtb_output.config(width = 115, height = 30, wrap = tk.WORD)
self.txtb_output.grid(row = 1, column = 0, columnspan = 3, padx = 50)
btn_start = tk.Button(self.frame)
btn_start.config(text = 'Start', borderwidth = 2, padx = 2, height = 2, width = 20)
btn_start.bind('<Button-1>', self.start)
btn_start.grid(row = 2, column = 1, padx = (0,65), pady = 20)
def battle(self):
if len(self.units) == 0:
self.txtb_output.insert(tk.END, 'The battle has ended.\n')
else:
try:
destroyed = random.randint(0,4)
#I want the text here to be red
self.txtb_output.insert(tk.END, 'The unit ' + self.units[destroyed] + ' has been destroyed.\n')
self.units.remove(self.units[destroyed])
self.frame.after(5000, self.battle)
except:
self.battle()
def start(self, event):
self.units = ['battle droid', 'battle droid', 'droid tank', 'super battle droid', 'battle droid']
self.txtb_output.insert(0.0, 'The battle has begun.\n')
self.battle()
def main():
root = tk.Tk()
main_window = SimulationWindow(root)
root.mainloop()
main()

insert() has option tags so you can assign tag (or many tags) when you insert text.
And then you have to only assign color to this tag.
import tkinter as tk
root = tk.Tk()
txt = tk.Text(root)
txt.pack()
txt.tag_config('warning', background="yellow", foreground="red")
txt.insert('end', "Hello\n")
txt.insert('end', "Alert #1\n", 'warning')
txt.insert('end', "World\n")
txt.insert('end', "Alert #2\n", 'warning')
root.mainloop()

Related

Two tk windows instead of one

I am new to tkinter and I would like to create a tkinter interface with "entries" , a canvas with a picture , and a button. I expected only one tk window but there is a second small empty tk window when I run my code. I guess it is because of the class I use but I'm not sure..
Here is my code :
from tkinter import*
import tkinter as tk
import time
class Application(Tk):
def __init__(self,transfo,nb_itération):
Tk.__init__(self)
self.transfo = transfo
self.nb_itération = nb_itération
def affichage_graphique(self):
self.matrix_hex = extraction_rgb(tk.PhotoImage(file='obama_128.gif'))
self.width , self.height = len(self.matrix_hex[0]) , len(self.matrix_hex)
self.WIDTH, self.HEIGHT = 8*self.width+6, 8*self.height+6
self.cnv = Canvas(self, width=self.WIDTH, height=self.HEIGHT, bg='white',highlightthickness=0)
self.cnv.grid(row = 2 , columnspan = 3)
self.txt1 = Label(self, text = 'Transformation :').grid(row = 0 , sticky = E)
self.txt2 = Label(self, text = 'Nombre d\'itérations : ').grid(row = 1 , sticky = E)
self.entr1 = Entry(self)
self.entr2 = Entry(self)
self.entr1.grid(row = 0, column = 1, sticky = W)
self.entr2.grid(row = 1, column = 1, sticky = W)
Button(self, text="Quitter", command=self.destroy).grid(row = 0 , column = 2)
if self.transfo == photomaton or self.transfo == boulanger:
print("valeur par défaut mauvaise")
self.cnv.delete(ALL)
self.img01 = self.PhotoImage(width=self.width*4, height=self.height*4)
self.img = self.PhotoImage(width=self.width*4, height=self.height*4)
self.matrix_hex4 = [[x for x in range(self.width*4)] for y in range(self.height*4)]
self.matrix_4 = [[x for x in range(self.width*4)] for y in range(self.height*4)]
self.img.put(data=self.matrix_4 , to=(0,0))
self.cnv.create_image(0, 0, image=self.img, anchor=tk.NW)
self.img01.put(data=self.matrix_hex4, to=(0,0))
self.cnv.create_image(self.WIDTH, 0, image=self.img01, anchor=tk.NE)
self.cnv.bind('<Button-1>',self.click_handler)
else:
self.img = tk.PhotoImage(width=self.width*4, height=self.height*4)
self.img.put(data=self.matrix_4 , to=(0,0))
self.cnv.create_image(0, 0, image=self.img, anchor=tk.NW)
self.entr1.insert(0,"photomaton")
self.entr2.insert(0,"1")
self.entr2.bind("<Return>",Programme().Enter)
class Programme(Application):
def __init__(self,transfo = 0, nb_itération = 0 ,orbites =[],période = []):
self.choice = 0
self.orbites = orbites
self.période = période
self.transfo = transfo
self.nb_itération = nb_itération
Application.__init__(self, self.transfo , self.nb_itération)
def Enter(self,event):
self.choix_transfo = self.entr1.get()
self.nb_itération = self.entr2.get()
return self.choix_transfo , self.nb_itération
def start(self):
self.affichage_graphique()
prog = Programme()
prog.start()
prog.mainloop()
The problem seems to be on this line:
self.entr2.bind("<Return>",Programme().Enter)
This line has multiple problems:
it will create a second Program() instance (assuming that this is a typo), thus creating a second Tk window (because Program extends Tk)
it will do so immediately, not only when the button is clicked, and bind the method of that new instance to the button
Instead, you probably want to use
self.entr2.bind("<Return>", self.Enter)
to call the Enter method of the current Program instance, or
self.entr2.bind("<Return>", lambda: Program().Enter)
if you actually want to create a second Program window when the button is clicked; however, in this case you will also have to call the gui method in order to initialize the widgets.
Note that there are a few other problems in your code, as discussed in comments, but I assume that those don't exist in your actual code, otherwise you would not get that behaviour.
You are creating a new window in two places. First, with this:
prog = Programme()
The second is here:
self.entr2.bind("<Return>",Programme().Enter)
Why? The above code is functionally identical to this:
p = Programme()
self.entr2.bind("<Return>", p.Enter)
The normal way to call functions defined in your program class is to use the existing reference. Since your code is being run in a method belonging to the Programme class, you can do this:
self.entr2.bind("<Return>", self.Enter)

Tkinter entry getting text entered by user

I am very new to Tkinter ( I find it very difficult to learn). I have a python script working based on user input. I would like to wrap a GUI around it and eventually put it on web. In any case for user input I would like to get this from the GUI with a combination of Entry widgets and some buttons. First thing is I was reading and some people mentioned to use a class so I have the following. I have a few questions
I would like to check to see if indeed the users entered a value before he hits the GO button. How do I do this?
I would like the value entered to be made accessible by the rest of the program in the main body. How do I do this?
Thanks,
from Tkinter import *
class MainWindow():
def get_fc(self):
a = self.fc_gui.get()
return a
def __init__(self, master):
self.master = master
self.master.title('TEST')
self.fc_gui = DoubleVar(self.master, value = 500.00)
self.fclabel1 = Label(self.master, text = 'Please Enter a value', fg = 'black', bg = 'yellow')
self.fclabel1.grid(row = 0, column = 0)
self.fcedit1 = Entry(self.master, textvariable = self.fc_gui, bd = 5 )
self.fcedit1.grid(row = 1, column = 0)
fcbutton1 = Button(self.master, text='GO', command = self.get_fc)
fcbutton1.grid(row = 1, column = 1)
master = Tk()
MainWindow(master)
master.mainloop()
It doesn't make sense to return to a Button. The Button can't do anything with the value. Instead, save the value as an instance variable.
You don't have a mainloop().
You can't really check if the user entered a value before they hit "Go" - at the start of the program, of course they haven't entered anything yet. If you needed to track the contents of this field, there are ways to do that, but it's not necessary for a simple validation. Just check the value when they hit the button.
from Tkinter import *
class MainWindow():
def get_fc(self):
a = self.fc_gui.get()
if a: # this block will execute if a has content
self.a = a # save it for future use
def __init__(self, master):
self.master = master
self.master.title('TEST')
self.fc_gui = DoubleVar(self.master, value = 500.00)
self.fclabel1 = Label(self.master, text='Please Enter a value',
fg = 'black', bg = 'yellow')
self.fclabel1.grid(row = 0, column = 0)
self.fcedit1 = Entry(self.master, textvariable = self.fc_gui, bd = 5 )
self.fcedit1.grid(row = 1, column = 0)
fcbutton1 = Button(self.master, text='GO', command = self.get_fc)
fcbutton1.grid(row = 1, column = 1)
master = Tk()
MainWindow(master)
master.mainloop() # don't forget mainloop()

Multi-page window cannot highlight Entry text with callback

Recently I've changed the layout of my program to include a multi-page window similar to what is in the provided example.
In the original, two-window configuration I had a binding set on each window to highlight all of the text in the Entry widget, based on a condition (no condition present in the example). This was fine.
Upon upgrading to a multi-page window, I tried to combine the callback to highlight text by passing the relevant widget and calling widget.select_range(0, END) as it is done in the example. Now I can't seem to highlight any text on mouse-click.
In addition to this, I've also tested my example code with having a separate callback for each Entry; even this would not highlight the text in the Entry upon clicking on it.
Could this have something to do with lifting frames & where the focus lies? As a test I've added a similar callback for "submitting" the Entry value, and this is working fine. At this point I'm confused as to why this wouldn't work. Any help is greatly appreciated.
UPDATE:
I forgot that to solve the highlighting problem, I've needed to include a return "break" line in the callback that is used to highlight the text.
Now, with this included, I have some very strange behavior with the Entry widgets. I can't click on them unless they have been focused using the tab key.
Is there any way to work around this problem?
Here is the example code I have been playing with (with the updated return statement):
from Tkinter import *
class Window():
def __init__(self, root):
self.root = root
self.s1 = StringVar()
self.s1.set("")
self.s2 = StringVar()
self.s2.set("")
# Frame 1
self.f1 = Frame(root, width = 50, height = 25)
self.f1.grid(column = 0, row = 1, columnspan = 2)
self.page1 = Label(self.f1, text = "This is the first page's entry: ")
self.page1.grid(column = 0, row = 0, sticky = W)
self.page1.grid_columnconfigure(index = 0, minsize = 90)
self.val1 = Label(self.f1, text = self.s1.get(), textvariable = self.s1)
self.val1.grid(column = 1, row = 0, sticky = E)
self.l1 = Label(self.f1, text = "Frame 1 Label")
self.l1.grid(column = 0, row = 1, sticky = W)
self.e1 = Entry(self.f1, width = 25)
self.e1.grid(column = 1, row = 1, sticky = E)
self.e1.bind("<Button-1>", lambda event: self.event(self.e1))
self.e1.bind("<Return>", lambda event: self.submit(self.e1, self.s1))
# Frame 2
self.f2 = Frame(root, width = 50, height = 25)
self.f2.grid(column = 0, row = 1, columnspan = 2)
self.page2 = Label(self.f2, text = "This is the 2nd page's entry: ")
self.page2.grid(column = 0, row = 0, sticky = W)
self.page2.grid_columnconfigure(index = 0, minsize = 90)
self.val2 = Label(self.f2, text = self.s2.get(), textvariable = self.s2)
self.val2.grid(column = 1, row = 0, sticky = E)
self.l2 = Label(self.f2, text = "Frame 2 Label")
self.l2.grid(column = 0, row = 1, sticky = W)
self.e2 = Entry(self.f2, width = 25)
self.e2.grid(column = 1, row = 1, sticky = E)
self.e2.bind("<Button-1>", lambda event: self.event(self.e2))
self.e2.bind("<Return>", lambda event: self.submit(self.e2, self.s2))
self.b1 = Button(root, width = 15, text = "Page 1", command = lambda: self.page(1), relief = SUNKEN)
self.b1.grid(column = 0, row = 0, sticky = E)
# Buttons
self.b2 = Button(root, width = 15, text = "Page 2", command = lambda: self.page(2))
self.b2.grid(column = 1, row = 0, sticky = W)
# Start with Frame 1 lifted
self.f1.lift()
def page(self, val):
self.b1.config(relief = RAISED)
self.b2.config(relief = RAISED)
if val == 1:
self.f1.lift()
self.b1.config(relief = SUNKEN)
else:
self.f2.lift()
self.b2.config(relief = SUNKEN)
def event(self, widget):
widget.select_range(0, END)
return "break"
def submit(self, widget, target):
target.set(widget.get())
root = Tk()
w = Window(root)
root.mainloop()
Well, this has been a productive question. If anyone in the future is doing something similar to this and needs a reference for how to solve the problem:
I was able to work around the problem by forcing the Entry widgets into focus every time I switch frames, and using the return "break" statement that I mention in the question's update.
This isn't ideal, as every time a page is changed you automatically focus on the Entry widget, but once the widget is in focus it's behavior is exactly what I would expect so this isn't of great concern. In my program, if you are changing pages it is quite likely you will use the Entry widget anyway (it is a search entry).
Here's the final changes required to make the code work correctly:
# .... some code ....
self.f1.lift()
self.e1.focus_force()
def page(self, val):
self.b1.config(relief = RAISED)
self.b2.config(relief = RAISED)
if val == 1:
self.f1.lift()
self.b1.config(relief = SUNKEN)
self.e1.focus_force()
else:
self.f2.lift()
self.b2.config(relief = SUNKEN)
self.e2.focus_force()
def event(self, widget):
widget.select_range(0, END)
return "break"
# .... more code ....

Tkinter / Pmw -- Centering Frame Inside Notebook Frame

What I'm trying to do is get the frame with the two buttons (sframe) centered inside of the notebook (master) frame. This works without issue on Python 2.4 but on Python 2.7 the frame is anchored to NW by default. I know if I rowconfigure() / columnconfigure() the master page frame the inner frame will center itself but this solution doesn't seem correct. Disabling propagation and changing row/column weights don't seem to help either. Is there anyway to just get that inner frame centered properly? Here is the test code I'm working with:
import Tkinter as tk, Tkinter
import Pmw
class SimpleApp(object):
def __init__(self, master, **kwargs):
title = kwargs.pop('title')
master.configure(bg='blue')
sframe = tk.Frame(master, relief=tk.RIDGE, bd=5, width=100,bg='green')
sframe.grid()
button = tk.Button(sframe, text = title)
button.grid(sticky = tk.W)
button = tk.Button(sframe, text = 'next')
button.grid(sticky = tk.E)
#sframe.propagate(0)
#master.rowconfigure(0, minsize = 300)
#master.columnconfigure(0, minsize = 300)
class Demo:
def __init__(self, parent):
# Create and pack the NoteBook.
notebook = Pmw.NoteBook(parent)
notebook.pack(fill = 'both', expand = 1, padx = 10, pady = 10)
# Add the "Appearance" page to the notebook.
page = notebook.add('Helpers')
app = SimpleApp(page, title= 'hello, world')
notebook.tab('Helpers').focus_set()
page = notebook.add('Appearance')
# Create the "Toolbar" contents of the page.
group = Pmw.Group(page, tag_text = 'Toolbar')
group.pack(fill = 'both', expand = 1, padx = 10, pady = 10)
b1 = Tkinter.Checkbutton(group.interior(), text = 'Show toolbar')
b1.grid(row = 0, column = 0)
b2 = Tkinter.Checkbutton(group.interior(), text = 'Toolbar tips')
b2.grid(row = 0, column = 1)
# Create the "Startup" contents of the page.
group = Pmw.Group(page, tag_text = 'Startup')
group.pack(fill = 'both', expand = 1, padx = 10, pady = 10)
home = Pmw.EntryField(group.interior(), labelpos = 'w',
label_text = 'Home page location:')
home.pack(fill = 'x', padx = 20, pady = 10)
page = notebook.add('Images')
notebook.setnaturalsize()
def basic():
root = tk.Tk()
#app = SimpleApp(root, title = 'Hello, world')
app = Demo(root)
root.mainloop()
basic()
Let me know if I can provide any additional information.
You need to configure the weight of row 0 and column 0 in the master:
master.grid_columnconfigure(0, weight=1)
master.grid_rowconfigure(0, weight=1)
You are placing that inner sframe in row 0 column 0 of master, and since that cell has no weight it shrinks up to the upper-left corner. Giving the row and column a weight of 1 makes the column and row fill the available space. Since you aren't using any sticky options for the sframe, it will stay centered in its cell rather than filling its cell.

Why is this code working I took from a textbook for GUI Python?

Why isn't this working. This is straight from the text book. I'm getting an Attribute error saying self._area does not exist.
from Tkinter import *
import math
class CircleArea(Frame):
def __init__(self):
"""Sets up a window and widgets."""
Frame.__init__(self)
self.master.title("Circle Area")
self.grid()
#Label and field for radius
self._radiusLabel = Label(self, text = "Radius")
self._radiusLabel.grid(row = 0, column = 0)
self._radiusVar = DoubleVar()
self._radiusEntry = Entry(self, textvariable = self._radiusVar)
self._radiusEntry.grid(row = 0, column = 1)
#Label and field for the area
self._areaLabel = Label(self, text = "Area")
self._areaLabel.grid(row = 1, column = 0)
self._areaVar = DoubleVar()
self._areaEntry = Entry(self, textvariable = self._areaVar)
self._areaEntry.grid(row = 1, column = 1)
# The command button
self._button = Button(self, text = "Compute", command = self._area)
self._button.grid(row = 2, column = 0, columnspan = 2)
def _area(self):
"""Event handler for button."""
radius = self._radiusVar.get()
area = radius ** 2 * math.pi
self._areaVar.set(area)
def main():
CircleArea(). mainloop()
run = CircleArea()
run.main()
Is it because the _area method is declared after it is called? That doesn't make sense why it wouldn't work using a down up programming technique. I'm really new to GUI just started learning. First chapter on GUI for class.
edit*: I'm expecting a window to pop up and have one Entry field for input for the radius of the circle. With a label Radius. And an output entry field for the results of the area of the circle based on the radius. and a compute button at the bottom which computes it.
And I just wanted to get used to typing the different commands and such. I haven't even been in the lecture for this yet. I was just seeing what this code would do and what it would look like. I typed it all out by hand if that makes you feel better.:P Instead of copy and pasting.
The problem is that your indenting is wrong. _area and main are defined within __init__, which you don't want. Correct indenting is below (you don't need a main function).
from Tkinter import *
import math
class CircleArea(Frame):
def __init__(self):
"""Sets up a window and widgets."""
Frame.__init__(self)
self.master.title("Circle Area")
self.grid()
#Label and field for radius
self._radiusLabel = Label(self, text = "Radius")
self._radiusLabel.grid(row = 0, column = 0)
self._radiusVar = DoubleVar()
self._radiusEntry = Entry(self, textvariable = self._radiusVar)
self._radiusEntry.grid(row = 0, column = 1)
#Label and field for the area
self._areaLabel = Label(self, text = "Area")
self._areaLabel.grid(row = 1, column = 0)
self._areaVar = DoubleVar()
self._areaEntry = Entry(self, textvariable = self._areaVar)
self._areaEntry.grid(row = 1, column = 1)
# The command button
self._button = Button(self, text = "Compute", command = self._area)
self._button.grid(row = 2, column = 0, columnspan = 2)
def _area(self):
"""Event handler for button."""
radius = self._radiusVar.get()
area = radius ** 2 * math.pi
self._areaVar.set(area)
run = CircleArea()
run.mainloop()
Actually I think you miss an argument in your main method,you define a class CircleArea , but in python you know that, each method defined in class must have an default argument named 'self',so just try this
def main(self):
CircleArea(). mainloop()
I think it will work as you wish :)

Categories

Resources