Port System.Drawing.Printing.MarginsConverter (dotnet/corefx#33640)
authorMaryam Ariyan <maryam.ariyan@microsoft.com>
Sat, 1 Dec 2018 01:47:12 +0000 (17:47 -0800)
committerSantiago Fernandez Madero <safern@microsoft.com>
Sat, 1 Dec 2018 01:47:12 +0000 (19:47 -0600)
* Exposes System.Drawing.Printing.MarginsConverter

Adds tests for MarginsConverter

Fixes dotnet/corefx#33441

* sort usings

* - nit fixes
- whitespace formatting
- code cleanup

* Fix formatting

* Moves MarginsConverter to System.Windows.Extensions

* Add missing using statement and remove extra line

* Apply PR feedback, undo caching.

* Fixing test failures on netfx

* Add comments and put mono headers

* Replace source code with referencesource content

* Cleanup

* Removing SuppressMessage Attributes

* Keeps FullFramework condition to show subtle differences

* Remove duplication

* Updating test and keeping fullframework code for CreateInstance

Commit migrated from https://github.com/dotnet/corefx/commit/a53d140654fe049036b9e9faee05371ebb6cdcb4

src/libraries/System.Drawing.Common/ref/System.Drawing.Common.cs
src/libraries/System.Drawing.Common/src/System/Drawing/Printing/Margins.cs
src/libraries/System.Windows.Extensions/ref/System.Windows.Extensions.cs
src/libraries/System.Windows.Extensions/src/Resources/Strings.resx
src/libraries/System.Windows.Extensions/src/System.Windows.Extensions.csproj
src/libraries/System.Windows.Extensions/src/System/Drawing/Printing/MarginsConverter.cs [new file with mode: 0644]
src/libraries/System.Windows.Extensions/tests/System.Windows.Extensions.Tests.csproj
src/libraries/System.Windows.Extensions/tests/System/Drawing/Printing/MarginsConverterTests.cs [new file with mode: 0644]

index 30d2212..0445212 100644 (file)
@@ -2534,6 +2534,9 @@ namespace System.Drawing.Printing
         protected InvalidPrinterException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { }
         public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { }
     }
+#if netcoreapp
+    [System.ComponentModel.TypeConverter("System.Drawing.Printing.MarginsConverter, System.Windows.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51")]
+#endif
     public partial class Margins : System.ICloneable
     {
         public Margins() { }
index b938408..12ce087 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using System.ComponentModel;
 using System.Globalization;
 using System.Runtime.Serialization;
 
@@ -10,6 +11,9 @@ namespace System.Drawing.Printing
     /// <summary>
     /// Specifies the margins of a printed page.
     /// </summary>
+#if netcoreapp
+    [TypeConverter("System.Drawing.Printing.MarginsConverter, System.Windows.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51")]
+#endif
     public partial class Margins : ICloneable
     {
         private int _left;
index 07325aa..2dbfe6d 100644 (file)
@@ -62,7 +62,19 @@ namespace System.Drawing
         public override bool GetStandardValuesSupported(System.ComponentModel.ITypeDescriptorContext context) { throw null; }
     }
 }
-
+namespace System.Drawing.Printing
+{
+    public partial class MarginsConverter : System.ComponentModel.ExpandableObjectConverter
+    {
+        public MarginsConverter() { }
+        public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Type sourceType) { throw null; }
+        public override bool CanConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Type destinationType) { throw null; }
+        public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { throw null; }
+        public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, System.Type destinationType) { throw null; }
+        public override object CreateInstance(System.ComponentModel.ITypeDescriptorContext context, System.Collections.IDictionary propertyValues) { throw null; }
+        public override bool GetCreateInstanceSupported(System.ComponentModel.ITypeDescriptorContext context) { throw null; }
+    }
+}
 namespace System.Security.Cryptography.X509Certificates
 {
     public enum X509SelectionFlag
index 18d8a1f..a1d739b 100644 (file)
   <data name="PlatformNotSupported_X509Certificate2UI" xml:space="preserve">
     <value>X509Certificate2UI is not supported on this platform.</value>
   </data>
+  <data name="TextParseFailedFormat" xml:space="preserve">
+    <value>Text "{0}" cannot be parsed. The expected text format is "{1}".</value>
+  </data>
+  <data name="PropertyValueInvalidEntry" xml:space="preserve">
+    <value>IDictionary parameter contains at least one entry that is not valid. Ensure all values are consistent with the object's properties.</value>
+  </data>
 </root>
\ No newline at end of file
index 2f187e4..2408e89 100644 (file)
@@ -43,6 +43,7 @@
     <Compile Include="System\Drawing\IconConverter.cs" />
     <Compile Include="System\Drawing\ImageConverter.cs" />
     <Compile Include="System\Drawing\ImageFormatConverter.cs" />
+    <Compile Include="System\Drawing\Printing\MarginsConverter.cs" />
   </ItemGroup>
   <ItemGroup Condition="'$(TargetGroup)' == 'netcoreapp'">
     <Reference Include="System.Buffers" />
@@ -57,6 +58,7 @@
     <Reference Include="System.Runtime.Extensions" />
     <Reference Include="System.Runtime.InteropServices" />
     <Reference Include="System.Security.Cryptography.X509Certificates" />
+    <Reference Include="System.Text.RegularExpressions" />
     <Reference Include="System.Threading" />
   </ItemGroup>
 </Project>
\ No newline at end of file
diff --git a/src/libraries/System.Windows.Extensions/src/System/Drawing/Printing/MarginsConverter.cs b/src/libraries/System.Windows.Extensions/src/System/Drawing/Printing/MarginsConverter.cs
new file mode 100644 (file)
index 0000000..07e5aed
--- /dev/null
@@ -0,0 +1,168 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.ComponentModel;
+using System.ComponentModel.Design.Serialization;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Reflection;
+
+namespace System.Drawing.Printing
+{
+    /// <summary>
+    /// Provides a type converter to convert <see cref='System.Drawing.Printing.Margins'/> to and from various other representations, such as a string.
+    /// </summary>
+    public class MarginsConverter : ExpandableObjectConverter
+    {
+        /// <summary>
+        /// Determines if a converter can convert an object of the given source
+        /// type to the native type of the converter.
+        /// </summary>
+        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+        {
+            if (sourceType == typeof(string))
+            {
+                return true;
+            }
+            return base.CanConvertFrom(context, sourceType);
+        }
+
+        /// <summary>
+        /// Gets a value indicating whether this converter can
+        /// convert an object to the given destination type using the context.
+        /// </summary>
+        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+        {
+            if (destinationType == typeof(InstanceDescriptor))
+            {
+                return true;
+            }
+            return base.CanConvertTo(context, destinationType);
+        }
+
+        /// <summary>
+        /// Converts the given object to the converter's native type.
+        /// </summary>
+        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+        {
+            if (value is string strValue)
+            {
+                string text = strValue.Trim();
+
+                if (text.Length == 0)
+                {
+                    return null;
+                }
+                else
+                {
+                    // Parse 4 integer values.
+                    if (culture == null)
+                    {
+                        culture = CultureInfo.CurrentCulture;
+                    }
+                    char sep = culture.TextInfo.ListSeparator[0];
+                    string[] tokens = text.Split(new char[] { sep });
+                    int[] values = new int[tokens.Length];
+                    TypeConverter intConverter = TypeDescriptor.GetConverter(typeof(int));
+                    for (int i = 0; i < values.Length; i++)
+                    {
+                        // Note: ConvertFromString will raise exception if value cannot be converted.
+                        values[i] = (int)intConverter.ConvertFromString(context, culture, tokens[i]);
+                    }
+                    if (values.Length != 4)
+                    {
+                        throw new ArgumentException(SR.Format(SR.TextParseFailedFormat, text, "left, right, top, bottom"));
+                    }
+                    return new Margins(values[0], values[1], values[2], values[3]);
+                }
+            }
+            return base.ConvertFrom(context, culture, value);
+        }
+
+        /// <summary>
+        /// Converts the given object to another type. The most common types to convert
+        /// are to and from a string object. The default implementation will make a call
+        /// to ToString on the object if the object is valid and if the destination
+        /// type is string. If this cannot convert to the desitnation type, this will
+        /// throw a NotSupportedException.
+        /// </summary>
+        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
+        {
+            if (destinationType == null)
+            {
+                throw new ArgumentNullException(nameof(destinationType));
+            }
+            if (value is Margins margins)
+            {
+                if (destinationType == typeof(string))
+                {
+                    if (culture == null)
+                    {
+                        culture = CultureInfo.CurrentCulture;
+                    }
+                    string sep = culture.TextInfo.ListSeparator + " ";
+                    TypeConverter intConverter = TypeDescriptor.GetConverter(typeof(int));
+                    string[] args = new string[4];
+                    int nArg = 0;
+
+                    // Note: ConvertToString will raise exception if value cannot be converted.
+                    args[nArg++] = intConverter.ConvertToString(context, culture, margins.Left);
+                    args[nArg++] = intConverter.ConvertToString(context, culture, margins.Right);
+                    args[nArg++] = intConverter.ConvertToString(context, culture, margins.Top);
+                    args[nArg++] = intConverter.ConvertToString(context, culture, margins.Bottom);
+
+                    return string.Join(sep, args);
+                }
+                if (destinationType == typeof(InstanceDescriptor))
+                {
+                    ConstructorInfo ctor = typeof(Margins).GetConstructor(new Type[] {
+                        typeof(int), typeof(int), typeof(int), typeof(int)});
+
+                    if (ctor != null)
+                    {
+                        return new InstanceDescriptor(ctor, new object[] {
+                            margins.Left, margins.Right, margins.Top, margins.Bottom});
+                    }
+                }
+            }
+            return base.ConvertTo(context, culture, value, destinationType);
+        }
+
+        /// <summary>
+        /// Determines if changing a value on this object should require a call to
+        /// CreateInstance to create a new value.
+        /// </summary>
+        public override bool GetCreateInstanceSupported(ITypeDescriptorContext context) => true;
+
+        /// <summary>
+        /// Creates an instance of this type given a set of property values
+        /// for the object.  This is useful for objects that are immutable, but still
+        /// want to provide changable properties.
+        /// </summary>
+        public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues)
+        {
+            if (propertyValues == null)
+            {
+                throw new ArgumentNullException(nameof(propertyValues));
+            }
+
+            object left = propertyValues["Left"];
+            object right = propertyValues["Right"];
+            object top = propertyValues["Top"];
+            object bottom = propertyValues["Bottom"];
+
+            if (left == null || right == null || bottom == null || top == null ||
+                !(left is int) || !(right is int) || !(bottom is int) || !(top is int))
+            {
+                throw new ArgumentException(SR.Format(SR.PropertyValueInvalidEntry));
+            }
+
+            return new Margins((int)left,
+                                (int)right,
+                                (int)top,
+                                (int)bottom);
+        }
+    }
+}
index ac8ded9..e492283 100644 (file)
@@ -18,6 +18,7 @@
     <Compile Include="System\Drawing\IconConverterTests.cs" />
     <Compile Include="System\Drawing\ImageConverterTests.cs" />
     <Compile Include="System\Drawing\ImageFormatConverterTests.cs" />
+    <Compile Include="System\Drawing\Printing\MarginsConverterTests.cs" />
   </ItemGroup>
   <ItemGroup>
     <SupplementalTestData Include="$(PackagesDir)system.componentmodel.typeconverter.testdata\$(TestDataPackageVersion)\content\**\*.*">
diff --git a/src/libraries/System.Windows.Extensions/tests/System/Drawing/Printing/MarginsConverterTests.cs b/src/libraries/System.Windows.Extensions/tests/System/Drawing/Printing/MarginsConverterTests.cs
new file mode 100644 (file)
index 0000000..e1f2bc3
--- /dev/null
@@ -0,0 +1,156 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.Design.Serialization;
+using System.Globalization;
+using Xunit;
+
+namespace System.Drawing.Printing.Tests
+{
+    public class MarginsConverterTests
+    {
+        [Fact]
+        public void CanConvertFrom()
+        {
+            MarginsConverter mc = new MarginsConverter();
+
+            // try once with then once without context
+            for (var context = new MyTypeDescriptorContext(); context != null; context = null)
+            {
+                Assert.True(mc.CanConvertFrom(context, typeof(string)));
+                Assert.False(mc.CanConvertFrom(context, typeof(Guid)));
+                Assert.False(mc.CanConvertFrom(context, typeof(object)));
+                Assert.False(mc.CanConvertFrom(context, typeof(int)));
+            }
+        }
+
+        [Fact]
+        public void CanConvertTo()
+        {
+            MarginsConverter mc = new MarginsConverter();
+
+            // try once with then once without context
+            for (var context = new MyTypeDescriptorContext(); context != null; context = null)
+            {
+                Assert.True(mc.CanConvertTo(context, typeof(string)));
+                Assert.False(mc.CanConvertTo(context, typeof(Guid)));
+                Assert.False(mc.CanConvertTo(context, typeof(object)));
+                Assert.False(mc.CanConvertTo(context, typeof(int)));
+            }
+        }
+
+        [Fact]
+        public void CreateInstance()
+        {
+            MarginsConverter mc = new MarginsConverter();
+            MyTypeDescriptorContext context = new MyTypeDescriptorContext();
+
+            IDictionary values = new Dictionary<string, int>();
+            values.Add("Left", 1);
+            values.Add("Right", 2);
+            values.Add("Top", 3);
+            Assert.Throws<ArgumentException>(() => mc.CreateInstance(context, values));
+            values.Add("Bottom", 4);
+
+            object result = mc.CreateInstance(context, values);
+            Assert.NotNull(result);
+
+            Assert.IsType<Margins>(result);
+            Margins margins = result as Margins;
+            Assert.Equal(1, margins.Left);
+            Assert.Equal(2, margins.Right);
+            Assert.Equal(3, margins.Top);
+            Assert.Equal(4, margins.Bottom);
+        }
+
+        [Fact]
+        public void GetCreateInstanceSupported()
+        {
+            MarginsConverter mc = new MarginsConverter();
+            Assert.True(mc.GetCreateInstanceSupported(null));
+            Assert.True(mc.GetCreateInstanceSupported(new MyTypeDescriptorContext()));
+        }
+
+        [Fact]
+        public void ConvertFrom()
+        {
+            MarginsConverter mc = new MarginsConverter();
+            CultureInfo culture = CultureInfo.InvariantCulture;
+
+            // try once with then once without context
+            for (var context = new MyTypeDescriptorContext(); context != null; context = null)
+            {
+                object result;
+                Assert.Equal(',', culture.TextInfo.ListSeparator[0]);
+                AssertExtensions.Throws<ArgumentException, Exception>(() => mc.ConvertFrom(context, culture, "1;2;3;4"));
+                result = mc.ConvertFrom(context, culture, "1,2,3,4");
+                Assert.IsType<Margins>(result);
+                Margins margins = result as Margins;
+                Assert.Equal(1, margins.Left);
+                Assert.Equal(2, margins.Right);
+                Assert.Equal(3, margins.Top);
+                Assert.Equal(4, margins.Bottom);
+            }
+        }
+
+        [Fact]
+        public void ConvertFrom_Throws()
+        {
+
+            MarginsConverter mc = new MarginsConverter();
+            CultureInfo culture = CultureInfo.InvariantCulture;
+
+            // try once with then once without context
+            for (var context = new MyTypeDescriptorContext(); context != null; context = null)
+            {
+                Assert.Throws<NotSupportedException>(() => mc.ConvertFrom(context, null, null));
+                Assert.Throws<NotSupportedException>(() => mc.ConvertFrom(context, culture, null));
+                Assert.Throws<NotSupportedException>(() => mc.ConvertFrom(context, culture, Guid.NewGuid()));
+                AssertExtensions.Throws<ArgumentException, Exception>(() => mc.ConvertFrom(context, null, "wrong string format"));
+                AssertExtensions.Throws<ArgumentException, Exception>(() => mc.ConvertFrom(context, culture, "wrong string format"));
+            }
+        }
+
+        [Fact]
+        public void ConvertTo()
+        {
+            MarginsConverter mc = new MarginsConverter();
+            Guid guid = Guid.NewGuid();
+            CultureInfo culture = CultureInfo.InvariantCulture;
+            Margins margins = new Margins() { Left = 1, Right = 2, Top = 3, Bottom = 4 };
+
+            // try once with then once without context
+            for (var context = new MyTypeDescriptorContext(); context != null; context = null)
+            {
+                Assert.Equal("1;2;3;4", mc.ConvertTo(context, culture, "1;2;3;4", typeof(string)));
+
+                object converted = mc.ConvertTo(context, culture, margins, typeof(string));
+                Assert.IsType<string>(converted);
+                Assert.Equal(',', culture.TextInfo.ListSeparator[0]);
+                Assert.Equal("1, 2, 3, 4", converted);
+
+                converted = mc.ConvertTo(context, culture, margins, typeof(InstanceDescriptor));
+                Assert.IsType<InstanceDescriptor>(converted);
+                Assert.Equal(new object[] { 1, 2, 3, 4 }, ((InstanceDescriptor)converted).Arguments);
+
+                Assert.Throws<NotSupportedException>(() => mc.ConvertTo(context, culture, new object(), typeof(object)));
+                Assert.Throws<NotSupportedException>(() => mc.ConvertTo(context, culture, 12, typeof(int)));
+                Assert.Throws<NotSupportedException>(() => mc.ConvertTo(context, culture, guid, typeof(Guid)));
+            }
+        }
+
+        private class MyTypeDescriptorContext : ITypeDescriptorContext
+        {
+            public IContainer Container => null;
+            public object Instance { get { return null; } }
+            public PropertyDescriptor PropertyDescriptor { get { return null; } }
+            public bool OnComponentChanging() { return true; }
+            public void OnComponentChanged() { }
+            public object GetService(Type serviceType) { return null; }
+        }
+    }
+}