Getting tkinter widget values outside main application class? - python-3.x

I have a pygame project which has a main menu - in said menu I have it so that upon clicking the Options button it triggers a Tkinter UI to open. The UI has a OptionMenu widget affecting game speed. How do I retrieve the value of the OptionMenu outside the class I used to create the Tkinter window
Here is a simplified issue:
class GUI(object):
def __init__(self):
import Tk # etc...
# CODE FOR THE PROGRAM WINDOW
defaultSpeed = StringVar(root)
speedMenu = OptionMenu(root, defaultSpeed, 'Slow', 'Normal, 'Fast')
speedMenu.pack()
I know I need defaultSpeed.get() to get the value.
The Pygame menu has this:
click = pygame.mouse.get_pressed()
clicked = click[0] == 1
if "Play" clicked:
startGame(ticks)
if "Options" clicked:
options = GUI()
At this point ^^^ How do I somehow obtain the speed (defaultSpeed.get()) and have it as global variable so that I can use it in the startGame function to affect the number of ticks on clock?
Thinking about it, I'm essentially asking for how to have a global variable defined when initialising a Tkinter class?
I've tried creating a function in GUI() called getSpeed and calling options.getSpeed() but for some reason I still have a logic error that means it's never actually printed/displayed/returned.
I am aware you shouldn't return values when in __init__ - should I just move all my Tkinter app. to a function inside GUI() and leave the __init_ out? surely I could just refrain from initalising it? Would this be better off programmed procedurally over OOP (allowing me to simply call GUI() in the main menu)?
Any help solving is appreciated.
RECAP OF PROBLEM:
Main Menu has 2 options (1 runs game in Pygame, 1 runs Options window in Tkinter)
I need to get the option from the options menu and have it apply to a variable in the game.
EDIT: I was asked to provide a minimal code
I'm currently really busy but here is minimal pseudocode - all you need to do is create the dimensions for the game window and for the menu (they are both 940,500)
import pygame
import time
import math
import tkinter
from tkinter import *
pygame.init()
clock = pygame.tick.Clock()
ticks = 0
#Here is all the game code
#This function is the main game and includes the game loop
def StartGame(ticks):
running = True
myGame1 = teams()
myGame1.startTeam()
#Here is some functions providing the rules of the sport
def updateBall(ticks):
theBall.x += float(theBall.speed) * ticks / 1000
while running:
myGame1.displayPlayers()
updateBall(ticks)
#Calling alot of functions and the game running is here
ticks = clock.tick(30)
pygame.display.flip
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.quit()
quit
def game_intro(menu_image):
intro = True
while intro:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.quit()
quit
click = pygame.mouse.get_pressed()
clicked = click[0] == 1
if 62+288 > mouse[0] > 62 and 150+28 > mouse[1] > 150:
if clicked:
startGame(ticks)
pass
elif 62+288 > mouse[0] > 62 and 230+30 > mouse[1] > 230:
if clicked:
options = GUI()
class GUI(object):
def __init__(self):
root = self.root = tkinter.Tk
root.geometry('500x400')
root.configure(background = '#ffffff')
speedLabel = tkinter.Message(root, text = 'Game speed: ')
defaultSpeed = StringVar(root)
speedMenu = OptionMenu(root,defaultSpeed, 'Slow', 'Normal', 'Fast')
speedLabel.grid(row = 0, column = 0)
speedMenu.grid(row = 0, column = 1)
game_intro(menu_image)

Before you create defaultSpeed:
global defaultSpeed
Then before you access it:
global defaultSpeed
It basically means that you can access the variable from anywhere after using global.

Related

How to trap a Text object selection event in Python3 / tkinter

Per the following Stackoverflow wisdom
Python/Tkinter: Trap text selection via keyboard/mouse as an event?
I tried to detect the creation of a user selection in a Text object as follows
import tkinter as tk
def handle_selection(e=None):
print('here')
try:
sel_first = input_text.index(tk.SEL_FIRST)
sel_last = input_text.index(tk.SEL_LAST)
print(sel_first, sel_last)
except:
return
def main():
window = tk.Tk()
window.title("Simple Text Editor")
input_text = tk.Text(window)
input_text.bind("<<Selection>>", handle_selection)
input_text.pack()
window.mainloop()
if __name__ == "__main__":
main()
However the event is triggered (apparently) for each horizontal pixel selected rather than when the selection is completed, which was my intention. I also tried the following
input_text.bind("<<Selection-ButtonRelease>>", handle_selection)
which wasn't triggered at all.
Is there a way to capture the event when the user releases the button after making the selection?

How to prompt user to open a file with python3?

I'm making a game and I want the user to be able to click a button which opens the file manager and asks them to open a game save file. Is there a module / how could I put this in my game? If there's no good gui way how would I make a text input thing (without using the terminal)? Thanks!
Pygame is a low-level library, so it doesn't have the kind of built-in dialogs you're after. You can create such, but it will be quicker to use the tkinter module which is distributed with Python and provides an interface to the TK GUI libraries. I'd recommend reading the documentation, but here's a function that will pop up a file selection dialog and then return the path selected:
def prompt_file():
"""Create a Tk file dialog and cleanup when finished"""
top = tkinter.Tk()
top.withdraw() # hide window
file_name = tkinter.filedialog.askopenfilename(parent=top)
top.destroy()
return file_name
Here's a small example incorporating this function, pressing Spacebar will popup the dialog:
import tkinter
import tkinter.filedialog
import pygame
WIDTH = 640
HEIGHT = 480
FPS = 30
def prompt_file():
"""Create a Tk file dialog and cleanup when finished"""
top = tkinter.Tk()
top.withdraw() # hide window
file_name = tkinter.filedialog.askopenfilename(parent=top)
top.destroy()
return file_name
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
f = "<No File Selected>"
frames = 0
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYUP:
if event.key == pygame.K_SPACE:
f = prompt_file()
# draw surface - fill background
window.fill(pygame.color.Color("grey"))
## update title to show filename
pygame.display.set_caption(f"Frames: {frames:10}, File: {f}")
# show surface
pygame.display.update()
# limit frames
clock.tick(FPS)
frames += 1
pygame.quit()
Notes: This will pause your game loop, as indicated by the frame counter but as events are being handled by the window manager, this shouldn't be an issue.
I'm not sure why I needed the explicit import tkinter.filedialog, but I get an AttributeError if I don't.
As for string entry in pygame, you might want to do this natively, in which case you'd be handling KEYUP events for letter keys to build the string and perhaps finishing when the user presses Enter or your own drawn button. You could continue down the tk path, in which case you'll want to use something like tkinter.simpledialog.askstring(…)
It is possible to make a file dialogue (though not using the native file explorer) using the pygame_gui module.
Simply create an instance of UIFileDialog and grab the path when the user hits 'ok':
file_selection = UIFileDialog(rect=Rect(0, 0, 300, 300), manager=manager, initial_file_path='C:\\')
if event.ui_element == file_selection.ok_button:
file_path = file_selection.current_file_path
If you want to allow selecting a directory set allow_picking_directories to True, but note that it does not allow picking an initial_file_path.
file_selection = UIFileDialog(rect=Rect(0, 0, 300, 300), manager=manager, allow_picking_directories=True)
Here's the above code in a simple program that allows you to pick a file when the button is clicked:
#!/usr/bin/env python
import pygame
import pygame_gui
from pygame_gui.windows.ui_file_dialog import UIFileDialog
from pygame_gui.elements.ui_button import UIButton
from pygame.rect import Rect
pygame.init()
window_surface = pygame.display.set_mode((800, 600))
background = pygame.Surface((800, 600))
background.fill(pygame.Color('#000000'))
manager = pygame_gui.UIManager((800, 600))
clock = pygame.time.Clock()
file_selection_button = UIButton(relative_rect=Rect(350, 250, 100, 100),
manager=manager, text='Select File')
while 1:
time_delta = clock.tick(60) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
if event.type == pygame.USEREVENT:
if event.user_type == pygame_gui.UI_BUTTON_PRESSED:
if event.ui_element == file_selection_button:
file_selection = UIFileDialog(rect=Rect(0, 0, 300, 300), manager=manager, allow_picking_directories=True)
if event.ui_element == file_selection.ok_button:
print(file_selection.current_file_path)
manager.process_events(event)
manager.update(time_delta)
window_surface.blit(background, (0, 0))
manager.draw_ui(window_surface)
pygame.display.update()

How can I fix the slowness of my pygame event loop?

I'm creating a button class for a game, and I'm using the pygame event loop to detect mouse clicks (specifically when the mouse is released) (I hear it is better the method of using pygame.mousemget_pressed()[0]). However, the event loop seems to be slow, not responding and performing the buttons function when it is clicked. I think it may be because relate to how I created the event loop in a class, but I'm not sure. Here's a sample of my code:
class Button:
"""A Button class, built for all kinds of purposes"""
def __init__(self, window, rect, message, off_color, on_color, message_color, message_font_size):
pass # just a bunch of variables that use the parameters given
def in_button(self):
mouse_pos = pygame.mouse.get_pos()
if pygame.Rect(self.rect).collidepoint(mouse_pos):
return True
def clicked(self):
if self.in_button():
pygame.event.pump()
for e in pygame.event.get():
if e.type == pygame.MOUSEBUTTONUP:
return True
# I proceed to create 5 instances using this class.
I removed some unnecessary method information in my code. If you need anything more, please help me.
You have to implement the clicked method in a different way, because there should be only one event loop in your application, not one in every button instance. pygame.event.get() empties the event queue, so calling it multiple times per frame will cause problems.
I suggest to pass the events to the buttons. In this (very simple) example I pass the pygame.MOUSEBUTTONDOWN events to the clicked method and then check if the event.pos (mouse position) of the event collides with the rect. If it returns True, I do something in the event loop in the main function.
import pygame as pg
class Button:
def __init__(self, pos):
self.image = pg.Surface((100, 40))
self.image.fill(pg.Color('dodgerblue1'))
self.rect = self.image.get_rect(center=pos)
def clicked(self, event):
"""Check if the user clicked the button."""
# pygame.MOUSE* events have an `event.pos` attribute, the mouse
# position. You can use `pygame.mouse.get_pos()` as well.
return self.rect.collidepoint(event.pos)
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
button = Button((100, 60))
number = 0
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.MOUSEBUTTONDOWN:
# Pass the MOUSEBUTTONDOWN event to the buttons.
if button.clicked(event):
number += 1 # Do something.
print('clicked', number)
screen.fill((30, 30, 30))
screen.blit(button.image, button.rect)
pg.display.flip()
clock.tick(60)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
If you want a button with several images, you can use something similar to the button class here (the first addendum) or search for more sophisticated Button classes for pygame.

Pygame play a sound when click on certain spot on GUI

I'm an amateur, very inexperience programmer. I've been working on an art project that I am programming using Pygame. I've hit a road block, ad can't figure out how to do what I need it to do.
I need it to play a specific sound when clicking on a specific place on the GUI. For example, when you click on the red button, it plays an audio file that says "red"
I also need it to be able to play sounds with clicking and dragging on the canvas part.
I hope this is enough detail. Thanks for the help!
import pygame, sys, time, random
from pygame.locals import *
# set up pygame
pygame.init()
pygame.mixer.init
pygame.mixer.get_init
bgimg="GUIsmall.gif"
inst="instructionssm.gif"
white=(255,255,255)
screen=pygame.display.set_mode((800,600), pygame.RESIZABLE)
screen.fill(white)
bg=pygame.image.load(bgimg)
instrimg=pygame.image.load(inst)
screen.blit(bg, (0,0))
pygame.display.flip()
red=pygame.mixer.Sound("red.mp3")
while True:
for event in pygame.event.get():
if event.type==pygame.QUIT:
raise SystemExit
elif event.type==pygame.MOUSEBUTTONDOWN:
red.play(0,0,0)
I think you should have a class button and a collection of buttons:
class Button:
__init__(self, name, position, image_file, sound_file):
self.name = name
self.image = pygame.image.load(image_file)
self.sound = pygame.mixer.Sound(sound_file)
self.position = position
self.rect = pygame.Rect(position, self.image.get_size())
buttons = []
buttons.add( Button("red", (0,0), "red.png", "red.mp3") )
...
Then you can use it in the main loop:
while True:
for event in pygame.event.get():
if event.type==pygame.QUIT:
raise SystemExit
elif event.type==pygame.MOUSEBUTTONDOWN:
for b in buttons:
if b.rect.collidepoint(event.pos):
b.sound.play()

Juxtapose a GtkMenuBar with other widgets?

Is it possible to place a GtkMenuBar with other widgets together , instead of showing at the top of that window ?
Or i could use buttons , but when you hover your mouse over those buttons , the menu on the buttons won't just behave like a menu , which is , when you are close to one menu item , the menu pops down directly , without click , and other menu hide automatically. Can i make buttons like that ? Or other widgets that could have: label , image , and pop down menu item is cool.
Any ideas is appreciated.
Maybe the "enter-notify-event" and "leave-notify-event", connected to buttons, may help you do the thing, with for example, a popup menu show and hide respectively.
EDIT
I finally forgot those "enter" and "leave" events whose behaviour was a little complex, and just used the "motion-notify-event"...
Now I hope it is what you want !
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
class MenuExample:
def __init__(self):
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.set_size_request(200, 100)
self.window.set_title("GTK Menu Test")
self.window.connect("delete_event", lambda w,e: gtk.main_quit())
# A vbox to put a button in:
vbox = gtk.VBox(False, 0)
self.window.add(vbox)
vbox.show()
self.popped = False
# Create a button to simulate a menu
button = gtk.Button("press me")
vbox.pack_start(button, False, False, 2)
self.window.add_events(gtk.gdk.POINTER_MOTION_MASK)
self.window.connect("motion-notify-event", self.wakeup)
self.window.show_all()
self.bmenu = gtk.Button("A single entry menu")
self.bmenu.connect("clicked", self. menuitem_response, "Click on the magic menu !")
vbox.pack_start(self.bmenu, False, False, 2)
def wakeup(self, widget, event):
#print "Event number %d woke me up" % event.type
(x, y) = self.window.get_pointer()
if y < 30:
if self.popped == False:
self.popped = True
self.bmenu.show()
elif y > 60:
if self.popped == True:
self.popped = False
self.bmenu.hide()
# Print a string when a menu item is selected
def menuitem_response(self, widget, string):
print "%s" % string
def main():
gtk.main()
return 0
if __name__ == "__main__":
MenuExample()
main()

Resources