python tkinter.ttk combobox down event on mouseclick - python

I do not find a correct answer to my issue, despite intense research and a rather simple problem.
All I would like to do, is my comboboxes drop down when clicked on by 'Button-1'. But regardless of what I code, the combos don't behave as I wish.
following I prepared a simple code to demonstrate my problem:
from tkinter import *
import tkinter.ttk
def combo_events(evt):
if int(evt.type) is 4:
w = evt.widget
w.event_generate('<Down>')
root = Tk()
li = ('row 1', 'row 2', 'row 3')
combo1 = tkinter.ttk.Combobox(root, value=li)
combo2 = tkinter.ttk.Combobox(root, value=li)
combo1.bind('<Button-1>', combo_events)
combo2.bind('<Button-1>', combo_events)
combo1.pack()
combo2.pack()
root.mainloop()
Well, if I try this code, the combos do dropdown, but not as expected. So, I tried to add a bind of the 'FocusIn' event but that rather complicates the situation and inhibits a 'FocusOut' ...
Can any1 help me to achieve my goal?
ps: I know, that the combo will drop down by clicking on the frame of the widget, but to be more precise I would like to drop it, when clicking into it.
And by the way, where do I find a rather complete list of events a combobox can trigger?
thx for effort and answer.

Why are you using int(evt.type) is 4 instead of int(evt.type) == 4 ?
Applying this change it works for me.
Edit 1
First of all, thank you for explaining to us what you really want to have. Did not expect this from your initial question.
If you want to override the editing behaviour it is time to dig a little deeper.
The widget you click into inside the combobox is an entry widget. What you can do now is to define when your event shall be fetched inside the event chain. Will apply code soon.
Edit 2
To get it at the first mouse click:
w.event_generate('<Down>', when='head')
Why? Because default of Event Generate is to append the generated Event to the Event Chain (put it at its end, value = 'tail'). Changing to when='head' gives your desired behaviour.

Related

Is it possible to alter binding order of a Tk/Tkinter Listbox widget <<ListboxSelect>> event

As far as I understand, the "normal" binding events order of a Tk/Tkinter Listbox widget is (simplifying): <ButtonPress>, <<ListboxSelect>>, <ButtonRelease>
Is it possible to "change" the order so to have <<ListboxSelect>> event triggering after the <ButtonRelease> one?
I was trying using bindtags together with a custom "fake" bind_class for this but without getting the desired result so far...
Here's the code sample:
import tkinter as tk
root = tk.Tk()
l = tk.Listbox(root, name='custlist')
for e in range(55): l.insert(tk.END, 'L_item'+str(e))
l.pack()
l.bind('<ButtonPress>', lambda e: print("L: Click"))
l.bind('<ButtonRelease>', lambda e: print("L: ButtonRelease"))
l.bind_class("post-class-bindings", "<<ListboxSelect>>", lambda e: print("L: post-ListboxSelect"))
l.bindtags((l.winfo_pathname(l.winfo_id()),'Listbox','post-class-bindings', '.', 'all'))
Can you tell me if is it possible to obtain such a thing in this way or another?
Thank you
EDIT:
Thinking of it I realize I can't get it that way 'cause I guess
<ButtonPress>+<<ListboxSelect>> events are somewhat "chained" in their flow while <ButtonRelease> is something "untied", so that I should invoke/generate the <<ListboxSelect>> event at the end of the <ButtonRelease> callback to trigger it... That might work most of the times but... That's not what I was trying to get here... (Indeed, that would be just like "moving" the <<ListboxSelect>> event code to the <ButtonRelease> callbak...)
So, in the end, the question would be if is it possible to "chain" ButtonRelease together with ListboxSelect (in the same way as for ButtonPress)? ... And I guess something like that isn't possible
Is it possible to "change" the order so to have <<ListboxSelect>> event triggering after the one?
The only way to do that is to prevent the selection from changing on a button click. <<ListboxSelect>> isn't a direct result of a button click or a button release, it's a direct result of the selection having been changed. You can't have the selection change on a click and not have <<ListboxSelect>> be generated.
I don't quite understand what your real need is, but if you want to be notified of a selection change on the release of the button you can always emit your own custom virtual event in a handler for <ButtonRelease-1>, for instance, <<ListboxSelectAfterClick>>.
Thanks to Bryan Oakley's clarifications I came to a possible solution for this:
The main intent for me was to have the element
selection in the list to activate/trigger only after the mouse button being released.
import tkinter as tk
root = tk.Tk()
l = tk.Listbox(temp, name='custlist', selectmode='single')
for e in range(55): l.insert(tk.END, 'L_item'+str(e))
l.pack()
l.bind('<ButtonPress>', lambda e: "break")
def AfterReleaseSelect(event):
event.widget.selection_clear(0,tk.END)
event.widget.selection_set(event.widget.nearest(event.y))
#more stuffs here if needed...
l.bind('<ButtonRelease>', AfterReleaseSelect)
What I'm doing here, in practice, is preventing the button click to do its job, so that the listbox selection won't be triggering, and then wait for the mouse button being released to do the stuff.
Some final thoughts on this:
First of all, please notice: I added selectmode='single' parameter to the Listbox widget here as I think it wouldn't be worth to apply such approach when dealing with default ("'browse'-select") Listboxes. This is because, differently than "'browse'-select" Listboxes, with "'single'-select" Listboxes elements selection doesn't "follow" the mouse 'till the end, it gets "stuck" on the first-clicked-element, even if you end releasing the mouse button on a different item.
I must say, anyhow, this probably won't be worth most of the times 'cause you might simply:
avoid using selectmode='single' parameter, staying with the default listbox 'browse'-select-behavior;
"move" the code you would execute with a bind <<ListboxSelect>> to a bind on <ButtonRelease> (as far as this doesn't involve too much "twisting" on the rest of the code, of course...)

How to have multiple widget buttons perform different actions in Jupyter Notebook?

I am trying to make a series of buttons that take samples from a data set based on some scenario. I have a 3x2 group of buttons, each describing a different scenario. I can't seem to get them to perform their separate actions.
I think I understand how to connect the action of clicking a button to its response. However, I don't understand how to do the same for multiple buttons.
Here's my code that worked to get a single, standalone button to work:
button = widgets.Button(description='Generate message!')
out = widgets.Output()
def on_button_clicked(_):
samp_text = raw_data.sample(1).column(1)
# "linking function with output"
with out:
# what happens when we press the button
print(samp_text)
# linking button and function together using a button's method
button.on_click(on_button_clicked)
# displaying button and its output together
widgets.VBox([button,out])
Now what I'm trying to do is take different kinds of samples given various situations. So I have functions written for each type of sampling method that returns a table of proportions:
1 47.739362
2 44.680851
3 4.920213
9 2.659574
Name: vote, dtype: float64
However the same method in the first example with just one button doesn't work the same with multiple. How do I use widgets.Output() and how do I connect it so that clicking the button outputs the corresponding sample summary?
I expect for a clicked button to output its sample summary as shown above.
I didn't have any problem extending your example to use
multiple buttons. I don't know where you were confused.
Sometimes exceptions that occur in widget callbacks do not
get printed -- maybe you had a bug in your code that you couldn't
see for that reason. It's best to have everything
wrapped in a "with out:"
Created two buttons using the list. Guess code itself explains better.
from ipywidgets import Button, HBox
thisandthat = ['ON', 'OFF']
switch = [Button(description=name) for name in thisandthat]
combined = HBox([items for items in switch])
def upon_clicked(btn):
print(f'The circuit is {btn.description}.', end='\x1b\r')
for n in range(len(thisandthat)):
switch[n].style.button_color = 'gray'
btn.style.button_color = 'pink'
for n in range(len(thisandthat)):
switch[n].on_click(upon_clicked)
display(combined)

page GUI - Combobox pass variable

New to GUI. Not quite getting there. I used page and get can get buttons to do something (click on a button and get a response). With Combobox, I can't pass a value. Searched here, tried many things, watched a few hours of youtube tutorials.
What am I doing wrong below? This is the code page generates (basically) then I added what I think I need to do to use the Combobox.
I am just trying to have 1,2,3 in a combo box and print out the value that is chosen. Once I figure that out I think I can actually make a simple GUI that passes variables I can then program what I want to do with these variables being selected.
class New_Toplevel_1:
def __init__(self, top):
self.box_value = StringVar()
self.TCombobox1 = ttk.Combobox(textvariable=self.box_value)
self.TCombobox1.place(relx=0.52, rely=0.38, relheight=0.05, relwidth=0.24)
self.TCombobox1['values']=['1','2','3']
self.TCombobox1.configure(background="#ffff80")
self.TCombobox1.configure(takefocus="")
self.TCombobox1.bind('<<ComboboxSelected>>',func=select_combobox)
def select_combobox(self,top=None):
print 'test combo ' # this prints so the bind works
self.value_of_combo = self.ttk.Combobox.get() # this plus many other attempts does not work
It's hard to know what you're actually asking about, since there is more than one thing wrong with your code. Since you say the print statement is working, I'm assuming the only problem you have with your actual code is with the last line.
To get the value of the combobox, get the value of the associated variable:
self.value_of_combo = self.box_value.get()
Here's a working version where I fixed the other things that were wrong with the program:
from tkinter import *
from tkinter import ttk
class New_Toplevel_1:
def __init__(self, top):
self.box_value = StringVar()
self.TCombobox1 = ttk.Combobox(textvariable=self.box_value)
self.TCombobox1.place(relx=0.52, rely=0.38, relheight=0.05, relwidth=0.24)
self.TCombobox1['values']=['1','2','3']
self.TCombobox1.configure(background="#ffff80")
self.TCombobox1.configure(takefocus="")
self.TCombobox1.bind('<<ComboboxSelected>>',func=self.select_combobox)
def select_combobox(self,top=None):
print('test combo ') # this prints so the bind works
self.value_of_combo = self.box_value.get()
print(self.value_of_combo)
root = Tk()
top = New_Toplevel_1(root)
root.mainloop()
Note: I strongly advise you not to start with place. You should try to learn pack and place first. I know place seems easier, but to get the most responsive and flexible GUI you should leverage the power of pack and grid.

Trying to check multiple qt radio buttons with python

I need to check multiple radio buttons from a qt ui with python.
Up to now we are using something similar to:
if main.ui.radioButton_1.isChecked():
responses["q1"] = "1"
elif main.ui.radioButton_2.isChecked():
responses["q1"] = "2"
elif main.ui.radioButton_3.isChecked():
responses["q1"] = "3"
if main.ui.radioButton_4.isChecked():
responses["q2"] = "1"
elif main.ui.radioButton_5.isChecked():
responses["q2"] = "2"
elif main.ui.radioButton_6.isChecked():
responses["q2"] = "3"
...
Since there are very many buttons and many different categories (q1, q2, ...) I was thinking of optimizing it a bit. So this is what I hoped would work (adopted from How to get the checked radiobutton from a groupbox in pyqt):
for i, button in enumerate(["main.ui.radioButton_" + str(1) for i in range(1, 8)]):
if button.isChecked():
responses["q1"] = str(i - 1)
I get why this doesn't work but writing it I hoped it would.
So I tried to iterate through the buttons using something similar to (Is there a way to loop through and execute all of the functions in a Python class?):
for idx, name, val in enumerate(main.ui.__dict__.iteritems()):
and then use some modulo 3 and such to assign the results. But that doesn't work either. Not sure if it's because i used __ dict __ or something else. The error I got was:
TypeError: 'QLabel' object is not iterable
Now some people could say that implicit is better that explicit and also because of readability the if elif chain is good the way it is but there are 400+ lines of that. Also after reading this post, Most efficient way of making an if-elif-elif-else statement when the else is done the most?, I thought there must be a better and more efficient way of doing this (see examples 3.py and 4.py of the of the accepted answer). Because I need to check the Boolean value of main.ui.radioButton_1.isChecked() and then assign thevalue according to the Buttons group (q1, q2,...), I haven't managed to implement the solution using dictionaries as described in the post.
Am I stuck with the if elif chain or is there a way to not only reduce the LOC but also make the code more efficient (faster)?
It looks like you have used Qt Designer to create your ui, so I would suggest putting each set of radio buttons in a QButtonGroup. This will give you a simple, ready-made API for getting the checked button in a group without having to query each button individually.
In Qt Designer, buttons can be added to a button-group by selecting them, and then choosing Assign to button group > New button group from the context menu. The button IDs (which you will need to use later) are assigned in the order the buttons are selected. So use Ctrl+Click to select each button of a group in the correct order. The IDs start at 1 for each group and just increase by one for each button that is added to that group.
When a new button-group is added, it will appear in the Object Inspector. This will allow you to select it and give it a more meaningful name.
Once you've created all the groups, you can get the checked button of a group like this:
responses["q1"] = str(main.ui.groupQ1.checkedId())
responses["q2"] = str(main.ui.groupQ2.checkedId())
# etc...
This could be simplified even further to process all the groups in a loop:
for index in range(1, 10):
key = 'q%d' % index
group = 'groupQ%d' % index
responses[key] = str(getattr(main.ui, group).checkedId())
Another way to do it is using signals. If you had lots of radio button in an application, I suspect this kind of approach would be noticeably faster. For example:
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class MoodExample(QGroupBox):
def __init__(self):
super(MoodExample, self).__init__()
# Create an array of radio buttons
moods = [QRadioButton("Happy"), QRadioButton("Sad"), QRadioButton("Angry")]
# Set a radio button to be checked by default
moods[0].setChecked(True)
# Radio buttons usually are in a vertical layout
button_layout = QVBoxLayout()
# Create a button group for radio buttons
self.mood_button_group = QButtonGroup()
for i in xrange(len(moods)):
# Add each radio button to the button layout
button_layout.addWidget(moods[i])
# Add each radio button to the button group & give it an ID of i
self.mood_button_group.addButton(moods[i], i)
# Connect each radio button to a method to run when it's clicked
self.connect(moods[i], SIGNAL("clicked()"), self.radio_button_clicked)
# Set the layout of the group box to the button layout
self.setLayout(button_layout)
#Print out the ID & text of the checked radio button
def radio_button_clicked(self):
print(self.mood_button_group.checkedId())
print(self.mood_button_group.checkedButton().text())
app = QApplication(sys.argv)
mood_example = MoodExample()
mood_example.show()
sys.exit(app.exec_())
I found more information at:
http://codeprogress.com/python/libraries/pyqt/showPyQTExample.php?index=387&key=QButtonGroupClick
http://www.pythonschool.net/pyqt/radio-button-widget/

Populating a Glade combobox "gtk_entry_set_text: assertion `text != NULL' failed"?

I am building a “pseudo-intelligent” GUI for a gimp plugin using Glade. The main part of the GUI has two frames and imports the contents using the “reparent” method. The main objective is to have the contents of the second frame determined by the selections made in the first frame. (Eventually, the intention is to import this GUI as the content for the tabbed pages of a "notebook")
To start with, I made a simple window, consisting of a “RadioButtonBox” and a “ComboBox” which is populated using:
# create the cell renderer
self.cell = gtk.CellRendererText()
#populate the default choice into the Selection combobox
self.SelectionBox = self.builder.get_object("SelectionBox")
self.SelectionBox.set_model(self.EditCommands)
self.SelectionBox.pack_start(self.cell, True)
self.SelectionBox.add_attribute(self.cell, 'text', 1)
self.SelectionBox.set_active(0)
# End: populate the selection combo box section
This works and I can successfully “import” and “reparent” the simple GUI as the first frame of the larger, more complex GUI without any problems. However, as the design progressed, it has become more convenient to have the code for the first frame as an integral part of the main GUI, and this is where my problems begin.
I have reproduced the contents of the simple GUI in the first frame of the larger GUI and copy/pasted the code from the simple GUI's “init” function. In other-words, everything is identical.
Unfortunately, when I run the code I get the following error:
C:\Documents and Settings\anonymous\Desktop\Glade-tutorial\BatchEditMain\BatchEditLibrary\Tab.py:46: GtkWarning: gtk_entry_set_text: assertion `text != NULL' failed
self.SelectionBox.set_active(0)
Could someone please explain what the problem is?
Thanks in advance
Irvine
The GTK Warning is saying that somewhere gtk_entry.set_text() is being called with None instead of some text. This is happening in the call to self.SelectionBox.set_active(0)
This feels a little bit like gravedigging , but i stumbled across the same Problem today and this was the first post google showed...
With a little further research i found this Question:
How create a combobox on Python with GTK3?
The author seems to have the same error.
What in his and my case seems to do the trick is a simple:
combobox.set_entry_text_column(0)
Important it has to be in front of set_active(0)!
So in your case this would be:
...
self.SelectionBox.add_attribute(self.cell, 'text', 1)
self.SelectionBox.set_entry_text_column(0)
self.SelectionBox.set_active(0)
...
PS: Whatch out if you want to apply attributes like "foreground", they seem to get overwritten by set_entry_text_column(0) if set befor.
E.g: if items in the model look like:
["TEXT_YOU_WANT_TO_DISPLAY","TEXT_FOREGROUNDCOLOR_AS_MARKUP_COLOR"]
The change to the foregroundcolor can be applied with the following:
...
self.SelectionBox.add_attribute(self.cell, 'text', 0)
self.SelectionBox.set_entry_text_column(0)
self.SelectionBox.add_attribute(self.cell, 'foreground', 1)
self.SelectionBox.set_active(0)
...

Categories

Resources