I am designing a custom widget which is basically a QGroupBox holding a configurable number of QCheckBox buttons, where each one of them should control a particular bit in a bitmask represented by a QBitArray. In order to do that, I added the QCheckBox instances to a QButtonGroup, with each button given an integer ID:
def populate(self, num_bits, parent = None):
"""
Adds check boxes to the GroupBox according to the bitmask size
"""
self.bitArray.resize(num_bits)
layout = QHBoxLayout()
for i in range(num_bits):
cb = QCheckBox()
cb.setText(QString.number(i))
self.buttonGroup.addButton(cb, i)
layout.addWidget(cb)
self.setLayout(layout)
Then, each time a user would click on a checkbox contained in self.buttonGroup, I'd like self.bitArray to be notified so the corresponding bit in the array can be set/unset accordingly. For that I intended to connect QButtonGroup's buttonClicked(int) signal to QBitArray's toggleBit(int) method and, to be as pythonic as possible, I wanted to use new-style signals syntax, so I tried this:
self.buttonGroup.buttonClicked.connect(self.bitArray.toggleBit)
The problem is that buttonClicked is an overloaded signal, so there is also the buttonClicked(QAbstractButton*) signature. In fact, when the program is executing I get this error when I click a check box:
The debugged program raised the exception unhandled TypeError
"QBitArray.toggleBit(int): argument 1 has unexpected type 'QCheckBox'"
which clearly shows the toggleBit method received the buttonClicked(QAbstractButton*) signal instead of the buttonClicked(int) one.
So, the question is, how can I specify the signal connection, using new-style syntax, so that self.bitArray receives the buttonClicked(int) signal instead of the default overload - buttonClicked(QAbstractButton*)?
EDIT: The PyQT's New-style Signal and Slot Support documentation states you can use pyqtSlot decorators to specify which signal you want to connect to a given slot, but that is for a slot you are creating. What to do when the slot is from a "ready made" class? Is the only option subclassing it and then reimplementing the given slot with the right decorator?
While browsing for related questions I found this answer to be exactly what I needed. The correct new-style signal syntax for connecting only QButtonGroup's buttonClicked(int) signal to QBitArray's toggleBit(int), ignoring the other overloaded signatures, involves specifying the desired type in brackets, like this:
self.buttonGroup.buttonClicked[int].connect(self.bitArray.toggleBit)
Related
I have created a UI that has a listWidget with 10 items and I want to create a signal that that executes a custom slot when I double-click on a specific item of the listWidget (ie. the last one).
The signal/slot I implemented is as follows
QObject::connect(ui->listWidget_RD_commands, SIGNAL(QListWidget::itemDoubleClicked(ui->listWidget_RD_commands->item(9))), this, SLOT(flash_read));
It throws no errors but, in the application output window when I run the code, the following message pops up
When I try to implement the signal/slot with the new way
QObject::connect(ui->listWidget_RD_commands, &QListWidget::itemDoubleClicked(ui->listWidget_RD_commands->item(9)), this, &MainWindow::flash_read);
an error pops up saying
mainwindow.cpp:102:64: error: call to non-static member function without an object argument
So, what am I doing wrong here?
[Question reformulated for clarity]
I have a simple GUI with 2 tabs, the first one contains a read only QTextEdit used for program logs, the second one contains various widgets for user input and a "generate" button.
The GUI was made using Qt Designer. Note : all elements have default attributes ( they were not edited / no options changed ) apart from the QTextEdit that is read only
The arrows are to show the QTextEdit that is used for the logs and the generate button that causes the tab change
Upon clicking on the generate button, the focused tab is switched to the first one ( with the logs ) and a separate thread is used to compute the user data and write it's log onto the QTextEdit
class GUI:
def __init__(self, core):
self.core = core
self.app = QtWidgets.QApplication(sys.argv)
self.window = Application()
self.__link_buttons__()
def __link_buttons__(self):
generate_button = self.window.get_generate_button()
if generate_button is None:
raise RuntimeError("could not find the generation button needed for the gui ( possibly an issue with the .gui file ), cannot start software")
generate_button.clicked.connect(self.__generate_threaded)
def __generate_threaded(self):
logger.logger_instance.log("user requested generation", LogLevel.NORMAL)
self.window.set_main_tab_to_log()
if self.core.check_if_generating() is True:
logger.logger_instance.log("Generator is currently in use", LogLevel.ERROR)
return
thread = threading.Thread(target=self.core.generate_gui)
thread.start()
class Application(QtWidgets.QMainWindow):
def __init__(self):
super(Application, self).__init__()
self.ui = uic.loadUi(settings.settings_instance.gui_path / "main_window.ui", self)
self.show()
self.__tabs = self.ui.findChild(QTabWidget, "mainTabs")
self.__log_tab = self.ui.findChild(QWidget, "logTab")
def get_gui_handle(self):
return self.ui.findChild(QTextEdit, "LogOutput")
def get_generate_button(self):
return self.ui.findChild(QPushButton, "generateButton")
def set_main_tab_to_log(self):
self.__tabs.setCurrentWidget(self.__log_tab)
The logger is the class that is in charge of writing to the QTextEdit on the GUI, it calls the get_gui_handle() method on start to get the QTextEdit and then uses append() to write to it ( with thread protection )
Possibly important details : I am using the standard Python threads ( import threading ), not the Qt as the rest of the software uses them and I am unsure if they can be mixed
The logger does write successfully to the QTextEdit but the application does not display the text as intended. Any new text is displayed normally but the previous logs are not and will only show upon window resize / clicking on the text / changing tabs / ... ( what I presume are events that get the application to re-render the QTextEdit )
Pictures for clarity :
First log is displayed as intended :
Second log is also displayed as intended but the first log is now invisible
First log is displayed again after window resize / text overlining / tab change
The issue was that even though I was using thread protections, I was calling the append() method from a separate thread ( thread protection is not enough ! ). Trying to refresh / actualize the gui after a call from a different thread is also a bad idea.
This is due to the fact that Qt manages internally events and that not using the main thread seems to circumvent them with in turn causes Qt to not see that it's content was changed.
Important note for the solution : using threads or qt threads is irrelevant in this specific case as qt simply wraps a threading library. It is however very recommended to use qt threads on all of the software simply to avoid having 2 different threads library used in the same program, witch in turn can cause issues.
I used pyqtSignal in order to be able to correctly communicate with the gui without having issues
from PyQt5.QtCore import QObject, pyqtSignal
class GUI(QObject):
__gui_log_signal = pyqtSignal(str)
def __init___():
# ...
self.__gui_handle = self.window.get_gui_handle() # this method returns the reference to the QTextEdit directly
self.__gui_log_signal.connect(self.__gui_handle.append)
This way I can use self.__gui_log_signal.emit("string or variable here") when needed
Important details to note : ( things that made getting the answer in the first place harder than expected )
The pyqtSygnal must be at the root of the class, not in the __init__() else it's not going to work properly ( in my instance the emit() method was not found )
the pyqtSignal can be used to pass variables, they just need to be declared as such ( example : pqtSygnal(str) ) and then connected to a method that uses the same types / variable count
The class that uses the pyqtSygnal must extend QObject or a class that extends it
I have an EnemySpawner Blueprint Class that does:
EnemySpawner has a Spline Component named "Path" which the Spawned Enemy must follow. For it, Enemy's Blueprint has another Spline Component named "Enemy Path" that must be initialized by the Enemy Spawner at the moment of the creation, as does in the screenshot above.
But, in my Enemy, if I try to access to "EnemyPath", I get the following error:
Blueprint Runtime Error: Accessed None trying to read property
PathActor from function: 'ExecuteUbergraph_Enemy' from node:
DestroyActor in graph: EventGraph in object: Enemy with description:
Accessed None trying to read property PathActor
So, in other words, Null Pointer Exception.
If, for example, in the Enemy's Blueprint I do (with testing purposes):
The Actor Owner of the Spline Component isn't destroyed.
Even if I try to assign all the Owner actor, not only the Spline Component, it doesn't work either.
I've also tried to print something in screen after the "SpawnActor Enemy" node of EnemySpawner and in the "Event BeginPlay" node of Enemy, and I've checked that obviously the print in EnemySpawner happens first, so the only problem is that the variable isn't really assigned.
The last thing I've tried is to destroy the Spline's Owner right after the "Set" node, directly in EnemySpawner blueprint, by getting it from the Spawned enemy, and to my surprise, it's destroyed!
So, some idea about what's the properly way of doing this?
You can set any variable in a BP actor with an "Expose on Spawn" flag (in the details panel when the variable is selected in the BP editor) which will add a pin for a value to pass into it when using the SpawnActorFromClass node. Make sure you set the variable(s) "Editable" as well.
I think maybe we are doing this wrong.
In straight up C++ you create a class, create the variables, modify the variables by creating a function & passing by ref.
I think that if the Blueprint is a class ( which it is), and if the variable is a variable (which it is), you can't really expect to assign a value to it without calling a function that does it... Then again for all i know maybe the engine does do it for you...
I have a poly actor which has a vtkBoxWidget that is connected to a callback as from the example in the docs:
def widget_callback(obj, event):
t = vtk.vtkTransform()
obj.GetTransform(t)
obj.GetProp3D().SetUserTransform(t)
All works fine and I am able to move and transform the actor with the widget but the transformation is applied to the UserTransform and not processed down to the actor properties.
So if I call:
actor.GetPosition()
It returns the initial position prior to making the changes with the widget. And if I call:
actor.GetUserTransform().GetPosition()
I get the updated position relative to the starting point of the first interaction.
Do I have to connect it all through a vtkTransformPolyDataFilter and then update the input connection to the mapper and also calculate the coordinate space offset or is there a simpler way of doing it? ... in short:
What is the correct way of updating an actor with changes applied to SetUserTransform?
After trying out many variations with vtkTransformPolyDataFilter and start/end interaction events linked to various steps in the process I found that for my purpose the absolute simplest way of handling this was simply linking the widget transformation directly to the actor properties:
def widget_callback(self, obj, event):
t = vtk.vtkTransform()
obj.GetTransform(t)
self.actor.SetPosition(t.GetPosition())
self.actor.SetScale(t.GetScale())
self.actor.SetOrientation(t.GetOrientation())
I have a table with some data that I want to be able to edit through the QTableWidget. Upon trying to connect the currentItemChanged signal:
self.QTableWidget.currentItemChanged(QTableWidgetItem,QTableWidgetItem).connect(self.editCell)
I get the following error:
'TypeError: native Qt signal is not callable'
I went looking in to the QtDesigner, where you can connect signals. I made a QTableWidget, and connected it to a label so that changing the currentItem hid the label.
In the signal connecting dialog the currentItemChanged signal was written thus:
currentItemChanged(QTableWidgetItem*,QTableWidgetItem*)
I don't know what the * means, but I assume it's important.
Am I using the wrong signal or is my syntax wrong somehow? In short, I want there a signal to be emitted upon changing any particular item/cell(I'm not sure what the distinction is)
____________________EDIT_________________________
EDIT: In the QTableWidgetItem class documentation I also found that it has functions column() and row().
I tried adding them like this:
self.QTableWidget.currentItemChanged(QTableWidgetItem.column(QTableWidgetItem.column()),QTableWidgetItem.row()).connect(self.editCell)
But got the error:
TypeError: descriptor 'column' requires a 'PySide.QtGui.QTableWidgetItem' object but received a 'Shiboken.ObjectType
This bit is concerning:
self.QTableWidget
If your table is literally called "QTableWidget" there may be confusion later on. Specifically, the error you are getting makes it look like you are calling QTableWidget.currentItemChanged.
Also, its worth reviewing the PyQT documentation on "new-style signals", specifically on dealing with overloads to understand how it all works. Fortunately however, QTableWidget.currentItemChanged isn't overloaded so, the code you should be using should just be:
self.yourTable.currentItemChanged.connect(self.editCell)
Regarding your later edits, in this code:
currentItemChanged(QTableWidgetItem*,QTableWidgetItem*)
The QTableWidgetItems that are being parsed are arguments that are given to the signal. You can't change them, as they are definined in the method that defines the slot, and passed when the signal is fired. From the documentation linked above:
void currentItemChanged (QTableWidgetItem *,QTableWidgetItem *)
This signal is emitted whenever the current item changes. The previous item is the item that previously had the focus, current is the new current item.