Getting text from a entry window in Python graphics - string

from graphics import *
win = GraphWin("Hangman", 600, 600)
win.setBackground("yellow")
textEntry = Entry(Point(233,200),10)
textEntry.draw(win)
text = textEntry.getText()
testText = Text(Point(150,15), text)
testText.draw(win)
exitText = Text(Point(200,50), 'Click anywhere to quit')
exitText.draw(win)
win.getMouse()
win.close()
I'm trying to obtain text from the user in Python graphics and be able to use that input, such as manipulate it, search for it in a list, etc. To test that, I created a entry window in graphics and tried to obtain the text from that entry window and simply display it in the window, just to check if it sucessfully obtained the text.
Unfortunately, it isn't working, it just shows the 'Click anywhere to quit' and then the empty window and despite writing text in it it does nothing. What am I doing wrong?

The following comes from the documentation.
The way the underlying events are hidden in graphics.py, there is no signal when the user is done entering text in an Entry box. To signal the program, a mouse press is used above. In this case the location of the mouse press is not relevant, but once the mouse press is processed, execution can go on and read the Entry text.
You're getting the text right after you draw the Entry, so it will be empty. You need to wait for a signal, then read the Entry. This excerpt from the documentation says to wait for a mouse click then read the Entry.
So try to add the
win.getMouse()
to your code as follows
from graphics import *
win = GraphWin("Hangman", 600, 600)
win.setBackground("yellow")
textEntry = Entry(Point(233,200),50)
textEntry.draw(win)
# click the mouse to signal done entering text
win.getMouse()
text = textEntry.getText()
testText = Text(Point(150,15), text)
testText.draw(win)
exitText = Text(Point(200,50), 'Click anywhere to quit')
exitText.draw(win)
win.getMouse()
win.close()
Here's what the output looks like. Note: I made the Entry 50 wide.

Related

Making parts of canvas transparent while still detecting and blocking mouse clicks in transparent areas in tkinter?

I'm trying to make a program where the user can paint on the screen. So I want to make an invisible canvas window in fullscreen where only the user's pen marks on the canvas will be visible. The closest thing I found is this function: root.attributes("-transparentcolor","color code here"), which will make all the parts of the window that's in the color you give transparent. So if I give the second parameter the background color of the canvas, then only the pen strokes on the canvas will be visible. This is so close to what I want, except for one thing, the transparent areas can't detect or block mouse clicks! Any mouse clicks will just go through to whatever is behind the tkinter window. Is there a way to make it so the transparent areas will still block mouse clicks? I really need help on this!
Here is a much better way to do this using only tkinter. Explanation is in code comments. Basically uses two windows, one for "blocking" the mouse and being transparent using the "-alpha" attribute and the other window for "hosting" canvas and having one completely transparent color while keeping others opaque using "-transparentcolor" attribute. That also means that this is cross-platform solution too (except I think the -transparentcolor attribute differs a little bit on other OS like Linux where I think it is -splash or sth and maybe something different on MacOS):
from tkinter import Tk, Toplevel, Canvas
# setting the starting coordinate of the line so that
# on motion it is possible to immediately draw it
def set_first(event):
points.extend([event.x, event.y])
# on motion append new coordinates to the list and if there are
# 4 (the minimum), create a new line and save the id
# otherwise update the existing line
def append_and_draw(event):
global line
points.extend([event.x, event.y])
if len(points) == 4:
line = canvas.create_line(points, **line_options)
else:
canvas.coords(line, points)
# when released clear the list to not waste space
# and not necessarily but also set "id" to None
def clear_list(event=None):
global line
points.clear()
line = None
line = None # this is a reference to the current line (id)
points = [] # list to keep track of current line coordinates
line_options = {} # dictionary to allow easier change of line options
# just a variable to more easily store the transparent color
transparent_color = 'grey15'
# creating the root window which will help with drawing the line
# because it will "block" mouse because `-alpha` (0.01 seems to be the lowest value)
# attribute is used, however it makes everything transparent on the window
# so need another window to "host" the canvas
root = Tk()
root.attributes('-alpha', 0.01)
root.attributes('-topmost', True)
root.attributes('-fullscreen', True)
# just press Esc key to close the whole thing, otherwise
# it is only doable by pressing Alt + F4 or turning off
# the computer
root.bind('<Escape>', lambda e: root.quit())
# create the host window, because it allows to have only
# one transparent color while keeping the other opaque and
# visible
top = Toplevel(root)
top.attributes('-transparentcolor', transparent_color)
top.attributes('-topmost', True)
top.attributes('-fullscreen', True)
# set the focus to root because that is where events are bound
root.focus_set()
# create the canvas to draw on
canvas = Canvas(top, bg=transparent_color, highlightthickness=0)
canvas.pack(fill='both', expand=True)
# bind all the events to `root` which "blocks" mouse
# but is also almost (because it has a very small alpha value
# it is not entirely invisible but human eye won't notice much)
# invisible
root.bind('<Button-1>', set_first)
root.bind('<B1-Motion>', append_and_draw)
root.bind('<ButtonRelease-1>', clear_list)
root.mainloop()
Here is an improvable example (you may need to pip install pyautogui, ctypes is a built-in library), it is also Windows only as far as I know:
Note: The other answer using two windows, however, is a lot better but I will keep this too just for the information.
from tkinter import Tk, Canvas
import pyautogui as pag
import ctypes
data = {
'draw': True,
'cur_line_points': [],
'cur_line_id': None
}
# function taken mainly from here: https://stackoverflow.com/a/46596592/14531062
def is_pressed(btn: str = 'left') -> bool:
if btn == 'left':
btn = 0x01
elif btn == 'right':
btn = 0x02
else:
raise Warning("incorrect argument, should be 'left' or 'right'")
return ctypes.windll.user32.GetKeyState(btn) not in (0, 1)
def draw_line(canvas_):
if not data['draw']:
root.after(10, draw_line, canvas_)
return
pressed = is_pressed('left')
cur_line_points = data['cur_line_points']
cur_line_id = data['cur_line_id']
if not pressed:
if cur_line_id is not None:
canvas_.coords(cur_line_id, cur_line_points)
data['cur_line_id'] = None
cur_line_points.clear()
else:
mouse_x, mouse_y = pag.position()
cur_line_points.extend((mouse_x, mouse_y))
len_points = len(cur_line_points)
if len_points == 4:
data['cur_line_id'] = canvas_.create_line(cur_line_points)
elif len_points > 4:
canvas_.coords(cur_line_id, cur_line_points)
root.after(10, draw_line, canvas_)
transparent_color = 'grey15'
root = Tk()
root.config(bg=transparent_color)
root.attributes('-transparentcolor', transparent_color)
root.attributes('-topmost', True)
root.attributes('-fullscreen', True)
canvas = Canvas(root, bg=transparent_color, highlightthickness=0)
canvas.pack(fill='both', expand=True)
draw_line(canvas)
root.mainloop()
Basically detects if mouse button is pressed using the built-in library ctypes and if it is adds the current mouse coordinates (does that using pyautogui library which may need be installed) to a list and then draws a line based on that list (it also keeps the reference of the currently drawn line and simply changes its coordinates instead of drawing a new line each time it loops), the only slight issue is that while drawing the mouse is also interacting with the window below, highlighting text and stuff, couldn't really figure out how to remove that yet but at least you get to draw a line.

Python AutoGUI not clicking

Hey I'm pretty bad at coding, but I wanted to automate a task in a game, specifically fishing. Therefore, you have to click a button, another one pops up with a white circle around it. This circle varies its size and when its the size of the button, the circle changes its color and you have to click the button to catch a fish. Therefore I searched the first button (fishing hole), and clicked on its position. Then I look at a specific pixel around the second button, that changes color from white to pale red?. If the color changes, I want to click the button. Everything works besides this last step, it moves there if I use the pyautogui.click(x,y), but it doesn't click. Any way to make it work (bear in mind I'm not an experienced programmer)?. I tried pyautogui.click(x,y), clicking several times and so on. Thank you.
import pyautogui as pa
import time
counter = 0
def main():
global counter
# Countdown to open the game
for i in reversed(range(0,5)):
print(i)
time.sleep(1)
# Finding the fishing hole using a prior screenshot
fishing_hole_location = pa.locateOnScreen("fishing_hole.png",confidence = 0.5)
# Getting the center of the hole
fishing_hole_center = pa.center(fishing_hole_location)
# Clicking the hole
pa.click(fishing_hole_center[0], fishing_hole_center[1])
time.sleep(3)
# Locating the second button (even though it appears in the middle of the screen)
fishing_button_location = pa.locateOnScreen("fishing_button.png",confidence = 0.5)
# Finding the buttons center
fishing_button_center = pa.center(fishing_button_location)
while True:
# Looking at the pixel at the edge of the button
pix = pa.pixel(int(fishing_button_center[0]+24), int(fishing_button_center[1]))
# If the pixel doesn´t have its usual colour 5 times in a row, I want to click the button
if pix != (75,99,118):
counter+= 1
if counter >= 5:
pa.moveTo(fishing_button_center[0], fishing_button_center[1])
time.sleep(2)
pa.click()
break
else:
counter = 0
time.sleep(0.1)
main()
Try making a function like this instead of using the pre-built one -
def click(button):
pyautogui.mouseDown(button=button)
pyautogui.mouseUp(button=button)
It worked for me when I had this same problem

PySimpleGUI - not show background when window change

In my application I have multiple windows that change based on events(one close and another open) and show only one window at a time. During one window close and another open its take some time since fetch data from database and prepare for window.
Here problem is that during the time of one window close and another open user can see and feel that one is being open and another is being close by seeing the background.
What I want, until second screen is not fully loaded, first window be visible on the screen.
My current code is something like,
import PySimpleGUI as sg
layout = [[sg.Button('Users', key='show_user_list')]]
window = sg.Window('users').Layout(layout)
while True:
event, values = window.Read()
if event == 'show_user_list':
window.Close()
# code ommited here for simplicity
# do mysql stuff to fetch data
# layout2 = ...
# window2 = sg.Window('user listing').Layout(layout2)
# while True:
# event, values = window2.Read()
# ...
# like that I have multiple windows
else:
pass
How I can give users feel like the window content is changing not one window closes and another opens?
Sure, you can make sure the background is not seen by first opening your Window 2, which will be created on top of Window 1, THEN closing your Window 1.
To do this, add a .Finalize() onto the Window 2 creation. This will cause the window to immediate show up. Then on the next line, close Window 1.
import PySimpleGUI as sg
layout = [[sg.Button('Users', key='show_user_list')]]
window = sg.Window('users').Layout(layout)
while True:
event, values = window.Read()
if event == 'show_user_list':
# code ommited here for simplicity
# do mysql stuff to fetch data
# layout2 = ...
# window2 = sg.Window('user listing').Layout(layout2).Finalize()
# window.Close()
# while True:
# event, values = window2.Read()
# ...
# like that I have multiple windows
else:
pass
The key to making this kind of window update work is to create the windows at the same location. The default is to make windows that are centered on the screen. This means if your windows are not the same size then you'll likely notice a small "blip" as you change from one to the other. But it shouldn't look bad because it'll happen so quickly.
If you really want to get fancy, you can add another step which will make the switch between the windows even smoother. This new step involves creating window 2 with Alpha=0, meaning that it's invisible, then after it's fully formed (using Finalize()) you change the Alpha to 1 which will make the window appear.
import PySimpleGUI as sg
layout = [[sg.Text('Example of window-replacement')],
[sg.Combo(['abdeffg', 'rrrfwwew'], size=(10, 4))],
[sg.B('Enable Filter'), sg.B('Warning'), sg.B('Reopen')],]
window = sg.Window('My Text Editor', layout)
while True: # Event Loop
event, values = window.Read()
if event is None:
break
print(event, values)
if event == 'Reopen':
layout2 = [[sg.Text('This is a completely different window')],
[sg.Combo(['abdeffg', 'rrrfwwew'], size=(10, 4))],
[sg.B('Enable Filter'), sg.B('Warning'), sg.B('Reopen')], ]
window2 = sg.Window('My Text Editor', layout2, alpha_channel=0).Finalize()
window2.SetAlpha(1)
window.Close()
window = window2
window.Close()
This removed some of the "painting" of the window that I was seeing. That shouldn't happen because I use this same trick when creating the window to begin with. Alpha is used to hide the window while it's being created.

Is it possible to make Tkinter scrollbars move independently of each other in different Toplevel windows?

Imagine there are two Tkinter.Toplevel() windows, called Window_1 and Window_2, which can be opened by clicking the same button (lets called Button_0).
Button_0 is pressed and Window_1 pops up. In Window_1, I can scroll up and down using a mouse pad (MAC OS). After that, I left Window_1 open.
Button_0 is pressed again and Window_2 pops up, while Window_1 stays open. In Window_2, I can again scroll up and down.
Now, I go back to Window_1 and try to scroll using mouse pad, contents in Window_1 DO NOT MOVE, but contents in Window_2 DO MOVE.
Then I close Window_2, and try to scroll on Window_1, this time I got error messages asking for a canvas on Window_2.
I did bind function,
def on_vertical(canvas,event):
canvas.yview_scroll(-3 * event.delta, 'units')
to a canvas inside each windows. As far as I know about the error, it seems that this function could not be used twice at the same time (both windows are opened).
I would like the way that when both Windows stay open. While on each window, I can scroll up-down while the another one do not move. Is it possible to code that?
This is the code example (please do noted that the Window name is not corrected label.)
from tkinter import *
######################## FUNCTIONS (DEF) ########################
def on_vertical(canvas,event):
canvas.yview_scroll(-3 * event.delta, 'units')
######################## FUNCTIONS (CLASS) ########################
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.init_window()
#INITIAL WINDOW
def init_window(self):
self.master.title("Main Window")
self.pack(fill=BOTH, expand=1)
Button(self, text="Button_0",command = self.load_and_print).place(x = 7, y = 95)
# creating a button instance
Button(self, text="EXIT PROGRAM", command=self.client_exit).place(x=500, y=250)
#OPEN A NEW WINDOW CONTAINING STOCK LISTS
def load_and_print(self):
new_window = Toplevel(self)
new_window.title("Window")
canvas = Canvas(new_window, width = 800, height = 500, scrollregion = (0, 0, 0, 2500))
frame = Frame(canvas)
vbar = Scrollbar(new_window, orient = VERTICAL, command = canvas.yview)
vbar.pack(side = RIGHT,fill = Y)
canvas.create_window(0,0, window = frame, anchor = NW)
canvas.config(yscrollcommand = vbar.set)
canvas.pack(side = TOP,expand = True,fill = BOTH)
canvas.bind_all('<MouseWheel>', lambda event, canvas=canvas: on_vertical(canvas,event))
#MAKE PROGRAM EXIT
def client_exit(self):
exit()
######################## MAIN PROGRAMME ########################
#call window
root = Tk()
#size of the window
root.geometry("700x300")
app = Window(root)
root.mainloop()
root.update()
The problem is that you are using bind_all instead of bind for the mousewheel event.
Because you're using bind_all, each time you create a new window it replaces the old binding with a new binding. No matter which window gets the event, your function will always only work for the last window to be created. And, of course, when that window is destroyed then the mouse binding will throw an error since the canvas no longer exists.
Using bind
One solution is simple: use bind instead of bind_all.
canvas.bind_all('<MouseWheel>', lambda event, canvas=canvas: on_vertical(canvas,event))
Using bind_all
If you want the benefits of bind_all -- namely, that the scrolling works even if the mouse is over some other widget, you need to modify on_vertical to figure out which canvas to scroll at the time that it is running instead of having the canvas being passed in.
You can do that with a little bit of introspection. For example, the event object knows which widget received the event. From that you can figure out which window the mouse is in, and from that you can figure out which canvas to scroll.
For example, move the binding up to the __init__ and change it like this:
self.bind_all('<MouseWheel>', on_vertical)
Next, change on_vertical to figure out which canvas to scroll. In the following example I assume each toplevel has exactly one canvas and that you always want to scroll that canvas (ie: you lose the ability to scroll text widgets and listboxes)
If that's not the case, you can add whatever logic you want to figure out which widget to scroll.
def on_vertical(event):
top = event.widget.winfo_toplevel()
for child in top.winfo_children():
if child.winfo_class() == "Canvas":
child.yview_scroll(-3 * event.delta, 'units')
break

tkinter insert into output text field (output.insert()) is not working

I have a Tkinter GUI, Where i have a input text field and output text field and
a button. User typying anything in text field and clicking the button then it have to be printed in the output field. But here the input values are not inserting into the output field.
from tkinter import *
base = Tk()
base.title('Demo')
base.geometry("400x500")
base.resizable(width=FALSE, height=FALSE)
outputwindow = Text(base, bd=0, bg="white", height="8", width="50", font="Arial",)
# outputwindow.insert(END, "Connecting to your partner..\n")
outputwindow.config(state=DISABLED)
#Bind a scrollbar to the Chat window
scrollbar = Scrollbar(base, command=outputwindow.yview, cursor="heart")
outputwindow['yscrollcommand'] = scrollbar.set
EntryBox = Text(base, bd=0, bg="white",width="29", height="5", font="Arial")
def ClickAction():
input=EntryBox.get("1.0",END)
print(input)
EntryBox.delete('1.0',END)
outputwindow.insert(END, input)
SendButton = Button(base, font=30, text="Send", width="12", height=5,bd=0, bg="lightgray", command=ClickAction)
#Place all components on the screen
scrollbar.place(x=376,y=6, height=386)
outputwindow.place(x=6,y=6, height=386, width=370)
EntryBox.place(x=128, y=401, height=90, width=265)
SendButton.place(x=6, y=401, height=90)
base.mainloop()
I have tried other programs for this same purpose, it works fine. But here i can't process and i can't find the issue.(i'm just learning tkinter)
When a text widget is disabled you can't put text in it, not even with .insert() or .delete(). To modify the text you need to change the state to normal, insert your text and then change the state back to disabled:
outputwindow.config(state=NORMAL)
outputwindow.insert(END, input)
outputwindow.config(state=DISABLED)

Resources