All the controls missing in WPF. Over 1 million downloads.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1074 lines
33 KiB

/*************************************************************************************
Toolkit for WPF
Copyright (C) 2007-2020 Xceed Software Inc.
This program is provided to you under the terms of the XCEED SOFTWARE, INC.
COMMUNITY LICENSE AGREEMENT (for non-commercial use) as published at
https://github.com/xceedsoftware/wpftoolkit/blob/master/license.md
For more features, controls, and fast professional support,
pick up the Plus Edition at https://xceed.com/xceed-toolkit-plus-for-wpf/
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;
}
/// <summary>The native RGB macro.</summary>
/// <param name="c"></param>
/// <returns></returns>
[SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
public static int RGB( Color c )
{
return c.R | ( c.G << 8 ) | ( c.B << 16 );
}
/// <summary>Convert a native integer that represent a color with an alpha channel into a Color struct.</summary>
/// <param name="color">The integer that represents the color. Its bits are of the format 0xAARRGGBB.</param>
/// <returns>A Color representation of the parameter.</returns>
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 );
}
}
/// <summary>
/// Is this using WPF4?
/// </summary>
/// <remarks>
/// There are a few specific bugs in Window in 3.5SP1 and below that require workarounds
/// when handling WM_NCCALCSIZE on the HWND.
/// </remarks>
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<BitmapFrame> 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<BitmapFrame> 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;
}
/// <summary>
/// Simple guard against the exceptions that File.Delete throws on null and empty strings.
/// </summary>
/// <param name="path">The path to delete. Unlike File.Delete, this can be null or empty.</param>
/// <remarks>
/// Note that File.Delete, and by extension SafeDeleteFile, does not throw an exception
/// if the file does not exist.
/// </remarks>
[SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
public static void SafeDeleteFile( string path )
{
if( !string.IsNullOrEmpty( path ) )
{
File.Delete( path );
}
}
/// <summary>GDI's DeleteObject</summary>
[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<T>( 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();
}
}
/// <summary>GDI+'s DisposeImage</summary>
/// <param name="gdipImage"></param>
[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<T>( ref T comObject ) where T : class
{
T t = comObject;
comObject = default( T );
if( null != t )
{
Assert.IsTrue( Marshal.IsComObject( t ) );
#if !NETCORE && !NET5
Marshal.ReleaseComObject( t );
#endif
}
}
/// <summary>
/// Utility to help classes catenate their properties for implementing ToString().
/// </summary>
/// <param name="source">The StringBuilder to catenate the results into.</param>
/// <param name="propertyName">The name of the property to be catenated.</param>
/// <param name="value">The value of the property to be catenated.</param>
[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( "<null>" );
}
else
{
source.Append( '\"' );
source.Append( value );
source.Append( '\"' );
}
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="object"></param>
/// <returns></returns>
[SuppressMessage( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )]
[Obsolete]
public static string GenerateToString<T>( 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}: <null>" : "{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();
}
/// <summary>
/// Encodes a URL string. Duplicated functionality from System.Web.HttpUtility.UrlEncode.
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
/// <remarks>
/// 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.
/// </remarks>
[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
}
}