root = tk.Tk()
text = tk.Text(root, width = 40, height = 1, wrap = 'word')
text.pack(pady = 50)
after_id = None
# Now i want to increase the height after wraping
def update():
line_length = text.cget('width')
lines = int(len(text.get(1.0, 'end'))/line_length) + 1 # This is to get the current lines.
text.config(height = lines) # this will update the height of the text widget.
after_id = text.after(600, update)
update()
root.mainloop()
Hi I'm making a text widget and I want to update it when some input the passed else keep it idle, Right now I'm using this code. but I don't know how to keep it idle when no input is passed or no button is pressed.
I know there is a better way to do this operation but didn't found yet. Please Help!!!
Hi after reading some documentation and article i got a solution of my problem. In this we can use KeyPress event and we can bind over update method with this event.
Here is the code....
import tkinter as tk
root = tk.Tk()
text = tk.Text(root, width = 40, height = 1, wrap = 'word')
text.pack(pady = 50)
# first of all we need to get the width of the Text box, width are equl to number of char.
line_length = text.cget('width')
Init_line = 1 # to compare the lines.
# Now we want to increase the height after wraping of text in the text box
# For that we will use event handlers, we will use KeyPress event
# whenever the key is pressed then the update will be called
def Update_TextHeight(event):
# Now in this we need to get the current number of char in the text box
# for the we will use .get() method.
text_length = len(text.get(1.0, tk.END))
# Now after this we need to get the total number of lines int the textbox
bline = int(text_length/line_length) + 1
# bline will be current lines in the text box
# text_length is the total number of char in the box
# Since we have line_length number of char in one line so by doing
# text_length//line_length we will get the totol line of numbers.
# 1 is added since initially it has one line in text box
# Now we need to update the length
if event.char != 'Return':
global Init_line
if Init_line +1 == bline:
text.config(height = bline)
text.update()
Init_line += 1
# Nowe we will bind the KeyPress event with our update method.
text.bind("<KeyPress>",Update_TextHeight)
root.mainloop()
Related
I intend to make a Py code which creates a tkinter dot that turns on a key press and deletes on a key press of couple keys.
The dot already is functional but i need it switch on and off on certain keypresses/mouse clicks which means i need an outside tkinter.mainloop() Update function.
The Update function with a while in it to constantly check if conditions to turn it off/on are present. But the Tkinter widget Somehow gets applied to the screen Only when the function nds. Like widget could be created but it will only take effect when function ends. And i need to turn it off/on dynamically.
I have tried to use a tkinter.after() with additional one at the end of called function only to find out an error of Recursion depth. What i expected to happen was that the function would be called over and over again, instead it runs that function like a while loop. I also have tried Asyncio.run() but it would result not making it visible till the function ends at least once. And I need to change it dynamically.
from tkinter import *
from tkinter import Canvas
from winsound import Beep
from time import sleep
import asyncio
import keyboard
import mouse
root = Tk()
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
class tk_Dot():
def __init__(self,x=-1,y=-1,radius=4,color="red"):
self.x = x
if x == -1:
self.x = width/2-radius//2
print(self.x)
self.y = y
if y == -1:
self.y = height/2+radius//2
print(self.y)
self.radius=radius
self.color = color
self.lines = []
self.count = 1
def line(self,i):
return canvas.create_line(self.x, self.y-i, self.x+self.radius, self.y-i, fill=self.color)
def create(self):
self.lines = []
for i in range(0,self.radius):
self.lines.append(self.line(i))
def delete(self):
for i in range(0,self.radius):
canvas.delete(self.lines[i])
canvas.dtag(self.lines[i])
opacity_of_tk_window = 1 # From 0 to 1 0 meaning completely transparent 1 meaning everything created in canvas will give the color it was given
root.attributes('-alpha',opacity_of_tk_window)
# Invisible Tkinter window label
root.overrideredirect(True)
# Makes Transparent background
transparent_color = '#f0f0f0'
root.wm_attributes('-transparent', transparent_color)
canvas = Canvas()
# Rectangle filled with color that is specified above as the transparent color so practically making transparent background
canvas.create_rectangle(0, 0, width, height, fill=transparent_color)
canvas.pack(fill=BOTH, expand=1)
radius = 2
radius = 1+radius\*2
# Create a dot class
game_dot = tk_Dot(width/2-radius//2+1,height/2+1+radius//2,radius,"Red")
# Create a Dot at the middle of the calorant crosshair
# game_dot.create()
# Delete the dot
# game_dot.delete()
def Update():
game_dot.create()
print("Dot should be visible by now")
print("Is it?")
sleep(5) #sec
print("Oh yeah after the function ends.") # the problem
def Delete():
game_dot.delete()
root.geometry('%dx%d+%d+%d' % (width, height, -2,-2))
# Tkinter window always on top
root.attributes('-topmost',True)
root.after(1000,Update())
root.mainloop()
My goal is to have an interactive bar chart. I want to open a window containing a text. This shall happen when the user clicks on one of the bars. I started playing around a bit. In the following example, if you click on the left bar a window containing a text pops up. The problem I have, I only want to have the window opening once. So if you click a second time on the left bar, I don't want to open a second window. Therefore my question, how can I check if the window already exists and avoid multiple windows of the same kind. I already found a post regarding this topic, but I do not understand the solution, which was only explained poorly.
Thank you very much for your help.
def on_press(event):
cont,att = rect[0].contains(event)
if cont == True:
win = tk.Tk()
label1 = ttk.Label(win, text ="Test1").grid(column=0,row=0)
label2 = ttk.Label(win, text ="Test2").grid(column=0,row=1)
label3 = ttk.Label(win, text ="Test3").grid(column=0,row=2)
fig = plt.figure()
ax = fig.add_subplot(111)
x = [1,2,3]
y = [10,20,5]
rect = ax.bar(x,y)
test = rect[0].figure.canvas.mpl_connect('button_press_event', on_press)
plt.show()
Meanwhile I found a solution for my problem. Like MediaEU said, I use a variable which I set to true or false. However, one important "trick" is to use the method state().
If a main window is open, state() returns 'normal'. If a main window is closed state() throws an exception. Took me - as a starter - quite some time to figure it out.
Here is my suggestion:
class info_window:
def __init__(self, rect): # has an argument rect, for a specific bar in your plot
self.rect = rect
self.win_open = False
self.state = 'normal'
self.temp = self.rect.figure.canvas.mpl_connect('button_press_event', self)
def label(self):
self.label1 = ttk.Label(self.win, text ="Test1").grid(column=0,row=0)
self.label2 = ttk.Label(self.win, text ="Test2").grid(column=0,row=1)
self.label3 = ttk.Label(self.win, text ="Test3").grid(column=0,row=2)
def __call__(self, event):
cont, att = self.rect.contains(event)
try: # here is the "trick", in case window was closed self.win.state() throws an exception
self.win.state()
except: # setting win_open to False ensures that a window can be opened again
self.win_open = False
if cont == True:
if self.win_open == False: # if window does not exist, create it
self.win = tk.Tk()
self.label()
self.win_open = True # as long as window is open, you can't create it again
I'm trying to figure out how the tkinter control flow works.
I want to display a rectangle and to make it blink three times. I wrote this code, but it doesn't work. I guess it's because blink is executed before mainloop, and it doesn't actually draw anything. If so, how can I swap the control flow between blink and mainloop to make it work?
My code:
from tkinter import *
from time import *
def blink(rectangle, canvas):
for i in range(3):
canvas.itemconfigure(rectangle, fill = "red")
sleep(1)
canvas.itemconfigure(rectangle, fill = "white")
sleep(1)
root = Tk()
fr = Frame(root)
fr.pack()
canv = Canvas(fr, height = 100, width = 100)
canv.pack()
rect = canv.create_rectangle(25, 25, 75, 75, fill = "white")
blink(rect, canv)
root.mainloop()
Event-driven programming requires a different mindset from procedural code. Your application is running in an infinite loop, pulling events off of a queue and processing them. To do animation, all you need to do is place items on that queue at an appropriate time.
Tkinter widgets have a method named after which lets you schedule functions to run after a certain period of time. The first step is to write a function that does one "frame" of your animation. In your case, you're defining animation as switching between two colors. A function that checks the current color, then switches to the other color is all you need:
def blink(rect, canvas):
current_color = canvas.itemcget(rect, "fill")
new_color = "red" if current_color == "white" else "white"
canvas.itemconfigure(rect, fill=new_color)
Now, we just need to have that function run three times at one second intervals:
root.after(1000, blink, rect, canv)
root.after(2000, blink, rect, canv)
root.after(3000, blink, rect, canv)
When you start your main loop, after one second the color will change, after another second it will change again, and after a third second it will change again.
That works for your very specific need, but that's not a very good general solution. A more general solution is to call blink once, and then have blink call itself again after some time period. blink then must be responsible to know when to stop blinking. You can set a flag or counter of some sort to keep track of how many times you've blinked. For example:
def blink(rect, canvas):
...
# call this function again in a second to
# blink forever. If you don't want to blink
# forever, use some sort of flag or computation
# to decide whether to call blink again
canvas.after(1000, blink, rect, canvas)
As a final bit of advice, I recommend that you define your program as a class, then create an instance of that class. This makes it so that you don't need global functions, and you don't need to pass around so many arguments. It doesn't really matter for a 20 line program, but it starts to matter when you want to write something substantial.
For example:
from tkinter import *
class MyApp(Tk):
def __init__(self):
Tk.__init__(self)
fr = Frame(self)
fr.pack()
self.canvas = Canvas(fr, height = 100, width = 100)
self.canvas.pack()
self.rect = self.canvas.create_rectangle(25, 25, 75, 75, fill = "white")
self.do_blink = False
start_button = Button(self, text="start blinking",
command=self.start_blinking)
stop_button = Button(self, text="stop blinking",
command=self.stop_blinking)
start_button.pack()
stop_button.pack()
def start_blinking(self):
self.do_blink = True
self.blink()
def stop_blinking(self):
self.do_blink = False
def blink(self):
if self.do_blink:
current_color = self.canvas.itemcget(self.rect, "fill")
new_color = "red" if current_color == "white" else "white"
self.canvas.itemconfigure(self.rect, fill=new_color)
self.after(1000, self.blink)
if __name__ == "__main__":
root = MyApp()
root.mainloop()
Each widget has an 'after' function - that is to say it can call a another function after a specified time period - So, what you would want to do is call:
root.after( 1000, blink )
If you want it to be a repeating call, just call 'after' again inside your blink function. The only problem you will have is passing arguments to blink - maybe look at using lamda inside of 'after' for that.
I'm currently working on a Python GUI version of Reversi for a programming class. I've already programmed the game logic, and I'm currently trying to implement the GUI using Tkinter. I'm having some problems with resizing the game board (root window) and everything on it (canvases and shapes) proportionally. The game currently works, but everything that I've tried to get the board to resize correctly hasn't worked. The relevant code is as follows.
class OthelloApplication:
def __init__(self, game_state: othello.Othello):
self._game_state = game_state
self._game_state.new_game()
self._game_board = game_state.show_board()
self._root_window = tkinter.Tk()
for row in range(self._game_state.show_rows()):
for col in range(self._game_state.show_cols()):
canvas = tkinter.Canvas(self._root_window, width = 100, height = 100,
borderwidth = 0, highlightthickness = 1,
background = _BACKGROUND_COLOR, highlightbackground = 'black')
canvas.grid(row = row, column = col, padx = 0, pady = 0,
sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
self._root_window.rowconfigure(row, weight = 1)
self._root_window.columnconfigure(col, weight = 1)
self._turn_window = tkinter.Canvas(self._root_window, width = 200,
height = 100, highlightthickness = 0, background = 'white')
self._root_window.bind('<Button-1>', self._on_canvas_clicked)
self._root_window.bind('<Configure>', self.on_resize)
def draw_game_pieces(self) -> None:
for row in range(self._game_state.show_rows()):
for col in range(self._game_state.show_cols()):
if self._game_board[col][row] == ' ':
pass
else:
canvas = tkinter.Canvas(master = self._root_window, width = 100, height = 100,
borderwidth = 0, highlightthickness = 1,
background = _BACKGROUND_COLOR, highlightbackground = 'black')
canvas.grid(row = row, column = col, padx = 0, pady = 0,
sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
canvas.update()
canvas.create_oval(2, 2, canvas.winfo_width() - 2,
canvas.winfo_height() - 2, fill = self._which_color(col,
row), outline = self._which_color(col, row))
self._root_window.rowconfigure(row, weight = 1)
self._root_window.columnconfigure(col, weight = 1)
def display_turn(self) -> None:
if self._game_state.show_turn() == 'B':
turn = 'black'
else:
turn = 'white'
self._turn_window.grid(row = self._game_state.show_rows() // 2 - 1,
column = self._game_state.show_cols() + 1,
sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
self._turn_info = self._turn_window.create_text(10, 10,
font = 'Helvetica', anchor = 'nw')
self._turn_window.itemconfig(self._turn_info, text = 'Turn = ' + turn)
def on_resize(self, event: tkinter.Event) -> None:
self.draw_game_pieces()
def _which_color(self, col: int, row: int) -> str:
if self._game_board[col][row] == 'B':
return 'black'
elif self._game_board[col][row] == 'W':
return 'white'
def _on_canvas_clicked(self, event: tkinter.Event) -> (int):
print(event.widget.winfo_reqwidth(), event.widget.winfo_reqheight())
print(event.widget.winfo_width(), event.widget.winfo_height())
try:
grid_info = event.widget.grid_info()
move = (int(grid_info["row"]), int(grid_info["column"]))
self._game_state.player_move(move[1], move[0])
self.draw_game_pieces()
self.display_turn()
except AttributeError:
pass
except othello.InvalidMoveError:
print('Error: that wasn\'t a valid move.')
except othello.GameOverError:
print('The game is over.')
def start(self) -> None:
self.draw_game_pieces()
self.display_turn()
self._root_window.mainloop()
The draw_game_pieces() method draws the appropriately colored circle in the correct space on the board based on the size of the canvas on which it is being drawn.
My solution to my resize problem was binding on_resize() to the '<Configure>' event in the init method, but that causes the program to crash in a cycle of recursions. I'm new to tkinter and GUIs in general. Why is the on_resize() method being bound to '<Configure>' causing the program to crash?
Sorry about the messy code, I'm very much still working on it.
Thanks.
The crash due to recursion is probably because your on_resize method creates new widgets. Here's what's happening:
the widget gets a event which ...
calls on_resize which ...
creates a nested loop which...
creates a single canvas, which ...
causes a configuration change in the widget
you call update inside the nested loop which ...
causes the event to be processed, which ...
calls on_resize, which...
creates a nested loop which ...
creates a canvas which ...
causes a configuration change in the widget
you call update inside the nested loop which ...
causes the event to be processed, which ...
calls on_resize, which ...
...
As a rule of thumb you should never call update unless you are absolutely certain you need to, and even then you need to stop and think hard about it. A rule that should almost never, ever be broken is to call update in a function that is called in response to an event. Every time you call update it's almost as if you're calling mainloop again -- it creates a nested event loop which tries to process all pending events. If the processing of pending events causes new events to be generated, you'll end up with the recursive situation you now find yourself in.
The first fix is to remove the call to update. The second fix is probably to not create new widgets in the handling of the configure event. You really only need to create all of those widgets once. The redraw event only needs to redraw the ovals, because the canvas objects will resize themselves automatically.
My advice is to step back and take it slower. Rewrite your program to just draw the board without worrying about the ovals or game logic. Get the resize behavior of the board working the way you want, and then worry about redrawing all of the ovals.
The following is my script. Basically, it will ask the user to input a number into the Entry box. Once the user enter a number and click OK, it will give you combination of Labels+Buttons depends on the number that user typed in to the Entry box.
from Tkinter import *
root=Tk()
sizex = 600
sizey = 400
posx = 0
posy = 0
root.wm_geometry("%dx%d+%d+%d" % (sizex, sizey, posx, posy))
def myClick():
myframe=Frame(root,width=400,height=300,bd=2,relief=GROOVE)
myframe.place(x=10,y=10)
x=myvalue.get()
value=int(x)
for i in range(value):
Mylabel=Label(myframe,text=" mytext "+str(i)).place(x=10,y=10+(30*i))
Button(myframe,text="Accept").place(x=70,y=10+(30*i))
mybutton=Button(root,text="OK",command=myClick)
mybutton.place(x=420,y=10)
myvalue=Entry(root)
myvalue.place(x=450,y=10)
root.mainloop()
Normally, when i create a label widget, i would do something like this
mylabel=Label(root,text='mylabel')
mylabel.pack()
So when i want to change the text of my label later on i can just simply do this
mylabel.config(text='new text')
But now, since i am using for loop to create all labels at once, is there anyway to address the individual labels after the labels has been created?
For example, the user typed in '5' into the entry box and the program will give me 5 lables + 5 buttons. Is there anyway for me to change the properties (ie, label.config(..)) of the individual labels?
Sure! Just make a list of labels, call place on each one, and then you can reference them later and change their values. Like so:
from Tkinter import *
root=Tk()
sizex = 600
sizey = 400
posx = 0
posy = 0
root.wm_geometry("%dx%d+%d+%d" % (sizex, sizey, posx, posy))
labels = []
def myClick():
del labels[:] # remove any previous labels from if the callback was called before
myframe=Frame(root,width=400,height=300,bd=2,relief=GROOVE)
myframe.place(x=10,y=10)
x=myvalue.get()
value=int(x)
for i in range(value):
labels.append(Label(myframe,text=" mytext "+str(i)))
labels[i].place(x=10,y=10+(30*i))
Button(myframe,text="Accept").place(x=70,y=10+(30*i))
def myClick2():
if len(labels) > 0:
labels[0].config(text="Click2!")
if len(labels) > 1:
labels[1].config(text="Click2!!")
mybutton=Button(root,text="OK",command=myClick)
mybutton.place(x=420,y=10)
mybutton2=Button(root,text="Change",command=myClick2)
mybutton2.place(x=420,y=80)
myvalue=Entry(root)
myvalue.place(x=450,y=10)
root.mainloop()
Also note! In the assignment Mylabel=Label(myframe,text=" mytext "+str(i)).place(x=10,y=10+(30*i)) in the original code, that call sets Mylabel to None, since the place method returns None. You want to separate the place call into its own line, like in the code above.