I want to present several questions, one after another. The first question is shown as I like, with the cursor set in the entry field. Then I destroy the window and call the function again to create a new window. This time the window is not shown in the front and therefore I first have to click on the screen in order to have the cursor set to the entry field. Also the escape key does not work until I click on the screen to bring the window to the top. I'd be very happy for your help!
Thank you in advance!
Here's my code:
from Tkinter import *
def text_input_restricted(fn,question, nr_letters, limit, len_min, len_max,keys, justify):
class MyApp():
def validate(root, S):
return all(c in keys for c in S)
def __init__(self, q= None):
#save response after "next"-button has been clicked
def okClicked():
lines = e.get()
if len_min < len(lines) < len_max:
lines = unicode(lines).encode('utf-8')
datFile = open(fn, "a")
datFile.write(" '%s'"%(lines))
datFile.close()
self.root.destroy()
self.root = Tk()
vcmd = (self.root.register(self.validate), '%S')
#quit if escape-key has been pressed
self.root.bind('<Escape>', lambda q: quit())
#colors
color = '#%02x%02x%02x' % (200, 200, 200)
self.root.configure(bg=color)
#set window size to screen size
RWidth=MAXX
RHeight=MAXY
self.root.geometry(("%dx%d")%(RWidth,RHeight))
#remove buttons (cross, minimize, maximize)
self.root.overrideredirect(1)
#remove title
self.root.title("")
#item
labelWidget = Label(self.root,text=question, font=("Arial", int(0.02*MAXX)), bd=5, bg=color, justify="center")
labelWidget.place(x=0, y=RHeight/40,width=RWidth)
#"next"-button
ok_width = RWidth/15
ok_height = RWidth/15
okWidget = Button(self.root, text= "next", command = okClicked, font=("Arial",int(0.015*MAXX)), bd=5, justify="center")
okWidget.place(x=RWidth/2-ok_width/2,y=13*RHeight/40, width=ok_width,height=ok_height)
def callback(sv):
c = sv.get()[0:limit]
sv.set(c)
sv = StringVar()
width=nr_letters * int(0.02*MAXX)*1.3
sv.trace("w", lambda name, index, mode, sv=sv: callback(sv))
e = Entry(self.root, textvariable=sv,font=("Arial", int(0.02*MAXX)),justify=justify,validate="key", validatecommand=vcmd)
e.place(x=RWidth/2-width/2, y=9*RHeight/40, width=width)
#show cursor
e.focus_set()
self.root.mainloop()
MyApp()
MAXX=1366
MAXY=768
fn = "D:/test.dat"
text_input_restricted(fn = fn, question=u"f for female, m for male", nr_letters=1, limit =1, len_min =0, len_max=2, keys = 'fm', justify="center")
text_input_restricted(fn = fn, question="How old are you?", nr_letters=2,limit=2, len_min = 1, len_max = 3, keys = '1234567890',justify="center")
In Tk you use the raise command to bring a window to the front of the Z-order. However, raise is a keyword in Python so this has been renamed to lift. Provided your application is still the foreground application you can call the lift() method on a toplevel widget. If the application is not the foreground application then this will raise the window but only above other windows from the same application. On Windows this causes the taskbar icon for your application to start flashing.
You might do better to destroy the contents of the toplevel and replace them. Or even better - create a number of frames holding each 'page' of your application and toggle the visibility of each frame by packing and pack_forgetting (or grid and grid forget). This will avoid loosing the focus completely - you can just set the focus onto the first widget of each frame as you make it visible.
Related
I have GUI built with tkInter like below.
My actual window is a bit more complex but I think this suffices for the question I have.
The window has Entry, 2 Button's & a scrollText widget.
With the Browse button, user selects the file that she wants to process and clicking the process button call the processFile function to process the file.
What I want to do now is to print the 'progress' of in the scrollText widget. Nothing but the values of the print statements should appear in the scrollText widget one after the other. It need not be print statements, it be some other methods also (I do not know what other methods are possible).
Also, if there is a error. If you click the process button without selecting the file for example, the program encounters an error and stops. That error also needs to be shown in the scrollText widget.
How do I accomplish this?
import tkinter as tk
import tkinter.filedialog
from tkinter import scrolledtext
import pandas as pd
root=tk.Tk() # create a window within which all buttons and fileds are located
root.geometry('700x300') #defien the sixe of the window
def quit_me():
"""Function to end the process when 'x' is clicked"""
root.quit()
root.destroy()
def browsefunc(entry):
"""Function to browse the file & set the browsed file path to the
entry box in the GUI"""
browsedFile =tkinter.filedialog.askopenfilename(filetypes=(("text files","*.txt"),("All files","*.*")))
if not browsedFile: #if cancel for pressed instead of selectign a file
pass
else: #if the file was seelcted
entry.delete(0,tk.END)
entry.insert(0, browsedFile)
def processFile(fileToProcess):
df = pd.read_csv(fileToProcess, header=None, sep=';')
print('df created...')
print('df processes...')
print('df processessing complete...')
return df
fileNameEntry=tk.Entry(root)
fileNameEntry.place(x = 30, y = 30,width = 400, height = 20)
fileNameEntryButton =tk.Button(master=root,
text="Browse",
command=lambda: browsefunc(fileNameEntry))
fileNameEntryButton.place(x = 460, y = 30, width = 45, height = 20)
executeButton =tk.Button(master=root,
text="Process",
fg="#45ad61",
command= lambda: processFile(fileNameEntry.get()))
executeButton.place(x = 400, y = 60, width = 150, height = 25)
progressWindow = scrolledtext.ScrolledText(root,
bg='white',
relief="groove")
progressWindow.place(x = 30, y = 100 ,height=100,width=600,)
root.protocol("WM_DELETE_WINDOW", quit_me)
root.mainloop()
Before you read:
I am a complete novice at programming let alone Python. I don't expect solutions. I will appreciate it even if I just get a pointer in the right direction. If you feel the way I've requested help is unrefined, please let me know so next time I need help, I'll do a better job at asking.
Goal:
I'm making a program to test how to get a program to respond depending on which widget has to focus on. Currently, I'm just having it respond by displaying 'what' has focus.
Eventually, once I've figured this out I can implement what I've learnt into another program I'm working on was focusing on an entry field will result in the field clearing of all input. So naturally, I will want this program to not only change the label but the entry widget too. That will be for later.
Progress:
I've managed to have the program print which widget has a focus in both the terminal and in a label widget.
Problem:
My issue is that the message is ugly, and I want it to look neater.
Example of the problem:
Instead of saying "Entry has focus", it says ".!frame.!entry {has focus}"
I have tried:
Checking if other's have made similar programs. The only one I found was how I made this much progress but it didn't make the text nicer. I've also done research on various sites on how Python handles certain commands I've used in this program.
I am a novice with programming so I admit I'm not completely sure what the best way to run this program is and so trying to research it is difficult for me.
Code:
# Call tkinter tools
from tkinter import *
from tkinter import ttk
"""
TODO:
Add different widgets that can 'gain' focus.
Print in the terminal which widget has focus.
Change entry and label text depending on which widget has focus.
"""
# Develop the window and frame
root = Tk()
root.title("Focus Test")
root.columnconfigure(0, weight = 1)
root.rowconfigure(0, weight = 1)
frame = ttk.Frame(root, padding = "1 1 1 1")
frame.grid(column = 0, row = 0, sticky = (N, S, E, W))
# Resizes columns in frame
for col in range(1, 3):
frame.columnconfigure(col, weight = 1)
# Resizes rows in frame
for row in range(1, 3):
frame.rowconfigure(row, weight = 1)
# Add response label
foc_labvar = StringVar()
foc_labvar.set("No focus")
foc_lab = ttk.Label(frame, width = 7, textvariable = foc_labvar)
foc_lab.grid(column = 2, row = 2, sticky = (W, E))
# Add entry box
foc_entvar = StringVar()
foc_entvar.set("Entry widget")
foc_ent = ttk.Entry(frame, width = 7, textvariable = foc_entvar)
foc_ent.grid(column = 1, row = 1, sticky = (W, E))
# Add button
foc_butvar = StringVar()
foc_butvar.set("Button widget")
foc_but = ttk.Button(frame, width = 7, textvariable = foc_butvar)
foc_but.grid(column = 2, row = 1, sticky = (W, E))
# Focus commands
def focus(event):
focused_widget = frame.focus_get()
foc_labvar.set((focused_widget, "has focus"))
print(focused_widget, "has focus")
# Bind mouse click to run focus command
root.bind("<Button-1>", focus)
# Resize widgets inside frame
for child in frame.winfo_children():
child.grid_configure(padx = 5, pady = 5)
root.mainloop()
You can just easily do this having event handle the widget identification part, like:
def focus(event):
focused_widget = event.widget.winfo_class()
foc_labvar.set((focused_widget, "has focus")) # Or f'{focused_widget} has focus'
print(focused_widget, "has focus")
Here event will provide the widget with widget method and then you can use any widget methods on it, like focus() or insert()(if its an entry widget) and so on. Here winfo_class should work, because it is a universal method and works with all the widgets.
More info on winfo_class, it will provide the class of the widget, like TButton for buttons, because that is how tk refers to them. If you adamantly still want to get rid of the T then just go for event.widget.winfo_class()[1:] to trim the 'T' off.
You can give widgets a name of your choosing. You can then use winfo_name to get the name of the widget. About the only required for a name is that it cannot have a period in it.
Also, you should bind to <FocusIn> and <FocusOut> so that your code works even if the focus changes via the keyboard.
foc_ent = ttk.Entry(..., name="foc entry")
foc_but = ttk.Button(..., name="foc button")
...
def focus(event):
focused_widget = frame.focus_get()
if focused_widget:
foc_labvar.set(f"{focused_widget.winfo_name()} as focus")
else:
foc_labvar.set("nothing has focus")
root.bind("<FocusIn>", focus)
root.bind("<FocusOut>", focus)
You can just use an if statement like this:
def focus(event):
focused_widget = frame.focus_get() # You can also use `event.widget` like what #CoolCloud did.
if focused_widget == foc_ent:
foc_labvar.set("Entry has focus")
if focused_widget == foc_but:
foc_labvar.set("Button has focus")
Combining the advice provided. Here is what I've ended up with which is working the way I had imagined in my head plus a little extra from everyone's help:
# Focus commands
def focus(event):
focused_widget = event.widget.winfo_class()[1:]
foc_labvar.set(focused_widget + " has focus")
foc_entvar.set(focused_widget + " has focus")
print(focused_widget, "has focus")
# Bind mouse and keyboard to run focus command
root.bind("<Button-1>", focus)
root.bind("<FocusIn>", focus)
root.bind("<FocusOut>", focus)
Based on the excellent answer of this question: Show Large Image using Scrollbar in Python
… I have been able to create a scrollable image frame, using this code, in Windows, Python 3:
import tkinter
from PIL import ImageTk, Image
class ScrollableImage(tkinter.Canvas):
def __init__(self, master=None, **kw):
self.image = kw.pop('image', None)
super(ScrollableImage, self).__init__(master=master, **kw)
self['highlightthickness'] = 0
self.propagate(0) # wont let the scrollbars rule the size of Canvas
self.create_image(0,0, anchor='nw', image=self.image)
# Vertical and Horizontal scrollbars
self.v_scroll = tkinter.Scrollbar(self, orient='vertical', width=6)
self.h_scroll = tkinter.Scrollbar(self, orient='horizontal', width=6)
self.v_scroll.pack(side='right', fill='y')
self.h_scroll.pack(side='bottom', fill='x')
# Set the scrollbars to the canvas
self.config(xscrollcommand=self.h_scroll.set,
yscrollcommand=self.v_scroll.set)
# Set canvas view to the scrollbars
self.v_scroll.config(command=self.yview)
self.h_scroll.config(command=self.xview)
# Assign the region to be scrolled
self.config(scrollregion=self.bbox('all'))
self.focus_set()
self.bind_class(self, "<MouseWheel>", self.mouse_scroll)
def mouse_scroll(self, evt):
if evt.state == 0 :
# self.yview_scroll(-1*(evt.delta), 'units') # For MacOS
self.yview_scroll( int(-1*(evt.delta/120)) , 'units') # For windows
if evt.state == 1:
# self.xview_scroll(-1*(evt.delta), 'units') # For MacOS
self.xview_scroll( int(-1*(evt.delta/120)) , 'units') # For windows
Now, I would like to add a row of checkboxes with numbers based on a given list numbers. The default option should be that all numbers are checked. One can then uncheck some numbers, and then when a bottom is pressed, all checked numbers should be stored in a new list, i.e. checked_numbers.
This is how it could look like:
This is what I have tried so far:
root = tkinter.Tk()
# Creating the check-boxes using a loop
numbers = [3,16,18,22,45]
for ii in numbers:
var = tkinter.IntVar()
c = tkinter.Checkbutton(root, text=str(ii), variable=var)
c.pack()
# PhotoImage from tkinter only supports:- PGM, PPM, GIF, PNG format.
# To use more formats use PIL ImageTk.PhotoImage
img = tkinter.PhotoImage(file='PATH TO IMAGE')
show_image = ScrollableImage(root, image=img, width=400, height=600)
show_image.pack()
This is how it looks:
There are obviously a lot of flaws and things that I don't know how to implement:
How to assign a new variable in every loop ?
How to have all checked-boxes in a row instead of a column ?
How to store which variable has been checked ?
How to store the checked variables as a new list, when a button is pressed ?
Edit
I tried to use the nice answer from #ncica, which works perfectly on its own, and added the scrollable image...
import tkinter as tk
from tkinter import *
root = tk.Tk()
def read_states():
arry = list(map(lambda var: var.get(), states)) # store which variable has been checked
checked = []
result=(zip(numbers,arry))
for item in result:
if item[1] == 1:
checked.append(item[0]) # store the checked variables in a new list "checked"
print ("Checked are:{}".format(checked))
states = []
numbers = [3,16,18,22,45]
for ii in numbers:
var = IntVar()
chk = Checkbutton(root, text=str(ii), variable=var) # assign a new variable in every loop
chk.grid(row=1,column =numbers.index(ii)) # all checked-boxes put in a row
states.append(var)
button = tk.Button(root, text='OK', command=read_states)
button.grid(row=1,column =len(numbers)+1)
# PhotoImage from tkinter only supports:- PGM, PPM, GIF, PNG format.
# To use more formats use PIL ImageTk.PhotoImage
img = tk.PhotoImage(file='IMAGE_FILE_PATH')
show_image = ScrollableImage(root, image=img, width=400, height=600)
show_image.pack()
root.mainloop()
...but I get the following error:
TclError Traceback (most recent call last)
<ipython-input-11-1556cebf9634> in <module>()
31
32 show_image = ScrollableImage(root, image=img, width=400, height=600)
---> 33 show_image.pack()
34
35
~\Anaconda3\lib\tkinter\__init__.py in pack_configure(self, cnf, **kw)
2138 self.tk.call(
2139 ('pack', 'configure', self._w)
-> 2140 + self._options(cnf, kw))
2141 pack = configure = config = pack_configure
2142 def pack_forget(self):
TclError: cannot use geometry manager pack inside . which already has slaves managed by grid
This can be resolved by replacing the line show_image.pack() with show_image.grid(row=2,column =1, columnspan=len(numbers)+1)
import Tkinter as tk
from Tkinter import *
root = tk.Tk()
def read_states():
arry = list(map(lambda var: var.get(), states)) # store which variable has been checked
checked = []
result=(zip(numbers,arry))
for item in result:
if item[1] == 1:
checked.append(item[0]) # store the checked variables in a new list "checked"
print ("Checked are:{}".format(checked))
states = []
numbers = [3,16,18,22,45]
for ii in numbers:
var = IntVar()
var.set(1) # by default set Checkbuttons as checked
chk = Checkbutton(root, text=str(ii), variable=var) # assign a new variable in every loop
chk.grid(row=1,column =numbers.index(ii)) # all checked-boxes put in a row
states.append(var)
button = tk.Button(root, text='OK', command=read_states) # on button clicked read_states
button.grid(row=1,column =len(numbers)+1) # put button after all Checkbuttons
root.mainloop()
output:
on button OK, checked objects will be printed
So I am trying to make a text-based game, but also want to incorporate images into it, I have a main menu, a window that is always open, and then a game window, that should have the image in, but for some reason, it appears in the menu window instead. Has anyone got any idea why?
def menu():
master = Tk()
master.geometry("350x300")
master.wm_iconbitmap("zombie.ico")
master.configure(background = '#484a2c')
master.title("Menu")
def game():
master = Tk()
master.geometry("800x500")
master.title("Game")
master.wm_iconbitmap("zombie.ico")
master.configure(background = '#484a2c')
image = PhotoImage(file="Kitchenimage.gif")
label5 = Label(image=image)
label5.image = image
label5.pack()
label = Label(master, text = "Zombie Mansion", background = '#484a2c',
font = ("AR CHRISTY", 30))
label.pack()
b = Button(master,text = "Play Game", height = 1, width = 10)
b.config(background = '#484a2c', activebackground = '#484a2c', font =
("AR CHRISTY", 14), command = get_username)
b.place(x = 120, y = 70)
mainloop()
"Why is my image appearing on the wrong window in Tkinter?"
Assuming you have another instance of Tk as what you refer to as main menu somewhere in your code that you're not showing us like:
main = Tk()
in addition to master = Tk() in your game method, then it's because when you instantiate widgets without passing a parent widget explicitly like in:
label5 = Label(image=image)
then the widget's parent defaults to the Tk instance whose mainloop is being run, in above case supposedly main's. By default widgets are shown under their parents, hence the reason.
Pass parent explicitly like:
label5 = Label(master=main, image=image)
or
label5 = Label(master, image=image)
In most cases, if not all cases, you shouldn't have multiple instances of Tk. If you require additional windows, please use Toplevel widget.
I have an entry, a listbox(dropdown) and another listbox. Whenever more than 3 characters are typed inside the entry. A completion list is looked up and inserted to the dropdown and the dropdown is shown. If an item is selected from the dropdown. It's value should fill the entry and the entry should get the focus again and the cursor should go to the end of the entry. And then, when Enter key is pressed the value of the entry should be inserted to the other listbox.
I've developed a code for that with much help from this utility and the code works perfectly fine. Except, I realized that whenever I select an option from the dropdown the corresponding method is called twice(I get two prints in the console from the same thing). But if I select the first option of the dropdown, it's called once(which is what should have actually happened in the other case) but the focus does not go to the entry (which is a problem).
Here is my code:
from tkinter import *
class Autocomplete(Frame, object):
def __init__(self, width, height, entries, *args, **kwargs):
super(Autocomplete, self).__init__(*args, **kwargs)
self._entries = entries
self.listbox_height = height
self.entry_width = width
self.text = StringVar()
self.entry = Entry(
self,
textvariable=self.text,
width=self.entry_width
)
self.frame = Frame(self)
self.listbox = Listbox(
self.frame,
height=self.listbox_height,
width=self.entry_width
)
self.dropdown = Listbox(
self.frame,
height=self.listbox_height,
width=self.entry_width,
background="#cfeff9"
)
def build(self):
self.text.trace("w", lambda name, index, mode, text=self.text: self._update_autocomplete())
self.entry.bind("<Return>", lambda event,: self._add_course())
self.entry.focus_set()
self.entry.pack()
self.frame.pack()
self.listbox.grid(column=0, row=0, sticky=N)
self.dropdown.bind("<<ListboxSelect>>", self._select_entry)
self.dropdown.grid(column=0, row=0, sticky=N)
self.dropdown.grid_forget()
return self
def _update_autocomplete(self):
self.dropdown["height"] = self.listbox_height
self.dropdown.delete(0, END)
text = self.text.get()
if len(text) < 3:
self.dropdown.grid_forget()
return
else:
for entry in self._entries:
if text.lower() in entry.strip().lower():
self.dropdown.insert(END, entry)
listbox_size = self.dropdown.size()
if not listbox_size:
self.dropdown.insert(END, "No results found for '{}'")
self.dropdown["height"] = 1
else:
if listbox_size <= self.dropdown["height"]:
self.dropdown["height"] = listbox_size
self.dropdown.grid(column=0, row=0, sticky=N)
def _select_entry(self, event):
widget = event.widget
value = widget.get(int(widget.curselection()[0]))
print(value)
self.text.set(value)
self.entry.focus_set()
self.entry.icursor(END)
def _add_course(self):
self.listbox.insert(END, self.text.get())
So what am I missing here?
By the way any general improvement to the code will also be much appreciated.
And here is how I call it:
from tkinter import *
from autocomplete import Autocomplete
from main import *
courses = load_courses_from_file("courses.txt")
root = Tk()
autocomplete_frame = Autocomplete(
60,
10,
list(set(course.name + ", " + course.instructor for course in courses))
).build().pack()
mainloop()
The selection of the listbox changes when you click on the item -- this is the default behavior of the listbox. This causes the entry widget value to change, which triggers a call to _update_autocomplete. That function deletes everything in the listbox, causing the selection to change again.