I've created two uis using the Qt Designer, imported them into my script and set them in the normal way using the setupUi() method. When a button is clicked, and the appropriate method is executed, the new ui is loaded, but all of the widgets and connections from the old one persist.
What is the proper way to remove the connections and then delete all of the MainWindow's current children so that only the new ui's widgets and connections remain? Below is a simplified example of what I'm doing.
from PyQt4 import QtGui, QtCore
from Models import *
from Backward import Ui_Backward
from Forward import Ui_Forward
class MainWindow( QtGui.QMainWindow ):
def __init__( self ):
QtGui.QMainWindow.__init__( self, None )
self.move_forward()
def move_forward( self ):
self.ui = Ui_Forward()
self.ui.setupUi( self )
self.connect( self.ui.Button, QtCore.SIGNAL('clicked()'), self.move_backward )
def move_backward( self ):
self.ui = Ui_Backward()
self.ui.setupUi( self )
self.connect( self.ui.Button, QtCore.SIGNAL('clicked()'), self.move_forward )
Once Ui_Forward is set and the button is pressed, Ui_Backward is set correctly, but all of the widgets from Ui_Forward are still in the MainWindow's list of children.
I'm trying to imagine what your goal is here. If you are trying to implement a wizard type application where the screen presents one set of controls and then switches to another screen with forward and backward buttons then you should try using a QStackedWidget.
A QStackedWidget will allow you to define one or more "pages" and switch between them. You design each page independently without (necessarily) having to move controls from one page to the next.
Related
I'm using PyQt, but I guess the same questions also applies to Qt C++.
Assume that I have a main window with a button that opens a wizard that collects data and that data needs to be used in the main window after the wizard has closed. standard procedure.
So there are multiple ways to do this. either I can pass a reference to the main window to the Wizard and it does all the work using the main window reference, but I'd say that breaks modularity. I can also wire up a callback to the wizard accepted rejected or finished signal, but in that callback, I don't have a reference to the wizard itself, so I cannot get to the data in the wizards fields. Unless I store a reference to the wizard as instance variable in order to access it again from the callback.
Another option is (even though I haven't fully understood it yet) to get a reference to the emitter of the signal (i.e. the wizard) in the callback using https://doc.qt.io/qt-5/qobject.html#sender. But that seems not recommended.
So whats the canonical way?
Premise: this is a bit of an opinion based question, as there is not one and only "good" way to do that. I just wanted to comment (opinion based answer/questions are discouraged here in SO), but the limited formatting isn't very helpful.
"Passing a reference" doesn't necessarily break modularity.
Instead, that's exactly what QDialog usually are initialized: the parent is the "calling" window, which is also how a QDialog can be "modal" to the parent or the whole application (meaning that no interaction outside the dialog is allowed as long as it is active).
AFAIK, I don't know if this is actually considered canonical, but the following is the most commonly suggested approach.
The idea is that you have a child object (a QDialog, usually) which might or might not be initialized everytime you need it, that's up to you; the important part is that you need a reference to it at least for the time required to update its result, which can even happen within the scope of a single method/slot.
from PyQt5 import QtWidgets
class MyWizard(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
self.checkBox = QtWidgets.QCheckBox('check')
layout.addWidget(self.checkBox)
self.input = QtWidgets.QLineEdit()
layout.addWidget(self.input)
buttonBox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Cancel)
layout.addWidget(buttonBox)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
def setData(self, **data):
self.checkBox.setChecked(data.get('check', False))
self.input.setText(data.get('text', ''))
def getData(self):
return {'check': self.checkBox.isChecked(), 'text': self.input.text()}
def exec_(self, **data):
self.setData(**data)
return super().exec_()
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
centralWidget = QtWidgets.QWidget()
self.setCentralWidget(centralWidget)
layout = QtWidgets.QHBoxLayout()
centralWidget.setLayout(layout)
self.showWizBtn = QtWidgets.QPushButton('Show wizard')
layout.addWidget(self.showWizBtn)
self.showWizBtn.clicked.connect(self.getDataFromWizard)
self.data = {}
def getDataFromWizard(self):
wiz = MyWizard(self)
if wiz.exec_(**self.data):
self.data.update(wiz.getData())
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
Another possibility is to create a persistent child dialog (but keep in mind that if the data can be changed by the parent, you'll have to find a way to update it, at least when executed); the concept here is that you can exec the dialog whenever you need, and you have the accepted signal connected to a slot that can get the data from the dialog. This is not a common use (nor very suggested IMHO) and should be used only for very specific scenarios.
As you already found out, using sender is not suggested: signals are asynchronous, and while in normal conditions the sender is reliable it's better to avoid using it unless absolutely necessary.
I'd like to create a dynamic menu which enumerates all QDockWidget from my QMainWindow and allows to show/hide the QDockWidgets, so far I got this code:
class PluginActionsViewDocks():
def __init__(self, main_window):
self.main_window = main_window
mapper = QSignalMapper(self.main_window)
self.actions = []
for dock in main_window.findChildren(QtWidgets.QDockWidget):
action = create_action(
main_window, dock.windowTitle(),
slot=mapper.map,
tooltip='Show {0} dock'.format(dock.windowTitle())
)
mapper.setMapping(action, dock)
self.actions.append(action)
mapper.mapped.connect(self.toggle_dock_widget)
help_menu = main_window.menuBar().addMenu('&View')
setattr(help_menu, "no_toolbar_policy", True)
add_actions(help_menu, tuple(self.actions))
def toggle_dock_widget(self, dock_widget):
print("toggle_dock_widget")
The menu is populated with all QDockWidget windowTitles but when i press each of them the slot toggle_dock_widget is not called. create_action is a helper which creates the QAction and connect the triggered signal to slot.
The thing is, I don't really understand quite well how QSignalMapper works but my intuition tells me it's the right choice for this particular problem.
What could I be missing here?
There's aleady a built-in dock-widget menu. Just right-click any dock title-bar, or any tool-bar or menu-bar. See: QMainWindow::createPopupMenu.
PS:
The reason why your QSignalMapper code doesn't work is probably because you are connecting to the wrong overload of the mapped signal. Try this instead:
mapper.mapped[QtWidgets.QWidget].connect(self.toggle_dock_widget)
I have created a Wizard in PyQt4 using Qt Designer. On one page of the wizard ,there exists a 'text_Browser' object of type QTextBrowser. I'm using the function QTextBrowser.append() to add text to it based on some processing.
I wish to execute the append function after the display of this page rather than the connecting the action(signal) to the Next or any other buttons on the previous page . How do I go about doing this ?
You can reimplement showEvent in the QTextBrowser.
# from PySide import QtGui
from PyQt4 import QtGui
class CustomTextBrowser(QtGui.QTextBrowser):
'''Reimplment show event to append text'''
def showEvent(self, event):
# do stuff here
#self.append('This random text string ')
event.accept()
Please be warned that this will append to the QTextBrowser's string every time the widget is shown, meaning other Qt events that toggle the visibility of this widget may cause unexpected behavior. Using signals and slots is preferable for this reason, but since you explicitly not to use signal/slots, here is a QEvent version on the showEvent with a fair warning.
One solution to avoid appending text multiple times would be to set an instance variable, and toggle the value after the widget has been shown:
# from PySide import QtGui
from PyQt4 import QtGui
class CustomTextBrowser(QtGui.QTextBrowser):
'''Reimplment show event to append text'''
def __init__(self, *args, **kwds):
super(CustomTextBrowser, self).__init__(*args, **kwds)
self._loaded = False
def showEvent(self, event):
if not self._loaded:
# do stuff here
#self.append('This random text string ')
self._loaded = True
event.accept()
Another solution would be to use the signal/slot strategy as mentioned above, or to override __init__ to automatically append text in your subclass. Probably the signal/slot mechanism is the most intuitive, and logical for Qt programmers.
I am trying to figure out a way to somehow "get" the GUI component that is invoking a function. This way I can further consolidate my code into reusable parts for components that do similar tasks. I need a way to do this in Maya's GUI commands as well as Qt's. I guess what I'm looking for is a general python trick like the "init", "file", "main", etc. If there isn't a general python way to do it, any Maya/Qt specific tricks are welcome as well.
Here is some arbitrary pseudo code to better explain what I'm looking for:
field1 = floatSlider(changeCommand=myFunction)
field2 = colorSlider(changeCommand=myFunction)
def myFunction(*args):
get the component that called this function
if the component is a floatSlider
get component's value
do the rest of the stuff
elif the component is a colorSlider
get component's color
do the rest of the stuff
Expanding from Gombat's comment, here's an example of how to get a generic function to work with a slider and a spinBox control:
from PySide import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self, parent = None):
super(Window, self).__init__(parent)
# Create a slider
self.floatSlider = QtGui.QSlider()
self.floatSlider.setObjectName('floatSlider')
self.floatSlider.valueChanged.connect(self.myFunction)
# Create a spinbox
self.colorSpinBox = QtGui.QSpinBox()
self.colorSpinBox.setObjectName('colorSlider')
self.colorSpinBox.valueChanged.connect(self.myFunction)
# Create widget's layout
mainLayout = QtGui.QHBoxLayout()
mainLayout.addWidget(self.floatSlider)
mainLayout.addWidget(self.colorSpinBox)
self.setLayout(mainLayout)
# Resize widget and show it
self.resize(300, 300)
self.show()
def myFunction(self):
# Getting current control calling this function with self.sender()
# Print out the control's internal name, its type, and its value
print "{0}: type {1}, value {2}".format( self.sender().objectName(), type( self.sender() ), self.sender().value() )
win = Window()
I don't know what control you would want colorSlider (I don't think PySide has the same slider as the one in Maya, you may have to customize it or use a QColorDialog). But this should give you a rough idea of how to go about it.
I'll start with the question and then try to explain:
Is there a way for an imported module to call a function in the module that imports it?
I am just learning to use Qt and am starting with Qt Designer to get some fundamentals worked out.
I have figured out how to create more than one ".ui" file in order to get the code for multiple windows and have managed to work out how to call the multiple windows from a main application by importing the code for the two windows.
For example, starting with win1.ui and win2.ui I create win1.py and win2.py - from my main application I import win1 and win2...
Note - I got this far by following this simple tutorial : http://www.youtube.com/watch?v=bHsC6WJsK-U&list=PLF4575388795F2531&index=10&feature=plpp_video
OK - now the question. If I have a button in win2, I know how to link that button to a function in the win2.py code. What I don't know how to do is link the button in win2 to a function in my main application.
My only thought would be to add a function as an argument to the class that sets up the second window but if I do that then any changes to win2.ui will wreck the code that I have changed.
Thus, Is there a way for an imported module to call a function in the module that imports it?
I hope this is clear without adding a bunch of code that isn't really relevant...
Qt is based on event-driven programming. Generally when you start building up your widgets, what you are going to be wanting to do is providing information to receiver widgets via signals that are then processed. You don't want to explicitly have a child widget know or require to call methods on a parent widget (this is not always the case, but it is good to avoid when possible).
I'm gonna post some examples that don't have UI files for ease here, but just assume you can build the same widget with designer and have it work the same way...
testwidget.py
from PyQt4 import QtGui, QtCore
class TestWidget(QtGui.QWidget):
textSaved = QtCore.pyqtSignal(str)
def __init__( self, parent = None ):
super(TestWidget, self).__init__(parent)
# create the ui (or load it)
self.__edit = QtGui.QTextEdit(self)
self.__button = QtGui.QPushButton(self)
self.__button.setText('Save')
layout = QtGui.QVBoxLayout()
layout.addWidget(self.__edit)
layout.addWidget(self.__button)
self.setLayout(layout)
# create connections
self.__button.clicked.connect(self.emitTextSaved)
def emitTextSaved( self ):
# allow Qt's blocking of signals paradigm to control flow
if ( not self.signalsBlocked() ):
self.textSaved.emit(self.__edit.toPlainText())
testwindow.py
from PyQt4 import QtGui, QtCore
import testwidget
class TestWindow(QtGui.QMainWindow):
def __init__( self, parent == None ):
super(TestWindow, self).__init__(parent)
# create the ui (or load it)
self.__editor = testwidget.TestWidget(self)
self.setCentralWidget(self.__editor)
# create connections
self.__editor.textSaved.connect(self.showMessage)
def showMessage( self, message ):
QtGui.QMessageBox.information(self, 'Message', message)
So, here you can see that instead of thinking about it like - "when I click the button in TestWidget, I want to show a message in TestWindow" and explicitly link the two methods, you expose a signal that the TestWidget will emit out when the user performs an action, then connect that signal to the showMessage slot of the TestWindow. This way, your smaller widgets become more independent, and its more a matter of how you connect to each event that drives your application.
I could have done something like self.parent().showMessage(self.__edit.toPlainText()) within the TestWidget's emitTextSaved method to call the method directly - but this is not a good design.