Click here to Skip to main content
15,896,111 members
Articles / Desktop Programming / Windows Forms

Numeric UpDown Control for Hardware Control Applications or Yet Another SpinEdit

Rate me:
Please Sign up or sign in to vote.
4.55/5 (9 votes)
18 Jun 2008CPOL4 min read 43.8K   942   25  
An article on numeric Up/Down control with some unique features useful for hardware control applications
//=================================================================//
// Copyright (C) 2008. Igor Voynovskyy. (igor.voynovskyy@gmail.com)
//=================================================================//

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace IgNS.Controls
{
    /// <summary>
    /// SpinEdit class with some extra features such as extra up/down for instant increment 
    /// adjustment; late update of the value thru external code, support for power 2 increment;
    /// adjustable format including hex and more.
    /// </summary>
    /// <remarks>
    /// Please pay attention for properties adjustment because some of them related and
    /// for flawless operation they should be set with understanding. Just apply common sense.
    /// </remarks>
    [DefaultEvent("")]
    [DefaultProperty("Value")]
    public partial class IgSpinEdit : UserControl, ISupportInitialize
    {
        public IgSpinEdit()
        {
            InitializeComponent();

            valAsInt = true;
            hint.SetToolTip(btnIncr, string.Format("Increment: {0}", incr));
        }

        private void edVal_SizeChanged(object sender, EventArgs e)
        {
            if (BorderStyle == BorderStyle.None)
                Height = edVal.Height + 4;
            else if (BorderStyle == BorderStyle.FixedSingle)
                Height = edVal.Height + 6;
            else Height = edVal.Height + 8;
        }

        #region ISupportInitialize Members
        bool initializing;
        public void BeginInit()
        {
            initializing = true;
        }
        public void EndInit()
        {
            initializing = false;
            Value = val; // to correct possibly erroneous display of value caused by ValAsInt set during construction
        }
        #endregion

        /// <summary>
        /// Allows to set tooptip message to be shown when mouse hover over control.
        /// </summary>
        /// <param name="caption">String to be shown in tooltip.</param>
        public void SetToolTip(string caption)
        {
            hint.SetToolTip(edVal, caption);
        }

        //== Format string =====
        string formatStr = "{0}";
        /// <summary>
        /// Format string adherent to the same conventions as used for standard Format().
        /// For displaying value as hex ValueAsHex should be set and hex format string used,
        /// e.g. "0x{0:X4}".
        /// </summary>
        [Description("Format string which controls how value is displayed.")]
        [Category("Appearance")]
        public string FormatString
        {
            get { return formatStr; }
            set {
                try
                {
                    if (valAsInt) 
                        edVal.Text = string.Format(value, (long)val);
                    else edVal.Text = string.Format(value, val);
                    formatStr = value;
                }
                catch { 
                    formatStr = "{0}";
                    edVal.Text = string.Format(formatStr, val);
                }
            }
        }
        /// <summary>
        /// Controls if extra increment up/down is shown. 
        /// If Power2Increament is set IncreamentVisible is reset.
        /// </summary>
        [Description("Controls if extra increment up/down is shown.")]
        [Category("Appearance")]
        public bool IncrementVisible
        {
            get { return btnIncr.Visible; }
            set { btnIncr.Visible = value; }
        }
        /// <summary>
        /// Back color of the edit portion of the control.
        /// </summary>
        [Description("Sets back color of the edit portion of the control.")]
        [Category("Appearance")]
        public Color ValueBackColor
        {
            get { return edVal.BackColor; }
            set { edVal.BackColor = value; }
        }
        protected override void OnForeColorChanged(EventArgs e)
        {
            edVal.ForeColor = this.ForeColor;
            base.OnForeColorChanged(e);
        } 
        /// <summary>
        /// Indicates how value should be aligned inside control.
        /// </summary>
        [Description("Indicates how value should be aligned inside control.")]
        [Category("Appearance")]
        public HorizontalAlignment TextAlign
        {
            get { return edVal.TextAlign; }
            set { edVal.TextAlign = value; }
        }

        bool extUpdate = false;
        bool pow2Incr = false;
        bool valAsHex = false;
        bool valAsInt = false;

        /// <summary>
        /// Enables to implement late uprate of the value. If this property is set and ValueChanging
        /// is assigned, the new value is passed to event handler, but Value is not updated until it is
        /// done by explicitly assigning Value from outside of the control. This is useful when new value is 
        /// send to remote destination, but it may be set or not or set to different value and this actual
        /// value returned from remote point is then assigned to the Value property of the control. 
        /// </summary>
        [Description("Enables to implement late (external) uprate of the value.")]
        [Category("Behavior")]
        public bool ExternalUpdate
        {
            get { return extUpdate; }
            set { extUpdate = value; }
        }
        /// <summary>
        /// Allows to implement increment scheme where next value is twice as higher or twice as lower 
        /// as previous. Most useful to generate 1, 2, 4, 8, ..., 2^n sequence. But not only.
        /// </summary>
        [Description("Allows to implement increment scheme where next value is twice as higher or twice as lower as previous.")]
        [Category("Behavior")]
        public bool Pow2Increment
        {
            get { return pow2Incr; }
            set { 
                pow2Incr = value;

                if (pow2Incr && val == 0) Value = 1.0;

                IncrementVisible = !value; 
            }
        }
        /// <summary>
        /// Allows to display value as integer. Does not influence internal value format which is double.
        /// </summary>
        [Description("Allows to display value as integer.")]
        [Category("Behavior")]
        public bool ValueAsInt
        {
            get { return valAsInt; }
            set { valAsInt = value; }
        }
        /// <summary>
        /// Along with proper hex format string allows to display Value as hex. Also sets the increment 
        /// increase/decrease multiplier to 16.
        /// </summary>
        [Description("Along with proper hex format string allows to display Value as hex. Also sets the increment increase/decrease multiplier to 16.")]
        [Category("Behavior")]
        public bool ValueAsHex
        {
            get { return valAsHex; }
            set {
                ValueAsInt = true;
                valAsHex = value; 
            }
        }

        //== Value change =====
        double val = 0.0;
        double valMax = 100.0;
        double valMin = 0.0;

        double incr = 1.0;
        double incrMax = 10.0;
        double incrMin = 0.1;

        /// <summary>
        /// Called before new value has been changed. Allows to cancel the value change or implement 
        /// late update along with ExternalUpdate property.
        /// </summary>
        [Description("The event is raised before new value has been changed. Allows to cancel the value change or implement "+ 
        "late update along with ExternalUpdate property.")]
        [Category("Behavior")]
        public event IgSpinEditChanged ValueChanging;
        /// <summary>
        /// Called when new value has been set.
        /// </summary>
        [Description("The event is raised when new value has been set.")]
        [Category("Behavior")]
        public event EventHandler ValueChanged;

        IgSpinEditChangedArgs arg = new IgSpinEditChangedArgs(0);
        decimal lastUpDownVal = 0;
        //-------------------------
        void btnVal_ValueChanged(object sender, EventArgs e)
        {
            double locVal = val;
            if (btnVal.Value > lastUpDownVal)
            {
                if (pow2Incr) locVal *= 2;
                else locVal += incr;
            }
            else
            {
                if (pow2Incr) locVal /= 2;
                else locVal -= incr;
            }
            lastUpDownVal = btnVal.Value;

            if (ValueChanging != null)
            {
                if (locVal < valMin) locVal = valMin;
                if (locVal > valMax) locVal = valMax;

                arg.Value = locVal;
                ValueChanging(this, arg);

                if (arg.Cancel)
                    return;
                if (extUpdate) return;
            }
            Value = locVal;
        }
        //-------------------------
        /// <summary>
        /// Maximum for the value.
        /// </summary>
        [Description("Maximum for the Value.")]
        [Category("Behavior")]
        public double ValueMax
        {
            get { return valMax; }
            set
            {
                if (initializing)
                {
                    valMax = value;
                    return;
                }
                if (value == valMax) return;
                if (value < valMin) value = valMin;
                valMax = value;
                if (val > value) Value = value;
            }
        }
        //-------------------------
        /// <summary>
        /// Minimum for the Value.
        /// </summary>
        [Description("Minimum for the Value.")]
        [Category("Behavior")]
        public double ValueMin
        {
            get { return valMin; }
            set
            {
                if (initializing)
                {
                    valMin = value;
                    return;
                }
                if (value == valMin) return;
                if (value > valMax) value = valMax;
                valMin = value;
                if (val < value) Value = value;
            }
        }
        //-------------------------
        /// <summary>
        /// Represent value controlled by this SpinEdit.
        /// </summary>
        [Description("Represent value controlled by this SpinEdit.")]
        [Category("Behavior")]
        public double Value
        {
            get { return val; }
            set
            {
                if ( ! initializing)
                {
                    //if (value = val) return;
                    if (value < valMin) value = valMin;
                    if (value > valMax) value = valMax;

                    if (pow2Incr && value == 0) value = 1;
                }
                val = value;

                try
                {
                    if (valAsInt)
                        edVal.Text = string.Format(formatStr, (long)val);
                    else edVal.Text = string.Format(formatStr, val);
                }
                catch
                {
                    formatStr = "{0}";
                    edVal.Text = string.Format(formatStr, val);
                }

                if (ValueChanged != null)
                    ValueChanged(this, EventArgs.Empty);
            }
        }
        //-------------------------

        //== Increament change =====
        decimal lastIncr = 0;
        private void btnIncr_ValueChanged(object sender, EventArgs e)
        {
            if (btnIncr.Value > lastIncr)
            {
                if (valAsHex) Increament *= 16;
                else Increament *= 10;
            }
            else
            {
                if (valAsHex) Increament /= 16;
                else Increament /= 10;
            }
            lastIncr = btnIncr.Value;
        }
        /// <summary>
        /// Increment of the value for every mode except when Pow2Increment is set.
        /// </summary>
        [Description("Increment of the value for every mode except when Pow2Increment is set.")]
        [Category("Behavior")]
        public double Increament
        {
            get { return incr; }
            set
            {
                if ( ! initializing)
                {
                    if (value == incr) return;
                    if (value == 0) value = 1.0;
                    if (value < incrMin) value = incrMin;
                    if (value > incrMax) value = incrMax;
                }                
                incr = value;
                try
                {
                    if (valAsHex)
                        hint.SetToolTip(btnIncr, string.Format("Increment: 0x{0:X}", (long)value));
                    else if (!valAsInt && (value < 1e-4 || value > 1e4))
                        hint.SetToolTip(btnIncr, string.Format("Increment: {0:0.0##e0}", value));
                    else hint.SetToolTip(btnIncr, string.Format("Increment: {0}", value));
                }
                catch
                {
                    hint.SetToolTip(btnIncr, string.Format("Increment: {0}", value));
                }
            }
        }

        /// <summary>
        /// Increment maximum value.
        /// </summary>
        [Description("Increment maximum value.")]
        [Category("Behavior")]
        public double IncrementMax
        {
            get { return incrMax; }
            set {
                if (initializing)
                {
                    incrMax = value;
                    return;
                }
                if (value < incrMin) value = incrMin;
                incrMax = value;
            }
        }
        /// <summary>
        /// Increment minimum value.
        /// </summary>
        [Description("Increment minimum value.")]
        [Category("Behavior")]
        public double IncrementMin
        {
            get { return incrMin; }
            set
            {
                if (initializing)
                {
                    incrMin = value;
                    return;
                }
                if (value > incrMax) value = incrMax;
                incrMin = value;
            }
        }
        
        //== Some last adjustments =====
        private void btnVal_Enter(object sender, EventArgs e)
        {
            // Draw selection rectangle only if control is not selected by mouse (no point to select)
            if (! btnVal.RectangleToScreen(btnVal.ClientRectangle).Contains(MousePosition))
            { 
                Graphics gr = CreateGraphics();
                gr.DrawRectangle(new Pen(Color.Black/*ValueBackColor*/), btnVal.Left, 1, 
                                                   btnVal.Width - 3, Height - 3);
            }            
        }
        private void btnIncr_Enter(object sender, EventArgs e)
        {
            // Draw selection rectangle only if control is not selected by mouse
            if (!btnIncr.RectangleToScreen(btnIncr.ClientRectangle).Contains(MousePosition))
            {
                Graphics gr = CreateGraphics();
                gr.DrawRectangle(new Pen(Color.Black/*ValueBackColor*/), btnIncr.Left, 1,
                                                   btnIncr.Width - 3, Height - 3);
            }  
        }
        private void btnVal_Leave(object sender, EventArgs e)
        {
            Refresh();
        }

        private void edVal_Click(object sender, EventArgs e)
        {
            // Relay text box Click event to user
            OnClick(e);
        }

        private void edVal_Enter(object sender, EventArgs e)
        {
            // Value field is readonly, so should not appear as if editable
            btnVal.Focus();
        }
    }
    
    /// <summary>
    /// Argument object passed with ValueCanging event.
    /// </summary>
    public class IgSpinEditChangedArgs : EventArgs
    {
        private bool cancel;
        /// <summary>
        /// Setting Cancel to true in event handler prevent value update.
        /// </summary>
        public bool Cancel
        {
            get { return cancel; }
            set { cancel = value; }
        }
        private double value;
        /// <summary>
        /// Read only new value which has not been set yet.
        /// </summary>
        public double Value
        {
            get { return this.value; }
            internal set { this.value = value; }
        }
        public IgSpinEditChangedArgs(double value)
            : base()
        {
            cancel = false;
            this.value = value;
        }
    }
    /// <summary>
    /// ValueChanging event handler signature.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public delegate void IgSpinEditChanged(object sender, IgSpinEditChangedArgs e);

}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
New Zealand New Zealand
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions