Spin-Button for Doubles

Date: 17 August 1999
Author: Michael Mogensen
Version: 2.2

Download files from here

In most cases perhaps we can live with the annoying int-limitation of TUpDown's but how to spin a [-pi; pi]- or [0.001; 0.01]-interval then, without going bananas in all kinds of string-conversions and following application overheat somewhere along the line? This is where TSpinButton comes in. It offers you to go from any double, dBottom, to any (bigger) double, dTop, stepping dStep's each time and starting from dCurrent - all set from the constructor.

How it works

Check out the interface yourself, but straight ahead my construction is this -

   // Defines a range to work with. Container of all doubles.
   class TRange { /* Data and methods */ };
   
   // Same as a TUpDown buddy.
   class TSpinEdit : public TEdit { /* Data and methods */ };
   
   // The one to 'do' it with.
   class TSpinScrollBar : public TScrollBar { /* Data and methods */ };
   
   // The control itself.
   class TSpinButton { /* Data and methods */ };

The TSpinButton owns a pointer to a TSpinScrollBar obj. and the TSpinScrollBar owns a pointer to a TSpinEdit obj. and the TSpinEdit instance owns a pointer to a TRange obj.

The pSpinScrollBar in TSpinButton is protected and the pSpinEdit and the pRange is public to gain access from the pSpinScrollBar "inside the spin-class" without methods. My class supports the normal vertical spinner (arrows up/down), the one you use in 95% of dialogs, but also enables you to have horizontals ditto (arrows left/right) - in other words TSpinScrollBar responds to WM_VSCROLL and WM_HSCROLL messages. When a WM_VSCROLL message is responded to, the...

   void TSpinScrollBar::EvVScroll(UINT uScrollCode, UINT uThumbPosition, HWND hWindow)
   // Respond to up/down events (for normal spinner look).
   {
      TScrollBar::EvVScroll(uScrollCode, uThumbPosition, hWindow);
      switch(uScrollCode)
      {
         case SB_LINEUP: (*pSpinEdit)++; return;
         case SB_LINEDOWN: (*pSpinEdit)--; return;
      }
   }

...is doing all the work for us - hidden away simple and fast!

How To Use

In the dialog resource you *must* separate your id's by two in this way because this the way the class expects to find them:

   #define ID_DIALOG_DBLSPIN 1000 // id of spinner.

   CONTROL "", ID_DIALOG_DBLSPIN - 1, "edit", ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 54, 60, 30, 12
   CONTROL "", ID_DIALOG_DBLSPIN + 1, "scrollbar", SBS_VERT | WS_CHILD | WS_VISIBLE, 45, 60, 10, 12

Note: Put physical edit and scrollbar side by side and make scrollbar compact without any separation of its arrows to have it look like a "real" TUpDown. Pay attention to the 'id - 1' and 'id + 1'.

Also remember to include its menu in your rc-file:

   #include "sb.rc"

To allocate one from a TMyDialog::SetupWindow() you normally just go:

  psb = new TSpinButton(
    this,
    ID_DIALOG_DBLSPIN,
    -PI,
    0.0,
    PI,
    PI / 360,
    true,
    true,
    3,
    "rad",
    ",");

... or whatever you want, using the constructor ...

  TSpinButton(
    // Parent pointer.
    TWindow*,
    // Control id.
    const int,
    // From this.
    const double dBottomEx = I_DBottom,
    // Begin at.
    const double dCurrentEx = I_DCurrent,
    // To this.
    const double dTopEx = I_DTop,
    // Increase with.
    const double dStepEx = I_DStep,
    // Show dialog box on invalid input or not.
    const bool bErrorBoxEx = true,
    // Show menu above scroller or not.
    const bool bMenuEx = true,
    // Number of digits after separator.
    const int iPrecisionEx = I_IPrecision,
    // Unit.
    const string sDimensionEx = I_SDimension,
    // Separator to use.
    const string sSeparatorEx = I_SSeparator,
    // When converting floating point number to a string.
    const int iSignificantEx = I_ISignificant,
    // Valid input for edit.
    const string sEditEx = I_SEdit);

Features

Version 1.0
Vertical and horizontal arrows and forced zero touchdown if 0.0 is in the interval (see inline const void TouchZero(const bool bTouchZeroEx) found in sb.h) to avoid wrong stepping if top or bottom has been reached "inside a step". Notice TRange::Normalize() to fix ill formed range.

Version 2.0 (this version)
Dimension (like: 122.5 gallon, 136.9 $ and 45.23 g/sqrm).
Separator (like: 254,34, 23|23, 67*23, 78'32).
Fixed width (like: 23,500, 456,1000, 523,00000, 73,000, pad zeros).
Extensive error correction (like: 1 3,,,5 -> 13,5, ...6,,9 -> 6,9).
Popup menu (Top|Middle|Bottom|Current-menu above arrows).
Error dialog when nonsense are written.
etc.

Now the spinner is very tolerant to what the user writes because it's interpreting a string-version of the edit. It's always possible to write digits (0,... 9), letters of the dimension (if used), letter(s) of the separator (if used), spaces (" "), signs ("-") and nothing. Rules are:

  1. Assume a double is D.d. If one or more digit(s) are present in the string the D-part of the double, by definition, are appended of digit(s) found in string when reading L -> R. SpinTo(...) stops forming the D-part when it sees the first separator (if present). Then the d-part is formed in the same way and stops when reaching the end of the string.

    Ex. All these are -10.00: "-10 , 00", "- 10-- --:00 cm" and "10, cm 0-".

  2. If one or more sign(s) are present in the string the number (if any), by definition, is negative.

    Ex. All these are -10.00: "-10,00", "-10----:00 cm" and "10, cm 0-".

  3. If one or more separator(s) are present in the string the number (if any), by definition, is a floating-point number.

    Ex. All these are -10.00: "-10 $ , 00", "-10 --,,--'00 m" and "10. . 0-.".

  4. If one or more space(s) are present in the string they are ignored.

    Ex. All these are -10.00: "-10 $ , 00", "-10 - -,,- -'00 m" and "10. 0-.".

  5. If one or more letter(s) (of the dimension) are present in the string they are ignored.

    Ex. All these are -10.00: "-10 $ $ , 00", "-$10 --$,,--'00 g/m" and "10.$0-.".

  6. Valid string has the generic format:
       "
       [space][sign][letter][separator] // One or more and on order.
       digit
       [space][sign][letter][separator] // One or more and on order.
       digit
       [space][sign][letter][separator] // One or more and on order.
       " 
    

For details on this; check: const void TSpinEdit::SpinTo(double dCurrentNew, const bool bRead, const bool bUpDown) found in sb.cpp and IdleAction(...) same place.