How to get variables from toplevel window to main window? - python

I have a code that gets keyinputs and show it on a entrybox in the toplevel window of the main window. In the main window I have a listbox and I am wishing to get the keyinput that is shown on the entrybox to be enlisted when the confirm button is pressed on toplevel.
I tried several ways to get evt.keysym to my listbox but all failed.
class EntryBox(tk.Entry):
def __init__(self, master, cnf = {}, **kw):
kw = tk._cnfmerge((kw, cnf))
kw['justify'] = kw.get('justify', 'center')
kw['width'] = 15
kw['state'] = 'readonly'
super(EntryBox, self).__init__(master=master, **kw)
self.unbind_class('Entry', '<BackSpace>')
self.unbind_class('Entry', '<Key>')
self.bind_class(self, '<Key>', self._display)
def _display(self, evt):
self['state'] = 'normal'
self.delete('0', 'end')
self.insert('0', str(evt.keysym))
self['state'] = 'readonly'
class Keyboard:
def __init__(self):
self.kb = tk.Toplevel()
kb_frame = ttk.Frame(self.kb)
kb_frame.grid(column=0, row=1, pady=(7, 19))
ttk.Label(kb_frame, text='Enter Key').grid(column=0, row=0, pady=4)
entry = EntryBox(kb_frame)
entry.grid(column=0, row=1)
# Confirm button
self.co_button = ttk.Button(self.kb, text='Confirm')
self.co_button.grid(column=0, row=2)
class Main:
def __init__(self):
self.win = tk.Tk()
# listbox
lb_frame = tk.Frame(self.win)
lb_frame.grid(column=0, row=0)
scrollbar = tk.Scrollbar(lb_frame, orient='vertical')
scrollbar.grid(column=1, row=0, sticky='NS', pady=(12, 4))
listbox = tk.Listbox(lb_frame, selectmode='extended', width=25,
height=16,
yscrollcommand=scrollbar.set, activestyle='none')
listbox.grid(column=0, row=0, sticky='NSEW', padx=(6, 0), pady=(12, 4))
scrollbar.config(command=listbox.yview)
# button to open toplevel
bt_frame = ttk.Frame(self.win)
bt_frame.grid(column=2, row=0, rowspan=2)
self.kb_button = ttk.Button(bt_frame, text='KeyBoard', command=KeyBoard)
self.kb_button.grid(column=0, row=0)
main = Main()
main.win.mainloop()

To get values from one class to another class you've to link them. Inheriting the Widgets directly to the class will help you a lot in establishing a connection between Tk() window and Toplevel() Window.
One more thing when a Keyboard window is already opened disable the button by configure state = 'disabled' so the user won't open another one by mistake and when Keyboard window is closed re-enable the button by state = 'normal'.
Here is the complete code:
import tkinter as tk
import tkinter.ttk as ttk
class EntryBox(tk.Entry):
def __init__(self, master, cnf = {}, **kw):
kw = tk._cnfmerge((kw, cnf))
kw['justify'] = kw.get('justify', 'center')
kw['width'] = 15
kw['state'] = 'readonly'
super(EntryBox, self).__init__(master=master, **kw)
self.bind_class(self, '<Key>', self._display)
def _display(self, evt):
self['state'] = 'normal'
self.delete('0', 'end')
self.insert('0', str(evt.keysym))
self['state'] = 'readonly'
class Keyboard(tk.Toplevel):
def __init__(self, master=None, cnf={}, **kw):
super(Keyboard, self).__init__(master=master, cnf=cnf, **kw)
self.master = master
kb_frame = ttk.Frame(self)
kb_frame.grid(column=0, row=1, pady=(7, 19))
ttk.Label(kb_frame, text='Enter Key').grid(column=0, row=0, pady=4)
self.entry = EntryBox(kb_frame)
self.entry.grid(column=0, row=1)
# This protocol calls the function when clicked on 'x' on titlebar
self.protocol("WM_DELETE_WINDOW", self.Destroy)
# Confirm button
self.co_button = ttk.Button(self, text='Confirm', command=self.on_press)
self.co_button.grid(column=0, row=2)
def on_press(self):
key = self.entry.get()
# Condition to not have duplicate values, If you want to have duplicate values remove the condition
if key not in self.master.lb.get('0', 'end') and key:
# Insert the Key to the listbox of mainwindow
self.master.lb.insert('end', key)
def Destroy(self):
# Enable the button
self.master.kb_button['state'] = 'normal'
# Then destroy the window
self.destroy()
class Main(tk.Tk):
def __init__(self):
super(Main, self).__init__()
bt_frame = ttk.Frame(self)
bt_frame.grid(column=2, row=0, rowspan=2)
self.kb_button = ttk.Button(bt_frame, text='KeyBoard', command=self.on_press)
self.kb_button.grid(column=0, row=0)
self.lb = tk.Listbox(bt_frame)
self.lb.grid(column=0, row=1)
def on_press(self):
# Creating a toplevel window and passed self as master parameter
self.Kb = Keyboard(self)
# Disabled the button
self.kb_button['state'] = 'disabled'
main = Main()
main.mainloop()

Related

How to change focus of simpledialog in Tkinter?

I have several simpledialog popup windows. The first one that shows is in focus and after it closes then every single one after that is not in focus. The simpledialog code is this:
from tkinter import *
from tkinter import messagebox
import os
import tkinter as tk
from tkinter import simpledialog
def doing_stocks():
name = myaskstring("Input data", "Enter box number:", parent = root)
name2 = myaskstring("Input data2", "Enter box number2:", parent = root)
name3 = myaskstring("Input data3", "Enter box number3:", parent = root)
class My_QueryString(tk.simpledialog._QueryString):
def body(self, master):
self.bind('<KP_Enter>', self.ok)
self.bind('<Return>', self.ok)
w = Label(master, text=self.prompt, justify=LEFT)
w.grid(row=0, padx=5, sticky=W)
self.entry = Entry(master, name="entry")
self.entry.grid(row=1, padx=5, sticky=W+E)
if self.initialvalue is not None:
self.entry.insert(0, self.initialvalue)
self.entry.select_range(0, END)
root.update_idletasks()
self.entry.focus_force()
return self.entry
def myaskstring(title, prompt, **kw):
d = My_QueryString(title, prompt, **kw)
root.update_idletasks()
answer = d.result
d.destroy()
return answer
root = Tk()
root.geometry("700x761")
label1 = Label(root, text="")
label1.place(x = 0, y = 0)
button2 = Button(root, text = "Doing Stocks", command=doing_stocks).place(x = 300, y = 340)
root.mainloop()
This is a simplified version of the code. I call my simpledialog popups like this:
myaskstring("Title", "Prompt", parent = root)
In the doing_stocks() method the first time I call myaskstring the window and the entry field will be in focus then all times after that it won't be. How can I make every simpledialog be in focus when it appears on the screen?
I agree is seems odd that the focus isn't set to each My_QueryString instance when it's initialized. Regardless of the reason why, a workaround for it not happening is to bind a <Map> event handler function to the dialog's Entry widget to shift keyboard focus to itself whenever it's made visible.
The code below shows an implementation doing that. It's based on your code with the important changes indicated with # ALL CAP comments to make them stand out.
import tkinter as tk
from tkinter import simpledialog
from tkinter.constants import *
def doing_stocks():
name = myaskstring("Input data", "Enter box number:", parent=root)
name2 = myaskstring("Input data2", "Enter box number2:", parent=root)
name3 = myaskstring("Input data3", "Enter box number3:", parent=root)
class My_QueryString(tk.simpledialog._QueryString):
def body(self, master):
self.bind('<KP_Enter>', self.ok)
self.bind('<Return>', self.ok)
w = tk.Label(master, text=self.prompt, justify=LEFT)
w.grid(row=0, padx=5, sticky=W)
self.entry = tk.Entry(master, name="entry")
self.entry.grid(row=1, padx=5, sticky=W+E)
self.entry.bind('<Map>', self.on_map) # <--- ADDED.
if self.initialvalue is not None:
self.entry.insert(0, self.initialvalue)
self.entry.select_range(0, END)
root.update_idletasks()
# self.entry.focus_force() # <--- NOT NEEDED.
return self.entry
# ADDED METHOD.
def on_map(self, event):
self.entry.focus_force()
def myaskstring(title, prompt, **kw):
d = My_QueryString(title, prompt, **kw)
root.update_idletasks()
answer = d.result
# d.destroy() # <--- NOT NEEDED.
return answer
root = tk.Tk()
root.geometry("700x761")
label1 = tk.Label(root, text="")
label1.place(x = 0, y = 0)
button2 = tk.Button(root, text = "Doing Stocks", command=doing_stocks)
button2.place(x = 300, y = 340)
root.mainloop()

Tkinter - Deleting widgets of a Canvas when calling Misc.lower() method

I'm just working on a GUI with tkinter in python. But I'm having some issues here. I'm creating a button that once it's clicked it will go back a page (Misc.lower(canvas2) and Misc.lift(canvas1)). Although i created a window for the button on the self.canvas2, the buttons is still there when the canvas is lowered.
Here is my code:
from tkinter import *
class Application:
def __init__(self):
self.window = Tk()
self.window.geometry("1280x720")
self.canvas1 = Canvas(self.window, highlightthickness=0, bg="#1b1b1b")
self.canvas2 = Canvas(self.window, highlightthickness=0, bg="red2")
self.initButtons()
self.window.mainloop()
def initButtons(self, *args):
buttons = {}
self.buttonList = []
buttons['backButtons'] = Button(self.window, bd=0, activebackground="#1b1b1b", bg="#1b1b1b", text='Back', fg="#ffffff")
self.buttonList.append(buttons['backButtons'])
self.initSecondWindow()
def initFirstWindow(self, *args):
Misc.lower(self.canvas2)
self.canvas1.place(relwidth=1, relheight=1)
def initSecondWindow(self, *args):
backButton = self.buttonList[0]
backButton.config(command=self.initFirstWindow)
self.canvas2.place(relwidth=1, relheight=1)
self.canvas2.create_window(640, 360, window=backButton)
Application()
I read a bit about canvas.delete(), but I'm not sure how it works.
Thanks in advance!
Not sure why you would just want to hide it though, then how would the users redirect to the previous page? Is this what you wanted?
from tkinter import *
class Application:
def __init__(self):
# self.backButton = None
self.window = Tk()
self.window.geometry("1280x720")
self.canvas1 = Canvas(self.window, highlightthickness=0, bg="#1b1b1b")
self.canvas2 = Canvas(self.window, highlightthickness=0, bg="red2")
self.initButtons()
self.window.mainloop()
def initButtons(self, *args):
buttons = {}
self.buttonList = []
buttons['backButtons'] = Button(self.window, bd=0, activebackground="#1b1b1b", bg="#1b1b1b", text='Back', fg="#ffffff")
self.buttonList.append(buttons['backButtons'])
self.initSecondWindow()
def initFirstWindow(self, *args):
Misc.lower(self.canvas2)
self.canvas2.delete(self.res)
self.canvas1.place(relwidth=1, relheight=1)
def initSecondWindow(self, *args):
backButton = self.buttonList[0]
backButton.config(command=self.initFirstWindow)
self.canvas2.place(relwidth=1, relheight=1)
self.res = self.canvas2.create_window(640, 360, window=backButton)
Application()

Tkinter add widget on scroolable frame last item does not shown

I want to create a window that allows entering one-to-many fields for the file transfer.
I created a Scrollable Frame and I am adding Entry-Text pairs in runtime. If I click the button for the first time, everything goes well. After the second time, nothing happens on the UI side. It works perfectly after the second click. But I saw that all pairs added successfully, just the UI did not display it. Does anybody know how to fix it?
import tkinter as tk
class VerticalScroolFrame(tk.Frame):
"""A frame with a vertical scroolbar"""
def __init__(self, master, *args, **kwargs):
main_frame = tk.Frame(master)
main_frame.grid()
main_frame.rowconfigure(0, weight=1)
main_frame.columnconfigure(0, weight=1)
self._canvas = tk.Canvas(main_frame)
self._canvas.grid(row=0, column=0, sticky=tk.NSEW)
scrollbar = tk.Scrollbar(main_frame, command=self._canvas.yview)
scrollbar.grid(row=0, column=1, sticky=tk.N+tk.S+tk.W)
self._canvas.configure(yscrollcommand=scrollbar.set)
self._canvas.bind('<Configure>', lambda *_: self.on_configure())
super().__init__(self._canvas, *args, **kwargs)
self._canvas.create_window((0, 0), window=self, anchor=tk.NW)
def on_configure(self):
"""update scrollregion after starting 'mainloop'
when all widgets are in self._canvas. And also need to be triggered
whenever a widget added as a child.
"""
self._canvas.configure(scrollregion=self._canvas.bbox('all'))
class AdvancedTransfer:
"""Opens a window that allows you to enter source file list
and targets for them. One-to-many relation.
"""
def __init__(self, root):
self._scroolable_frame = VerticalScroolFrame(root)
self._entry_text_dict = {}
self._button = tk.Button(root, text="Add", command=self.add_item)
self._button.grid()
def add_item(self):
"""Add entry-text widget group"""
row = len(self._entry_text_dict)
entry = tk.Entry(self._scroolable_frame)
entry.insert(0, "row number: {0}".format(row))
entry.grid(row=row, column=0)
text = tk.Text(self._scroolable_frame)
text.grid(row=row, column=1)
self._entry_text_dict[entry] = text
self._scroolable_frame.on_configure()
def main():
root = tk.Tk()
main_frame = tk.Frame(root)
main_frame.grid()
AdvancedTransfer(main_frame)
root.mainloop()
if __name__ == "__main__":
main()
Repro steps:
Run the code below.
Click the button two times.
You should see 2 pairs but only 1 pair shown instead.
It is because you bind <Configure> event on wrong widget. You should bind on the internal frame (i.e. instance of VerticalScroolFrame) instead of canvas (self._canvas):
class VerticalScroolFrame(tk.Frame):
"""A frame with a vertical scroolbar"""
def __init__(self, master, *args, **kwargs):
main_frame = tk.Frame(master)
main_frame.grid()
main_frame.rowconfigure(0, weight=1)
main_frame.columnconfigure(0, weight=1)
self._canvas = tk.Canvas(main_frame)
self._canvas.grid(row=0, column=0, sticky=tk.NSEW)
scrollbar = tk.Scrollbar(main_frame, command=self._canvas.yview)
scrollbar.grid(row=0, column=1, sticky=tk.N+tk.S+tk.W)
self._canvas.configure(yscrollcommand=scrollbar.set)
#self._canvas.bind('<Configure>', lambda *_: self.on_configure())
super().__init__(self._canvas, *args, **kwargs)
self._canvas.create_window((0, 0), window=self, anchor=tk.NW)
# bind <Configure> event on itself
self.bind('<Configure>', lambda _: self.on_configure())

how can i have the function to enable / disable the entries near the buttons?

import tkinter as tk
class App(tk.Frame):
def __init__(self):
super().__init__()
self.pack()
self.buttons = []
self.entries = []
for n_row in range(20):
button = tk.Button(self, text='disable')
button.grid(row=n_row, column=1)
entry = tk.Entry(self)
entry.grid(row=n_row, column=0, pady=(10,10))
self.entries.append(entry)
self.buttons.append(button)
#each button must activate / deactivate the entry of the same row
if __name__ == '__main__':
root = tk.Tk()
app = App()
root.mainloop()
You will want to use lambda here to keep the correct index value for each of the entry fields.
You can use the value of n_row to keep track of what button links to what entry.
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.entries = []
for n_row in range(20):
self.entries.append(tk.Entry(self))
self.entries[-1].grid(row=n_row, column=0, pady=(10,10))
tk.Button(self, text='disable', command=lambda i=n_row: self.disable_entry(i)).grid(row=n_row, column=1)
def disable_entry(self, ndex):
self.entries[ndex]['state'] = 'disabled'
if __name__ == '__main__':
App().mainloop()
For switching between both states you can use the same method to check if the state is one thing and then change it to another.
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.entries = []
self.buttons = []
for n_row in range(20):
self.entries.append(tk.Entry(self))
self.buttons.append(tk.Button(self, text='disable', command=lambda i=n_row: self.toggle_state(i)))
self.entries[-1].grid(row=n_row, column=0, pady=(10, 10))
self.buttons[-1].grid(row=n_row, column=1)
def toggle_state(self, ndex):
if self.entries[ndex]['state'] == 'normal':
self.entries[ndex]['state'] = 'disabled'
self.buttons[ndex].config(text='Enable')
else:
self.entries[ndex]['state'] = 'normal'
self.buttons[ndex].config(text='Disable')
if __name__ == '__main__':
App().mainloop()
Question: A Button that activate / deactivate the entry in the same Row
Define your own widget which combines tk.Button and tk.Entry in a own tk.Frame.
The Entry state get toggeld on every click on the Button.
Relevant
- extend a tkinter widget using inheritance.
import tkinter as tk
class ButtonEntry(tk.Frame):
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.button = tk.Button(self, text='\u2327', width=1, command=self.on_clicked)
self.button.grid(row=0, column=0)
self.entry = tk.Entry(self)
self.columnconfigure(1, weight=1)
self.entry.grid(row=0, column=1, sticky='ew')
def on_clicked(self):
b = not self.entry['state'] == 'normal'
state, text = {True: ('normal', '\u2327'), False: ('disabled', '\u221A')}.get(b)
self.entry.configure(state=state)
self.button.configure(text=text)
Usage:
class App(tk.Tk):
def __init__(self):
super().__init__()
for row in range(5):
ButtonEntry(self).grid(row=row, column=1)
if __name__ == '__main__':
App().mainloop()
Tested with Python: 3.5 - 'TclVersion': 8.6 'TkVersion': 8.6
You can set the stateof a widget to 'disabled'
from tkinter import *
root = Tk()
entry = Entry(root, state = 'disabled')
To disable an individual item:
entry['state'] = 'disabled'
When you push the button, get it's location in the list, than compare that to the entry in the other list. If they match, change its state.

Lift and raise a Canvas over a Canvas in tkinter

I'm creating a game, and I am using tkinter to build the GUI.
In this game, I want the user to start with a window, filled by a Canvas, with an image as background, and some buttons in some sort of item windows. And this Canvas is the home Canvas of the game.
The problem is that I want the user to click on a Button to land on other Canvas, who will host other things like the map or others buttons for others actions. Actually, I would like to superimpose several canvas (as in the Z-index method), and put a canvas on the top of the list, when I want it (if I click on a button for example).
I already searched and I had to change my mind several times, and now I really don't know how to do it.
I find the following code here on Stack Overflow, but it is coded for Python 2 (I think), and I'm starting coding in Python 3, so I am not able to translate it to Python 3 and solve my problem.
import Tkinter as tk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.frame = tk.Frame(self)
self.frame.pack(side="top", fill="both", expand=True)
self.label = tk.Label(self, text="Hello, world")
button1 = tk.Button(self, text="Click to hide label",
command=self.hide_label)
button2 = tk.Button(self, text="Click to show label",
command=self.show_label)
self.label.pack(in_=self.frame)
button1.pack(in_=self.frame)
button2.pack(in_=self.frame)
def show_label(self, event=None):
self.label.lift(self.frame)
def hide_label(self, event=None):
self.label.lower(self.frame)
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
My code using grid :
from tkinter import *
fenetre = Tk()
fenetre.title(my game)
# acceuil
# variable acceuil
largeur = 700
hauteur = 430
BG_acceuil = PhotoImage(file="BG_acceuil.gif")
acceuil = Canvas(fenetre, width=largeur, height=hauteur)
acceuil.create_image(0, 0, anchor=NW, image=BG_acceuil)
acceuil.grid(row=0)
acceuil.pack()
# fond
fond = PhotoImage(file="BG_acceuil.gif")
acceuil2 = Canvas(fenetre, width=largeur, height=hauteur)
acceuil2.create_image(0, 0, anchor=NW, image=fond)
acceuil2.pack()
# variable bt_jouer
x0 = 80
y0 = 230
class hide_me():
def hide_me(event, widget, pos):
widget.grid_forget()
def show_me(event, widget, pos):
widget.grid(row=pos)
# Boutton jouer
BT_jouer = Button(acceuil, text="Jouer", command=hide_me())
BT_jouer.configure(width=10, activebackground="#33B5E5", relief=GROOVE)
BT_jouer_window = acceuil.create_window(x0, y0, window=BT_jouer,)
BT_jouer.bind('<Button-1>', lambda event: hide_me(event, BT_jouer, 1))
BT_jouer.grid(row=1)
# Bouton règle
BT_regle = Button(acceuil2, text="Règles", command=fenetre.destroy)
BT_regle.configure(width=10, activebackground="#33B5E5", relief=FLAT, bd=0)
BT_regle_window = acceuil2.create_window(x0, y0 + 50, window=BT_regle)
# Boutton quitter
BT_quit = Button(acceuil, text="Quitter", command=fenetre.destroy)
BT_quit.configure(width=10, activebackground="#33B5E5", relief=FLAT)
BT_quit_window = acceuil.create_window(x0, y0 + 100, window=BT_quit)
fenetre.mainloop()
The answer is very easy: To convert to Python3, change Tkinter to tkinter, and it works!
import tkinter as tk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.frame = tk.Frame(self)
self.frame.pack(side="top", fill="both", expand=True)
self.label = tk.Label(self, text="Hello, world")
button1 = tk.Button(self, text="Click to hide label",
command=self.hide_label)
button2 = tk.Button(self, text="Click to show label",
command=self.show_label)
self.label.pack(in_=self.frame)
button1.pack(in_=self.frame)
button2.pack(in_=self.frame)
def show_label(self, event=None):
self.label.lift(self.frame)
def hide_label(self, event=None):
self.label.lower(self.frame)
def main():
app = SampleApp()
app.mainloop()
return 0
if __name__ == '__main__':
main()
Note: You are not really hiding the label - it still occupies space on the canvas. The following code, from this entry, really removes the item. It can then be recalled with a pack() call:
from Tkinter import *
def hide_me(event):
event.widget.pack_forget()
root = Tk()
btn=Button(root, text="Click")
btn.bind('<Button-1>', hide_me)
btn.pack()
btn2=Button(root, text="Click too")
btn2.bind('<Button-1>', hide_me)
btn2.pack()
root.mainloop()
I did some testing, and made an equivalent program to yours... The only problem is that the unhidden widget is always packed at the end:
from tkinter import *
def hide_me(event, widget):
widget.pack_forget()
def show_me(event, widget):
widget.pack()
root = Tk()
lbl = Label(root, text="Victim")
btn = Button(root, text="Hide the victim")
btn.bind('<Button-1>', lambda event: hide_me(event, lbl))
btn.pack()
btn2 = Button(root, text="Show the victim")
btn2.bind('<Button-1>', lambda event: show_me(event, lbl))
btn2.pack()
lbl.pack()
root.mainloop()
A better version uses the grid() packer. Here you can actually restore the 'forgotten' widget to its original position. Only slightly more complicated :)
from tkinter import *
def hide_me(event, widget, pos):
widget.grid_forget()
def show_me(event, widget, pos):
widget.grid(row = pos)
root = Tk()
lbl = Label(root, text="Victim")
lbl.grid(row = 0)
btn = Button(root, text="Hide the victim")
btn.bind('<Button-1>', lambda event: hide_me(event, lbl, 0))
btn.grid(row = 1)
btn2 = Button(root, text="Show the victim")
btn2.bind('<Button-1>', lambda event: show_me(event, lbl, 0))
btn2.grid(row = 2)
root.mainloop()
EDIT: Another observation from the comments: Bryan Oakley commented that if you use .grid_remove() instead of .grid_forget(), then the coordinates will not be lost, and a simple .grid() will restore the widget at its location.

Categories