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)
Related
I am searching for a way to have a QTreeView that contains hierarchical items which themselfs have a layout that is propperly drawn.
I tried to inherit from both QStandartItem and QWidget (to have a layout) but the second i set the layout on the widget part of this class the programm is shutting down when it tries to render.
class modPackItem(qtg.QStandardItem,qtw.QWidget):
def __init__(self,txt:str='',image_path:str='./assets/defaultModPack.jpg'):
super().__init__()
fnt = qtg.QFont('Calibri',12)
fnt.setBold(True)
self.setEditable(False)
self.setForeground(qtg.QColor(0,0,0))
self.setFont(fnt)
self.setText(txt)
self.horLayout = qtw.QHBoxLayout()
self.horLayout.addWidget(qtw.QLabel("test"))
#self.setLayout(self.horLayout) #this breaks the rendering
modPack_image = qtg.QImage(image_path)
self.setData(modPack_image.scaled(64,64,qtc.Qt.AspectRatioMode.KeepAspectRatioByExpanding),qtc.Qt.ItemDataRole.DecorationRole)
Is there a possible way to have all items in the QTreeView contain layouts (For example with multiple texts[description,tag-words,etc]).
Note: I also considered switching to a simple List of widgets which have children containing the hierarchical items. But that would increase complexity of my app-structure a lot and therefore i would like to avoid that.
Edit: To clearify what i want to do:
I want to build a mod(pack) manager in the style of the technic-launcher for minecraft mods but instead for any kind of game in any kind of infrastructure(steam, local instal,etc). By clicking different buttons i add new "modpacks" or "mods" (optimally custom QStandartItem with Layout for all the data) in an hierarchical fashion (therefore treeview). Adding the items and the steam-subsrciption or filecopy logic is no problem but i would like to see all infos (Name,descritpion, custom tags) on the overview (like in the example pic). I know i could bind the QStandartItem selection method to a new popup showing all infos but that would be inconvinient.
Edit2: On terms of implementation i just add the QStandartItem-object as an additional row to the root-node before setting the model. I allready tested adding new objects to the rootnode by clicking on a button and that worked fine. Just setting the layout in the object crashes the application at start.
class SteamModManager_Dialog(qtw.QDialog):
window: Ui_SteamModManagerFrame
treeModel: qtg.QStandardItemModel
rootNode: qtg.QStandardItem
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.window = Ui_SteamModManagerFrame()
self.window.setupUi(self)
self.window.label_footer.setText("")
self.treeModel = qtg.QStandardItemModel()
self.rootNode = self.treeModel.invisibleRootItem()
modPack = modPackItem('Dont Starve Together')
testMod = modItem("TestMod")
modPack.appendRow(testMod)
self.rootNode.appendRow(modPack)
self.window.tView_modPacks.setModel(self.treeModel)
self.window.tView_modPacks.expandAll()
On the behalf of #musicamente here the solution that worked out for me:
I created a widget in the designer (as usual, not posting the full ui code here).
Then i implemented the following code into the Dialog:
self.treeModel = qtg.QStandardItemModel()
self.rootNode = self.treeModel.invisibleRootItem()
modPack = modPackItem('Dont Starve Together')
testMod = modItem("TestMod")
modPack.appendRow(testMod)
self.rootNode.appendRow(modPack)
self.window.tView_modPacks.setModel(self.treeModel)
self.window.tView_modPacks.expandAll()
modPackWidget = qtw.QWidget()
ui = Ui_modPackWidget()
ui.setupUi(modPackWidget)
self.window.tView_modPacks.setIndexWidget(self.treeModel.index(0,0),modPackWidget)
This code resulted setting the custom widget to the treeview item. Here the final look:
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'm using PyQt5 to create a program. I created 3 Radio Buttons, but when I check the first button and check the second button after that. The program will run both of the functions which are connected to these buttons. How I can make it only run the function which is connected to that button. Thanks.
def __init__(self):
super(Program, self).__init__()
self.ui = Ui_APIManager()
self.ui.setupUi(self)
self.show()
self.ui.add_btn.toggled.connect(self.start)
self.ui.check_btn.toggled.connect(self.start)
self.ui.delete_btn.toggled.connect(self.start)
def start(self):
if self.ui.add_btn.isChecked():
self.ui.third_lbl.setEnabled(True)
self.ui.first_lbl.setText('Tool name')
self.ui.second_lbl.setText('ID')
self.ui.third_lbl.setText('Username')
self.ui.action_btn.clicked.connect(self.add_user)
elif self.ui.check_btn.isChecked():
self.ui.first_lbl.setText('Type of search')
self.ui.second_lbl.setText('Keyword')
self.ui.third_lbl.setEnabled(False)
self.ui.action_btn.clicked.connect(self.check_user)
elif self.ui.delete_btn.isChecked():
self.ui.first_lbl.setText('Type of search')
self.ui.second_lbl.setText('Keyword')
self.ui.third_lbl.setEnabled(False)
self.ui.action_btn.clicked.connect(self.delete_user)
Qt signals can have multiple slots attached to them. Every time you click a button the start function is adding another connection to the action_button.clicked signal.
You will need to disconnect any existing slots from the signal first to achieve the desired behaviour. You can disconnect everything from self.ui.action_btn all at once by calling its disconnect() function.
Rather than trying to reassign the roles of the GUI elements you have created, you would be better off creating separate widgets containing the elements for each checkbox state and switching between them. You might find QStackedWidget useful.
I wrote a python GUI program consisting of two separate files; One is for logic code and the other for GUI using PyQt4. The behaviour of some objects (buttons, text fields ...) changes throughout the code and I need to reset everything to its original status by clicking on a QAction class menu item. How can I do that?
EDIT: the function that supposed to reset the GUI to the original status:
def newSession(self):
self.ui.setupUi(self)
self.filename = ""
self.paramsSplitted = []
self.timestep = None
self.index = None
self.selectedParam = None
self.selectedMethod = None
--Snip--
What you could do:
Define a ResetHandler(QtCore.QObject) object with a reset_everything signal
During startup create an instance and set it on the globally available QApplication like qapplication.reset_handler = ResetHandler()
Every UI element that needs to update itself defines a on_reset_everything_triggered() slot. (Optional: You could also just use update for example).
When You create UI elements that are supposed to update, connect them to the globally available reset_everything signal from the handler on the QApplication.
Connect your QAction.triggered with the ResetHandler.reset_everything signal.
Now every time you press the QAction the reset_everything signal is invoked, and all UI elements that you connected will update themself.
Like you requested in your comment here is a schematic way of utilizing a function to connect all signals and the method setupUi.
class MainWindow(QtGui.QMainWindow) :
def __init__(self) :
QtGui.QMainWindow.__init__(self)
self.ui.setupUi(self)
# Some code
self.connectAllSignals()
def connectAllSignals(self) :
self.someWidget.clicked.connect(self.someFunction)
self.someAction.triggered.connect(self.otherFunction)
# All the other signals
def disconnectAllSignals(self) :
try :
self.someWidget.clicked.disconnect()
self.someAction.triggered.disconnect()
# All the other signals
except :
print("Something went wrong. Check your code.")
pass
def newSession(self) :
self.ui.setupUi(self)
self.disconnectAllSignals()
self.connectAllSignals()
# Do whatever it takes
By this you ensure you have only the initial settings for your signals and all dynamically added connections are broken. In the method disconnectAllSignals be sure all widgets exist and all signals have at least one connection by the time you call it. If you have new widgets invoked dynamically you should consider deleting them in th method newSession after calling connectAllSignals.
im just a beginner in PyQT.
and im not sure if my thread title is the correct thing to put for my problem.
im having a problem creating a popmenu on a Qpushbutton.
based on the doc of QT docs
i need to make a QPushButton.setMenu (self, QMenu menu)
but i really dont know where to start.. i cant find a sample on how to use this.
please help me making one.
The basic idea is that you first have to create a QMenu, then use the setMenu method to attach it to your push button. If you look at the QMenu documentation, you'll see that there is a method called addAction that will add menu items to your newly created QMenu. addAction is overloaded, so there are a lot of different ways to call it. You can use icons in your menu, specify keyboard shortcuts and other things. To keep things simple though, let's just add a menu item and give it a method to call if that item is selected.
from PyQt4 import QtGui, QtCore
import sys
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
pushbutton = QtGui.QPushButton('Popup Button')
menu = QtGui.QMenu()
menu.addAction('This is Action 1', self.Action1)
menu.addAction('This is Action 2', self.Action2)
pushbutton.setMenu(menu)
self.setCentralWidget(pushbutton)
def Action1(self):
print 'You selected Action 1'
def Action2(self):
print 'You selected Action 2'
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Main()
main.show()
app.exec_()
Here we've created a push button (creatively named pushbutton). We then create a menu (again creatively named menu) using QtGui.QMenu(). The actions are created by calling addAction and giving it a string that will be used as the menu item text and a method (self.Action1 or self.Action2) that will be called if that menu item is selected. Then we call the setMenu method of pushbutton to assign our menu to it. When you run it and select an item, you should see text printed corresponding to the selected item.
That's the basic idea. You can look through the QMenu docs to get a better idea of the functionality of QMenu.