1

I am trying to use a for loop to assign methods to the valueChanged signals of QScrollbars to make my code cleaner. However, this does not seem to work correctly. What am I doing wrong?

import sys
from PyQt5.QtWidgets import QScrollBar, QDialog, QVBoxLayout, QApplication


class MainWindow(QDialog):
    def __init__(self):
        super().__init__()
        self.layout = QVBoxLayout(self)
        self.scrollbar1 = QScrollBar(self)
        self.scrollbar2 = QScrollBar(self)
        self.scrollbars = [self.scrollbar1, self.scrollbar2]
        self.names = ['scrollbar 1', 'scrollbar 2']
        self.layout.addWidget(self.scrollbar1)
        self.layout.addWidget(self.scrollbar2)
        for scrollbar, name in zip(self.scrollbars, self.names):
            print(scrollbar, name)
            scrollbar.valueChanged.connect(lambda: self.test(name))

        # self.scrollbar1.valueChanged.connect(
        #     lambda: self.test(self.names[0])
        # )
        # self.scrollbar2.valueChanged.connect(
        #     lambda: self.test(self.names[1])
        # )

        self.show()

    def test(self, scrollbar):
        print(scrollbar)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    GUI = MainWindow()
    sys.exit(app.exec_())

Assigning the methods "manually" works as expected, i.e. different names are passed on. When using the for loop however, for both scrollbars the same name is printed on a value change.

EDIT: Here is my snap_slider method

old:

def snap_slider(scrollbar):
    x = np.modf(scrollbar.value() / scrollbar.singleStep())
    if x[0] < 0.5:
        scrollbar.setSliderPosition(
            int(x[1] * scrollbar.singleStep()))
    else:
        scrollbar.setSliderPosition(
            int((x[1]+1) * scrollbar.singleStep()))

new:

def snap_slider(self):
    x = np.modf(self.sender().value() / self.sender().singleStep())
    if x[0] < 0.5:
        self.sender().setSliderPosition(
            int(x[1] * self.sender().singleStep()))
    else:
        self.sender().setSliderPosition(
            int((x[1] + 1) * self.sender().singleStep()))
mapf
  • 1,906
  • 1
  • 14
  • 40

1 Answers1

1

A few things here, since you are trying to make your code cleaner:

  • You should prefer the use of the sender() method to a lambda function
  • It is a good practice to isolate the UI setup in a separate function that you could call elsewhere
  • Same thing about the show() method: avoid to put it in the __init__ method, since you may need to instanciate a MainWindow without showing it (eg: for testing purposes)

Which would give something like:

import sys
from PyQt5.QtWidgets import QScrollBar, QDialog, QVBoxLayout, QApplication

class MainWindow(QDialog):
    def __init__(self):
        super().__init__()
        self.createWidgets()

    def createWidgets(self):
        self.layout = QVBoxLayout(self)

        self.scrollbar1 = QScrollBar(self)
        self.scrollbar2 = QScrollBar(self)

        for widget in [self.scrollbar1, self.scrollbar2]:
            widget.valueChanged.connect(self.test)
            self.layout.addWidget(widget)

    def test(self, event):
        print(self.sender())


if __name__ == '__main__':
    app = QApplication(sys.argv)
    GUI = MainWindow()
    GUI.show()
    sys.exit(app.exec_())
olinox14
  • 6,177
  • 2
  • 22
  • 39
  • Oh wow, that was fast! Thank you so much for your feedback! I didn't know about the sender() method, this makes things a lot easier. E.g. I was writing a snap_slider method that would affect the scrollbar, but originally, I had to pass on the scrollbar for that to work (see edited main post). This is much better! Can you explain why this isn't working with the lambda function? – mapf Feb 05 '19 at 17:36
  • 1
    Take a look [there](https://stackoverflow.com/questions/25314547/cell-var-from-loop-warning-from-pylint), this should answer to your question – olinox14 Feb 05 '19 at 18:17
  • It does! Thanks! – mapf Feb 05 '19 at 23:09