I'm working on a checkers game GUI, and i can't seem to figure out why it keeps opening two windows instead of the one i initialize.
Here's my code: https://pastebin.com/s7XWLdfY
And here's a screenshot:link
Any help would be really appreciated, i'm fairly new to tkinter.
def init():
global root
global extraref #creates a reference to the pieces images
#so they don't get garbage collected
extraref = np.array([tk.Label() for item in range(64)]).reshape(8, 8)
def switch(board, val):
switcher = {
Board.E: "imgs/E.gif",
Board.W: "imgs/W.gif",
Board.B: "imgs/B.gif",
Board.DW: "imgs/DW.gif",
Board.DB: "imgs/DB.gif",
}
return switcher.get(val)
def refresh(board): #refreshes the board after a turn
mat = board.getMat()
#print(mat)
for i in range(8):
for j in range(8):
path = switch(board, mat[i][j])
#path = "imgs/B.gif"
tmp_img = Image.open(path)
img = itk.PhotoImage(tmp_img, master=root)
extraref[i][j] = tk.Label(root)
extraref[i][j].img = img
extraref[i][j].config(image = extraref[i][j].img)
butts[i*8+j].config(image = extraref[i][j].img)
extraref[i][j].pack()
init()
root = tk.Tk()
frame = tk.Frame(root, width=800, height=800, background="white")
frame.pack_propagate(0)
frame.pack()
lab = tk.Label(frame)
lab.pack()
butts = list()
for i in range (8):
for j in range (8):
lab.grid(row=i, column=j)
butt = tk.Button(lab, bg=("black" if i%2 != j%2 else "white"))
#butt.config(height=5, width=8)
#butt.bind("<Enter>", on_enter)
#butt.bind("<Leave>", on_leave)
butts.append(butt)
butt.grid(row=i, column=j) #creating button grid
board = Board()
refresh(board) #places pieces on the grid
root.mainloop()
P.S: for the complete code please check pastebin, i had to cut some functions to be able to post it
You are calling tk.Label() inside init() function which will create the first window. Then root = tk.Tk() will create the second window.
You should call init() after root = tk.Tk():
root = tk.Tk()
init()
frame = ...
Related
I am trying to create a battlemap for dnd (picture) with adjustable grid and movable enemy/creature tokens. The idea is to drag one of the token from the right onto the map on the left.
The window is made of 3 frames. The frame for the map, the frame for the "new map" button and slider. And then frame for the tokens, which are buttons tiled using button.grid()
I found a drag and drop system here that I'm using to drag the tokens. However, when I bring them over the map, they go behind it and you can't see them (I know they go behind because they can be partially visible between the two frames). Is there any way to bring them to the front?
import tkinter as tk
class DragManager():
def add_dragable(self, widget):
widget.bind("<ButtonPress-1>", self.on_start)
widget.bind("<B1-Motion>", self.on_drag)
widget.bind("<ButtonRelease-1>", self.on_drop)
widget.configure(cursor="hand1")
def on_start(self, event):
# you could use this method to create a floating window
# that represents what is being dragged.
pass
def on_drag(self, event):
# you could use this method to move a floating window that
# represents what you're dragging
event.widget.place(x=event.x_root + event.x, y= event.y_root + event.y)
#when button is dropped, create a new one where this one originally was
def on_drop(self, event):
# find the widget under the cursor
x,y = event.widget.winfo_pointerxy()
target = event.widget.winfo_containing(x,y)
try:
target.configure(image=event.widget.cget("image"))
except:
pass
if x > window.winfo_screenwidth() - 200:
del event.widget
return
if not event.widget.pure:
return
button = tk.Button(master=entity_select_frame, text = "dragable", borderwidth=1, compound="top")
#avoiding garbage collection
button.gridx = event.widget.gridx
button.gridy = event.widget.gridy
button.grid(row = event.widget.gridx, column = event.widget.gridy)
button.grid()
button.pure = True
dnd.add_dragable(button)
window = tk.Tk()
window.geometry("1000x800")
map_frame = tk.Frame()
controls_frame = tk.Frame(width=200, borderwidth=1, relief=tk.RAISED)
tk.Label(master=controls_frame, text="controls here").pack()
entity_select_frame = tk.Frame(width=200, relief=tk.RAISED, borderwidth=1)
dnd = DragManager()
button = tk.Button(master=entity_select_frame, text = "dragable", borderwidth=1)
button.gridx = 0
button.gridy = 0
button.grid(row = 0, column = 0)
button.pure = True
dnd.add_dragable(button)
map_frame.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
controls_frame.pack(fill=tk.BOTH)
entity_select_frame.pack(fill=tk.BOTH)
window.mainloop()
I played around a little bit and used stuff from this post. I did not structure it as a class and I used the picture frame as my root-frame and put the control-frame inside that. I'm not sure how this would be best combined with your "draw-grid", "token" functionalities etc., however I hope it helps. I did not find a way to drag widgets across frames though (tried to set a new master for the button, recreate it after dropping it etc.). Get the image used in my code from here.
from tkinter import Tk, Frame, Label, Button, Canvas, font
from tkinter import ttk
from PIL import Image, ImageTk
root = Tk()
""" ####################### Configuration parameters ###################### """
image_file_path = "Island_AngelaMaps-1024x768.jpg"
resize_img = False # set to True if you want to resize the image > window size
resize_to = (600, 600) # resolution to rescale image to
""" ####################### Drag and drop functionality ################### """
def make_draggable(widget):
widget.bind("<Button-1>", on_drag_start)
widget.bind("<B1-Motion>", on_drag_motion)
def on_drag_start(event):
widget = event.widget
widget._drag_start_x = event.x
widget._drag_start_y = event.y
def on_drag_motion(event):
widget = event.widget
x = widget.winfo_x() - widget._drag_start_x + event.x
y = widget.winfo_y() - widget._drag_start_y + event.y
widget.place(x=x, y=y)
""" ################################# Layout ############################## """
# picture frame with picture as background
picture_frame = Frame(root)
picture_frame.pack(side="left", anchor="w", fill="both", expand=True)
# load the image
if resize_img:
img = ImageTk.PhotoImage(Image.open(image_file_path).resize(resize_to, Image.ANTIALIAS))
else:
img = ImageTk.PhotoImage(Image.open(image_file_path))
# create canvas, set canvas background to the image
canvas = Canvas(picture_frame, width=img.width(), height=img.height())
canvas.pack(side="left")
canvas.background = img # Keep a reference in case this code is put in a function.
bg = canvas.create_image(0, 0, anchor="nw", image=img)
# subframe inside picture frame for controls
ctrl_subframe = Frame(picture_frame)
ctrl_subframe.pack(side="right", anchor="n")
# separator between picture and controls, inside picture frame
ttk.Separator(picture_frame, orient="vertical").pack(side="right", fill="y")
# underlined label 'Controls' in subframe
ctrl_header = Label(ctrl_subframe, text="Controls", font=("Arial", 10, "bold"))
f = font.Font(ctrl_header, ctrl_header.cget("font"))
f.configure(underline=True)
ctrl_header.configure(font=f)
ctrl_header.pack(side="top", pady=2)
# update window to get proper sizes from widgets
root.update()
# a draggable button, placed below ctrl_header
# (based on X of ctrl_subframe and height of ctrl_header, plus padding)
drag_button = Button(picture_frame, text="Drag me", bg="green", width=6)
drag_button.place(x=ctrl_subframe.winfo_x()+2, y=ctrl_header.winfo_height()+10)
make_draggable(drag_button)
""" ################################ Mainloop ############################# """
root.mainloop()
I have question about how to call some variables, in this case from the method inside exportCsv that belongs to back_Gui class. I want to use the variables self.msg_ and self.opstat in the method __update from class _Gui to stop the reproduction of the gift and promt out the window to save the file. When I run the code and press the button it iterates in an infinite loop because the variable is not passing to. Also, I try to aggregate some threading to not to freeze the window when the button is pressed. Any solution?.
Also, I think these variables I mention would be inside the try but what happens in the except?. Do I have to create more variables to avoid infinite looping on the gift?
from tkinter import *
import tkinter as tk
from tkinter import filedialog
import sqlite3
import pandas as pd
from PIL import Image, ImageTk
import time
import threading
class back_Gui: #Superclass
'''Class that handle the data'''
def __init__(self, db_name = 'database.db'):
self.db_name = db_name
self.msg_ = None
self.opstat = None
.
.
.
def exportCSV(self):
df_to = self.df
try:
export_file = filedialog.asksaveasfilename(defaultextension='.csv')
df_to.to_csv(export_file, index=False, header=True)
except:
pass
#These are the variables that I need
self.msg_ = "Done."
self.opstat = -1
class _Gui(back_Gui): #Subclass
def __init__(self, window):
'''Gui of the windw tk '''
self.wind = window #child
super().__init__()
self.text_font = ('Helvetica', '10', 'bold')
self.Button_(self.wind)
def Button_(self, wind):
"""Run the gift while the csv is being generated"""
#Button
b1 = Button(self.wind, text="random",
font=self.text_font,
command=self.job_genCsv,
).grid(row=1, column=5, padx=5, pady=5 ,sticky=W)
def frame_maps(self, wind):
'''Frame Containter'''
self.frame = LabelFrame(self.wind, text='Hail Hydra', font=self.text_font, height = 500, width = 1300, bd=4)
self.frame.grid(row=2, columnspan=20, sticky=W+E)#, columnspan=3, padx=25, pady=25)
# create the canvas, size in pixels
self.canvas = Canvas(self.frame, width=1300, height=500, bg='white')
# pack the canvas into a frame/form
self.canvas.grid(row=0, columnspan=20, sticky=N, ipadx=20, ipady=20)#
# load the .gif image file
#Here it has to be use the self because is local variable
self.current_image_number = 0
file="cain.gif"
info = Image.open(file)
self.frames = info.n_frames
self.gif1 = [PhotoImage(file=file, format=f"gif -index {i}") for i in range(self.frames)]
def __update(self):
#self.job_genCsv()
''' update the gift '''
self.frame.update_idletasks()#update
if self.opstat >= 0.0:
#msg = self.image_on_canvas #
# next image
self.current_image_number += 1
# return to first image
if self.current_image_number == self.frames: #len(self.images):
self.current_image_number = 0
# change image on canvas
self.canvas.itemconfig(self.update, image=self.gif1[self.current_image_number])
_ = threading.Timer(0, self.__update).start()
print("loop")
else:
if self.msg_ == "Done.":
self.update = self.canvas.create_image(250, 50, anchor=NW, image=self.gif1[self.current_image_number])
del self.msg_
#control variable restablished
self.opstat = 0
print("ends")
def job_genCsv(self):
'''Runs his job and call frame_maps '''
self.frame_maps(self)
_ = threading.Timer(0, self.exportCSV).start()
_ = threading.Timer(0, self.__update).start()
if __name__ == '__main__':
window = Tk()
application = _Gui(window)
window.mainloop()
I'm getting this error:
File "C:\Users\Documents\run.py", line 214, in __update
if self.opstat >= 0.0:
AttributeError: '_Gui' object has no attribute 'opstat'
This question already has answers here:
Tkinter: AttributeError: NoneType object has no attribute <attribute name>
(4 answers)
Closed 2 years ago.
I have a problem with a GUI I created, that includes a label that indicates that the program is working in the background, the real script processes a huge amount of data so I want that indicator to tell the user it is indeed doing something. It worked fine so far like this, displaying "Idle" at script start, "running" while the function runs its course, and "Done" when it's done. (code is only the relevant part):
from tkinter import*
def make_pause_function(sleepytime):
import time
print("Falling asleep")
time.sleep(sleepytime)
print("Awakening")
class MyGUI:
def __init__(self):
self.__mainWindow = Tk()
self.header = StringVar()
self.header.set(3)
self.labelText = 'Idle'
# self.Button2 = Button(text = "Sleep for X seconds!", command = self.run_main_script).grid(row=1,column=0)
# self.Entry2 = Entry(self.__mainWindow, textvariable=self.header, width = 100).grid(row=2,column=0)
# self.Label3 = Label(self.__mainWindow, text = self.labelText).grid(row=3,column=0)
self.Button2 = Button(text = "Sleep for X seconds!", command = self.run_main_script)
self.Entry2 = Entry(self.__mainWindow, textvariable=self.header, width = 100)
self.Label3 = Label(self.__mainWindow, text = self.labelText)
self.Button2.pack()
self.Entry2.pack()
self.Label3.pack()
mainloop()
def run_main_script(self):
self.Label3["text"] = 'running'
self.__mainWindow.update_idletasks()
header=self.header.get()
make_pause_function(int(header))
self.Label3["text"] = 'done'
self.__mainWindow.update_idletasks()
myGUI = MyGUI()
But when the GUI grew because of many options, I switched from pack to grid geometry manager and then the label updating I had gotten working after a lot of trial and error got broken again. The follwing code doesn't work:
from tkinter import*
def make_pause_function(sleepytime):
import time
print("Falling asleep")
time.sleep(sleepytime)
print("Awakening")
class MyGUI:
def __init__(self):
self.__mainWindow = Tk()
self.header = StringVar()
self.header.set(3)
self.labelText = 'Idle'
self.Button2 = Button(text = "Sleep for X seconds!", command = self.run_main_script).grid(row=1,column=0)
self.Entry2 = Entry(self.__mainWindow, textvariable=self.header, width = 100).grid(row=2,column=0)
self.Label3 = Label(self.__mainWindow, text = self.labelText).grid(row=3,column=0)
# self.Button2 = Button(text = "Sleep for X seconds!", command = self.run_main_script)
# self.Entry2 = Entry(self.__mainWindow, textvariable=self.header, width = 100)
# self.Label3 = Label(self.__mainWindow, text = self.labelText)
# self.Button2.pack()
# self.Entry2.pack()
# self.Label3.pack()
mainloop()
def run_main_script(self):
self.Label3["text"] = 'running'
self.__mainWindow.update_idletasks()
header=self.header.get()
make_pause_function(int(header))
self.Label3["text"] = 'done'
self.__mainWindow.update_idletasks()
myGUI = MyGUI()
Seems that update_idletasks doesn't like grid. But I don't like pack for a GUI with lots of buttons and fields. Any way to do what I want with grid packing?
try this:
self.Label3 = Label(self.__mainWindow, text = self.labelText).grid(row=3,column=0)
from that, to this:
self.Label3 = Label(self.__mainWindow, text = self.labelText)
self.Label3.grid(row=3,column=0)
The follwing line causes the error:
self.Label3 = Label(self.__mainWindow, text = self.labelText).grid(row=3,column=0)
grid() is a function that doesn't return anything (None). Because of that, you're saving in variable self.Lable3 nothing. Then, when you run the line self.Label3["text"] = 'running' an error pops up because self.Lable3 is None. In order to solve it, seperate those lines- first save the Label in a variable and then use the grid() function on it:
self.Label3 = Label(self.__mainWindow, text = self.labelText)
self.Label3.grid(row=3,column=0)
By the way, I recommend using place() method instead of grid() because in my opinion it is easier to place objects with it. You can read about it Here
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()
At first I implemented this code which uses classes and worked fine:
from Tkinter import *
import numpy as np
from PIL import Image,ImageTk
import time
#----------------------------------------------------------------------
class MainWindow():
#----------------
def __init__(self,main,pix):
# canvas for image
self.canvas = Canvas(main, width=424, height=424)
self.canvas.grid(row=0, column=0)
# images
self.im=Image.fromarray(pix.astype('uint8'))
self.photo = ImageTk.PhotoImage(image=self.im)
self.my_images = self.photo
self.my_image_number = 0
# set first image on canvas
self.image_on_canvas = self.canvas.create_image(0, 0, anchor = NW, image = self.my_images )
self.i=0
self.j=0
main.after(1,self.onButton,main,pix)# button to change image
main.update()
#----------------
def onButton(self,main,pix):
print self.i,self.j
if self.j==100:
return
pix[self.i][self.j]=255-pix[self.i][self.j]
self.i+=1
if self.i==100:
self.i=0
self.j+=1
self.im=Image.fromarray(pix.astype('uint8'))
self.photo = ImageTk.PhotoImage(image=self.im)
self.my_images = self.photo
self.canvas.itemconfig(self.image_on_canvas, image = self.my_images)
main.after(1,self.onButton,main,pix)
else:
main.after(0,self.onButton,main,pix)
#----------------------------------------------------------------------
root = Tk()
pix=np.array([[(i,j,255) for i in range(256)] for j in range(255,-1,-1)])
x=MainWindow(root,pix)
root.mainloop()
Later I tried to use the same functions without the class and it looks like this:
from Tkinter import *
import numpy as np
from PIL import Image,ImageTk
import time
def onButton(main,pix):
global i,j
if j==100:
return
pix[i][j]=255-pix[i][j]
i+=1
if i==100:
i=0
j+=1
im=Image.fromarray(pix.astype('uint8'))
photo = ImageTk.PhotoImage(image=im)
my_images = photo
canvas.itemconfig(image_on_canvas, image = my_images)
main.after(1,onButton,main,pix)
else:
main.after(0,onButton,main,pix)
root = Tk()
pix=np.array([[(i,j,255) for i in range(256)] for j in range(255,-1,-1)])
canvas = Canvas(root, width=424, height=424)
canvas.grid(row=0, column=0)
im=Image.fromarray(pix.astype('uint8'))
photo = ImageTk.PhotoImage(image=im)
image_on_canvas = canvas.create_image(0, 0, anchor = NW, image = photo )
i,j=0,0
root.after(1,onButton,root,pix)# button to change image
print "hi"
root.mainloop(f i==100:
i=0
j+=1
im=Image.fromarray(pix.astype('uint8'))
photo = ImageTk.PhotoImage(image=im)
my_images = photo
canvas.itemconfig(image_on_canvas, image = my_images)
main.after(1,onButton,main,pix)
else:
main.after(0,onButton,main,pix)
root = Tk()
pix=np.array([[(i,j,255) for i in range(256)] for j in range(255,-1,-1)])
canvas = Canvas(root, width=424, height=424)
canvas.grid(row=0, column=0)
im=Image.fromarray(pix.astype('uint8'))
photo = ImageTk.PhotoImage(image=im)
image_on_canvas = canvas.create_image(0, 0, anchor = NW, image = photo )
i,j=0,0
root.after(1,onButton,root,pix)# button to change image
print "hi"
root.mainloop()
Why does this not work? This is the first time I am working with tkinter, so I am probably missing something crucial. What do I need to change?
I'm curious why you need to implement such a thing without a class, but first let's look at your problem, namely the OnButton function.
You have already defined global variables i and j. But how about others?
All variables that you are trying to mutate (and keep value) must also be declared as global! This is a root of your problem and as for the answer to the question "why doesn't it work" - #PM 2Ring give you a good refer to photoimage docs.
Note: When a PhotoImage object is garbage-collected by Python (e.g. when you return from a function which stored an image in a local variable), the image is cleared even if it’s being displayed by a Tkinter widget.
try:
from tkinter import *
except ImportError:
from Tkinter import *
import numpy as np
from PIL import Image,ImageTk
def onButton():
global i, j, photo #, pix
print(i, j)
if j == 100:
return
pix[i][j] = 255-pix[i][j]
i += 1
if i == 100:
i = 0
j += 1
photo = ImageTk.PhotoImage(image=Image.fromarray(pix.astype('uint8')))
canvas.itemconfig(image_on_canvas, image=photo)
root.after(1, onButton)
else:
root.after(0, onButton)
root = Tk()
pix=np.array([[(i, j, 255) for i in range(256)] for j in range(255, -1, -1)])
canvas = Canvas(root, width=424, height=424)
canvas.grid(row=0, column=0)
photo = ImageTk.PhotoImage(image=Image.fromarray(pix.astype('uint8')))
image_on_canvas = canvas.create_image(0, 0, anchor=NW, image=photo)
i, j = 0, 0
root.after(1, onButton) # button to change image
print("hi")
root.mainloop()
And if there's no need for this variables outside function - then make'em local and move all relevant code into function!
After all, please, take a time to read this article. Maybe you change your mind about the classes (and about wildcard import).