Index: Core/Common/src/Core.Common.Gui/Converters/RoundDoubleToDecimalPlacesConverter.cs =================================================================== diff -u --- Core/Common/src/Core.Common.Gui/Converters/RoundDoubleToDecimalPlacesConverter.cs (revision 0) +++ Core/Common/src/Core.Common.Gui/Converters/RoundDoubleToDecimalPlacesConverter.cs (revision 018134e6188262c4393c4d104a342c86e411081d) @@ -0,0 +1,108 @@ +using System; +using System.ComponentModel; +using System.Globalization; + +namespace Core.Common.Gui.Converters +{ + /// + /// This class can convert doubles (and their string counterparts) to doubles rounded + /// to a given number of decimal places. + /// + public class RoundDoubleToDecimalPlacesConverter : TypeConverter + { + private readonly int numberOfDecimalPlaces; + + /// + /// Initializes a new instance of the class. + /// + /// The number of decimal places. + public RoundDoubleToDecimalPlacesConverter(int numberOfPlaces) + { + if (numberOfPlaces < 0 || numberOfPlaces > 28) + { + throw new ArgumentOutOfRangeException("numberOfPlaces", "Value must be in range [0, 28]."); + } + numberOfDecimalPlaces = numberOfPlaces; + } + + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string) || sourceType == typeof(double)) + { + return true; + } + return base.CanConvertFrom(context, sourceType); + } + + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + if (destinationType == typeof(string) || destinationType == typeof(double)) + { + return true; + } + return base.CanConvertTo(context, destinationType); + } + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (destinationType == typeof(double)) + { + decimal numberAsDecimal = ConvertValueToDecimal(value); + decimal roundedDecimal = Math.Round(numberAsDecimal, numberOfDecimalPlaces); + return Convert.ToDouble(roundedDecimal); + } + if (destinationType == typeof(string)) + { + decimal numberAsDecimal = ConvertValueToDecimal(value); + decimal roundedDecimal = Math.Round(numberAsDecimal, numberOfDecimalPlaces); + return roundedDecimal.ToString(GetStringFormat()); + } + return base.ConvertTo(context, culture, value, destinationType); + } + + private string GetStringFormat() + { + return "F"+numberOfDecimalPlaces; + } + + private static decimal ConvertValueToDecimal(object value) + { + try + { + return Convert.ToDecimal(value); + } + catch (FormatException formatException) + { + string message = string.Format("De waarde '{0}' is geen getal.", value); + throw new NotSupportedException(message, formatException); + } + catch (OverflowException overflowException) + { + string message = string.Format("De waarde '{0}' is te groot of te klein om te kunnen verwerken.", value); + throw new NotSupportedException(message, overflowException); + } + } + } + + #region Specializations of RoundDoubleToDecimalPlacesConverter to be used for TypeConverterAttribute + + /// + /// This class can convert doubles (and their string counterparts) to doubles rounded + /// to two decimal places. + /// + public class RoundDoubleToTwoDecimalPlacesConverter : RoundDoubleToDecimalPlacesConverter + { + public RoundDoubleToTwoDecimalPlacesConverter() : base(2) {} + } + + /// + /// This class can convert doubles (and their string counterparts) to doubles rounded + /// to three decimal places. + /// + public class RoundDoubleToThreeDecimalPlacesConverter : RoundDoubleToDecimalPlacesConverter + { + public RoundDoubleToThreeDecimalPlacesConverter() : base(3) {} + } + + #endregion +} \ No newline at end of file Index: Core/Common/src/Core.Common.Gui/Core.Common.Gui.csproj =================================================================== diff -u -reff889238a2ecfab9aacabe9b99a35c6989006d9 -r018134e6188262c4393c4d104a342c86e411081d --- Core/Common/src/Core.Common.Gui/Core.Common.Gui.csproj (.../Core.Common.Gui.csproj) (revision eff889238a2ecfab9aacabe9b99a35c6989006d9) +++ Core/Common/src/Core.Common.Gui/Core.Common.Gui.csproj (.../Core.Common.Gui.csproj) (revision 018134e6188262c4393c4d104a342c86e411081d) @@ -133,6 +133,7 @@ + Index: Core/Common/test/Core.Common.Gui.Test/Converters/RoundDoubleToDecimalPlacesConverterTest.cs =================================================================== diff -u --- Core/Common/test/Core.Common.Gui.Test/Converters/RoundDoubleToDecimalPlacesConverterTest.cs (revision 0) +++ Core/Common/test/Core.Common.Gui.Test/Converters/RoundDoubleToDecimalPlacesConverterTest.cs (revision 018134e6188262c4393c4d104a342c86e411081d) @@ -0,0 +1,344 @@ +using System; +using System.ComponentModel; +using System.Globalization; + +using Core.Common.Gui.Converters; +using Core.Common.TestUtil; + +using NUnit.Framework; + +namespace Core.Common.Gui.Test.Converters +{ + [TestFixture] + public class RoundDoubleToDecimalPlacesConverterTest + { + [Test] + public void DefaultConstructor_ExpectedValues( + [Random(0, 28, 1)]int numberOfPlaces) + { + // Call + var converter = new RoundDoubleToDecimalPlacesConverter(numberOfPlaces); + + // Assert + Assert.IsInstanceOf(converter); + } + + [Test] + [TestCase(-46)] + [TestCase(-1)] + [TestCase(29)] + [TestCase(23456789)] + public void Constructor_InvalidNumberOfPlaces_ThrowArgumentOutOfRangeException(int invalidNumberOfPlaces) + { + // Call + TestDelegate call = () => new RoundDoubleToDecimalPlacesConverter(invalidNumberOfPlaces); + + // Assert + string expectedMessage = "Value must be in range [0, 28]."; + TestHelper.AssertThrowsArgumentExceptionAndTestMessage(call, expectedMessage); + } + + [Test] + public void CanConvertFrom_SourceIsStringType_ReturnTrue( + [Random(0, 28, 1)]int numberOfPlaces) + { + // Setup + var converter = new RoundDoubleToDecimalPlacesConverter(numberOfPlaces); + + // Call + var isConversionPossible = converter.CanConvertFrom(typeof(string)); + + // Assert + Assert.IsTrue(isConversionPossible); + } + + [Test] + public void CanConvertFrom_SourceIsDoubleType_ReturnTrue( + [Random(0, 28, 1)]int numberOfPlaces) + { + // Setup + var converter = new RoundDoubleToDecimalPlacesConverter(numberOfPlaces); + + // Call + var isConversionPossible = converter.CanConvertFrom(typeof(double)); + + // Assert + Assert.IsTrue(isConversionPossible); + } + + [Test] + public void CanConvertFrom_SourceIsNotDoubleNorStringType_ReturnFalse( + [Random(0, 28, 1)]int numberOfPlaces) + { + // Setup + var converter = new RoundDoubleToDecimalPlacesConverter(numberOfPlaces); + + // Call + var isConversionPossible = converter.CanConvertFrom(typeof(object)); + + // Assert + Assert.IsFalse(isConversionPossible); + } + + [Test] + public void CanConvertTo_TargetIsStringType_ReturnTrue( + [Random(0, 28, 1)]int numberOfPlaces) + { + // Setup + var converter = new RoundDoubleToDecimalPlacesConverter(numberOfPlaces); + + // Call + var isConversionPossible = converter.CanConvertTo(typeof(string)); + + // Assert + Assert.IsTrue(isConversionPossible); + } + + [Test] + public void CanConvertTo_TargetIsDoubleType_ReturnTrue( + [Random(0, 28, 1)]int numberOfPlaces) + { + // Setup + var converter = new RoundDoubleToDecimalPlacesConverter(numberOfPlaces); + + // Call + var isConversionPossible = converter.CanConvertTo(typeof(double)); + + // Assert + Assert.IsTrue(isConversionPossible); + } + + [Test] + public void CanConvertTo_TargetIsNotDoubleNorStringType_ReturnFalse( + [Random(0, 28, 1)]int numberOfPlaces) + { + // Setup + var converter = new RoundDoubleToDecimalPlacesConverter(numberOfPlaces); + + // Call + var isConversionPossible = converter.CanConvertTo(typeof(object)); + + // Assert + Assert.IsFalse(isConversionPossible); + } + + [Test] + [TestCase(1.0, 2, 1.00)] + [TestCase(123456789.0, 3, 123456789.000)] + [TestCase(12345678.90, 2, 12345678.90)] + [TestCase(12345678.90, 3, 12345678.900)] + [TestCase(1234567.890, 2, 1234567.89)] + [TestCase(1234567.890, 3, 1234567.890)] + [TestCase(123456.7890, 2, 123456.79)] + [TestCase(123456.7890, 3, 123456.789)] + [TestCase(12345.67890, 2, 12345.68)] + [TestCase(12345.67890, 3, 12345.679)] + [TestCase(1234.567890, 2, 1234.57)] + [TestCase(1234.567890, 3, 1234.568)] + [TestCase(123.4567890, 2, 123.46)] + [TestCase(123.4567890, 3, 123.457)] + [TestCase(12.34567890, 2, 12.35)] + [TestCase(12.34567890, 3, 12.346)] + [TestCase(1.234567890, 2, 1.23)] + [TestCase(1.234567890, 3, 1.235)] + [TestCase(0.1234567890, 2, 0.12)] + [TestCase(0.1234567890, 3, 0.123)] + [TestCase(0.01234567890, 2, 0.01)] + [TestCase(0.01234567890, 3, 0.012)] + [TestCase(0.001234567890, 2, 0.00)] + [TestCase(0.001234567890, 3, 0.001)] + [TestCase(0.0001234567890, 2, 0.00)] + [TestCase(0.0001234567890, 3, 0.000)] + public void ConvertTo_FromDoubleToDouble_ReturnRoundedDouble( + double input, int numberOfPlaces, double expectedOutput) + { + // Setup + var converter = new RoundDoubleToDecimalPlacesConverter(numberOfPlaces); + + // Call + double result = (double)converter.ConvertTo(input, typeof(double)); + + // Assert + Assert.AreEqual(expectedOutput, result); + } + + [Test] + [TestCase(1.0, 2, 1.00)] + [TestCase(123456789.0, 3, 123456789.000)] + [TestCase(12345678.90, 2, 12345678.90)] + [TestCase(12345678.90, 3, 12345678.900)] + [TestCase(1234567.890, 2, 1234567.89)] + [TestCase(1234567.890, 3, 1234567.890)] + [TestCase(123456.7890, 2, 123456.79)] + [TestCase(123456.7890, 3, 123456.789)] + [TestCase(12345.67890, 2, 12345.68)] + [TestCase(12345.67890, 3, 12345.679)] + [TestCase(1234.567890, 2, 1234.57)] + [TestCase(1234.567890, 3, 1234.568)] + [TestCase(123.4567890, 2, 123.46)] + [TestCase(123.4567890, 3, 123.457)] + [TestCase(12.34567890, 2, 12.35)] + [TestCase(12.34567890, 3, 12.346)] + [TestCase(1.234567890, 2, 1.23)] + [TestCase(1.234567890, 3, 1.235)] + [TestCase(0.1234567890, 2, 0.12)] + [TestCase(0.1234567890, 3, 0.123)] + [TestCase(0.01234567890, 2, 0.01)] + [TestCase(0.01234567890, 3, 0.012)] + [TestCase(0.001234567890, 2, 0.00)] + [TestCase(0.001234567890, 3, 0.001)] + [TestCase(0.0001234567890, 2, 0.00)] + [TestCase(0.0001234567890, 3, 0.000)] + public void ConvertTo_FromStringToDouble_ReturnRoundedDouble( + double input, int numberOfPlaces, double expectedOutput) + { + // Setup + var converter = new RoundDoubleToDecimalPlacesConverter(numberOfPlaces); + + CultureInfo currentCulture = CultureInfo.CurrentCulture; + string stringInput = input.ToString(currentCulture); + + // Call + double result = (double)converter.ConvertTo(null, currentCulture, stringInput, typeof(double)); + + // Assert + Assert.AreEqual(expectedOutput, result); + } + + [Test] + [SetCulture("en-US")] + [TestCase(1.0, 2, "1.00")] + [TestCase(123456789.0, 3, "123456789.000")] + [TestCase(12345678.90, 2, "12345678.90")] + [TestCase(12345678.90, 3, "12345678.900")] + [TestCase(1234567.890, 2, "1234567.89")] + [TestCase(1234567.890, 3, "1234567.890")] + [TestCase(123456.7890, 2, "123456.79")] + [TestCase(123456.7890, 3, "123456.789")] + [TestCase(12345.67890, 2, "12345.68")] + [TestCase(12345.67890, 3, "12345.679")] + [TestCase(1234.567890, 2, "1234.57")] + [TestCase(1234.567890, 3, "1234.568")] + [TestCase(123.4567890, 2, "123.46")] + [TestCase(123.4567890, 3, "123.457")] + [TestCase(12.34567890, 2, "12.35")] + [TestCase(12.34567890, 3, "12.346")] + [TestCase(1.234567890, 2, "1.23")] + [TestCase(1.234567890, 3, "1.235")] + [TestCase(0.1234567890, 2, "0.12")] + [TestCase(0.1234567890, 3, "0.123")] + [TestCase(0.01234567890, 2, "0.01")] + [TestCase(0.01234567890, 3, "0.012")] + [TestCase(0.001234567890, 2, "0.00")] + [TestCase(0.001234567890, 3, "0.001")] + [TestCase(0.0001234567890, 2, "0.00")] + [TestCase(0.0001234567890, 3, "0.000")] + public void ConvertTo_FromDoubleToString_ReturnRoundedDouble( + double input, int numberOfPlaces, string expectedOutput) + { + // Setup + var converter = new RoundDoubleToDecimalPlacesConverter(numberOfPlaces); + + // Call + string result = (string)converter.ConvertTo(null, CultureInfo.CurrentCulture, input, typeof(string)); + + // Assert + Assert.AreEqual(expectedOutput, result); + } + + [Test] + [SetCulture("nl-NL")] + [TestCase(1.0, 2, "1,00")] + [TestCase(123456789.0, 3, "123456789,000")] + [TestCase(12345678.90, 2, "12345678,90")] + [TestCase(12345678.90, 3, "12345678,900")] + [TestCase(1234567.890, 2, "1234567,89")] + [TestCase(1234567.890, 3, "1234567,890")] + [TestCase(123456.7890, 2, "123456,79")] + [TestCase(123456.7890, 3, "123456,789")] + [TestCase(12345.67890, 2, "12345,68")] + [TestCase(12345.67890, 3, "12345,679")] + [TestCase(1234.567890, 2, "1234,57")] + [TestCase(1234.567890, 3, "1234,568")] + [TestCase(123.4567890, 2, "123,46")] + [TestCase(123.4567890, 3, "123,457")] + [TestCase(12.34567890, 2, "12,35")] + [TestCase(12.34567890, 3, "12,346")] + [TestCase(1.234567890, 2, "1,23")] + [TestCase(1.234567890, 3, "1,235")] + [TestCase(0.1234567890, 2, "0,12")] + [TestCase(0.1234567890, 3, "0,123")] + [TestCase(0.01234567890, 2, "0,01")] + [TestCase(0.01234567890, 3, "0,012")] + [TestCase(0.001234567890, 2, "0,00")] + [TestCase(0.001234567890, 3, "0,001")] + [TestCase(0.0001234567890, 2, "0,00")] + [TestCase(0.0001234567890, 3, "0,000")] + public void ConvertTo_FromStringToString_ReturnRoundedDouble( + double input, int numberOfPlaces, string expectedOutput) + { + // Setup + var converter = new RoundDoubleToDecimalPlacesConverter(numberOfPlaces); + + string stringInput = input.ToString(CultureInfo.CurrentCulture); + + // Call + string result = (string)converter.ConvertTo(null, CultureInfo.CurrentCulture, stringInput, typeof(string)); + + // Assert + Assert.AreEqual(expectedOutput, result); + } + + [Test] + [TestCase("I'm really not a number", 2)] + [TestCase("This value also does not represent a number!", 3)] + public void ConvertTo_FromNotNumberStringToDouble_ThrowNowSupportedException( + string stringInput, int numberOfPlaces) + { + // Setup + var converter = new RoundDoubleToDecimalPlacesConverter(numberOfPlaces); + + CultureInfo currentCulture = CultureInfo.CurrentCulture; + + // Call + TestDelegate call = () => converter.ConvertTo(null, currentCulture, stringInput, typeof(double)); + + // Assert + string message = Assert.Throws(call).Message; + Assert.AreEqual(string.Format("De waarde '{0}' is geen getal.", stringInput), message); + } + + [Test] + [TestCase(79228162514264337593543950335.0 + 1e-6, 2)] + [TestCase(79228162514264337593543950335.0 + 1e-6, 3)] + [TestCase("79228162514264337593543950336", 2)] + [TestCase("79228162514264337593543950336", 3)] + [TestCase(99999999999999999999999999999.0, 2)] + [TestCase(99999999999999999999999999999.0, 3)] + [TestCase("99999999999999999999999999999", 2)] + [TestCase("99999999999999999999999999999", 3)] + [TestCase(-79228162514264337593543950335.0 - 1e-6, 2)] + [TestCase(-79228162514264337593543950335.0 - 1e-6, 3)] + [TestCase("-79228162514264337593543950336", 2)] + [TestCase("-79228162514264337593543950336", 3)] + [TestCase(-99999999999999999999999999999.0, 2)] + [TestCase(-99999999999999999999999999999.0, 3)] + [TestCase("-99999999999999999999999999999", 2)] + [TestCase("-99999999999999999999999999999", 3)] + public void ConvertTo_FromTooLargeOrTooSmallNumberToDouble_ThrowNowSupportedException( + object input, int numberOfPlaces) + { + // Setup + var converter = new RoundDoubleToDecimalPlacesConverter(numberOfPlaces); + + CultureInfo currentCulture = CultureInfo.CurrentCulture; + + // Call + TestDelegate call = () => converter.ConvertTo(null, currentCulture, input, typeof(double)); + + // Assert + string message = Assert.Throws(call).Message; + Assert.AreEqual(string.Format("De waarde '{0}' is te groot of te klein om te kunnen verwerken.", input), message); + } + } +} \ No newline at end of file Index: Core/Common/test/Core.Common.Gui.Test/Core.Common.Gui.Test.csproj =================================================================== diff -u -reff889238a2ecfab9aacabe9b99a35c6989006d9 -r018134e6188262c4393c4d104a342c86e411081d --- Core/Common/test/Core.Common.Gui.Test/Core.Common.Gui.Test.csproj (.../Core.Common.Gui.Test.csproj) (revision eff889238a2ecfab9aacabe9b99a35c6989006d9) +++ Core/Common/test/Core.Common.Gui.Test/Core.Common.Gui.Test.csproj (.../Core.Common.Gui.Test.csproj) (revision 018134e6188262c4393c4d104a342c86e411081d) @@ -111,6 +111,7 @@ +