Different tkinter binding for any ttk treeview row - python

I'm writing a simple script that create a ttk treeview (that act as a table) and, when you double-click it, it opens a file (with the path saved in the dictionary). Double click opening is possible by this method:
t.bind("<Double-1>", lambda f=nt[x]["URIallegato"]: os.startfile(str(f)))
However, this doesn't gave me the ID of the row (stored in the #0 column). With the ID I can get the path of the file saved in a dictionary.
Here is the full Treeview code:
t=Treeview(w)
t.pack(padx=10,pady=10)
for x in list(nt.keys()):
t.insert("",x,text=nt[x]["allegati"])
if nt[x]["allegati"]!="":
t.bind("<Double-1>",
lambda f=nt[x]["URIallegato"]: os.startfile(str(f)))
Thanks!

The normal way to do this is to bind a single binding on the treeview for a double click. The default binding for single-click will select the item, and in your double-click binding you can ask the treeview for the selected item.
If you associate values with the treeview item, you can fetch them so that you don't have to store them in a dictionary.
Here's an example:
import tkinter as tk
from tkinter import ttk
def on_double_click(event):
item_id = event.widget.focus()
item = event.widget.item(item_id)
values = item['values']
url = values[0]
print("the url is:", url)
root = tk.Tk()
t=ttk.Treeview(root)
t.pack(fill="both", expand=True)
t.bind("<Double-Button-1>", on_double_click)
for x in range(10):
url = "http://example.com/%d" % x
text = "item %d" % x
t.insert("", x, text=text, values=[url])
root.mainloop()

Related

is there a way to jump from one entry field to another entry field in tkinter?

I was trying to make billing software with multiple entries in python, so it's hard to move from one entry field to another entry field using a mouse, I just wanted it to jump from the customer name field to customer phone no entry was filed when I press "enter" key but it's not working how can I make it work
from Tkinter import*
root = Tk()
root.geometry('1350x700+0+0')
root.resizable(width=False,height=False)
root.title('Billing App')
def jump_cursor(event):
customer_PhoneNo_entry.icursor(0)
customer_detail_frame=Frame(root,bd=10,highlightbackground="blue", highlightthickness=2)
customer_detail_frame.place(x=0,y=10,width=1350,height=100)
customer_detail_lbl=Label(customer_detail_frame,text="customer detail",font=('verdana',10,'bold'))
customer_detail_lbl.grid(row=0,column=0,pady=10,padx=20)
customer_name_lbl=Label(customer_detail_frame,text="customer name",font=('verdana',10,'bold'))
customer_name_lbl.grid(row=1,column=0,pady=10,padx=20)
customer_name_entry=Entry(customer_detail_frame,width=20,textvariable = Customer_Name)
customer_name_entry.grid(row=1,column=1,pady=10,padx=20)
customer_phoneno_lbl=Label(customer_detail_frame,text="phone no.",font=('verdana',10,'bold'))
customer_phoneno_lbl.grid(row=1,column=2,pady=10,padx=20)
customer_PhoneNo_entry=Entry(customer_detail_frame,width=20,textvariable = Customer_Ph_No)
customer_PhoneNo_entry.grid(row=1,column=3,pady=10,padx=20)
customer_PhoneNo_entry.bind("<Return>",jump_cursor)
This answer helped me with the same problem.
First, get all children of customer_detail_frame which are Entry,
using winfo_children()
Second, bind each child to <Return>, (in which lambda can be used
to create another function).
Last, define the jump_cursor function, such that it switches the
focus to the desired widget, using focus_set().
Here is what changes in your code would look like:
from tkinter import*
root = Tk()
root.geometry('1350x700+0+0')
root.resizable(width=False,height=False)
root.title('Billing App')
Customer_Name, Customer_Ph_No = StringVar(), StringVar()
customer_detail_frame=Frame(root,bd=10,highlightbackground="blue", highlightthickness=2)
customer_detail_frame.place(x=0,y=10,width=1350,height=100)
customer_detail_lbl=Label(customer_detail_frame,text="customer detail",font=('verdana',10,'bold'))
customer_detail_lbl.grid(row=0,column=0,pady=10,padx=20)
customer_name_lbl=Label(customer_detail_frame,text="customer name",font=('verdana',10,'bold'))
customer_name_lbl.grid(row=1,column=0,pady=10,padx=20)
customer_name_entry=Entry(customer_detail_frame,width=20,textvariable = Customer_Name)
customer_name_entry.grid(row=1,column=1,pady=10,padx=20)
customer_phoneno_lbl=Label(customer_detail_frame,text="phone no.",font=('verdana',10,'bold'))
customer_phoneno_lbl.grid(row=1,column=2,pady=10,padx=20)
customer_PhoneNo_entry=Entry(customer_detail_frame,width=20,textvariable = Customer_Ph_No)
customer_PhoneNo_entry.grid(row=1,column=3,pady=10,padx=20)
def jump_cursor(event, entry_list, this_index):
next_index = (this_index + 1) % len(entry_list)
entry_list[next_index].focus_set()
entries = [child for child in customer_detail_frame.winfo_children() if isinstance(child, Entry)]
for idx, entry in enumerate(entries):
entry.bind('<Return>', lambda e, idx=idx: jump_cursor(e, entries, idx))
root.mainloop()

How can I pass the 2nd item of sqlite 3 table of every item in a label in tkinter python module?

I have a problem, the problem is i want to create a post-it tkinter gui app and I want to store all the posts the user create so they can open it when they rerun the app, so i used sqlite 3 module to achieve this, but im stuck at the moment when the user opens the existing post-its bcs it opens the last content of the for loop
In case u dont get it here is the code:
"""
from tkinter import *
import sqlite3
conn = sqlite3.connect("post-it.db")
row = 1
cursor = conn.cursor()
posts = cursor.execute("SELECT rowid, * FROM postits")
postsFetch = posts.fetchall()
print(f"{postsFetch}")
def createPost():
pass
def openPost(name):
print(name)
post = Tk()
text = Label(post,text=name)
text.pack()
post.mainloop()
window = Tk()
window.geometry("400x400")
window.config(bg="blue")
createNew = Button(text="Create new Post-it",command=createPost)
createNew.grid(column=1,row=1)
createName = Entry()
createName.grid(column=1,row=2)
frame = Frame()
frame.grid(column=2)
#the problem is at this for loop it opens the last item text
for postit in postsFetch:
postitBtn = Button(frame,text=postit[1],command=lambda: openPost(postit[2]))
postitBtn.grid(column=8,row=row)
row += 1
conn.commit()
window.mainloop()
conn.close()
"""
if u know the answer please help
Firstly, don't use Tk more than once in a program - it can cause problems later on. For all other windows, use Toplevel. Replace post = Tk() with post = Toplevel().The reason your loop doesn't work is explained here. To fix it, change your lambda function to lambda postit = postit: openPost(postit[2]))

How to justify the characters in drop-down list of a Combobox?

How to justify the values listed in drop-down part of a ttk.Combobox? I have tried justify='center' but that seems to only configure the selected item. Could use a resource link too if there is, I couldn't find it.
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
import tkinter.ttk as ttk
except ImportError:
import Tkinter as tk
import ttk
if __name__ == '__main__':
root = tk.Tk()
cbb = ttk.Combobox(root, justify='center', values=(0, 1, 2))
cbb.pack()
root.mainloop()
I have a one-liner solution. Use the .option_add() method after declaring ttk.Combobox. Example:
cbb = ttk.Combobox(root, justify='center', values=(0, 1, 2)) # original
cbb.option_add('*TCombobox*Listbox.Justify', 'center') # new line added
Here's one pure Python way that gets close to what you want. The items in the dropdown list all get justified to fit within the Combobox's width (or a default value will be used).
Update
Part of the reason the initial version of my answer wasn't quite right was because the code assumed that a fixed-width font was being used. That's not the case on my test platform at least, so I've modified the code to actually measure the width of values in pixels instead of whole characters, and do essentially what it did originally, but in those units of string-length measure.
import tkinter as tk
import tkinter.font as tkFont
from tkinter import ttk
class CenteredCombobox(ttk.Combobox):
DEFAULT_WIDTH = 20 # Have read that 20 is the default width of an Entry.
def __init__(self, master=None, **kwargs):
values = kwargs.get('values')
if values:
entry = ttk.Entry(None) # Throwaway for getting the default font.
font = tkFont.Font(font=entry['font'])
space_width = font.measure(' ')
entry_width = space_width * kwargs.get('width', self.DEFAULT_WIDTH)
widths = [font.measure(str(value)) for value in values]
longest = max(entry_width, *widths)
justified_values = []
for value, value_width in zip(values, widths):
space_needed = (longest-value_width) / 2
spaces_needed = int(space_needed / space_width)
padding = ' ' * spaces_needed
justified_values.append(padding + str(value))
kwargs['values'] = tuple(justified_values)
super().__init__(master, **kwargs)
root = tk.Tk()
ccb = CenteredCombobox(root, justify='center', width=10, values=('I', 'XLII', 'MMXVIII'))
ccb.pack()
root.mainloop()
(Edit: Note that this solution works for Tcl/Tk versions 8.6.5 and above. #CommonSense notes that some tkinter installations may not be patched yet,
and this solution will not work).
In Tcl ( I don't know python, so one of the python people can edit the question).
A combobox is an amalgamation of an 'entry' widget and a 'listbox' widget. Sometimes to make the configuration changes you want, you need to access the internal widgets directly.
Tcl:
% ttk::combobox .cb -values [list a abc def14 kjsdf]
.cb
% pack .cb
% set pd [ttk::combobox::PopdownWindow .cb]
.cb.popdown
% set lb $pd.f.l
.cb.popdown.f.l
% $lb configure -justify center
Python:
cb = ttk.Combobox(value=['a', 'abc', 'def14', 'kjsdf'])
cb.pack()
pd = cb.tk.call('ttk::combobox::PopdownWindow', cb)
lb = cb.tk.eval('return {}.f.l'.format(pd))
cb.tk.eval('{} configure -justify center'.format(lb))
Some caveats. The internals of ttk::combobox are subject to change.
Not likely, not anytime soon, but in the future, the hard-coded .f.l
could change.
ttk::combobox::PopdownWindow will force the creation of the listbox when it is called. A better method is to put the centering adjustment into
a procedure and call that procedure when the combobox/listbox is mapped.
This will run for all comboboxes, you will need to check the argument
in the proc to make sure that this is the combobox you want to adjust.
proc cblbhandler { w } {
if { $w eq ".cb" } {
set pd [ttk::combobox::PopdownWindow $w]
set lb $pd.f.l
$lb configure -justify center
}
}
bind ComboboxListbox <Map> +[list ::cblbhandler %W]
After digging through combobox.tcl source code I've come up with the following subclass of ttk.Combobox. JustifiedCombobox justifies the pop-down list's items almost precisely after1 pop-down list's been first created & customized and then displayed. After the pop-down list's been created, setting self.justify value to a valid one will again, customize the justification almost right after the pop-down list's first been displayed. Enjoy:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
from tkinter import ttk
except:
import Tkinter as tk
import ttk
class JustifiedCombobox(ttk.Combobox):
"""
Creates a ttk.Combobox widget with its drop-down list items
justified with self.justify as late as possible.
"""
def __init__(self, master, *args, **kwargs):
ttk.Combobox.__init__(self, master, *args, **kwargs)
self.justify = 'center'
def _justify_popdown_list_text(self):
self._initial_bindtags = self.bindtags()
_bindtags = list(self._initial_bindtags)
_index_of_class_tag = _bindtags.index(self.winfo_class())
# This dummy tag needs to be unique per object, and also needs
# to be not equal to str(object)
self._dummy_tag = '_' + str(self)
_bindtags.insert(_index_of_class_tag + 1, self._dummy_tag)
self.bindtags(tuple(_bindtags))
_events_that_produce_popdown = tuple([ '<KeyPress-Down>',
'<ButtonPress-1>',
'<Shift-ButtonPress-1>',
'<Double-ButtonPress-1>',
'<Triple-ButtonPress-1>',
])
for _event_name in _events_that_produce_popdown:
self.bind_class(self._dummy_tag, _event_name,
self._initial_event_handle)
def _initial_event_handle(self, event):
_instate = str(self['state'])
if _instate != 'disabled':
if event.keysym == 'Down':
self._justify()
else:
_ = self.tk.eval('{} identify element {} {}'.format(self,
event.x, event.y))
__ = self.tk.eval('string match *textarea {}'.format(_))
_is_click_in_entry = bool(int(__))
if (_instate == 'readonly') or (not _is_click_in_entry):
self._justify()
def _justify(self):
self.tk.eval('{}.popdown.f.l configure -justify {}'.format(self,
self.justify))
self.bindtags(self._initial_bindtags)
def __setattr__(self, name, value):
self.__dict__[name] = value
if name == 'justify':
self._justify_popdown_list_text()
def select_handle():
global a
_selected = a['values'][a.current()]
if _selected in ("left", "center", "right"):
a.justify = _selected
if __name__ == '__main__':
root = tk.Tk()
for s in ('normal', 'readonly', 'disabled'):
JustifiedCombobox(root, state=s, values=[1, 2, 3]).grid()
a = JustifiedCombobox(root, values=["Justify me!", "left", "center", "right"])
a.current(0)
a.grid()
a.bind("<<ComboboxSelected>>", lambda event: select_handle())
root.mainloop()
1 It basically makes use of bindtag event queue. This was mostly possible thanks to being able to creating a custom bindtag.

Change the color of a single word in a tk option menu?

So I'm grabbing links of events off a website and putting them into a drop down menu to be selected. My code for the menu:
import Tkinter as tk
from Tkinter import StringVar
selectMenu = tk.Tk()
# #-> this is what I have
# Followed by what you can use
#var = Vars()
#events = var.GetVars('Event')
events = " "
options = []
links = []
#forms = (driver.find_elements_by_class_name("with-cats")) #This is what I have
forms = ["Yolo ","Dad? Closed","Anotha One","Normies! Closed"] #This is so you can try it for yourself
for x in forms:
#info = x.text
info = x #Again, this is so you can try it for yourself
if events in info.lower():
links.append(x)
for link in range(0,len(links)):
#options.append(links[link].text)
options.append(links[link])
list(set(options))
selection = []
for link in range(0,len(options)):
selection.append(options[link])
select = StringVar(selectMenu)
select.set("--None Selected--")
menu = tk.OptionMenu(selectMenu, select, *(selection))
msg = "Which one would you like to attend?"
label = tk.Label(selectMenu, text=msg, font="Helvedica 14")
label.pack(side='top', pady=10)
menu.pack(side="top", pady=10)
selectMenu.attributes('-topmost', True)
selectMenu.mainloop()
So this works fine and dandy, but I would like to improve the look to make it more obvious which events are open. To clarify, an event found that is open and put into the menu may look like "This is a cool event", but one that is closed would be read as "This is a cool event Closed". My aim is to be able to make the foreground red of either just the word Closed or the string containing Closed, whichever is possible if any (And I'm not sure if it's possible because menus and buttons on osx are usually defaulted to system settings, maybe there is a way around this?).
Current: Desired:
According to the documentation for OptionMenu here and here I don't think there is a way to set the color of text.
You might be able to get something close to what you want by using a listBox instead. See post here for the listBox example.
Found a solution! Using a Menu inside of a MenuButton the same way Tkinter creates MenuOptions, I was able to create a custom MenuOption. If you want to add more options, you can use the menbutton.configure() option to edit the button, and menbutton.menu to edit the menu items.
import Tkinter as tk
from Tkinter import Menu, Menubutton
class Vars():
global vari
vari = {}
def GetVars(self, var):
return vari.get(str(var))
def SendVars(self, var, val):
vari[str(var)] = val
class App():
def buttselect(self, link, menbutton, selectMenu):
var = Vars()
var.SendVars("Selection", link) # Store selected event
menbutton.configure(text=link) # Set menu text to the selected event
def prnt(self, link):
var = Vars()
print var.GetVars("Selection") # Print event
def __init__(self, selectMenu):
events = " "
options = []
links = []
forms = ["Yolo ","Dad? Closed","Anotha One","Normies! Closed"] #This is so you can try it for yourself
menbutton = Menubutton (selectMenu, text="--None Selected--", relief="raised")
menbutton.grid()
menbutton.menu = Menu (menbutton, tearoff=0)
menbutton["menu"] = menbutton.menu
#Get a list of event names
for x in forms:
info = x #Again, this is so you can try it for yourself
#If desired event keyword is in an event name, add it to the correct links
if events in info.lower():
links.append(x)
#Remove duplicates
for link in range(0,len(links)):
options.append(links[link])
list(set(options))
#Final list of event names turned into menu commands
for link in options:
if "Closed" in link:
menbutton.menu.add_command( label= link, command= lambda link=link: self.buttselect(link, menbutton, selectMenu), foreground='red')
else:
menbutton.menu.add_command( label= link, command= lambda link=link: self.buttselect(link, menbutton, selectMenu))
b = tk.Button(selectMenu, text="Selection", command= lambda link=link: self.prnt(link)) #Print selected event
b.pack()
msg = "Which one would you like to attend?"
label = tk.Label(selectMenu, text=msg, font="Helvedica 14")
label.pack(side='top', pady=10)
menbutton.pack(side="top", pady=10)
selectMenu = tk.Tk()
selectMenu.attributes('-topmost', True)
app = App(selectMenu)
selectMenu.mainloop()
This results in exactly the result desired:
I found a way!
Let's say x is an optionmenu with options:
options=['Red','Blue','Green']
defopt=tk.StringVar(options[0]) #StringVariable to hold the selected option.
x=tk.OptionMenu(self.optmenuframe,defopt,*options)
Now, get the menu object from the optionmenu and use entryconfig method. That's it!
x.children['menu'].entryconfig(0,foreground='red')
x.children['menu'].entryconfig(1,foreground='blue')
x.children['menu'].entryconfig(2,foreground='green')
#0 is the index of the option you want to apply the configurations to.

Python - Gtk.TreeView with CheckBox

I need to store items in a Gtk TreeView and when interacting with this TreeView, the user will can select one or more items in the list.
Because I'm new to GTK, I managed to populate the treeview and display a checkbox as the code below shows. But when I try to select, nothing happens and I do not know how to make this possible.
This is my Code:
# the column is created
renderer_products = gtk.CellRendererText()
column_products = gtk.TreeViewColumn("Products", renderer_products, text=0)
# and it is appended to the treeview
view.append_column(column_products)
# the column checkbox is created
renderer_checkbox = gtk.CellRendererToggle()
column_checkbox = gtk.TreeViewColumn("Selected", renderer_checkbox, text=0)
# and it is appended to the treeview
view.append_column(column_checkbox)
If you want to select the whole row and something happen:
#double click or not double click use
Gtk.TreeView.set_activate_on_single_click (bool)
#connect the treeview
treeview.connect ("row-activated", on_row_activate)
#inside the callback
def on_row_activate (treeview, path, column):
model = treeview.get_model ()
iter = treeview.get_iter (path)
yourdata = model[iter][model_index]
#do whatever with yourdata
If you want when you click the toggle and something happen:
#connect the renderer
renderer_checkbox.connect ("toggled", on_selected_toggled)
#inside the callback
def on_selected_toggled (renderer, path):
#modify the model or get the value or whatever

Categories