Hide and show ttk.Combobox dropdown list - python

Situation: When I use the mouse button to click the "down-arrow" of a ttk.Combobox it's standard behaviour is to show a dropdown list. When the down arrow is clicked on the second time, the combobox dropdown list will become hidden.
Using the keyboard. it is possible to show the combobox dropdown list by pressing the "down-arrow" once. Pressing the "down-arrow" further will scroll down the dropdown list to its end. Pressing the "up-arrow" repeatedly will scroll up the dropdown list until the highlight/selection reaches to the top of the dropdown list, but it does not finally hide the dropdown list.
Question: Without using the mouse or keyboard, that is, using computer programming, how can I hide an expose dropdown list of a ttk.Combobox. I am aware that the w.event_generate("<Down>") command can be used to program a ttk.Combobox to show it's dropdown list. But how do I achieve the opposite? That is, how can I use the same w.event_generate() command to hide the dropdown list? Or what other tkinter command should I use to achieve what I want?

I made several attempts at this question and finally found a way to hide the combobox droplist via programming. My code is shown below.
OBSERVATIONS:
Using "combobox_widget_object.event_generate('<Button-1>')" can
cause the combobox dropdown list to show. Event '<Button-1>' appears to be
inherently defined to cause this behavior.
Running 2 of this command back to back do not lead to the showing
and hiding of the combobox dropdown list. It still only SHOWS the dropdown
list as with a single command.
The "combobox_widget_object.after(delay_ms, callback=None, *args)"
method can be used to instruct the combobox to run a function
after certain time delays. That function should contain the
"combobox_widget_object.event_generate('<Button-1>')" method to cause the
hiding of the dropdown list.
CODE:
# tkinter modules
import tkinter as tk
import tkinter.ttk as ttk
"""
Aim:
Create a combobox widget and use w.event_generate(sequence, sequence,**kw) to
simulate external stimuli to cause combobox dropdown list to show and hide.
Author: Sun Bear
Date: 16/01/2017
"""
# Function to activate combobox's '<Button-1>' event
def _source_delayed_clicked():
print ('\n def __source_delayed_clicked():')
print('Delayed 2nd simulation of external stimuli')
print('HIDE combobox Dropdown list. \n'
'IT WORKED!')
source.event_generate('<Button-1>')
root = tk.Tk()
source_var=tk.StringVar()
reference=['Peter', 'Scotty', 'Walter', 'Scott', 'Mary', 'Sarah']
# Create Main Frame in root
frame0 = ttk.Frame(root, borderwidth=10, relief=tk.RAISED)
frame0.grid(row=0, column=0, sticky='nsew')
# Create Combobox
source = ttk.Combobox(frame0, textvariable=source_var, values=reference)
source.grid(row=0, column=0, sticky='nsew')
# Simulate external stimuli using w.event_generate(sequence,**kw)
print('\n', '1st simulation of external stimuli using: \n'
' source.event_generate('"<Button-1>"') \n'
' SHOW Combobox Dropdown List.')
source.event_generate('<Button-1>')
#source.event_generate('<Button-1>') # running another similar command
# back to back didn't work
delay = 1000*6 # 6 seconds delay
source.after(delay, _source_delayed_clicked)
Update:
Alternatively, to hide the combobox dropdown list, the command
source.event_generate('<Escape>') can be used in place of the source.event_generate('<Button-1>') command defined in the function def _source_delayed_clicked(). This simulates pressing the keyboard "Esc" key.

Related

receiving selected choice on Tkinter listbox

I'm trying to build a listbox using Tkinter and receive the selected option by clicking it.
import Tkinter as tk
from Tkinter import *
root = tk.Tk()
lst=Listbox(root, height=30, width=50)
lst.insert(1, "hy")
lst.insert(2, "hello")
lst.insert(3, "hey")
lst.pack()
sel = lst.curselection()
print sel
root.mainloop()
However, when I run the code it prints me an empty tuple before I pressed any choise.
Does someone know how to get the selected choise after I press one and not right after I run it?
Thanks a lot :)
You are getting the selection about a millisecond after creating the widget, well before the user has a chance to see the UI much less interact with it.
GUI programs are event based, meaning that things happen in response to events. Events are things like clicking buttons, inserting data into input widgets, and selecting items from listboxes.
You need to do one of two things: create a button or other widget which will get the selected item, or configure it so that a function is called whenever an item is selected.
No matter which solution you use, you will need a function that ultimately calls the curselection method of the listbox to get a list of indices. You can then call the get method to get the selected item or items.
Here's a function definition that will print the selected item, or print "no selection" if nothing is selected. So that it can be resused without modification. we'll define it to take the listbox as an argument.
Note: this example assumes the widget only supports a single select, to keep it simple:
def print_selection(listbox):
selection = listbox.curselection()
if selection:
print(f"selected item: {listbox.get(selection[0])}")
else:
print("nothing is selected")
Using a button
To call this from a button is straight-forward. We just create a button after we create the listbox, and use the command attribute to call the function. Since the function we wrote earlier needs a parameter, we'll use lambda to create a temporary function for the button.
button = tk.Button(root, text="Print Selected Item", command=lambda: print_selection(lst))
button.pack()
Calling the function when the selection is made
To call the function whenever the user changes the selection, we can bind a function to the <<ListboxSelect>> event. We'll create a separate function for this, and then pull the widget from the event object that is automatically passed to the function.
def print_callback(event):
print_selection(event.widget)
lst.bind("<<ListboxSelect>>", print_callback)
First of all, the reason you are getting an empty tuple is because you have executed the statements:
sel = lst.curselection()
print(sel)
before you have executed the root.mainloop()
Secondly, your setup for listbox fails to include a StringVar variable to hold your list.
Once the variable has been defined, you should be able to use the .insert statements to add your list items one at a time, or you can initialize the StringVar variable using a .set('hy', 'hello', 'hey') command.
To provide a return of a selected variable, you must incorporate an event handler to determine the list position selected onclick or some other triggering method.
For a pretty clear explanation of these characteristics check here

How to keep a button stuck at the bottom of a tkniter window?

I am trying to create a tkinter window with a button that is stuck at the bottom. I want the button to be used to add new items to the window (separate code, not shown here), but always keep the button at the bottom. Something like the following:
import tkinter as tk
m = tk.Tk(className="My window")
create = tk.Button(m, text="Create new item", width=25)
create.grid(row=inf, padx=40, pady=20)
m.mainloop()
except of course tkinter.grid() doesn;t accept inf as a valid value for row=. My question is how can I ensure that even as I add items to the tkinter window, my button will always remain on the bottom.
You can combine pack and grid.
Your base application could be this
upperframe.pack(side=tk.TOP)
bottomframe.pack(side=tk.BOTTOM)
Within the upperframe use your grid to add buttons and whatever. The bottom frame containing your changing buttons will be stuck there. You can use pack or grid or whatever you like there.
Creating a status bar goes much the same way.

How to cause ttk OptionMenu dropdown selection (expansion) on object focus via tab traversion?

We have a Tkinter form with ttk OptionMenu dropdown boxes on it. When you click on the dropdown you can start typing your selection and the dropdown will automatically focus on the selection that starts with those characters.
The problem occurs when you use tab to traverse to the menu, rather than using the mouse. Tab will highlight the ttk.OptionMenu however it will not expand the dropdown to begin typing text unless you hit the spacebar or click.
Is there are way to force click() or something any time the OptionMenu gets tab focused?
I attempted to do some stuff with .bind and .configure but I'm definitely lost :)
cust_selection = StringVar(window)
customers = getcustomerlist() # pulls customer list from file
vic_name_lbl = Label(window, text="Select Customer:")
vic_name_lbl.grid(column=0, row=3)
vic_name_box = ttk.OptionMenu(window, cust_selection, *customers)
vic_name_box.grid(column=1, row=3, sticky=(W,E))
vic_name_box.configure(width=15)
Not sure if this is helpful, but the documentation says that all ttk widgets have a takefocus attribute, which:
Determines whether the window accepts the focus during keyboard
traversal. 0, 1 or an empty string is returned. If 0 is returned, it
means that the window should be skipped entirely during keyboard
traversal. If 1, it means that the window should receive the input
focus as long as it is viewable. And an empty string means that the
traversal scripts make the decision about whether or not to focus on
the window.

Tkinter active fill by tag

I'm designing a GUI application using Tkinter and for this project, I need buttons for the menu. While looking into the buttons I wasn't blown away by the customization options that come with the buttons, especially when I found out that you can bind click arguments to rectangles.
This allows me to customize the "button" in (almost) limitless ways, but to allow me to put text on the button I need to create a rectangle element and a text element and bind them together using Tkinter's tag_bind property.
One of the design properties of the button that I wanted was active fill when the user moused over the element. Right now I'm just using activefill="" which works, except the text element and the button element will only fill while the mouse is over that element. So, for example, when I mouse over the button the button excluding the text will highlight and vise versa when I mouse over the text.
Below is a simplified (for brevity) version of what I use to generate the buttons;
button = canvas.create_rectangle(button_width, button_height, 10, 10, fill="000", activefill="111", tags="test")
text = canvas.create_text((button_width/2), (button_height/2), activefill="111", tags="test")
canvas.tag_bind("test", "<Button-1>", "foo")
Is there a way to bind the active fill function to a tag rather than a specific element?
Another option is that I completely missed a bunch of information about customizing the buttons in Tkinter, and I would not be apposed to learning about that.
Option 1
I would personally not go for the presented solution. I do not know if you are using the button provided by tk or ttk. But, with the tkinter.tk, you could absolutely change the appearance of the button.
Following, I give you an example that produces a button with the following characteristics:
Blue foreground
Flat appearance
When hovered, the background is green
When pressed, the background is red
The code is as follows:
import tkinter as tk
root = tk.Tk()
# Function hovering
def on_enter(e):
btn['background'] = 'green'
def on_leave(e):
btn['background'] = 'white'
# Create the button
btn = tk.Button(root, background='white', activebackground='red', foreground='blue',relief='flat',text='Test',width=20)
btn.pack()
# Bindings
btn.bind("<Enter>", on_enter)
btn.bind("<Leave>", on_leave)
# Loop
root.mainloop()
Option 2
If even after having tried the tk.Button, you are not glad with the result, I would create a Frame containing a Label (you can do nearly anything with that combination). Then, you could change the background of the frame according to any user action, like:
import tkinter as tk
root = tk.Tk()
# Function hovering
def on_enter(e):
lab['background'] = 'green'
def on_leave(e):
lab['background'] = 'white'
# Click
def on_click(e):
print("hi")
# Create the frame with a label inside
fr = tk.Frame(root)
lab = tk.Label(fr, text="Test", width=20, background="white")
# Packing
fr.pack()
lab.pack()
# Bindings
fr.bind("<Enter>", on_enter)
fr.bind("<Leave>", on_leave)
lab.bind("<Button-1>", on_click)
# Loop
root.mainloop()
You could even create a class with the above combination.

Tkinter Menu and Buttons

I'm trying to make a Tkinter menu that is like a taskbar checker.
So if I go to this menu and check a box, a specific button then appears on my window, and then the user can select multiple buttons based on what they want.
The program is just a bunch of buttons that after entering text in my text field, and clicking the button, a web browser launches with a search of the website that the button is linked to.
How can I make a menu like I mentioned above?
Edit:
I've just tried basic menu stuff:
buttonmenu = Menu(menubar, tearoff=0)
buttonmenu.add_command(label="button1", command=turnbuttononoff)
buttonmenu.add_command(label="button2", command=turnbuttononoff)
buttonmenu.add_command(label="button3", command=turnbuttononoff)
buttonmenu.add_command(label="button4", command=turnbuttononoff)
buttonmenu.add_command(label="button5", command=turnbuttononoff)
This just creates a basic menu. And if I could have a function that triggers a button to be turned on or off that would be great.
So essentially just a function to swap a button from being shown to not being shown
def turnbuttononoff():
#togglebutton here
ANSWER:
I made a dictionary of the data of where each button was stored, and then checked to see if the button was active, and if it was, turned it off, and if it was inactive, turn it off.
Making this a command lambda function for each button works.
def Toggle_Button(myButton):
if myButton.winfo_ismapped()==1:
myButton.grid_forget()
else:
myButton.grid(row=gridData[myButton][0],column=gridData[myButton][1])
gridData = {}
gridData[button] = [row,col]
def Toggle_Button(myButton):
if myButton.winfo_ismapped()==1:
myButton.grid_forget()
else:
myButton.grid(row=gridData[myButton][0],column=gridData[myButton][1])
If you already have buttons on a grid, use button.grid_info to find what you need, it returns a dictionary.

Categories