Implementing a data converter component

From OPC Labs Knowledge Base
Revision as of 12:36, 14 December 2016 by User (talk | contribs) (Created page with "Category:C-sharp Category:How to Category:Live Binding <syntaxhighlight lang="c#"> using System; using System.ComponentModel; using System.Drawing; using System...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)


using System;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Runtime.Serialization;
using System.Security;
using OpcLabs.BaseLib.Data;
using OpcLabs.BaseLib.Extensions;
using OpcLabs.BaseLib.Runtime.Serialization.Extensions;
using OpcLabs.BaseLib.Utilities;
using OpcLabs.BaseLib.Widgets;

namespace OpcLabs.BaseLib.Components
{
    /// <summary>
    /// Assigns different colors based on status information.
    /// </summary>
    [Category("Data Converters")]
    [Description("Assigns different colors based on status information.")] // used e.g. in Toolbox
    [Serializable]
    [ToolboxBitmap(typeof(StatusToColorConverter))]
    public sealed class StatusToColorConverter
        : Widget
        , ICloneable
        , IDataConverter
    {
#region Public Constructors

        /// <overloads>
        /// <summary>
        /// Initializes an instance of the <see cref="StatusToColorConverter"/> class. 
        /// </summary>
        /// </overloads>
        public StatusToColorConverter()
        {
            Construct();
        }

        /// <param name="unknownColor">The color returned for Unknown status.</param>
        /// <param name="normalColor">The color returned for Normal status.</param>
        /// <param name="warningColor">The color returned for Warning status.</param>
        /// <param name="errorColor">The color returned for Error status.</param>
        public StatusToColorConverter(Color unknownColor, Color normalColor, Color warningColor, Color errorColor)
            : this()
        {
            UnknownColor = unknownColor;
            NormalColor = normalColor;
            WarningColor = warningColor;
            ErrorColor = errorColor;
        }

        /// <summary>
        /// Initializes an instance of the <see cref="StatusToColorConverter"/> class with Designer support. 
        /// </summary>
        /// <param name="container">The component container to which the new instance will be added.</param>
        public StatusToColorConverter(IContainer container)
        {
            Construct();
            container.Add(this);    // place it *after* the Construct, as the VS designer can call us from here
        }

        /// <summary>Initializes a new instance of the class, copying values from a given object.</summary>
        /// <param name="statusToColorConverter">The object to be copied from.</param>
        public StatusToColorConverter(StatusToColorConverter statusToColorConverter)
            : base(statusToColorConverter)
        {
            UnknownColor = statusToColorConverter.UnknownColor;
            NormalColor = statusToColorConverter.NormalColor;
            WarningColor = statusToColorConverter.WarningColor;
            ErrorColor = statusToColorConverter.ErrorColor;
        }

#endregion



#region Private Constructors

        /// <summary>Initializes a new instance of the class with serialized data.</summary>
        /// <param name="info">The SerializationInfo that holds the serialized object data.</param>
        /// <param name="context">The StreamingContext that contains contextual information about the source or destination. 
        /// </param>
        private StatusToColorConverter(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
            UnknownColor = info.GetObject<Color>("UnknownColor");
            NormalColor = info.GetObject<Color>("NormalColor");
            WarningColor = info.GetObject<Color>("WarningColor");
            ErrorColor = info.GetObject<Color>("ErrorColor");
        }

#endregion



#region Static Public Properties

        /// <summary>
        /// The default Error color.
        /// </summary>
        static public Color DefaultErrorColor => Color.Red;

        /// <summary>
        /// The default Normal color.
        /// </summary>
        static public Color DefaultNormalColor => Color.White;

        /// <summary>
        /// The default Unknown color.
        /// </summary>
        static public Color DefaultUnknownColor => Color.Gray;

        /// <summary>
        /// The default Warning color.
        /// </summary>
        static public Color DefaultWarningColor => Color.Yellow;

#endregion



#region Static Public Methods

        /// <summary>Determines whether the two objects are equal.</summary>
        /// <param name="left">First object to be compared.</param>
        /// <param name="right">Second object to be compared.</param>
        /// <returns><c>True</c> if the objects are equal; <c>false</c> otherwise.</returns>
        static public bool operator ==(StatusToColorConverter left, StatusToColorConverter right)
        {
            return Equals(left, right);
        }

        /// <summary>Determines whether the two objects are not equal.</summary>
        /// <param name="left">First object to be compared.</param>
        /// <param name="right">Second object to be compared.</param>
        /// <returns><c>True</c> if the objects are not equal; <c>false</c> if they are equal.</returns>
        static public bool operator !=(StatusToColorConverter left, StatusToColorConverter right)
        {
            return !Equals(left, right);
        }

#endregion



#region Public Properties

        /// <summary>
        /// The color returned for Error status.
        /// </summary>
        [Description("The color returned for Error status.")]
        public Color ErrorColor { get; set; }

        /// <summary>
        /// The color returned for Normal status.
        /// </summary>
        [Description("The color returned for Normal status.")]
        public Color NormalColor { get; set; }

        /// <summary>
        /// The color returned for Unknown status.
        /// </summary>
        [Description("The color returned for Unknown status.")]
        public Color UnknownColor { get; set; }

        /// <summary>
        /// The color returned for Warning status.
        /// </summary>
        [Description("The color returned for Warning status.")]
        public Color WarningColor { get; set; }

#endregion



#region Public Methods

        /// <summary>
        /// Determines whether the specified <see cref="StatusToColorConverter"/> is equal to the current object.
        /// </summary>
        /// <param name="other">The <see cref="StatusToColorConverter"/> to compare with the current object.</param>
        /// <returns><c>true</c> if the specified object is equal to the current object; otherwise, <c>false</c>.</returns>
        public bool Equals(StatusToColorConverter other)
        {
            if (other == null)
                return false;
            return
                base.Equals(other) &&
                ErrorColor.Equals(other.ErrorColor) &&
                NormalColor.Equals(other.NormalColor) &&
                UnknownColor.Equals(other.UnknownColor) &&
                WarningColor.Equals(other.WarningColor);
        }

#endregion



#region ICloneable Members

        object ICloneable.Clone()
        {
            return Clone();
        }

#endregion



#region IDataConverter Members

        /// <inheritdoc cref="IDataConverter.Convert"/>
        /// <exception cref="ArgumentException">When the value is not of any of the supported types.</exception>
        /// <remarks>
        /// Values are converted to colors according to following rules:
        /// <ul>
        ///     <li><c>null</c> converts to <see cref="NormalColor"/>.</li>
        ///     <li>A <see cref="StatusInfo.Normal"/> converts to <see cref="NormalColor"/>, 
        ///         a <see cref="StatusInfo.Warning"/> converts to <see cref="WarningColor"/>,
        ///         a <see cref="StatusInfo.Error"/> converts to <see cref="ErrorColor"/>, and
        ///         a <see cref="StatusInfo.Unknown"/> converts to <see cref="UnknownColor"/>.</li>
        ///     <li>An <see cref="Exception"/>-typed value converts to <see cref="WarningColor"/>.</li>
        ///     <li>Empty strings convert to <see cref="NormalColor"/>, 
        ///         non-empty strings convert to <see cref="ErrorColor"/>.</li>
        ///     <li>Zero integers convert to <see cref="NormalColor"/>, 
        ///         positive integers convert to <see cref="WarningColor"/>,
        ///         negative integers convert to <see cref="ErrorColor"/>.</li>
        ///     <li>A <c>true</c> Boolean converts to <see cref="NormalColor"/>,
        ///         a <c>false</c> Boolean converts to <see cref="ErrorColor"/>.</li>
        /// </ul>
        /// </remarks>
        public object Convert(
            object value, object parameter, CultureInfo culture)
        {
            Color color = ConvertToColor(value);
            return color;
        }

        /// <inheritdoc cref="IDataConverter.ConvertBack"/>
        /// <exception cref="NotSupportedException">Thrown.</exception>
        /// <remarks>Conversion in this direction is not supported.</remarks>
        public object ConvertBack(object value, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException("Inverse conversion is not supported by StatusToColorConverter.");
        }

#endregion



#region ISerializable Members

        /// <inheritdoc cref="ISerializable.GetObjectData"/>
        [SecurityCritical]
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
            info.AddValue("UnknownColor", UnknownColor);
            info.AddValue("NormalColor", NormalColor);
            info.AddValue("WarningColor", WarningColor);
            info.AddValue("ErrorColor", ErrorColor);
        }

#endregion



#region Protected Methods

        /// <inheritdoc cref="Widget.Equals2"/>
        protected override bool Equals2(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != /*this.*/GetType()) return false;
            return Equals((StatusToColorConverter)obj);
        }

        /// <inheritdoc cref="Widget.GetHashCode2"/>
        protected override int GetHashCode2()
        {
            unchecked
            {
                var hashCode = base.GetHashCode2();
                hashCode = (hashCode * 397) ^ ErrorColor.GetHashCode();
                hashCode = (hashCode * 397) ^ NormalColor.GetHashCode();
                hashCode = (hashCode * 397) ^ UnknownColor.GetHashCode();
                hashCode = (hashCode * 397) ^ WarningColor.GetHashCode();
                return hashCode;
            }
        }

#endregion



#region Private Methods

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>A new object that is a copy of this instance.</returns>
        private StatusToColorConverter Clone()
        {
            return new StatusToColorConverter(this);
        }

        private void Construct()
        {
            ResetErrorColor();
            ResetNormalColor();
            ResetUnknownColor();
            ResetWarningColor();
        }

        private Color ConvertToColor(object value)
        {
            if (value == null)
                return NormalColor; // this is actually required so that we can test against Exception properties

            {
                if (value is StatusInfo)
                    return DetermineColor((StatusInfo)value);
            }

            {
                var exception = value as Exception;
                if (exception != null)
                    return DetermineColor(exception);
            }

            {
                var s = value as string;
                if (s != null)
                    return DetermineColor(s);
            }

            {
                if (value is Byte)
                    return DetermineColor(Math.Sign((Byte) value));
                if (value is SByte)
                    return DetermineColor(Math.Sign((SByte)value));
                if (value is UInt16)
                    return DetermineColor(Math.Sign((UInt16)value));
                if (value is Int16)
                    return DetermineColor(Math.Sign((Int16)value));
                if (value is UInt32)
                    return DetermineColor(Math.Sign((UInt32)value));
                if (value is Int32)
                    return DetermineColor(Math.Sign((Int32)value));
                if (value is UInt64)
                    return DetermineColor(MathUtilities.Sign((UInt64)value));
                if (value is Int64)
                    return DetermineColor(Math.Sign((Int64)value));

                if (value is UIntPtr)
                    return DetermineColor(MathUtilities.Sign((UIntPtr)value));
                if (value is IntPtr)
                    return DetermineColor(MathUtilities.Sign((IntPtr)value));
            }

            {
                if (value is bool)
                    return DetermineColor((bool) value);
            }

            throw new ArgumentException(
                String.Format(CultureInfo.CurrentCulture,
                    "We do not know how to convert a status of type '{0}' to a color.",
                    value.GetType()));
        }

        private Color DetermineColor(bool b)
        {
            return b ? NormalColor : ErrorColor;
        }

        private Color DetermineColor(
            Exception exception)
        {
            return ErrorColor;
        }

        private Color DetermineColor(int sign)
        {
            switch (sign)
            {
                case -1:
                    return ErrorColor;
                case 0:
                    return NormalColor;
                case +1:
                    return WarningColor;
            }
            return UnknownColor;
        }

        private Color DetermineColor(StatusInfo statusInfo)
        {
            switch (statusInfo)
            {
                case StatusInfo.Normal:
                    return NormalColor;
                case StatusInfo.Warning:
                    return WarningColor;
                case StatusInfo.Error:
                    return ErrorColor;
            }
            return UnknownColor;
        }

        private Color DetermineColor(string s)
        {
            return DetermineColor(s.IsEmpty());
        }

        /// <summary>
        /// Reset the <see cref="ErrorColor"/> property to its default value.
        /// </summary>
        // The method name is of the essence - for the designer, it must match the name of the property
        private void ResetErrorColor()
        {
            ErrorColor = DefaultErrorColor;
        }

        /// <summary>
        /// Reset the <see cref="NormalColor"/> property to its default value.
        /// </summary>
        // The method name is of the essence - for the designer, it must match the name of the property
        private void ResetNormalColor()
        {
            NormalColor = DefaultNormalColor;
        }

        /// <summary>
        /// Reset the <see cref="UnknownColor"/> property to its default value.
        /// </summary>
        // The method name is of the essence - for the designer, it must match the name of the property
        private void ResetUnknownColor()
        {
            UnknownColor = DefaultUnknownColor;
        }

        /// <summary>
        /// Reset the <see cref="WarningColor"/> property to its default value.
        /// </summary>
        // The method name is of the essence - for the designer, it must match the name of the property
        private void ResetWarningColor()
        {
            WarningColor = DefaultWarningColor;
        }

        /// <summary>
        /// Determines whether the <see cref="ErrorColor"/> property is equal to its default value.
        /// </summary>
        /// <returns><c>true</c> if the property has changed from its default value; <c>false</c> otherwise.</returns>
        // The method name is of the essence - for the designer, it must match the name of the property
        private bool ShouldSerializeErrorColor()
        {
            return ErrorColor != DefaultErrorColor;
        }

        /// <summary>
        /// Determines whether the <see cref="NormalColor"/> property is equal to its default value.
        /// </summary>
        /// <returns><c>true</c> if the property has changed from its default value; <c>false</c> otherwise.</returns>
        // The method name is of the essence - for the designer, it must match the name of the property
        private bool ShouldSerializeNormalColor()
        {
            return NormalColor != DefaultNormalColor;
        }

        /// <summary>
        /// Determines whether the <see cref="UnknownColor"/> property is equal to its default value.
        /// </summary>
        /// <returns><c>true</c> if the property has changed from its default value; <c>false</c> otherwise.</returns>
        // The method name is of the essence - for the designer, it must match the name of the property
        private bool ShouldSerializeUnknownColor()
        {
            return UnknownColor != DefaultUnknownColor;
        }

        /// <summary>
        /// Determines whether the <see cref="WarningColor"/> property is equal to its default value.
        /// </summary>
        /// <returns><c>true</c> if the property has changed from its default value; <c>false</c> otherwise.</returns>
        // The method name is of the essence - for the designer, it must match the name of the property
        private bool ShouldSerializeWarningColor()
        {
            return WarningColor != DefaultWarningColor;
        }

#endregion
    }
}