Python Tkinter Treeview Shift-Up binding is one iteration behind input - python

I want to be able to also use the <Shift-Up> keys to select through the items in the treeview. No errors are given but the selection is late with one element. In my code, I am intending for the selection to be the same as the focus, but the selection ends up being on element down the list.
My guess is that the default binding for Arrow-Up key is being run after my binding.
I have searched for a virtual event to replace the <Up> in my binding - something that has a similar functionality to <<TreeviewSelect>> as opposed to <ButtonPress-1> - but with no luck.
Any idea how to synchronize the selection and focus when <Shift-Up> is pressed?
Note: the selectmode for the tree is set to none as in my main application I need the selection to be done slightly different than the default.
import tkinter as tk
from tkinter import ttk
class Treeview_Select:
def __init__(self, tree):
tree.bind('<Shift-Up>', self.ShiftUp, add='+')
def ShiftUp(self, event):
if event.widget.index(event.widget.focus()) is not '':
print(event.widget.index(event.widget.focus()))
event.widget.selection_set(event.widget.focus())
app = tk.Tk()
tree = ttk.Treeview(app)
v_scrollbar = ttk.Scrollbar(app, orient='vertical', command=tree.yview)
tree.config(selectmode='none', yscrollcommand=v_scrollbar.set)
tree.grid(row=0, column=0, sticky='nesw')
v_scrollbar.grid(row=0, column=1, sticky='nes')
Treeview_Select(tree)
for i in range(14):
tree.insert("", 'end', text=i)
app.mainloop()

You should use tree.prev(item) for this kind of action. If you want to implement a <Shift-Down>, use tree.next(item).
def ShiftUp(self, event):
if event.widget.index(event.widget.focus()) is not '':
previous = event.widget.prev(event.widget.focus()) #use current focus to get previous one
event.widget.selection_set(previous)
event.widget.see(previous) #make sure the new selection is shown

Related

Checkbutton responds to clicks on text

By default, tkinter's Checkbutton widget responds to clicks anywhere in the widget, rather than just in the check box field.
For example, consider the following (Python 2) code:
import Tkinter as tk
main = tk.Tk()
check = tk.Checkbutton(main, text="Click").pack()
main.mainloop()
Running this code results in a small window, with a single check box, accompanied by the word "Click". If you click on the check box, it is toggled.
However, this also happens if you click on the text of the widget, rather than the check box.
Is this preventable? I could make a separate widget to hold the text, but that still results in a small area around the check box that responds to clicks.
Two solutions come to mind:
Do as you suggest and create a separate widget for the checkbutton and for the label.
replace the bindings on the checkbutton with your own, and examine the x/y coordinates of the click and only accept the click if it happens in a small region of the widget.
This program creates a checkbutton and overrides the default event on it by binding a method to the checkbutton. When the button is clicked, the method checks a defined limit to allow the normal operation or to override. OP wanted to make sure that when text of the checkbutton is clicked, no default action is taken. That is essentially what this does.
import tkinter as tk
import tkinter.ttk as ttk
class App(tk.Frame):
def __init__(self, parent, *args, **kwargs):
self.checkvar = IntVar()
check = tk.Checkbutton(parent, text='Click', variable=self.checkvar)
check.bind('<Button-1>', self.checkCheck)
check.pack()
print(dir(check))
def checkCheck(self, event):
# Set limit based on your font and preference
limit = 18
print(event.x, event.y, self.checkvar.get())
if event.x > limit or event.y > limit:
self.checkvar.set(not self.checkvar.get())
else:
print("Normal behavior")
if __name__ == "__main__":
window = tk.Tk()
app = App(window)
window.mainloop()

Tkinter Python listbox

I am a new python user. I'm used to programing on matlab.
I've trying to make a simple GUI with Tkinter pack, but I'm having some problems with that. I had already read and searched what i want but I couldn't develop it.
What I'm trying to do is to make a listbox and when I choose one (or more) options the index be returned (and stored) as a variable (array or vector) that could be used to indexing another array.
The best result I got was a listbox where the index were printed, but not stored as a variable (at least it hasn't been shows in the variables list)
I'm using spyder (anaconda).
I tryied a lot of codes and I don't have this anymore.
Sorry for the dumb question. I guess I still thinking in a Matlab way to write
To keep this application simple, your best option is to get the listbox selection when you want to do something with it:
from tkinter import Tk, Listbox, MULTIPLE, END, Button
def doStuff():
selected = lb.curselection()
if selected: # only do stuff if user made a selection
print(selected)
for index in selected:
print(lb.get(index)) # how you get the value of the selection from a listbox
def clear(lb):
lb.select_clear(0, END) # unselect all
root = Tk()
lb = Listbox(root, selectmode=MULTIPLE) # create Listbox
for n in range(5): lb.insert(END, n) # put nums 0-4 in listbox
lb.pack() # put listbox on window
# notice no parentheses on the function name doStuff
doStuffBtn = Button(root, text='Do Stuff', command=doStuff)
doStuffBtn.pack()
# if you need to add parameters to a function call in the button, use lambda like this
clearBtn = Button(root, text='Clear', command=lambda: clear(lb))
clearBtn.pack()
root.mainloop()
I've also added a button to clear the listbox selection because you cannot unselect items by default.
First, import tkinter, then, create the listbox. Then, you can use curselection to get the contents of the listbox.
import tkinter as tk
root = tk.Tk() #creates the window
myListbox = tk.Listbox(root, select=multiple) #allows you to select multiple things
contentsOfMyListbox = myListbox.curselection(myListbox) #stores selected stuff in tuple
See the documentation here.

WASD input in python

In python, how can I receive keyboard input. I'm well aware of console input with input("...") but I'm more concerned with receiving keyboard input while the console window is not active. For example if I created an instance of a Tkinter screen how could I check to see if let's say "w" was pressed. Then if the statement returned true i could move an object accordingly.
The way you do this with a GUI toolkit like tkinter is to create a binding. Bindings say "when this widget has focus and the user presses key X, call this function".
There are many ways to accomplish this. You can, for example, create a distinct binding for each character. Or, you could create a single binding that fires for any character. With these bindings, you can have them each call a unique function, or you can have all the bindings call a single function. And finally, you can put the binding on a single widget, or you can put the binding on all widgets. It all depends on exactly what you are trying to accomplish.
In a simple case where you only want to detect four keys, four bindings (one for each key) calling a single function makes perhaps the most sense. For example, in python 2.x it would look something like this:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent, width=400, height=400)
self.label = tk.Label(self, text="last key pressed: ", width=20)
self.label.pack(fill="both", padx=100, pady=100)
self.label.bind("<w>", self.on_wasd)
self.label.bind("<a>", self.on_wasd)
self.label.bind("<s>", self.on_wasd)
self.label.bind("<d>", self.on_wasd)
# give keyboard focus to the label by default, and whenever
# the user clicks on it
self.label.focus_set()
self.label.bind("<1>", lambda event: self.label.focus_set())
def on_wasd(self, event):
self.label.configure(text="last key pressed: " + event.keysym);
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()

Python Tkinter Toplevel

I am working on a program that requires multiple windows, and the first one to appear is the login window, I used the Toplevel widget in order to make other windows its children, but this code keeps showing two windows instead of one.
from Tkinter import Frame, Toplevel
from ttk import Label, Entry, Button
class loginWindow(Toplevel):
def __init__(self):
Toplevel.__init__(self)
self.title("Title")
self.frame = Frame(self)
self.frame.pack()
self.__make_layout()
self.mainloop()
def __make_layout(self):
self.frame.user_name_label = Label(text="User name:")
self.frame.user_name_text = Entry()
self.frame.user_name_label.grid(row=0, column=0)
self.frame.user_name_text.grid(row=0, column=1)
self.frame.password_label = Label(text="Password:")
self.frame.password_text = Entry()
self.frame.password_label.grid(row=1, column=0)
self.frame.password_text.grid(row=1, column=1)
self.frame.login_button = Button(text="Login")# , command=self.__create_window)
self.frame.login_button.grid(row=2, column=0, columnspan=2)
if __name__ == '__main__':
win1 = loginWindow()
All of the widgets created in _make_layout are created without a parent. This means they're children of the default root. You need to pass a parent to each of them, the same way you do to the Frame. Like this:
self.frame.user_name_label = Label(self.frame, text="User name:")
self.frame.user_name_text = Entry(self.frame)
# etc.
When I run your exact code, I don't get a second window, on any platform I try. The closest I get is on OS X, where an entry for the default root window appears in the Window menu, but the window itself still doesn't appear and the widgets all end up on the Toplevel (although not on the Frame where you wanted them). But it certainly would be legal for Tkinter to show a second window here, and put some or all of your widgets on it.
This must be a platform dependent issue, since abarnert isn't having issues with multiple windows. I use OS X with XQuartz and the following code gives me two windows:
from Tkinter import Toplevel, Tk
Toplevel().mainloop()
However, this code gives me one window:
from Tkinter import Toplevel, Tk
Tk().mainloop()
I believe your first window should be declared Tk() and subsequent windows should be Toplevel().

Python3.x: tkinter (ttk) stop displaying widget

So I've been trying to make some basic GUIs with tkinter (not te be confused with Tkinter) and I ran into a problem for which I know no solution and can't really find anything on the almighty Google...
I have a small SQLite database with a table of directories on my pc. I would like to draw all directorypaths into a label and add a 'rempve' button next to that label. The button should be able to remove directory from the database and also remove it from the GUI. I also have a 'add' button where one can add directories to the database and this new directory should be shown in the GUI. This is my basic layout:
---------------
| ADD |
|dir1 REMOVE|
|dir2 REMOVE|
---------------
I use the gridlayout to show the buttons and labels. Most things work, all database related stuff works. Also when starting the GUI the current directories and 'remove'-buttons are shown nicely. BUT... when using the 'remove' button the directory does not disappear from the GUI even though it is not in the database anymore, restarting the GUI fixes it of course. Adding a label works... but I'm not sure if I'm doing it correctly...
How can I somehow 'repaint' the GUI with the new information?
This is my code for the GUI:
class GUI():
def __init__(self,db):
self.root = Tk()
self.root.title("Example")
self.frame = ttk.Frame(self.root, padding="3 3 12 12")
self.frame.rowconfigure(5, weight=1)
self.frame.columnconfigure(5, weight=1)
self.frame.grid(sticky=W+E+N+S)
lbl = ttk.Label(self.frame, text="", width=17)
lbl.grid(row=0, column=2, sticky=W)
ttk.Button(self.frame, text="Add directory", command=lambda:self.load_file(db), width=30).grid(row=0, column=0, sticky=W, padx=(500,50))
ttk.Button(self.frame, text="Sort files", command=lambda:self.sort(db,lbl), width=17).grid(row=0, column=1, sticky=W)
self.draw(db)
self.root.mainloop()
def load_file(self,db):
fname = filedialog.askdirectory()
db.addPath(fname)
self.draw(db)
def remove_dir(self,db,pid):
db.removePath(pid)
self.draw(db)
def sort(self,db,lbl):
lbl['text'] = 'Sorting...'
sortFiles.moveFiles(db)
lbl['text'] = 'Done!'
def draw(self,db):
i = 0
paths = db.getPaths()
for path in paths:
ttk.Label(self.frame,text=path[1]).grid(row=1+i,column=0,sticky=W)
ttk.Button(self.frame, text="Remove directory", command=lambda:self.remove_dir(db,path[0]), width=17).grid(row=1+i,column=1, sticky=E)
i = i+1
for child in self.frame.winfo_children(): child.grid_configure(padx=5, pady=5)
if i == 0:
ttk.Label(self.root,text='No directories added yet').grid(row=1,column=0,sticky=W)
If you prefer to redraw the GUI every time you add or delete something, you need to first destroy any old widgets before creating new ones. For example:
def draw(self, db):
# first, delete any existing widgets
for child in self.frame.winfo_children():
child.destroy()
# next, redraw all the widgets
paths = db.getPaths()
for path in paths:
...
You have another bug, which is how you're using lambda. As it stands with the code in the question, all of your callbacks will see the same value. By specifying the value as a keyword argument to the lambda you'll get the right value:
ttk.Button(..., command=lambda p=path[0]:self.remove_dir(db, p)...)
Unrelated to the actual problem, I don't think you need to be passing db around. Assuming you only use a single db, I recommend you do self.db = db in your GUI constructor. That will make your code just a little easier to maintain because your method signatures will be simplified.
Finally, there's really no need to completely redraw the GUI when you delete one item. You can delete just one label and button at a time. This requires that you spend a little more time thinking about how you manage data in your program. If, for example, you keep a reference to each label and button, you can delete it when you delete the path from the database. Your removeDir function might look something like:
def removeDir(self, pid):
label, button = self.widgets(pid)
label.destroy()
button.destroy()

Categories

Resources