/************************************************************************************* Extended WPF Toolkit Copyright (C) 2007-2013 Xceed Software Inc. This program is provided to you under the terms of the Microsoft Public License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license For more features, controls, and fast professional support, pick up the Plus Edition at http://xceed.com/wpf_toolkit Stay informed: follow @datagrid on Twitter or Like http://facebook.com/datagrids ***********************************************************************************/ /**************************************************************************\ Copyright Microsoft Corporation. All Rights Reserved. \**************************************************************************/ // This file contains general utilities to aid in development. // Classes here generally shouldn't be exposed publicly since // they're not particular to any library functionality. // Because the classes here are internal, it's likely this file // might be included in multiple assemblies. namespace Standard { using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; internal static partial class Utility { private static readonly Version _osVersion = Environment.OSVersion.Version; private static readonly Version _presentationFrameworkVersion = Assembly.GetAssembly( typeof( Window ) ).GetName().Version; [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] private static bool _MemCmp( IntPtr left, IntPtr right, long cb ) { int offset = 0; for( ; offset < ( cb - sizeof( Int64 ) ); offset += sizeof( Int64 ) ) { Int64 left64 = Marshal.ReadInt64( left, offset ); Int64 right64 = Marshal.ReadInt64( right, offset ); if( left64 != right64 ) { return false; } } for( ; offset < cb; offset += sizeof( byte ) ) { byte left8 = Marshal.ReadByte( left, offset ); byte right8 = Marshal.ReadByte( right, offset ); if( left8 != right8 ) { return false; } } return true; } /// The native RGB macro. /// /// [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static int RGB( Color c ) { return c.R | ( c.G << 8 ) | ( c.B << 16 ); } /// Convert a native integer that represent a color with an alpha channel into a Color struct. /// The integer that represents the color. Its bits are of the format 0xAARRGGBB. /// A Color representation of the parameter. public static Color ColorFromArgbDword( uint color ) { return Color.FromArgb( ( byte )( ( color & 0xFF000000 ) >> 24 ), ( byte )( ( color & 0x00FF0000 ) >> 16 ), ( byte )( ( color & 0x0000FF00 ) >> 8 ), ( byte )( ( color & 0x000000FF ) >> 0 ) ); } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static int GET_X_LPARAM( IntPtr lParam ) { return LOWORD( lParam.ToInt32() ); } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static int GET_Y_LPARAM( IntPtr lParam ) { return HIWORD( lParam.ToInt32() ); } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static int HIWORD( int i ) { return ( short )( i >> 16 ); } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static int LOWORD( int i ) { return ( short )( i & 0xFFFF ); } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] [SuppressMessage( "Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands" )] public static bool AreStreamsEqual( Stream left, Stream right ) { if( null == left ) { return right == null; } if( null == right ) { return false; } if( !left.CanRead || !right.CanRead ) { throw new NotSupportedException( "The streams can't be read for comparison" ); } if( left.Length != right.Length ) { return false; } var length = ( int )left.Length; // seek to beginning left.Position = 0; right.Position = 0; // total bytes read int totalReadLeft = 0; int totalReadRight = 0; // bytes read on this iteration int cbReadLeft = 0; int cbReadRight = 0; // where to store the read data var leftBuffer = new byte[ 512 ]; var rightBuffer = new byte[ 512 ]; // pin the left buffer GCHandle handleLeft = GCHandle.Alloc( leftBuffer, GCHandleType.Pinned ); IntPtr ptrLeft = handleLeft.AddrOfPinnedObject(); // pin the right buffer GCHandle handleRight = GCHandle.Alloc( rightBuffer, GCHandleType.Pinned ); IntPtr ptrRight = handleRight.AddrOfPinnedObject(); try { while( totalReadLeft < length ) { Assert.AreEqual( totalReadLeft, totalReadRight ); cbReadLeft = left.Read( leftBuffer, 0, leftBuffer.Length ); cbReadRight = right.Read( rightBuffer, 0, rightBuffer.Length ); // verify the contents are an exact match if( cbReadLeft != cbReadRight ) { return false; } if( !_MemCmp( ptrLeft, ptrRight, cbReadLeft ) ) { return false; } totalReadLeft += cbReadLeft; totalReadRight += cbReadRight; } Assert.AreEqual( cbReadLeft, cbReadRight ); Assert.AreEqual( totalReadLeft, totalReadRight ); Assert.AreEqual( length, totalReadLeft ); return true; } finally { handleLeft.Free(); handleRight.Free(); } } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static bool GuidTryParse( string guidString, out Guid guid ) { Verify.IsNeitherNullNorEmpty( guidString, "guidString" ); try { guid = new Guid( guidString ); return true; } catch( FormatException ) { } catch( OverflowException ) { } // Doesn't seem to be a valid guid. guid = default( Guid ); return false; } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static bool IsFlagSet( int value, int mask ) { return 0 != ( value & mask ); } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static bool IsFlagSet( uint value, uint mask ) { return 0 != ( value & mask ); } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static bool IsFlagSet( long value, long mask ) { return 0 != ( value & mask ); } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static bool IsFlagSet( ulong value, ulong mask ) { return 0 != ( value & mask ); } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static bool IsOSVistaOrNewer { get { return _osVersion >= new Version( 6, 0 ); } } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static bool IsOSWindows7OrNewer { get { return _osVersion >= new Version( 6, 1 ); } } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static bool IsOSWindows8OrNewer { get { return _osVersion >= new Version( 6, 2 ); } } /// /// Is this using WPF4? /// /// /// There are a few specific bugs in Window in 3.5SP1 and below that require workarounds /// when handling WM_NCCALCSIZE on the HWND. /// public static bool IsPresentationFrameworkVersionLessThan4 { get { return _presentationFrameworkVersion < new Version( 4, 0 ); } } // Caller is responsible for destroying the HICON // Caller is responsible to ensure that GDI+ has been initialized. [SuppressMessage( "Microsoft.Usage", "CA2202:Do not dispose objects multiple times" )] [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static IntPtr GenerateHICON( ImageSource image, Size dimensions ) { if( image == null ) { return IntPtr.Zero; } // If we're getting this from a ".ico" resource, then it comes through as a BitmapFrame. // We can use leverage this as a shortcut to get the right 16x16 representation // because DrawImage doesn't do that for us. var bf = image as BitmapFrame; if( bf != null ) { bf = GetBestMatch( bf.Decoder.Frames, ( int )dimensions.Width, ( int )dimensions.Height ); } else { // Constrain the dimensions based on the aspect ratio. var drawingDimensions = new Rect( 0, 0, dimensions.Width, dimensions.Height ); // There's no reason to assume that the requested image dimensions are square. double renderRatio = dimensions.Width / dimensions.Height; double aspectRatio = image.Width / image.Height; // If it's smaller than the requested size, then place it in the middle and pad the image. if( image.Width <= dimensions.Width && image.Height <= dimensions.Height ) { drawingDimensions = new Rect( ( dimensions.Width - image.Width ) / 2, ( dimensions.Height - image.Height ) / 2, image.Width, image.Height ); } else if( renderRatio > aspectRatio ) { double scaledRenderWidth = ( image.Width / image.Height ) * dimensions.Width; drawingDimensions = new Rect( ( dimensions.Width - scaledRenderWidth ) / 2, 0, scaledRenderWidth, dimensions.Height ); } else if( renderRatio < aspectRatio ) { double scaledRenderHeight = ( image.Height / image.Width ) * dimensions.Height; drawingDimensions = new Rect( 0, ( dimensions.Height - scaledRenderHeight ) / 2, dimensions.Width, scaledRenderHeight ); } var dv = new DrawingVisual(); DrawingContext dc = dv.RenderOpen(); dc.DrawImage( image, drawingDimensions ); dc.Close(); var bmp = new RenderTargetBitmap( ( int )dimensions.Width, ( int )dimensions.Height, 96, 96, PixelFormats.Pbgra32 ); bmp.Render( dv ); bf = BitmapFrame.Create( bmp ); } // Using GDI+ to convert to an HICON. // I'd rather not duplicate their code. using( MemoryStream memstm = new MemoryStream() ) { BitmapEncoder enc = new PngBitmapEncoder(); enc.Frames.Add( bf ); enc.Save( memstm ); using( var istm = new ManagedIStream( memstm ) ) { // We are not bubbling out GDI+ errors when creating the native image fails. IntPtr bitmap = IntPtr.Zero; try { Status gpStatus = NativeMethods.GdipCreateBitmapFromStream( istm, out bitmap ); if( Status.Ok != gpStatus ) { return IntPtr.Zero; } IntPtr hicon; gpStatus = NativeMethods.GdipCreateHICONFromBitmap( bitmap, out hicon ); if( Status.Ok != gpStatus ) { return IntPtr.Zero; } // Caller is responsible for freeing this. return hicon; } finally { Utility.SafeDisposeImage( ref bitmap ); } } } } public static BitmapFrame GetBestMatch( IList frames, int width, int height ) { return _GetBestMatch( frames, _GetBitDepth(), width, height ); } private static int _MatchImage( BitmapFrame frame, int bitDepth, int width, int height, int bpp ) { int score = 2 * _WeightedAbs( bpp, bitDepth, false ) + _WeightedAbs( frame.PixelWidth, width, true ) + _WeightedAbs( frame.PixelHeight, height, true ); return score; } private static int _WeightedAbs( int valueHave, int valueWant, bool fPunish ) { int diff = ( valueHave - valueWant ); if( diff < 0 ) { diff = ( fPunish ? -2 : -1 ) * diff; } return diff; } /// From a list of BitmapFrames find the one that best matches the requested dimensions. /// The methods used here are copied from Win32 sources. We want to be consistent with /// system behaviors. private static BitmapFrame _GetBestMatch( IList frames, int bitDepth, int width, int height ) { int bestScore = int.MaxValue; int bestBpp = 0; int bestIndex = 0; bool isBitmapIconDecoder = frames[ 0 ].Decoder is IconBitmapDecoder; for( int i = 0; i < frames.Count && bestScore != 0; ++i ) { int currentIconBitDepth = isBitmapIconDecoder ? frames[ i ].Thumbnail.Format.BitsPerPixel : frames[ i ].Format.BitsPerPixel; if( currentIconBitDepth == 0 ) { currentIconBitDepth = 8; } int score = _MatchImage( frames[ i ], bitDepth, width, height, currentIconBitDepth ); if( score < bestScore ) { bestIndex = i; bestBpp = currentIconBitDepth; bestScore = score; } else if( score == bestScore ) { // Tie breaker: choose the higher color depth. If that fails, choose first one. if( bestBpp < currentIconBitDepth ) { bestIndex = i; bestBpp = currentIconBitDepth; } } } return frames[ bestIndex ]; } // This can be cached. It's not going to change under reasonable circumstances. private static int s_bitDepth; // = 0; private static int _GetBitDepth() { if( s_bitDepth == 0 ) { using( SafeDC dc = SafeDC.GetDesktop() ) { s_bitDepth = NativeMethods.GetDeviceCaps( dc, DeviceCap.BITSPIXEL ) * NativeMethods.GetDeviceCaps( dc, DeviceCap.PLANES ); } } return s_bitDepth; } /// /// Simple guard against the exceptions that File.Delete throws on null and empty strings. /// /// The path to delete. Unlike File.Delete, this can be null or empty. /// /// Note that File.Delete, and by extension SafeDeleteFile, does not throw an exception /// if the file does not exist. /// [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static void SafeDeleteFile( string path ) { if( !string.IsNullOrEmpty( path ) ) { File.Delete( path ); } } /// GDI's DeleteObject [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static void SafeDeleteObject( ref IntPtr gdiObject ) { IntPtr p = gdiObject; gdiObject = IntPtr.Zero; if( IntPtr.Zero != p ) { NativeMethods.DeleteObject( p ); } } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static void SafeDestroyIcon( ref IntPtr hicon ) { IntPtr p = hicon; hicon = IntPtr.Zero; if( IntPtr.Zero != p ) { NativeMethods.DestroyIcon( p ); } } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static void SafeDestroyWindow( ref IntPtr hwnd ) { IntPtr p = hwnd; hwnd = IntPtr.Zero; if( NativeMethods.IsWindow( p ) ) { NativeMethods.DestroyWindow( p ); } } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static void SafeDispose( ref T disposable ) where T : IDisposable { // Dispose can safely be called on an object multiple times. IDisposable t = disposable; disposable = default( T ); if( null != t ) { t.Dispose(); } } /// GDI+'s DisposeImage /// [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static void SafeDisposeImage( ref IntPtr gdipImage ) { IntPtr p = gdipImage; gdipImage = IntPtr.Zero; if( IntPtr.Zero != p ) { NativeMethods.GdipDisposeImage( p ); } } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] [SuppressMessage( "Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands" )] public static void SafeCoTaskMemFree( ref IntPtr ptr ) { IntPtr p = ptr; ptr = IntPtr.Zero; if( IntPtr.Zero != p ) { Marshal.FreeCoTaskMem( p ); } } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] [SuppressMessage( "Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands" )] public static void SafeFreeHGlobal( ref IntPtr hglobal ) { IntPtr p = hglobal; hglobal = IntPtr.Zero; if( IntPtr.Zero != p ) { Marshal.FreeHGlobal( p ); } } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] [SuppressMessage( "Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands" )] public static void SafeRelease( ref T comObject ) where T : class { T t = comObject; comObject = default( T ); if( null != t ) { Assert.IsTrue( Marshal.IsComObject( t ) ); Marshal.ReleaseComObject( t ); } } /// /// Utility to help classes catenate their properties for implementing ToString(). /// /// The StringBuilder to catenate the results into. /// The name of the property to be catenated. /// The value of the property to be catenated. [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static void GeneratePropertyString( StringBuilder source, string propertyName, string value ) { Assert.IsNotNull( source ); Assert.IsFalse( string.IsNullOrEmpty( propertyName ) ); if( 0 != source.Length ) { source.Append( ' ' ); } source.Append( propertyName ); source.Append( ": " ); if( string.IsNullOrEmpty( value ) ) { source.Append( "" ); } else { source.Append( '\"' ); source.Append( value ); source.Append( '\"' ); } } /// /// Generates ToString functionality for a struct. This is an expensive way to do it, /// it exists for the sake of debugging while classes are in flux. /// Eventually this should just be removed and the classes should /// do this without reflection. /// /// /// /// [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] [Obsolete] public static string GenerateToString( T @object ) where T : struct { var sbRet = new StringBuilder(); foreach( PropertyInfo property in typeof( T ).GetProperties( BindingFlags.Public | BindingFlags.Instance ) ) { if( 0 != sbRet.Length ) { sbRet.Append( ", " ); } Assert.AreEqual( 0, property.GetIndexParameters().Length ); object value = property.GetValue( @object, null ); string format = null == value ? "{0}: " : "{0}: \"{1}\""; sbRet.AppendFormat( format, property.Name, value ); } return sbRet.ToString(); } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static void CopyStream( Stream destination, Stream source ) { Assert.IsNotNull( source ); Assert.IsNotNull( destination ); destination.Position = 0; // If we're copying from, say, a web stream, don't fail because of this. if( source.CanSeek ) { source.Position = 0; // Consider that this could throw because // the source stream doesn't know it's size... destination.SetLength( source.Length ); } var buffer = new byte[ 4096 ]; int cbRead; do { cbRead = source.Read( buffer, 0, buffer.Length ); if( 0 != cbRead ) { destination.Write( buffer, 0, cbRead ); } } while( buffer.Length == cbRead ); // Reset the Seek pointer before returning. destination.Position = 0; } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static string HashStreamMD5( Stream stm ) { stm.Position = 0; var hashBuilder = new StringBuilder(); using( MD5 md5 = MD5.Create() ) { foreach( byte b in md5.ComputeHash( stm ) ) { hashBuilder.Append( b.ToString( "x2", CultureInfo.InvariantCulture ) ); } } return hashBuilder.ToString(); } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static void EnsureDirectory( string path ) { if( !Directory.Exists( Path.GetDirectoryName( path ) ) ) { Directory.CreateDirectory( Path.GetDirectoryName( path ) ); } } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static bool MemCmp( byte[] left, byte[] right, int cb ) { Assert.IsNotNull( left ); Assert.IsNotNull( right ); Assert.IsTrue( cb <= Math.Min( left.Length, right.Length ) ); // pin this buffer GCHandle handleLeft = GCHandle.Alloc( left, GCHandleType.Pinned ); IntPtr ptrLeft = handleLeft.AddrOfPinnedObject(); // pin the other buffer GCHandle handleRight = GCHandle.Alloc( right, GCHandleType.Pinned ); IntPtr ptrRight = handleRight.AddrOfPinnedObject(); bool fRet = _MemCmp( ptrLeft, ptrRight, cb ); handleLeft.Free(); handleRight.Free(); return fRet; } private class _UrlDecoder { private readonly Encoding _encoding; private readonly char[] _charBuffer; private readonly byte[] _byteBuffer; private int _byteCount; private int _charCount; [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public _UrlDecoder( int size, Encoding encoding ) { _encoding = encoding; _charBuffer = new char[ size ]; _byteBuffer = new byte[ size ]; } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public void AddByte( byte b ) { _byteBuffer[ _byteCount++ ] = b; } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public void AddChar( char ch ) { _FlushBytes(); _charBuffer[ _charCount++ ] = ch; } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] private void _FlushBytes() { if( _byteCount > 0 ) { _charCount += _encoding.GetChars( _byteBuffer, 0, _byteCount, _charBuffer, _charCount ); _byteCount = 0; } } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public string GetString() { _FlushBytes(); if( _charCount > 0 ) { return new string( _charBuffer, 0, _charCount ); } return ""; } } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static string UrlDecode( string url ) { if( url == null ) { return null; } var decoder = new _UrlDecoder( url.Length, Encoding.UTF8 ); int length = url.Length; for( int i = 0; i < length; ++i ) { char ch = url[ i ]; if( ch == '+' ) { decoder.AddByte( ( byte )' ' ); continue; } if( ch == '%' && i < length - 2 ) { // decode %uXXXX into a Unicode character. if( url[ i + 1 ] == 'u' && i < length - 5 ) { int a = _HexToInt( url[ i + 2 ] ); int b = _HexToInt( url[ i + 3 ] ); int c = _HexToInt( url[ i + 4 ] ); int d = _HexToInt( url[ i + 5 ] ); if( a >= 0 && b >= 0 && c >= 0 && d >= 0 ) { decoder.AddChar( ( char )( ( a << 12 ) | ( b << 8 ) | ( c << 4 ) | d ) ); i += 5; continue; } } else { // decode %XX into a Unicode character. int a = _HexToInt( url[ i + 1 ] ); int b = _HexToInt( url[ i + 2 ] ); if( a >= 0 && b >= 0 ) { decoder.AddByte( ( byte )( ( a << 4 ) | b ) ); i += 2; continue; } } } // Add any 7bit character as a byte. if( ( ch & 0xFF80 ) == 0 ) { decoder.AddByte( ( byte )ch ); } else { decoder.AddChar( ch ); } } return decoder.GetString(); } /// /// Encodes a URL string. Duplicated functionality from System.Web.HttpUtility.UrlEncode. /// /// /// /// /// Duplicated from System.Web.HttpUtility because System.Web isn't part of the client profile. /// URL Encoding replaces ' ' with '+' and unsafe ASCII characters with '%XX'. /// Safe characters are defined in RFC2396 (http://www.ietf.org/rfc/rfc2396.txt). /// They are the 7-bit ASCII alphanumerics and the mark characters "-_.!~*'()". /// This implementation does not treat '~' as a safe character to be consistent with the System.Web version. /// [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] public static string UrlEncode( string url ) { if( url == null ) { return null; } byte[] bytes = Encoding.UTF8.GetBytes( url ); bool needsEncoding = false; int unsafeCharCount = 0; foreach( byte b in bytes ) { if( b == ' ' ) { needsEncoding = true; } else if( !_UrlEncodeIsSafe( b ) ) { ++unsafeCharCount; needsEncoding = true; } } if( needsEncoding ) { var buffer = new byte[ bytes.Length + ( unsafeCharCount * 2 ) ]; int writeIndex = 0; foreach( byte b in bytes ) { if( _UrlEncodeIsSafe( b ) ) { buffer[ writeIndex++ ] = b; } else if( b == ' ' ) { buffer[ writeIndex++ ] = ( byte )'+'; } else { buffer[ writeIndex++ ] = ( byte )'%'; buffer[ writeIndex++ ] = _IntToHex( ( b >> 4 ) & 0xF ); buffer[ writeIndex++ ] = _IntToHex( b & 0xF ); } } bytes = buffer; Assert.AreEqual( buffer.Length, writeIndex ); } return Encoding.ASCII.GetString( bytes ); } // HttpUtility's UrlEncode is slightly different from the RFC. // RFC2396 describes unreserved characters as alphanumeric or // the list "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" // The System.Web version unnecessarily escapes '~', which should be okay... // Keeping that same pattern here just to be consistent. [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] private static bool _UrlEncodeIsSafe( byte b ) { if( _IsAsciiAlphaNumeric( b ) ) { return true; } switch( ( char )b ) { case '-': case '_': case '.': case '!': //case '~': case '*': case '\'': case '(': case ')': return true; } return false; } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] private static bool _IsAsciiAlphaNumeric( byte b ) { return ( b >= 'a' && b <= 'z' ) || ( b >= 'A' && b <= 'Z' ) || ( b >= '0' && b <= '9' ); } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] private static byte _IntToHex( int n ) { Assert.BoundedInteger( 0, n, 16 ); if( n <= 9 ) { return ( byte )( n + '0' ); } return ( byte )( n - 10 + 'A' ); } [SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] private static int _HexToInt( char h ) { if( h >= '0' && h <= '9' ) { return h - '0'; } if( h >= 'a' && h <= 'f' ) { return h - 'a' + 10; } if( h >= 'A' && h <= 'F' ) { return h - 'A' + 10; } Assert.Fail( "Invalid hex character " + h ); return -1; } public static void AddDependencyPropertyChangeListener( object component, DependencyProperty property, EventHandler listener ) { if( component == null ) { return; } Assert.IsNotNull( property ); Assert.IsNotNull( listener ); DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty( property, component.GetType() ); dpd.AddValueChanged( component, listener ); } public static void RemoveDependencyPropertyChangeListener( object component, DependencyProperty property, EventHandler listener ) { if( component == null ) { return; } Assert.IsNotNull( property ); Assert.IsNotNull( listener ); DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty( property, component.GetType() ); dpd.RemoveValueChanged( component, listener ); } #region Extension Methods public static bool IsThicknessNonNegative( Thickness thickness ) { if( !IsDoubleFiniteAndNonNegative( thickness.Top ) ) { return false; } if( !IsDoubleFiniteAndNonNegative( thickness.Left ) ) { return false; } if( !IsDoubleFiniteAndNonNegative( thickness.Bottom ) ) { return false; } if( !IsDoubleFiniteAndNonNegative( thickness.Right ) ) { return false; } return true; } public static bool IsCornerRadiusValid( CornerRadius cornerRadius ) { if( !IsDoubleFiniteAndNonNegative( cornerRadius.TopLeft ) ) { return false; } if( !IsDoubleFiniteAndNonNegative( cornerRadius.TopRight ) ) { return false; } if( !IsDoubleFiniteAndNonNegative( cornerRadius.BottomLeft ) ) { return false; } if( !IsDoubleFiniteAndNonNegative( cornerRadius.BottomRight ) ) { return false; } return true; } public static bool IsDoubleFiniteAndNonNegative( double d ) { if( double.IsNaN( d ) || double.IsInfinity( d ) || d < 0 ) { return false; } return true; } #endregion } }