double valued gauge in wxpython - python

Does anyone know of a double valued gauge in wxPython? I would like to show the range of a set of data that has been selected, I have the max and min values of the gauge and then I'd like to highlight the region of the gauge between two values. The normal wx.Gauge highlights from the gauge minimum (always the bottom of the gauge) to the set value.
Thanks

I'm not aware of any built-in widgets with the appearance of wx.Gauge that can do what you're looking for.
One option with a different appearance is RulerCtrl, which can give you two value indicators on a scale with whatever range you want. It just takes a bit of extra work to adapt it since not all of its properties are exposed in the listed methods. Quick example:
import wx
import wx.lib.agw.rulerctrl as rc
class MinMax(rc.RulerCtrl):
def __init__(self, parent, range_min, range_max, orient=wx.HORIZONTAL):
rc.RulerCtrl.__init__(self, parent, orient)
self.SetRange(range_min, range_max)
self.LabelMinor(False)
self.range_min = range_min
self.range_max = range_max
self.AddIndicator(wx.NewId(), range_min)
self.AddIndicator(wx.NewId(), range_max)
self.Bind(rc.EVT_INDICATOR_CHANGING, self.OnIndicatorChanging)
def GetMinIndicatorValue(self):
return self._indicators[0]._value
def GetMaxIndicatorValue(self):
return self._indicators[1]._value
def SetMinIndicatorValue(self, value):
# Value must be within range and <= Max indicator value.
if value < self.range_min or value > self.GetMaxIndicatorValue():
raise ValueError('Out of bounds!')
self._indicators[0]._value=value
self.Refresh()
def SetMaxIndicatorValue(self, value):
# Value must be within range and >= Min indicator value.
if value > self.range_max or value < self.GetMinIndicatorValue():
raise ValueError('Out of bounds!')
self._indicators[1]._value=value
self.Refresh()
def OnIndicatorChanging(self, evt):
# Eat the event so the user can't change values manually.
# Do some validation here and evt.Skip() if you want to allow it.
# Related: EVT_INDICATOR_CHANGED
pass
class MainWindow(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title)
self.minmax = MinMax(self, 0, 100)
self.minmax.SetSpacing(20)
self.minmax.SetMinIndicatorValue(30)
self.minmax.SetMaxIndicatorValue(84)
self.Show()
app = wx.App(redirect=False)
frame = MainWindow(None, wx.ID_ANY, "Range Indicator")
app.MainLoop()

Related

Is storing pyQT widgets in a list bad form?

So I am building I program that manages a bunch of custom slider widgets. Currently, I have a slider_container(class) that holds a list of slider objects(class). These slider objects are then inserted into the layout in the main window. This has been working well while I was only adding and moving the position of the sliders up and down. But when I try to delete the sliders, everything goes bad. When ever there the list of slider is manipulated (add, move or delete a slider), the clear and rebuild functions are called in the main window as seen below.
def clear_layout(self, layout):
print "Cleared Layout..."
while layout.count() > 0:
item = layout.takeAt(0)
widget = item.widget()
if widget is not None:
widget.deleteLater()
def rebuild_slider_display(self):
""" Delete the old sliders, then add all the new sliders """
self.clear_layout(self.slider_layout)
print "Rebuild layout..."
print len(self._widgets.widgets)
for i, widget in enumerate(self._widgets.widgets):
print widget.slider_name, " ", i
self.slider_layout.insertWidget(i, widget)
print "Layout widget count: ", self.slider_layout.count()
Currently I am running into this error on this line "self.slider_layout.insertWidget(i, widget)"
RuntimeError: wrapped C/C++ object of type SliderWidget has been deleted
My hunch is that storing the actual widget in the widget container is bad form. I think what is happening when I deleteLater() a widget, is that it isnt just deleting a widget from the list, it actually deletes the widget class that was store in the widget container itself.
Hopefully that is explained clearly, thanks for your help in advance.
Edit:
Here is the widget class:
class SliderWidget(QWidget, ui_slider_widget.Ui_SliderWidget):
""" Create a new slider. """
def __init__(self, name, slider_type, digits, minimum, maximum, value, index, parent=None):
super(SliderWidget, self).__init__(parent)
self.setupUi(self)
self.slider_name = QString(name)
self.expression = None
self.accuracy_type = int(slider_type)
self.accuracy_digits = int(digits)
self.domain_min = minimum
self.domain_max = maximum
self.domain_range = abs(maximum - minimum)
self.numeric_value = value
self.index = index
#self.name.setObjectName(_fromUtf8(slider.name))
self.update_slider_values()
self.h_slider.valueChanged.connect(lambda: self.update_spinbox())
self.spinbox.valueChanged.connect(lambda: self.update_hslider())
self.edit.clicked.connect(lambda: self.edit_slider())
# A unique has for the slider.
def __hash__(self):
return super(Slider, self).__hash__()
# How to compare if this slider is less than another slider.
def __lt__(self, other):
r = QString.localAware.Compare(self.name.toLower(), other.name.toLower())
return True if r < 0 else False
# How to compare if one slider is equal to another slider.
def __eq__(self, other):
return 0 == QString.localAwareCompare(self.name.toLower(), other.name.toLower())
And here is the actually creation of the widget in the widget container:
def add_slider(self, params=None):
if params:
new_widget = SliderWidget(params['name'], params['slider_type'], params['digits'], params['minimum'],
params['maximum'], params['value'], params['index'])
else:
new_widget = SliderWidget('Slider_'+str(self.count()+1), 1, 0, 0, 50, 0, self.count())
#new_widget.h_slider.valueChanged.connect(self.auto_save)
#new_widget.h_slider.sliderReleased.connect(self.slider_released_save)
new_widget.move_up.clicked.connect(lambda: self.move_widget_up(new_widget.index))
new_widget.move_down.clicked.connect(lambda: self.move_widget_down(new_widget.index))
self.widgets.append(new_widget)
Thanks for all the help!
The problem I was having was with the way I cleared the layout. It is important to clear the layout from the bottom to the top as seen below.
for i in reversed(range(layout.count())):
layout.itemAt(i).widget().setParent(None)

pyqt: dynamically update properties for dynamically created widgets

I'm trying to find a way to update labels which are within dynamically created widgets (that can be deleted), according to properties dynamically set in the preceding widgets.
Is there a way to automatically and dynamically link a pyqt object to a property of other widgets, so that when I change a value anywhere, that object updates itself?
Example: object 'a' has property start, bars and end; bars is given, start is taken by the previous object (or 1 if None), end is calculated. object 'b' takes its start from a.end and so on.
class myclass(object):
def __init__(self, referrer=None, value=1):
self.referrer=referrer
self.value=value
def _get_start(self):
if not self.referrer:
return 1
else:
return self.referrer.end+1
def _get_end(self):
return self.start+self.value-1
start=property(_get_start)
end=property(_get_end)
def create(value=1):
if not vList:
ref=None
else:
ref=vList[-1]
val=myclass(ref, value)
vList.append(val)
def showList():
for i in vList:
print 'item: %d\tstart: %d\tend: %d' % (vList.index(i),i.start, i.end)
vList=[]
If I call create() 3 times, showList() will show:
item: 0 start: 1 end: 1
item: 1 start: 2 end: 2
item: 2 start: 3 end: 3
if I change vList[0].value to 3:
item: 0 start: 1 end: 3
item: 1 start: 4 end: 4
item: 2 start: 5 end: 5
The problem raises when I need to keep those values updated in the gui (think to it as an interface like this): every horizontal widget has a label showing the property of start, a spinbox for bars, and a label for end, and as soon as any spinbox value changes, every subsequent widget should update its start and end properties according to its previous widget and show them in the relative labels.
Moreover, when any widget is deleted, all the subsequent widget should recompute every property.
Using getter/setter to set the values in the labels of the widgets' next instancies doesn't obviously work as I need, because when I change any x.value, the following instancies' start and end will be actually updated only when recalled, AFAIU.
I could connect every new widget to its previous (with valueChanged()) or create a function which finds the subsequent widget and update their properties, but that's not a good solution.
Since I am almost new to python (most important: I'm not a programmer) I think that I am ignoring something about "connecting" variables in a better and cleanest way (maybe related to signals or threading?).
Consider that those widgets will actually be children widgets of another "main" widget, which will have similar properties: start taken from its previous main widget (if any), bars which is the sum of all bars in every children widget, end which will be again start+bars-a.
Thanks!
(I hope you will understand what I meant, my english is not perfect and, since I'm not a programmer, my terminology is not always correct)
I can't find use case for things from your question, but here is possible solution using Qt Signals-Slots:
# -*- coding: utf-8 -*-
import functools
from PyQt4 import QtGui, QtCore
class ObservableVariable(QtCore.QObject):
""" Represents variable with value, when value changes it emits
signal: changed(new_value)
"""
changed = QtCore.pyqtSignal(object)
def __init__(self, initial_value=0):
super(ObservableVariable, self).__init__()
self._value = initial_value
def get_value(self):
return self._value
def set_value(self, new_val):
self._value = new_val
self.changed.emit(new_val)
value = property(get_value, set_value)
def __str__(self):
return str(self.value)
# it can support more operators if needed
def __iadd__(self, other):
self.value += other
return self
def __isub__(self, other):
self.value -= other
return self
class MyClass(object):
def __init__(self, referrer=None, value=1):
self.referrer = referrer
self.value = ObservableVariable(value)
self._initial_value = value
if referrer:
# propagate referrer changes to subscribers
referrer.value.changed.connect(
lambda x: self.value.changed.emit(self.value.value)
)
#property
def start(self):
if not self.referrer:
return self.value.value
return self.referrer.end + 1
#property
def end(self):
return self.start + self.value.value - 1
class GuiExample(QtGui.QWidget):
def __init__(self):
super(GuiExample, self).__init__()
self.values = []
layout = QtGui.QVBoxLayout(self)
obj = None
for i in range(5):
obj = MyClass(obj, i)
self.values.append(obj)
# create gui elements
hlayout = QtGui.QHBoxLayout()
spinbox = QtGui.QSpinBox()
spinbox.setValue(obj.value.value)
start_label = QtGui.QLabel()
end_label = QtGui.QLabel()
hlayout.addWidget(start_label)
hlayout.addWidget(spinbox)
hlayout.addWidget(end_label)
layout.addLayout(hlayout)
# function called on value change
def update_start_end(instance, start_label, end_label):
start_label.setText(str(instance.start))
end_label.setText(str(instance.end))
action = functools.partial(update_start_end, obj, start_label,
end_label)
action() # set initial start end text to labels
# connect signals to gui elements
obj.value.changed.connect(action)
spinbox.valueChanged.connect(obj.value.set_value)
obj.value.changed.connect(spinbox.setValue)
layout.addWidget(QtGui.QPushButton('test',
clicked=self.test_modification))
def test_modification(self):
self.values[1].value += 1
app = QtGui.QApplication([])
gui = GuiExample()
gui.show()
app.exec_()

How to allow infinite integer values in a spinner?

I need a Spinner widget in which the user can select integer values with a certain step and without lower or upper limits
(I mean, they should be at least in the billion range, so no chance of memorizing the whole sequence).
I saw kivy's Spinner widget but I don't think doing something like Spinner(values=itertool.count()) would work.
Also it is limited to string values.
Is there any simple way of obtaining something similar to QSpinBox of the Qt?
It seems like kivy at the moment does not provide anything similar to a Spinner or SpinBox or however you want to call it. A widget that might be used instead is the Slider but it looks awful and it's not so useful if you want to allow a very big range but with a small step.
Therefore I wrote my own implementation of a SpinBox:
class SpinBox(BoxLayout):
"""A widget to show and take numeric inputs from the user.
:param min_value: Minimum of the range of values.
:type min_value: int, float
:param max_value: Maximum of the range of values.
:type max_value: int, float
:param step: Step of the selection
:type step: int, float
:param value: Initial value selected
:type value: int, float
:param editable: Determine if the SpinBox is editable or not
:type editable: bool
"""
min_value = NumericProperty(float('-inf'))
max_value = NumericProperty(float('+inf'))
step = NumericProperty(1)
value = NumericProperty(0)
range = ReferenceListProperty(min_value, max_value, step)
def __init__(self, btn_size_hint_x=0.2, **kwargs):
super(SpinBox, self).__init__(orientation='horizontal', **kwargs)
self.value_label = Label(text=str(self.value))
self.inc_button = TimedButton(text='+')
self.dec_button = TimedButton(text='-')
self.inc_button.bind(on_press=self.on_increment_value)
self.inc_button.bind(on_time_slice=self.on_increment_value)
self.dec_button.bind(on_press=self.on_decrement_value)
self.dec_button.bind(on_time_slice=self.on_decrement_value)
self.buttons_vbox = BoxLayout(orientation='vertical',
size_hint_x=btn_size_hint_x)
self.buttons_vbox.add_widget(self.inc_button)
self.buttons_vbox.add_widget(self.dec_button)
self.add_widget(self.value_label)
self.add_widget(self.buttons_vbox)
def on_increment_value(self, btn_instance):
if float(self.value) + float(self.step) <= self.max_value:
self.value += self.step
def on_decrement_value(self, btn_instance):
if float(self.value) - float(self.step) >= self.min_value:
self.value -= self.step
def on_value(self, instance, value):
instance.value_label.text = str(value)
Actually the code I use is slightly different because I think it is ugly to subclass a layout to implement a widget and thus I subclassed Widget and added a horizontal BoxLayout as only children of the Widget, then I binded every size and position change to update the size and position of this child(see this question for why I had to do that).
The TimedButton is a subclass of Button that allows long-presses and, when long-pressed, emits a on_time_slice event every a certain amount of millisecond(thus the user will be able to hold the button to do a continuous increment). You can simply use a normal Button if you want, removing the binds to on_time_slice event.
The TimedButton source code is this:
class TimedButton(Button):
"""A simple ``Button`` subclass that produces an event at regular intervals
when pressed.
This class, when long-pressed, emits an ``on_time_slice`` event every
``time_slice`` milliseconds.
:param long_press_interval: Defines the minimum time required to consider
the press a long-press.
:type long_press_interval: int
:param time_slice: The number of milliseconds of each slice.
:type time_slice: int
"""
def __init__(self, long_press_interval=550, time_slice=225, **kwargs):
super(TimedButton, self).__init__(**kwargs)
self.long_press_interval = long_press_interval
self.time_slice = time_slice
self._touch_start = None
self._long_press_callback = None
self._slice_callback = None
self.register_event_type('on_time_slice')
self.register_event_type('on_long_press')
def on_state(self, instance, value):
if value == 'down':
start_time = time.time()
self._touch_start = start_time
def callback(dt):
self._check_long_press(dt)
Clock.schedule_once(callback, self.long_press_interval / 1000.0)
self._long_press_callback = callback
else:
end_time = time.time()
delta = (end_time - (self._touch_start or 0)) * 1000
Clock.unschedule(self._slice_callback)
# Fixes the bug of multiple presses causing fast increase
Clock.unschedule(self._long_press_callback)
if (self._long_press_callback is not None and
delta > self.long_press_interval):
self.dispatch('on_long_press')
self._touch_start = None
self._long_press_callback = self._slice_callback = None
def _check_long_press(self, dt):
delta = dt * 1000
if delta > self.long_press_interval and self.state == 'down':
self.dispatch('on_long_press')
self._long_press_callback = None
def slice_callback(dt):
self.dispatch('on_time_slice')
return self.state == 'down'
Clock.schedule_interval(slice_callback, self.time_slice / 1000.0)
self._slice_callback = slice_callback
def on_long_press(self):
pass
def on_time_slice(self):
pass
Note that I had to bind the state property instead of using on_touch_down and on_touch_up because they give some strange behaviour, and even when "working" there were some strange things happening by no reason(e.g. clicking the decrement button caused on_increment to be called even though the bindings where correct).
Edit: Updated the TimedButton class fixing a little bug(the previous implementation when clicked rapidly multiple times and then holding down the button would yield too many on_time_slice events: I'd forgot to "unschedule" the _long_press_callback when the state goes 'normal'

Make column width take up available space in wxPython ListCtrl

I have three columns in my wx.ListCtrl(size=(-1,200)). I would like the columns to fill up the width of the ListCtrl after its created. Ideally, the first column can expand to fill up the extra space available. The second and third columns don't need to expand, and preferably will not change in width (formatting ocd).
Currently, each ListCtrl column is set up using (width=-1).
I have a feeling I can use this section of the code to my advantage...
# Expand first column to fit longest entry item
list_ctrl.SetColumnWidth(0, wx.LIST_AUTOSIZE)
Pseudo-code (perhaps):
# After wx.ListCtrl creation
Get width of ListCtrl control
Get width of each ListCtrl column
Calculate unused width of ListCtrl
Set first column width to original width + unused width
Added:
Given the following example, I don't understand how to initiate the autowidthmixin. Currently, I am trying to put the listctrl inside a foldpanel. The foldpanel is a class and a function within the class creates the listctrl. I am not even confident that this can be done given the structure of my code at the moment!
class MyPanel(wx.Panel):
def __init__(self, parent, dictionary):
self.dictionary = dictionary
"""Constructor"""
wx.Panel.__init__(self, parent)
# Layout helpers (sizers) and content creation (setPanel)
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self.mainSizer)
list_ctrl = self.setPanel()
self.mainSizer.Add(list_ctrl, 0, wx.ALL | wx.EXPAND, 5)
self.GetSizer().SetSizeHints(self)
def setPanel(self):
index = 0
list_ctrl = wx.ListCtrl(self, size=(-1, 200),
style=wx.LC_REPORT | wx.BORDER_SUNKEN)
list_ctrl.InsertColumn(0, "Variable", format=wx.LIST_FORMAT_LEFT, width=-1)
list_ctrl.InsertColumn(1, "x", format=wx.LIST_FORMAT_RIGHT, width=-1)
list_ctrl.InsertColumn(2, u"\u03D0", format=wx.LIST_FORMAT_RIGHT, width=-1)
for key, value in self.dictionary.iteritems():
list_ctrl.InsertStringItem(index, str(key))
list_ctrl.SetStringItem(index, 1, ("%.2f" % value[0]))
list_ctrl.SetStringItem(index, 2, ("%.8f" % value[1]))
index += 1
list_ctrl.SetColumnWidth(0, wx.LIST_AUTOSIZE)
list_ctrl.SetColumnWidth(1, wx.LIST_AUTOSIZE)
list_ctrl.SetColumnWidth(2, wx.LIST_AUTOSIZE)
return list_ctrl
You need to use the ListCtrlAutoWidthMixin mixin class. The wxPython demo application has an example in the ListCtrl demo. According to the documentation, you can use its setResizeColumn method to tell it which column to resize. The default is the last column.
EDIT (07/05/2012): In your code, create a ListCtrl class similar to the one in the demo. It would look something like this:
class TestListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
def __init__(self, parent, ID, pos=wx.DefaultPosition,
size=wx.DefaultSize, style=0):
wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
listmix.ListCtrlAutoWidthMixin.__init__(self)
self.setResizeColumn(0)
Then when you instantiate it, you'd just call list_ctrl = TestListCtrl(arg1, arg2...argN)
Note that I included a call to setResizeColumn() in my code above. It's not tested, but it should work.

wxPython ListCtrl Column Ignores Specific Fields

I'm rewriting this post to clarify some things and provide a full class definition for the Virtual List I'm having trouble with. The class is defined like so:
from wx import ListCtrl, LC_REPORT, LC_VIRTUAL, LC_HRULES, LC_VRULES, \
EVT_LIST_COL_CLICK, EVT_LIST_CACHE_HINT, EVT_LIST_COL_RIGHT_CLICK, \
ImageList, IMAGE_LIST_SMALL, Menu, MenuItem, NewId, ITEM_CHECK, Frame, \
EVT_MENU
class VirtualList(ListCtrl):
def __init__(self, parent, datasource = None,
style = LC_REPORT | LC_VIRTUAL | LC_HRULES | LC_VRULES):
ListCtrl.__init__(self, parent, style = style)
self.columns = []
self.il = ImageList(16, 16)
self.Bind(EVT_LIST_CACHE_HINT, self.CheckCache)
self.Bind(EVT_LIST_COL_CLICK, self.OnSort)
if datasource is not None:
self.datasource = datasource
self.Bind(EVT_LIST_COL_RIGHT_CLICK, self.ShowAvailableColumns)
self.datasource.list = self
self.Populate()
def SetDatasource(self, datasource):
self.datasource = datasource
def CheckCache(self, event):
self.datasource.UpdateCache(event.GetCacheFrom(), event.GetCacheTo())
def OnGetItemText(self, item, col):
return self.datasource.GetItem(item, self.columns[col])
def OnGetItemImage(self, item):
return self.datasource.GetImg(item)
def OnSort(self, event):
self.datasource.SortByColumn(self.columns[event.Column])
self.Refresh()
def UpdateCount(self):
self.SetItemCount(self.datasource.GetCount())
def Populate(self):
self.UpdateCount()
self.datasource.MakeImgList(self.il)
self.SetImageList(self.il, IMAGE_LIST_SMALL)
self.ShowColumns()
def ShowColumns(self):
for col, (text, visible) in enumerate(self.datasource.GetColumnHeaders()):
if visible:
self.columns.append(text)
self.InsertColumn(col, text, width = -2)
def Filter(self, filter):
self.datasource.Filter(filter)
self.UpdateCount()
self.Refresh()
def ShowAvailableColumns(self, evt):
colMenu = Menu()
self.id2item = {}
for idx, (text, visible) in enumerate(self.datasource.columns):
id = NewId()
self.id2item[id] = (idx, visible, text)
item = MenuItem(colMenu, id, text, kind = ITEM_CHECK)
colMenu.AppendItem(item)
EVT_MENU(colMenu, id, self.ColumnToggle)
item.Check(visible)
Frame(self, -1).PopupMenu(colMenu)
colMenu.Destroy()
def ColumnToggle(self, evt):
toggled = self.id2item[evt.GetId()]
if toggled[1]:
idx = self.columns.index(toggled[2])
self.datasource.columns[toggled[0]] = (self.datasource.columns[toggled[0]][0], False)
self.DeleteColumn(idx)
self.columns.pop(idx)
else:
self.datasource.columns[toggled[0]] = (self.datasource.columns[toggled[0]][0], True)
idx = self.datasource.GetColumnHeaders().index((toggled[2], True))
self.columns.insert(idx, toggled[2])
self.InsertColumn(idx, toggled[2], width = -2)
self.datasource.SaveColumns()
I've added functions that allow for Column Toggling which facilitate my description of the issue I'm encountering. On the 3rd instance of this class in my application the Column at Index 1 will not display String values. Integer values are displayed properly. If I add print statements to my OnGetItemText method the values show up in my console properly. This behavior is not present in the first two instances of this class, and my class does not contain any type checking code with respect to value display.
It was suggested by someone on the wxPython users' group that I create a standalone sample that demonstrates this issue if I can. I'm working on that, but have not yet had time to create a sample that does not rely on database access. Any suggestions or advice would be most appreciated. I'm tearing my hair out on this one.
Are you building on the wxPython demo code for virtual list controls? There are a couple of bookkeeping things you need to do, like set the ItemCount property.
One comment about your OnGetItemText method: Since there's no other return statement, it will return None if data is None, so your test has no effect.
How about return data or "" instead?
There's a problem with the native object in Windows. If GetImg returns None instead of -1 the list has a problem with column 1 for some reason. That from Robin over on the Google Group post for this issue.

Categories

Resources