Re: Re: FloatSpin strange behavior?

Rolf <[email protected]>
Newsgroups gmane.comp.python.wxpython
Message-ID <[email protected]>
Not an answer per se, but I had real issues with the size of Gtk3
spinctrls and wrote mini-versions.

i.e. minifloatspin, minispinctrl and minispinbutton

I've attached the minifloatspin.py, try it and see if you get the same
problem. (or others)

run it with: python minifloatspin.py

The others were posted on the board a while back.

Regards,

RolfofSaxony


On 14/10/2020 23:14, [email protected] wrote:
> Sorry for resurrecting this very old thread, but I have the same
> issue, also on GTK. Are there any new insights to this problem?
>
> Juampy schrieb am Donnerstag, 29. März 2012 um 22:31:06 UTC+2:
>
>     The odd behavior is that it only happens around the "original
>     value". Lets say you set the value on 25 with increments of 1. If
>     you press the up arrow it will go: 25, 26, 27, 28, etc.. But if
>     you press the down arrow it will go:*25, 26 , 25* , 24,23,22, etc.
>     Another odd thing is that it repeats its behavior every time you
>     go through 25. For example, if you start on 25, and press the up
>     arrow until you reach 28 and then go down, it will go: 28, 27,
>     *26, 25, 26, 25*, 24, 23, 22, etc. 
>     It doesn't matter the increment of the arrow and/or the initial
>     value. If you start on 10, with 0.2 increments, it will go: *10,
>     10.2, 10,* 9.8,  9.6, etc.
>
>     I've also noticed that either reaching the max_val or min_val, or
>     doing a "press and hold" on the arrow (so it will increase the
>     value faster) makes the FloatSpin work as it should.
>
>     I really think that this is GTK related, but in any case if you
>     have a clue on how could I workaround this, would be really helpful.
>
>     Best Regards,
>
>
>     On Thursday, March 29, 2012 5:17:32 PM UTC-3, Juampy wrote:
>
>         Hi Andrea, 
>         I know this thread have quite some time, but I stomped into
>         this now. Is there any workaround to have it working on GTK?
>
>         By the way, thanks for such great job on the AGW library.
>
>         Regards,
>
>         On Tuesday, June 2, 2009 11:22:43 PM UTC-3, Carlos Grohmann
>         wrote:
>
>             Hello all,
>
>             I'm using Andrea's FloatSpin in an app, and it works fine
>             but there is
>             one small issue: if I set the initial value to, say, 3.5
>             (with steps
>             of 0.5), and the first thing I do when I run the pregram
>             is to
>             decrease that value, it will go from 3.5 to 4.0, then 3.5
>             and then
>             finally 3.0. If I run the program and try to increase the
>             values,it's
>             all good. Any hints?
>
>             cheers
>
> -- 
> You received this message because you are subscribed to the Google
> Groups "wxPython-users" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to [email protected]
> <mailto:[email protected]>.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/wxpython-users/d48d811c-0e90-4dbd-96b5-8920a3092209n%40googlegroups.com
> <https://groups.google.com/d/msgid/wxpython-users/d48d811c-0e90-4dbd-96b5-8920a3092209n%40googlegroups.com?utm_medium=email&utm_source=footer>.

-- 
You received this message because you are subscribed to the Google Groups "wxPython-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To view this discussion on the web visit https://groups.google.com/d/msgid/wxpython-users/7be7b0f6-9b2f-b9a1-b511-a0e967682c07%40gmail.com.
minifloatspin.py (text/x-python, 25.1 KB)
'''
    MiniFloatSpin.py

    A custom class that looks like the small gtk2 FloatSpin, not the monstrous version under gtk3.
    Based on TextCtrl with number format checking
    An attempt has been made to make it work with python2 and python3 - See the variable PY2
    Because the idea behind this SpinCtrl is to allow it to be really small, size it to cope
    with your Min/Max values or use the SetFontSize function.
    Accepts manual input from number keys and the numeric keypad.

    Styles: wx.TE_RIGHT (Default)
            wx.TE_LEFT
            wx.TE_CENTRE

            wx.BORDER_NONE is always applied

    Events: EVT_MINISPIN        A value change occurred in the spinctrl
                                A value was keyed in
                                A mouse click up or down
                                A mouse scroll up or down
                                An arrow-up or arrow-down

            EVT_MINISPINUP      A click up
                                A mouse scroll up
                                An arrow-up key

            EVT_MINISPINDOWN    A click down
                                A mouse scroll down
                                An arrow-down key
    Event Notes:
        All events return GetValue() i.e.

            def OnSpin(self,event):
                current_spin_value = event.GetValue()

        If monitoring SpinUp/SpinDown and Spin, SpinUp or SpinDown is the first event, followed by
        the Spin

        Entering values manually gives a Spin event for each Valid key press
        i.e.
            typing in 1.2 will give an event for 1 and another for 2
            With 1.5 in the control, using delete or backspace to remove the 5 or the 1
            will only give an event after pressing Return
            With 1.1 in the control using arrow keys to position the cursor between the
            ones and then pressing 2 (1.21) will fire an event on pressing 2 but the returned
            value will be the number in the control 1.21

        Pasting a value in sends a single Spin event

        Entering values manually WHEN Limited is set to True
            If the value becomes less than the Minimum value, Minimum value is applied
            If the value becomes greater than the Maximum value, Maximum value is applied

    Functions:
        GetValue()              Returns numeric value in the control

        GetMin()                Returns minimum value

        GetMax()                Returns maximum value

        GetRange()              Returns tuple (min, max) values

        GetIncrement()          Get the value used to increment the control per spin

        GetFontSize()           Get the Font size used for the control

        GetDigits()             Return the number of digits after the decimal point

        SetValue(int)           Set numeric value in the control

        SetMin(int)             Set minimum value - Automatically resets Range

        SetMax(int)             Set maximum value - Automatically resets Range

        SetRange(min,max)       Set minimum and maximum values

        SetIncrement(int)       Set the value to increment the control per spin

        SetFontSize()           Set the Font size used for the control

        SetLimited(Bool)        Set whether the control should limit the value to fall
                                within the current range
                                If False values not within current range are displayed in red

        IsLimited()             Is the control currently limiting the value to fall
                                within the current range - Returns True/False

        SetBackgroundColour(colour)

        SetForegroundColour(colour)

        Enable(Bool)            Enable/Disable the control
                                On Disable the control value is frozen

        IsEnabled()             Is the control Enabled - Returns True/False

        SetDigits()             Set the number of digits after the decimal point

    MiniFloatSpin is decimal point aware, in as much, as it is aware that in some locales
    the separator between the significant digits and the mantissa, is not always a decimal point.
    The current locales "decimal point" is used for display and input purposes and converted
    to a decimal point for calculation purposes.

    When incrementing/decrementing values the resultant float is always rounded to the number
    of set digits after the decimal point, to avoid numbers like 1.3000000003, interfering
    with tests on min and max values.

    Only Floating point decimal format is supported (%f).

    Default Values:
        min     -       0.0
        max     -       100.0
        initial -       0.1
        digits  -       2
        style   -       Align Right | Border None
        limited -       True
        range   -       (min, max)
        increment       0.1
        font size       SYS_SYSTEM_FONT Size

Author:     J Healey
Created:    31/08/2018
Copyright:  J Healey - 2018-2020
License:    GPL 2 or any later version
Email:      [email protected]

Usage example:

import wx
import minifloatspin as MFS
class Frame(wx.Frame):

    def __init__(self, parent):
        wx.Frame.__init__ (self, parent, -1, "Mini Float Spin")

        panel = wx.Panel(self, -1, size=(400,100))
        self.ctl = MFS.MiniFloatSpin(panel, -1, pos=(10,20), size=(35,20), min=0.0, max=9.99, initial=1.0, style= wx.TE_RIGHT, name="minifloatspin")
        self.ctl.Bind(MFS.EVT_MINISPIN, self.OnSpin)
        self.ctl.Bind(MFS.EVT_MINISPINUP, self.OnSpinUp)
        self.ctl.Bind(MFS.EVT_MINISPINDOWN, self.OnSpinDown)
        self.Show()

    def OnSpin(self, event):
        obj = event.GetEventObject()
        print ("Spin", obj.GetValue())

    def OnSpinUp(self, event):
        print ("Spin Up", event.GetValue())

    def OnSpinDown(self, event):
        print ("Spin Down")

app = wx.App()
frame = Frame(None)
app.MainLoop()
'''

import wx
from wx.lib.embeddedimage import PyEmbeddedImage
import sys
import locale
if sys.version_info.major == 2:
    PY2 = True
else:
    PY2 = False
spinupdown = PyEmbeddedImage(
    b'iVBORw0KGgoAAAANSUhEUgAAADIAAABkCAYAAADE6GNbAAAABmJLR0QA/wD/AP+gvaeTAAAA'
    b'CXBIWXMAAAPiAAAD4gHuD5mHAAAAB3RJTUUH4ggSCB03FPpcVwAAAbZJREFUeNrtmrtKA0EY'
    b'Rs+gnZekSBQEQd9DMIUYL6go6qvYW4qILyUaExVBsLG1slIQxUvWJtNEjGMyszMr34GPrXaZ'
    b's7P7Mzv7gxBCCCGEEEJ8Y+8/SOwAGbBVZIndjoTNPGCKJGCAUpeETakoMgYo/yBhUy3KjGS/'
    b'5LwIElcOIm3gMmWJo84gM0eZwxQlNh0FurOR0su92KeEzWzsSuZSoVwTtSxPepKwGYsl0vIs'
    b'chJDoulZwqaRp8R+IAmbtTwk1gNL2KyGlFjJScKm7LuSGWACeMlZ5A2o+JSZ7lw0ixRv3ESU'
    b'yIBr18emFxPAVAJLoXvgQdsfQgghhBBCCCGEEEKIsLjsdM8B75HHeAs8Dnohl26GkPkAZnzd'
    b'lVYkiSc8/yOZAl4jiNSNMd6f0wrwnKPEUsiXrp6TxGkeVWQ5sMRZP4Ma6uOcu86xFuhGTedd'
    b'3xueZ6JNxOYan2V5hIgdQlVPEispLHHGgc8BJLZTWq8t9CnRTHEBukbCLU1/mxpjDv4gMpz6'
    b'p8GFQ5kdpSCt5b1EahSoP36MiN1xoWWOi/wpbTu0W/9hX2ALIYQQQgghhBDp8wVxiX7Ava8A'
    b'egAAAABJRU5ErkJggg==')
spindown = PyEmbeddedImage(
    b'iVBORw0KGgoAAAANSUhEUgAAADIAAABkCAYAAADE6GNbAAAABmJLR0QA/wD/AP+gvaeTAAAA'
    b'CXBIWXMAAAPiAAAD4gHuD5mHAAAAB3RJTUUH4ggSCBk1npj4fwAAAiJJREFUeNrt2M+LEmEc'
    b'BvDvK1tYEJg/Rr0EeulsRsHqevHgdPLmWbqFYtBJQpK5CN6yf8DSgwPhqUNERy8aaOcFL0ER'
    b'1cWMCEV5uqwgtKvt6rgjPB+Y28w77zPvO+983xEhIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiKi'
    b'8wBgq76s649a04ASkaiI/D7j2qtWZxCRa0qp7roTD1Y9BaUU+v3+w2g0+ui0czRNE5fLZVmK'
    b'QCAg9Xr9iYh0T/qz2fQqlUpvT57OTg/TNF9ufZqbpvl5lyEajcZHS1768Xjs03X9yy5C6Lp+'
    b'DOC6ZYsOgGg8Hrc0xOHhIQDcsnw5nkwmD9xu99yKEJqm/QFwz/Llf9H4YDB4ZkWQXq+X2/lH'
    b'qtPpvHA4HFsJEAqF0O12yzsPMZvNBMBBLpd7t40g2Wz2NQDHpVUTAG5UKpXjiwZwOp0wDOMD'
    b'gI0rBMcm74tS6lcmk7mbTCZ/XKSNRCLxyev1JpRS00ut7RY3BxCLRCLnGo10Oj2bTqcR21XL'
    b'tVrtKBwO/1cIj8eD4XB4x3Yh6vW6iIi0Wq2nwWBwZQi/349ms/nYbluFf1Sr1VdnhfD5fBiN'
    b'Rs9tvxkrFApKRMQwjPfLAZRSEBGUy+U3S/sc++8qATjz+fzP5TDtdvsbgCu2n1IL8/l8EcYX'
    b'i8W+iwhSqdRXADf3JsQpI3NULBYB4P6iItjbnxcAbu/dSKwIw99SRERERERb8xeP3+hzsiuu'
    b'uQAAAABJRU5ErkJggg==')
spinup = PyEmbeddedImage(
    b'iVBORw0KGgoAAAANSUhEUgAAADIAAABkCAYAAADE6GNbAAAABmJLR0QA/wD/AP+gvaeTAAAA'
    b'CXBIWXMAAAPiAAAD4gHuD5mHAAAAB3RJTUUH4ggSCAYkOXLWEwAAAipJREFUeNrt2M+LEmEc'
    b'BvDvK9uyBQumqHgp9NLZhALdPQk6nfwT8q7YVUISj57KQK+WHhTDU4eI8KIXhTQ6Bgodimjx'
    b'YEKEMvJ0acCW3DZz/AHPBx68zAzz8H3fmUERIiIiIiJaAwC//e57iVt7W2ahxGkqlQKAuyIi'
    b'uq7vT4n5fG6UcASDwTMRQSQS+QLg+t5MZmESR4lE4puIwEij0fgK4MpelEkmk0pEJJvNvlks'
    b'oZSCiCCTybz8VUTt/FRyudzzxRKLcTgcGI/HT3b25kulkoiIVKvVh263G8uKiAhcLhcqlcqD'
    b'nV1i+Xz+1Ov1XljCiN1ux2AwuL2Lmzvo8/kuVcJINBrVZ7OZb2dKDAaD41AodPYvJYyEw+GP'
    b'hULhaB3LzLLqiUopAXBcr9ffNptNxyrXaLVaN0ejUQvAoVJbeJDpui4ADuLx+OtVJnE+sVjs'
    b'BQDLVjZ/u91+arFYsI4iHo8HnU4ns/F90e/3H62jwPl0u934xkpMp9N7NpttbkYRp9P5A8Ad'
    b'098xAPwnJycwo4SRQCAAADdMm8RkMnFomvbZzBJGNE37AOCaKZOp1WqfNlHCSLlcfrf2aaTT'
    b'6VebLGGkVqs9u+xUDi4qoZSSXq9X9Pv92p+OcTqdYrVaTduTxWLx/nA4fK+Uemzcz9IX9F8m'
    b'okTELyLfl5x7aPbDUkSuKqU6/72sduXjdK//iSEiIiIiIiIiIiIiIiIiIiIiIiIiIiIiItqe'
    b'nwRG71f6w806AAAAAElFTkSuQmCC')
spindisabled = PyEmbeddedImage(
    b'iVBORw0KGgoAAAANSUhEUgAAAJUAAAEqCAYAAAAcSRJbAAAABHNCSVQICAgIfAhkiAAAAAlw'
    b'SFlzAAAD4gAAA+IB7g+ZhwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoA'
    b'AADDSURBVHic7cExAQAAAMKg9U9tDQ+gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgytxAAARuD4uMAAAAASUVORK5C'
    b'YII=')

getspinupdownImage = spinupdown.GetImage
getspindownImage = spindown.GetImage
getspinupImage = spinup.GetImage
getspindisabledImage = spindisabled.GetImage

mfsEVT_MINISPINCTRL = wx.NewEventType()
EVT_MINISPIN = wx.PyEventBinder(mfsEVT_MINISPINCTRL, 1)
mfsEVT_MINISPINUP = wx.NewEventType()
EVT_MINISPINUP = wx.PyEventBinder(mfsEVT_MINISPINUP, 1)
mfsEVT_MINISPINDOWN = wx.NewEventType()
EVT_MINISPINDOWN = wx.PyEventBinder(mfsEVT_MINISPINDOWN, 1)

class SpinEvent(wx.PyCommandEvent):
    """ Event sent from the :class:`MiniFloatSpin` when a spin value changes. """

    def __init__(self, eventType, eventId=1, value=0):
        """
        Default class constructor.

        :param `eventType`: the event type;
        :param `eventId`: the event identifier.
        """

        wx.PyCommandEvent.__init__(self, eventType, eventId)
        self._eventType = eventType
        self.value = value

    def GetValue(self):
        """
        Retrieve the value of the control at the time
        this event was generated."""
        return self.value

class MiniFloatSpin(wx.Control):

    def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, min=0.0, max=100.0, initial=0.1, style=wx.BORDER_SIMPLE, name="MiniSpinCtrl"):
        """
        Default class constructor.

        @param parent:  Parent window. Must not be None.
        @param id:      identifier. A value of -1 indicates a default value.
        @param pos:     MiniSpinCtrl position. If the position (-1, -1) is specified
                        then a default position is chosen.
        @param size:    If the default size (-1, -1) is specified then a default size is chosen.
        @param min:     Minimum allowed value.
        @param max:     Maximum allowed value.
        @param initial: Initial value.
        @param style:   Alignment (Left,Middle,Right).
        @param name:    Widget name.
        """

        wx.Control.__init__(self, parent, id, pos=pos, size=size, name=name, style=style)

        self._min = float(min)
        self._max = float(max)
        self._digits = 2
        self._initial = float(initial)
        self._pos = pos
        self._size = size
        self._name = name
        self._limited = True
        self._id = id
        self._frozen_value = "0.0"
        self._fgcolour = wx.BLACK
        self._bgcolour = wx.WHITE
        self._outrange_colour = wx.RED
        self._increment = 0.1
        self._font = wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT)

        if self._limited:
            if self._initial > self._max:
                self._initial = self._max
            if self._initial < self._min:
                self._initial = self._min

        self.SetForegroundColour(self._fgcolour)
        self.SetBackgroundColour(self._bgcolour)
        self.SetWindowStyle(wx.BORDER_NONE)
        self._style = style

        txtstyle = wx.TE_NOHIDESEL | wx.TE_PROCESS_ENTER

        if style & wx.TE_LEFT or style == wx.TE_LEFT:
            txtstyle = txtstyle | wx.TE_LEFT
        elif style & wx.TE_CENTRE:
            txtstyle = txtstyle | wx.TE_CENTER
        else:
            txtstyle = txtstyle | wx.TE_RIGHT
        if style & wx.TE_READONLY:
            txtstyle = txtstyle | wx.TE_READONLY

        # Test decimal point is comma # locale.setlocale(locale.LC_ALL, 'es_ES.utf8')
        self._decimal = locale.localeconv()["decimal_point"]
        self._formatstring = '%.'+str(self._digits)+'f'
        disp_initial = self._formatstring % self._initial

        # Initialize images
        self.InitialiseBitmaps()

        #MinifloatSpin

        self.ctl = wx.TextCtrl(self, id, value=disp_initial, \
            pos=self._pos, size=self._size, style=txtstyle, name=self._name)
        if PY2:
            self.spinner = wx.StaticBitmap(self, -1, bitmap=wx.BitmapFromImage(self._img))
        else:
            self.spinner = wx.StaticBitmap(self, -1, bitmap=wx.Bitmap(self._img))

        #End

        # Bind the events
        self.Bind(wx.EVT_MOUSEWHEEL, self.OnScroll)
        self.spinner.Bind(wx.EVT_LEFT_DOWN, self.OnSpin)
        self.ctl.Bind(wx.EVT_CHAR_HOOK, self.OnChar)
        self.Bind(wx.EVT_PAINT, self.OnPaint)

        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(self.ctl, 1, wx.EXPAND, 0)
        sizer.Add(self.spinner, 0, wx.ALIGN_CENTER_VERTICAL)
        self.SetImage(self._initial)
        self.SetSizerAndFit(sizer)
        self.Show()

    def OnPaint(self,event):
        if self.IsInRange():
            self.ctl.SetForegroundColour(self.GetForegroundColour())
        else:
            self.ctl.SetForegroundColour(self._outrange_colour)
        self.ctl.SetBackgroundColour(self.GetBackgroundColour())
        self.spinner.SetBackgroundColour(self.GetBackgroundColour())

    def InitialiseBitmaps(self):
        self._imgup = self.SetImageSize(getspinupImage())
        self._imgdown = self.SetImageSize(getspindownImage())
        self._imgupdown = self.SetImageSize(getspinupdownImage())
        self._imgdisabled = self.SetImageSize(getspindisabledImage())
        if self._initial <= self._min:
            self._img = self._imgup
        elif self._initial >= self._max:
            self._img = self._imgdown
        else:
            self._img = self._imgupdown

    def SetImageSize(self,img):
        #Size the image as Full height and the width is half the height
        h = self.GetSize()[1]
        img = img.Scale(int(h/2),int(h),quality=wx.IMAGE_QUALITY_HIGH)
        return img

    def SetValue(self,value):
        value = float(value)
        upd_value = self._formatstring % value
        upd_value = upd_value.replace(".", self._decimal)
        self.ctl.SetValue(upd_value)
        self.SetImage(value)
        self.Update()

    def GetValue(self):
        value = self.ctl.GetValue()
        value = value.replace(self._decimal, ".")
        try:
            value = float(value)
        except:
            value = 0.0
        return value

    def SetMin(self,value):
        self._min = float(value)

    def GetMin(self):
        return self._min

    def SetMax(self,value):
        self._max = float(value)

    def GetMax(self):
        return self._max

    def GetDigits(self):
        return self._digits

    def SetDigits(self,value):
        self._digits = value
        self._formatstring = '%.'+str(self._digits)+'f'
        value = self.ctl.GetValue()
        if value == "":
            value = "0.0"
        value = float(value)
        upd_value = self._formatstring % value
        upd_value = upd_value.replace(".", self._decimal)
        self.ctl.SetValue(str(upd_value))

    def SetLimited(self,value):
        self._limited = value

    def SetRange(self,min,max):
        self._min = float(min)
        self._max = float(max)

    def GetRange(self):
        return self._min, self._max

    def IsLimited(self):
        return self._limited

    def IsEnabled(self):
        return wx.Control.IsEnabled(self)

    def Enable(self, value):
        if value and self.IsEnabled(): # If value = current state do nothing
            return
        if not value and not self.IsEnabled():
            return
        wx.Control.Enable(self, value)
        self.Update()
        if value:
            #Enable via callafter in case someone has been scrolling away on the disabled control
            wx.CallAfter(self.OnReset)
        else:
            #Disable - Freeze the controls Value and change bitmap
            self._frozen_value = self.ctl.GetValue()
            self._img = self._imgdisabled
            if PY2:
                self.spinner.SetBitmap(wx.BitmapFromImage(self._img))
            else:
                self.spinner.SetBitmap(wx.Bitmap(self._img))

    def OnReset(self):
        #Reset the control to the state it was in when it was Disabled
        self.ctl.SetValue(self._frozen_value)
        self.SetImage(float(self._frozen_value))

    def IsInRange(self):
        if self._min <= self.GetValue() <= self._max:
            return True
        else:
            return False

    def SetIncrement(self,value):
        self._increment = value

    def GetIncrement(self):
        return self._increment

    def SetFontSize(self,value):
        self._font.SetPointSize(value)
        self.ctl.SetFont(self._font)

    def GetFontSize(self):
        return self._font.GetPointSize()

    #Spin image clicked (Top half = Up | Bottom half = Down)
    def OnSpin(self, event):
        H = self.ctl.GetSize()[1] / 2
        pos = event.GetY()
        if pos < H:
            self.OnScroll(None, self._increment)
        else:
            self.OnScroll(None, -self._increment)

    #Keyboard input, test for Arrow Up / Arrow down
    def OnChar(self, event):
        obj = event.GetEventObject()
        pos = obj.GetInsertionPoint()
        curr_text = obj.GetValue()
        curr_text = curr_text.replace(self._decimal,".")
        key = event.GetUnicodeKey()
        if key == wx.WXK_NONE:
            key = event.GetKeyCode()

        #Test for and Swap out numeric pad keys for standard 0-9 values
        if key == wx.WXK_NUMPAD0:
            key = 48
        elif key == wx.WXK_NUMPAD1:
            key = 49
        elif key == wx.WXK_NUMPAD2:
            key = 50
        elif key == wx.WXK_NUMPAD3:
            key = 51
        elif key == wx.WXK_NUMPAD4:
            key = 52
        elif key == wx.WXK_NUMPAD5:
            key = 53
        elif key == wx.WXK_NUMPAD6:
            key = 54
        elif key == wx.WXK_NUMPAD7:
            key = 55
        elif key == wx.WXK_NUMPAD8:
            key = 56
        elif key == wx.WXK_NUMPAD9:
            key = 57
        else:
            pass

        #Test for position keys
        if key == wx.WXK_UP:
            self.OnScroll(None, self._increment)
        elif key == wx.WXK_DOWN:
            self.OnScroll(None, -self._increment)
        elif key == wx.WXK_LEFT:
            event.Skip()
        elif key == wx.WXK_RIGHT:
            event.Skip()
        elif key == wx.WXK_BACK:
            event.Skip()
        elif key == wx.WXK_DELETE:
            event.Skip()
        elif key == wx.WXK_RETURN:
            new_text = curr_text
            if new_text == "":
                new_text = "0.0"
            #Test for beyond range and limited display
            if float(new_text) > self._max and self._limited:
                new_text = self._formatstring % self._max

            if float(new_text) < self._min and self._limited:
                new_text = self._formatstring % self._min

            new_text = self._formatstring % float(new_text)
            disp_text = new_text.replace(".", self._decimal)


            self.ctl.SetValue(disp_text)
            if len(new_text) > 0: #Avoid Return on empty text
                event = SpinEvent(mfsEVT_MINISPINCTRL, self.GetId(),float(new_text))
                event.SetEventObject(self)
                self.GetEventHandler().ProcessEvent(event)
                self.SetImage(float(new_text))
            event.Skip()

        #Test for Minus character at first position
        elif chr(key) == "-" and pos == 0:
            event.Skip()

        #Test for decimal divider character
        elif chr(key) == self._decimal and curr_text.count(self._decimal) == 0:
            event.Skip()


        #Test for Valid numeric input
        elif chr(key).isdigit():
            #Test for nothing in curr_text
            if len(curr_text) < 1:
                check_text = "0.0"
            else:
                check_text = curr_text

            #create replacement text comprising old text and the new character
            # Allowing for the insertion point being changed by the keys
            # Delete, Backspace and Left, Right arrow keys
            new_text = curr_text[:pos]+chr(key)+curr_text[pos:]

            #Test for beyond range and limited display
            if float(new_text) > self._max and self._limited:
                new_text = self._formatstring % self._max
            if float(new_text) < self._min and self._limited:
                new_text = self._formatstring % self._min

            disp_text = new_text.replace(".", self._decimal)

            self.ctl.SetValue(disp_text)
            self.ctl.SetInsertionPoint(len(new_text))
            event = SpinEvent(mfsEVT_MINISPINCTRL, self.GetId(),float(new_text))
            event.SetEventObject(self)
            self.GetEventHandler().ProcessEvent(event)
            self.SetImage(float(new_text))
            event.Skip()
        else:
            pass

    #Mouse scroll: check rotation for direction
    #Check for non event override value in spin
    def OnScroll(self, event=None, spin=None):
        value = self.ctl.GetValue()
        value = value.replace(self._decimal,".")
        if value == "":
            value = "0.0"
        value = float(value)
        if event:
            if event.GetWheelRotation() > 0:
                rotation = self._increment
            else:
                rotation = -self._increment
        else:
            rotation = spin
        adj=True
        #All values are added, because negative rotation or spin are already negative values
        if rotation > 0:
            if round(value + rotation,self._digits) <= self._max and self._limited == True:
                value += rotation
            elif self._limited == False:
                value += rotation
            else:
                adj=False
            value = round(value,self._digits)

            #fire SpinUp event if the adjustment value is not zero
            if adj:
                upd_value = self._formatstring % value
                upd_value = upd_value.replace(".", self._decimal)
                self.ctl.SetValue(str(upd_value))
                event = SpinEvent(mfsEVT_MINISPINUP, self.GetId(), value)
                event.SetEventObject(self)
                self.GetEventHandler().ProcessEvent(event)
                event = SpinEvent(mfsEVT_MINISPINCTRL, self.GetId(), value)
                event.SetEventObject(self)
                self.GetEventHandler().ProcessEvent(event)

        elif rotation < 0:
            if round(value + rotation,self._digits) >= self._min and self._limited == True:
                value += rotation
            elif self._limited == False:
                value += rotation
            else:
                adj=False
            value = round(value,self._digits)

            #fire SpinDown event if the adjustment value is not zero
            if adj:
                upd_value = self._formatstring % value
                upd_value = upd_value.replace(".", self._decimal)
                self.ctl.SetValue(str(upd_value))
                event = SpinEvent(mfsEVT_MINISPINDOWN, self.GetId(), value)
                event.SetEventObject(self)
                self.GetEventHandler().ProcessEvent(event)
                event = SpinEvent(mfsEVT_MINISPINCTRL, self.GetId(), value)
                event.SetEventObject(self)
                self.GetEventHandler().ProcessEvent(event)
        else:
            #No rotation
            pass

        self.SetImage(value)

    def SetImage(self, value):
        #Set appropriate image
        if value <= self._min:
            self._img = self._imgup
        elif value >= self._max:
            self._img = self._imgdown
        else:
            self._img = self._imgupdown
        if PY2:
            self.spinner.SetBitmap(wx.BitmapFromImage(self._img))
        else:
            self.spinner.SetBitmap(wx.Bitmap(self._img))
        self.Layout()

if __name__ == '__main__':

    import wx

    class MyFrame(wx.Frame):

        def __init__(self, parent):

            wx.Frame.__init__(self, parent, -1, "Minifloatspin Demo")

            panel = wx.Panel(self)

            floatspin = MiniFloatSpin(panel, -1, pos=(50, 50), min=0, max=1,
                                  initial=0.1, style=0)
            floatspin.SetIncrement(0.01)
            floatspin.SetDigits(2)


    # our normal wxApp-derived class, as usual

    app = wx.App(0)

    frame = MyFrame(None)
    app.SetTopWindow(frame)
    frame.Show()

    app.MainLoop()
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.