Results 1 to 15 of 15

Thread: PyQt QDialogButtonBox signals

Hybrid View

  1. #1

    Default PyQt QDialogButtonBox signals



    Hi all, I have some questions on PyQt.

    I created 2 sets of QDialogButtonBox, each with 3 buttons - 'Add', 'Remove', 'Clear'.
    Each set is connected/ for with a QListWidget, and since both buttons sets are working the same way except to different list, I created 3 separate methods, so that it is easier to maintain as I do not see the need to have a new method for each button.

    Since I have used `rejected` for the 'Remove' button, I used `clicked` for the 'Clear' button since I am unable to see if there are any other signals that I can used for it.

    However while it works for the 'Add' and 'Remove' buttons, it is not working 'Clear'.
    If either 'Add' or 'Remove' is clicked onto, the command for 'Clear' is called as well which ends up emptying the list everytime I tried to remove or add new item(s).

    This is some portions of my code:
    Code:
    # List widget 01
    self.widget_01_btns = QtGui.QDialogButtonBox()
    self.widget_01_btns.addButton('Add', QtGui.QDialogButtonBox.AcceptRole)
    self.widget_01_btns.addButton('Remove', QtGui.QDialogButtonBox.RejectRole)
    self.widget_01_btns.addButton('Clear', QtGui.QDialogButtonBox.ResetRole)
    
    # List widget 02
    self.widget_02_btns = QtGui.QDialogButtonBox()
    self.widget_02_btns.addButton('Add', QtGui.QDialogButtonBox.AcceptRole)
    self.widget_02_btns.addButton('Remove', QtGui.QDialogButtonBox.RejectRole)
    self.widget_02_btns.addButton('Clear', QtGui.QDialogButtonBox.ResetRole)
    
    # Button Connections
    
    # For widget 01
    self.widget_01_btns.accepted.connect(lambda:self.add_objects(self.widget01_list))
    self.widget_01_btns.rejected.connect(lambda:self.remove_objects(self.widget01_list))
    # This is called in conjunction whenever either 'Add' or 'Remove' button is clicked
    self.widget_01_btns.clicked.connect(lambda:self.clear_objects(self.widget01_list))
    
    # For widget 02
    self.widget_02_btns.accepted.connect(lambda:self.add_objects(self.widget02_list))
    self.widget_02_btns.rejected.connect(lambda:self.remove_objects(self.widget02_list))
    # This is called in conjunction whenever either 'Add' or 'Remove' button is clicked
    self.widget_02_btns.clicked.connect(lambda:self.clear_objects(self.widget02_list))

    I tried another method for the 'Clear' button that I found online, but upon using it, I got this error instead - `AttributeError: 'NoneType' object has no attribute 'clicked'`

    Code:
    self.widget_01_btns.button(QtGui.QDialogButtonBox.Reset).clicked.connect(lambda:self.clear_objects(self.widget01_list))
    Any pointers for me?

  2. #2
    if clause
    Join Date
    Sep 2011
    Location
    Austin, TX
    Posts
    123

    Default

    My advice would be not to use QDialogButtonBox at all and instead just use several QPushButtons. QDialogButtonBox is designed for actions that affect the entire dialog, like closing it or clearing all the data in the dialog. You are trying to affect only certain parts of the dialog.

    If there is some reason you want to avoid individual QPushButtons, please post it.

  3. #3
    float Claudio A's Avatar
    Join Date
    Feb 2012
    Location
    Montreal
    Posts
    65

    Default

    In this specific scenario, I would suggest you connect your methods to QActions, and connect those actions to your buttons or toolbar/toolbuttons.

    https://srinikom.github.io/pyside-do...i/QAction.html

  4. #4

    Default

    Quote Originally Posted by RFlannery View Post
    My advice would be not to use QDialogButtonBox at all and instead just use several QPushButtons. QDialogButtonBox is designed for actions that affect the entire dialog, like closing it or clearing all the data in the dialog. You are trying to affect only certain parts of the dialog.

    If there is some reason you want to avoid individual QPushButtons, please post it.
    I used QDialogButtonBox because I am trying to cater/ fit it into my ui to make it 'pretty' due to its layout - 1 button on the left-hand side, while 2 buttons to the far right..
    And with the use of buttonRoles, it seems to fit my cause well and hence I used it. But all seems to work well until the use of `resetRole` which is causing me quite some issue.

    By the way, I replaced `reset` to using of `helpRequested`, it works but does not seems to make sense as my button is not a Help but a Reset button.



    Quote Originally Posted by Claudio A View Post
    In this specific scenario, I would suggest you connect your methods to QActions, and connect those actions to your buttons or toolbar/toolbuttons.

    https://srinikom.github.io/pyside-do...i/QAction.html
    How do I do that? Could you give me an example?

  5. #5
    float Claudio A's Avatar
    Join Date
    Feb 2012
    Location
    Montreal
    Posts
    65

    Default

    What I was proposing is far more explicit, and although I can see the appeal of what you're trying to do, I feel it's one of those places where you probably don't need to do this. Roles describe defaults scenarios in the UI - however what you want is explicit. You can possibly recycle a Role to fill in the Clear functionality (like you did with the RejectRole), but it implies customizing the default Button associated with that Role. It just seems like the same amount of work as defining your own buttons/slots explicitly. Maybe it's just personal preference...

    Here's an example of how to use QAction:

    Code:
    from PySide2 import QtWidgets, QtGui, QtCore
    from functools import partial
    
    self.action_addWidget01 = QtWidgets.QAction('Add')
    self.action_remWidget01 = QtWidgets.QAction('Remove')
    self.action_clrWidget01 = QtWidgets.QAction('Clear')
    # ...
    self.action_addWidget01.triggered.connect(partial(self.addToWidget, widget01, widget01_items))
    # ...
    self.button_addWidget01 = QtWidgets.QToolButton()
    self.button_addWidget01.setDefaultAction(self.action_addWidget01)
    
    
    def addToWidget(widget, items):
        # ...
        widget.addItems(items)
    The only real advantage is that you can connect to the action's trigger/toggle slot and potentially reuse this action elsewhere from the UI.

  6. #6

    Default

    Quote Originally Posted by Claudio A View Post
    What I was proposing is far more explicit, and although I can see the appeal of what you're trying to do, I feel it's one of those places where you probably don't need to do this. Roles describe defaults scenarios in the UI - however what you want is explicit. You can possibly recycle a Role to fill in the Clear functionality (like you did with the RejectRole), but it implies customizing the default Button associated with that Role. It just seems like the same amount of work as defining your own buttons/slots explicitly. Maybe it's just personal preference...

    Here's an example of how to use QAction:

    Code:
    from PySide2 import QtWidgets, QtGui, QtCore
    from functools import partial
    
    self.action_addWidget01 = QtWidgets.QAction('Add')
    self.action_remWidget01 = QtWidgets.QAction('Remove')
    self.action_clrWidget01 = QtWidgets.QAction('Clear')
    # ...
    self.action_addWidget01.triggered.connect(partial(self.addToWidget, widget01, widget01_items))
    # ...
    self.button_addWidget01 = QtWidgets.QToolButton()
    self.button_addWidget01.setDefaultAction(self.action_addWidget01)
    
    
    def addToWidget(widget, items):
        # ...
        widget.addItems(items)
    The only real advantage is that you can connect to the action's trigger/toggle slot and potentially reuse this action elsewhere from the UI.
    Hey Claudio, thanks for getting back.
    Unfortunately I do not have PySide2. Will try that out when I get the chance. Thanks!

  7. #7
    if clause
    Join Date
    Sep 2011
    Location
    Austin, TX
    Posts
    123

    Default

    Okay, I see now why you want to use the dialog button box. You could try something like the following code. It uses the "clicked" signal only. Then it figures out which button was clicked and calls the correct function. (Warning: This is untested.)
    Code:
    from functools import partial
    
        self.widget_01_btns.clicked.connect(partial(self.doTheThing, self.widget01_list))
        self.widget_02_btns.clicked.connect(partial(self.doTheThing, self.widget02_list))
    
    def doTheThing(self, listWidget, buttonClicked):
        role = buttonClicked.buttonRole()
        if role == QtGui.QDialogButtonBox.AcceptRole:
            self.add_objects(listWidget)
        if role == QtGui.QDialogButtonBox.Reject:
            self.remove_objects(listWidget)
        if role == QtGui.QDialogButtonBox.Reset:
            self.clear_objects(listWidget)

  8. #8

    Default

    Quote Originally Posted by RFlannery View Post
    Okay, I see now why you want to use the dialog button box. You could try something like the following code. It uses the "clicked" signal only. Then it figures out which button was clicked and calls the correct function. (Warning: This is untested.)
    Code:
    from functools import partial
    
        self.widget_01_btns.clicked.connect(partial(self.doTheThing, self.widget01_list))
        self.widget_02_btns.clicked.connect(partial(self.doTheThing, self.widget02_list))
    
    def doTheThing(self, listWidget, buttonClicked):
        role = buttonClicked.buttonRole()
        if role == QtGui.QDialogButtonBox.AcceptRole:
            self.add_objects(listWidget)
        if role == QtGui.QDialogButtonBox.Reject:
            self.remove_objects(listWidget)
        if role == QtGui.QDialogButtonBox.Reset:
            self.clear_objects(listWidget)
    Thanks for getting back.
    Tried the code you have provided, and there is an error - `# AttributeError: 'PySide.QtGui.QPushButton' object has no attribute 'buttonRole'`

  9. #9
    if clause
    Join Date
    Sep 2011
    Location
    Austin, TX
    Posts
    123

    Default

    Oops. Here's another try. (Again, this is untested and not very elegant. Feel free to clean it up):
    Code:
    from functools import partial
    
        self.widget_01_btns.clicked.connect(partial(self.doTheThing, self.widget01_list))
        self.widget_02_btns.clicked.connect(partial(self.doTheThing, self.widget02_list))
    
    def doTheThing(self, listWidget, buttonClicked):
        buttonBox = self.sender()
        role = buttonBox.buttonRole(buttonClicked)
        if role == QtGui.QDialogButtonBox.AcceptRole:
            self.add_objects(listWidget)
        if role == QtGui.QDialogButtonBox.Reject:
            self.remove_objects(listWidget)
        if role == QtGui.QDialogButtonBox.Reset:
            self.clear_objects(listWidget)

  10. #10

    Default

    Quote Originally Posted by RFlannery View Post
    Oops. Here's another try. (Again, this is untested and not very elegant. Feel free to clean it up):
    Code:
    from functools import partial
    
        self.widget_01_btns.clicked.connect(partial(self.doTheThing, self.widget01_list))
        self.widget_02_btns.clicked.connect(partial(self.doTheThing, self.widget02_list))
    
    def doTheThing(self, listWidget, buttonClicked):
        buttonBox = self.sender()
        role = buttonBox.buttonRole(buttonClicked)
        if role == QtGui.QDialogButtonBox.AcceptRole:
            self.add_objects(listWidget)
        if role == QtGui.QDialogButtonBox.Reject:
            self.remove_objects(listWidget)
        if role == QtGui.QDialogButtonBox.Reset:
            self.clear_objects(listWidget)
    Hmm, actually it is not working as well.

    Got the same error, erroing at the btn_role.
    Error is: `AttributeError: 'NoneType' object has no attribute 'buttonRole'`

    And buttonBox is registering as `None`

  11. #11
    if clause
    Join Date
    Sep 2011
    Location
    Austin, TX
    Posts
    123

    Default

    Looks like you will have to pass both the button box and the list widget to your function.

    Code:
    from functools import partial
    
            self.buttonBox1.clicked.connect(partial(self.doTheThing, self.buttonBox1, self.listWidget1))
            self.buttonBox2.clicked.connect(partial(self.doTheThing, self.buttonBox2, self.listWidget2))
        
        def doTheThing(self, sender, listWidget, buttonClicked):
            role = sender.buttonRole(buttonClicked)
            if role == QtGui.QDialogButtonBox.AcceptRole:
                self.add_objects(listWidget)
            elif role == QtGui.QDialogButtonBox.RejectRole:
                self.remove_objects(listWidget)
            elif role == QtGui.QDialogButtonBox.ResetRole:
                self.clear_objects(listWidget)

  12. #12

    Default

    Quote Originally Posted by RFlannery View Post
    Looks like you will have to pass both the button box and the list widget to your function.

    Code:
    from functools import partial
    
            self.buttonBox1.clicked.connect(partial(self.doTheThing, self.buttonBox1, self.listWidget1))
            self.buttonBox2.clicked.connect(partial(self.doTheThing, self.buttonBox2, self.listWidget2))
        
        def doTheThing(self, sender, listWidget, buttonClicked):
            role = sender.buttonRole(buttonClicked)
            if role == QtGui.QDialogButtonBox.AcceptRole:
                self.add_objects(listWidget)
            elif role == QtGui.QDialogButtonBox.RejectRole:
                self.remove_objects(listWidget)
            elif role == QtGui.QDialogButtonBox.ResetRole:
                self.clear_objects(listWidget)
    This does works.

    But right now I am curious - is there no exact signal when using ResetRole?
    From online searches, I have and only been seeing that 'accepted' is used for AcceptRole, while 'rejected' is for RejectRole and 'helpRequested' is for helpRole.

  13. #13
    if clause
    Join Date
    Sep 2011
    Location
    Austin, TX
    Posts
    123

    Default

    Quote Originally Posted by salik89 View Post
    This does works.

    But right now I am curious - is there no exact signal when using ResetRole?
    From online searches, I have and only been seeing that 'accepted' is used for AcceptRole, while 'rejected' is for RejectRole and 'helpRequested' is for helpRole.
    That is correct. The "clicked" signal will be emitted no matter what button is pressed. The "AcceptRole", "RejectRole", and "HelpRole" are the only three roles with specific signals tied to them. If you want your buttons to have any other role, you must use the "clicked" signal.

  14. #14
    if clause
    Join Date
    Sep 2011
    Location
    Austin, TX
    Posts
    123

    Default

    Fascinating. It looks like both "partial" and "lambda" cause self.sender() to return None.

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •