Gtk.Widget.destroy() is necessary on Python? - python

When reading the documentation about GtkBuilder, I came across this passage:
A Gtk.Builder holds a reference to all objects that it has
constructed and drops these references when it is finalized. This
finalization can cause the destruction of non-widget objects or
widgets which are not contained in a toplevel window. For toplevel
windows constructed by a builder, it is the responsibility of the user
to call Gtk.Widget.destroy() to get rid of them and all the widgets
they contain.
But this applies to python too? That is when I load a top-level window I must destroy it manually?

Well, sort of. You generally don't have to call destroy() on the window manually because it happens automatically when the user clicks the window's close button.

It is never necessary. But destruction of widgets has nothing to do with memory management, or at least not primarily. It is mainly a matter of existence in the GUI model. If you add a widget to a container, it remains there until destroyed. Even if you hide it, it is still there. It can respond to signals, is part of lists or whatever.

Related

How to use "on edit" events/signals with e.g. QLineEdit in pyside6 (Qt6)

Sorry for this post but I still don't get it after my research on the internet.
I am using Python PySide6 since I need to due to some display scaling issues I have with PyQt6 and other older versions.
I already have implemented the basics of a large program with dynamic .ui loading and now I want to implement the event actions for all my gui elements. Some I already managed to use by overloading the eventFilter method of the gui objects by using installEventFilter(self) function. That works well for hovering and other things and the used events are always of the type QtCore.QEvent. .... The problem is that I am missing some events here.
First an easy example and maybe the potential solution explains it for all other cases in my code:
How can I implement a function for the "on change" event of an QLineEdit object? I just want to get notified when the value of the control has been changed.
On the internet I find documentation for signals that should be connected to a slot that I can implement myself.
For example:
self.any_line_edit.textChanged.connect(self.any_slot)
The strange thing is, my QLineEdit object seems to have no reference for the "textChanged" signal. At least PyCharm is not able to resolve the name and I see the other methods of the QLineEdit class so the type of the object seems to be fine.
On other websites I find a similar example for QPushButton:
self.button.clicked.connect(self.the_button_was_clicked)
Same problem here. My PySide6 QPushButton seem to have no signal called "clicked".
Then I thought, no problem. Maybe I just continue using the eventFilter overload function and just install and replace the eventFilter for every object and manually filter the needed event myself.
But it seems that a simple QLineEdit throws no event that equals an "on edit" functionality.
I just see events like: QEvent::Paint, QEvent::DynamicPropertyChange, QEvent::WindowDeactivate, QEvent::Leave and many others but nothing like "on edit" or "edit end" or "on changed" or something.
So it seems that I don't see a respective event in the eventFilter function and due to any circumstance I also don't see the signals used to connect the Object to Slots.
I am sure I am missing something very basic but currently I am really confused.
I am using Python 3.9, PySide6 and PyCharm 2022.1 .
I just want to get notified if these input controls are getting changed in their values. I even have more complex signal-slot constructs and eventFilter reimplementations in my code and now I fail on such a basic thing. To my excuse: Normally I am implementing in Qt5 with C++ so I am a noob regarding Python and PySide6. xD
There is no such "on edit" event. The reason is that events are not meant for that. As the documentation explains:
In Qt, events are objects, derived from the abstract QEvent class, that represent things that have happened either within an application or as a result of outside activity that the application needs to know about. Events can be received and handled by any instance of a QObject subclass, but they are especially relevant to widgets.
So, events are "generic", not specialized for any widget or object type.
An "on edit" event would make sense only for editable widgets (which are just a few: QLineEdit, QPlainTextEdit, QTextEdit and their derived classes), and such an event would be also too generic and abstract to justify its existence.
Most importantly, events are normally created by Qt and then "sent" to the objects that could and possibly should handle them; again, from the above docs:
When an event occurs, Qt creates an event object to represent it by constructing an instance of the appropriate QEvent subclass, and delivers it to a particular instance of QObject (or one of its subclasses) by calling its event() function.
Note: some events are "synthetic", generated internally by Qt when required, sometimes even by widgets: in some cases, it's to work around some OS specific issues in complex focus situations, others due to the architecture of a particular framework (such as the Graphics View, which uses an internal "graphics event" system for items).
Qt cannot know (nor should) if the target object(s) is "editable". That's also because events are normally propagated from the target object up to the object tree, going up through all its parents, until the event is actually handled or (at least) accepted. That is of utmost importance for the event system: especially with input events, those events are intended for generic use by any object in the widget structure, so a widget (instance) specific "on edit" event wouldn't make any sense, mostly because it would be an event that would be only used by the widget itself.
In fact, some similar events do exist (such as QInputEvent, or the more specific QKeyEvent), but, as the name suggests, they are input events, they do not tell with certainty that the event actually results in an edit.
When the user presses a key, Qt creates a QKeyEvent and sends it to the currently focused widget. If that widget accepts that event, that event is (normally) not processed anymore by anything else. If the widget does not accept it, Qt will then propagate the event to its parent, which will decide if accept it in turn or not, and so on up to the top level window.
Note: events can be handled (or not) and accepted (or ignored). This means that a widget could handle (do "something" with that event) and still ignore it. A typical example is the Tab key: a QLineEdit would handle it in the sense that it will check that event, and then ignore it; then that event will be managed by its parent which possibly use it to switch the focus to the next widget that accepts it. A QTextEdit, instead, will both handle and accept the event, as it will normally write a tabulation character in the document contents.
In summary, an event is dispatched to a specific designated object and generally only handled by the single object that actually accepts it.
Signals, on the other hand, are a "communication system" that follows the observer pattern, similarly to the callback mechanism: you "subscribe" to a certain signal and whenever the object emits that signal, the receiver will do anything it wants. The eventual argument(s) of that signal is not changed (or, at least, it shouldn't), and the subscriber(s) can be anything. The sender of that signals doesn't need to care about the targets, nor the different "hierarchy" of those objects matters, as opposed to events.
In summary, don't use events for this, use signals, as that's what they've been made for. This is completely fine:
self.any_line_edit.textChanged.connect(self.any_slot)
Finally, the warnings you're getting from PyCharm are just that: warnings (which are not errors). When the UI is dynamically loaded on runtime, the IDE cannot know the object types of the new attributes, because they will be effective only when the program is running.
In fact, the object to which the attribute is referencing could also change during the lifespan of the program: imagine a class that sets an instance attribute for a "target" widget that is used as an argument in its __init__. How could the IDE know if that widget is a QLineEdit or a QPushButton and "accept" the attributes of that specific widget, such as textChanged or clicked? It couldn't.
So, dynamically created attributes will always show that warning; and you can safely ignore them, as annoying as they are (there should be some shortcut to do that, AFAIK).

How can I make area outside toplevel unclickable?

I want to create a toplevel window in tkinter, and I want the outside area unclickable. So this way, the user cannot click out from the toplevel window, just after it's been destroyed. (And also preventing to be able to create the same window from the root Tk())
Tkinter supports something called a "grab". When you put a grab on a window, all events are funneled through the widget. Even if you click outside the window, the click will register with the window.
There are two types of grabs: local and global. Local means the grab only works for your application -- you can still click on the desktop, for example. A global grab works for the whole computer. These can be very dangerous because you can completely lock up your UI if you don't provide a way to release the grab.
To set a local grab you can call grab_set on any widget, and all events will go to that widget. To set a global graph, call grab_set_global.
A local grab is how tkinter implements modal dialogs -- while the dialog is open it has a local grab so that you must dismiss the dialog before clicking on buttons in the main window.
Danger Will Robinson! if you are working with global grabs, make sure there's a absolutely foolproof way to release the grab. For example, during development you might use after to release the grab after 15 seconds. Or, bind to the escape key. Always, always test with a local grab first. As a rule of thumb, however, you should avoid using a global grab unless absolutely necessary.

Turn off PyQt Event Loop While Editing Table

I'm developing a GUI with PyQt. The GUI has a qListWidget, a qTableWidget, and a plot implemented with Mayavi. The list refers to shapes that are plotted (cylinders and cones for example). When a shape is selected in the list, I want the shape's properties to be loaded into the table (from a dictionary variable) and the shape to be highlighted in the plot. I've got the Mayavi plotting working fine. Also, if the table is edited, I need the shape to be re-plotted, to reflect the new property value (like for a cylinder, if the radius is changed).
So, when a list item is selected -> update the table with the item's properties (from a dictionary variable), highlight the item on the plot
When the table is edited -> update the dictionary variable and re-plot the item
The Problem: when I select a list item and load data into the table, the qTableWidget ItemChanged signal fires every time a cell is updated, which triggers re-plotting the shape numerous times with incomplete data.
Is there a typical means of disabling the GUI event loop while the table is being programmatically updated? (I have experience with Excel VBA, in that context setting Application.EnableEvents=False will prevent triggering a WorksheetChange event every time a cell is programmatically updated.)
Should I have a "table update in progress" variable to prevent action from being taken while the table is being updated?
Is there a way to update the Table Widget all at once instead of item by item? (I'll admit I'm intentionally avoiding Model-View framework for the moment, hence the qListWIdget and qTableWidget).
Any suggestions?
I'm a first time poster, but a long time user of StackOverflow, so I just want to say thanks in advance for being such an awesome community!
blockSignals(bool) is intended for suppressing QObjects and their subclasses from emitting signals, thus preventing any other objects from receiving them in slots. But this is a QObject method. If you are specifically trying to prevent one object from emitting signals in response to changes that you are making, which might trigger calculations or some other expensive processing in a slot, then this is what you want.
But if your situation is that making repeated changes is causing expensive paint operations over and over (or other expensive events being generated on the widget), then you have the ability to disable updates with updatesEnabled(bool). A benefit of this method is that it recursively disables the children of the target widget, preventing them from being updated as well. So nothing in the hierarchy will receive updates until you enable again.
mainWidget.setUpdatesEnabled(False)
# do a bunch of operations that would trigger expensive events
# like repaints
mainWidget.setUpdatesEnabled(True)
Ultimately it depends on whether the source of your problem comes from triggering signals, or triggering widget events. Blocking the signals will still allow the widget to process its events, but just not notify any other listeners about it. updatesEnabled is a common way to wrap a number of list/table/tree updates. When it is enabled again afterwards, a single post update will be performed.
Signals can be temporarily blocked for any object that inherits QObject:
self.tableWidget.blockSignals(True)
# perform updates, etc
self.tableWidget.blockSignals(False)
If you disable the entire event loop, the app becomes unresponsive. And, even if the user doesn't notice, the OS might, and put up some kind of "hang" notification (like OS X's brightly-colored spinning beachball, which no user will ever miss).
You might want to disable repaints without disabling the event loop entirely. But even that's probably too drastic.
All you're really trying to do is make sure the table stops redrawing itself (without changing the way you've implemented your table view, which you admit isn't ideal, but you have reasons for).
So, just disable the ItemChanged updates. The easiest way to do this, in almost every case, is to call blockSignals(True) on the widget.
In the rare cases where this won't work (or when you're dealing with ancient code that's meant to be used in both Qt4-based and earlier projects), you can still get the handler(s) for the signal, stash them away, and remove them, then do your work, then restore the previous handler(s).
You could instead create a flag that the handlers can access, and change them so they do nothing if the flag is set. This is the traditional C way of doing things, but it's usually not what you want to do in Python.

Change the focus from one Text widget to another

I'm new to Python and I'm trying to create a simple GUI using Tkinter.
So often in many user interfaces, hitting the tab button will change the focus from one Text widget to another. Whenever I'm in a Text widget, tab only indents the text cursor.
Does anyone know if this is configurable?
This is very easy to do with Tkinter.
There are a couple of things that have to happen to make this work. First, you need to make sure that the standard behavior doesn't happen. That is, you don't want tab to both insert a tab and move focus to the next widget. By default events are processed by a specific widget prior to where the standard behavior occurs (typically in class bindings). Tk has a simple built-in mechanism to stop events from further processing.
Second, you need to make sure you send focus to the appropriate widget. There is built-in support for determining what the next widget is.
For example:
def focus_next_window(event):
event.widget.tk_focusNext().focus()
return("break")
text_widget=Text(...)
text_widget.bind("<Tab>", focus_next_window)
Important points about this code:
The method tk_focusNext() returns the next widget in the keyboard traversal hierarchy.
the method focus() sets the focus to that widget
returning "break" is critical in that it prevents the class binding from firing. It is this class binding that inserts the tab character, which you don't want.
If you want this behavior for all text widgets in an application you can use the bind_class() method instead of bind() to make this binding affect all text widgets.
You can also have the binding send focus to a very specific widget but I recommend sticking with the default traversal order, then make sure the traversal order is correct.
It is really simple in PyQt4 simply use this one single line below and you will be able to change focus by pressing tab button:
self.textEdit.setTabChangesFocus(True)
The focus traversal is somewhat customizable, usually letting the X windows manager handle it (with focus follows mouse, or click). According to the manual it should be possible to bind an event to the key press event, for tab presses, and triggering a focusNext event in those cases.

wxpython: How do I examine dragged data in OnDragOver?

I'm a bit perplexed by drag and drop in wxPython (but perhaps this questions pertains to drag and drop in other GUI frameworks as well). The frameworks provides a couple of callbacks (OnEnter and OnDragOver) that purportedly allow me to inform the system whether the current mouse position is a valid place to drop whatever it is that is being dragged. From these methods I can return wx.DragNone, wx.DragCopy, etc. What baffles me is that from within these methods I am not allowed to call GetData, which means I am not allowed to examine the data that the user is dragging. If I cannot see the data, how am I supposed to know whether it is OK for the user to drop here?
One solution, which is a hack of limited usefulness, is when a drag is initiated, store the dragged data in a global or static reference somewhere. This way, in the OnEnter and OnDragOver handlers, it is possible to get a reference to the data being dragged. This is of course only useful for drags within the same application (the same instance of the application, actually).
There is no way to see dragged data in OnEnter and OnDragOver methods.
The only solution I found is to store the dragged item in some instance variable that is then readable inside these methods.

Categories