I've created a game "dice poker" using Zelle's graphing package, and have a button on the main screen which opens a text file. The text file opens when the button is clicked, but the main window closes. How can I keep the parent window open?
The button class is below:
from graphics import *
from tkinter import Button as tkButton
class Button():
"""A button is a labeled rectangle in a window.
It is activated or deactivated with the activate()
and deactivate() methods. The clicked(p) method
returns true if the button is active and p is inside it."""
def __init__(self, win, center, width, height, label):
""" Creates a rectangular button, eg:
qb = Button(myWin, centerPoint, width, height, 'Quit') """
w,h = width/2.0, height/2.0
x,y = center.getX(), center.getY()
self.xmax, self.xmin = x+w, x-w
self.ymax, self.ymin = y+h, y-h
p1 = Point(self.xmin, self.ymin)
p2 = Point(self.xmax, self.ymax)
self.rect = Rectangle(p1,p2)
self.rect.setFill('lightgray')
self.rect.draw(win)
self.label = Text(center, label)
self.label.draw(win)
self.deactivate()
def clicked(self, p):
"Returns true if button active and p is inside"
return (self.active and
self.xmin <= p.getX() <= self.xmax and
self.ymin <= p.getY() <= self.ymax)
def getLabel(self):
"Returns the label string of this button."
return self.label.getText()
def activate(self):
"Sets this button to 'active'."
self.label.setFill('black')
self.rect.setWidth(2)
self.active = True
def deactivate(self):
"Sets this button to 'inactive'."
self.label.setFill('darkgrey')
self.rect.setWidth(1)
self.active = False
How can I include a command argument that can open an executable in a fashion similar to this tkinter implementation:
import Tkinter as tk
def create_window():
window = tk.Toplevel(root)
root = tk.Tk()
b = tk.Button(root, text="Create new window", command=create_window)
b.pack()
root.mainloop()
Where the command can be subprocess.run(['open', '-t', 'poker_help.txt']) and still keep the original window open?
I have to make some assumptions since you didn't include top level code (e.g. you're on a Mac):
Zelle graphics, unlike tkinter and turtle, which is also built on tkinter, doesn't have an explicit win.mainloop() call to turn control over to the Tk event handler to idle awaiting events to happen. Instead, you have to patch one together yourself, otherwise once you get the mouse click that fires off your button, the program falls through the end of the file and the main window closes:
import subprocess
from graphics import *
from button import Button
win = GraphWin()
help_button = Button(win, Point(150, 150), 50, 50, "Help")
help_button.activate()
quit_button = Button(win, Point(50, 50), 50, 50, "Quit")
quit_button.activate()
while True:
point = win.getMouse()
if help_button.clicked(point):
subprocess.call(['open', '-t', 'poker_help.txt'])
elif quit_button.clicked(point):
win.close()
Where from button import Button brings in your button code above. Another thing to check is your window is actually closing, and not simply being obscured by the new window opened atop it.
Related
I am attempting to create a layout using Tkinter for Python3 that involves several buttons and scales. The buttons work fine, but the command that I give to the scale widget is called when I call grid on the scale. Why is this happening and what can I do to stop it?
Here is a simplified version of my code:
import tkinter
import time
WINDOW_HEIGHT = 150
WINDOW_WIDTH = 340
class Player(object):
def __init__(self):
self.window = tkinter.Tk()
self.window.geometry(str(WINDOW_WIDTH) + 'x' + str(WINDOW_HEIGHT))
self.current_time = tkinter.DoubleVar()
self.progress_bar = tkinter.Scale(self.window,
variable = self.current_time,
command = self.set_current_time,
orient = tkinter.HORIZONTAL,
showvalue = 0, resolution=.001)
self.progress_bar.grid(row=1, column=10)
def set_current_time(self, time):
print('setting current time')
print(time)
def update(self):
self.window.update_idletasks()
self.window.update()
def main():
media_player = Player()
while True:
media_player.update()
time.sleep(.1)
if __name__ == "__main__":
main()
The set_current_time function should only be called when the slider is actually clicked and moved, however as soon as grid is executed, set_current_time is called with a time value of 0. How can I place the slider without executing the command? After placement the slider works as expected, but I would like to avoid the initial calling of the set_current_time function.
I am working on a program that would move a ball around depending on joystick inputs. The GUI I am using is Tkinter.
Recently, I found a demo of a method that used Tkinter and was compatible with Pygame (Prior to this, I thought that Pygame was incompatible with Tkinter. I was psyched when I could found this.)
I defined the virtual event "LT" in the class Find_Joystick. ("LT" refers to the "LT" button on a gamepad.) When pressed, it's supposed to move the ball to the left. However, when I try to bind the event to the actual movement function, the function doesn't appear to receive the input.
Here is my code (somewhat simplified):
from tkinter import *
import pygame
class Find_Joystick:
def __init__(self, root):
self.root = root
## initialize pygame and joystick
pygame.init()
if(pygame.joystick.get_count() < 1):
# no joysticks found
print("Please connect a joystick.\n")
self.quit()
else:
# create a new joystick object from
# ---the first joystick in the list of joysticks
joystick = pygame.joystick.Joystick(0)
# tell pygame to record joystick events
joystick.init()
## start looking for events
self.root.after(0, self.find_events)
def find_events(self):
## check everything in the queue of pygame events
events = pygame.event.get()
joystick = pygame.joystick.Joystick(0)
LT = joystick.get_button(6)
for event in events:
# event type for pressing any of the joystick buttons down
if event.type == pygame.JOYBUTTONDOWN:
self.root.event_generate('<<JoyFoo>>')
if LT == 1:
self.root.event_generate('<<LT>>')
if event.type == pygame.JOYAXISMOTION:
self.root.event_generate('<<JoyMove>>')
#Move left
if axisX < 0:
self.root.event_generate('<<Left>>')
#Move right
if axisX > 0:
self.root.event_generate('<<Right>>')
#Move upwards
if axisY < -0.008:
self.root.event_generate('<<Up>>')
#Move downwards
if axisY > -0.008:
self.root.event_generate('<<Down>>')
## return to check for more events in a moment
self.root.after(20, self.find_events)
def main():
## Tkinter initialization
root = Tk()
app = Find_Joystick(root)
frame = Canvas(root, width=500, height = 250)
frame.pack()
ball = frame.create_oval([245, 120], [255, 130], outline = 'red', fill = 'red')
def callback(event):
frame.focus_set()
def moveLeftFunc(event):
frame.move(ball, -3, 0)
print('Move left')
frame.bind('<Button-1>', callback)
frame.bind('<<LT>>', moveLeftFunc)
root.mainloop()
if __name__ == "__main__":
main()
Here is the demo I was referring to earlier (the class is the main part I am using).
The solution might be in the event binding.
You bind the events to the frame Canvas, and then events are generated by the root widget (self.root.event_generate('<<LT>>'))
It could work in the other way, the event are propagated from the widget to the parent, but not from the root to the children widgets.
Can you try with :
root.bind('<Button-1>', callback)
root.bind('<<LT>>', moveLeftFunc)
(you should also remove the line self.root.event_generate('<<JoyFoo>>'))
EDIT :
Here is a reproducible case without the joystick stuff:
(the child button propagates the event to close the window, but the event triggered by the root timer doesn't hit the frame binding to close)
from tkinter import *
import sys
def quit(*arg):
sys.exit()
class ChildFrame(Frame):
def __init__(self, root):
Frame.__init__(self, root)
self.grid()
button = Button(self, text="Propagate Quit", command=self.child_event)
button.pack()
def child_event(self):
self.event_generate('<<Propagated>>') # triggers the root binding
root = Tk()
frame = ChildFrame(root)
def root_event(*arg):
global root
root.event_generate('<<NotPropagated>>') # doesn't trigger child binding
#root.event_generate('<<Propagated>>') # triggers the root binding
root.bind('<<Propagated>>', quit)
frame.bind('<<NotPropagated>>', quit)
root.after(2000, root_event) # timer to create event from root to child
root.mainloop()
I've been having this problem with a python program I am making where if I display a TopLevel window, in this case my Help Menu, then withdraw it then try to display it again I get the following error
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python34\lib\tkinter\__init__.py", line 1533, in __call__
return self.func(*args)
File "C:\Users\****\Documents\GitHub\ProjectName\ProjectName\GUI.py", line 60, in displayHelp
self.helpMenu.display();
File "C:\Users\****\Documents\GitHub\ProjectName\ProjectName\HelpMenu.py", line 35, in display
self.deiconify();
File "C:\Python34\lib\tkinter\__init__.py", line 1646, in wm_deiconify
return self.tk.call('wm', 'deiconify', self._w)
_tkinter.TclError: bad window path name ".60000336"
The error first happened when I was withdrawing from within HelpMenu.py and using deiconify to redisplay it from the GUI.py file.
Since then I have tried multiple ways to fix the problem including calling deiconify from within HelpMenu.py and updating the copy of help menu stored in the GUI when I withdraw it.
I am running Python 3.4.2
I have already done extensive searches online and failed to find a solution to my problem. I have found other mentions of this error but they either didn't relate to my situation or their solutions did not work.
Here is the entire code for the HelpMenu.py followed by an extract from GUI.py that retains the functionality to reproduce the error but has other code removed.
#!/usr/bin/python
try:
from Tkinter import *
except ImportError:
from tkinter import *
class HelpMenu(Toplevel):
def __init__(self, parent, observer):
Toplevel.__init__(self);
self.observer = observer;#Observer is the GUI, this is here just so I can update the GUI when I withdraw this window
self.setup();
self.withdraw();
self.protocol('WM_DELETE_WINDOW', self.quit());#Changes the close button to just hide the window
def setup(self):
self.columnconfigure(0,weight=1);
w = 400;#Sets up the window position on the screen
h = 150;
sw = self.winfo_screenwidth();
sh = self.winfo_screenheight();
x=(sw-w)/2;
y =(sh-h)/2;
self.update();
self.geometry('%dx%d+%d+%d' % (w,h,x,y));
self.resizable(width=0, height=0);
self.grid();
self.title("Help Menu");
def quit(self):#Hides the window
self.withdraw();
self.observer.updateHelp(self);
def display(self):#Re-displays the window
self.deiconify();
Here is code taken from GUI.py and modified to only have the code needed to reproduce the issue.
#!/usr/bin/python
#Allows compatibility with any version of Python by checking for both versions of Tkinter
try:
from Tkinter import *
except ImportError:
from tkinter import *
#Imports the AutoCompleteEntry
from HelpMenu import HelpMenu
class UI(Tk):
def initialize(self):
#Handles setting up most of the GUI
w = 500;#Window width
h = 500;#Window height
sw = self.winfo_screenwidth();#Gets screen width
sh = self.winfo_screenheight();#Gets screen height
x=(sw-w)/2;#Calculates the x position for the left side of the window that allows it to be placed in the center of the screen
y =(sh-h)/2;#Calculates the y position for the top of the window that allows it to be placed in the center of the screen
self.update();#Forces and update on the window
self.geometry('%dx%d+%d+%d' % (w,h,x,y));#Sets the windows width, height and position
self.minsize(int(w),int(h/2));#Sets the minimum size of the window
self.configureMenu();
def updateHelp(self, helpMenu):
self.helpMenu=helpMenu;
def displayHelp(self):
self.helpMenu.display();
def configureMenu(self):
#Handles configuring and setting up the menus
menu = Menu(self);#Setup the menu bar
menu.add_command(label="Help",command=self.displayHelp);
self.config(menu=menu);
def __init__(self, parent):
#Handles the initial call to create a GUI
Tk.__init__(self,parent);#Parent constructor
self.parent = parent;#Store the parent
self.initialize();#Initilize the GUI
self.helpMenu = HelpMenu(self, self);
self.mainloop();#Start the main loop
if __name__ == "__main__":
import sys
main = UI(None);
One last note, I am slightly new to Python, so there might be other errors in my code and while I wont mind if they get pointed out, the main focus I have right now is fixing this path name error.
EDIT:Almost a month now and I have still not found a solution to the problem. Any help would be great but at this point I am probably going to have to abandon my project.
So, after a break I went back to look at this problem again.
Turns out that the issue was self.protocol('WM_DELETE_WINDOW', self.quit()) was not actually calling self.quit() and was destroying the window completely.
A quick change to self.protocol('WM_DELETE_WINDOW', self.quit) seems to have fixed it.
I think maybe the comma causes the problem. Try write it like this:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
try:
from Tkinter import *
except ImportError:
from tkinter import *
class HelpMenu(Toplevel):
def __init__(self, parent, observer):
Toplevel.__init__(self)
self.observer = observer # Observer is the GUI, this is here just so I can update the GUI when I withdraw this window
self.setup()
self.withdraw()
self.protocol('WM_DELETE_WINDOW', self.quit()) # Changes the close button to just hide the window
def setup(self):
self.columnconfigure(0, weight=1)
w = 400 # Sets up the window position on the screen
h = 150
sw = self.winfo_screenwidth()
sh = self.winfo_screenheight()
x = (sw - w) / 2
y = (sh - h) / 2
self.update()
self.geometry('%dx%d+%d+%d' % (w, h, x, y))
self.resizable(width=0, height=0)
self.grid()
self.title("Help Menu")
def quit(self): # Hides the window
self.withdraw()
self.observer.updateHelp(self)
def display(self): # Re-displays the window
self.deiconify()
class UI(Tk):
def initialize(self):
# Handles setting up most of the GUI
w = 500 # Window width
h = 500 # Window height
sw = self.winfo_screenwidth() # Gets screen width
sh = self.winfo_screenheight() # Gets screen height
x = (sw - w) / 2 # Calculates the x position for the left side of the window that allows it to be placed in the center of the screen
y = (sh - h) / 2 # Calculates the y position for the top of the window that allows it to be placed in the center of the screen
self.update() # Forces and update on the window
self.geometry('%dx%d+%d+%d' % (w, h, x, y)) # Sets the windows width, height and position
self.minsize(int(w), int(h / 2)) # Sets the minimum size of the window
self.configureMenu()
def updateHelp(self, helpMenu):
self.helpMenu = helpMenu
def displayHelp(self):
self.helpMenu.display()
def configureMenu(self):
# Handles configuring and setting up the menus
menu = Menu(self) # Setup the menu bar
menu.add_command(label="Help", command=self.displayHelp)
self.config(menu=menu)
def __init__(self, parent):
# Handles the initial call to create a GUI
Tk.__init__(self, parent) # Parent constructor
self.parent = parent # Store the parent
self.initialize() # Initilize the GUI
self.helpMenu = HelpMenu(self, self)
self.mainloop() # Start the main loop
if __name__ == "__main__":
main = UI(None)
It works perfectly from myside.
I know how to make a window fullscreen in the "main" display, but even when moving my app's window to a secondary display connected to my PC, when I call:
self.master.attributes('-fullscreen', True)
to fullscreen that window, it does so in the "main" display and not in the secondary one (the app's window disappears from the secondary display and instantly appears in the "main" one, in fullscreen).
How can I make it fullscreen in the secondary display?
This works on Windows 7: If the second screen width and height are the same as the first one, you can use win1 or win2 geometry of the following code depending its relative position(leftof or rightof) to have a fullscreen in a secondary display:
from Tkinter import *
def create_win():
def close(): win1.destroy();win2.destroy()
win1 = Toplevel()
win1.geometry('%dx%d%+d+%d'%(sw,sh,-sw,0))
Button(win1,text="Exit1",command=close).pack()
win2 = Toplevel()
win2.geometry('%dx%d%+d+%d'%(sw,sh,sw,0))
Button(win2,text="Exit2",command=close).pack()
root=Tk()
sw,sh = root.winfo_screenwidth(),root.winfo_screenheight()
print "screen1:",sw,sh
w,h = 800,600
a,b = (sw-w)/2,(sh-h)/2
Button(root,text="Exit",command=lambda r=root:r.destroy()).pack()
Button(root,text="Create win2",command=create_win).pack()
root.geometry('%sx%s+%s+%s'%(w,h,a,b))
root.mainloop()
Try:
from Tkinter import *
rot = Tk()
wth,hgh = rot.winfo_screenwidth(),rot.winfo_screenheight()
#take desktop width and hight (pixel)
_w,_h = 800,600 #root width and hight
a,b = (wth-_w)/2,(hgh-_h)/2 #Put root to center of display(Margin_left,Margin_top)
def spann():
def _exit():
da.destroy()
da = Toplevel()
da.geometry('%dx%d+%d+%d' % (wth, hgh,0, 0))
Button(da,text="Exit",command=_exit).pack()
da.overrideredirect(1)
da.focus_set()#Restricted access main menu
Button(rot,text="Exit",command=lambda rot=rot : rot.destroy()).pack()
but = Button(rot,text="Show SUB",command=spann)
but.pack()
rot.geometry('%sx%s+%s+%s'%(_w,_h,a,b))
rot.mainloop()
""" Geometry pattern 'WxH+a+b'
W = Width
H = Height
a = Margin_left+Margin_Top"""
Super simple method working in 2021
This works even if both displays are different resolutions. Use geometry to offset the second display by the width of the first display. The format of the geometry string is <width>x<height>+xoffset+yoffset:
root = tkinter.Tk()
# specify resolutions of both windows
w0, h0 = 3840, 2160
w1, h1 = 1920, 1080
# set up a window for first display, if wanted
win0 = tkinter.Toplevel()
win0.geometry(f"{w0}x{h0}+0+0")
# set up window for second display with fullscreen
win1 = tkinter.Toplevel()
win1.geometry(f"{w1}x{h1}+{w0}+0") # <- this is the key, offset to the right by w0
win1.attributes("-fullscreen", True)
As long as you know the width of the first display, this will work fine. The X system TK runs on puts the second monitor to the right of the first one by default.
Windows, Python 3.8
In this solution, pressing F11 will make the window fullscreen on the current screen.
Note that self.root.state("zoomed") is Windows specific according to doc.
self.root.overrideredirect(True) is weird in Windows and may have unwanted side effects. For instance I've had issues related to changing screen configuration with this option active.
#!/usr/bin/env python3
import tkinter as tk
class Gui:
fullScreen = False
def __init__(self):
self.root = tk.Tk()
self.root.bind("<F11>", self.toggleFullScreen)
self.root.bind("<Alt-Return>", self.toggleFullScreen)
self.root.bind("<Control-w>", self.quit)
self.root.mainloop()
def toggleFullScreen(self, event):
if self.fullScreen:
self.deactivateFullscreen()
else:
self.activateFullscreen()
def activateFullscreen(self):
self.fullScreen = True
# Store geometry for reset
self.geometry = self.root.geometry()
# Hides borders and make truly fullscreen
self.root.overrideredirect(True)
# Maximize window (Windows only). Optionally set screen geometry if you have it
self.root.state("zoomed")
def deactivateFullscreen(self):
self.fullScreen = False
self.root.state("normal")
self.root.geometry(self.geometry)
self.root.overrideredirect(False)
def quit(self, event=None):
print("quiting...", event)
self.root.quit()
if __name__ == '__main__':
Gui()
I am creating a program in Python that creates a shape based on user input. I need to create two functions to create buttons using Zeller's graphic.py file. One button needs to say Quit and the second needs to say Process. Here is what i have so far but as you can see, they are not in defined functions:
#create Quit button
quitButton = Text(Point(70,73), "Quit")
quitButton.draw(w)
Rectangle(Point(45, 50), Point(95,97)).draw(w)
#create Process button
enterButton = Text(Point(145,73), "Process")
enterButton.draw(w)
Rectangle(Point(120, 48), Point(170,98)).draw(w)
Here is a description of the necessary methods
createButton(text, pt1button, pt2button, win) creates a rectangle with corner points pt1button and pt2button with centered text in window win
clickedButton(button, clickPt) returns true/false if clickPt is in button.
I tried to create the function and received the following error.
Here is my function:
def createButton(text, pt1button, pt2button, win):
button = Text(Point(pt1button, pt2button), text)
button.draw(win)
Here is where I called the function:
createButton("Process",145,73,win)
createButton("Quit",70,73,win)
Here is the error that was thrown:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/project4FINAL.py", line 77, in <module> main()
File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/project4FINAL.py", line 27, in main buttonCreate("Process", 145,73, win)
NameError: global name 'win' is not defined
Any suggestions?
So from looking at the code it looks like you want to create a callback function for each button and then assign each to the canvas via the setMouseHandler of method of GraphWin.
So from the example given in the API:
from graphics import *
def example_callback():
print "I love unicorns!"
def main():
# win is a Canvas with a setMouseHandler method
win = GraphWin("My Circle", 100, 100)
c = Circle(Point(50,50), 10)
c.draw(win)
#Add a callback to the canvas
c.cavas.setMouseHandler(example_callback)
# or win.setMouseHandler(example_callback)
win.getMouse() # Pause to view result
win.close() # Close window when done
main()
Unless you have bounds checking in you callbacks (to see which shape on the canvas the mouse is inside), you should only have one canvas per drawn shape.
An example following the use of a createButton function:
def createButton(text, pt1button, pt2button, win):
button = Text(Point(pt1button, pt2button), text)
button.draw(win)
return button
def _callback(pt):
print "I love unicorns!"
print
print "Mouse clicked at x=%d, y=%d"%(pt.x,pt.y)
print
def test():
win = GraphWin()
win.setCoords(0,0,100,100)
quitButton = createButton("Quit",70,73,win)
Rectangle(Point(45, 50), Point(95,97)).draw(win)
win.setMouseHandler(_callback)
while True:
win.getMouse()
win.close()
Below is a complete example using a new Button object:
from graphics import *
class Button(object):
def __init__(self, text, text_pos, rect_pos, win, callback):
self.win = win
self.text = Text(text_pos, text)
self.text.draw(self.win)
# the limits of the button will be defined by the rectangle
self.coords = [rect_pos[0].x,rect_pos[0].y,rect_pos[1].x,rect_pos[1].y]
self.rect = Rectangle(*rect_pos)
self.rect.draw(self.win)
# set the buttons callback
self.callback = callback
def _is_inside(self,click):
limits = self.coords
return (limits[0] < click.x < limits[2]) and (limits[1] < click.y < limits[3])
class MyWindow(object):
def __init__(self,coords=(0,0,100,100)):
self.win = GraphWin()
self.win.setCoords(*coords)
# a list of all possible buttons
self.buttons = []
# register the master callback function
self.win.setMouseHandler(self._callback)
self._quit = False
#create a quit and confess button with a custom create method
self.create_button("Quit",(Point(10,10),Point(40,40)),Point(20,20),self.quit)
self.create_button("Confess",(Point(50,50),Point(80,80)),Point(60,60),self.confess)
def create_button(self,text,coords,text_coords,callback):
button = Button(text,text_coords,coords,self.win,callback)
# create a button and add it to our list of buttons
self.buttons.append(button)
def confess(self,point):
print
print "I love unicorns!"
print
def quit(self,point):
self._quit = True
self.win.close()
# This function is called for any click on the canvas
def _callback(self,point):
# Need to do a coordinate transform here to get the click in the coordinates of the button
x,y = self.win.trans.world(point.x,point.y)
point = Point(x,y)
print "Clicked at x=%d, y=%d"%(point.x,point.y)
# cycle through buttons and execute all for which the clicked point lies inside their rectangle
for button in self.buttons:
if button._is_inside(point):
button.callback(point)
# a loop to keep getting mouse input
def run(self):
while not self._quit:
try:
self.win.getMouse()
except GraphicsError:
break
if __name__ == "__main__":
x = MyWindow()
x.run()