I am trying to build a GUI creator using Python and Tkinter, but ran into a problem.
My problem is How to add\update widgets in runtime?
for example:
I have created the main window.
In that main window, I have created a frame name w_frame which contains a bunch of widget.
Based on my input in the Text or Entry widget beside the w_frame, I want to update a particular widget.
Lets say w_frame contains a Entry widget, radio button, button and label all available with the basic or main attributes need to display it.
Now I want to change the background color of label.
In short I want to write the code label_name.property_name=value or for example a_label.bg=red in the text widget and as soon as I press apply button, the widget should change.
I have searched on web, but not able to find the required solution. Also tried using How can i update a certain widget in tkinter, but that does not work depending on my input.
from tkinter import *
root=Tk()
w_frame=Frame()
w_frame.pack()
def update_Frame():
a=u_text_wid.get("1.0",END)
b.config(a)
root.update()
def add_wid_in_frame():
global a,b
a=Button(w_frame,text='heelo')
a.pack()
b=Label(w_frame,text='heelo')
b.pack()
u_text_wid=Text()
u_text_wid.pack()
button1=Button(text="add",command=add_wid_in_frame)
button1.pack()
button1=Button(text="update",command=update_Frame)
button1.pack()
root.mainloop()
this results me in an error
unknown option "-bg="red"
Note:
I want to update the widget based on the property value provided by the user, so it wont be hard-code into the script.
You are getting the error because every thing you retrieve from Text widget is a string and you cannot directly pass an string to .config method, you need a keyword and then you can assign value which can be string.
According to your question and the comments on the question, what i have figured out is:
You want to run lable.config(bg='red') from the Text widget.
You want to change the property of specific widget.
Here's what you can do:
To run Tkinter code form Text widget, you can use:
getattr method
eval method
Just to change property of widget:
def update_Frame():
global bcd
a = u_text_wid.get("1.0", "end-1c")
b=a.split(",")
c=[tuple(i.split("=")) if "=" in i else i for i in b]
d=dict(i for i in c)
for key,value in d.items():
bcd[key]=value
We can use string to change property only in this format widget_name[key]=value.
Some Useful Links:
Eval()
Getattr()
For your case, you can use ast.literal_eval() to convert a JSON string to dictionary and use the dictionary in .config():
from ast import literal_eval
...
def update_Frame():
a = u_text_wid.get("1.0", "end-1c") # don't include ending newline
cnf = literal_eval(a) # convert JSON string to dictionary
b.config(cnf)
Example input of the JSON string:
{"fg":"yellow", "bg":"red"}
Note that you can also use json module to convert the JSON string as well.
Related
I have a list of frames that each have an optionmenu with the same list of choices. When a choice is made in that specific optionmenu, I've only been able to get the last entry widget to change, not the corresponding one. In other widgets I've been able to use something like "lambda F=F:function(args)" but that isn't working here.
I've tried a trace on the variable in the option menu, I've tried wrapper functions, I've tried every combination of a lambda in the command section of the optionmenu widget. Most approaches create errors, some, like the one attached, modify the bottom frame/entry but not the correct corresponding one.
This doesn't seem like it should be too hard. If the option for the top frame selected is "Continuous" or "Discrete", the entry next to it should be 'normal' state with "?..?" in the box, if it is categorical, it should change to be 'disabled' with no contents. I could do this easily if I could somehow pass the Frame dictionary key to the "updateOnChange" function, but I can't, it only allows a single argument to be passed and that is the string value of mType[F].
from tkinter import *
def updateOnChange(type):
print(type)
if type.upper()=='CATEGORICAL':
rangeEntry[F].delete(0,END)
rangeEntry[F].config(state='disabled')
print("runCat")
else:
rangeEntry[F].config(state='normal')
rangeEntry[F].delete(0,END)
rangeEntry[F].insert(0,'?..?')
print("runCont")
mType={}
frame={}
om={}
rangeEntry={}
root=Tk()
Frames=['FrameOne','FrameTwo']
miningTypes=['Continuous','Categorical','Discrete']
for F in Frames:
mType[F]=StringVar(root)
if F=='FrameOne':
mType[F].set("Continuous")
else:
mType[F].set("Categorical")
frame[F]=Frame(root,borderwidth=3,relief=SUNKEN)
frame[F].pack(side=TOP,fill=X)
rangeEntry[F]=Entry(frame[F],width=20,font=("Arial",12))
om[F]=OptionMenu(frame[F],mType[F],*miningTypes,command=updateOnChange)
om[F].pack(side=LEFT)
rangeEntry[F].pack(side=LEFT)
mainloop()
``
Your updateOnChange function hard-coded the entry to be changed as rangeEntry[F], which points to the last Entry widget created in your for loop. To properly associate each entry, you should pass the widget as a parameter:
def updateOnChange(type, entry):
if type.upper()=='CATEGORICAL':
entry.delete(0,END)
entry.config(state='disabled')
print("runCat")
else:
entry.config(state='normal')
entry.delete(0,END)
entry.insert(0,'?..?')
print("runCont")
And then pass the parameter in your command:
om[F]= OptionMenu(frame[F],mType[F],*miningTypes,command=lambda e, i=rangeEntry[F]: updateOnChange(e, i))
I'm creating a game using tkinter, and need to change some of the text in a function. How do I initialize a text box and change the text in the text block?
I have tried to create text and then in a separate function I used itemconfigure to update it but I got an error.
self.player1_troops = self.canvas.create_text(80, 140,text='')
self.player2_troops = self.canvas.create_text(1210, 140,text='')
def changeValues(self, player1, player2):
self.canvas.itemconfigure(self.player1_troops, player1.printTroops())
self.canvas.itemconfigure(self.player2_troops, player2.printTroops())
I expected the text blocks value to change but instead I recieved and error about an unknown object.
You have to tell itemconfigure which property to change. You should use it like this:
self.canvas.itemconfigure(self.player1_troops, text=player1.printTroops())
Notice the addition of text=.
I want to make my tkinter Text to be only an output and not an input. Through some research I've found that text.config(state="disabled") disables user input, but it still allows for selecting text, which I do not want.
How can I get my Text widget to be unselectable and unwritable?
The simplest way is to replace the default text bindings that support selection so that they do nothing. There are a couple ways to do this: using binding tags you can remove all default bindings, or you can remove the bindings to only a subset of default bindings.
Removing all default bindings
All bindings on widgets -- including the default bindings -- are associated with binding tags (also called "bindtags"). The binding tag for the the text widget is "Text", and all default bindings for the text widget are associated with this tag. If you remove that binding tag, you remove all Text-specific bindings.
The default binding tags for any widget is a tuple of the string representation of the widget, the internal widget class (in this case, "Text"), the internal name of the toplevel window (in this case, root), and the special tag "all".
In the following example we change the binding tags so that "Text" is not included, effectively removing all default bindings on the text widget:
import tkinter as tk
root = tk.Tk()
text = tk.Text(root)
text.bindtags((str(text), str(root), "all"))
Removing specific bindings
If you prefer to keep some of the default bindings, you can replace just the ones that you don't want. You do that by creating your own bindings, and having those bindings return the string "break". This special return value tells tkinter to stop processing the event any further.
For example, to prevent a double-click from selecting the word under the cursor you could do this:
text.bind("<Double-1>", lambda event: "break")
The downside to this approach is that you have to figure out what all of the bindings are that are related to the selection mechanism. On the other hand, it gives you complete control over what each key or button press does.
A read-only, unselectable text widget.
class Textarea(tkinter.Text):
def __init__(self, master, **kw):
super().__init__(master, **kw)
# disable text alteration
self.configure(state="disabled")
# untag any selection from beginning to end
def unselect(event):
self.tag_remove("sel", "1.0", "end")
# catch different ways selections could be made and unselect before copying or cutting
good = ["<ButtonRelease-1>", "<Leave>", "<Control-c>", "<Control-C>", "<Control-x>", "<Control-X>"]
better = good + ["<Shift-Left>", "<Shift-Up>", "<Shift-Right>", "<Shift-Down>", "<Shift-Home>", "<Shift-End>", "<Shift-Next>", "<Shift-Prior>"]
excessive = better + ["<Shift-KP_1>", "<Shift-KP_2>", "<Shift-KP_3>", "<Shift-KP_4>", "<Shift-KP_6>", "<Shift-KP_7>", "<Shift-KP_8>", "<Shift-KP_9>"]
for sequence in better:
self.bind(sequence, unselect, add="+")
# remove the appearance of selection
self.configure(selectforeground=self.cget("foreground"), selectbackground=self.cget("background"))
# disallow export of selection in case anything gets through
self.configure(exportselection=False)
Tested on python 3.8.2
I believe you will have to replace it with another widget that such as a Label or LabelFrame to accomplish this. As well you could use a from tkinter import messagebox and have the text you want pop up in another window (like an info window or error message window). I think that as far as the Text widget goes, setting the state to disabled is the best you can do for your purposes unfortunately and users will be able to copy that text despite being unable to edit it.
Here is the simplest method to prevent text from being selected/highlighted when you just want the Text widget to be an ordinary log that is disabled and unselectable.
When I had the issue I figured I just needed to set some Text configuration property (highlightbackground, highlightcolor or selectbackground) to "Black". Nothing worked. The Text widget employs tags that can be used to mark up the Text within the control. Configuration for user defined tags as well as the special tag "sel" have a number of settings including foreground (color) and background (color).
tag_config("sel", background="black")
Too easy right? That doesn't work either.
Turns out that the highlight is actually a bitmap overlaid on the text. This is controlled by the bgstipple (bitmap) configuration for the tag. The documentation indicates that there are a number of system bitmaps (shades of grey) that can be used however it is also possible to specify your own. The bitmap needs to be an xbm and it's easy to create your own as it's a text file.
Put the following in a file named transparent.xbm.
#define trans_width 2
#define trans_height 2
static unsigned char trans_bits[] = {
0x00, 0x00
};
Here it is...
class TextLog(tk.Text):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tag_config("sel", bgstipple="#transparent.xbm",
foreground="white")
self.config(state="disabled")
def write_log(self, text="", clear=False)
self.configure(state='normal')
if clear is True:
self.delete("1.0","end")
self.insert(tk.END, text)
self.see(tk.END)
self.config(state="disabled")
Depending on where the .xbm is relative to the module using the TextLog you may need to prefix it with a path "#path/to/transparent.xbm"
I need a widget in TKinter to be a global widget, however, I need the text displayed in it to be different every time. I'm quite new with TKinter and haven't yet successfully managed to edit an option in a widget.
I assume it's something to do with widget.add_option() but the documentation is quite confusing to me and I can't figure out the command.
I specifically just need to edit the text = "" section.
Thanks
EDIT:
gm1_b_current_choice_label = Label(frame_gm1_b, text = "Current input is:\t %s"% str(save_game[6]))
I specifically need to update the save_game[6] (which is a list) in the widget creation, but I assume once the widget is created that's it. I could create the widget every time before I place it but this causes issues with destroying it later.
You can use the .config method to change options on a Tkinter widget.
To demonstrate, consider this simple script:
from Tkinter import Tk, Button, Label
root = Tk()
label = Label(text="This is some text")
label.grid()
def click():
label.config(text="This is different text")
Button(text="Change text", command=click).grid()
root.mainloop()
When the button is clicked, the label's text is changed.
Note that you could also do this:
label["text"] = "This is different text"
or this:
label.configure(text="This is different text")
All three solutions ultimately do the same thing, so you can pick whichever you like.
You can always use the .configure(text = "new text") method, as iCodez suggested.
Alternatively, try using a StringVar as the text_variable parameter:
my_text_var = StringVar(frame_gm1_b)
my_text_var.set("Current input is:\t %s"% str(save_game[6]))
gm1_b_current_choice_label = Label(frame_gm1_b, textvariable = my_text_var)
Then, you can change the text by directly altering my_text_var:
my_text_var.set("Some new text")
This can be linked to a button or another event-based widget, or however else you want to change the text.
How would i go about locking a Text widget so that the user can only select and copy text out of it, but i would still be able to insert text into the Text from a function or similar?
Have you tried simply disabling the text widget?
text_widget.configure(state="disabled")
On some platforms, you also need to add a binding on <1> to give the focus to the widget, otherwise the highlighting for copy doesn't appear:
text_widget.bind("<1>", lambda event: text_widget.focus_set())
If you disable the widget, to insert programatically you simply need to
Change the state of the widget to NORMAL
Insert the text, and then
Change the state back to DISABLED
As long as you don't call update in the middle of that then there's no way for the user to be able to enter anything interactively.
Sorry I'm late to the party but I found this page looking for the same solution as you.
I found that if you "disable" the Text widget by default and then "normal" it at the beginning of a function that gives it input and "disable" it again at the end of the function.
def __init__():
self.output_box = Text(fourth_frame, width=160, height=25, background="black", foreground="white")
self.output_box.configure(state="disabled")
def somefunction():
self.output_box.configure(state="normal")
(some function goes here)
self.output_box.configure(state="disable")
I stumbled upon the state="normal"/state="disabled" solution as well, however then you are unable to select and copy text out of it. Finally I found the solution below from: Is there a way to make the Tkinter text widget read only?, and this solution allows you to select and copy text as well as follow hyperlinks.
import Tkinter
root = Tkinter.Tk()
readonly = Tkinter.Text(root)
readonly.bind("<Key>", lambda e: "break")