1 //------------------------------------------------------------------------------ 2 // <copyright file="ColorConverter.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 //------------------------------------------------------------------------------ 6 7 /* 8 */ 9 namespace System.Drawing { 10 using System.Runtime.Serialization.Formatters; 11 using System.Runtime.InteropServices; 12 using System.Diagnostics; 13 using System.Diagnostics.CodeAnalysis; 14 using Microsoft.Win32; 15 using System.Collections; 16 using System.ComponentModel; 17 using System.ComponentModel.Design.Serialization; 18 using System.Globalization; 19 using System.Reflection; 20 using System.Threading; 21 22 /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter"]/*' /> 23 /// <devdoc> 24 /// ColorConverter is a class that can be used to convert 25 /// colors from one data type to another. Access this 26 /// class through the TypeDescriptor. 27 /// </devdoc> 28 public class ColorConverter : TypeConverter { 29 private static string ColorConstantsLock = "colorConstants"; 30 private static Hashtable colorConstants; 31 private static string SystemColorConstantsLock = "systemColorConstants"; 32 private static Hashtable systemColorConstants; 33 private static string ValuesLock = "values"; 34 private static StandardValuesCollection values; 35 36 /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.ColorConverter"]/*' /> 37 /// <devdoc> 38 /// <para>[To be supplied.]</para> 39 /// </devdoc> ColorConverter()40 public ColorConverter() { 41 } 42 43 /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.Colors"]/*' /> 44 /// <devdoc> 45 /// Hashtable of color / value pairs (color name is key) 46 /// for standard colors. 47 /// </devdoc> 48 private static Hashtable Colors { 49 get { 50 if (colorConstants == null) { 51 lock(ColorConstantsLock) { 52 if (colorConstants == null) { 53 Hashtable tempHash = new Hashtable(StringComparer.OrdinalIgnoreCase); 54 FillConstants(tempHash, typeof(Color)); 55 colorConstants = tempHash; 56 } 57 } 58 } 59 60 return colorConstants; 61 } 62 } 63 64 /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.SystemColors"]/*' /> 65 /// <devdoc> 66 /// Hashtable of color / value pairs (color name is key) 67 /// for system colors. 68 /// </devdoc> 69 private static Hashtable SystemColors { 70 get { 71 if (systemColorConstants == null) { 72 lock (SystemColorConstantsLock) { 73 if (systemColorConstants == null) { 74 Hashtable tempHash = new Hashtable(StringComparer.OrdinalIgnoreCase); 75 FillConstants(tempHash, typeof(System.Drawing.SystemColors)); 76 systemColorConstants = tempHash; 77 } 78 } 79 } 80 81 return systemColorConstants; 82 } 83 } 84 85 /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.CanConvertFrom"]/*' /> 86 /// <devdoc> 87 /// Determines if this converter can convert an object in the given source 88 /// type to the native type of the converter. 89 /// </devdoc> CanConvertFrom(ITypeDescriptorContext context, Type sourceType)90 public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { 91 if (sourceType == typeof(string)) { 92 return true; 93 } 94 return base.CanConvertFrom(context, sourceType); 95 } 96 97 /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.CanConvertTo"]/*' /> 98 /// <devdoc> 99 /// <para>Gets a value indicating whether this converter can 100 /// convert an object to the given destination type using the context.</para> 101 /// </devdoc> CanConvertTo(ITypeDescriptorContext context, Type destinationType)102 public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { 103 if (destinationType == typeof(InstanceDescriptor)) { 104 return true; 105 } 106 return base.CanConvertTo(context, destinationType); 107 } 108 GetNamedColor(string name)109 internal static object GetNamedColor(string name) { 110 object color = null; 111 // First, check to see if this is a standard name. 112 // 113 color = Colors[name]; 114 if (color != null) { 115 return color; 116 } 117 // Ok, how about a system color? 118 // 119 color = SystemColors[name]; 120 return color; 121 } 122 123 /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.ConvertFrom"]/*' /> 124 /// <devdoc> 125 /// Converts the given object to the converter's native type. 126 /// </devdoc> 127 [SuppressMessage("Microsoft.Performance", "CA1808:AvoidCallsThatBoxValueTypes")] ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)128 public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { 129 string strValue = value as string; 130 if (strValue != null) { 131 object obj = null; 132 string text = strValue.Trim(); 133 134 if (text.Length == 0) { 135 obj = Color.Empty; 136 } 137 else { 138 // First, check to see if this is a standard name. 139 // 140 obj = GetNamedColor(text); 141 142 if (obj == null) { 143 if (culture == null) { 144 culture = CultureInfo.CurrentCulture; 145 } 146 147 char sep = culture.TextInfo.ListSeparator[0]; 148 bool tryMappingToKnownColor = true; 149 150 TypeConverter intConverter = TypeDescriptor.GetConverter(typeof(int)); 151 152 // If the value is a 6 digit hex number only, then 153 // we want to treat the Alpha as 255, not 0 154 // 155 if (text.IndexOf(sep) == -1) { 156 157 // text can be '' (empty quoted string) 158 if (text.Length >= 2 && (text[0] == '\'' || text[0] == '"') && text[0] == text[text.Length -1]) { 159 // In quotes means a named value 160 string colorName = text.Substring(1, text.Length - 2); 161 obj = Color.FromName(colorName); 162 tryMappingToKnownColor = false; 163 } 164 else if ((text.Length == 7 && text[0] == '#') || 165 (text.Length == 8 && (text.StartsWith("0x") || text.StartsWith("0X"))) || 166 (text.Length == 8 && (text.StartsWith("&h") || text.StartsWith("&H")))) { 167 // Note: ConvertFromString will raise exception if value cannot be converted. 168 obj = Color.FromArgb(unchecked((int)(0xFF000000 | (uint)(int)intConverter.ConvertFromString(context, culture, text)))); 169 } 170 } 171 172 // Nope. Parse the RGBA from the text. 173 // 174 if (obj == null) { 175 string[] tokens = text.Split(new char[] {sep}); 176 int[] values = new int[tokens.Length]; 177 for (int i = 0; i < values.Length; i++) { 178 values[i] = unchecked((int)intConverter.ConvertFromString(context, culture, tokens[i])); 179 } 180 181 // We should now have a number of parsed integer values. 182 // We support 1, 3, or 4 arguments: 183 // 184 // 1 -- full ARGB encoded 185 // 3 -- RGB 186 // 4 -- ARGB 187 // 188 switch (values.Length) { 189 case 1: 190 obj = Color.FromArgb(values[0]); 191 break; 192 193 case 3: 194 obj = Color.FromArgb(values[0], values[1], values[2]); 195 break; 196 197 case 4: 198 obj = Color.FromArgb(values[0], values[1], values[2], values[3]); 199 break; 200 } 201 tryMappingToKnownColor = true; 202 } 203 204 if ((obj != null) && tryMappingToKnownColor) { 205 206 // Now check to see if this color matches one of our known colors. 207 // If it does, then substitute it. We can only do this for "Colors" 208 // because system colors morph with user settings. 209 // 210 int targetARGB = ((Color)obj).ToArgb(); 211 212 foreach (Color c in Colors.Values) { 213 if (c.ToArgb() == targetARGB) { 214 obj = c; 215 break; 216 } 217 } 218 } 219 } 220 221 if (obj == null) { 222 throw new ArgumentException(SR.Format(SR.InvalidColor, text)); 223 } 224 } 225 return obj; 226 } 227 return base.ConvertFrom(context, culture, value); 228 } 229 230 /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.ConvertTo"]/*' /> 231 /// <devdoc> 232 /// Converts the given object to another type. The most common types to convert 233 /// are to and from a string object. The default implementation will make a call 234 /// to ToString on the object if the object is valid and if the destination 235 /// type is string. If this cannot convert to the desitnation type, this will 236 /// throw a NotSupportedException. 237 /// </devdoc> ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)238 public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { 239 if (destinationType == null) { 240 throw new ArgumentNullException("destinationType"); 241 } 242 243 if( value is Color ){ 244 if (destinationType == typeof(string)) { 245 Color c = (Color)value; 246 247 if (c == Color.Empty) { 248 return string.Empty; 249 } 250 else { 251 // If this is a known color, then Color can provide its own 252 // name. Otherwise, we fabricate an ARGB value for it. 253 // 254 if (c.IsKnownColor) { 255 return c.Name; 256 } 257 else if (c.IsNamedColor) { 258 return "'" + c.Name + "'"; 259 } 260 else { 261 if (culture == null) { 262 culture = CultureInfo.CurrentCulture; 263 } 264 string sep = culture.TextInfo.ListSeparator + " "; 265 TypeConverter intConverter = TypeDescriptor.GetConverter(typeof(int)); 266 string[] args; 267 int nArg = 0; 268 269 if (c.A < 255) { 270 args = new string[4]; 271 args[nArg++] = intConverter.ConvertToString(context, culture, (object)c.A); 272 } 273 else { 274 args = new string[3]; 275 } 276 277 // Note: ConvertToString will raise exception if value cannot be converted. 278 args[nArg++] = intConverter.ConvertToString(context, culture, (object)c.R); 279 args[nArg++] = intConverter.ConvertToString(context, culture, (object)c.G); 280 args[nArg++] = intConverter.ConvertToString(context, culture, (object)c.B); 281 282 // Now slam all of these together with the fantastic Join 283 // method. 284 // 285 return string.Join(sep, args); 286 } 287 } 288 } 289 if (destinationType == typeof(InstanceDescriptor)) { 290 MemberInfo member = null; 291 object[] args = null; 292 293 Color c = (Color)value; 294 295 if (c.IsEmpty) { 296 member = typeof(Color).GetField("Empty"); 297 } 298 else if (c.IsSystemColor) { 299 member = typeof(SystemColors).GetProperty(c.Name); 300 } 301 else if (c.IsKnownColor) { 302 member = typeof(Color).GetProperty(c.Name); 303 } 304 else if (c.A != 255) { 305 member = typeof(Color).GetMethod("FromArgb", new Type[] {typeof(int), typeof(int), typeof(int), typeof(int)}); 306 args = new object[] {c.A, c.R, c.G, c.B}; 307 } 308 else if (c.IsNamedColor) { 309 member = typeof(Color).GetMethod("FromName", new Type[] {typeof(string)}); 310 args = new object[] {c.Name}; 311 } 312 else { 313 member = typeof(Color).GetMethod("FromArgb", new Type[] {typeof(int), typeof(int), typeof(int)}); 314 args = new object[] {c.R, c.G, c.B}; 315 } 316 317 Debug.Assert(member != null, "Could not convert color to member. Did someone change method name / signature and not update Colorconverter?"); 318 if (member != null) { 319 return new InstanceDescriptor(member, args); 320 } 321 else { 322 return null; 323 } 324 } 325 } 326 327 return base.ConvertTo(context, culture, value, destinationType); 328 } 329 330 /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.FillConstants"]/*' /> 331 /// <devdoc> 332 /// Fills the given hashtable with field name / value pairs. It walks all public static 333 /// properties of enumType that have a property type of Color. 334 /// </devdoc> FillConstants(Hashtable hash, Type enumType)335 private static void FillConstants(Hashtable hash, Type enumType) { 336 MethodAttributes attrs = MethodAttributes.Public | MethodAttributes.Static; 337 PropertyInfo[] props = enumType.GetProperties(); 338 339 for (int i = 0; i < props.Length; i++) { 340 PropertyInfo prop = props[i]; 341 if (prop.PropertyType == typeof(Color)) { 342 MethodInfo method = prop.GetGetMethod(); 343 if (method != null && (method.Attributes & attrs) == attrs) { 344 object[] tempIndex = null; 345 hash[prop.Name] = prop.GetValue(null, tempIndex); 346 } 347 } 348 } 349 } 350 351 /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.GetStandardValues"]/*' /> 352 /// <devdoc> 353 /// Retrieves a collection containing a set of standard values 354 /// for the data type this validator is designed for. This 355 /// will return null if the data type does not support a 356 /// standard set of values. 357 /// </devdoc> GetStandardValues(ITypeDescriptorContext context)358 public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { 359 if (values == null) { 360 lock (ValuesLock) { 361 if (values == null) { 362 363 // We must take the value from each hashtable and combine them. 364 // 365 ArrayList arrayValues = new ArrayList(); 366 arrayValues.AddRange(Colors.Values); 367 arrayValues.AddRange(SystemColors.Values); 368 369 // Now, we have a couple of colors that have the same names but 370 // are identical values. Look for these and remove them. Too 371 // bad this is n^2. 372 // 373 int count = arrayValues.Count; 374 for (int i = 0; i < count - 1; i++) { 375 for (int j = i + 1; j < count; j++) { 376 if (arrayValues[i].Equals(arrayValues[j])) { 377 // Remove this item! 378 // 379 arrayValues.RemoveAt(j); 380 count--; 381 j--; 382 } 383 } 384 } 385 386 // Sort the array. 387 // 388 arrayValues.Sort(0, arrayValues.Count, new ColorComparer()); 389 values = new StandardValuesCollection(arrayValues.ToArray()); 390 } 391 } 392 } 393 394 return values; 395 } 396 397 /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.GetStandardValuesSupported"]/*' /> 398 /// <devdoc> 399 /// Determines if this object supports a standard set of values 400 /// that can be picked from a list. 401 /// </devdoc> GetStandardValuesSupported(ITypeDescriptorContext context)402 public override bool GetStandardValuesSupported(ITypeDescriptorContext context) { 403 return true; 404 } 405 406 /// <include file='doc\ColorConverter.uex' path='docs/doc[@for="ColorConverter.ColorComparer"]/*' /> 407 /// <devdoc> 408 /// IComparer for color values. This takes color values but compares their 409 /// names. 410 /// </devdoc> 411 private class ColorComparer : IComparer { 412 Compare(object left, object right)413 public int Compare(object left, object right) { 414 Color cLeft = (Color)left; 415 Color cRight = (Color)right; 416 return string.Compare(cLeft.Name, cRight.Name, false, CultureInfo.InvariantCulture); 417 } 418 } 419 } 420 } 421 422 423 424