I'm trying to build a binary lamp in Python using Tkinter.
The steps are as follows:
1. Prompt user for input, using the console/shell.
2. Draw the lamp
3. Use mainloop to show the image
However, this doesn't always work as I expect it to.
The code:
from tkinter import *
HEIGHT = 235
WIDTH = 385
tk = Tk()
canvas = Canvas(tk, width=WIDTH, height=HEIGHT, bg="grey")
size = 35
canvas.pack()
def dec_to_binh(num, lst):
if num>1:
dec_to_binh(num // 2, lst)
lst.append(num%2)
return lst
def dec_to_bin(num):
answ = dec_to_binh(num, [])
while len(answ) < 4:
answ.insert(0,0)
return answ
class Diode:
def __init__(self, x, y, label):
self.shape = canvas.create_oval(x, y, x+size, y+size, width=4,
outline='black', fill='#232323')
self.label = canvas.create_text(x+int(size/2), y+size+10, text=str(label))
self.onOff = 0
def update(self, num):
if int(num) == 1:
canvas.itemconfig(self.shape, fill=oncolor)
onOff = 1
else:
canvas.itemconfig(self.shape, fill='black')
onOff = 0
class Lamp:
def __init__(self):
self.LEDs = [Diode(100, 100, 8), Diode(150, 100, 4), Diode(200, 100, 2), Diode(250, 100, 1)]
def update(self, number):
n=0
for i in self.LEDs:
i.update(dec_to_bin(number)[n])
n=n+1
print("What to show as a binary lamp?")
answer=int(input())
while answer > 15 or answer < 0:
print("Invalid input, can only show between range of 0 to 15 inclusive")
answer=int(input())
lamp = Lamp()
lamp.update(answer)
tk.mainloop()
The bug:
When I run this in my IDE (Wing 101), it patiently waits for me to input the number I want to show before drawing the image.
However, if I run it straight from the python shell (i.e. I just click on bin_lamp.py) it opens and creates the tk window and shows a blank screen, without waiting for me to tell it what number I want.
After I actually enter the number, it draws what I want it to, but is there any way I can stop the blank tk window with the blank canvas from actually showing up until I'm done entering the input?
You can either put the input before you create the window with tk = Tk(), or you can leave everything as is, but hide the window and show it once it's done. Simply hide the window after it has been created and show it afterwards:
tk = Tk()
tk.withdraw()
# do all your processing
tk.deiconify()
Related
Why does the time.sleep() work before the window of tkinter opens?
Code:
import tkinter
import time
window = tkinter.Tk()
window.geometry("500x500")
window.title("Holst")
holst = tkinter.Canvas(window, width = 450, height = 450, bg = "white")
holst.place(x = 25, y = 25)
x = 30
y = 50
d = 30
circle = holst.create_oval(x, y, x+d, y+d, fill = "red")
time.sleep(2)
holst.move(circle, 50, 40)
You asked that why is time.sleep() is called before the windows loads
because you hopefully called the window.mainloop() at the last of the code which loads the window and keep maintain the tkinter window
The time.sleep() function code executes before the window.mainloop() function so it was stop the execution and sleeps before the window could load
A nice approach will be to call the time.sleep() in a if statement.
The Tk instance requires you to run it's mainloop function in order to take control of the main process thread.
Your code is calling time.sleep() on the main process thread, which blocks the GUI from doing anything. If you want to be able to do things with the GUI while waiting (such as drawing the window, moving it around, or drawing other things to it) then you would need to extend Tk to have the UI handle the callback using self.after()
Here's a simple example of how you would extend the Tk class to achieve what you want.
import tkinter
class TkInstance(tkinter.Tk):
def __init__(self):
tkinter.Tk.__init__(self)
#Set up the UI here
self.canvas = tkinter.Canvas(self, width = 450, height = 450, bg = "white")
self.canvas.place(x = 25, y = 25) #Draw the canvas widget
#Tell the UI to call the MoveCircle function after 2 seconds
self.after(2000, self.MoveCircle) #in ms
def MoveCircle(self):
x = 30
y = 50
d = 30
circle = self.canvas.create_oval(x, y, x+d, y+d, fill = "red")
self.canvas.move(circle, 50, 40) #Draw the circle
#Main entrance point
if __name__ == "__main__": #good practice with tkinter to check this
instance = TkInstance()
instance.mainloop()
I have functioning code but there are a few things which I would like to change about it but don't know how to so thought i'd ask here. My code is as follows:
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
#Define the target, source and output arrays. Source has to be completely white otherwise it kills everything
def initialize(x,y):
xarr = np.zeros(x)
yarr = np.zeros(y)
target = np.meshgrid(xarr,yarr)
target = target[0]
source = np.meshgrid(xarr,yarr)
source = source[0]
output = np.meshgrid(xarr,yarr)
output = output[0]
for i in range(x):
for n in range(y):
source[n][i] = 1
return target, source, output
# creates trap between XTrapMin-XTrapMax and YTrapMin-YTrapMax on Array
def trap(xtmi,xtma,xs,ytmi,ytma,ys,array):
for i in range(xs):
if xtmi < i < xtma:
for n in range(ys):
if ytmi < n < ytma:
array[n][i] = 255
return
#Returns the amplitude of a complex number
def Amplitude(x):
if isinstance(x, complex):
return np.sqrt(x.real**2+x.imag**2)
else:
return np.abs(x)
#Returns the phase of a complex number
def Phase(z):
return np.angle(z)
#Main GS algorithm implementation using numpy FFT package
#performs the GS algorithm to obtain a phase distribution for the plane, Source
#such that its Fourier transform would have the amplitude distribution of the plane, Target.
def GS(target,source):
A = np.fft.ifft2(target)
for i in range(5):
B = Amplitude(source) * np.exp(1j * Phase(A))
C = np.fft.fft2(B)
D = Amplitude(target) * np.exp(1j * Phase(C))
A = np.fft.ifft2(D)
output = Phase(A)
return output
#Make array into PIL Image
def mkPIL(array):
im = Image.fromarray(np.uint8(array))
return im
def up():
global ytmi
global ytma
ytmi -= 10
ytma -= 10
return
def down():
global ytmi
global ytma
ytmi += 10
ytma += 10
return
def right():
global xtmi
global xtma
xtmi += 10
xtma += 10
return
def left():
global xtmi
global xtma
xtmi -= 10
xtma -= 10
return
xtmi = 125
xtma = 130
xs = 1024
ytmi = 0
ytma = 5
ys = 768
root = tk.Tk()
root.attributes('-fullscreen', True)
def main():
app = Lower(root)
root.mainloop()
class Lower:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(self.master).pack()
self.displayimg = tk.Button(self.frame, text = 'Display', width = 25, command = self.plot)
self.displayimg.pack()
self.makewidg()
def makewidg(self):
self.fig = plt.figure(figsize=(100,100), frameon=False) #changing figsize doesnt cange the size of the plot display
self.fig.subplots_adjust(left=0, right=1, top=1, bottom=0)
self.fig.tight_layout()
self.ax = self.fig.add_subplot(111)
self.ax.set_yticklabels([])
self.ax.set_xticklabels([])
self.canvas = FigureCanvasTkAgg(self.fig, master=self.master)
self.canvas.get_tk_widget().pack(expand=True)
self.canvas.figure.tight_layout()
self.canvas.draw()
self.new_window()
def new_window(self):
self.newWindow = tk.Toplevel()
self.app = Display(self.newWindow)
def plot(self):
global xtmi, xtma, xs, ytmi, ytma, ys, i
target,source,output=initialize(xs,ys)
trap(xtmi,xtma,xs,ytmi,ytma,ys,target)
output = GS(target,source)
self.ax.imshow(output, cmap='gray')
self.ax.set_yticklabels([])
self.ax.set_xticklabels([])
self.canvas.draw()
self.ax.clear()
def kill(self):
root.destroy()
class Display:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(self.master)
self.frame.pack()
self.up = tk.Button(self.frame, text = 'Up', width = 25, command = up)
self.up.pack()
self.down = tk.Button(self.frame, text = 'Down', width = 25, command = down)
self.down.pack()
self.right = tk.Button(self.frame, text = 'Right', width = 25, command = right)
self.right.pack()
self.left = tk.Button(self.frame, text = 'Left', width = 25, command = left)
self.left.pack()
self.kill = tk.Button(self.frame, text = 'Kill', width = 25, command = self.kill)
self.kill.pack()
def kill(self):
root.destroy()
main()
Currently the button displayimg from the class Lower is displayed above the image, is there a way in which I can have the display button on the Display class and still have it manipulate the image on the Lower screen? Also, I intend to display the window opened by Lower on a separate monitor, but can't drag it seeing as it is fullscreen, is there a way around that I can get it on my second monitor?
I try that as such:
self.displayimg = tk.Button(self.top, text = 'Display', width = 25, command = Lower.plot(Lower))
self.displayimg.pack()
But this causes a misreference I think as I get an error code
AttributeError: type object 'Lower' has no attribute 'ax'
Call to Lower.plot
You are using Lower.plot as your button command. It needs one argument, self which must be an instance of Lower - so Lower.plot(Lower) is passing a class where an instance is expected. Instead you need to use the app instance you've made, and call app.plot(). The arguement self is automatically the instance itself, this is fundamental to OOP in python. Calling the method on an instance passes self as the first arg, so it's missing from the call. Calling Lower.plot(...) is calling the method on the class Lower, so there is no instance, and you have to supply your own. I'd avoid calling methods without an instance like this in this situation, and use your app instance.
Command for the display button
Your button creation becomes something like:
self.displayimg = tk.Button(self.top, text = 'Display', width = 25, command = app.plot)
If you need to pass additional args to plot, you need to delay the function call so it happens on click, not on creation of the button. You can use lambda : app.plot("red", 10, whatever) to make a nameless function, taking no arguments, that when called will go on to call app.plot with the given args.
Positioning the window
You can control the position of the app window using wm_geometry:
app.wm_geometry("200x200+100+500")
Will cause the app window to be 200px by 200px, positioned 100px left and 500px down from the origin, and on a windows machine, this is the top left corner of your primary monitor. You can keep the width and height the same and just move the window with eg
app.wm_geometry("+100+500")
You can use more scripting to build the string +{xpos}+{ypos} with whichever values you like to match your desktop layout.
I am new to tkinter and trying to make a basic drawing app. However, when I move my cursor around, it will sometimes suddenly stop drawing, and then only show the finished line several seconds later. This is shown here.
https://www.youtube.com/edit?video_referrer=watch&video_id=_g8n55V6qPQ
Is this lag? My laptop runs fine otherwise, and it can even 'lag' on the first go (i.e. before there are any other objects on the canvas). If it is just lag, what sort of workarounds do I have in making my python drawing app?
This is my code:
from tkinter import *
root = Tk()
root.title("Note Taking")
can_width = 800
can_height = 800
canvas = Canvas(root, width=can_width, height=can_height, bg='white')
canvas.pack(padx=20, pady=20)
class g():
points = []
user_line = None
drawing = False
t = 0
def leftClick(event):
g.points = []
g.user_line = None
g.drawing = True
g.points.append(event.x)
g.points.append(event.y)
def leftMove(event):
# Print out an increasing number: t, so I can see it in the output
print(g.t)
g.t+=1
if g.drawing:
g.points.append(event.x)
g.points.append(event.y)
if g.user_line == None:
g.user_line = canvas.create_line(g.points, width=4, smooth=1)
else:
canvas.coords(g.user_line, g.points)
def leftRelease(event):
g.points = []
g.user_line = None
canvas.bind('<Button-1>', leftClick)
canvas.bind('<B1-Motion>', leftMove)
canvas.bind('<ButtonRelease-1>', leftRelease)
root.mainloop()
I'm trying to make this really simple program, all it does is store the current x/y pos of the mouse on the canvas and then use them to draw a line when you click for the second time. I've already bound it and I'm not getting any errors, it seems like it's not even being activated. Any help is greatly appreciated
from tkinter import *
main = Tk()
c = Canvas(main, width=600, height=600)
c.pack()
#For colored lines
presses = 0
def click(event):
if presses == 0:
initX = int(c.canvasx(event.x))
initY = int(c.canvasy(event.y))
presses == 1
elif presses == 1:
c.create_line(initX, initY,
int(c.canvasx(event.x)),
int(c.canvasy(event.y)))
presses == 0
c.bind("<Button-1>", click)
mainloop()
How does something like this work for you?
from tkinter import *
main = Tk()
c = Canvas(main, width=600, height=600)
c.pack()
line = []
def click(event):
global line
X = int(c.canvasx(event.x))
Y = int(c.canvasy(event.y))
line.append((X,Y))
if len(line) > 1:
startX,startY = line[-2]
c.create_line(startX, startY, X, Y)
c.bind("<Button-1>", click)
mainloop()
I've changed around your code a bit to store a list of the X,Y coordinates that have been clicked on. If more than 1 point on the screen has been clicked, it will draw a line between the current point clicked on and the last point clicked on.
Reason your code wasn't working was that initX and initY are forgotten in between calls on the the click function. Adding them to a list solves this.
I made this program, I recommend run it first. The main idea is popping window, it should pop after clicking on text info. This works correctly, than, an user needs to close this window, so I made red closing button (rectangle with lines) in top right corner. After clicking on this button, new window should disappear, but first window with ovals should stay.
I need function for closing this window, I was trying to find solution a long time, but I am not able to. Any idea please?
import tkinter
class Desktop:
def __init__(self):
self.canvas = tkinter.Canvas()
self.canvas.pack()
x, y = 25,25
for i in range(1,61):
self.canvas.create_oval(x-10, y-10, x+10, y+10, fill='blue')
self.canvas.create_text(x, y, text=i)
x += 30
if i%10==0:
x = 25
y += 40
self.canvas.create_text(340, 150, text='info', font='arial 17', tags='info')
self.canvas.tag_bind('info', '<Button-1>', self.window)
def window(self, event):
self.canvas.create_rectangle(40,40,310,220,fill='white')
x, y = 75,100
for i in range(1,9):
self.canvas.create_rectangle(x-30,y-30,x+30,y+30)
self.canvas.create_text(x,y,text='number ' + str(i), font='arial 9')
x += 65
if i == 4:
x = 75
y += 70
self.rec = self.canvas.create_rectangle(310,40,290,60, fill="red", tags="cancel")
self.line = self.canvas.create_line(310,40,290,60,310,60,290,40, tags="cancel")
self.canvas.tag_bind('cancel', '<Button-1>', self.close)
def close(self,event):
#?
print('should close this part of screen')
d = Desktop()
What I use in my code is:
def quit(self):
root.destroy()
self.quit()
This might be a really long winded way to do it but it works for me every time.
EDIT: Just thinking about it now, depending on the way you open a new frame,root.destroy would be the way I would do it.