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
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
|
|
}
|
|
}
|
|
|