QSlider stepping? - python

I'd like to specify the steps that a QSlider can slide, like it is possible for the QSpinBox by using setSingleStep. I tried to use setSingleStep of QAbstractSlider, but this seems to have no effect.
Any ideas?

Try setting the tickInterval
EDIT
Sorry for the tickInterval, didn't quite thinked about it, however i have this working code and it does what you want using setSingleStep
import sys
from PyQt4.QtGui import QApplication, QSlider, QMainWindow
class Window(QMainWindow):
def __init__(self, parent = None):
super(Window, self).__init__(parent)
slider = QSlider()
slider.setMinimum(0)
slider.setMaximum(100)
slider.setTickInterval(20)
slider.setSingleStep(20)
self.setCentralWidget(slider)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())

I extended the QSlider class to limit the user so that they cannot track the slider between the steps.
The SetInterval method is equivalent to combining the setTickInterval and setSingleStep methods, but also stops the slider being positioned between tick values.
The class also allows the use of float values as slider limits or intervals and allows the index of the point selected on the slider to be set and read.
class DoubleSlider(qw.QSlider):
def __init__(self, *args, **kargs):
super(DoubleSlider, self).__init__( *args, **kargs)
self._min = 0
self._max = 99
self.interval = 1
def setValue(self, value):
index = round((value - self._min) / self.interval)
return super(DoubleSlider, self).setValue(index)
def value(self):
return self.index * self.interval + self._min
#property
def index(self):
return super(DoubleSlider, self).value()
def setIndex(self, index):
return super(DoubleSlider, self).setValue(index)
def setMinimum(self, value):
self._min = value
self._range_adjusted()
def setMaximum(self, value):
self._max = value
self._range_adjusted()
def setInterval(self, value):
# To avoid division by zero
if not value:
raise ValueError('Interval of zero specified')
self.interval = value
self._range_adjusted()
def _range_adjusted(self):
number_of_steps = int((self._max - self._min) / self.interval)
super(DoubleSlider, self).setMaximum(number_of_steps)

setSingleStep() still don't work on slider in PyQt5 now
so I tried this to make the same effect
your_slider = QtWidgets.QSlider()
your_slider.valueChanged.connect(lambda:set_step(step))
flag = 0
def your_func():
pass
#func here
def set_step(step):
value = your_slider.value()//step*step
your_slider.setValue(value)
if flag != value:
flag = value
your_func()
I just learned python for several months
Please correct it if there is a mistake

Related

Qcombobox with Qlabel and signal&slot

I have a Qgroupbox which contains Qcombobox with Qlabels, I want to select a value from Qcombobox and display the value as Qlabel. I have the complete code, even I do print value before and after within function every thing works as it should, Only display setText wont set text to Qlabel and update it.
Current screen
What I want
I've corrected signal code, when Qgroupbox in it Qcombobox appears or value would be changed, self.activation.connect(......) would emit an int of the index. to ensure that would work I print it-value inside the def setdatastrength(self, index), see figure below indeed it works, then argument would be passed to function self.concreteproperty.display_condata(it) would be called and do a print of value inside def display_condata(self, value) to make sure about value passing, as shown figure below, it does work. This line code self.con_strength_value.setText(fmt.format(L_Display))
wont assign value to Qlabel.
The script
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class secondtabmaterial(QtWidgets.QWidget):
def __init__(self, parent=None):
super(secondtabmaterial, self).__init__(parent)
self.concretewidgetinfo = ConcreteStrengthInFo()
Concrete_Group = QtWidgets.QGroupBox(self)
Concrete_Group.setTitle("&Concrete")
Concrete_Group.setLayout(self.concretewidgetinfo.grid)
class ConcreteStrengthComboBox(QtWidgets.QComboBox):
def __init__(self, parent = None):
super(ConcreteStrengthComboBox, self).__init__(parent)
self.addItems(["C12/15","C16/20","C20/25","C25/30","C30/37","C35/45"
,"C40/50","C45/55","C50/60","C55/67","C60/75","C70/85",
"C80/95","C90/105"])
self.setFont(QtGui.QFont("Helvetica", 10, QtGui.QFont.Normal, italic=False))
self.compressive_strength = ["12","16","20","25","30","35","40",
"45","50","55","60","70","80","90"]
class ConcreteProperty(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ConcreteProperty, self).__init__(parent)
self.setFont(QtGui.QFont("Helvetica", 10, QtGui.QFont.Normal, italic=False))
concretestrength_lay = QtWidgets.QHBoxLayout(self)
fctd = "\nfcd\n\nfctd\n\nEc"
con_strength = QtWidgets.QLabel(fctd)
self.con_strength_value = QtWidgets.QLabel(" ")
concretestrength_lay.addWidget(con_strength)
concretestrength_lay.addWidget(self.con_strength_value, alignment=QtCore.Qt.AlignRight)
self.setLayout(concretestrength_lay)
#QtCore.pyqtSlot(int)
def display_condata(self, value):
try:
L_Display = str(value)
print("-------- After ------")
print(L_Display, type(L_Display))
fmt = "{}mm"
self.con_strength_value.setText(fmt.format(L_Display))
except ValueError:
print("Error")
class ConcreteStrengthInFo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ConcreteStrengthInFo, self).__init__(parent)
self.concreteproperty = ConcreteProperty()
self.concretestrengthbox = ConcreteStrengthComboBox()
self.concretestrengthbox.activated.connect(self.setdatastrength)
hbox = QtWidgets.QHBoxLayout()
concrete_strength = QtWidgets.QLabel("Concrete strength: ")
hbox.addWidget(concrete_strength)
hbox.addWidget(self.concretestrengthbox)
self.grid = QtWidgets.QGridLayout()
self.grid.addLayout(hbox, 0, 0)
self.grid.addWidget(self.concreteproperty, 1, 0)
#QtCore.pyqtSlot(int)
def setdatastrength(self, index):
it = self.concretestrengthbox.compressive_strength[index]
self.concreteproperty.display_condata(it)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = secondtabmaterial()
w.show()
sys.exit(app.exec_())
Above code is corrected and final. Now it works as it should.
I think the issue is that your receiving slot doesn't match any of the available .activated signals.
self.activated.connect(self.setdatastrength)
#QtCore.pyqtSlot()
def setdatastrength(self):
index = self.currentIndex()
it = self.compressive_strength[index]
print(it)
self.concreteproperty.display_condata(it)
The QComboBox.activated signal emits either an int of the index, or a str of the selected value. See documentation.
You've attached it to setdatastrength which accepts doesn't accept any parameters (aside from self, from the object) — this means it doesn't match the signature of either available signal, and won't be called. If you update the definition to add the index value, and accept a single int it should work.
self.activated.connect(self.setdatastrength)
#QtCore.pyqtSlot(int) # add the target type for this slot.
def setdatastrength(self, index):
it = self.compressive_strength[index]
print(it)
self.concreteproperty.display_condata(it)
After the update — the above looks now to be fixed, although you don't need the additional index = self.currentIndex() in setdatastrength it's not doing any harm.
Looking at your code, I think the label is being updated. The issue actually is that you can't see the label at all. Looking at the init for ConcreteProperty
class ConcreteProperty(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ConcreteProperty, self).__init__(parent)
self.setFont(QtGui.QFont("Helvetica", 10, QtGui.QFont.Normal, italic=False))
self.concretestrength_lay = QtWidgets.QHBoxLayout()
fctd = "\nfcd\n\nfctd\n\nEc"
con_strength = QtWidgets.QLabel(fctd)
self.con_strength_value = QtWidgets.QLabel(" ")
self.concretestrength_lay.addWidget(con_strength)
self.concretestrength_lay.addWidget(self.con_strength_value, alignment=QtCore.Qt.AlignLeft)
The reason the changes are not appearing is that you create two ConcreteProperty objects, one in ConcreteStrengthInfo and one in ConcreteStrengthComboBox. Updates to the combo box trigger an update of the ConcreteProperty attached to the combobox, not the other one (they are separate objects). The visible ConcreteProperty is unaffected.
To make this work, you need to move the signal attachment + the slot out of the combo box object. The following is a replacement for the two parts —
class ConcreteStrengthComboBox(QtWidgets.QComboBox):
def __init__(self, parent = None):
super(ConcreteStrengthComboBox, self).__init__(parent)
self.addItems(["C12/15","C16/20","C20/25","C25/30","C30/37","C35/45","C40/50","C45/55",
"C50/60","C55/67","C60/75","C70/85","C80/95","C90/105"])
self.setFont(QtGui.QFont("Helvetica", 10, QtGui.QFont.Normal, italic=False))
self.compressive_strength = ["12","16","20","25","30","35","40","45","50","55",
"60","70","80","90"]
class ConcreteStrengthInFo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ConcreteStrengthInFo, self).__init__(parent)
hbox = QtWidgets.QHBoxLayout()
concrete_strength = QtWidgets.QLabel("Concrete strength: ")
hbox.addWidget(concrete_strength)
self.concreteproperty = ConcreteProperty()
self.concretestrengthbox = ConcreteStrengthComboBox()
hbox.addWidget(self.concretestrengthbox)
self.concretestrengthbox.activated.connect(self.setdatastrength)
self.vlay = QtWidgets.QVBoxLayout()
self.vlay.addLayout(hbox)
self.vlay.addLayout(self.concreteproperty.concretestrength_lay)
#QtCore.pyqtSlot(int)
def setdatastrength(self, index):
it = self.concretestrengthbox.compressive_strength[index]
print(it)
self.concreteproperty.display_condata(it)
This works for me locally.

Get currently selected cell of QTreeWidget

I'd like to modify QTreeWidget to make the selected cell editable when the enter key is hit, but keep the selection to full rows.
I've done a hacky implementation of figuring out where the last click was and saving the value, then sending those values to my edit_item function on the key press (also used for the itemDoubleClicked signal). It's not great though and I'm wondering if there's a much easier way to do it.
For the record, clicking on an item still selects the whole row. It's probably hidden behaviour by default, but in Maya there's a visible selection thing of the last cell that was moved over while the mouse button was held. If I could somehow get access to that, I could also add in behaviour to control it with the arrow keys.
This is an example of the selected cell:
This is my code so far:
class QTreeWidget(QtWidgets.QTreeWidget):
returnPressed = QtCore.Signal(QTreeWidget, int)
def __init__(self, *args, **kwargs):
QtWidgets.QTreeWidget.__init__(self, *args, **kwargs)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Return:
self.returnPressed.emit(self._selected_item, self._selected_column)
else:
QtWidgets.QTreeWidget.keyPressEvent(self, event)
def _mouse_pos_calculate(self, x_pos):
"""Find the currently selected column."""
try:
item = self.selectedItems()[0]
except IndexError:
item = None
header = self.header()
total_width = 0
for i in range(self.columnCount()):
total_width += header.sectionSize(i)
if total_width > x_pos:
return (item, i)
def mousePressEvent(self, event):
QtWidgets.QTreeWidget.mousePressEvent(self, event)
self._selected_item, self._selected_column = self._mouse_pos_calculate(event.pos().x())
def mouseReleaseEvent(self, event):
QtWidgets.QTreeWidget.mouseReleaseEvent(self, event)
self._selected_item, self._selected_column = self._mouse_pos_calculate(event.pos().x())
Edit: Improved function thanks to eyllanesc
class QTreeWidget(QtWidgets.QTreeWidget):
"""Add ability to edit cells when pressing return."""
itemEdit = QtCore.Signal(QtWidgets.QTreeWidgetItem, int)
def __init__(self, *args, **kwargs):
QtWidgets.QTreeWidget.__init__(self, *args, **kwargs)
self._last_item = None
self._last_column = 0
self.itemDoubleClicked.connect(self._edit_item_intercept)
def _edit_item_intercept(self, item=None, column=None):
if item is None:
item = self._last_item
if column is None:
column = self._last_column
self.itemEdit.emit(item, column)
def _store_last_cell(self, pos):
selected_item = self.itemAt(pos)
if selected_item is None:
return
self._last_item = selected_item
self._last_column = self.header().logicalIndexAt(pos.x())
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Return:
return self._edit_item_intercept()
QtWidgets.QTreeWidget.keyPressEvent(self, event)
def mouseMoveEvent(self, event):
QtWidgets.QTreeWidget.mouseMoveEvent(self, event)
self._store_last_cell(event.pos())
You are doing a lot of calculation unnecessarily, in the next part I show a cleaner solution:
from PySide2 import QtCore, QtGui, QtWidgets
class QTreeWidget(QtWidgets.QTreeWidget):
def __init__(self, *args, **kwargs):
super(TreeWidget, self).__init__(*args, **kwargs)
self.special_item = None
self.special_col = 0
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Return:
self.editItem(self.special_item, self.special_col)
QtWidgets.QTreeWidget.keyPressEvent(self, event)
def editEnable(self, pos):
press_item = self.itemAt(pos)
if press_item is None:
return
if press_item is self.selectedItems()[0]:
col = self.header().logicalIndexAt(pos.x())
self.special_item = press_item
self.special_col = col
def mousePressEvent(self, event):
QtWidgets.QTreeWidget.mousePressEvent(self, event)
self.editEnable(event.pos())

"QStackedWidget.setCurrentIndex": It does not work or error mark

I'm doing a program with graphical interface using PyQt5 . I want to do is that when the user presses certain button, this change widget and show other options.
For this I decided to use QStackedWidget, and all my interface build it from the QT5 designer.
However, in my code, wanting to determine that my name button "btfr" show me "page_2" of my stackedWidget when pressed, using the QStackedWidget.setCurrentIndex method, this does nothing or make any error.
the code is as follows:
import sys
from PyQt5 import uic
from PyQt5.QtCore import QTimeLine
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
infz = uic.loadUiType("main.ui")[0]
class FaderWidget(QWidget):
def __init__(self, old_widget, new_widget):
QWidget.__init__(self, new_widget)
self.old_pixmap = QPixmap(new_widget.size())
old_widget.render(self.old_pixmap)
self.pixmap_opacity = 1.0
self.timeline = QTimeLine()
self.timeline.valueChanged.connect(self.animate)
self.timeline.finished.connect(self.close)
self.timeline.setDuration(333)
self.timeline.start()
self.resize(new_widget.size())
self.show()
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
painter.setOpacity(self.pixmap_opacity)
painter.drawPixmap(0, 0, self.old_pixmap)
painter.end()
def animate(self, value):
self.pixmap_opacity = 1.0 - value
self.repaint()
class StackedWidget(QStackedWidget):
def __init__(self, parent=None):
QStackedWidget.__init__(self, parent)
def setCurrentIndex(self, index):
self.stack = MyWindowClass()
self.a = self.stack.stackedWidget.currentWidget()
self.b = self.stack.stackedWidget.widget(index)
self.fader_widget = FaderWidget(self.a, self.b)
QStackedWidget.setCurrentIndex(self, index)
print(self, index)
def setPage1(self):
self.setCurrentIndex(0)
def setPage2(self):
self.setCurrentIndex(1)
class MyWindowClass(QStackedWidget, infz):
def __init__(self, parent=None):
global pos, c, f
self.pos = 0
self.c = []
self.f = False
QStackedWidget.__init__(self, parent)
self.setupUi(self)
self.setWindowTitle('SkR')
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MyWindowClass()
window.resize(788, 518)
stack = StackedWidget()
window.btfr.clicked.connect(stack.setPage2)
window.btnpx.clicked.connect(stack.setPage1)
window.show()
sys.exit(app.exec_())
What I intend with this code is that the change of widget does so with an effect: "fade out".
If I print the "self " and the "index " receiving QStackedWidget.setCurrentIndex shows the following:
<__main__.StackedWidget object at 0x7fc2eb6b5c18> 0
The number zero is index, and the other element is self
Thank you for your attention, I hope someone can help.
Your question isn't completely clear, but don't you just want:
def setIndex(self, index):
self.setCurrentIndex(index)
However, this is a little redundant as you should able to link the button directly to the setCurrentIndex method and use lambda to pass the index value:
btfr.clicked.connect(lambda: self.setCurrentIndex(2))

Synchronize two element values in PyQt5

I have a slider and a text box that contains an integer (is there a dedicated integer box?) in PyQt5 shown side by side.
I need these two values to be synchronized, and the way I am doing it right now is with a QtTimer and if statements detecting if one value has changed more recently than the other, and then updating the opposite element. I was told this was "hacky" and was wondering if there was a proper way to do this.
You can see the text box values and sliders that I need to synchronize in the clear areas of the image below.
The simple solution is to connect the valueChanged for each slider/number box to a slot which synchronises the values
self.slider1.valueChanged.connect(self.handleSlider1ValueChange)
self.numbox1.valueChanged.connect(self.handleNumbox1ValueChange)
#QtCore.pyqtSlot(int)
def handleSlider1ValueChange(self, value):
self.numbox1.setValue(value)
#QtCore.pyqtSlot(int)
def handleNumbox1ValueChange(self.value):
self.slider1.setValue(value)
A better solution is to define a custom slider class that handles everything internally. This way you only have to handle the synchronisation once.
from PyQt5 import QtCore, QtWidgets
class CustomSlider(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super(CustomSlider, self).__init__(*args, **kwargs)
self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.slider.valueChanged.connect(self.handleSliderValueChange)
self.numbox = QtWidgets.QSpinBox()
self.numbox.valueChanged.connect(self.handleNumboxValueChange)
layout = QtWidgets.QHBoxLayout(self)
layout.addWidget(self.numbox)
layout.addWidget(self.slider)
#QtCore.pyqtSlot(int)
def handleSliderValueChange(self, value):
self.numbox.setValue(value)
#QtCore.pyqtSlot(int)
def handleNumboxValueChange(self, value):
# Prevent values outside slider range
if value < self.slider.minimum():
self.numbox.setValue(self.slider.minimum())
elif value > self.slider.maximum():
self.numbox.setValue(self.slider.maximum())
self.slider.setValue(self.numbox.value())
app = QtWidgets.QApplication([])
slider1 = CustomSlider()
slider2 = CustomSlider()
window = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout(window)
layout.addWidget(slider1)
layout.addWidget(slider2)
window.show()
app.exec_()
Edit: With regard to comments from ekhumoro, the above class can be simplified to
class CustomSlider(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super(CustomSlider, self).__init__(*args, **kwargs)
self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.numbox = QtWidgets.QSpinBox()
self.numbox.setRange(self.slider.minimum(), self.slider.maximum())
self.slider.valueChanged.connect(self.numbox.setValue)
self.slider.rangeChanged.connect(self.numbox.setRange)
self.numbox.valueChanged.connect(self.slider.setValue)
layout = QtWidgets.QHBoxLayout(self)
layout.addWidget(self.numbox)
layout.addWidget(self.slider)
You'll probably also want to mimic some of the QSlider methods to change the range and value. Note we don't need to explicitly set anything on self.numbox as the signal/slot connections made above take care of it.
#QtCore.pyqtSlot(int)
def setMinimum(self, minval):
self.slider.setMinimum(minval)
#QtCore.pyqtSlot(int)
def setMaximum(self, maxval):
self.slider.setMaximum(maxval)
#QtCore.pyqtSlot(int, int)
def setRange(self, minval, maxval):
self.slider.setRange(minval, maxval)
#QtCore.pyqtSlot(int)
def setValue(self, value):
self.slider.setValue(value)
You can just connect each of the sliders to the other one, straight-forward. I don't know the exact connection you want between the sliders, but it could look something like this.
max_player_slider.valueChanged.connect(self.slider1_fu)
npc_stream_slider.valueChanged.conenct(self.slider2_fu)
def slider1_fu(self):
# do stuff with the npc_stream_slider
def slider2_fu(self):
# do stuff with the max_player_slider
Edit: Here is a Tutorial on YouTube that might be helpful.

PyQt5 connection doesn't work: item cannot be converted to PyQt5.QtCore.QObject in this context

I am trying to connect a signal from a created object and am getting an error. Here is a simplified version of my code:
class OverviewWindow(QMainWindow):
def __init__(self, projectClusters, users, contributorDict, userLastRevPerProj):
QMainWindow.__init__(self)
# Code....
def createUserNodes(self):
userNodes = {}
nodeSpread = 50
yPos = -400
nodeSpan = nodeSpread + 100
width = (len(self.usersFilt) - 1) * nodeSpan
xPos = 0 - (width / 2)
for user in self.usersFilt:
newItem = NodeItem(xPos, yPos, self.nodeDiameter, user, True)
newItem.nodeDoubleClicked.connect(self.dc)
userNodes[user] = newItem
self.graphicsScene.addItem(newItem)
xPos += nodeSpan
return userNodes
#pyqtSlot(str)
def dc(self, text):
print(text)
class NodeItem(QGraphicsItem):
nodeDoubleClicked = pyqtSignal(str)
def __init__(self, xPos, yPos, diameter, text, isUserNode):
super(NodeItem, self).__init__()
# Code...
def mouseDoubleClickEvent(self, event):
self.nodeDoubleClicked.emit(self.texts)
When trying to run it it give me this error:
line 84, in createUserNodes
newItem.nodeDoubleClicked[str].connect(self.dc)
TypeError: NodeItem cannot be converted to PyQt5.QtCore.QObject in this context
I have no idea what this means or how to fix it.
QGraphicsItem does not inherit from QObject, therefore it is not possible to emit a signal from an instance of QGraphicsItem. You can solve this by subclassing QGraphicsObject instead of QGraphicsItem: http://doc.qt.io/qt-5/qgraphicsobject.html.

Categories