Here is my program as of yet:
from tkinter import *
from collections import deque
class App():
def __init__(self, *images):
self.root = Tk()
self.root.title("Skin")
self.image_dict = {image: PhotoImage(file=image) for image in images}
self.image_queue = deque(images)
b = Button(self.root, text="Click here to see the diagram!", command=self.change_image)
b.pack(fill=X)
self.label = Label(self.root, image=self.image_dict["1.gif"])
self.label.image = self.image_dict["1.gif"]
self.label.pack()
def change_image(self):
self.image_queue.rotate(-1)
next_image = self.image_queue[0]
self.label.configure(image=self.image_dict[next_image])
self.label.image = self.image_dict[next_image]
if __name__ == "__main__":
app = App('1.gif', '2.gif')
app.root.mainloop()
What this does is when you run the scipt, a window comes up diplaying "1.gif", and a button. When you click the button, "1.gif" changes to "2.gif". "1.gif" is a blank diagram, "2.gif" is a diagram with labels showing what each part of the diagram is.
Now for the next stage of my program, I need some way to add multiple invisible buttons, or something like it, over each word on the diagram on "2.gif", and when you click on it, I need a seperate window to come up with text on it. Is there any way to implement that into my current program? I have no idea where to start. Thank you!
I think you will be better off using a Canvas to hold your image(s) rather
than a Label. You can then place 'hotspots' over the diagram and bind
events to them. eg. something like:
from tkinter import *
class App():
def __init__(self):
self.root = Tk()
self.messages = {}
self.canvas = Canvas(self.root, bd=0, highlightthickness=0)
self.image = PhotoImage(file='2.gif')
self.canvas.create_image(0,0, image=self.image, anchor='nw')
w,h = self.image.width(), self.image.height()
self.canvas.config(width=w, height=h)
self.canvas.pack()
self.balloon = b = Toplevel(self.root)
b.withdraw()
b.wm_overrideredirect(1)
self.msg = Label(b, bg='black', fg='yellow', bd=2, text='')
self.msg.pack()
self.set_up_hotspots()
def set_up_hotspots(self):
box1 = self.canvas.create_polygon(50,100,100,100,100,150,50,150,
outline='blue', fill='')
#note: outline can be '' also ;)
self.messages[box1] = "The machine head consists of a "\
"Flynn mechanism,\na Demmel crank, "\
"and a heavy-duty frobulator. "
box2 = self.canvas.create_polygon(150,100,200,100,200,150,150,150,
outline='red', fill='')
self.messages[box2] = "And now for something completely different"
for item in [box1, box2]:
for event, handler in [('<Enter>', self.on_hotspot_enter),
('<Leave>', self.on_hotspot_leave)]:
self.canvas.tag_bind(item, event, handler)
def on_hotspot_enter(self, event):
if not self.balloon.winfo_ismapped():
txt = self.messages[event.widget.find_withtag('current')[0]]
self.msg.config(text=txt)
x,y = event.x_root, event.y_root
self.balloon.geometry('+%d+%d' %(x+16,y))
self.balloon.deiconify()
def on_hotspot_leave(self, event):
if self.balloon.winfo_ismapped():
self.balloon.withdraw()
if __name__ == "__main__":
app = App()
app.root.mainloop()
Related
Is it possible to somehow highlight a word in my program (one and the other with the same) in a different color. I thought about making a canvas in a canvas, but it didn’t work out.
class Application(object):
def __init__(self):
root = tk.Tk()
self.canvas = tk.Canvas(root,bg='black')
self.canvas.pack()
self.canvas_text = self.canvas.create_text(180, 130, text='',anchor=tk.NW,fill="white",font=('Arial',18))
self.entry = tk.Entry(root)
self.entry.bind("<Return>", self.entry_cb)
self.entry.pack()
self.entry.focus()
root.mainloop()
def animate_text(self, text, delta):
delay = 0
for i in range(len(text)+1):
update_text = lambda s=text[i - 1:-1]: self.canvas.itemconfigure(self.canvas_text, text=s)
self.canvas.after(delay, update_text)
delay += delta
def entry_cb(self, event):
# s = self.entry.get().split()
s = 'London is the capital of Great Britain'.split()
s += [s[-1]]
self.animate_text(s, skorost)
skorost = 400
app = Application()
I'm using Python and tkinter to create a GUI. I've created two classes, App and drawBall. The App class inherits from Tk.Frame. I'm having trouble creating a drawBall object from within App.
I'd appreciate any other feedback about my code as well, I'm fairly new to OOP.
After creating the class App, which inherits from Tk.Frame. I'd like to create another class to draw a ball on the screen (using a canvas). I've created a base GUI, but when trying to call the class drawBall, I receive the following error: 'drawBall' object has no attribute 'canvas'.
class App(tk.Frame):
def __init__(self,master):
super().__init__(master)
#create title and size for the window
self.master.geometry("640x360")
self.canvas = tk.Canvas(self.master,relief = 'raised',borderwidth = 1)
self.canvas.grid(row = 0,column = 0,sticky = 'NW')
#create a startSimulation button, place it in the bottom right corner
self.startButton = tk.Button(self.master,text = 'Start',command = self.startCallback)
self.startButton.grid(row = 2,column = 3)
#create a quit button, place it in the bottom right corner
self.quitButton = tk.Button(self.master,text = "Quit",command = self.master.destroy)
self.quitButton.grid(row = 3, column =3)
#callback for start button click
def startCallback(self):
#### this is where the error occurs #####
self.ball1 = drawBall(self.master,self.canvas)
class drawBall():
def __init__(self,master,canvas):
self.canvas.create_oval(25,75,35,85,fill = 'blue')
def moveBall(self):
deltaX = 1
self.canvas.move(self.seed,deltaX,0)
self.canvas.after(50,self.moveBall)
if __name__ == '__main__':
window = tk.Tk()
simulate = App(window)
window.mainloop()
I'd hope that the call "self.ball1 = drawBall(self.master,self.canvas)" would result in the circle being drawn on the screen.
You need a class Ball, that will take a canvas, and has the ability to move itself.
Then, in the App, you create a collection of balls, and order them to move.
something like this:
import tkinter as tk
class App(tk.Frame):
def __init__(self, master):
super().__init__(master)
self.master.geometry("640x360")
self.canvas = tk.Canvas(self.master, relief='raised', borderwidth=1)
self.canvas.grid(row=0, column=0, sticky='NW')
self.startButton = tk.Button(self.master, text='animate', command=self.launch_animation)
self.startButton.grid(row=2, column=3)
self.stopButton = tk.Button(self.master, text='stop', command=self.stop_animation)
self.stopButton.grid(row=3, column=3)
self.quitButton = tk.Button(self.master, text="Quit",command=self.master.destroy)
self.quitButton.grid(row=4, column=3)
self.balls = [Ball(self.canvas)]
self.anim_is_on = False
def stop_animation(self):
self.anim_is_on = False
def launch_animation(self):
if self.anim_is_on: # prevent launching several overlapping animation cycles
return
self.anim_is_on = True
self.animate()
def animate(self):
if not self.anim_is_on:
return
for ball in self.balls:
ball.moveball()
self.after(100, self.animate)
class Ball():
def __init__(self, canvas):
self.canvas = canvas
self.id = self.canvas.create_oval(25, 75, 35, 85, fill='blue')
def moveball(self):
delta_x = 1
self.canvas.move(self.id, delta_x, 0)
window = tk.Tk()
simulate = App(window)
window.mainloop()
I want to have two tkinter windows. A button should be in the first window, and a reaction text should be in the second window.
My questions:
Must the second window have no modal?
How do I make the second window movable?
How can I give information to second window via callback function?
Thanks in advance for answers and advice!
Here is some code that may help you:
from tkinter import *
class App:
def __init__(self):
self.window1 = Tk()
self.window2 = Toplevel()
self.button = Button(self.window1, bd = 5, text = "Click Me!", command = self.update)
self.button.pack()
self.label = Label(self.window2, bd = 5, text = "Button has not been clicked.")
self.label.pack()
def update(self):
self.label.config(text = "Button has been clicked!")
self.window2.update()
app = App()
Explanation:
The first line imports tkinter
In the next line, we create a class. At the bottom of the code, we create an object using that class. This is useful because when the object is created, the functions in the class are already defined, so the function definition can be after when it is called.
After we declare our class, in __init__, we write code that will run when an object is created from that class. The code creates two windows. One contains a button, and the other one contains a label. The button has a command parameter to run the class function, update.
In update, we change the label text and update the window.
I have not next questions. My problems solution is here:
import tkinter as tk
class ViewOnMoon(tk.Toplevel):
def __init__(self, parent = None, draw = None):
tk.Toplevel.__init__(self, parent)
self.transient(parent)
self.title('View')
self.minsize(height = 300, width = 300)
fr_canvas = tk.Frame(self)
fr_canvas.place(relx=0.23, rely=0.01, anchor="nw")
self.canv_w = 200
self.canv_h = 200
self.canvas = tk.Canvas(fr_canvas, bg='white', width = self.canv_w, height=self.canv_h)
self.canvas.grid(column = 0, row = 0)
return
class GuiMoonMove(tk.Frame):
def __init__(self, master):
mon_h = 600
mon_w = 1250
tk.Frame.__init__(self, master)
self.frame = tk.Frame(master, width=1000, height=200, bd=2)
self.master.title('Move')
self.master.minsize(height = mon_h, width = mon_w)
fr_canvas = tk.Frame(self.master)
fr_canvas.place(relx=0.23, rely=0.01, anchor="nw")
fr_button = tk.Frame(self.master)
fr_button.place(relx=0.02, rely=0.06, anchor="nw")
self.canv_h = 600
self.canv_w = 950
self.lbl_view = tk.BooleanVar()
chb_view_on_moon = tk.Checkbutton(fr_button, text="Pohled na Měsíc", variable = self.lbl_view, \
onvalue=True, offvalue=False,command = self.callback)
chb_view_on_moon.grid(column= 0, row= 4,pady = 10)
self.canvas = tk.Canvas(fr_canvas, bg='white', width = self.canv_w, height=self.canv_h)
self.canvas.grid(column = 0, row = 0)
def callback(self,*args):
if self.lbl_view.get()==True:
self.view_on_moon = ViewOnMoon(parent = self.master)
else:
self.vom.destroy()
if __name__=="__main__":
root = tk.Tk()
app = GuiMoonMove(master = root)
app.mainloop()
I made this tkinter application, and I have a bug where the layout changes and the button at the bottom disappears.
I am unable to reproduce the bug 100%. It happens at random times.
For this reason, this SSCCE may contain more than it needs. It's still a lot less than my original app.
from random import random
from threading import Thread, Lock
from time import sleep
from tkinter import Tk, LEFT, RIGHT, BOTH, X, Y, Listbox, Scrollbar, VERTICAL, END
from tkinter.ttk import Frame, Button, Style, Entry
class ListManager:
def __init__(self, ui):
self.entry_list = []
self.ui = ui # to notify of list update
self.timer = ListManager.interval + 1
self.stop = False
interval = 1 # between updates
#staticmethod
def api_request() -> dict:
new_list = ["line"]
while random() > .4:
new_list.append("line")
return {"data": new_list}
def get_entries(self):
r = self.api_request()
self.entry_list = []
for line in r["data"]:
self.entry_list.append(line)
def run(self):
self.timer = ListManager.interval + 1
while not self.stop:
if self.timer > ListManager.interval:
self.get_entries()
if self.ui is None:
print("entries:", len(self.entry_list))
for entry in self.entry_list:
print(entry)
else:
self.ui.receive(self.entry_list)
self.timer = 0
else:
self.timer += 1
sleep(1)
class UI(Frame):
def __init__(self):
super().__init__()
self.style = Style()
self.listbox = None
self.name_entry = None
self.entries = []
self.mutex = Lock()
self.init_pack()
def init_pack(self):
# TODO: investigate open button disappearing (probably need to repack in receive function)
self.master.title("list")
self.style.theme_use("default")
add_streamer_frame = Frame(self)
add_button = Button(add_streamer_frame, text="Add Line")
add_button.pack(side=RIGHT, padx=5, pady=5)
self.name_entry = Entry(add_streamer_frame)
self.name_entry.pack(fill=X, padx=5, expand=True)
add_streamer_frame.pack(fill=X)
list_frame = Frame(self)
self.listbox = Listbox(list_frame, height=20)
self.listbox.configure(font=("Courier New", 12, "bold"))
scrollbar = Scrollbar(self.listbox, orient=VERTICAL)
self.listbox.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=self.listbox.yview)
scrollbar.pack(side=RIGHT, fill=Y)
self.listbox.pack(fill=BOTH, padx=5, expand=True)
list_frame.pack(fill=BOTH, expand=True)
self.pack(fill=BOTH, expand=True)
open_button = Button(self, text="Open")
open_button.pack(side=LEFT, padx=5, pady=5)
def receive(self, entries):
self.mutex.acquire()
self.listbox.delete(0, END)
self.entries = []
for entry in entries:
self.listbox.insert(END, entry)
self.entries.append(entry.split(" ")[0])
self.mutex.release()
def main():
root = Tk()
root.geometry("800x400+300+300")
app = UI()
tl = ListManager(app)
thread = Thread(target=tl.run)
thread.start()
root.mainloop()
tl.stop = True
thread.join()
if __name__ == "__main__":
main()
Here's what it normally looks like most of the time, and should look like:
Here's what it looks like after the bug:
Add:
self.ui.update_idletasks()
after:
sleep(1)
or:
Replace:
self.listbox = Listbox(list_frame, height=20)
with:
self.listbox = Listbox(list_frame)
# or self.listbox = Listbox(list_frame, height=17)
It must be that when too much is going on there's no time spent on updating the GUI. I couldn't reproduce the 2nd screenshot directly without lowering sleep argument as low as 0.001.
The root of your problem seems to be that each time something's added into the listbox its height(winfo_height which is in pixels) is recalculated and resized down from 20 character height, to 17, for which there isn't always time for before a new insertion.
Basically, your widget does not necessarily has a fixed 20 character height at all times, it resizes based on its content and other widgets, unless propagation is disabled by calling self.listbox.pack_propagate(False).
Add:
print(self.ui.listbox.winfo_height())
after:
sleep(1)
to see its height changing for yourself.
I am new to python so I was trying to make a GUI, in that I have to place a button in a particular position.
I tried using self.nxt_form.place(x=200,y=100) instead of self.nxt_form.pack().
But the button disappeared and only the frame appeared when it ran. Can you tell me how to place the button in a particular position?
Here is the code:
import tkinter as tk
class Main_form:
def __init__(self, root,title="Simulated MTBF"):
self.root = root
self.frame = tk.Frame(self.root)
"""Button nxt_form which moves to next form"""
self.nxt_form = tk.Button(self.frame, text = 'Next Form', width = 25,command = self.new_window)
self.nxt_form.pack()
self.frame.pack()
"""command to open new window by clicking Button """
def new_window(self):
self.newWindow = tk.Toplevel(self.root)
self.app = Demo2(self.newWindow)
class Demo2:
def __init__(self, root):
self.root = root
self.frame = tk.Frame(self.root)
self.quitButton = tk.Button(self.frame, text = 'Quit', width = 25, command = self.close_windows)
self.quitButton.pack()
self.frame.pack()
def close_windows(self):
self.root.destroy()
def main():
root = tk.Tk()
app = Main_form(root)
root.mainloop()
if __name__ == '__main__':
main()
when i am using tkinter i used column and row to position objects
self.btn = tk.Button(self, text = "button")
self.btn.grid(row = 1, column = 1)
EDIT - expanded on information in response to comment (below)
I would make an label and change its width and height to make the spacing you need (note im a beginer at python as well so this is probly a bad way but it works)
from tkinter import *
import tkinter as tk
from tkinter.ttk import Combobox,Treeview,Scrollbar
class MainMenu(Frame):
def __init__(self, master):
""" Initialize the frame. """
super(MainMenu, self).__init__(master)
self.grid()
self.create_GUI()
def create_GUI(self):
frame1 = tk.LabelFrame(self, text="frame1", width=300, height=130, bd=5)
frame1.grid(row=0, column=0, columnspan=3, padx=8)
#the frame is not needed but it is a good thing to use as can group
#parts of your interface together
self.text1 = Entry(frame1)
#note if you were not using frames would just put self here
self.text1.grid(row = 1, column = 0)
self.text2 = Label(frame1, text = "",height = 10)
self.text2.grid(row = 2 , column = 0)
self.text3 = Entry(frame1)
self.text3.grid(row = 3, column = 0)
root = Tk()
root.title("hi")
root.geometry("500x500")
root.configure(bg="white")
app = MainMenu(root)
root.mainloop()
Also note that you can not use pack and grid together what you could do is group your objects in different frames then use grid in one frame and pack in a different frame. I personally prefer to use grid to pack as it gives you more control over your object then pack does