'pyqt5 - update a dynamically created widget with its corresponding data
I have created a tool with help of a custom widget.
This widget fits into the main window like so. User can create new instances of custom widget with a click of a button.
custom widget :
class RenderWidget(QWidget):
def __init__(self, toolname, process_queue):
QWidget.__init__(self)
self.initSubject()
self.organize()
self.toolname = toolname
self.process_queue = process_queue
def initSubject(self):
self.script_dir = os.path.dirname(os.path.realpath(__file__))
self.scene_widget = uic.loadUi(os.path.join(self.script_dir, 'ui', 'test_widget.ui'))
self.scene_widget.pushButton_scenefile.clicked.connect(lambda x: self.open_file_name_dialog(x))
def organize(self):
grid = QGridLayout(self)
self.setLayout(grid)
grid.addWidget(self.scene_widget)
def open_file_name_dialog(self, x):
dialog = QFileDialog(
self,
'submit_render',
"path",
"*.ma",
# supportedSchemes=["file"],
options=QFileDialog.DontUseNativeDialog,
)
self.change_button_name(dialog)
dialog.findChild(QTreeView).selectionModel().currentChanged.connect(
lambda: self.change_button_name(dialog)
)
if dialog.exec_() == QDialog.Accepted:
filename = dialog.selectedUrls()[0]
filename = filename.toLocalFile()
self.scene_widget.lineEdit_scenefile.setText(filename)
if validate_path(self.toolname, filename):
self.process_queue.put((filename, _count))
def change_button_name(self, dialog):
for btn in dialog.findChildren(QPushButton):
if btn.text() == self.tr("&Open"):
QTimer.singleShot(0, lambda btn=btn: btn.setText("open"))
Main Widget :
class MyAppView(QWidget):
def __init__(self, toolname, process_queue):
self.toolname = toolname
self.process_queue = process_queue
super(MyAppView, self).__init__()
self.initUi()
def initUi(self):
self.layoutV = QVBoxLayout(self)
self.area = QScrollArea(self)
self.area.setWidgetResizable(True)
self.scrollAreaWidgetContents = QWidget()
self.scrollAreaWidgetContents.setGeometry(0, 0, 200, 100)
self.layoutH = QHBoxLayout(self.scrollAreaWidgetContents)
self.layoutH_Button = QHBoxLayout(self.scrollAreaWidgetContents)
self.gridLayout = QGridLayout()
self.layoutH.addLayout(self.gridLayout)
self.area.setWidget(self.scrollAreaWidgetContents)
self.left_spacer = QSpacerItem(50, 10, QSizePolicy.Minimum)
self.center_spacer = QSpacerItem(100, 10, QSizePolicy.Minimum)
self.submit_button = UVPush("Submit")
self.cancel_button = UVPush("Cancel")
# Log text
self.output = QPlainTextEdit()
self.output.setFixedHeight(150)
self.output.setPlaceholderText('output..')
self.layoutH_Button.addSpacerItem(self.left_spacer)
self.layoutH_Button.addWidget(self.submit_button)
self.layoutH_Button.addSpacerItem(self.center_spacer)
self.layoutH_Button.addWidget(self.cancel_button)
self.layoutV.addWidget(self.area)
self.layoutV.addLayout(self.layoutH_Button)
self.layoutV.addWidget(self.output)
self.widget = RenderWidget(self.toolname, self.process_queue)
self.gridLayout.addWidget(self.widget)
self.setGeometry(700, 200, 350, 300)
Process :
class ChildProc(Process):
def __init__(self, to_emitter, from_mother, daemon=True):
super(ChildProc, self).__init__()
self.daemon = daemon
self.to_emitter = to_emitter
self.data_from_mother = from_mother
self.maya_output = MayaOutput()
def run(self):
from python_library.maya_python import data_from_maya_scene
while True:
filename, count = self.data_from_mother.get()
print('+++ filename ', filename, count)
_cameras, _frames, _layers = data_from_maya_scene(filename)
self.maya_output.cameras = _cameras
self.maya_output.layers = _layers
self.maya_output.frames = _frames
self.maya_output.count = count
self.to_emitter.send(self.maya_output)
#TODO call signal slot to start stop loader
(ref: How to signal slots in a GUI from a different process?)
Emitter :
class Emitter(QThread):
ui_data_available = pyqtSignal(object) # Signal indicating new UI data is available.
def __init__(self, from_process):
super(Emitter, self).__init__()
self.data_from_process = from_process
def run(self):
while True:
try:
_object = self.data_from_process.recv()
except EOFError:
break
else:
self.ui_data_available.emit(_object)
MainWindow :
class CloudRenderView(QtWidgets.QMainWindow):
def __init__(self, toolname, child_process_queue, emitter):
QtWidgets.QMainWindow.__init__(self)
self.toolname = toolname
self.process_queue = child_process_queue
self.MyAppView = MyAppView(self.toolname, self.process_queue)
.
.
self.emitter = emitter
self.emitter.daemon = True
self.emitter.start()
# set window dimension
self.connections()
.
.
def connections(self):
.
.
self.emitter.ui_data_available.connect(self.update_ui)
.
.
def update_ui(self, maya_object):
count = maya_object.count
example_widget = self.MyAppView.gridLayout.itemAt(count).widget()
if 'Error' not in maya_object.cameras:
example_widget.scene_widget.textEdit_camera.setText('\n'.join([str(elem) for elem in maya_object.cameras]))
else:
example_widget.scene_widget.textEdit_camera.setHtml("<font color='red' size='2'><red>{}</font>".format('Error'))
if 'Error' not in maya_object.frames:
example_widget.scene_widget.lineEdit_frames.setText(maya_object.frames)
else:
example_widget.scene_widget.lineEdit_frames.setText('Error')
if 'Error' not in maya_object.layers:
example_widget.scene_widget.textEdit_render_layer.setText('\n'.join([str(elem) for elem in maya_object.layers]))
else:
example_widget.scene_widget.textEdit_render_layer.setHtml("<font color='red' size='2'><red>{}</font>".format('Error'))
Question : A button to choose file from system is clicked. This selected file is send to a qprocess via queue and after processing is complete, emitter sends signal to main UI via a pipe.
Coming to the question, say data was entered for widget 3 or 4, After process, data gets send back to widget no 1 , always.
How to map it to same widget that it was selected from. ?
Solution 1:[1]
Attempting an answer that will help but without code and knowing your particular approach it's difficult.
I'm assuming that you are not sending the data to the correct widget because your reference to that widget is wrong. You need a way to reference the new widget that you create, and there are generally two ways to do this.
Store a reference to the new widget in a place you can easily get to it, like a
self._second_widget
variable or a list or dictionary of variables. Then when you send data to your widget, use that reference.Find the widget in the QGridLayout using appropriate calls, which could be something like
.itemAtPosition(x,y).widget()
Hopefully this helps - if you want a better answer it's a good idea to post some code.
Solution 2:[2]
My code certainly has a lot of scope to be optimized. I see that. But in context of what is required. I.e. get index of custom widget, answer from @eyllanesc's helped. @eyllanesc, You rock.
In, class RenderWidget
class RenderWidget(QWidget):
clicked = pyqtSignal(int)
def __init__(self, toolname, process_queue, index):
QWidget.__init__(self)
self.index = index <--------
self.initSubject()
self.process_queue = process_queue
def initSubject(self):
.
.
self.scene_widget = uic.loadUi(os.path.join(self.script_dir, 'ui', 'scene_widget.ui'))
self.scene_widget.setProperty("index", self.index) <--------
.
self.scene_widget.installEventFilter(self)
def eventFilter(self, widget, event): <--------
if isinstance(widget, QWidget) and event.type() == QEvent.MouseButtonPress:
i = widget.property("index") <--------
self.clicked.emit(i) <--------
return QWidget.eventFilter(self, widget, event)
def open_file_name_dialog(self, x):
.
dialog = QFileDialog(
self,
'submit_render',
"path",
"*.ma",
# supportedSchemes=["file"],
options=QFileDialog.DontUseNativeDialog,
)
if dialog.exec_() == QDialog.Accepted:
filename = dialog.selectedUrls()[0]
filename = filename.toLocalFile()
self.scene_widget.lineEdit_scenefile.setText(filename)
if validate_path(self.toolname, filename):
widget_count = self.index - 1 <--------
self.process_queue.put((filename, widget_count)) <--------
.
After accepting input from user, before passing data to qprocess get index value.
Finally, in QMainWindow where user clicks "+ icon" button, increment index and pass the value
class CloudRenderView(QtWidgets.QMainWindow):
def __init__(self, toolname, child_process_queue, emitter):
QtWidgets.QMainWindow.__init__(self)
self.toolname = toolname
self.process_queue = child_process_queue
self.MyAppView = MyAppView(self.toolname, self.process_queue)
self.widget_count = 0 <--------
def add_maya_widget(self):
self.widget_count += 1 <--------
self.widget = RenderWidget(self.toolname, self.process_queue, self.widget_count) <--------
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Alex |
Solution 2 | nish |