Subclass QPushbutton with different args - python

How can I subclass the QPushbutton in Pyside but require an arg. In my example I need the arg to be a list of ints [0,0,0].
My goal is to make it so i can create MyButton like this:
# GOAL
MyButton([0,255,0])
When the arguement containing the list of values is passed in, it should set the value self._data. I'm not sure if i have this setup correctly, so any corrections are appreciated.
class MyButton(QtGui.QPushButton):
def __init__(self, *args, **kwargs):
super(MyButton, self).__init__(*args, **kwargs)
self._data = stuff
#property
def data(self):
return self._data
#data.setter
def data(self, value):
self._data = value
MyButton([0,255,0])
Updated: I noticed though when i pass that value into my Init it doesn't appear to trigger the setter for that property?? Why is that? If you test the code below, you'll see the color of the button isn't set when it's instantiated. How do i fix that?
import os
import sys
import json
from PySide import QtCore, QtGui
class QColorSwatch(QtGui.QPushButton):
colorClicked = QtCore.Signal(list)
colorChanged = QtCore.Signal(list)
def __init__(self, stuff, *args, **kwargs):
super(QColorSwatch, self).__init__(*args, **kwargs)
self._color = stuff
self.setMaximumWidth(22)
self.setMaximumHeight(22)
self.setAutoFillBackground(True)
self.pressed.connect(self.color_clicked)
#property
def color(self):
return self._color
#color.setter
def color(self, value):
self._color = value
pixmap = QtGui.QPixmap(self.size())
pixmap.fill(QtGui.QColor(value[0], value[1], value[2]))
self.setIcon(pixmap)
self.colorChanged.emit(value)
# swatch.setIconSize(pixmap.size() - QtCore.QSize(6,6))
def color_clicked(self):
self.colorClicked.emit(self.color)
class ExampleWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(ExampleWindow, self).__init__(parent)
self.resize(300, 200)
self.ui_swatch = QColorSwatch([255,0,0])
# main layout
main_layout = QtGui.QVBoxLayout()
main_layout.setContentsMargins(5,5,5,5)
main_layout.setSpacing(5)
main_layout.addWidget(self.ui_swatch)
main_widget = QtGui.QWidget()
main_widget.setLayout(main_layout)
self.setCentralWidget(main_widget)
# Signals
self.ui_swatch.colorClicked.connect(self.color_clicked)
self.ui_swatch.colorChanged.connect(self.color_changed)
def color_clicked(self, col):
print 'CLICKED:', col
self.ui_swatch.color = [255,0,0]
def color_changed(self, col):
print 'CHANGED:', col
def main():
app = QtGui.QApplication(sys.argv)
ex = ExampleWindow()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

You have to put stuff as a parameter.
class MyButton(QtGui.QPushButton):
def __init__(self, stuff, *args, **kwargs):
super(MyButton, self).__init__(*args, **kwargs)
self._data = stuff
#property
def data(self):
return self._data
#data.setter
def data(self, value):
self._data = value
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mainWin = MyButton([0,255,0])
print(mainWin.data)
mainWin.show()
sys.exit(app.exec_())
Update:
You have to use self.color to use the setter.
class QColorSwatch(QtGui.QPushButton):
colorClicked = QtCore.Signal(list)
colorChanged = QtCore.Signal(list)
def __init__(self, stuff, *args, **kwargs):
super(QColorSwatch, self).__init__(*args, **kwargs)
self._color = None
self.color = stuff
self.setMaximumWidth(22)
self.setMaximumHeight(22)
self.setAutoFillBackground(True)
self.pressed.connect(self.color_clicked)
#property
def color(self):
return self._color
#color.setter
def color(self, value):
self._color = value
pixmap = QtGui.QPixmap(self.size())
pixmap.fill(QtGui.QColor(value[0], value[1], value[2]))
self.setIcon(pixmap)
self.colorChanged.emit(value)
# swatch.setIconSize(pixmap.size() - QtCore.QSize(6,6))
def color_clicked(self):
self.colorClicked.emit(self.color)

Related

Qt widget that can be either checkbox or text depending on initial value

I'm trying to figure out how to make a widget that would merge QCheckBox with QLineEdit into one.
In my interface, I parse attributes that can be either booleans or something else and I need to display them for users to edit. This means I have a fair bit of IFs in the code and I would like to encapsulate the logic into a widget.
My very naive implementation looks like this but it is not a true widget and this class requires special handling in the UI. How can I make it a normal widget that can be used in any interface like all native widgets?
from Qt import QtWidgets, QtCore
class SettingWidget(QtWidgets.QWidget):
'''a simple widget that can be either a bool or string. not sure how to make it an actual widget and this is just a wrapper'''
changed = QtCore.Signal(str, object)
def __init__(self, name, value):
super(self.__class__, self).__init__()
self.name = name
if isinstance(value, bool):
self.w_value = QtWidgets.QCheckBox()
self.w_value.stateChanged.connect(self._checkboxChanged)
self.w_value.setChecked(value)
self.changed.emit(self.name, value)
else:
self.w_value = QtWidgets.QLineEdit()
self.w_value.textChanged.connect(self._textChanged)
self.w_value.setText(str(value))
self.changed.emit(self.name, str(value))
def _checkboxChanged(self, state):
if state == QtCore.Qt.Checked:
self.changed.emit(self.name, True)
else:
self.changed.emit(self.name, False)
def _textChanged(self, text):
self.changed.emit(self.name, str(text))
def getWidget(self):
return self.w_value
A possible solution is to use SettingWidget as a container for one of the other widgets and then set it through a layout:
lay = QtWidgets.QHBoxLayout(self)
lay.addWidget(self.w_value)
But IMO a better design is to create a mixin and design a function that provides the widget.
from Qt import QtCore, QtWidgets
class SettingMixin:
changed = QtCore.Signal(str, object)
def __init__(self, name):
self._name = name
#property
def name(self):
return self._name
#name.setter
def name(self, name):
self._name = name
def handle_changed(self, value):
self.changed.emit(self.name, value)
class SettingCheckBox(QtWidgets.QCheckBox, SettingMixin):
def __init__(self, name, initial_value, parent=None):
super().__init__(parent)
self.name = name
self.setChecked(initial_value)
self.toggled.connect(self.handle_changed)
class SettingLineEdit(QtWidgets.QLineEdit, SettingMixin):
def __init__(self, name, initial_value, parent=None):
super().__init__(parent)
self.name = name
self.setText(initial_value)
self.textChanged.connect(self.handle_changed)
def build_setting_widget(name, initial_value):
if isinstance(initial_value, bool):
return SettingCheckBox(name, initial_value)
elif isinstance(initial_value, str):
return SettingLineEdit(name, initial_value)
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
widget = QtWidgets.QWidget()
lay = QtWidgets.QHBoxLayout(widget)
w1 = build_setting_widget("Foo", True)
w1.changed.connect(print)
w2 = build_setting_widget("Bar", "Hello World")
w2.changed.connect(print)
lay.addWidget(w1)
lay.addWidget(w2)
widget.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

PyQt - Easily add a QWidget to all Views

I have the following
class MyView(QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
layout = QVBoxLayout()
layout.addWidget(QLabel('Hello World'))
self.setLayout(layout)
class NavigationMenu(QWidget):
pass
# Renders a bar of full width and 15 px height
What is the easiest way to add the NavigationMenu to MyView?
In the future, I would have to also add the NavigationMenu to all other Views, so I am looking for something scalable from a typing and maintainability stand point.
I tried decorators (just #NavigationMenuDecorator on top of the class), but I either cannot bind them or they get initialized at parse time and error QWidget: Must construct a QApplication before a QWidget.
I tried just adding it into MyView, but there is a lot of boilerplate
class MyWidget(Widget.QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
layout = Widget.QVBoxLayout()
layout.addWidget(QLabel('Hello World'))
topLayout = Widget.QVBoxLayout()
topLayout.setContentsMargins(0, 0, 0, 0)
topLayout.addWidget(NavigationMenu())
topLayout.addLayout(layout)
self.setLayout(topLayout)
A possible solution is to use metaclass:
from PyQt5 import QtCore, QtWidgets
class NavigationMenu(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(QtWidgets.QLabel("NavigationMenu"))
class MetaNavigationMenu(type(QtWidgets.QWidget), type):
def __call__(cls, *args, **kw):
obj = super().__call__(*args, **kw)
lay = obj.layout()
if lay is not None:
lay.addWidget(NavigationMenu())
return obj
class View(QtWidgets.QWidget, metaclass=MetaNavigationMenu):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(QtWidgets.QLabel('Hello World'))
self.setLayout(layout)
if __name__=="__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = View()
w.show()
sys.exit(app.exec_())
Update:
With the following method you can inject the view and the additional arguments that the view requires:
from PyQt5 import QtCore, QtWidgets
class NavigationMenu(QtWidgets.QWidget):
def __init__(self, value, text="", parent=None):
super().__init__(parent)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(QtWidgets.QLabel(text))
print(value)
class MetaMenu(type(QtWidgets.QWidget), type):
def __new__(cls, class_name, parents, attrs, **kwargs):
cls._view = kwargs.pop('view', None)
cls._args = kwargs.pop('args', tuple())
cls._kwargs = kwargs.pop('kwargs', dict())
return type.__new__(cls, class_name, parents, attrs)
def __call__(cls, *args, **kw):
obj = super().__call__(*args, **kw)
layout = getattr(obj, 'layout', None)
if callable(layout) and View is not None:
layout().addWidget(cls._view(*cls._args, **cls._kwargs))
return obj
class View(QtWidgets.QWidget, metaclass=MetaMenu, view=NavigationMenu, args=(10, ), kwargs={"text": "NavigationMenu"}):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(QtWidgets.QLabel('Hello World'))
self.setLayout(layout)
if __name__=="__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = View()
w.show()
sys.exit(app.exec_())
The other solution here is amazing and I learned a lot about metaclasses. However, it is quite hard to read and adds unnecessary complexity. I settled for a composition-based approach, where I just extracted the boilerplate to a separate function.
The add_navigation() function wraps the old layout in a widget, creates a QVBoxLayout with the NavigationMenu and the old layout, and finally swaps the layouts.
def add_navigation(widget, title)
main = QWidget()
main.setLayout(widget.layout())
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
layout.addWidget(NavigationBar(title))
layout.addWidget(main)
widget.setLayout(layout)
We then have just a 1-liner of boilerplate and the code then becomes.
class MyView(QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
layout = QVBoxLayout()
layout.addWidget(QLabel('Hello World'))
self.setLayout(layout)
add_navigation(self, 'Navigation Title')
class NavigationMenu(QWidget):
pass
# Renders a bar of full width and 15 px height

Custom Item Delegate Resizing and Interaction

I'm creating a custom item delegate for a news feed I'm trying to create in pyside. I'm not quite sure how to make the textEdit auto adjust it's size to fit the contents of the text it's wrapping and secondly maintain the Text Interaction feature, where users can click and highlight text?
This is what I'm currently getting and you can see the text boxes are being drawn overtop and not vertically being sized correctly:
import os, sys
from Qt import QtWidgets, QtCore, QtGui
class NewsItem(object):
def __init__(self, **kwargs):
super(NewsItem, self).__init__()
self.title = kwargs.get('title', '')
self.date = kwargs.get('date', '')
self.content = kwargs.get('content', '')
class NewsItemDelegate(QtWidgets.QItemDelegate):
def __init__(self, parent=None):
super(NewsItemDelegate, self).__init__(parent)
def paint(self, painter, option, index):
# rect = option.rect.adjusted(1, 1, -1, -1)
# painter.fillRect(rect, QtGui.QColor(20,40,170,50))
# QtWidgets.QItemDelegate.paint(self, painter, option, index)
# get data from userrole
data = index.data(role=QtCore.Qt.UserRole)
# Main Widget
title = QtWidgets.QLabel(data.title)
content = QtWidgets.QTextEdit(data.content)
content.setFixedHeight(content.sizeHint().height())
widget = QtWidgets.QWidget()
layout = QtWidgets.QGridLayout(widget)
layout.addWidget(title, 0, 0)
layout.addWidget(content, 1, 0)
widget.setGeometry(option.rect)
widget.render(painter, option.rect.topLeft())
# painter.save()
# painter.restore()
def sizeHint(self, option, index):
return QtCore.QSize(100, 50)
return QtWidgets.QItemDelegate.sizeHint(self, option, index)
class NewsModel(QtGui.QStandardItemModel):
def __init__(self, *args, **kwargs):
QtGui.QStandardItemModel.__init__(self, *args, **kwargs)
class NewsListView(QtWidgets.QListView):
def __init__(self, parent=None):
super(NewsListView, self).__init__(parent)
self.setModel(NewsModel(self))
self.setItemDelegate(NewsItemDelegate(self))
self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
def setNewsItems(self, lst):
self.model().clear()
for x in lst:
item = QtGui.QStandardItem()
# item.setData(x.title, role=QtCore.Qt.DisplayRole)
item.setData(x, role=QtCore.Qt.UserRole)
self.model().appendRow(item)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.resize(350, 500)
# Controls
self.uiListView = NewsListView()
self.setCentralWidget(self.uiListView)
def unitTest(self):
self.uiListView.setNewsItems([
NewsItem(title='Big Update', date='Today', content='Something goes here...'),
NewsItem(title='Smaller Update', date='Yesterday', content='Something goes here which should support word wrap'),
NewsItem(title='Another Update', date='Last Year', content='Something goes here...'),
NewsItem(title='Old Update', date='Unknown', content='Something goes here...'),
])
def main():
app = QtWidgets.QApplication(sys.argv)
ex = MainWindow()
ex.unitTest()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
In this type of cases is to create a widget as an editor, and for this you must make in the paint method call openPersistentEditor(). The createEditor(), setEditorData() and setModelData() methods must also be overwritten.
# ...
from functools import partial
# ...
class EditorWidget(QtWidgets.QWidget):
editingFinished = QtCore.Signal()
def __init__(self, data=None, parent=None):
super(EditorWidget, self).__init__(parent)
self.title_label = QtWidgets.QLabel()
self.content_textedit = QtWidgets.QTextEdit()
self.content_textedit.textChanged.connect(self.editingFinished)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.title_label)
lay.addWidget(self.content_textedit)
if data is not None:
self.data = data
#property
def data(self):
return NewsItem(
title=self.title_label.text(),
content=self.content_textedit.toPlainText(),
)
#data.setter
def data(self, d):
self.title_label.setText(d.title)
tc = self.content_textedit.textCursor()
self.content_textedit.setPlainText(d.content)
self.content_textedit.setTextCursor(tc)
class NewsItemDelegate(QtWidgets.QItemDelegate):
def paint(self, painter, option, index):
if isinstance(self.parent(), QtWidgets.QAbstractItemView):
self.parent().openPersistentEditor(index)
def createEditor(self, parent, option, index):
data = index.data(QtCore.Qt.UserRole)
editor = EditorWidget(data, parent)
wrapper = partial(self.commitData.emit, editor)
editor.editingFinished.connect(wrapper)
model = index.model()
model.setData(index, editor.sizeHint(), QtCore.Qt.SizeHintRole)
return editor
def setEditorData(self, editor, index):
data = index.data(QtCore.Qt.UserRole)
editor.data = data
def setModelData(self, editor, model, index):
model.setData(index, editor.data, QtCore.Qt.UserRole)
# ...

Emit signal from QAction passing custom widget value

I have created a simple menu using a custom widget. How can i make the menu QAction emit the color value of the swatch clicked? Each color swatch contains a property called 'color'. If the user clicks 'Reset' i would like the value emitted to be 'None'
I tried to overload the triggered signal to pass the color of the swatch clicked but it didn't work either.
import sys
from PySide import QtGui, QtCore
class QColorSwatch(QtGui.QPushButton):
colorClicked = QtCore.Signal(object)
colorChanged = QtCore.Signal(object)
def __init__(self, *args, **kwargs):
super(QColorSwatch, self).__init__(*args, **kwargs)
self.setFixedWidth(18)
self.setFixedHeight(18)
self.setAutoFillBackground(True)
self._color = None
self.color = QtGui.QColor(0,0,0)
self.pressed.connect(self.color_clicked)
#property
def color(self):
return self._color
#color.setter
def color(self, value):
self._color = value
self.setIconSize(self.size())
pixmap = QtGui.QPixmap(self.size())
pixmap.fill(QtCore.Qt.black)
painter = QtGui.QPainter(pixmap)
painter.setBrush(QtGui.QColor(self.color))
painter.setPen(QtGui.QPen(QtCore.Qt.white, 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
painter.drawRect(1,1,self.size().width()-3,self.size().height()-3)
painter.end()
self.setIcon(pixmap)
self.colorChanged.emit(value)
def color_clicked(self):
self.colorClicked.emit(self.color)
class ColorFilters(QtGui.QWidget):
def __init__(self, action):
super(ColorFilters,self).__init__()
self.action = action
self.ui_any_color = QtGui.QLabel('Reset')
self.ui_swatch_01 = QColorSwatch()
self.ui_swatch_01.color = QtGui.QColor(255,0,0)
self.ui_swatch_02 = QColorSwatch()
self.ui_swatch_02.color = QtGui.QColor(0,255,0)
self.ui_swatch_03 = QColorSwatch()
self.ui_swatch_03.color = QtGui.QColor(0,0,255)
lay_main = QtGui.QGridLayout()
lay_main.setSpacing(5)
lay_main.setContentsMargins(5,5,5,5)
lay_main.addWidget(self.ui_any_color,0,0,1,4)
lay_main.addWidget(self.ui_swatch_01,1,0)
lay_main.addWidget(self.ui_swatch_02,1,1)
lay_main.addWidget(self.ui_swatch_03,1,2)
self.setLayout(lay_main)
# connections
self.ui_swatch_01.colorClicked.connect(self.clicked_swatch)
self.ui_swatch_02.colorClicked.connect(self.clicked_swatch)
self.ui_swatch_03.colorClicked.connect(self.clicked_swatch)
def mouseReleaseEvent(self,e):
self.action.trigger()
def clicked_swatch(self, col):
col = self.sender().color
self.action.trigger()
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
colAction = QtGui.QWidgetAction(self)
ql = ColorFilters(colAction)
colAction.setDefaultWidget(ql)
colAction.triggered.connect(self.clicked_color)
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(colAction)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Menubar')
self.show()
def clicked_color(self):
print 'Clicked'
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
Instead of trying to use the triggered signal it creates a new signal that sends that information. Also you should know that a signal can also connect to another signal as I show below.
import sys
from PySide import QtGui, QtCore
class QColorSwatch(QtGui.QPushButton):
colorClicked = QtCore.Signal(QtGui.QColor)
colorChanged = QtCore.Signal(object)
def __init__(self, *args, **kwargs):
super(QColorSwatch, self).__init__(*args, **kwargs)
self.setFixedWidth(18)
self.setFixedHeight(18)
self.setAutoFillBackground(True)
self._color = None
self.color = QtGui.QColor(0,0,0)
self.pressed.connect(self.color_clicked)
#property
def color(self):
return self._color
#color.setter
def color(self, value):
self._color = value
self.setIconSize(self.size())
pixmap = QtGui.QPixmap(self.size())
pixmap.fill(QtCore.Qt.black)
painter = QtGui.QPainter(pixmap)
painter.setBrush(QtGui.QColor(self.color))
painter.setPen(QtGui.QPen(QtCore.Qt.white, 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
painter.drawRect(1,1,self.size().width()-3,self.size().height()-3)
painter.end()
self.setIcon(pixmap)
self.colorChanged.emit(value)
def color_clicked(self):
self.colorClicked.emit(self.color)
class ColorFilters(QtGui.QWidget):
colorSelected = QtCore.Signal(QtGui.QColor)
def __init__(self, parent=None):
super(ColorFilters, self).__init__(parent)
lay_main = QtGui.QGridLayout(self)
lay_main.setSpacing(5)
lay_main.setContentsMargins(5,5,5,5)
self.ui_any_color = QtGui.QLabel('Reset')
lay_main.addWidget(self.ui_any_color,0,0,1,4)
self.ui_any_color.installEventFilter(self)
for i, color in enumerate((QtGui.QColor(255,0,0), QtGui.QColor(0,255,0), QtGui.QColor(0,0,255))):
ui_swatch = QColorSwatch()
ui_swatch.color = color
lay_main.addWidget(ui_swatch,1,i+1)
ui_swatch.colorClicked.connect(self.colorSelected)
def eventFilter(self, obj, event):
if obj == self.ui_any_color and event.type() == QtCore.QEvent.Type.MouseButtonPress:
self.colorSelected.emit(QtGui.QColor)
return super(ColorFilters, self).eventFilter(obj, event)
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
colAction = QtGui.QWidgetAction(self)
ql = ColorFilters(self)
colAction.setDefaultWidget(ql)
ql.colorSelected.connect(self.clicked_color)
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(colAction)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Menubar')
self.show()
def clicked_color(self, color):
if not color.isValid():
print("reset")
else:
print('Clicked', color)
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

SpinCtrl with step increment value

I'm trying to create a custom SpinCtrl with a step increment. It seems like such a simple thing so I was surprised the native SpinCtrl doesn't appear to have this functionality, and Google proves uncommonly useless in this matter as well.
When I try to make a custom one, however, I run into problems. Here's some quick and dirty code
class SpinStepCtrl( wx.SpinCtrl ):
def __init__( self, *args, **kwargs ):
super( SpinStepCtrl, self ).__init__( *args, **kwargs )
self.step = 99
self.Bind( wx.EVT_SPINCTRL, self.OnSpin )
#self.Bind( wx.EVT_SPIN_UP, self.OnUp )
#self.Bind( wx.EVT_SPIN_DOWN, self.OnDown )
def OnSpin( self, event ):
print 'X'
self.SetValue( self.GetValue() + self.step )
The print is just there so I can see what, if anything, happens. The EVT_SPIN_UP and EVT_SPIN_DOWN events don't seem to work at all, at least the callbacks are never called which is why I took them out.
When using EVT_SPINCTRL, the callback is called, but end up in an infinite loop because SetValue apparently causes a new such event to be called. It doesn't help much either way, because I can't find a way of telling whether it was a spin up or spin down event, so I can't change the value appropriately.
How do I get this to work?
wx.lib.agw has floatspin widget which has more options. I use it with SetDigits(0) for integer input as well.
import wx
import wx.lib.agw.floatspin as FS
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "FloatSpin Demo")
panel = wx.Panel(self)
floatspin = FS.FloatSpin(panel, -1, pos=(50, 50), min_val=0, max_val=1000,
increment=99, value=0, agwStyle=FS.FS_LEFT)
floatspin.SetDigits(0)
# our normal wxApp-derived class, as usual
app = wx.App(0)
frame = MyFrame(None)
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()
OK, not the best, but works on Ubuntu:
#!/usr/bin/python
import wx
class SpinStepCtrl(wx.SpinCtrl):
def __init__(self, *args, **kwargs):
wx.SpinCtrl.__init__(self, *args, **kwargs)
self.step = 99
self.last_value = 0
self.Bind(wx.EVT_SPINCTRL, self.OnSpin)
def OnSpin(self, event):
delta = self.GetValue() - self.last_value
if delta == 0:
return
elif delta > 0:
self.last_value = self.GetValue() + self.step
else:
self.last_value = self.GetValue() - self.step
self.SetValue(self.last_value)
class MainWindow(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.panel = wx.Panel(self)
self.spin = SpinStepCtrl(self.panel, min=0, max=1000)
self.Show()
app = wx.App(False)
win = MainWindow(None)
app.MainLoop()
Still, I would consider "self.SetValue" generating "wx.EVT_SPINCTRL" a bug.
This works for me:
import wx
class SpinStepCtrl(wx.SpinCtrl):
def __init__(self, *args, **kwargs):
wx.SpinCtrl.__init__(self, *args, **kwargs)
self.step = 99
self.Bind(wx.EVT_SPIN_UP, self.OnUp)
self.Bind(wx.EVT_SPIN_DOWN, self.OnDown)
def OnUp(self, event):
self.SetValue(self.GetValue() + self.step)
def OnDown(self, event):
self.SetValue(self.GetValue() - self.step)
class MainWindow(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.panel = wx.Panel(self)
self.spin = SpinStepCtrl(self.panel, min=0, max=1000)
self.Show()
app = wx.App(False)
win = MainWindow(None)
app.MainLoop()
Here is my solution for wxSpinCtrl, which takes values from a list to spin.
It is a pity that the events EVT_SPIN_UP and EVT_SPIN_DOWN from wx.SpinButton do not work under all platforms (not working here with wx 4.0.6 and Windows 10 / 64 Bit)
import wx
class SpinCtrlList(wx.SpinCtrl):
"""A SpinCtrl that spins through a given list. This list must be in
ascending order and the values of the list must be integers,
whereat negative numbers are allowed
"""
def __init__(self, *args, **kwargs):
wx.SpinCtrl.__init__(self, *args, **kwargs)
self.Bind(wx.EVT_SPINCTRL, self._on_spin)
self.thelist = ([0,1])
self.SetList([0, 1])
self.SetValue(self.thelist[0])
def SetList(self, thelist):
self.thelist = thelist
self.SetRange(self.thelist[0], self.thelist[len(self.thelist)-1])
def GetList(self):
return self.thelist
def SetValue(self, val):
self.sp_old_value = val
super(SpinCtrlList, self).SetValue(val)
def _next_greater_ele(self, in_list, in_val):
for x, val in enumerate(in_list):
if val > in_val:
break
return val
def _next_smaller_ele(self, in_list, in_val):
for x, val in enumerate(reversed(in_list)):
if val < in_val:
break
return val
def _on_spin(self, event):
self.sp_new_value = self.GetValue()
#print(self.sp_old_value, self.sp_new_value)
if self.sp_new_value > self.sp_old_value:
set_val = self._next_greater_ele(self.thelist, self.sp_new_value)
self.SetValue(set_val)
self.sp_old_value = set_val
elif self.sp_new_value < self.sp_old_value:
set_val = self._next_smaller_ele(self.thelist, self.sp_new_value)
self.SetValue(set_val)
self.sp_old_value = set_val
class MainWindow(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.panel = wx.Panel(self)
self.spin = SpinCtrlList(self.panel)
ctrl_list =[100, 200, 500, 1000, 2000, 5000, 10000, 12000, 15000]
#ctrl_list = [-300, -100, 0]
self.spin.SetList(ctrl_list)
self.spin.SetValue(ctrl_list[-2])
self.Show()
def main():
app = wx.App(False)
win = MainWindow(None)
app.MainLoop()
if __name__ == '__main__':
main()

Categories

Resources