The proper use of QSignalMapper

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • borntonetwork

    #1

    The proper use of QSignalMapper

    Hi.

    I am trying to implement QTCore.QSignalM apper using PyQT. I finally got
    to a point where I don't receive any compile or runtime error messages,
    but I also do not see the final slot function fire off. Here is a
    snippet of the code:

    self.signalMapp er = QtCore.QSignalM apper(window)
    # Use qsignalmapper to use of one slot function for multiple
    # widgets. The map() function sends the slot an extra param
    # that identifies the sender.
    for idx in range(1, maxIngredients+ 1):
    wName = 'chkProductIngr edientsDelete_' +str(idx)
    w = self.__dict__[wName]
    self.app.connec t(w, QtCore.SIGNAL(" stateChanged(in t)"),
    self.signalMapp er,
    QtCore.SLOT("se lf.signalMapper .map"))
    self.signalMapp er.setMapping(w , idx)

    self.app.connec t(self.signalMa pper, QtCore.SIGNAL(
    "self.signalMap per.mapped(int) "),
    self.deleteProd uctIngredient)

    def deleteProductIn gredient(self, state, widgetIdx):
    """Delete a product ingredient."""
    print "INSIDE FUNCTION\n"

    The idea here is to iterate through several checkbox widgets (named
    "chkProductIngr edientsDelete_1 , _2, etc.) and connect each object's
    "stateChang ed" signal to the mapper's map() function, then connect the
    map() function to the actual slot, deleteProductIn gredient().

    By the way, I've checked to ensure the statements inside the loop are
    being executed.

  • David Boddie

    #2
    Re: The proper use of QSignalMapper

    borntonetwork wrote:
    I am trying to implement QTCore.QSignalM apper using PyQT. I finally got
    to a point where I don't receive any compile or runtime error messages,
    but I also do not see the final slot function fire off. Here is a
    snippet of the code:
    >
    self.signalMapp er = QtCore.QSignalM apper(window)
    # Use qsignalmapper to use of one slot function for multiple
    # widgets. The map() function sends the slot an extra param
    # that identifies the sender.
    for idx in range(1, maxIngredients+ 1):
    wName = 'chkProductIngr edientsDelete_' +str(idx)
    w = self.__dict__[wName]
    self.app.connec t(w, QtCore.SIGNAL(" stateChanged(in t)"),
    self.signalMapp er,
    QtCore.SLOT("se lf.signalMapper .map"))
    You need to pass the C++ signature to the SLOT() function. I believe
    you want the form that does not accept any arguments:

    self.app.connec t(w, QtCore.SIGNAL(" stateChanged(in t)"),
    self.signalMapp er,
    QtCore.SLOT("ma p()"))

    Alternatively, you can pass the slot directly to the function:

    self.app.connec t(w, QtCore.SIGNAL(" stateChanged(in t)"),
    self.signalMapp er.map)

    PyQt accepts Python methods as slots, without requiring that they be
    wrapped in calls to SLOT().

    David

    Comment

    • borntonetwork

      #3
      Re: The proper use of QSignalMapper

      Thanks, David, for you help.

      When I change the slot function to what you show in your second
      example, I get the same results: nothing. When I change it to what you
      have in your first example, I get the following:

      Object::connect : No such slot QApplication::m ap()
      Object::connect : (sender name: 'chkProductIngr edientsDelete_1 ')
      Object::connect : (receiver name: 'main.py')
      Object::connect : No such slot QApplication::m ap()
      Object::connect : (sender name: 'chkProductIngr edientsDelete_2 ')
      Object::connect : (receiver name: 'main.py')
      Object::connect : No such slot QApplication::m ap()
      Object::connect : (sender name: 'chkProductIngr edientsDelete_3 ')
      Object::connect : (receiver name: 'main.py')
      Object::connect : No such slot QApplication::m ap()
      Object::connect : (sender name: 'chkProductIngr edientsDelete_4 ')
      Object::connect : (receiver name: 'main.py')
      Object::connect : No such slot QApplication::m ap()
      Object::connect : (sender name: 'chkProductIngr edientsDelete_5 ')
      Object::connect : (receiver name: 'main.py')
      Object::connect : No such slot QApplication::m ap()
      Object::connect : (sender name: 'chkProductIngr edientsDelete_6 ')
      Object::connect : (receiver name: 'main.py')


      David Boddie wrote:
      borntonetwork wrote:
      >
      I am trying to implement QTCore.QSignalM apper using PyQT. I finally got
      to a point where I don't receive any compile or runtime error messages,
      but I also do not see the final slot function fire off. Here is a
      snippet of the code:

      self.signalMapp er = QtCore.QSignalM apper(window)
      # Use qsignalmapper to use of one slot function for multiple
      # widgets. The map() function sends the slot an extra param
      # that identifies the sender.
      for idx in range(1, maxIngredients+ 1):
      wName = 'chkProductIngr edientsDelete_' +str(idx)
      w = self.__dict__[wName]
      self.app.connec t(w, QtCore.SIGNAL(" stateChanged(in t)"),
      self.signalMapp er,
      QtCore.SLOT("se lf.signalMapper .map"))
      >
      You need to pass the C++ signature to the SLOT() function. I believe
      you want the form that does not accept any arguments:
      >
      self.app.connec t(w, QtCore.SIGNAL(" stateChanged(in t)"),
      self.signalMapp er,
      QtCore.SLOT("ma p()"))
      >
      Alternatively, you can pass the slot directly to the function:
      >
      self.app.connec t(w, QtCore.SIGNAL(" stateChanged(in t)"),
      self.signalMapp er.map)
      >
      PyQt accepts Python methods as slots, without requiring that they be
      wrapped in calls to SLOT().
      >
      David

      Comment

      • David Boddie

        #4
        Re: The proper use of QSignalMapper

        borntonetwork wrote:
        Thanks, David, for you help.
        >
        When I change the slot function to what you show in your second
        example, I get the same results: nothing.
        This may be due to something I missed in your code. When you
        connect the signal from the signal mapper to your class, you
        need to specify the signal as a C++ signature as well:

        self.connect(se lf.signalMapper , QtCore.SIGNAL(
        "mapped(int )"),
        self.deleteProd uctIngredient)

        The second example should now work.

        Looking at the first example, which uses SLOT() rather than specifying
        a Python method, the following might be due to the way you call
        connect, though it is surprising:
        When I change it to what you
        have in your first example, I get the following:
        >
        Object::connect : No such slot QApplication::m ap()
        Object::connect : (sender name: 'chkProductIngr edientsDelete_1 ')
        Object::connect : (receiver name: 'main.py')
        [...]

        It looks like you should try something like this:

        self.connect(w, QtCore.SIGNAL(" stateChanged(in t)"),
        self.signalMapp er,
        QtCore.SLOT("ma p()"))

        I can't understand why calling self.app.connec t() would cause the
        connection to be attempted between self.signalMapp er and self.app.

        Maybe someone on the PyQt/PyKDE mailing list would be able to
        explain the behaviour you're seeing:



        David

        Comment

        • borntonetwork

          #5
          Re: The proper use of QSignalMapper

          David, thanks for your help. Unfortunately, all attempts of making this
          solution work have failed. I would be interested to know if anyone has
          used QSignalMapper successfully in a similar situation. For any
          interested, I worked around the problem using a closure, which seems a
          bit cleaner from a coding point of view, although I don't have any idea
          if it is more efficient or not:

          def makeHandler(sel f, idx):
          def handler(state):
          print 'The state is:', str(state), 'for checkbox object',
          str(idx)
          return handler

          Each iteration then created a new function based on the closure:

          self.handlers = []
          for idx in range(1, maxIngredients+ 1):
          wName = 'chkProductIngr edientsDelete_' +str(idx)
          w = self.__dict__[wName]
          self.handlers.a ppend(self.make Handler(idx - 1))
          self.window.con nect(w, QtCore.SIGNAL(" stateChanged(in t)"),
          self.handlers[idx - 1])

          So now the output when each checkbox is clicked is something like this:

          The state is: 2 for checkbox object 0
          The state is: 2 for checkbox object 1
          ....



          David Boddie wrote:
          borntonetwork wrote:
          Thanks, David, for you help.

          When I change the slot function to what you show in your second
          example, I get the same results: nothing.
          >
          This may be due to something I missed in your code. When you
          connect the signal from the signal mapper to your class, you
          need to specify the signal as a C++ signature as well:
          >
          self.connect(se lf.signalMapper , QtCore.SIGNAL(
          "mapped(int )"),
          self.deleteProd uctIngredient)
          >
          The second example should now work.
          >
          Looking at the first example, which uses SLOT() rather than specifying
          a Python method, the following might be due to the way you call
          connect, though it is surprising:
          >
          When I change it to what you
          have in your first example, I get the following:

          Object::connect : No such slot QApplication::m ap()
          Object::connect : (sender name: 'chkProductIngr edientsDelete_1 ')
          Object::connect : (receiver name: 'main.py')
          >
          [...]
          >
          It looks like you should try something like this:
          >
          self.connect(w, QtCore.SIGNAL(" stateChanged(in t)"),
          self.signalMapp er,
          QtCore.SLOT("ma p()"))
          >
          I can't understand why calling self.app.connec t() would cause the
          connection to be attempted between self.signalMapp er and self.app.
          >
          Maybe someone on the PyQt/PyKDE mailing list would be able to
          explain the behaviour you're seeing:
          >

          >
          David

          Comment

          • David Boddie

            #6
            Re: The proper use of QSignalMapper

            On Saturday 20 January 2007 16:03, borntonetwork wrote:
            David, thanks for your help. Unfortunately, all attempts of making this
            solution work have failed. I would be interested to know if anyone has
            used QSignalMapper successfully in a similar situation.
            Looking again at what you originally wrote, and looking at your
            solution below, I don't think it's really appropriate to use a
            QSignalMapper for this purpose. (That's what I understood from the
            signature of the deleteProductIn gredient() method you had in your
            original code.)

            What you appear to want is a way of being notified about state
            changes to widgets that also identifies the individual widget
            that changed. You can do that by connecting the individual widgets
            to the same slot and call sender() to find out which object emitted
            the signal; for example:

            # ...

                    for idx in range(1, maxIngredients+ 1):
                        wName = 'chkProductIngr edientsDelete_' +str(idx)
                        w = self.__dict__[wName]
                        self.connect(w, QtCore.SIGNAL(" stateChanged(in t)"),
                         self.deleteProd uctIngredient)

            def deleteProductIn gredient(self, state):
            """Delete a product ingredient."""
            print self.sender(), "changed state to", state

            What QSignalMapper does is collect signals from different objects,
            assigning an ID to each of them, and emits a single signal with
            an ID whenever it receives a signal from one of those objects.
            In other words, it allows the actions of a group of objects to be
            described by a parameter but discards the signal's arguments.
            It's most useful for things like collections of push buttons where
            you only need to know that they were clicked.
            For any interested, I worked around the problem using a closure,
            which seems a bit cleaner from a coding point of view, although
            I don't have any idea if it is more efficient or not:
            [...]
            Each iteration then created a new function based on the closure:
            [...]

            It's good to know that you got something working in the end. There's
            also another solution that involves QSignalMapper, but it may not make
            the resulting code any simpler.

            David

            Comment

            • borntonetwork

              #7
              Re: The proper use of QSignalMapper

              How simple. I will remember that sender() function for future
              reference.
              Thanks, David.

              David Boddie wrote:
              On Saturday 20 January 2007 16:03, borntonetwork wrote:
              >
              David, thanks for your help. Unfortunately, all attempts of making this
              solution work have failed. I would be interested to know if anyone has
              used QSignalMapper successfully in a similar situation.
              >
              Looking again at what you originally wrote, and looking at your
              solution below, I don't think it's really appropriate to use a
              QSignalMapper for this purpose. (That's what I understood from the
              signature of the deleteProductIn gredient() method you had in your
              original code.)
              >
              What you appear to want is a way of being notified about state
              changes to widgets that also identifies the individual widget
              that changed. You can do that by connecting the individual widgets
              to the same slot and call sender() to find out which object emitted
              the signal; for example:
              >
              # ...
              >
              for idx in range(1, maxIngredients+ 1):
              wName = 'chkProductIngr edientsDelete_' +str(idx)
              w = self.__dict__[wName]
              self.connect(w, QtCore.SIGNAL(" stateChanged(in t)"),
              self.deleteProd uctIngredient)
              >
              def deleteProductIn gredient(self, state):
              """Delete a product ingredient."""
              print self.sender(), "changed state to", state
              >
              What QSignalMapper does is collect signals from different objects,
              assigning an ID to each of them, and emits a single signal with
              an ID whenever it receives a signal from one of those objects.
              In other words, it allows the actions of a group of objects to be
              described by a parameter but discards the signal's arguments.
              It's most useful for things like collections of push buttons where
              you only need to know that they were clicked.
              >
              For any interested, I worked around the problem using a closure,
              which seems a bit cleaner from a coding point of view, although
              I don't have any idea if it is more efficient or not:
              >
              [...]
              >
              Each iteration then created a new function based on the closure:
              >
              [...]
              >
              It's good to know that you got something working in the end. There's
              also another solution that involves QSignalMapper, but it may not make
              the resulting code any simpler.
              >
              David

              Comment

              Working...