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.
1062 lines
37 KiB
1062 lines
37 KiB
/*************************************************************************************
|
|
|
|
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;
|
|
}
|
|
|
|
/// <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));
|
|
Marshal.ReleaseComObject(t);
|
|
}
|
|
}
|
|
|
|
/// <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
|
|
}
|
|
}
|
|
|