I have designed a gtk3 layout with Glade, including some comboboxtext widgets.
The bookings_tour_selector ComboBoxText has the changed signal connected, so when user selects an option, this is detected. That part works fine.
Now the problem, when I make the call: bookings_tour_selector.remove_all() the changed signal is triggered once for every single item being removed. That's not the expected behaviour. I expect it to not trigger the signal at all.
How to prevent this signal to be triggered when removing items?
Just add conditional in your callback, i.e:
def on_changed_combobox (self, widget):
if self.bookings_tour_selector.get_active () != -1:
#do whatever you want when combo box changed
#if not, it simply, does nothing
Related
I'm implementing some customised behaviour for a QFileDialog.
reference_dir = r'D:\My Documents\month'
create_dir_dialog.setDirectory(reference_dir)
create_dir_dialog.setFileMode(create_dir_dialog.Directory)
options = create_dir_dialog.Options(create_dir_dialog.DontUseNativeDialog | create_dir_dialog.ShowDirsOnly)
create_dir_dialog.setOptions(options)
# identify "Choose" button
choose_button = create_dir_dialog.findChild(QtWidgets.QDialogButtonBox).button(
QtWidgets.QDialogButtonBox.Open)
# disconnect "clicked" signal from this button
choose_button.disconnect()
def check_line_edit(path):
# this slot handles enablement of the "Choose" button: if the text
# corresponds to an already-existing directory the button is disabled.
# This is because we are trying to create a new directory.
...
# get the line edit used for the path
lineEdit = create_dir_dialog.findChild(QtWidgets.QLineEdit)
lineEdit.textChanged.connect(check_line_edit)
def create_dir_and_proj():
# this function creates a directory based on the text in the QLE
new_dir = lineEdit.text()
...
create_dir_dialog.close() # programmatically close the dialog
# use the above method as the slot for the clicked signal
choose_button.clicked.connect(create_dir_and_proj)
dlg_result = create_dir_dialog.exec_()
# thread gets held up here
To my delight this works OK.
There's only one fly in the ointment: if, instead of clicking "Choose" with the mouse or using the mnemonic Alt-C to cause the click (both of which cause create_dir_and_proj to run OK), I just press "Return" key when focus is on the dialog, the previous (standard) behaviour occurs, i.e. the behaviour (slot) that I disconnected from the "Choose" button's click signal. This then causes a message box to come up saying "directory does not exist". But the point is that my new slot wants to create the new directory as entered by the user.
I surmise that this is because the "Choose" button is the "default" button of the dialog, and that things have been wired up so that this "Return-key-pressed" signal is wired up to the standard slot as normally used by the "Choose" button.
How do I get hold of this signal with a view to disconnecting it and wiring up a new slot (i.e. the above create_dir_and_proj function)?
That message-box will appear when the line-edit has focus and you press return/enter. The returnPressed signal of the line-edit is connected to the QFileDialog.accept slot. The resulting behaviour will then depend on the FileMode. For the Directory mode, this equates to a request to open the specified directory, which will obviously fail if it doesn't exist.
To override this behviour, you can simply connect your own slot:
lineEdit.returnPressed.disconnect()
lineEdit.returnPressed.connect(create_dir_and_proj)
When the line-edit does not have the focus, pressing return/enter will activate the default button (unless the current focus-widget has built-in behaviour of its own: e.g. navigating to the selected directory in the tree-view). Since you've connected your own slot to the clicked signal of this button, your slot will be called by default.
UPDATE:
It seems that connecting to a Python slot that forms a closure over the dialog can affect the order in which objects get deleted. This may sometimes result in the line-edit completer popup being left without a parent after the dialog closes, which means it won't get deleted and may remain visible on screen. A couple of possible work-arounds are to either explicitly close the popup inside the slot:
def create_dir_and_proj():
lineEdit.completer().popup().hide()
dialog.close()
or disconnect all signals connected to the slot before closing:
def create_dir_and_proj():
choose_button.clicked.disconnect()
lineEdit.returnPressed.disconnect()
dialog.close()
(NB: I have only tested this on Linux; there's no guarantee the behaviour will be the same on other platforms).
I need my GUI to feedback the user. For this purpose I color the background of a certain button in Green or Red according to the result of a certain verification. My purpose is to leave the Green color for several seconds and then return to the initial state (clear the fields and recover the button's original color).
My problem is that the "after" function, that I use for delaying the GUI before I return to the default GUI, doesn't show the before and after. I only see that the button is SUNKEN and then raised after the fields and buttons already returned to their defaults.
What am I doing wrong ?
if condition1 == condition2:
orig_color = self.button2.cget("background")
self.button2.config(bg='springgreen2')
self.return2default(orig_color)
self.after(3000) # 3 seconds delay to realize a Pass result
# return to the defaults
self.SN_field.delete("1.0", "end")
self.HwVer_field.delete("1.0", "end")
self.button2.config(bg=color)
else:
self.button2.config(bg='red2')
If you want something to happen after a delay, move that "something" into a function and schedule it to be called with after. That will allow the event loop to continue to process events (including events that cause the display to refresh).
For example:
def reset(self):
self.SN_field.dellete("1.0", ,"end")
self.HwVer_field.delete("1.0", "end")
self.button2.config(bg=color)
To call it, use after:
if condition1 == condition2:
orig_color = self.button2.cget("background")
self.button2.config(bg='springgreen2')
self.return2default(orig_color)
self.after(3000, self.reset)
When you call after with only a time argument, it causes your entire program to go to sleep, which means it is not able to update the display while it is sleeping.
I am trying to figure out how to unittest a bind command in a dialog window. I'm attempting this with tkinter's event_generate. It is not working the way I expect. For this StackOverflow question I've set up some code with a single call to event_generate. Sometimes that line works and sometimes it is as if the line doesn't even exist.
The bind in the dialog's __init__ method looks like this:
self.bind('<BackSpace>', #Print "BackSpace event generated."
lambda event: print(event.keysym, 'event generated.'))
Any action in the dialog will call back to its terminate method (The dialog is based on Frederik Lundh's example Dialog in 'An Introduction to Tkinter'.)
def terminate(self, event=None):
print('terminate called') # Make sure we got here and the next line will be called
self.event_generate('<BackSpace>')
self.parent.focus_set()
self.destroy()
When the dialog is called using the code below any user action will end up calling terminate. In each case "terminate called" and "BackSpace event generated." are displayed. This proves that the call to event_generate is set up correctly.
parent = tk.Tk()
dialog = Dialog(parent)
dialog.wait_window()
In case it's relevant I ought to mention that I have moved Lundh's call to self.wait_window from his dialog's __init__ method to the caller. Whilst this breaks the neat encapsulation of his dialog it appears to be necessary for automated unittests. Otherwise the unittest will display the dialog and halt waiting for user input. I don't like this solution but I'm not aware of any alternative.
The problem I'm having is when wait_window is replaced with a direct call to the terminate method. This is the sort of thing that I'd expect to be able to do in unittesting which is to test my GUI code without running tkinter's mainloop or wait_window.
parent = tk.Tk()
dialog = Dialog(parent)
dialog.terminate()
This only prints "terminate called" and does not print "BackSpace event generated.". The call to event_generate appears to have no effect. If I follow the call in the debugger I can see that tkinter's event_generate() is being called with the correct arguments. self = {Dialog} .99999999, sequence = {str}'<BackSpace>', kw = {dict}{}
In view of the warning in the TkCmd man pages about window focus I have verified the dialog with the binding is given focus in its __init__ method.
Tkinter is not executing the callback. Why?
EDIT: This bare bones code shows update working. However, it only works if it is called in __init__ before event_generate is called by the main program. (This puzzle has been raised as a separate question)
class UpdWin(tk.Tk):
def __init__(self):
super().__init__()
self.bind('<BackSpace>',
lambda event: print(event.keysym, 'event generated.'))
self.update() # Update works if placed here
app = UpdWin()
app.event_generate('<BackSpace>')
# app.update() # Update doesn't work if placed here
Six Years On
4/12/2021. See Mark Roseman's excellent web site for a detailed explanation of why any use of update is a bad idea.
The problem posed by this six year old question is entirely avoided by better program design in which tkinter widget objects are never subclassed. Instead they should be created by composition where they can be easily monkey patched. (This advice is contrary to patterns shown in Frederik Lundh's example Dialog in 'An Introduction to Tkinter'.)
For unittest design, not only is there no need to start Tk/Tcl via tkinter but it is also unwise.
event_generate will by default process all event callbacks immediately. However, if you don't call update before calling event_generate, the window won't be visible and tkinter will likely ignore any events. You can control when the generated event is processed with the when attribute. By default the value is "now", but another choice is "tail" which means to append it to the event queue after any events (such as redraws) have been processed.
Full documentation on the when attribute is on the tcl/tk man page for event_generate: http://tcl.tk/man/tcl8.5/TkCmd/event.htm#M34
Don't know if this is relevant to your problem, but I got widget.event_generate() to work by calling widget.focus_set() first.
#lemi57ssss I know this is an old question, but I just want to highlight the point brought up by Bryan Oakley and to correct your last code to make it work. He said you have to update first before it can respond to the generated event. So if you switch the positions of update() and event_generate(), you will get the "BackSpace event generated." text printed out.
It worked when you put the update() in the __init__() was because of the same reason, i.e., it got called first before the event_generated().
See the amended code below:
class UpdWin(tk.Tk):
def __init__(self):
super().__init__()
self.bind('<BackSpace>',
lambda event: print(event.keysym, 'event generated.'))
#self.update() # Update works if placed here
app = UpdWin()
app.update() # Update also works if you placed it here
app.event_generate('<BackSpace>')
In My App, I have a QWidget which is not showing after I call show(), even though isVisible returns true.
This widget is created from an event of the main application window. But when its started on its own, i.e., as the only widget on an app, it shows up normally.
Anyone knows what may cause this behavior?
Other widgets in my app show up normally only this one is giving me troubles. It actually use to work just fine under a previous version of Qt4 (don't remember which).
the code for the widget is here
update: windows seems to appear and is immediately destroyed.
The relevant code is in hidx/GUI/main.py:
#pyqtSignature("")
def on_actionScatterplot_Matrix_activated(self):
...
spm = scatmat.ScatMat(pars, self.currentdbname)
print "==>", spm.pw.isVisible()
spm.pw.hide()
spm.pw.showMaximized()
print spm.pw.size()
print "==>", spm.pw.isVisible()
#pyqtSignature("int")
def on_rowStart_valueChanged(self, p0):
...
In on_actionScatterplot_Matrix_activated, you create an instance of ScatMat, but don't keep a reference to it. So the window will be briefly shown, and then immediately garbage-collected once the function completes.
I have a RichTextCtrl in my application, that has a handler for EVT_KEY_DOWN. The code that is executed is the following :
def move_caret(self):
pdb.set_trace()
self.rich.GetCaret().Move((0,0))
self.Refresh()
def onClick(self,event):
self.move_caret()
event.Skip()
rich is my RichTextCtrl.
Here is what I would like it to do :
on each key press, add the key to the control ( which is default behaviour )
move the cursor at the beginning of the control, first position
Here's what it actually does :
it adds the key to the control
I inspected the caret position, and the debugger reports it's located at 0,0 but on the control, it blinks at the current position ( which is position before I pressed a key + 1 )
Do you see anything wrong here? There must be something I'm doing wrong.
Apparently, there are two problems with your code:
You listen on EVT_KEY_DOWN, which is probably handled before EVT_TEXT, whose default handler sets the cursor position.
You modify the Caret object instead of using SetInsertionPoint method, which both moves the caret and makes the next character appear in given place.
So the working example (I tested it and it works as you would like it to) would be:
# Somewhere in __init__:
self.rich.Bind(wx.EVT_TEXT, self.onClick)
def onClick(self, event):
self.rich.SetInsertionPoint(0) # No refresh necessary.
event.Skip()
EDIT: if you want the text to be added at the end, but the cursor to remain at the beginning (see comments), you can take advantage of the fact that EVT_KEY_DOWN is handled before EVT_TEXT (which in turn is handled after character addition). So the order of events is:
handle EVT_KEY_DOWN
add character at current insertion point
handle EVT_TEXT
Adding a handler of EVT_KEY_DOWN that moves the insertion point to the end just before actually adding the character does the job quite nicely. So, in addition to the code mentioned earlier, write:
# Somewhere in __init__:
self.rich.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
def onKeyDown(self, event):
self.rich.SetInsertionPointEnd()
event.Skip()
By the way, event.Skip() does not immediately invoke next event handler, it justs sets a flag in the event object, so that event processor knows whether to stop propagating the event after this handler.