.InnerClass.foo(System.String[], int&)
+ Formal = 0,
+ // Signature style used for our COM engine to filter method
+ // Full quailfied type name, no method parameters, only records number of generic parameters for classes
+ // An example: NSName1.NSName2.OuterClass`2.InnerClass`2.foo
+ Com = 1
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Input/Key.cs b/src/dotnetCampus.UITest.WPFTestHelper/Input/Key.cs
new file mode 100644
index 0000000..cfbfe9a
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Input/Key.cs
@@ -0,0 +1,1039 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+
+namespace dotnetCampus.UITest.WPFTestHelper.Input
+{
+ ///
+ /// An enumeration of all the possible key values on a keyboard.
+ ///
+ public enum Key : int
+ {
+ ///
+ /// No key pressed.
+ ///
+ None = 0,
+
+ ///
+ /// The CANCEL key.
+ ///
+ Cancel = 0x03,
+
+ ///
+ /// The BACKSPACE key.
+ ///
+ Back = 0x08,
+
+ ///
+ /// The TAB key.
+ ///
+ Tab = 0x09,
+
+ ///
+ /// The LineFeed key.
+ ///
+ LineFeed,
+
+ ///
+ /// The CLEAR key.
+ ///
+ Clear = 0x0C,
+
+ ///
+ /// The RETURN key.
+ ///
+ Return = 0x0D,
+
+ ///
+ /// The ENTER key.
+ ///
+ Enter = Return,
+
+ ///
+ /// The SHIFT key.
+ ///
+ Shift = 0x10,
+
+ ///
+ /// The CTRL key.
+ ///
+ Ctrl = 0x11,
+
+ ///
+ /// The ALT key.
+ ///
+ Alt = 0x12,
+
+ ///
+ /// The PAUSE key.
+ ///
+ Pause = 0x13,
+
+ ///
+ /// The CAPS LOCK key.
+ ///
+ Capital = 0x14,
+
+ ///
+ /// The CAPS LOCK key.
+ ///
+ CapsLock = Capital,
+
+ ///
+ /// The IME Kana mode key.
+ ///
+ KanaMode = 0x15,
+
+ ///
+ /// The IME Hangul mode key.
+ ///
+ HangulMode = KanaMode,
+
+ ///
+ /// The IME Junja mode key.
+ ///
+ JunjaMode = 0x17,
+
+ ///
+ /// The IME Final mode key.
+ ///
+ FinalMode= 0x18,
+
+ ///
+ /// The IME Hanja mode key.
+ ///
+ HanjaMode = 0x19,
+
+ ///
+ /// The IME Kanji mode key.
+ ///
+ KanjiMode = HanjaMode,
+
+ ///
+ /// The ESC key.
+ ///
+ Escape = 0x1B,
+
+ ///
+ /// The IME Convert key.
+ ///
+ ImeConvert = 0x1C,
+
+ ///
+ /// The IME NonConvert key.
+ ///
+ ImeNonConvert = 0x1D,
+
+ ///
+ /// The IME Accept key.
+ ///
+ ImeAccept = 0x1E,
+
+ ///
+ /// The IME Mode change request.
+ ///
+ ImeModeChange = 0x1F,
+
+ ///
+ /// The SPACEBAR key.
+ ///
+ Space = 0x20,
+
+ ///
+ /// The PAGE UP key.
+ ///
+ Prior = 0x21,
+
+ ///
+ /// The PAGE UP key.
+ ///
+ PageUp = Prior,
+
+ ///
+ /// The PAGE DOWN key.
+ ///
+ Next = 0x22,
+
+ ///
+ /// The PAGE DOWN key.
+ ///
+ PageDown = Next,
+
+ ///
+ /// The END key.
+ ///
+ End = 0x23,
+
+ ///
+ /// The HOME key.
+ ///
+ Home = 0x24,
+
+ ///
+ /// The LEFT ARROW key.
+ ///
+ Left = 0x25,
+
+ ///
+ /// The UP ARROW key.
+ ///
+ Up = 0x26,
+
+ ///
+ /// The RIGHT ARROW key.
+ ///
+ Right = 0x27,
+
+ ///
+ /// The DOWN ARROW key.
+ ///
+ Down = 0x28,
+
+ ///
+ /// The SELECT key.
+ ///
+ Select = 0x29,
+
+ ///
+ /// The PRINT key.
+ ///
+ Print = 0x2A,
+
+ ///
+ /// The EXECUTE key.
+ ///
+ Execute = 0x2B,
+
+ ///
+ /// The SNAPSHOT key.
+ ///
+ Snapshot = 0x2C,
+
+ ///
+ /// The PRINT SCREEN key.
+ ///
+ PrintScreen = Snapshot,
+
+ ///
+ /// The INS key.
+ ///
+ Insert = 0x2D,
+
+ ///
+ /// The DEL key.
+ ///
+ Delete = 0x2E,
+
+ ///
+ /// The HELP key.
+ ///
+ Help = 0x2F,
+
+ ///
+ /// The 0 key.
+ ///
+ D0 = 0x30,
+
+ ///
+ /// The 1 key.
+ ///
+ D1 = 0x31,
+
+ ///
+ /// The 2 key.
+ ///
+ D2 = 0x32,
+
+ ///
+ /// The 3 key.
+ ///
+ D3 = 0x33,
+
+ ///
+ /// The 4 key.
+ ///
+ D4 = 0x34,
+
+ ///
+ /// The 5 key.
+ ///
+ D5 = 0x35,
+
+ ///
+ /// The 6 key.
+ ///
+ D6 = 0x36,
+
+ ///
+ /// The 7 key.
+ ///
+ D7 = 0x37,
+
+ ///
+ /// The 8 key.
+ ///
+ D8 = 0x38,
+
+ ///
+ /// The 9 key.
+ ///
+ D9 = 0x39,
+
+ ///
+ /// The A key.
+ ///
+ A = 0x41,
+
+ ///
+ /// The B key.
+ ///
+ B = 0x42,
+
+ ///
+ /// The C key.
+ ///
+ C = 0x43,
+
+ ///
+ /// The D key.
+ ///
+ D = 0x44,
+
+ ///
+ /// The E key.
+ ///
+ E = 0x45,
+
+ ///
+ /// The F key.
+ ///
+ F = 0x46,
+
+ ///
+ /// The G key.
+ ///
+ G = 0x47,
+
+ ///
+ /// The H key.
+ ///
+ H = 0x48,
+
+ ///
+ /// The I key.
+ ///
+ I = 0x49,
+
+ ///
+ /// The J key.
+ ///
+ J = 0x4A,
+
+ ///
+ /// The K key.
+ ///
+ K = 0x4B,
+
+ ///
+ /// The L key.
+ ///
+ L = 0x4C,
+
+ ///
+ /// The M key.
+ ///
+ M = 0x4D,
+
+ ///
+ /// The N key.
+ ///
+ N = 0x4E,
+
+ ///
+ /// The O key.
+ ///
+ O = 0x4F,
+
+ ///
+ /// The P key.
+ ///
+ P = 0x50,
+
+ ///
+ /// The Q key.
+ ///
+ Q = 0x51,
+
+ ///
+ /// The R key.
+ ///
+ R = 0x52,
+
+ ///
+ /// The S key.
+ ///
+ S = 0x53,
+
+ ///
+ /// The T key.
+ ///
+ T = 0x54,
+
+ ///
+ /// The U key.
+ ///
+ U = 0x55,
+
+ ///
+ /// The V key.
+ ///
+ V = 0x56,
+
+ ///
+ /// The W key.
+ ///
+ W = 0x57,
+
+ ///
+ /// The X key.
+ ///
+ X = 0x58,
+
+ ///
+ /// The Y key.
+ ///
+ Y = 0x59,
+
+ ///
+ /// The Z key.
+ ///
+ Z = 0x5A,
+
+ ///
+ /// The left Windows logo key (Microsoft Natural Keyboard).
+ ///
+ LWin = 0x5B,
+
+ ///
+ /// The right Windows logo key (Microsoft Natural Keyboard).
+ ///
+ RWin = 0x5C,
+
+ ///
+ /// The Application key (Microsoft Natural Keyboard).
+ ///
+ Apps = 0x5D,
+
+ ///
+ /// The Computer Sleep key.
+ ///
+ Sleep = 0x5F,
+
+ ///
+ /// The 0 key on the numeric keypad.
+ ///
+ NumPad0 = 0x60,
+
+ ///
+ /// The 1 key on the numeric keypad.
+ ///
+ NumPad1 = 0x61,
+
+ ///
+ /// The 2 key on the numeric keypad.
+ ///
+ NumPad2 = 0x62,
+
+ ///
+ /// The 3 key on the numeric keypad.
+ ///
+ NumPad3 = 0x63,
+
+ ///
+ /// The 4 key on the numeric keypad.
+ ///
+ NumPad4 = 0x64,
+
+ ///
+ /// The 5 key on the numeric keypad.
+ ///
+ NumPad5 = 0x65,
+
+ ///
+ /// The 6 key on the numeric keypad.
+ ///
+ NumPad6 = 0x66,
+
+ ///
+ /// The 7 key on the numeric keypad.
+ ///
+ NumPad7 = 0x67,
+
+ ///
+ /// The 8 key on the numeric keypad.
+ ///
+ NumPad8 = 0x68,
+
+ ///
+ /// The 9 key on the numeric keypad.
+ ///
+ NumPad9 = 0x69,
+
+ ///
+ /// The Multiply key.
+ ///
+ Multiply = 0x6A,
+
+ ///
+ /// The Add key.
+ ///
+ Add = 0x6B,
+
+ ///
+ /// The Separator key.
+ ///
+ Separator = 0x6C,
+
+ ///
+ /// The Subtract key.
+ ///
+ Subtract = 0x6D,
+
+ ///
+ /// The Decimal key.
+ ///
+ Decimal = 0x6E,
+
+ ///
+ /// The Divide key.
+ ///
+ Divide = 0x6F,
+
+ ///
+ /// The F1 key.
+ ///
+ F1 = 0x70,
+
+ ///
+ /// The F2 key.
+ ///
+ F2 = 0x71,
+
+ ///
+ /// The F3 key.
+ ///
+ F3 = 0x72,
+
+ ///
+ /// The F4 key.
+ ///
+ F4 = 0x73,
+
+ ///
+ /// The F5 key.
+ ///
+ F5 = 0x74,
+
+ ///
+ /// The F6 key.
+ ///
+ F6 = 0x75,
+
+ ///
+ /// The F7 key.
+ ///
+ F7 = 0x76,
+
+ ///
+ /// The F8 key.
+ ///
+ F8 = 0x77,
+
+ ///
+ /// The F9 key.
+ ///
+ F9 = 0x78,
+
+ ///
+ /// The F10 key.
+ ///
+ F10 = 0x79,
+
+ ///
+ /// The F11 key.
+ ///
+ F11 = 0x7A,
+
+ ///
+ /// The F12 key.
+ ///
+ F12 = 0x7B,
+
+ ///
+ /// The F13 key.
+ ///
+ F13 = 0x7C,
+
+ ///
+ /// The F14 key.
+ ///
+ F14 = 0x7D,
+
+ ///
+ /// The F15 key.
+ ///
+ F15 = 0x7E,
+
+ ///
+ /// The F16 key.
+ ///
+ F16 = 0x7F,
+
+ ///
+ /// The F17 key.
+ ///
+ F17 = 0x80,
+
+ ///
+ /// The F18 key.
+ ///
+ F18 = 0x81,
+
+ ///
+ /// The F19 key.
+ ///
+ F19 = 0x82,
+
+ ///
+ /// The F20 key.
+ ///
+ F20 = 0x83,
+
+ ///
+ /// The F21 key.
+ ///
+ F21 = 0x84,
+
+ ///
+ /// The F22 key.
+ ///
+ F22 = 0x85,
+
+ ///
+ /// The F23 key.
+ ///
+ F23 = 0x86,
+
+ ///
+ /// The F24 key.
+ ///
+ F24 = 0x87,
+
+ ///
+ /// The NUM LOCK key.
+ ///
+ NumLock = 0x90,
+
+ ///
+ /// The SCROLL LOCK key.
+ ///
+ Scroll = 0x91,
+
+ ///
+ /// The left SHIFT key.
+ ///
+ LeftShift = 0xA0,
+
+ ///
+ /// The right SHIFT key.
+ ///
+ RightShift = 0xA1,
+
+ ///
+ /// The left CTRL key.
+ ///
+ LeftCtrl = 0xA2,
+
+ ///
+ /// The right CTRL key.
+ ///
+ RightCtrl = 0xA3,
+
+ ///
+ /// The left ALT key.
+ ///
+ LeftAlt = 0xA4,
+
+ ///
+ /// The right ALT key.
+ ///
+ RightAlt = 0xA5,
+
+ ///
+ /// The Browser Back key.
+ ///
+ BrowserBack = 0xA6,
+
+ ///
+ /// The Browser Forward key.
+ ///
+ BrowserForward = 0xA7,
+
+ ///
+ /// The Browser Refresh key.
+ ///
+ BrowserRefresh = 0xA8,
+
+ ///
+ /// The Browser Stop key.
+ ///
+ BrowserStop = 0xA9,
+
+ ///
+ /// The Browser Search key.
+ ///
+ BrowserSearch = 0xAA,
+
+ ///
+ /// The Browser Favorites key.
+ ///
+ BrowserFavorites = 0xAB,
+
+ ///
+ /// The Browser Home key.
+ ///
+ BrowserHome = 0xAC,
+
+ ///
+ /// The Volume Mute key.
+ ///
+ VolumeMute = 0xAD,
+
+ ///
+ /// The Volume Down key.
+ ///
+ VolumeDown = 0xAE,
+
+ ///
+ /// The Volume Up key.
+ ///
+ VolumeUp = 0xAF,
+
+ ///
+ /// The Media Next Track key.
+ ///
+ MediaNextTrack = 0xB0,
+
+ ///
+ /// The Media Previous Track key.
+ ///
+ MediaPreviousTrack = 0xB1,
+
+ ///
+ /// The Media Stop key.
+ ///
+ MediaStop = 0xB2,
+
+ ///
+ /// The Media Play Pause key.
+ ///
+ MediaPlayPause = 0xB3,
+
+ ///
+ /// The Launch Mail key.
+ ///
+ LaunchMail = 0xB4,
+
+ ///
+ /// The Select Media key.
+ ///
+ SelectMedia = 0xB5,
+
+ ///
+ /// The Launch Application1 key.
+ ///
+ LaunchApplication1 = 0xB6,
+
+ ///
+ /// The Launch Application2 key.
+ ///
+ LaunchApplication2 = 0xB7,
+
+ ///
+ /// The Oem 1 key. ',:' for US
+ ///
+ Oem1 = 0xBA,
+
+ ///
+ /// The Oem Semicolon key.
+ ///
+ OemSemicolon = Oem1,
+
+ ///
+ /// The Oem plus key. '+' any country
+ ///
+ OemPlus = 0xBB,
+
+ ///
+ /// The Oem comma key. ',' any country
+ ///
+ OemComma = 0xBC,
+
+ ///
+ /// The Oem Minus key. '-' any country
+ ///
+ OemMinus = 0xBD,
+
+ ///
+ /// The Oem Period key. '.' any country
+ ///
+ OemPeriod = 0xBE,
+
+ ///
+ /// The Oem 2 key. '/?' for US
+ ///
+ Oem2 = 0xBF,
+
+ ///
+ /// The Oem Question key.
+ ///
+ OemQuestion = Oem2,
+
+ ///
+ /// The Oem 3 key. '`~' for US
+ ///
+ Oem3 = 0xC0,
+
+ ///
+ /// The Oem tilde key.
+ ///
+ OemTilde = Oem3,
+
+ ///
+ /// The ABNT_C1 (Brazilian) key.
+ ///
+ AbntC1 = 0xC1,
+
+ ///
+ /// The ABNT_C2 (Brazilian) key.
+ ///
+ AbntC2 = 0xC2,
+
+ ///
+ /// The Oem 4 key.
+ ///
+ Oem4 = 0xDB,
+
+ ///
+ /// The Oem Open Brackets key.
+ ///
+ OemOpenBrackets = Oem4,
+
+ ///
+ /// The Oem 5 key.
+ ///
+ Oem5 = 0xDC,
+
+ ///
+ /// The Oem Pipe key.
+ ///
+ OemPipe = Oem5,
+
+ ///
+ /// The Oem 6 key.
+ ///
+ Oem6 = 0xDD,
+
+ ///
+ /// The Oem Close Brackets key.
+ ///
+ OemCloseBrackets = Oem6,
+
+ ///
+ /// The Oem 7 key.
+ ///
+ Oem7 = 0xDE,
+
+ ///
+ /// The Oem Quotes key.
+ ///
+ OemQuotes = Oem7,
+
+ ///
+ /// The Oem8 key.
+ ///
+ Oem8 = 0xDF,
+
+ ///
+ /// The Oem 102 key.
+ ///
+ Oem102 = 0xE2,
+
+ ///
+ /// The Oem Backslash key.
+ ///
+ OemBackslash = Oem102,
+
+ ///
+ /// A special key masking the real key being processed by an IME.
+ ///
+ ImeProcessed = 0xE5,
+
+ ///
+ /// A special key masking the real key being processed as a system key.
+ ///
+ System,
+
+ ///
+ /// The OEM_ATTN key.
+ ///
+ OemAttn = 0xF0,
+
+ ///
+ /// The DBE_ALPHANUMERIC key.
+ ///
+ DbeAlphanumeric = OemAttn,
+
+ ///
+ /// The OEM_FINISH key.
+ ///
+ OemFinish = 0xF1,
+
+ ///
+ /// The DBE_KATAKANA key.
+ ///
+ DbeKatakana = OemFinish,
+
+ ///
+ /// The OEM_COPY key.
+ ///
+ OemCopy = 0xF2,
+
+ ///
+ /// The DBE_HIRAGANA key.
+ ///
+ DbeHiragana = OemCopy,
+
+ ///
+ /// The OEM_AUTO key.
+ ///
+ OemAuto = 0xF3,
+
+ ///
+ /// The DBE_SBCSCHAR key.
+ ///
+ DbeSbcsChar = OemAuto,
+
+ ///
+ /// The OEM_ENLW key.
+ ///
+ OemEnlw = 0xF4,
+
+ ///
+ /// The DBE_DBCSCHAR key.
+ ///
+ DbeDbcsChar = OemEnlw,
+
+ ///
+ /// The OEM_BACKTAB key.
+ ///
+ OemBackTab = 0xF5,
+
+ ///
+ /// The DBE_ROMAN key.
+ ///
+ DbeRoman = OemBackTab,
+
+ ///
+ /// The ATTN key.
+ ///
+ Attn = 0xF6,
+
+ ///
+ /// The DBE_NOROMAN key.
+ ///
+ DbeNoRoman = Attn,
+
+ ///
+ /// The CRSEL key.
+ ///
+ CrSel = 0xF7,
+
+ ///
+ /// The DBE_ENTERWORDREGISTERMODE key.
+ ///
+ DbeEnterWordRegisterMode = CrSel,
+
+ ///
+ /// The EXSEL key.
+ ///
+ ExSel = 0xF8,
+
+ ///
+ /// The DBE_ENTERIMECONFIGMODE key.
+ ///
+ DbeEnterImeConfigureMode = ExSel,
+
+ ///
+ /// The ERASE EOF key.
+ ///
+ EraseEof = 0xF9,
+
+ ///
+ /// The DBE_FLUSHSTRING key.
+ ///
+ DbeFlushString = EraseEof,
+
+ ///
+ /// The PLAY key.
+ ///
+ Play = 0xFA,
+
+ ///
+ /// The DBE_CODEINPUT key.
+ ///
+ DbeCodeInput = Play,
+
+ ///
+ /// The ZOOM key.
+ ///
+ Zoom = 0xFB,
+
+ ///
+ /// The DBE_NOCODEINPUT key.
+ ///
+ DbeNoCodeInput = Zoom,
+
+ ///
+ /// A constant reserved for future use.
+ ///
+ NoName = 0xFC,
+
+ ///
+ /// The DBE_DETERMINESTRING key.
+ ///
+ DbeDetermineString = NoName,
+
+ ///
+ /// The PA1 key.
+ ///
+ Pa1 = 0xFD,
+
+ ///
+ /// The DBE_ENTERDLGCONVERSIONMODE key.
+ ///
+ DbeEnterDialogConversionMode = Pa1,
+
+ ///
+ /// The CLEAR key.
+ ///
+ OemClear = 0xFE,
+
+ ///
+ /// Indicates the key is part of a dead-key composition
+ ///
+ DeadCharProcessed = 0,
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Input/Keyboard.cs b/src/dotnetCampus.UITest.WPFTestHelper/Input/Keyboard.cs
new file mode 100644
index 0000000..1d55772
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Input/Keyboard.cs
@@ -0,0 +1,418 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Input
+{
+ ///
+ /// Exposes a simple interface to common keyboard operations, allowing the user to simulate keyboard input.
+ ///
+ ///
+ /// The following code types "Hello, world!" with the specified casing,
+ /// and then types "hello, capitalized world!" which will be in all caps because
+ /// the left shift key is being held down.
+ ///
+ /// Keyboard.Type("Hello, world!");
+ /// Keyboard.Type(Key.Enter);
+ /// Keyboard.Press(Key.LeftShift);
+ /// Keyboard.Type("hello, capitalized world!");
+ /// Keyboard.Release(Key.LeftShift);
+ ///
+ ///
+ public class Keyboard
+ {
+ static Keyboard()
+ {
+ KeyBoardKeys = new Dictionary();
+ KeyBoardKeys.Add(Key.Cancel, new KeySpec((ushort)Key.Cancel, false, "cancel"));
+ KeyBoardKeys.Add(Key.Back, new KeySpec((ushort)Key.Back, false, "backspace"));
+ KeyBoardKeys.Add(Key.Tab, new KeySpec((ushort)Key.Tab, false, "tab"));
+ KeyBoardKeys.Add(Key.Clear, new KeySpec((ushort)Key.Clear, false, "clear"));
+ KeyBoardKeys.Add(Key.Return, new KeySpec((ushort)Key.Return, false, "return"));
+ KeyBoardKeys.Add(Key.Shift, new KeySpec((ushort)Key.Shift, false, "shift"));
+ KeyBoardKeys.Add(Key.Ctrl, new KeySpec((ushort)Key.Ctrl, false, "ctrl"));
+ KeyBoardKeys.Add(Key.Alt, new KeySpec((ushort)Key.Alt, false, "alt"));
+ KeyBoardKeys.Add(Key.Pause, new KeySpec((ushort)Key.Pause, false, "pause"));
+ KeyBoardKeys.Add(Key.Capital, new KeySpec((ushort)Key.Capital, false, "capital"));
+ KeyBoardKeys.Add(Key.KanaMode, new KeySpec((ushort)Key.KanaMode, false, "kanamode"));
+ KeyBoardKeys.Add(Key.JunjaMode, new KeySpec((ushort)Key.JunjaMode, false, "junjamode"));
+ KeyBoardKeys.Add(Key.FinalMode, new KeySpec((ushort)Key.FinalMode, false, "finalmode"));
+ KeyBoardKeys.Add(Key.HanjaMode, new KeySpec((ushort)Key.HanjaMode, false, "hanjamode"));
+ KeyBoardKeys.Add(Key.Escape, new KeySpec((ushort)Key.Escape, false, "esc"));
+ KeyBoardKeys.Add(Key.ImeConvert, new KeySpec((ushort)Key.ImeConvert, false, "imeconvert"));
+ KeyBoardKeys.Add(Key.ImeNonConvert, new KeySpec((ushort)Key.ImeNonConvert, false, "imenonconvert"));
+ KeyBoardKeys.Add(Key.ImeAccept, new KeySpec((ushort)Key.ImeAccept, false, "imeaccept"));
+ KeyBoardKeys.Add(Key.ImeModeChange, new KeySpec((ushort)Key.ImeAccept, false, "imemodechange"));
+ KeyBoardKeys.Add(Key.Space, new KeySpec((ushort)Key.Space, false, " "));
+ KeyBoardKeys.Add(Key.Prior, new KeySpec((ushort)Key.Prior, true, "prior"));
+ KeyBoardKeys.Add(Key.Next, new KeySpec((ushort)Key.Next, true, "next"));
+ KeyBoardKeys.Add(Key.End, new KeySpec((ushort)Key.End, true, "end"));
+ KeyBoardKeys.Add(Key.Home, new KeySpec((ushort)Key.Home, true, "home"));
+ KeyBoardKeys.Add(Key.Left, new KeySpec((ushort)Key.Left, true, "left"));
+ KeyBoardKeys.Add(Key.Up, new KeySpec((ushort)Key.Up, true, "up"));
+ KeyBoardKeys.Add(Key.Right, new KeySpec((ushort)Key.Right, true, "right"));
+ KeyBoardKeys.Add(Key.Down, new KeySpec((ushort)Key.Down, true, "down"));
+ KeyBoardKeys.Add(Key.Select, new KeySpec((ushort)Key.Select, false, "select"));
+ KeyBoardKeys.Add(Key.Print, new KeySpec((ushort)Key.Print, false, "print"));
+ KeyBoardKeys.Add(Key.Execute, new KeySpec((ushort)Key.Execute, false, "execute"));
+ KeyBoardKeys.Add(Key.Snapshot, new KeySpec((ushort)Key.Snapshot, true, "snapshot"));
+ KeyBoardKeys.Add(Key.Insert, new KeySpec((ushort)Key.Insert, true, "insert"));
+ KeyBoardKeys.Add(Key.Delete, new KeySpec((ushort)Key.Delete, true, "delete"));
+ KeyBoardKeys.Add(Key.Help, new KeySpec((ushort)Key.Help, false, "help"));
+
+ KeyBoardKeys.Add(Key.D0, new KeySpec((ushort)Key.D0, false, "0"));
+ KeyBoardKeys.Add(Key.D1, new KeySpec((ushort)Key.D1, false, "1"));
+ KeyBoardKeys.Add(Key.D2, new KeySpec((ushort)Key.D2, false, "2"));
+ KeyBoardKeys.Add(Key.D3, new KeySpec((ushort)Key.D3, false, "3"));
+ KeyBoardKeys.Add(Key.D4, new KeySpec((ushort)Key.D4, false, "4"));
+ KeyBoardKeys.Add(Key.D5, new KeySpec((ushort)Key.D5, false, "5"));
+ KeyBoardKeys.Add(Key.D6, new KeySpec((ushort)Key.D6, false, "6"));
+ KeyBoardKeys.Add(Key.D7, new KeySpec((ushort)Key.D7, false, "7"));
+ KeyBoardKeys.Add(Key.D8, new KeySpec((ushort)Key.D8, false, "8"));
+ KeyBoardKeys.Add(Key.D9, new KeySpec((ushort)Key.D9, false, "9"));
+
+ KeyBoardKeys.Add(Key.A, new KeySpec((ushort)Key.A, false, "a"));
+ KeyBoardKeys.Add(Key.B, new KeySpec((ushort)Key.B, false, "b"));
+ KeyBoardKeys.Add(Key.C, new KeySpec((ushort)Key.C, false, "c"));
+ KeyBoardKeys.Add(Key.D, new KeySpec((ushort)Key.D, false, "d"));
+ KeyBoardKeys.Add(Key.E, new KeySpec((ushort)Key.E, false, "e"));
+ KeyBoardKeys.Add(Key.F, new KeySpec((ushort)Key.F, false, "f"));
+ KeyBoardKeys.Add(Key.G, new KeySpec((ushort)Key.G, false, "g"));
+ KeyBoardKeys.Add(Key.H, new KeySpec((ushort)Key.H, false, "h"));
+ KeyBoardKeys.Add(Key.I, new KeySpec((ushort)Key.I, false, "i"));
+ KeyBoardKeys.Add(Key.J, new KeySpec((ushort)Key.J, false, "j"));
+ KeyBoardKeys.Add(Key.K, new KeySpec((ushort)Key.K, false, "k"));
+ KeyBoardKeys.Add(Key.L, new KeySpec((ushort)Key.L, false, "l"));
+ KeyBoardKeys.Add(Key.M, new KeySpec((ushort)Key.M, false, "m"));
+ KeyBoardKeys.Add(Key.N, new KeySpec((ushort)Key.N, false, "n"));
+ KeyBoardKeys.Add(Key.O, new KeySpec((ushort)Key.O, false, "o"));
+ KeyBoardKeys.Add(Key.P, new KeySpec((ushort)Key.P, false, "p"));
+ KeyBoardKeys.Add(Key.Q, new KeySpec((ushort)Key.Q, false, "q"));
+ KeyBoardKeys.Add(Key.R, new KeySpec((ushort)Key.R, false, "r"));
+ KeyBoardKeys.Add(Key.S, new KeySpec((ushort)Key.S, false, "s"));
+ KeyBoardKeys.Add(Key.T, new KeySpec((ushort)Key.T, false, "t"));
+ KeyBoardKeys.Add(Key.U, new KeySpec((ushort)Key.U, false, "u"));
+ KeyBoardKeys.Add(Key.V, new KeySpec((ushort)Key.V, false, "v"));
+ KeyBoardKeys.Add(Key.W, new KeySpec((ushort)Key.W, false, "w"));
+ KeyBoardKeys.Add(Key.X, new KeySpec((ushort)Key.X, false, "x"));
+ KeyBoardKeys.Add(Key.Y, new KeySpec((ushort)Key.Y, false, "y"));
+ KeyBoardKeys.Add(Key.Z, new KeySpec((ushort)Key.Z, false, "z"));
+
+ KeyBoardKeys.Add(Key.LWin, new KeySpec((ushort)Key.LWin, true, "lwin"));
+ KeyBoardKeys.Add(Key.RWin, new KeySpec((ushort)Key.RWin, true, "rwin"));
+ KeyBoardKeys.Add(Key.Apps, new KeySpec((ushort)Key.Apps, true, "apps"));
+ KeyBoardKeys.Add(Key.Sleep, new KeySpec((ushort)Key.Sleep, false, "sleep"));
+
+ KeyBoardKeys.Add(Key.NumPad0, new KeySpec((ushort)Key.NumPad0, false, "n0"));
+ KeyBoardKeys.Add(Key.NumPad1, new KeySpec((ushort)Key.NumPad1, false, "n1"));
+ KeyBoardKeys.Add(Key.NumPad2, new KeySpec((ushort)Key.NumPad2, false, "n2"));
+ KeyBoardKeys.Add(Key.NumPad3, new KeySpec((ushort)Key.NumPad3, false, "n3"));
+ KeyBoardKeys.Add(Key.NumPad4, new KeySpec((ushort)Key.NumPad4, false, "n4"));
+ KeyBoardKeys.Add(Key.NumPad5, new KeySpec((ushort)Key.NumPad5, false, "n5"));
+ KeyBoardKeys.Add(Key.NumPad6, new KeySpec((ushort)Key.NumPad6, false, "n6"));
+ KeyBoardKeys.Add(Key.NumPad7, new KeySpec((ushort)Key.NumPad7, false, "n7"));
+ KeyBoardKeys.Add(Key.NumPad8, new KeySpec((ushort)Key.NumPad8, false, "n8"));
+ KeyBoardKeys.Add(Key.NumPad9, new KeySpec((ushort)Key.NumPad9, false, "n9"));
+
+ KeyBoardKeys.Add(Key.Multiply, new KeySpec((ushort)Key.Multiply, false, "*"));
+ KeyBoardKeys.Add(Key.Add, new KeySpec((ushort)Key.Add, false, "+"));
+ KeyBoardKeys.Add(Key.Separator, new KeySpec((ushort)Key.Separator, false, "separator"));
+ KeyBoardKeys.Add(Key.Subtract, new KeySpec((ushort)Key.Subtract, false, "-"));
+ KeyBoardKeys.Add(Key.Decimal, new KeySpec((ushort)Key.Decimal, false, "decimal"));
+ KeyBoardKeys.Add(Key.Divide, new KeySpec((ushort)Key.Divide, true, "/"));
+
+ KeyBoardKeys.Add(Key.F1, new KeySpec((ushort)Key.F1, false, "f1"));
+ KeyBoardKeys.Add(Key.F2, new KeySpec((ushort)Key.F2, false, "f2"));
+ KeyBoardKeys.Add(Key.F3, new KeySpec((ushort)Key.F3, false, "f3"));
+ KeyBoardKeys.Add(Key.F4, new KeySpec((ushort)Key.F4, false, "f4"));
+ KeyBoardKeys.Add(Key.F5, new KeySpec((ushort)Key.F5, false, "f5"));
+ KeyBoardKeys.Add(Key.F6, new KeySpec((ushort)Key.F6, false, "f6"));
+ KeyBoardKeys.Add(Key.F7, new KeySpec((ushort)Key.F7, false, "f7"));
+ KeyBoardKeys.Add(Key.F8, new KeySpec((ushort)Key.F8, false, "f8"));
+ KeyBoardKeys.Add(Key.F9, new KeySpec((ushort)Key.F9, false, "f9"));
+ KeyBoardKeys.Add(Key.F10, new KeySpec((ushort)Key.F10, false, "f10"));
+ KeyBoardKeys.Add(Key.F11, new KeySpec((ushort)Key.F11, false, "f11"));
+ KeyBoardKeys.Add(Key.F12, new KeySpec((ushort)Key.F12, false, "f12"));
+ KeyBoardKeys.Add(Key.F13, new KeySpec((ushort)Key.F13, false, "f13"));
+ KeyBoardKeys.Add(Key.F14, new KeySpec((ushort)Key.F14, false, "f14"));
+ KeyBoardKeys.Add(Key.F15, new KeySpec((ushort)Key.F15, false, "f15"));
+ KeyBoardKeys.Add(Key.F16, new KeySpec((ushort)Key.F16, false, "f16"));
+ KeyBoardKeys.Add(Key.F17, new KeySpec((ushort)Key.F17, false, "f17"));
+ KeyBoardKeys.Add(Key.F18, new KeySpec((ushort)Key.F18, false, "f18"));
+ KeyBoardKeys.Add(Key.F19, new KeySpec((ushort)Key.F19, false, "f19"));
+ KeyBoardKeys.Add(Key.F20, new KeySpec((ushort)Key.F20, false, "f20"));
+ KeyBoardKeys.Add(Key.F21, new KeySpec((ushort)Key.F21, false, "f21"));
+ KeyBoardKeys.Add(Key.F22, new KeySpec((ushort)Key.F22, false, "f22"));
+ KeyBoardKeys.Add(Key.F23, new KeySpec((ushort)Key.F23, false, "f23"));
+ KeyBoardKeys.Add(Key.F24, new KeySpec((ushort)Key.F24, false, "f24"));
+
+ KeyBoardKeys.Add(Key.NumLock, new KeySpec((ushort)Key.NumLock, true, "numlock"));
+ KeyBoardKeys.Add(Key.Scroll, new KeySpec((ushort)Key.Scroll, false, "scroll"));
+ KeyBoardKeys.Add(Key.LeftShift, new KeySpec((ushort)Key.LeftShift, false, "leftshift"));
+ KeyBoardKeys.Add(Key.RightShift, new KeySpec((ushort)Key.RightShift, false, "rightshift"));
+ KeyBoardKeys.Add(Key.LeftCtrl, new KeySpec((ushort)Key.LeftCtrl, false, "leftctrl"));
+ KeyBoardKeys.Add(Key.RightCtrl, new KeySpec((ushort)Key.RightCtrl, true, "rightctrl"));
+ KeyBoardKeys.Add(Key.LeftAlt, new KeySpec((ushort)Key.LeftAlt, false, "leftalt"));
+ KeyBoardKeys.Add(Key.RightAlt, new KeySpec((ushort)Key.RightAlt, true, "rightalt"));
+
+ KeyBoardKeys.Add(Key.BrowserBack, new KeySpec((ushort)Key.BrowserBack, false, "browserback"));
+ KeyBoardKeys.Add(Key.BrowserForward, new KeySpec((ushort)Key.BrowserForward, false, "browserforward"));
+ KeyBoardKeys.Add(Key.BrowserRefresh, new KeySpec((ushort)Key.BrowserRefresh, false, "browserrefresh"));
+ KeyBoardKeys.Add(Key.BrowserStop, new KeySpec((ushort)Key.BrowserStop, false, "browserstop"));
+ KeyBoardKeys.Add(Key.BrowserSearch, new KeySpec((ushort)Key.BrowserSearch, false, "browsersearch"));
+ KeyBoardKeys.Add(Key.BrowserFavorites, new KeySpec((ushort)Key.BrowserFavorites, false, "BrowserFavorites"));
+ KeyBoardKeys.Add(Key.BrowserHome, new KeySpec((ushort)Key.BrowserHome, false, "BrowserHome"));
+
+ KeyBoardKeys.Add(Key.VolumeMute, new KeySpec((ushort)Key.VolumeMute, false, "VolumeMute"));
+ KeyBoardKeys.Add(Key.VolumeDown, new KeySpec((ushort)Key.VolumeDown, false, "VolumeDown"));
+ KeyBoardKeys.Add(Key.VolumeUp, new KeySpec((ushort)Key.VolumeUp, false, "VolumeUp"));
+ KeyBoardKeys.Add(Key.MediaNextTrack, new KeySpec((ushort)Key.MediaNextTrack, false, "MediaNextTrack"));
+ KeyBoardKeys.Add(Key.MediaPreviousTrack, new KeySpec((ushort)Key.MediaPreviousTrack, false, "MediaPreviousTrack"));
+ KeyBoardKeys.Add(Key.MediaStop, new KeySpec((ushort)Key.MediaStop, false, "MediaStop"));
+ KeyBoardKeys.Add(Key.MediaPlayPause, new KeySpec((ushort)Key.MediaPlayPause, false, "MediaPlayPause"));
+ KeyBoardKeys.Add(Key.LaunchMail, new KeySpec((ushort)Key.LaunchMail, false, "LaunchMail"));
+ KeyBoardKeys.Add(Key.SelectMedia, new KeySpec((ushort)Key.SelectMedia, false, "SelectMedia"));
+ KeyBoardKeys.Add(Key.LaunchApplication1, new KeySpec((ushort)Key.LaunchApplication1, false, "LaunchApplication1"));
+ KeyBoardKeys.Add(Key.LaunchApplication2, new KeySpec((ushort)Key.LaunchApplication2, false, "LaunchApplication2"));
+
+ KeyBoardKeys.Add(Key.Oem1, new KeySpec((ushort)Key.Oem1, false, ";"));
+ KeyBoardKeys.Add(Key.OemPlus, new KeySpec((ushort)Key.OemPlus, false, "+"));
+ KeyBoardKeys.Add(Key.OemComma, new KeySpec((ushort)Key.OemComma, false, ","));
+ KeyBoardKeys.Add(Key.OemMinus, new KeySpec((ushort)Key.OemMinus, false, "-"));
+ KeyBoardKeys.Add(Key.OemPeriod, new KeySpec((ushort)Key.OemPeriod, false, "."));
+ KeyBoardKeys.Add(Key.Oem2, new KeySpec((ushort)Key.Oem2, false, "?"));
+ KeyBoardKeys.Add(Key.Oem3, new KeySpec((ushort)Key.Oem3, false, "~"));
+ KeyBoardKeys.Add(Key.AbntC1, new KeySpec((ushort)Key.AbntC1, false, "AbntC1"));
+ KeyBoardKeys.Add(Key.AbntC2, new KeySpec((ushort)Key.AbntC2, false, "AbntC2"));
+ KeyBoardKeys.Add(Key.Oem4, new KeySpec((ushort)Key.Oem4, false, "["));
+ KeyBoardKeys.Add(Key.Oem5, new KeySpec((ushort)Key.Oem5, false, "|"));
+ KeyBoardKeys.Add(Key.Oem6, new KeySpec((ushort)Key.Oem6, false, "]"));
+ KeyBoardKeys.Add(Key.Oem7, new KeySpec((ushort)Key.Oem7, false, "\""));
+ KeyBoardKeys.Add(Key.Oem8, new KeySpec((ushort)Key.Oem8, false, "Oem8"));
+ KeyBoardKeys.Add(Key.Oem102, new KeySpec((ushort)Key.Oem102, false, "\\"));
+ KeyBoardKeys.Add(Key.ImeProcessed, new KeySpec((ushort)Key.ImeProcessed, false, "ImeProcessed"));
+ KeyBoardKeys.Add(Key.OemAttn, new KeySpec((ushort)Key.OemAttn, false, "OemAttn"));
+ KeyBoardKeys.Add(Key.OemFinish, new KeySpec((ushort)Key.OemFinish, false, "OemFinish"));
+ KeyBoardKeys.Add(Key.OemCopy, new KeySpec((ushort)Key.OemCopy, false, "OemCopy"));
+ KeyBoardKeys.Add(Key.OemAuto, new KeySpec((ushort)Key.OemAuto, false, "OemAuto"));
+ KeyBoardKeys.Add(Key.OemEnlw, new KeySpec((ushort)Key.OemEnlw, false, "OemEnlw"));
+ KeyBoardKeys.Add(Key.OemBackTab, new KeySpec((ushort)Key.OemBackTab, false, "OemBackTab"));
+ KeyBoardKeys.Add(Key.Attn, new KeySpec((ushort)Key.Attn, false, "Attn"));
+ KeyBoardKeys.Add(Key.CrSel, new KeySpec((ushort)Key.CrSel, false, "CrSel"));
+ KeyBoardKeys.Add(Key.ExSel, new KeySpec((ushort)Key.ExSel, false, "ExSel"));
+ KeyBoardKeys.Add(Key.EraseEof, new KeySpec((ushort)Key.EraseEof, false, "EraseEof"));
+ KeyBoardKeys.Add(Key.Play, new KeySpec((ushort)Key.Play, false, "Play"));
+ KeyBoardKeys.Add(Key.Zoom, new KeySpec((ushort)Key.Zoom, false, "Zoom"));
+ KeyBoardKeys.Add(Key.NoName, new KeySpec((ushort)Key.NoName, false, "NoName"));
+ KeyBoardKeys.Add(Key.Pa1, new KeySpec((ushort)Key.Pa1, false, "Pa1"));
+ KeyBoardKeys.Add(Key.OemClear, new KeySpec((ushort)Key.OemClear, false, "OemClear"));
+ KeyBoardKeys.Add(Key.DeadCharProcessed, new KeySpec((ushort)Key.DeadCharProcessed, false, "DeadCharProcessed"));
+ }
+
+ #region Public Methods
+
+ ///
+ /// Presses down a key.
+ ///
+ /// The key to press.
+ public static void Press(Key key)
+ {
+ var keySpec = GetKeySpecFromKey(key);
+ SendKeyboardKey(keySpec.KeyCode, true, keySpec.IsExtended, false);
+ }
+
+ ///
+ /// Releases a key.
+ ///
+ /// The key to release.
+ public static void Release(Key key)
+ {
+ var keySpec = GetKeySpecFromKey(key);
+ SendKeyboardKey(keySpec.KeyCode, false, keySpec.IsExtended, false);
+ }
+
+ ///
+ /// Resets the system keyboard to a clean state.
+ ///
+ public static void Reset()
+ {
+ foreach (Key key in Enum.GetValues(typeof(Key)))
+ {
+ if ((GetKeyState(key) & KeyStates.Down) > 0)
+ {
+ Release(key);
+ }
+ }
+ }
+
+ ///
+ /// Performs a press-and-release operation for the specified key, which is effectively equivallent to typing.
+ ///
+ /// The key to press.
+ public static void Type(Key key)
+ {
+ Press(key);
+ Release(key);
+ }
+
+
+ ///
+ /// Types the specified text.
+ ///
+ ///
+ /// Note that a combination of a combination of Key.Shift or Key.Capital and a Unicode point above 0xFE
+ /// is not considered valid and will not result in the Unicode point being types without
+ /// applying of the modifier key.
+ ///
+ /// The text to type.
+ public static void Type(string text)
+ {
+ foreach (char c in text)
+ {
+ // If code point is bigger than 8 bits, we are going for Unicode approach by setting wVk to 0.
+ if (c > 0xFE)
+ {
+ SendKeyboardKey(c, true, false, true);
+ SendKeyboardKey(c, false, false, true);
+ }
+ else
+ {
+ // We get the vKey value for the character via a Win32 API. We then use bit masks to pull the
+ // upper and lower bytes to get the shift state and key information. We then use WPF KeyInterop
+ // to go from the vKey key info into a System.Windows.Input.Key data structure. This work is
+ // necessary because Key doesn't distinguish between upper and lower case, so we have to wrap
+ // the key type inside a shift press/release if necessary.
+ int vKeyValue = NativeMethods.VkKeyScan(c);
+ bool keyIsShifted = (vKeyValue & NativeMethods.VKeyShiftMask) == NativeMethods.VKeyShiftMask;
+ Key key = (Key)(vKeyValue & NativeMethods.VKeyCharMask);
+
+ if (keyIsShifted)
+ {
+ Type(key, new Key[] { Key.Shift });
+ }
+ else
+ {
+ Type(key);
+ }
+ }
+ }
+ }
+
+ #endregion Public Methods
+
+ #region Private Methods
+
+ private static KeySpec GetKeySpecFromKey(Key key)
+ {
+ KeySpec resultKey;
+ if (!KeyBoardKeys.TryGetValue(key, out resultKey))
+ {
+ resultKey = new KeySpec();
+ }
+
+ return resultKey;
+ }
+
+ private static void Type(Key key, Key[] modifierKeys)
+ {
+ foreach (Key modiferKey in modifierKeys)
+ {
+ Press(modiferKey);
+ }
+
+ Type(key);
+
+ foreach (Key modifierKey in modifierKeys.Reverse())
+ {
+ Release(modifierKey);
+ }
+ }
+
+ private static void SendKeyboardKey(ushort key, bool isKeyDown, bool isExtended, bool isUnicode)
+ {
+ var input = new NativeMethods.INPUT();
+ input.Type = NativeMethods.INPUT_KEYBOARD;
+ if (!isKeyDown)
+ {
+ input.Data.Keyboard.dwFlags |= NativeMethods.KEYEVENTF_KEYUP;
+ }
+
+ if (isUnicode)
+ {
+ input.Data.Keyboard.dwFlags |= NativeMethods.KEYEVENTF_UNICODE;
+ input.Data.Keyboard.wScan = key;
+ input.Data.Keyboard.wVk = 0;
+ }
+ else
+ {
+ input.Data.Keyboard.wScan = 0;
+ input.Data.Keyboard.wVk = key;
+ }
+
+ if (isExtended)
+ {
+ input.Data.Keyboard.dwFlags |= NativeMethods.KEYEVENTF_EXTENDEDKEY;
+ }
+
+ input.Data.Keyboard.time = 0;
+ input.Data.Keyboard.dwExtraInfo = IntPtr.Zero;
+
+ NativeMethods.SendInput(1, new NativeMethods.INPUT[] { input }, Marshal.SizeOf(input));
+ Thread.Sleep(100);
+ }
+
+ private static KeyStates GetKeyState(Key key)
+ {
+ var keyStates = KeyStates.None;
+ var nativeKeyState = NativeMethods.GetKeyState((int)key);
+
+ if ((nativeKeyState & 0x00008000) == 0x00008000)
+ {
+ keyStates |= KeyStates.Down;
+ }
+
+ if ((nativeKeyState & 0x00000001) == 0x00000001)
+ {
+ keyStates |= KeyStates.Toggled;
+ }
+
+ return keyStates;
+ }
+
+ #endregion Private Methods
+
+ #region Private Data
+
+ private struct KeySpec
+ {
+ public ushort KeyCode;
+ public bool IsExtended;
+ public string Name;
+
+ public KeySpec(ushort keyCode, bool isExtended, string name)
+ {
+ this.KeyCode = keyCode;
+ this.IsExtended = isExtended;
+ this.Name = name;
+ }
+ }
+
+ private enum KeyStates
+ {
+ None = 0,
+ Down = 1,
+ Toggled = 2
+ }
+
+ private static Dictionary KeyBoardKeys = null;
+
+ #endregion Private Data
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Input/Mouse.cs b/src/dotnetCampus.UITest.WPFTestHelper/Input/Mouse.cs
new file mode 100644
index 0000000..c8ba5f1
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Input/Mouse.cs
@@ -0,0 +1,291 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+using System;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Input
+{
+ ///
+ /// Exposes a simple interface to common mouse operations, allowing the user to simulate mouse input.
+ ///
+ /// The following code moves to screen coordinate 100,100 and left clicks.
+ ///
+ /// Mouse.MoveTo(new Point(100, 100));
+ /// Mouse.Click(MouseButton.Left);
+ ///
+ ///
+ public static class Mouse
+ {
+ #region Public Methods
+
+ ///
+ /// Clicks a mouse button.
+ ///
+ /// The mouse button to click.
+ public static void Click(MouseButton mouseButton)
+ {
+ Down(mouseButton);
+ Up(mouseButton);
+ }
+
+ ///
+ /// Double-clicks a mouse button.
+ ///
+ /// The mouse button to click.
+ public static void DoubleClick(MouseButton mouseButton)
+ {
+ Click(mouseButton);
+ Click(mouseButton);
+ }
+
+ ///
+ /// Performs a mouse-down operation for a specified mouse button.
+ ///
+ /// The mouse button to use.
+ public static void Down(MouseButton mouseButton)
+ {
+ int additionalData;
+ var inputFlags = GetInputFlags(mouseButton, false, out additionalData);
+ SendMouseInput(0, 0, additionalData, inputFlags);
+ }
+
+ ///
+ /// Moves the mouse pointer to the specified screen coordinates.
+ ///
+ /// The screen coordinates to move to.
+ public static void MoveTo(System.Drawing.Point point)
+ {
+ SendMouseInput(point.X, point.Y, 0, SendMouseInputFlags.Move | SendMouseInputFlags.Absolute);
+ }
+
+ ///
+ /// Resets the system mouse to a clean state.
+ ///
+ public static void Reset()
+ {
+ MoveTo(new System.Drawing.Point(0, 0));
+
+ if (GetButtonState(MouseButton.Left) == MouseButtonState.Pressed)
+ {
+ SendMouseInput(0, 0, 0, SendMouseInputFlags.LeftUp);
+ }
+
+ if (GetButtonState(MouseButton.Middle) == MouseButtonState.Pressed)
+ {
+ SendMouseInput(0, 0, 0, SendMouseInputFlags.MiddleUp);
+ }
+
+ if (GetButtonState(MouseButton.Right) == MouseButtonState.Pressed)
+ {
+ SendMouseInput(0, 0, 0, SendMouseInputFlags.RightUp);
+ }
+
+ if (GetButtonState(MouseButton.XButton1) == MouseButtonState.Pressed)
+ {
+ SendMouseInput(0, 0, (int)NativeMethods.XBUTTON1, SendMouseInputFlags.XUp);
+ }
+
+ if (GetButtonState(MouseButton.XButton2) == MouseButtonState.Pressed)
+ {
+ SendMouseInput(0, 0, (int)NativeMethods.XBUTTON2, SendMouseInputFlags.XUp);
+ }
+ }
+
+ ///
+ /// Simulates scrolling of the mouse wheel up or down.
+ ///
+ ///
+ /// The number of lines to scroll. Use positive numbers to
+ /// scroll up and negative numbers to scroll down.
+ ///
+ public static void Scroll(double lines)
+ {
+ int amount = (int)(NativeMethods.WheelDelta * lines);
+ SendMouseInput(0, 0, amount, SendMouseInputFlags.Wheel);
+ }
+
+ ///
+ /// Performs a mouse-up operation for a specified mouse button.
+ ///
+ /// The mouse button to use.
+ public static void Up(MouseButton mouseButton)
+ {
+ int additionalData;
+ var inputFlags = GetInputFlags(mouseButton, true, out additionalData);
+ SendMouseInput(0, 0, additionalData, inputFlags);
+ }
+
+ #endregion Public Methods
+
+ #region Private Methods
+
+ ///
+ /// Sends mouse input.
+ ///
+ /// x coordinate
+ /// y coordinate
+ /// scroll wheel amount
+ /// SendMouseInputFlags flags
+ private static void SendMouseInput(int x, int y, int data, SendMouseInputFlags flags)
+ {
+ uint intflags = (uint)flags;
+
+ if ((intflags & (int)SendMouseInputFlags.Absolute) != 0)
+ {
+ // Absolute position requires normalized coordinates.
+ NormalizeCoordinates(ref x, ref y);
+ intflags |= NativeMethods.MouseeventfVirtualdesk;
+ }
+
+ // don't coalesce mouse moves - tests expect to see the results immediately
+ if ((intflags & (int)SendMouseInputFlags.Move) != 0)
+ {
+ intflags |= NativeMethods.MOUSEEVENTF_MOVE_NOCOALESCE;
+ }
+
+ var mi = new NativeMethods.INPUT();
+ mi.Type = NativeMethods.INPUT_MOUSE;
+ mi.Data.Mouse.dx = x;
+ mi.Data.Mouse.dy = y;
+ mi.Data.Mouse.mouseData = data;
+ mi.Data.Mouse.dwFlags = intflags;
+ mi.Data.Mouse.time = 0;
+ mi.Data.Mouse.dwExtraInfo = new IntPtr(0);
+
+ if (NativeMethods.SendInput(1, new NativeMethods.INPUT[] { mi }, Marshal.SizeOf(mi)) == 0)
+ {
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
+ if ((intflags & (int)SendMouseInputFlags.Wheel) != 0)
+ {
+ // MouseWheel input seems to be getting coalesced by the OS, similar to mouse-move.
+ // There isn't a NOCOALESCE flag to turn this off, so instead just sleep for
+ // a short time, hopefully enough to avoid the coalescing.
+ System.Threading.Thread.Sleep(50);
+ }
+ }
+
+ private static SendMouseInputFlags GetInputFlags(MouseButton mouseButton, bool isUp, out int additionalData)
+ {
+ SendMouseInputFlags flags;
+ additionalData = 0;
+
+ if (mouseButton == MouseButton.Left && isUp)
+ {
+ flags = SendMouseInputFlags.LeftUp;
+ }
+ else if (mouseButton == MouseButton.Left && !isUp)
+ {
+ flags = SendMouseInputFlags.LeftDown;
+ }
+ else if (mouseButton == MouseButton.Right && isUp)
+ {
+ flags = SendMouseInputFlags.RightUp;
+ }
+ else if (mouseButton == MouseButton.Right && !isUp)
+ {
+ flags = SendMouseInputFlags.RightDown;
+ }
+ else if (mouseButton == MouseButton.Middle && isUp)
+ {
+ flags = SendMouseInputFlags.MiddleUp;
+ }
+ else if (mouseButton == MouseButton.Middle && !isUp)
+ {
+ flags = SendMouseInputFlags.MiddleDown;
+ }
+ else if (mouseButton == MouseButton.XButton1 && isUp)
+ {
+ flags = SendMouseInputFlags.XUp;
+ additionalData = (int)NativeMethods.XBUTTON1;
+ }
+ else if (mouseButton == MouseButton.XButton1 && !isUp)
+ {
+ flags = SendMouseInputFlags.XDown;
+ additionalData = (int)NativeMethods.XBUTTON1;
+ }
+ else if (mouseButton == MouseButton.XButton2 && isUp)
+ {
+ flags = SendMouseInputFlags.XUp;
+ additionalData = (int)NativeMethods.XBUTTON2;
+ }
+ else if (mouseButton == MouseButton.XButton2 && !isUp)
+ {
+ flags = SendMouseInputFlags.XDown;
+ additionalData = (int)NativeMethods.XBUTTON2;
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
+
+ return flags;
+ }
+
+ private static void NormalizeCoordinates(ref int x, ref int y)
+ {
+ int vScreenWidth = NativeMethods.GetSystemMetrics(NativeMethods.SMCxvirtualscreen);
+ int vScreenHeight = NativeMethods.GetSystemMetrics(NativeMethods.SMCyvirtualscreen);
+ int vScreenLeft = NativeMethods.GetSystemMetrics(NativeMethods.SMXvirtualscreen);
+ int vScreenTop = NativeMethods.GetSystemMetrics(NativeMethods.SMYvirtualscreen);
+
+ // Absolute input requires that input is in 'normalized' coords - with the entire
+ // desktop being (0,0)...(65536,65536). Need to convert input x,y coords to this
+ // first.
+ //
+ // In this normalized world, any pixel on the screen corresponds to a block of values
+ // of normalized coords - eg. on a 1024x768 screen,
+ // y pixel 0 corresponds to range 0 to 85.333,
+ // y pixel 1 corresponds to range 85.333 to 170.666,
+ // y pixel 2 correpsonds to range 170.666 to 256 - and so on.
+ // Doing basic scaling math - (x-top)*65536/Width - gets us the start of the range.
+ // However, because int math is used, this can end up being rounded into the wrong
+ // pixel. For example, if we wanted pixel 1, we'd get 85.333, but that comes out as
+ // 85 as an int, which falls into pixel 0's range - and that's where the pointer goes.
+ // To avoid this, we add on half-a-"screen pixel"'s worth of normalized coords - to
+ // push us into the middle of any given pixel's range - that's the 65536/(Width*2)
+ // part of the formula. So now pixel 1 maps to 85+42 = 127 - which is comfortably
+ // in the middle of that pixel's block.
+ // The key ting here is that unlike points in coordinate geometry, pixels take up
+ // space, so are often better treated like rectangles - and if you want to target
+ // a particular pixel, target its rectangle's midpoint, not its edge.
+ x = ((x - vScreenLeft) * 65536) / vScreenWidth + 65536 / (vScreenWidth * 2);
+ y = ((y - vScreenTop) * 65536) / vScreenHeight + 65536 / (vScreenHeight * 2);
+ }
+
+ private static MouseButtonState GetButtonState(MouseButton mouseButton)
+ {
+ var mouseButtonState = MouseButtonState.Released;
+
+ int virtualKeyCode = 0;
+ switch (mouseButton)
+ {
+ case MouseButton.Left:
+ virtualKeyCode = NativeMethods.VK_LBUTTON;
+ break;
+ case MouseButton.Right:
+ virtualKeyCode = NativeMethods.VK_RBUTTON;
+ break;
+ case MouseButton.Middle:
+ virtualKeyCode = NativeMethods.VK_MBUTTON;
+ break;
+ case MouseButton.XButton1:
+ virtualKeyCode = NativeMethods.VK_XBUTTON1;
+ break;
+ case MouseButton.XButton2:
+ virtualKeyCode = NativeMethods.VK_XBUTTON2;
+ break;
+ }
+
+ mouseButtonState = (NativeMethods.GetKeyState(virtualKeyCode) & 0x8000) != 0 ? MouseButtonState.Pressed : MouseButtonState.Released;
+ return mouseButtonState;
+ }
+
+ #endregion Private Methods
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Input/MouseButton.cs b/src/dotnetCampus.UITest.WPFTestHelper/Input/MouseButton.cs
new file mode 100644
index 0000000..517ecca
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Input/MouseButton.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+
+namespace dotnetCampus.UITest.WPFTestHelper.Input
+{
+ ///
+ /// Defines values that specify the buttons on a mouse device.
+ ///
+ public enum MouseButton
+ {
+ ///
+ /// The left mouse button.
+ ///
+ Left = 0,
+
+ ///
+ /// The middle mouse button.
+ ///
+ Middle = 1,
+
+ ///
+ /// The right mouse button.
+ ///
+ Right = 2,
+
+ ///
+ /// The first extended mouse button.
+ ///
+ XButton1 = 3,
+
+ ///
+ /// The second extended mouse button
+ ///
+ XButton2 = 4,
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Input/MouseButtonState.cs b/src/dotnetCampus.UITest.WPFTestHelper/Input/MouseButtonState.cs
new file mode 100644
index 0000000..4ac1e59
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Input/MouseButtonState.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+
+namespace dotnetCampus.UITest.WPFTestHelper.Input
+{
+ ///
+ /// The state of the mouse button.
+ ///
+ internal enum MouseButtonState
+ {
+ Released = 0,
+ Pressed = 1,
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Input/NativeMethods.cs b/src/dotnetCampus.UITest.WPFTestHelper/Input/NativeMethods.cs
new file mode 100644
index 0000000..8d19ea3
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Input/NativeMethods.cs
@@ -0,0 +1,156 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Input
+{
+ internal static class NativeMethods
+ {
+ #region Const data
+
+ private const string Gdi32Dll = "GDI32.dll";
+ private const string User32Dll = "User32.dll";
+
+ public const int INPUT_MOUSE = 0;
+ public const int INPUT_KEYBOARD = 1;
+ public const int INPUT_HARDWARE = 2;
+ public const uint KEYEVENTF_EXTENDEDKEY = 0x0001;
+ public const uint KEYEVENTF_KEYUP = 0x0002;
+ public const uint KEYEVENTF_UNICODE = 0x0004;
+ public const uint KEYEVENTF_SCANCODE = 0x0008;
+ public const uint XBUTTON1 = 0x0001;
+ public const uint XBUTTON2 = 0x0002;
+ public const uint MOUSEEVENTF_MOVE = 0x0001;
+ public const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
+ public const uint MOUSEEVENTF_LEFTUP = 0x0004;
+ public const uint MOUSEEVENTF_RIGHTDOWN = 0x0008;
+ public const uint MOUSEEVENTF_RIGHTUP = 0x0010;
+ public const uint MOUSEEVENTF_MIDDLEDOWN = 0x0020;
+ public const uint MOUSEEVENTF_MIDDLEUP = 0x0040;
+ public const uint MOUSEEVENTF_XDOWN = 0x0080;
+ public const uint MOUSEEVENTF_XUP = 0x0100;
+ public const uint MOUSEEVENTF_WHEEL = 0x0800;
+ public const uint MOUSEEVENTF_MOVE_NOCOALESCE = 0x2000;
+ public const uint MOUSEEVENTF_VIRTUALDESK = 0x4000;
+ public const uint MOUSEEVENTF_ABSOLUTE = 0x8000;
+
+ public const int VKeyShiftMask = 0x0100;
+ public const int VKeyCharMask = 0x00FF;
+
+ public const int VK_LBUTTON = 0x0001;
+ public const int VK_RBUTTON = 0x0002;
+ public const int VK_MBUTTON = 0x0004;
+ public const int VK_XBUTTON1 = 0x0005;
+ public const int VK_XBUTTON2 = 0x0006;
+
+ public const int SMXvirtualscreen = 76;
+ public const int SMYvirtualscreen = 77;
+ public const int SMCxvirtualscreen = 78;
+ public const int SMCyvirtualscreen = 79;
+
+ public const int MouseeventfVirtualdesk = 0x4000;
+ public const int WheelDelta = 120;
+
+ #endregion Const data
+
+ #region Structs
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct MOUSEINPUT
+ {
+ public int dx;
+ public int dy;
+ public int mouseData;
+ public uint dwFlags;
+ public uint time;
+ public IntPtr dwExtraInfo;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct KEYBDINPUT
+ {
+ public ushort wVk;
+ public ushort wScan;
+ public uint dwFlags;
+ public uint time;
+ public IntPtr dwExtraInfo;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct HARDWAREINPUT
+ {
+ public uint uMsg;
+ public ushort wParamL;
+ public ushort wParamH;
+ }
+
+ ///
+ /// The INPUT structure is used by SendInput to store information for synthesizing input events such as keystrokes, mouse movement, and mouse clicks. (see: http://msdn.microsoft.com/en-us/library/ms646270(VS.85).aspx)
+ /// Declared in Winuser.h, include Windows.h
+ ///
+ ///
+ /// This structure contains information identical to that used in the parameter list of the keybd_event or mouse_event function.
+ /// Windows 2000/XP: INPUT_KEYBOARD supports nonkeyboard input methods, such as handwriting recognition or voice recognition, as if it were text input by using the KEYEVENTF_UNICODE flag. For more information, see the remarks section of KEYBDINPUT.
+ ///
+ public struct INPUT
+ {
+ ///
+ /// Specifies the type of the input event. This member can be one of the following values.
+ /// InputType.MOUSE - The event is a mouse event. Use the mi structure of the union.
+ /// InputType.KEYBOARD - The event is a keyboard event. Use the ki structure of the union.
+ /// InputType.HARDWARE - Windows 95/98/Me: The event is from input hardware other than a keyboard or mouse. Use the hi structure of the union.
+ ///
+ public UInt32 Type;
+
+ ///
+ /// The data structure that contains information about the simulated Mouse, Keyboard or Hardware event.
+ ///
+ public MOUSEKEYBDHARDWAREINPUT Data;
+ }
+
+ ///
+ /// The combined/overlayed structure that includes Mouse, Keyboard and Hardware Input message data (see: http://msdn.microsoft.com/en-us/library/ms646270(VS.85).aspx)
+ ///
+ [StructLayout(LayoutKind.Explicit)]
+ public struct MOUSEKEYBDHARDWAREINPUT
+ {
+ [FieldOffset(0)]
+ public MOUSEINPUT Mouse;
+
+ [FieldOffset(0)]
+ public KEYBDINPUT Keyboard;
+
+ [FieldOffset(0)]
+ public HARDWAREINPUT Hardware;
+ }
+
+ #endregion Structs
+
+ #region Methods
+
+ [DllImport(User32Dll)]
+ public static extern short GetKeyState(int nVirtKey);
+
+ [DllImport(User32Dll, CharSet = CharSet.Auto)]
+ public static extern short VkKeyScan(char ch);
+
+ [DllImport(User32Dll, SetLastError = true)]
+ public static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
+
+ [DllImport(User32Dll, ExactSpelling = true, EntryPoint = "GetSystemMetrics", CharSet = CharSet.Auto)]
+ public static extern int GetSystemMetrics(int nIndex);
+
+ /// Converts the client-area coordinates of a specified point to screen coordinates.
+ /// Handle to the window whose client area is used for the conversion.
+ /// POINT structure that contains the client coordinates to be converted.
+ /// true if the function succeeds, false otherwise.
+ [DllImport("user32.dll", EntryPoint = "ClientToScreen", CharSet = CharSet.Auto)]
+ public static extern bool ClientToScreen(IntPtr hwndFrom, [In, Out] ref System.Drawing.Point pt);
+
+ #endregion Methods
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Input/SendMouseInputFlags.cs b/src/dotnetCampus.UITest.WPFTestHelper/Input/SendMouseInputFlags.cs
new file mode 100644
index 0000000..9d7d5a7
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Input/SendMouseInputFlags.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+using System;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Input
+{
+ ///
+ /// Mouse input flags used by the Native Input struct.
+ ///
+ [Flags]
+ internal enum SendMouseInputFlags
+ {
+ Move = 0x0001,
+ LeftDown = 0x0002,
+ LeftUp = 0x0004,
+ RightDown = 0x0008,
+ RightUp = 0x0010,
+ MiddleDown = 0x0020,
+ MiddleUp = 0x0040,
+ XDown = 0x0080,
+ XUp = 0x0100,
+ Wheel = 0x0800,
+ Absolute = 0x8000,
+ };
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/LeakDetection/MemoryInterop.cs b/src/dotnetCampus.UITest.WPFTestHelper/LeakDetection/MemoryInterop.cs
new file mode 100644
index 0000000..891b721
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/LeakDetection/MemoryInterop.cs
@@ -0,0 +1,81 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace dotnetCampus.UITest.WPFTestHelper.LeakDetection
+{
+ internal static class MemoryInterop
+ {
+ internal static PROCESS_MEMORY_COUNTERS_EX GetCounters(IntPtr hProcess)
+ {
+ PROCESS_MEMORY_COUNTERS_EX counters = new PROCESS_MEMORY_COUNTERS_EX();
+ counters.cb = Marshal.SizeOf(counters);
+ if (NativeMethods.GetProcessMemoryInfo(hProcess, out counters, Marshal.SizeOf(counters)) == 0)
+ {
+ throw new Win32Exception();
+ }
+
+ return counters;
+ }
+
+ internal static long GetPrivateWorkingSet(Process process)
+ {
+ SYSTEM_INFO sysinfo = new SYSTEM_INFO();
+ NativeMethods.GetSystemInfo(ref sysinfo);
+
+ int wsInfoLength = (int)(Marshal.SizeOf(new PSAPI_WORKING_SET_INFORMATION()) +
+ Marshal.SizeOf(new PSAPI_WORKING_SET_BLOCK()) * (process.WorkingSet64 / (sysinfo.dwPageSize)));
+ IntPtr workingSetPointer = Marshal.AllocHGlobal(wsInfoLength);
+
+ if (NativeMethods.QueryWorkingSet(process.Handle, workingSetPointer, wsInfoLength) == 0)
+ {
+ throw new Win32Exception();
+ }
+
+ PSAPI_WORKING_SET_INFORMATION workingSet = GenerateWorkingSetArray(workingSetPointer);
+ Marshal.FreeHGlobal(workingSetPointer);
+
+ return CalculatePrivatePages(workingSet) * sysinfo.dwPageSize;
+ }
+
+ // Generates an array containing working set information based on a pointer in memory.
+ private static PSAPI_WORKING_SET_INFORMATION GenerateWorkingSetArray(IntPtr workingSetPointer)
+ {
+ int entries = Marshal.ReadInt32(workingSetPointer);
+
+ PSAPI_WORKING_SET_INFORMATION workingSet = new PSAPI_WORKING_SET_INFORMATION();
+ workingSet.NumberOfEntries = entries;
+ workingSet.WorkingSetInfo = new PSAPI_WORKING_SET_BLOCK[entries];
+
+ for (int i = 0; i < entries; i++)
+ {
+ workingSet.WorkingSetInfo[i].Flags = (uint)Marshal.ReadInt32(workingSetPointer, 4 + i * 4);
+ }
+
+ return workingSet;
+ }
+
+ // Calculates the number of private pages in memory based on working set information.
+ private static int CalculatePrivatePages(PSAPI_WORKING_SET_INFORMATION workingSet)
+ {
+ int totalPages = workingSet.NumberOfEntries;
+ int privatePages = 0;
+
+ for (int i = 0; i < totalPages; i++)
+ {
+ if (workingSet.WorkingSetInfo[i].Block1.Shared == 0)
+ {
+ privatePages++;
+ }
+ }
+
+ return privatePages;
+ }
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/LeakDetection/MemorySnapshot.cs b/src/dotnetCampus.UITest.WPFTestHelper/LeakDetection/MemorySnapshot.cs
new file mode 100644
index 0000000..2508c38
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/LeakDetection/MemorySnapshot.cs
@@ -0,0 +1,696 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+using System;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.IO;
+using System.Xml;
+
+namespace dotnetCampus.UITest.WPFTestHelper.LeakDetection
+{
+ ///
+ /// Represents a snapshot in time of the memory consumed by a specified OS process.
+ /// MemorySnapshot objects can be instantiated from a running process or from a file.
+ /// MemorySnapshot objects are used for detection of memory leaks.
+ ///
+ ///
+ ///
+ /// The following example demonstrates taking two memory snapshots of Notepad and comparing them for leaks.
+ ///
+ /// Process p = Process.Start("notepad.exe");
+ /// p.WaitForInputIdle(5000);
+ /// Thread.Sleep(3000);
+ /// MemorySnapshot s1 = MemorySnapshot.FromProcess(p.Id);
+ ///
+ /// // Perform operations that may cause a leak...
+ ///
+ /// MemorySnapshot s2 = MemorySnapshot.FromProcess(p.Id);
+ ///
+ /// MemorySnapshot diff = s2.CompareTo(s1);
+ /// if (diff.GdiObjectCount != 0)
+ /// {
+ /// s1.ToFile(@"\s1.xml");
+ /// s2.ToFile(@"\s2.xml");
+ /// Console.WriteLine("Possible GDI handle leak! Review the saved memory snapshots.");
+ /// }
+ ///
+ /// p.CloseMainWindow();
+ /// p.Close();
+ ///
+ ///
+ ///
+ ///
+ /// For more information on memory leak detection in native code, refer to the
+ /// Memory Leak Detection and Isolation article. The table below provides a relationship between the metrics reported by the different tools:
+ ///
+ ///
+ ///
+ /// | TestApi |
+ /// Performance Counters |
+ /// Process Explorer |
+ /// Task Manager (Windows 7) |
+ ///
+ ///
+ /// |
+ /// - |
+ /// Handles : GDI Objects |
+ /// GDI Handles |
+ ///
+ ///
+ /// |
+ /// HandleCount |
+ /// Handles : Handles |
+ /// Handles |
+ ///
+ ///
+ /// |
+ /// PageFileBytes |
+ /// - |
+ /// - |
+ ///
+ ///
+ /// |
+ /// PageFileBytesPeak |
+ /// - |
+ /// - |
+ ///
+ ///
+ /// |
+ /// Pool Nonpaged Bytes |
+ /// - |
+ /// NonPaged Pool |
+ ///
+ ///
+ /// |
+ /// Pool Paged Bytes |
+ /// - |
+ /// Paged Pool |
+ ///
+ ///
+ /// |
+ /// - |
+ /// Threads |
+ /// Threads |
+ ///
+ ///
+ /// |
+ /// - |
+ /// - |
+ /// - |
+ ///
+ ///
+ /// |
+ /// - |
+ /// Handles : USER Objects |
+ /// USER Handles |
+ ///
+ ///
+ /// |
+ /// VirtualBytes |
+ /// Virtual Memory : Virtual Size |
+ /// - |
+ ///
+ ///
+ /// |
+ /// PrivateBytes |
+ /// Virtual Memory : Private Bytes |
+ /// Commit Size |
+ ///
+ ///
+ /// |
+ /// WorkingSet |
+ /// Physical Memory : WorkingSet |
+ /// Working Set (Memory) |
+ ///
+ ///
+ /// |
+ /// WorkingSetPeak |
+ /// Physical Memory : Peak Working Set |
+ /// Peak Working Set (Memory) |
+ ///
+ ///
+ /// |
+ /// WorkingSetPrivate |
+ /// Physical Memory : Working Set : WS Private |
+ /// Memory (Private Working Set) |
+ ///
+ ///
+ ///
+ public class MemorySnapshot
+ {
+ #region Public members
+
+ ///
+ /// Creates a MemorySnapshot instance for the specified OS process.
+ ///
+ /// The ID of the process for which to generate the memory snapshot.
+ /// A MemorySnapshot instance containing memory information for the specified process,
+ /// at the time of the snapshot.
+ public static MemorySnapshot FromProcess(int processId)
+ {
+ MemorySnapshot memorySnapshot = new MemorySnapshot();
+ Process process = Process.GetProcessById(processId);
+ process.Refresh();
+ PROCESS_MEMORY_COUNTERS_EX counters = MemoryInterop.GetCounters(process.Handle);
+
+ // Populate memory statistics.
+ memorySnapshot.GdiObjectCount = NativeMethods.GetGuiResources(process.Handle, NativeMethods.GR_GDIOBJECTS);
+ memorySnapshot.HandleCount = process.HandleCount;
+ memorySnapshot.PageFileBytes = counters.PagefileUsage;
+ memorySnapshot.PageFilePeakBytes = counters.PeakPagefileUsage;
+ memorySnapshot.PoolNonpagedBytes = counters.QuotaNonPagedPoolUsage;
+ memorySnapshot.PoolPagedBytes = counters.QuotaPagedPoolUsage;
+ memorySnapshot.ThreadCount = process.Threads.Count;
+ memorySnapshot.UserObjectCount = NativeMethods.GetGuiResources(process.Handle, NativeMethods.GR_USEROBJECTS);
+ memorySnapshot.VirtualMemoryBytes = process.VirtualMemorySize64;
+ memorySnapshot.VirtualMemoryPrivateBytes = counters.PrivateUsage;
+ memorySnapshot.WorkingSetBytes = process.WorkingSet64;
+ memorySnapshot.WorkingSetPeakBytes = process.PeakWorkingSet64;
+ memorySnapshot.WorkingSetPrivateBytes = MemoryInterop.GetPrivateWorkingSet(process);
+ memorySnapshot.Timestamp = DateTime.Now;
+
+ return memorySnapshot;
+ }
+
+ ///
+ /// Creates a MemorySnapshot instance from data in the specified file.
+ ///
+ /// The path to the memory snapshot file.
+ /// A MemorySnapshot instance containing memory information recorded in the specified file.
+ public static MemorySnapshot FromFile(string filePath)
+ {
+ MemorySnapshot memorySnapshot = new MemorySnapshot();
+
+ XmlDocument xmlDoc = new XmlDocument();
+ using (Stream s = new FileInfo(filePath).OpenRead())
+ {
+ try
+ {
+ xmlDoc.Load(s);
+ }
+ catch (XmlException)
+ {
+ throw new XmlException("MemorySnapshot file \"" + filePath + "\" could not be loaded.");
+ }
+ }
+
+ // Grab memory stats.
+ XmlNode rootNode = xmlDoc.DocumentElement;
+ memorySnapshot = Deserialize(rootNode);
+
+ return memorySnapshot;
+ }
+
+ ///
+ /// Writes the current MemorySnapshot to a file.
+ ///
+ /// The path to the output file.
+ public void ToFile(string filePath)
+ {
+ if (String.IsNullOrEmpty(filePath))
+ {
+ throw new ArgumentNullException("MemorySnapshot.ToFile(): the specified file path \"" + filePath + "\" is null or empty.");
+ }
+
+ XmlDocument xmlDoc = new XmlDocument();
+ XmlNode rootNode = Serialize(xmlDoc);
+
+ xmlDoc.AppendChild(rootNode);
+ xmlDoc.Save(filePath);
+ }
+
+ ///
+ /// Compares the current MemorySnapshot instance to the specified MemorySnapshot to produce a difference.
+ ///
+ /// The MemorySnapshot to be compared to.
+ /// A new MemorySnapshot object representing the difference between the two memory snapshots
+ /// (i.e. the result of the comparison).
+ public MemorySnapshot CompareTo(MemorySnapshot memorySnapshot)
+ {
+ MemorySnapshot diff = new MemorySnapshot();
+
+ diff.GdiObjectCount = GdiObjectCount - memorySnapshot.GdiObjectCount;
+ diff.HandleCount = HandleCount - memorySnapshot.HandleCount;
+ diff.PageFileBytes = PageFileBytes - memorySnapshot.PageFileBytes;
+ diff.PageFilePeakBytes = PageFilePeakBytes - memorySnapshot.PageFilePeakBytes;
+ diff.PoolNonpagedBytes = PoolNonpagedBytes - memorySnapshot.PoolNonpagedBytes;
+ diff.PoolPagedBytes = PoolPagedBytes - memorySnapshot.PoolPagedBytes;
+ diff.ThreadCount = ThreadCount - memorySnapshot.ThreadCount;
+ diff.UserObjectCount = UserObjectCount - memorySnapshot.UserObjectCount;
+ diff.VirtualMemoryBytes = VirtualMemoryBytes - memorySnapshot.VirtualMemoryBytes;
+ diff.VirtualMemoryPrivateBytes = VirtualMemoryPrivateBytes - memorySnapshot.VirtualMemoryPrivateBytes;
+ diff.WorkingSetBytes = WorkingSetBytes - memorySnapshot.WorkingSetBytes;
+ diff.WorkingSetPeakBytes = WorkingSetPeakBytes - memorySnapshot.WorkingSetPeakBytes;
+ diff.WorkingSetPrivateBytes = WorkingSetPrivateBytes - memorySnapshot.WorkingSetPrivateBytes;
+
+ return diff;
+ }
+
+ ///
+ /// The number of handles to GDI objects in use by the process. For more information see the
+ /// GetGuiResources function.
+ ///
+ ///
+ ///
+ /// This metric is reported as follows by the other tools:
+ ///
+ /// | Tool | Metric |
+ ///
+ /// |
+ /// Performance Counters |
+ /// n/a |
+ ///
+ /// |
+ /// Process Explorer |
+ /// Handles : GDI Objects |
+ ///
+ /// | Task Manager (Windows 7) |
+ /// GDI Handles |
+ ///
+ ///
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Gdi")]
+ public long GdiObjectCount { get; private set; }
+
+ ///
+ /// The total number of handles currently open by the process. This number is equal to the sum of the handles
+ /// currently open by each thread in this process.
+ ///
+ ///
+ ///
+ /// This metric is reported as follows by the other tools:
+ ///
+ /// | Tool | Metric |
+ ///
+ /// |
+ /// Performance Counters |
+ /// HandleCount |
+ ///
+ /// |
+ /// Process Explorer |
+ /// Handles : Handles |
+ ///
+ /// | Task Manager (Windows 7) |
+ /// Handles |
+ ///
+ ///
+ public long HandleCount { get; private set; }
+
+ ///
+ /// The current amount of virtual memory that this process has reserved for use
+ /// in the paging file or files. Those pages may or may not be in memory.
+ ///
+ ///
+ ///
+ /// This metric is reported as follows by the other tools:
+ ///
+ /// For more information, see the
+ /// PROCESS_MEMORY_COUNTERS_EX structure.
+ ///
+ public long PageFileBytes { get; private set; }
+
+ ///
+ /// The maximum amount of virtual memory that this process has reserved for use in
+ /// the paging file or files.
+ ///
+ ///
+ ///
+ /// This metric is reported as follows by the other tools:
+ ///
+ /// For more information, see the
+ /// PROCESS_MEMORY_COUNTERS_EX structure.
+ ///
+ public long PageFilePeakBytes { get; private set; }
+
+ ///
+ /// The size of the nonpaged pool, an area of system memory (physical memory used by the operating
+ /// system) for objects that cannot be written to disk but must remain in physical memory as long as they are
+ /// allocated. For more information, see the GetProcessMemoryInfo
+ /// function and the PROCESS_MEMORY_COUNTERS_EX structure.
+ ///
+ ///
+ ///
+ /// This metric is reported as follows by the other tools:
+ ///
+ /// | Tool | Metric |
+ ///
+ /// |
+ /// Performance Counters |
+ /// Pool Nonpaged Bytes |
+ ///
+ /// |
+ /// Process Explorer |
+ /// n/a |
+ ///
+ /// | Task Manager (Windows 7) |
+ /// NonPaged Pool |
+ ///
+ ///
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Nonpaged")]
+ public long PoolNonpagedBytes { get; private set; }
+
+ ///
+ /// The size of the paged pool, an area of system memory (physical memory used by the operating system)
+ /// for objects that can be written to disk when they are not being used. For more information, see
+ /// the GetProcessMemoryInfo function
+ /// and the PROCESS_MEMORY_COUNTERS_EX
+ /// structure.
+ ///
+ ///
+ ///
+ /// This metric is reported as follows by the other tools:
+ ///
+ /// | Tool | Metric |
+ ///
+ /// |
+ /// Performance Counters |
+ /// Pool Paged Bytes |
+ ///
+ /// |
+ /// Process Explorer |
+ /// n/a |
+ ///
+ /// | Task Manager (Windows 7) |
+ /// Paged Pool |
+ ///
+ ///
+ public long PoolPagedBytes { get; private set; }
+
+ ///
+ /// The number of threads currently active in the process.
+ ///
+ ///
+ ///
+ /// This metric is reported as follows by the other tools:
+ ///
+ ///
+ public long ThreadCount { get; private set; }
+
+ ///
+ /// The time when the memory snapshot was taken.
+ ///
+ public DateTime Timestamp { get; private set; }
+
+ ///
+ /// The number of handles to USER objects in use by the process. For more information see the
+ /// GetGuiResources function.
+ ///
+ ///
+ ///
+ /// This metric is reported as follows by the other tools:
+ ///
+ /// | Tool | Metric |
+ ///
+ /// |
+ /// Performance Counters |
+ /// n/a |
+ ///
+ /// |
+ /// Process Explorer |
+ /// Handles : USER Objects |
+ ///
+ /// | Task Manager (Windows 7) |
+ /// USER Handles |
+ ///
+ ///
+ public long UserObjectCount { get; private set; }
+
+ ///
+ /// The current size of the virtual address space that the process is using. Use of virtual address space does
+ /// not necessarily imply corresponding use of either disk or main memory pages. Virtual space is finite,
+ /// and the process can limit its ability to load libraries. For more information see the
+ /// GlobalMemoryStatusEx function
+ /// and the MEMORYSTATUSEX structure.
+ /// This metric is calculated as MEMORYSTATUSEX.ullTotalVirtual ?MEMORYSTATUSEX.ullAvailVirtual.
+ ///
+ ///
+ ///
+ /// This metric is reported as follows by the other tools:
+ ///
+ /// | Tool | Metric |
+ ///
+ /// |
+ /// Performance Counters |
+ /// VirtualBytes |
+ ///
+ /// |
+ /// Process Explorer |
+ /// Virtual Memory : Virtual Size |
+ ///
+ /// | Task Manager (Windows 7) |
+ /// n/a |
+ ///
+ ///
+ public long VirtualMemoryBytes { get; private set; }
+
+ ///
+ /// The current size of memory that this process has allocated that cannot be shared with other processes. For more
+ /// information see the GetProcessMemoryInfo
+ /// function and the PROCESS_MEMORY_COUNTERS_EX
+ /// structure (this metric corresponds to the PrivateUsage field in the structure).
+ ///
+ ///
+ ///
+ /// This metric is reported as follows by the other tools:
+ ///
+ /// | Tool | Metric |
+ ///
+ /// |
+ /// Performance Counters |
+ /// PrivateBytes |
+ ///
+ /// |
+ /// Process Explorer |
+ /// Virtual Memory : Private Bytes |
+ ///
+ /// | Task Manager (Windows 7) |
+ /// Commit Size |
+ ///
+ ///
+ public long VirtualMemoryPrivateBytes { get; private set; }
+
+ ///
+ /// The current size of the working set of the process. The working set is the set of memory pages recently touched
+ /// by the threads in the process. If free memory in the computer is above a threshold, pages are left in the working set
+ /// of a process even if they are not in use. When free memory falls below a threshold, pages are trimmed from working sets.
+ /// If they are needed they will then be soft-faulted back into the working set before leaving main memory.
+ ///
+ ///
+ ///
+ /// This metric is reported as follows by the other tools:
+ ///
+ /// | Tool | Metric |
+ ///
+ /// |
+ /// Performance Counters |
+ /// WorkingSet |
+ ///
+ /// |
+ /// Process Explorer |
+ /// Physical Memory : Working Set |
+ ///
+ /// | Task Manager (Windows 7) |
+ /// Working Set (Memory) |
+ ///
+ ///
+ public long WorkingSetBytes { get; private set; }
+
+ ///
+ /// The maximum size, in bytes, of the working set of the process at any one time.
+ /// For more information see the GetProcessMemoryInfo
+ /// function and the PROCESS_MEMORY_COUNTERS_EX structure.
+ ///
+ ///
+ ///
+ /// This metric is reported as follows by the other tools:
+ ///
+ /// | Tool | Metric |
+ ///
+ /// |
+ /// Performance Counters |
+ /// WorkingSetPeak |
+ ///
+ /// |
+ /// Process Explorer |
+ /// Physical Memory : Peak Working Set |
+ ///
+ /// | Task Manager (Windows 7) |
+ /// Peak Working Set (Memory) |
+ ///
+ ///
+ public long WorkingSetPeakBytes { get; private set; }
+
+ ///
+ /// The size of the working set that is only used for the process and not shared nor shareable by other processes.
+ /// For more information see the Memory Performance Information article.
+ ///
+ ///
+ ///
+ /// This metric is reported as follows by the other tools:
+ ///
+ /// | Tool | Metric |
+ ///
+ /// |
+ /// Performance Counters |
+ /// WorkingSetPrivate |
+ ///
+ /// |
+ /// Process Explorer |
+ /// Physical Memory : Working Set : WS Private |
+ ///
+ /// | Task Manager (Windows 7) |
+ /// Memory (Private Working Set) |
+ ///
+ ///
+ ///
+ public long WorkingSetPrivateBytes { get; private set; }
+
+ ///
+ /// An instance of the class can only be created by using one of the static From* methods.
+ ///
+ private MemorySnapshot()
+ {
+ // Nothing
+ }
+
+ #endregion
+
+ #region Serialization / Deserialization Helpers
+
+
+ ///
+ /// Serializes a MemorySnapshot instance to xml format.
+ ///
+ /// The XmlDocument to which the MemorySnapshot is to be serialized
+ ///
+ internal XmlNode Serialize(XmlDocument xmlDoc)
+ {
+ XmlNode rootNode = xmlDoc.CreateElement("MemorySnapshot");
+
+ // Create memory stats attributes.
+ SerializeNodes(xmlDoc, rootNode, "GdiObjectCount", GdiObjectCount);
+ SerializeNodes(xmlDoc, rootNode, "HandleCount", HandleCount);
+ SerializeNodes(xmlDoc, rootNode, "PageFileBytes", PageFileBytes);
+ SerializeNodes(xmlDoc, rootNode, "PageFilePeakBytes", PageFilePeakBytes);
+ SerializeNodes(xmlDoc, rootNode, "PoolNonpagedBytes", PoolNonpagedBytes);
+ SerializeNodes(xmlDoc, rootNode, "PoolPagedBytes", PoolPagedBytes);
+ SerializeNodes(xmlDoc, rootNode, "ThreadCount", ThreadCount);
+ SerializeNodes(xmlDoc, rootNode, "UserObjectCount", UserObjectCount);
+ SerializeNodes(xmlDoc, rootNode, "VirtualMemoryBytes", VirtualMemoryBytes);
+ SerializeNodes(xmlDoc, rootNode, "VirtualMemoryPrivateBytes", VirtualMemoryPrivateBytes);
+ SerializeNodes(xmlDoc, rootNode, "WorkingSetBytes", WorkingSetBytes);
+ SerializeNodes(xmlDoc, rootNode, "WorkingSetPeakBytes", WorkingSetPeakBytes);
+ SerializeNodes(xmlDoc, rootNode, "WorkingSetPrivateBytes", WorkingSetPrivateBytes);
+
+ // Save Timestamp.
+ XmlNode TimestampNode = xmlDoc.CreateElement("Timestamp");
+ XmlAttribute attribute = xmlDoc.CreateAttribute("Value");
+ attribute.InnerText = Timestamp.ToString(CultureInfo.InvariantCulture);
+ TimestampNode.Attributes.Append(attribute);
+ rootNode.AppendChild(TimestampNode);
+
+ return rootNode;
+ }
+
+ ///
+ /// De-Serializes a MemorySnapshot instance from xml format.
+ ///
+ /// The Xml Node from which the MemorySnapshot is to be de-serialized
+ ///
+ internal static MemorySnapshot Deserialize(XmlNode rootNode)
+ {
+ MemorySnapshot memorySnapshot = new MemorySnapshot();
+
+ memorySnapshot.GdiObjectCount = DeserializeNode(rootNode, "GdiObjectCount");
+ memorySnapshot.HandleCount = DeserializeNode(rootNode, "HandleCount");
+ memorySnapshot.PageFileBytes = DeserializeNode(rootNode, "PageFileBytes");
+ memorySnapshot.PageFilePeakBytes = DeserializeNode(rootNode, "PageFilePeakBytes");
+ memorySnapshot.PoolNonpagedBytes = DeserializeNode(rootNode, "PoolNonpagedBytes");
+ memorySnapshot.PoolPagedBytes = DeserializeNode(rootNode, "PoolPagedBytes");
+ memorySnapshot.ThreadCount = DeserializeNode(rootNode, "ThreadCount");
+ memorySnapshot.UserObjectCount = DeserializeNode(rootNode, "UserObjectCount");
+ memorySnapshot.VirtualMemoryBytes = DeserializeNode(rootNode, "VirtualMemoryBytes");
+ memorySnapshot.VirtualMemoryPrivateBytes = DeserializeNode(rootNode, "VirtualMemoryPrivateBytes");
+ memorySnapshot.WorkingSetBytes = DeserializeNode(rootNode, "WorkingSetBytes");
+ memorySnapshot.WorkingSetPeakBytes = DeserializeNode(rootNode, "WorkingSetPeakBytes");
+ memorySnapshot.WorkingSetPrivateBytes = DeserializeNode(rootNode, "WorkingSetPrivateBytes");
+
+ // Grab Timestamp.
+ XmlNode memoryStatNode = rootNode.SelectSingleNode("Timestamp");
+ if (memoryStatNode == null)
+ {
+ throw new XmlException("MemorySnapshot file is missing value: Timestamp");
+ }
+
+ XmlAttribute attribute = memoryStatNode.Attributes["Value"];
+ memorySnapshot.Timestamp = (DateTime)Convert.ToDateTime(attribute.InnerText, CultureInfo.InvariantCulture);
+
+ return memorySnapshot;
+ }
+
+ private void SerializeNodes(XmlDocument xmlDoc, XmlNode rootNode, string nodeName, long value)
+ {
+ XmlNode newNode = xmlDoc.CreateElement(nodeName);
+ XmlAttribute attribute = xmlDoc.CreateAttribute("Value");
+ attribute.InnerText = value.ToString(CultureInfo.InvariantCulture);
+ newNode.Attributes.Append(attribute);
+ rootNode.AppendChild(newNode);
+ }
+
+ private static long DeserializeNode(XmlNode rootNode, string nodeName)
+ {
+ XmlNode memoryStatNode = rootNode.SelectSingleNode(nodeName);
+ if (memoryStatNode == null)
+ {
+ throw new XmlException("MemorySnapshot file is missing value: " + nodeName);
+ }
+
+ XmlAttribute attribute = memoryStatNode.Attributes["Value"];
+ return (long)Convert.ToInt64(attribute.InnerText, NumberFormatInfo.InvariantInfo);
+ }
+
+ #endregion
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/LeakDetection/MemorySnapshotCollection.cs b/src/dotnetCampus.UITest.WPFTestHelper/LeakDetection/MemorySnapshotCollection.cs
new file mode 100644
index 0000000..2743edf
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/LeakDetection/MemorySnapshotCollection.cs
@@ -0,0 +1,111 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+using System;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Xml;
+
+namespace dotnetCampus.UITest.WPFTestHelper.LeakDetection
+{
+ ///
+ /// A collection of MemorySnapshots that can be serialized to an XML file.
+ ///
+ ///
+ ///
+ /// The following example demonstrates taking multiple memory snapshots of Notepad
+ /// and saving them on disk for later analysis.
+ ///
+ /// MemorySnapshotCollection c = new MemorySnapshotCollection();
+ ///
+ /// Process p = Process.Start("notepad.exe");
+ /// p.WaitForInputIdle(5000);
+ /// MemorySnapshot s1 = MemorySnapshot.FromProcess(p.Id);
+ /// c.Add(s1);
+ ///
+ /// // Perform operations that may cause a leak...
+ ///
+ /// MemorySnapshot s2 = MemorySnapshot.FromProcess(p.Id);
+ /// c.Add(s2);
+ ///
+ /// c.ToFile(@"MemorySnapshots.xml");
+ ///
+ /// p.CloseMainWindow();
+ /// p.Close();
+ ///
+ ///
+ ///
+ ///
+ /// A MemorySnapshotCollection can also be loaded from a XML file.
+ ///
+ /// MemorySnapshotCollection c = MemorySnapshotCollection.FromFile(@"MemorySnapshots.xml");
+ ///
+ ///
+ public class MemorySnapshotCollection : Collection
+ {
+ ///
+ /// Creates a MemorySnapshotCollection instance from data in the specified file.
+ ///
+ /// The path to the MemorySnapshotCollection file.
+ /// A MemorySnapshotCollection instance, containing memory information recorded in the specified file.
+ public static MemorySnapshotCollection FromFile(string filePath)
+ {
+ MemorySnapshotCollection msColllection = new MemorySnapshotCollection();
+
+ XmlDocument xmlDoc = new XmlDocument();
+ using (Stream s = new FileInfo(filePath).OpenRead())
+ {
+ try
+ {
+ xmlDoc.Load(s);
+ }
+ catch (XmlException)
+ {
+ throw new XmlException("MemorySnapshotCollection file \"" + filePath + "\" could not be loaded.");
+ }
+ }
+
+ XmlNodeList msNodeList = xmlDoc.DocumentElement.SelectNodes("MemorySnapshot");
+ foreach (XmlNode msNode in msNodeList)
+ {
+ // Desrialize node.
+ MemorySnapshot ms = MemorySnapshot.Deserialize(msNode);
+
+ // Add to collection.
+ msColllection.Add(ms);
+
+ }
+
+ return msColllection;
+ }
+
+ ///
+ /// Writes the current MemorySnapshotCollection to a file.
+ ///
+ /// The path to the output file.
+ public void ToFile(string filePath)
+ {
+ if (String.IsNullOrEmpty(filePath))
+ {
+ throw new ArgumentNullException("MemorySnapshot.ToFile(): the specified file path \"" + filePath + "\" is null or empty.");
+ }
+
+ XmlDocument xmlDoc = new XmlDocument();
+ XmlNode rootNode = xmlDoc.CreateElement("MemorySnapshotCollection");
+
+ foreach (MemorySnapshot ms in this.Items)
+ {
+ // Call serializer on MemorySnapshot.
+ XmlNode msNode = ms.Serialize(xmlDoc);
+
+ // Append to MemorySnapshotCollection.
+ rootNode.AppendChild(msNode);
+ }
+
+ xmlDoc.AppendChild(rootNode);
+ xmlDoc.Save(filePath);
+ }
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/LeakDetection/NativeMethods.cs b/src/dotnetCampus.UITest.WPFTestHelper/LeakDetection/NativeMethods.cs
new file mode 100644
index 0000000..921c319
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/LeakDetection/NativeMethods.cs
@@ -0,0 +1,108 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace dotnetCampus.UITest.WPFTestHelper.LeakDetection
+{
+ internal static class NativeMethods
+ {
+ internal const int GR_GDIOBJECTS = 0;
+ internal const int GR_USEROBJECTS = 1;
+
+ [DllImport("User32.dll")]
+ internal static extern int GetGuiResources(IntPtr hProcess, int flags);
+
+ [DllImport("kernel32.dll")]
+ internal static extern void GetSystemInfo([MarshalAs(UnmanagedType.Struct)] ref SYSTEM_INFO lpSystemInfo);
+
+ // Interop call the get workingset information.
+ [DllImport("psapi.dll", SetLastError = true)]
+ internal static extern int QueryWorkingSet(IntPtr hProcess, IntPtr info, int size);
+
+ // Interop call the get performance memory counters
+ [DllImport("psapi.dll", SetLastError = true)]
+ internal static extern int GetProcessMemoryInfo(IntPtr hProcess, out PROCESS_MEMORY_COUNTERS_EX counters, int size);
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct SYSTEM_INFO
+ {
+ internal _PROCESSOR_INFO_UNION uProcessorInfo;
+ public uint dwPageSize;
+ public IntPtr lpMinimumApplicationAddress;
+ public IntPtr lpMaximumApplicationAddress;
+ public IntPtr dwActiveProcessorMask;
+ public uint dwNumberOfProcessors;
+ public uint dwProcessorType;
+ public uint dwAllocationGranularity;
+ public ushort dwProcessorLevel;
+ public ushort dwProcessorRevision;
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ internal struct _PROCESSOR_INFO_UNION
+ {
+ [FieldOffset(0)]
+ internal uint dwOemId;
+ [FieldOffset(0)]
+ internal ushort wProcessorArchitecture;
+ [FieldOffset(2)]
+ internal ushort wReserved;
+ }
+
+ // Struct to hold performace memory counters.
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct PROCESS_MEMORY_COUNTERS_EX
+ {
+ public int cb;
+ public int PageFaultCount;
+ public int PeakWorkingSetSize;
+ public int WorkingSetSize;
+ public int QuotaPeakPagedPoolUsage;
+ public int QuotaPagedPoolUsage;
+ public int QuotaPeakNonPagedPoolUsage;
+ public int QuotaNonPagedPoolUsage;
+ public int PagefileUsage;
+ public int PeakPagefileUsage;
+ public int PrivateUsage;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal class PSAPI_WORKING_SET_INFORMATION
+ {
+ public int NumberOfEntries;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1, ArraySubType = UnmanagedType.Struct)]
+ public PSAPI_WORKING_SET_BLOCK[] WorkingSetInfo;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct BLOCK
+ {
+ public uint bitvector1;
+
+ public uint Protection;
+ public uint ShareCount;
+ public uint Reserved;
+ public uint VirtualPage;
+
+ public uint Shared
+ {
+ get { return ((uint)(((this.bitvector1 & 256u) >> 8))); }
+ }
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ internal struct PSAPI_WORKING_SET_BLOCK
+ {
+ [FieldOffset(0)]
+ public uint Flags;
+
+ [FieldOffset(0)]
+ public BLOCK Block1;
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/ObjectComparison/GraphNode.cs b/src/dotnetCampus.UITest.WPFTestHelper/ObjectComparison/GraphNode.cs
new file mode 100644
index 0000000..fde0e40
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/ObjectComparison/GraphNode.cs
@@ -0,0 +1,166 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+
+namespace dotnetCampus.UITest.WPFTestHelper.ObjectComparison
+{
+ ///
+ /// Represents one node in the object graph.
+ /// The root of the graph is a graph node.
+ ///
+ [DebuggerDisplay("{Name}")]
+ public class GraphNode
+ {
+ #region Public and Protected Members
+
+ ///
+ /// A name to identify this node. When comparing,
+ /// the left and right nodes are matched by name.
+ ///
+ public string Name
+ {
+ get
+ {
+ return this.name;
+ }
+
+ set
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ throw new ArgumentException("Please provide a valid name", "value");
+ }
+ this.name = value;
+ }
+ }
+
+ ///
+ /// Gets the collection of child nodes to this
+ /// node. The child nodes represent properties or
+ /// fields on an object.
+ ///
+ public Collection Children
+ {
+ get
+ {
+ if (this.children == null)
+ {
+ this.children = new Collection();
+ }
+
+ return children;
+ }
+ }
+
+ ///
+ /// Represents the immediate parent to this node.
+ /// The parent node of the root of the graph is null.
+ ///
+ public GraphNode Parent { get; set; }
+
+ ///
+ /// Contains the value of the object represented by
+ /// this node.
+ ///
+ public object ObjectValue { get; set; }
+
+ ///
+ /// Provides the System.Type of the object represented by
+ /// this node. Returns null if the object value is null.
+ ///
+ public Type ObjectType
+ {
+ get
+ {
+ Type objectType = null;
+ if (this.ObjectValue != null)
+ {
+ objectType = this.ObjectValue.GetType();
+ }
+
+ return objectType;
+ }
+ }
+
+ ///
+ /// Gets the depth of this node from the root. If the depth of
+ /// the root node is 0, root.child is 1.
+ ///
+ public int Depth
+ {
+ get
+ {
+ GraphNode node = this;
+ int depth = 0;
+ while (node.Parent != null)
+ {
+ depth++;
+ node = node.Parent;
+ }
+ return depth;
+ }
+ }
+
+ ///
+ /// Gets the fully qualified name of this node
+ /// If the root node has a child that is named child1 and the child
+ /// has another child that is named child12, qualified name of child12
+ /// would be root.child1.child12.
+ ///
+ public string QualifiedName
+ {
+ get
+ {
+ GraphNode node = this;
+ string qualifiedName = this.Name;
+ while (node.Parent != null)
+ {
+ qualifiedName = node.Parent.Name + "." + qualifiedName;
+ node = node.Parent;
+ }
+ return qualifiedName;
+ }
+ }
+
+ ///
+ /// Performs a depth-first traversal of the graph with
+ /// this node as root, and provides the nodes visited, in that
+ /// order.
+ ///
+ /// Nodes visited in depth-first order.
+ public IEnumerable GetNodesInDepthFirstOrder()
+ {
+ Stack pendingNodes = new Stack();
+ pendingNodes.Push(this);
+ HashSet visitedNodes = new HashSet();
+ while (pendingNodes.Count != 0)
+ {
+ GraphNode currentNode = pendingNodes.Pop();
+ if (!visitedNodes.Contains(currentNode))
+ {
+ foreach (GraphNode node in currentNode.Children)
+ {
+ pendingNodes.Push(node);
+ }
+ visitedNodes.Add(currentNode);
+ yield return currentNode;
+ }
+ }
+ }
+
+ #endregion
+
+ #region Private Data
+
+ private Collection children;
+ private string name;
+
+ #endregion
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/ObjectComparison/ObjectComparer.cs b/src/dotnetCampus.UITest.WPFTestHelper/ObjectComparison/ObjectComparer.cs
new file mode 100644
index 0000000..561a723
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/ObjectComparison/ObjectComparer.cs
@@ -0,0 +1,251 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace dotnetCampus.UITest.WPFTestHelper.ObjectComparison
+{
+ ///
+ /// Represents a generic object comparer. This class uses an
+ /// instance to convert objects to graph
+ /// representations before comparing the representations.
+ ///
+ ///
+ /// Comparing two objects for equivalence is a relatively common task during test validation.
+ /// One example would be to test whether a type follows the rules required by a particular
+ /// serializer by saving and loading the object and comparing the two. A deep object
+ /// comparison is one where all the properties and its properties are compared repeatedly
+ /// until primitives are reached. The .NET Framework provides mechanisms to perform such comparisons but
+ /// requires the types in question to implement part of the comparison logic
+ /// (IComparable, .Equals). However, there are often types that do not follow
+ /// these mechanisms. This API provides a mechanism to deep compare two objects using
+ /// reflection.
+ ///
+ ///
+ ///
+ /// The following example demonstrates how to compare two objects using a general-purpose object
+ /// comparison strategy (represented by ).
+ ///
+ ///
+ /// Person p1 = new Person("Microsoft");
+ /// p1.Children.Add(new Person("Peter"));
+ /// p1.Children.Add(new Person("Mary"));
+ ///
+ /// Person p2 = new Person("Microsoft");
+ /// p2.Children.Add(new Person("Peter"));
+ ///
+ /// ObjectGraphFactory factory = new PublicPropertyObjectGraphFactory();
+ /// ObjectComparer comparer = new ObjectComparer(factory);
+ /// Console.WriteLine(
+ /// "Objects p1 and p2 {0}",
+ /// comparer.Compare(p1, p2) ? "match!" : "do NOT match!");
+ ///
+ ///
+ /// where Person is declared as follows:
+ ///
+ ///
+ /// class Person
+ /// {
+ /// public Person(string name)
+ /// {
+ /// Name = name;
+ /// Children = new Collection<Person>();
+ /// }
+ /// public string Name { get; set; }
+ /// public Collection<Person> Children { get; private set; }
+ /// }
+ ///
+ ///
+ ///
+ ///
+ /// In addition, the object comparison API allows the user to get back a list of comparison mismatches.
+ /// For an example, see objects).
+ ///
+ public sealed class ObjectComparer
+ {
+ #region Constuctors
+
+ ///
+ /// Creates an instance of the ObjectComparer class.
+ ///
+ /// An ObjectGraphFactory to use for
+ /// converting objects to graphs.
+ public ObjectComparer(ObjectGraphFactory factory)
+ {
+ if (factory == null)
+ {
+ throw new ArgumentNullException("factory");
+ }
+
+ this.objectGraphFactory = factory;
+ }
+
+ #endregion
+
+ #region Public and Protected Members
+
+ ///
+ /// Gets the ObjectGraphFactory used to convert objects
+ /// to graphs.
+ ///
+ public ObjectGraphFactory ObjectGraphFactory
+ {
+ get
+ {
+ return this.objectGraphFactory;
+ }
+ }
+
+ ///
+ /// Performs a deep comparison of two objects.
+ ///
+ /// The left object.
+ /// The right object.
+ /// true if the objects match.
+ public bool Compare(object leftValue, object rightValue)
+ {
+ IEnumerable mismatches;
+ return Compare(leftValue, rightValue, out mismatches);
+ }
+
+ ///
+ /// Performs a deep comparison of two objects and provides
+ /// a list of mismatching nodes.
+ ///
+ /// The left object.
+ /// The right object.
+ /// The list of mismatches.
+ /// true if the objects match.
+ public bool Compare(object leftValue, object rightValue, out IEnumerable mismatches)
+ {
+ List mismatch;
+ bool isMatch = this.CompareObjects(leftValue, rightValue, out mismatch);
+ mismatches = mismatch;
+
+ return isMatch;
+ }
+
+ #endregion
+
+ #region Private Members
+
+ private bool CompareObjects(object leftObject, object rightObject, out List mismatches)
+ {
+ mismatches = new List();
+
+ // Get the graph from the objects
+ GraphNode leftRoot = this.ObjectGraphFactory.CreateObjectGraph(leftObject);
+ GraphNode rightRoot = this.ObjectGraphFactory.CreateObjectGraph(rightObject);
+
+ // Get the nodes in breadth first order
+ List leftNodes = new List(leftRoot.GetNodesInDepthFirstOrder());
+ List rightNodes = new List(rightRoot.GetNodesInDepthFirstOrder());
+
+ // For each node in the left tree, search for the
+ // node in the right tree and compare them
+ for (int i = 0; i < leftNodes.Count; i++)
+ {
+ GraphNode leftNode = leftNodes[i];
+
+ var nodelist = from node in rightNodes
+ where leftNode.QualifiedName.Equals(node.QualifiedName)
+ select node;
+
+ List matchingNodes = nodelist.ToList();
+ if (matchingNodes.Count != 1)
+ {
+ ObjectComparisonMismatch mismatch = new ObjectComparisonMismatch(leftNode, null, ObjectComparisonMismatchType.MissingRightNode);
+ mismatches.Add(mismatch);
+ continue;
+ }
+
+ GraphNode rightNode = matchingNodes[0];
+
+ // Compare the nodes
+ ObjectComparisonMismatch nodesMismatch = CompareNodes(leftNode, rightNode);
+ if (nodesMismatch != null)
+ {
+ mismatches.Add(nodesMismatch);
+ }
+ }
+
+ bool passed = mismatches.Count == 0 ? true : false;
+
+ return passed;
+ }
+
+ private static ObjectComparisonMismatch CompareNodes(GraphNode leftNode, GraphNode rightNode)
+ {
+ // Check if both are null
+ if (leftNode.ObjectValue == null && rightNode.ObjectValue == null)
+ {
+ return null;
+ }
+
+ // check if one of them is null
+ if (leftNode.ObjectValue == null || rightNode.ObjectValue == null)
+ {
+ ObjectComparisonMismatch mismatch = new ObjectComparisonMismatch(
+ leftNode,
+ rightNode,
+ ObjectComparisonMismatchType.ObjectValuesDoNotMatch);
+ return mismatch;
+ }
+
+ // compare type names //
+ if (!leftNode.ObjectType.Equals(rightNode.ObjectType))
+ {
+ ObjectComparisonMismatch mismatch = new ObjectComparisonMismatch(
+ leftNode,
+ rightNode,
+ ObjectComparisonMismatchType.ObjectTypesDoNotMatch);
+ return mismatch;
+ }
+
+ // compare primitives, strings
+ if (leftNode.ObjectType.IsPrimitive || leftNode.ObjectType == typeof(string))
+ {
+ if (!leftNode.ObjectValue.Equals(rightNode.ObjectValue))
+ {
+ ObjectComparisonMismatch mismatch = new ObjectComparisonMismatch(
+ leftNode,
+ rightNode,
+ ObjectComparisonMismatchType.ObjectValuesDoNotMatch);
+ return mismatch;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ // compare the child count
+ if (leftNode.Children.Count != rightNode.Children.Count)
+ {
+ var type = leftNode.Children.Count > rightNode.Children.Count ?
+ ObjectComparisonMismatchType.RightNodeHasFewerChildren : ObjectComparisonMismatchType.LeftNodeHasFewerChildren;
+
+ ObjectComparisonMismatch mismatch = new ObjectComparisonMismatch(
+ leftNode,
+ rightNode,
+ type);
+ return mismatch;
+ }
+
+ // No mismatch //
+ return null;
+ }
+
+ #endregion
+
+ #region Private Data
+
+ private ObjectGraphFactory objectGraphFactory;
+
+ #endregion
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/ObjectComparison/ObjectComparisonMismatch.cs b/src/dotnetCampus.UITest.WPFTestHelper/ObjectComparison/ObjectComparisonMismatch.cs
new file mode 100644
index 0000000..05d8a13
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/ObjectComparison/ObjectComparisonMismatch.cs
@@ -0,0 +1,124 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+using System.Diagnostics;
+
+namespace dotnetCampus.UITest.WPFTestHelper.ObjectComparison
+{
+ ///
+ /// Represents one comparison mismatch.
+ ///
+ ///
+ ///
+ /// The following example shows how to derive the list of comparison mismatches.
+ ///
+ ///
+ /// Person p1 = new Person("Microsoft");
+ /// p1.Children.Add(new Person("Peter"));
+ /// p1.Children.Add(new Person("Mary"));
+ ///
+ /// Person p2 = new Person("Microsoft");
+ /// p2.Children.Add(new Person("Peter"));
+ ///
+ /// // Perform the compare operation
+ /// ObjectGraphFactory factory = new PublicPropertyObjectGraphFactory();
+ /// ObjectComparer comparer = new ObjectComparer(factory);
+ /// IEnumerable<ObjectComparisonMismatch> m12 = new List<ObjectComparisonMismatch>();
+ /// Console.WriteLine(
+ /// "Objects p1 and p2 {0}",
+ /// comparer.Compare(p1, p2, out m12) ? "match!" : "do NOT match!");
+ ///
+ /// foreach (ObjectComparisonMismatch m in m12)
+ /// {
+ /// Console.WriteLine(
+ /// "Nodes '{0}' and '{1}' do not match. Mismatch message: '{2}'",
+ /// m.LeftObjectNode != null ? m.LeftObjectNode.Name : "null",
+ /// m.RightObjectNode != null ? m.LeftObjectNode.Name : "null",
+ /// m.MismatchType);
+ /// }
+ ///
+ ///
+ /// where Person is declared as follows:
+ ///
+ ///
+ /// class Person
+ /// {
+ /// public Person(string name)
+ /// {
+ /// Name = name;
+ /// Children = new Collection<Person>();
+ /// }
+ /// public string Name { get; set; }
+ /// public Collection<Person> Children { get; private set; }
+ /// }
+ ///
+ ///
+
+ [DebuggerDisplay("{MismatchType}: LeftNodeName={LeftObjectNode.QualifiedName}")]
+ public sealed class ObjectComparisonMismatch
+ {
+ #region Constructors
+
+ ///
+ /// Creates an instance of the ObjectComparisonMismatch class.
+ ///
+ /// The node from the left object.
+ /// The node from the right object.
+ /// Represents the type of mismatch.
+ public ObjectComparisonMismatch(GraphNode leftObjectNode, GraphNode rightObjectNode, ObjectComparisonMismatchType mismatchType)
+ {
+ this.leftObjectNode = leftObjectNode;
+ this.rightObjectNode = rightObjectNode;
+ this.mismatchType = mismatchType;
+ }
+
+ #endregion Public Members
+
+ #region Public Members
+
+ ///
+ /// Gets the node in the left object.
+ ///
+ public GraphNode LeftObjectNode
+ {
+ get
+ {
+ return this.leftObjectNode;
+ }
+ }
+
+ ///
+ /// Gets the node in the right object.
+ ///
+ public GraphNode RightObjectNode
+ {
+ get
+ {
+ return this.rightObjectNode;
+ }
+ }
+
+ ///
+ /// Represents the type of mismatch.
+ ///
+ public ObjectComparisonMismatchType MismatchType
+ {
+ get
+ {
+ return this.mismatchType;
+ }
+ }
+
+ #endregion
+
+ #region Private Data
+
+ private GraphNode leftObjectNode;
+ private GraphNode rightObjectNode;
+ private ObjectComparisonMismatchType mismatchType;
+
+ #endregion
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/ObjectComparison/ObjectComparisonMismatchType.cs b/src/dotnetCampus.UITest.WPFTestHelper/ObjectComparison/ObjectComparisonMismatchType.cs
new file mode 100644
index 0000000..75d00ce
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/ObjectComparison/ObjectComparisonMismatchType.cs
@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+namespace dotnetCampus.UITest.WPFTestHelper.ObjectComparison
+{
+ ///
+ /// Represents the type of mismatch.
+ ///
+ public enum ObjectComparisonMismatchType
+ {
+ ///
+ /// The node is missing in the right graph.
+ ///
+ MissingRightNode = 0,
+
+ ///
+ /// The node is missing in the left graph.
+ ///
+ MissingLeftNode = 1,
+
+ ///
+ /// The right node has fewer children than the left node.
+ ///
+ RightNodeHasFewerChildren = 2,
+
+ ///
+ /// The left node has fewer children than the right node.
+ ///
+ LeftNodeHasFewerChildren = 3,
+
+ ///
+ /// The node types do not match.
+ ///
+ ObjectTypesDoNotMatch = 4,
+
+ ///
+ /// The node values do not match.
+ ///
+ ObjectValuesDoNotMatch = 5
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/ObjectComparison/ObjectGraphFactory.cs b/src/dotnetCampus.UITest.WPFTestHelper/ObjectComparison/ObjectGraphFactory.cs
new file mode 100644
index 0000000..7910f41
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/ObjectComparison/ObjectGraphFactory.cs
@@ -0,0 +1,68 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+using System;
+
+namespace dotnetCampus.UITest.WPFTestHelper.ObjectComparison
+{
+ ///
+ /// Creates a graph for the provided object.
+ ///
+ ///
+ /// The following example demonstrates the use of a simple factory to do shallow comparison of two objects.
+ ///
+ /// class Person
+ /// {
+ /// public Person(string name)
+ /// {
+ /// Name = name;
+ /// Children = new Collection<Person>();
+ /// }
+ /// public string Name { get; set; }
+ /// public Collection<Person> Children { get; private set; }
+ /// }
+ ///
+ ///
+ /// class SimpleObjectGraphFactory : ObjectGraphFactory
+ /// {
+ /// public override GraphNode CreateObjectGraph(object o)
+ /// {
+ /// // Build the object graph with nodes that need to be compared.
+ /// // in this particular case, we only pick up the object itself
+ /// GraphNode node = new GraphNode();
+ /// node.Name = "PersonObject";
+ /// node.ObjectValue = (o as Person).Name;
+ /// return node;
+ /// }
+ /// }
+ ///
+ ///
+ /// Person p1 = new Person("Microsoft");
+ /// p1.Children.Add(new Person("Peter"));
+ /// p1.Children.Add(new Person("Mary"));
+ ///
+ /// Person p2 = new Person("Microsoft");
+ /// p2.Children.Add(new Person("Peter"));
+ ///
+ /// ObjectGraphFactory factory = new SimpleObjectGraphFactory();
+ /// ObjectComparer comparer = new ObjectComparer(factory);
+ /// Console.WriteLine(
+ /// "Objects p1 and p2 {0}",
+ /// comparer.Compare(p1, p2) ? "match!" : "do NOT match!");
+ ///
+ ///
+ public abstract class ObjectGraphFactory
+ {
+ ///
+ /// Creates a graph for the given object.
+ ///
+ /// The object to convert.
+ /// The root node of the created graph.
+ public virtual GraphNode CreateObjectGraph(object value)
+ {
+ throw new NotSupportedException("Please provide a behavior for this method in a derived class");
+ }
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/ObjectComparison/PublicPropertyObjectGraphFactory.cs b/src/dotnetCampus.UITest.WPFTestHelper/ObjectComparison/PublicPropertyObjectGraphFactory.cs
new file mode 100644
index 0000000..f8188b5
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/ObjectComparison/PublicPropertyObjectGraphFactory.cs
@@ -0,0 +1,214 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reflection;
+
+namespace dotnetCampus.UITest.WPFTestHelper.ObjectComparison
+{
+ ///
+ /// Creates a graph by extracting public instance properties in the object. If the
+ /// property is an IEnumerable, extract the items. If an exception is thrown
+ /// when accessing a property on the left object, it is considered a match if
+ /// the same exception type is thrown when accessing the property on the right
+ /// object.
+ ///
+ ///
+ ///
+ /// For examples, refer to .
+ ///
+ public sealed class PublicPropertyObjectGraphFactory : ObjectGraphFactory
+ {
+ #region Public Members
+
+ ///
+ /// Creates a graph for the given object by extracting public properties.
+ ///
+ /// The object to convert.
+ /// The root node of the created graph.
+ public override GraphNode CreateObjectGraph(object value)
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+
+ // Queue of pending nodes
+ Queue pendingQueue = new Queue();
+
+ // Dictionary of < object hashcode, node > - to lookup already visited objects
+ Dictionary visitedObjects = new Dictionary();
+
+ // Build the root node and enqueue it
+ GraphNode root = new GraphNode()
+ {
+ Name = "RootObject",
+ ObjectValue = value,
+ };
+
+ pendingQueue.Enqueue(root);
+
+ while (pendingQueue.Count != 0)
+ {
+ GraphNode currentNode = pendingQueue.Dequeue();
+ object nodeData = currentNode.ObjectValue;
+ Type nodeType = currentNode.ObjectType;
+
+ // If we have reached a leaf node -
+ // no more processing is necessary
+ if (IsLeafNode(nodeData, nodeType))
+ {
+ continue;
+ }
+
+ // Handle loops by checking the visted objects
+ if (visitedObjects.Keys.Contains(nodeData.GetHashCode()))
+ {
+ // Caused by a cycle - we have alredy seen this node so
+ // use the existing node instead of creating a new one
+ GraphNode prebuiltNode = visitedObjects[nodeData.GetHashCode()];
+ currentNode.Children.Add(prebuiltNode);
+ continue;
+ }
+ else
+ {
+ visitedObjects.Add(nodeData.GetHashCode(), currentNode);
+ }
+
+ // Extract and add child nodes for current object //
+ Collection childNodes = GetChildNodes(nodeData);
+ foreach (GraphNode childNode in childNodes)
+ {
+ childNode.Parent = currentNode;
+ currentNode.Children.Add(childNode);
+
+ pendingQueue.Enqueue(childNode);
+ }
+ }
+
+ return root;
+ }
+
+ #endregion
+
+ #region Private Members
+
+ ///
+ /// Given an object, get a list of the immediate child nodes
+ ///
+ /// The object whose child nodes need to be extracted
+ /// Collection of child graph nodes
+ private Collection GetChildNodes(object nodeData)
+ {
+ Collection childNodes = new Collection();
+
+ // Extract and add properties
+ foreach (GraphNode child in ExtractProperties(nodeData))
+ {
+ childNodes.Add(child);
+ }
+
+ // Extract and add IEnumerable content
+ if (IsIEnumerable(nodeData))
+ {
+ foreach (GraphNode child in GetIEnumerableChildNodes(nodeData))
+ {
+ childNodes.Add(child);
+ }
+ }
+
+ return childNodes;
+ }
+
+ private List ExtractProperties(object nodeData)
+ {
+ List childNodes = new List();
+
+ PropertyInfo[] properties = nodeData.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
+ foreach (PropertyInfo property in properties)
+ {
+ object value = null;
+
+ ParameterInfo[] parameters = property.GetIndexParameters();
+ // Skip indexed properties and properties that cannot be read
+ if (property.CanRead && parameters.Length == 0)
+ {
+ try
+ {
+ value = property.GetValue(nodeData, null);
+ }
+ catch (Exception ex)
+ {
+ // If accessing the property threw an exception
+ // then make the type of exception as the child.
+ // Do we want to validate the entire exception object
+ // here ? - currently not doing to improve perf.
+ value = ex.GetType().ToString();
+ }
+
+ GraphNode childNode = new GraphNode()
+ {
+ Name = property.Name,
+ ObjectValue = value,
+ };
+
+ childNodes.Add(childNode);
+ }
+ };
+
+ return childNodes;
+ }
+
+ private static List GetIEnumerableChildNodes(object nodeData)
+ {
+ List childNodes = new List();
+
+ IEnumerable enumerableData = nodeData as IEnumerable;
+ IEnumerator enumerator = enumerableData.GetEnumerator();
+
+ int count = 0;
+ while (enumerator.MoveNext())
+ {
+ GraphNode childNode = new GraphNode()
+ {
+ Name = "IEnumerable" + count++,
+ ObjectValue = enumerator.Current,
+ };
+
+ childNodes.Add(childNode);
+ }
+
+ return childNodes;
+ }
+
+ private static bool IsIEnumerable(object nodeData)
+ {
+ IEnumerable enumerableData = nodeData as IEnumerable;
+ if (enumerableData != null &&
+ enumerableData.GetType().IsPrimitive == false &&
+ nodeData.GetType() != typeof(System.String))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ private static bool IsLeafNode(object nodeData, Type nodeType)
+ {
+ return nodeData == null ||
+ nodeType.IsPrimitive ||
+ nodeType == typeof(string);
+ }
+
+ #endregion
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/README.md b/src/dotnetCampus.UITest.WPFTestHelper/README.md
new file mode 100644
index 0000000..fa9e436
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/README.md
@@ -0,0 +1,3 @@
+# dotnetCampus.UITest.WPFTestHelper
+
+Copy from https://github.com/dotnet/wpf-test/
\ No newline at end of file
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/BidiProperty.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/BidiProperty.cs
new file mode 100644
index 0000000..274538d
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/BidiProperty.cs
@@ -0,0 +1,197 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// Bidi Unicode range property
+ ///
+ internal class BidiProperty : IStringProperty
+ {
+ ///
+ /// Dictionary to store Bidi control code points for RegExp use
+ ///
+ private Dictionary bidiDictionary = new Dictionary();
+
+ private List bidiPropertyRangeList = new List();
+
+ private List latinRangeList = new List();
+
+ private static readonly int[] exclusions = {0x0604, 0x0605, 0x061C, 0x061D, 0x0620, 0x065F, 0xFBB2, 0xFBB3, 0xFBB4, 0xFBB5, 0xFBB6, 0xFBB7, 0xFBB8,
+ 0xFBB9, 0xFBBA, 0xFBBB, 0xFBBC, 0xFBBD, 0xFBBE, 0xFBBF, 0xFBC0, 0xFBC1, 0xFBC2, 0xFBC3, 0xFBC4, 0xFBC5, 0xFBC6, 0xFBC7, 0xFBC8, 0xFBC9, 0xFBCA,
+ 0xFBCB, 0xFBCD, 0xFBCE, 0xFBCF, 0xFBD0, 0xFBD1, 0xFBD2, 0xFD40, 0xFD41, 0xFD42, 0xFD43, 0xFD44, 0xFD45, 0xFD46, 0xFD47, 0xFD48, 0xFD49, 0xFD4A,
+ 0xFD4B, 0xFD4C, 0xFD4D, 0xFD4E, 0xFD4F, 0xFD90, 0xFD91, 0xFDC8, 0xFDC9, 0xFDCA, 0xFDCB, 0xFDCC, 0xFDCD, 0xFDCE, 0xFDCF, 0xFDD0, 0xFDD1, 0xFDD2,
+ 0xFDD3, 0xFDD4, 0xFDD5, 0xFDD6, 0xFDD7, 0xFDD8, 0xFDD9, 0xFDDA, 0xFDDB, 0xFDDC, 0xFDDD, 0xFDDE, 0xFDDF, 0xFDE0, 0xFDE1, 0xFDE2, 0xFDE3, 0xFDE4,
+ 0xFDE5, 0xFDE6, 0xFDE7, 0xFDE8, 0xFDE9, 0xFDEA, 0xFDEB, 0xFDEC, 0xFDEB, 0xFDEC, 0xFDED, 0xFDEE, 0xFDEF, 0xFDFE, 0xFDFF, 0xFE75, 0xFEFD, 0xFEFE,
+ 0xFEFF, 0x0590, 0x05C8, 0x05C9, 0x05CA, 0x05CB, 0x05CC, 0x05CD, 0x05CE, 0x05CF, 0x05EB, 0x05EC, 0x05ED, 0x05EE, 0x05EF, 0x05F5, 0x05F6, 0x05F7,
+ 0x05F8, 0x05F9, 0x05FA, 0x05FB, 0x05FC, 0x05FD, 0x05FE, 0x05FF, 0xFB07, 0xFB08, 0xFB09, 0xFB0A, 0xFB0B, 0xFB0C, 0xFB0D, 0xFB0E, 0xFB0F, 0xFB10,
+ 0xFB11, 0xFB12, 0xFB18, 0xFB19, 0xFB1A, 0xFB1B, 0xFB1C, 0xFB37, 0xFB3D, 0xFB3F, 0xFB42, 0xFB45};
+
+ private int [] bidiMarks;
+
+ ///
+ /// Define minimum code points need to be a bidi string
+ ///
+ public static readonly int MINNUMOFCODEPOINT = 2;
+
+ ///
+ /// Define SurrogatePairDictionary class
+ /// Newline
+ ///
+ public BidiProperty(UnicodeRangeDatabase unicodeDb, Collection expectedRanges)
+ {
+ bool isValid = false;
+
+ foreach (UnicodeRange range in expectedRanges)
+ {
+ if (RangePropertyCollector.BuildPropertyDataList(
+ unicodeDb,
+ range,
+ bidiPropertyRangeList,
+ "Arabic",
+ GroupAttributes.Name))
+ {
+ isValid = true;
+ }
+
+ if (RangePropertyCollector.BuildPropertyDataList(
+ unicodeDb,
+ range,
+ bidiPropertyRangeList,
+ "Hebrew",
+ GroupAttributes.Name))
+ {
+ isValid = true;
+ }
+ }
+
+ if (InitializeBidiDictionary(expectedRanges))
+ {
+ isValid = true;
+ }
+
+ if (!isValid)
+ {
+ throw new ArgumentOutOfRangeException("expectedRanges", "BidiProperty, Bidi ranges are beyond expected range. " +
+ "Refer to Arabic and Hebrew ranges.");
+ }
+
+ // Reset isValid to validate Latin range
+ isValid = false;
+ foreach (UnicodeRange expectedRange in expectedRanges)
+ {
+ UnicodeRange range = RangePropertyCollector.GetRange(new UnicodeRange(0x0030, 0x0039), expectedRange);
+ if (null != range)
+ {
+ latinRangeList.Add(range);
+ isValid = true;
+ }
+
+ range = RangePropertyCollector.GetRange(new UnicodeRange(0x0041, 0x005A), expectedRange);
+ if (null != range)
+ {
+ latinRangeList.Add(range);
+ isValid = true;
+ }
+
+ range = RangePropertyCollector.GetRange(new UnicodeRange(0x0061, 0x007A), expectedRange);
+ if (null != range)
+ {
+ latinRangeList.Add(range);
+ isValid = true;
+ }
+ }
+
+ if (!isValid)
+ {
+ throw new ArgumentOutOfRangeException("expectedRanges", "BidiProperty, Bidi ranges are beyond expected range. " +
+ "0x0030 - 0x0039, 0x0041 - 0x005A, and 0x0061 - 0x007A ranges are needed to construct Bidi string.");
+ }
+ }
+
+ ///
+ /// Dictionary to store code points corresponding to culture.
+ ///
+ private bool InitializeBidiDictionary(Collection expectedRanges)
+ {
+ bool isValid = false;
+
+ bidiDictionary.Add("LEFTTORIGHTMARK", '\u200E');
+ bidiDictionary.Add("RIGHTTOLEFTMARK", '\u200F');
+ bidiDictionary.Add("LEFTTORIGHTEMBEDDING", '\u202A'); // quoation needed
+ bidiDictionary.Add("RIGHTTOLEFTEMBEDDING", '\u202B'); // quoation needed
+ bidiDictionary.Add("POPDIRECTIONALFORMATTING", '\u202C');
+ bidiDictionary.Add("LEFTTORIGHTOVERRIDE", '\u202D');
+ bidiDictionary.Add("RIGHTTOLEFTOVERRIDE", '\u202E');
+
+ int i = 0;
+ bidiMarks = new int [bidiDictionary.Count];
+ Dictionary.ValueCollection valueColl = bidiDictionary.Values;
+ foreach (char codePoint in valueColl)
+ {
+ foreach (UnicodeRange range in expectedRanges)
+ {
+ if (codePoint >= range.StartOfUnicodeRange && codePoint <= range.EndOfUnicodeRange)
+ {
+ bidiMarks[i++] = (int)codePoint;
+ isValid = true;
+ }
+ }
+ }
+ Array.Resize(ref bidiMarks, i);
+ return isValid;
+ }
+
+ ///
+ /// Check if code point is in the property range
+ ///
+ public bool IsInPropertyRange(int codePoint)
+ {
+ bool isIn = false;
+ foreach (UnicodeRangeProperty prop in bidiPropertyRangeList)
+ {
+ if (codePoint >= prop.Range.StartOfUnicodeRange && codePoint <= prop.Range.EndOfUnicodeRange)
+ {
+ isIn = true;
+ break;
+ }
+ }
+
+ return isIn;
+ }
+
+ ///
+ /// Get random bidi string
+ ///
+ public string GetRandomCodePoints(int numOfProperty, int seed)
+ {
+ if (numOfProperty < 1)
+ {
+ throw new ArgumentOutOfRangeException("BidiProperty, numOfProperty, " + numOfProperty + " cannot be less than one.");
+ }
+
+ // only support Arabic and Hebrew for current version
+ string bidiStr = string.Empty;
+ Random rand = new Random(seed);
+ int index = 0;
+
+ for (int i=0; i < numOfProperty; i++)
+ {
+ index = rand.Next(0, bidiPropertyRangeList.Count);
+ bidiStr += TextUtil.GetRandomCodePoint(bidiPropertyRangeList[index].Range, 1, exclusions, seed);
+ index = rand.Next(0, latinRangeList.Count);
+ bidiStr += TextUtil.GetRandomCodePoint(latinRangeList[index], 1, null, seed);
+ }
+
+ return bidiStr;
+ }
+ }
+}
+
+
+
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/CombiningMarksProperty.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/CombiningMarksProperty.cs
new file mode 100644
index 0000000..7857bb4
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/CombiningMarksProperty.cs
@@ -0,0 +1,213 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// Collect combining mark code points
+ ///
+ internal class CombiningMarksProperty : IStringProperty
+ {
+ ///
+ /// Dictionary to store code point corresponding to culture.
+ ///
+ private Dictionary combiningMarksDictionary = new Dictionary();
+
+ private List combiningMarksPropertyRangeList = new List();
+
+ private static readonly int[] exclusions = {0xFE27, 0xFE28, 0xFE29, 0xFE2A, 0xFE2B, 0xFE2C, 0xFE2D, 0xFE2F, 0x1DE7, 0x1DE8, 0x1DE9, 0x1DEA,
+ 0x1DEB, 0x1DEC, 0x1DED, 0x1DEE, 0x1DEF, 0x1DF0, 0x1DF1, 0x1DF2, 0x1DF3, 0x1DF4, 0x1DF5, 0x1DF6, 0x1DF7, 0x1DF8, 0x1DF9, 0x1DFA, 0x1DFB, 0x1DFC};
+
+ private int [] combiningMarks;
+
+ ///
+ /// Define minimum code points needed to be a combining mark string
+ ///
+ public static readonly int MINNUMOFCODEPOINT = 2;
+
+ ///
+ /// Define CombiningMarksProperty class
+ /// Newline
+ /// Newline
+ /// Newline
+ ///
+ public CombiningMarksProperty(UnicodeRangeDatabase unicodeDb, Collection expectedRanges)
+ {
+ bool isValid = false;
+
+ foreach (UnicodeRange range in expectedRanges)
+ {
+ if (RangePropertyCollector.BuildPropertyDataList(
+ unicodeDb,
+ range,
+ combiningMarksPropertyRangeList,
+ "Combining Diacritics",
+ GroupAttributes.GroupName))
+ {
+ isValid = true;
+ }
+ }
+
+ if (InitializeCombiningMarksDictionary(expectedRanges))
+ {
+ isValid = true;
+ }
+
+ if (!isValid)
+ {
+ throw new ArgumentOutOfRangeException("expectedRanges", "CombiningMarksProperty, Combining mark ranges are beyond expected range. " +
+ "Refer to Combining Diacritics range.");
+ }
+ }
+
+ private bool InitializeCombiningMarksDictionary(Collection expectedRanges)
+ {
+ // Grave and acute accent
+ char [] other = {'\u0302', '\u0307', '\u030A', '\u0315', '\u0316', '\u0317', '\u0318', '\u0319', '\u031A', '\u031C', '\u031D',
+ '\u031E', '\u031F', '\u0320', '\u0321', '\u0322', '\u0324', '\u032A', '\u032B', '\u032C', '\u032E', '\u0330', '\u0332',
+ '\u0333', '\u0334', '\u0335', '\u0336', '\u0337', '\u0338', '\u0339', '\u033A', '\u033B', '\u033C', '\u033D', '\u033F',
+ '\u0346', '\u0347', '\u0348', '\u0349', '\u034A', '\u034B', '\u034C', '\u034D', '\u034E', '\u034F', '\u0358', '\u0359',
+ '\u035A', '\u035B', '\u035C', '\u035D', '\u035E', '\u0360', '\u0361', '\u0362', '\u0323', '\u0328', '\u032D', '\u032F',
+ '\u1DC8', '\u1DC9', '\u1DCA', '\u1DCE', '\u1DCF', '\u1DD0', '\u1DD1', '\u1DD2', '\u1DD3', '\u1DD4', '\u1DD5', '\u1DD6',
+ '\u1DD7', '\u1DD8', '\u1DD9', '\u1DDA', '\u1DDB', '\u1DDC', '\u1DDD', '\u1DDE', '\u1DDF', '\u1DE0', '\u1DE1', '\u1DE2',
+ '\u1DE3', '\u1DE4', '\u1DE5', '\u1DE6', '\uFE20', '\uFE21', '\uFE22', '\uFE23'};
+ combiningMarksDictionary.Add("other", other);
+ char [] vi = {'\u0303', '\u0308', '\u031B', '\u0323', '\u0340', '\u0341'};
+ combiningMarksDictionary.Add("vi", vi);
+ char [] el = {'\u0300','\u0301', '\u0304', '\u0305', '\u0306', '\u0308', '\u0313', '\u0314', '\u0331', '\u0342', '\u0343',
+ '\u0344', '\u0345', '\u1DC0', '\u1DC1', '\u1DC4', '\u1DC5', '\u1DC6', '\u1DC7', '\uFE24', '\uFE25', '\uFE26'};
+ combiningMarksDictionary.Add("el", el);
+ char [] hu = {'\u030B', '\u0350', '\u0351', '\u0352', '\u0353', '\u0354', '\u0355', '\u0356', '\u0357'};
+ combiningMarksDictionary.Add("hu", hu);
+ char [] cs = {'\u030C'};
+ combiningMarksDictionary.Add("cs", cs);
+ char [] id = {'\u030D', '\u030E', '\u0325'};
+ combiningMarksDictionary.Add("id", id);
+ char [] ms = {'\u030D', '\u030E'};
+ combiningMarksDictionary.Add("ms", ms);
+ char [] srsp = {'\u030F', '\u0311', '\u0313', '\u0314', '\u033E', '\u0351', '\u0352', '\u0353', '\u0354', '\u0355', '\u0356',
+ '\u0357', '\u1DC3'};
+ combiningMarksDictionary.Add("sr-sp", srsp);
+ char [] hr = {'\u030F', '\u1DC3'};
+ combiningMarksDictionary.Add("hr", hr);
+ char [] hi = {'\u0310', '\u0325'};
+ combiningMarksDictionary.Add("hi", hi);
+ char [] azaz = {'\u0311', '\u0313', '\u0314', '\u033E', '\u0327'};
+ combiningMarksDictionary.Add("az-az", azaz);
+ char [] uzuz = {'\u0311', '\u0313', '\u0314', '\u033E'};
+ combiningMarksDictionary.Add("uz-uz", uzuz);
+ char [] lv = {'\u0312', '\u0326'};
+ combiningMarksDictionary.Add("lv", lv);
+ char [] fi = {'\u0326', '\u0350', '\u0351', '\u0352', '\u0353', '\u0354', '\u0355', '\u0356', '\u0357'};
+ combiningMarksDictionary.Add("fi", fi);
+ char [] hy = {'\u0313', '\u0314'};
+ combiningMarksDictionary.Add("hy", hy);
+ char [] he = {'\u0323'};
+ combiningMarksDictionary.Add("he", he);
+ char [] ar = {'\u0323'};
+ combiningMarksDictionary.Add("ar", ar);
+ char [] ro = {'\u0326', '\u0350', '\u0351', '\u0352', '\u0353', '\u0354', '\u0355', '\u0356', '\u0357'};
+ combiningMarksDictionary.Add("ro", ro);
+ char [] fr = {'\u0327'};
+ combiningMarksDictionary.Add("fr", fr);
+ char [] tr = {'\u0327'};
+ combiningMarksDictionary.Add("tr", tr);
+ char [] pl = {'\u0328'};
+ combiningMarksDictionary.Add("pl", pl);
+ char [] lt = {'\u0328', '\u035B', '\u1DCB', '\u1DCC'};
+ combiningMarksDictionary.Add("lt", lt);
+ char [] yoruba = {'\u0329'};
+ combiningMarksDictionary.Add("yoruba", yoruba);
+ char [] de = {'\u0329', '\u0363', '\u0364', '\u0365', '\u0366', '\u0367', '\u0368', '\u0369', '\u036A', '\u036B', '\u036C', '\u036D',
+ '\u036E', '\u036F'};
+ combiningMarksDictionary.Add("de", de);
+ char [] et = {'\u0350', '\u0351', '\u0352', '\u0353', '\u0354', '\u0355', '\u0356', '\u0357'};
+ combiningMarksDictionary.Add("et", et);
+ char [] ru = {'\u030B', '\u0350', '\u0351', '\u0352', '\u0353', '\u0354', '\u0355', '\u0356', '\u0357', '\u1DC3'};
+ combiningMarksDictionary.Add("ru", ru);
+ char [] sk = {'\u0351', '\u0352', '\u0353', '\u0354', '\u0355', '\u0356', '\u0357', '\u1DC3'};
+ combiningMarksDictionary.Add("sk", sk);
+ char [] be = {'\u1DC3'};
+ combiningMarksDictionary.Add("be", be);
+ char [] bg = {'\u1DC3'};
+ combiningMarksDictionary.Add("bg", be);
+ char [] mk = {'\u1DC3'};
+ combiningMarksDictionary.Add("mk", mk);
+ char [] sl = {'\u1DC3'};
+ combiningMarksDictionary.Add("sl", sl);
+ char [] uk = {'\u1DC3'};
+ combiningMarksDictionary.Add("uk", uk);
+ char [] symbol = {'\u20D0', '\u20D1', '\u20D2', '\u20D3', '\u20D4', '\u20D5', '\u20D6', '\u20D7', '\u20D8', '\u20D9', '\u20DA',
+ '\u20DF', '\u20E0', '\u20E1', '\u20E2', '\u20E3', '\u20E4', '\u20E5', '\u20E6', '\u20E7', '\u20E8', '\u20E9', '\u20EA', '\u20EB',
+ '\u20EC', '\u20ED', '\u20EF', '\u20F0'};
+ combiningMarksDictionary.Add("symbol", symbol);
+
+ bool isValid = false;
+ int i = 0;
+ combiningMarks = new int [other.Length + vi.Length + el.Length + hu.Length + cs.Length + id.Length + ms.Length + srsp.Length + hr.Length +
+ hi.Length + azaz.Length + uzuz.Length + lv.Length + fi.Length + hy.Length + he.Length + ar.Length + ro.Length + fr.Length + tr.Length +
+ pl.Length + lt.Length + yoruba.Length + de.Length + et.Length + ru.Length + sk.Length + be.Length + bg.Length + mk.Length + sl.Length +
+ uk.Length + symbol.Length];
+ Dictionary.ValueCollection valueColl = combiningMarksDictionary.Values;
+ foreach (char [] values in valueColl)
+ {
+ foreach (char codePoint in values)
+ {
+ foreach (UnicodeRange range in expectedRanges)
+ {
+ if (codePoint >= range.StartOfUnicodeRange && codePoint <= range.EndOfUnicodeRange)
+ {
+ combiningMarks[i++] = (int)codePoint;
+ isValid = true;
+ }
+ }
+ }
+ }
+ Array.Resize(ref combiningMarks, i);
+ return isValid;
+ }
+
+ ///
+ /// Check if code point is in the property range
+ ///
+ public bool IsInPropertyRange(int codePoint)
+ {
+ bool isIn = false;
+ foreach (UnicodeRangeProperty prop in combiningMarksPropertyRangeList)
+ {
+ if (codePoint >= prop.Range.StartOfUnicodeRange && codePoint <= prop.Range.EndOfUnicodeRange)
+ {
+ isIn = true;
+ break;
+ }
+ }
+
+ return isIn;
+ }
+
+ ///
+ /// Get random combining marks code points
+ ///
+ public string GetRandomCodePoints(int numOfProperty, int seed)
+ {
+ if (numOfProperty < 1)
+ {
+ throw new ArgumentOutOfRangeException(
+ "CombiningMarksProperty, numOfProperty, " + numOfProperty + " cannot be less than one.");
+ }
+
+ Random rand = new Random(seed);
+ string combiningMarkStr = string.Empty;
+ int index = rand.Next(0, combiningMarksPropertyRangeList.Count);
+ combiningMarkStr += TextUtil.GetRandomCodePoint(combiningMarksPropertyRangeList[index].Range, numOfProperty, exclusions, seed);
+
+ return combiningMarkStr;
+ }
+ }
+}
+
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/EudcProperty.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/EudcProperty.cs
new file mode 100644
index 0000000..3487559
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/EudcProperty.cs
@@ -0,0 +1,103 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// Collect EUDC code points
+ ///
+ internal class EudcProperty : IStringProperty
+ {
+ private List eudcRangeList = new List();
+
+ private int low; // 0xE000
+
+ private int high; // 0xF8FF
+
+ ///
+ /// Define minimum code point needed to be an EUDC string
+ ///
+ public static readonly int MINNUMOFCODEPOINT = 1;
+
+
+ ///
+ /// Define SurrogatePairDictionary class
+ /// Newline
+ ///
+ public EudcProperty(UnicodeRangeDatabase unicodeDb, Collection expectedRanges)
+ {
+ low = 0xE000; high = 0xF8FF;
+
+ bool isValid = false;
+ foreach (UnicodeRange range in expectedRanges)
+ {
+ if (RangePropertyCollector.BuildPropertyDataList(
+ unicodeDb,
+ range,
+ eudcRangeList,
+ "Private Use",
+ GroupAttributes.GroupName))
+ {
+ isValid = true;
+ }
+ }
+
+ if(!isValid)
+ {
+ throw new ArgumentOutOfRangeException("expectedRanges", "EudcProperty, EUDC ranges are beyond expected range. " +
+ "Refer to Private Use range.");
+ }
+
+ foreach (UnicodeRangeProperty data in eudcRangeList)
+ {
+ if (data.Name.Equals("Private Use Area", StringComparison.OrdinalIgnoreCase))
+ {
+ low = data.Range.StartOfUnicodeRange;
+ high = data.Range.EndOfUnicodeRange;
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Check if code point is in the property range
+ ///
+ public bool IsInPropertyRange(int codePoint)
+ {
+ bool isIn = false;
+ foreach (UnicodeRangeProperty prop in eudcRangeList)
+ {
+ if (codePoint >= prop.Range.StartOfUnicodeRange && codePoint <= prop.Range.EndOfUnicodeRange)
+ {
+ isIn = true;
+ break;
+ }
+ }
+
+ return isIn;
+ }
+
+ ///
+ /// Get random EUDC code points
+ ///
+ public string GetRandomCodePoints(int numOfProperty, int seed)
+ {
+ if (numOfProperty < 1)
+ {
+ throw new ArgumentOutOfRangeException("EudcProperty, numOfProperty, " + numOfProperty + " cannot be less than one.");
+ }
+
+ string eudcStr = string.Empty;
+ eudcStr += TextUtil.GetRandomCodePoint(new UnicodeRange(low, high), numOfProperty, null, seed);
+
+ return eudcStr;
+ }
+ }
+}
+
+
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/Group.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/Group.cs
new file mode 100644
index 0000000..63831b2
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/Group.cs
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ internal class Group
+ {
+ public Group(UnicodeRange range, string groupName, string name, string ids, UnicodeChart chart)
+ {
+ UnicodeRange = new UnicodeRange(range);
+ GroupName = groupName;
+ Name = name;
+ Ids = ids;
+ UnicodeChart = chart;
+ SubGroups = null;
+ }
+
+ public UnicodeRange UnicodeRange { get; set; }
+
+ public string GroupName { get; set; }
+
+ public string Name { get; set; }
+
+ public string Ids { get; set; }
+
+ public UnicodeChart UnicodeChart { get; set; }
+
+ public SubGroup [] SubGroups { get; set; }
+ }
+}
+
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/GroupAttributes.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/GroupAttributes.cs
new file mode 100644
index 0000000..98cc7eb
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/GroupAttributes.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// An enum type to have each enum type associate with attributes in Group
+ ///
+ internal enum GroupAttributes
+ {
+ ///
+ /// Name of the Group
+ ///
+ GroupName = 1,
+
+ ///
+ /// Name
+ ///
+ Name,
+
+ ///
+ /// Culture ids
+ ///
+ Ids
+ }
+}
+
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/IStringProperty.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/IStringProperty.cs
new file mode 100644
index 0000000..840ab05
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/IStringProperty.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// Inteface for specific string property class
+ ///
+ internal interface IStringProperty
+ {
+ ///
+ /// Get next random code point or points that belongs to a specific
+ /// string property. number of code points does not necessarily translate
+ /// to number of chars since surrogate pair are two bytes
+ ///
+ string GetRandomCodePoints(int numOfProperty, int seed);
+
+ ///
+ /// Check if code point is in the property range
+ ///
+ bool IsInPropertyRange(int codePoint);
+ }
+}
+
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/LineBreakProperty.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/LineBreakProperty.cs
new file mode 100644
index 0000000..e567155
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/LineBreakProperty.cs
@@ -0,0 +1,120 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// Collect line break code points
+ ///
+ internal class LineBreakProperty : IStringProperty
+ {
+ ///
+ /// Dictionary to store code points corresponding to culture.
+ ///
+ private Dictionary lineBreakCharDictionary = new Dictionary();
+
+ private int [] lineBreakCodePoints;
+
+ ///
+ /// Define minimum code point needed to be a string has line break
+ ///
+ public static readonly int MINNUMOFCODEPOINT = 1;
+
+ ///
+ /// Define LineBreakProperty class,
+ /// Newline
+ ///
+ public LineBreakProperty(Collection expectedRangse)
+ {
+ if (!InitializeLineBreakCharDictionary(expectedRangse))
+ {
+ throw new ArgumentOutOfRangeException("expectedRangse", "LineBreakProperty, Linebreak ranges are beyond expected range. " +
+ "Refert to CR, LF, CRLF, NEL, VT, FF, LS, and PS.");
+ }
+ }
+
+ private bool InitializeLineBreakCharDictionary(Collection expectedRanges)
+ {
+ char [] cr = {'\u000D'};
+ lineBreakCharDictionary.Add("CR", cr);
+ char [] lf = {'\u000A'};
+ lineBreakCharDictionary.Add("LF", lf);
+ char [] crlf = {'\u000D', '\u000A'};
+ lineBreakCharDictionary.Add("CRLF", crlf);
+ char [] nel = {'\u0085'};
+ lineBreakCharDictionary.Add("NEL", nel);
+ char [] vt = {'\u000B'};
+ lineBreakCharDictionary.Add("VT", vt);
+ char [] ff = {'\u000C'};
+ lineBreakCharDictionary.Add("FF", ff);
+ char [] ls = {'\u2028'};
+ lineBreakCharDictionary.Add("LS", ls);
+ char [] ps = {'\u2029'};
+ lineBreakCharDictionary.Add("PS", ps);
+
+ int i = 0;
+ bool isValid = false;
+ lineBreakCodePoints = new int [cr.Length + lf.Length + crlf.Length + nel.Length + vt.Length + ff.Length + ls.Length + ps.Length];
+ Dictionary.ValueCollection valueColl = lineBreakCharDictionary.Values;
+ foreach (char[] values in valueColl)
+ {
+ foreach (char codePoint in values)
+ {
+ foreach (UnicodeRange range in expectedRanges)
+ {
+ if (codePoint >= range.StartOfUnicodeRange && codePoint <= range.EndOfUnicodeRange)
+ {
+ lineBreakCodePoints[i++] = (int)codePoint;
+ isValid = true;
+ }
+ }
+ }
+ }
+ Array.Resize(ref lineBreakCodePoints, i);
+ return isValid;
+ }
+
+ ///
+ /// Check if code point is in the property range
+ ///
+ public bool IsInPropertyRange(int codePoint)
+ {
+ bool isIn = false;
+ foreach (int i in lineBreakCodePoints)
+ {
+ if (i == codePoint)
+ {
+ isIn = true;
+ break;
+ }
+ }
+
+ return isIn;
+ }
+
+ ///
+ /// Get next line break points
+ ///
+ public string GetRandomCodePoints(int numOfProperty, int seed)
+ {
+ if (numOfProperty < 1)
+ {
+ throw new ArgumentOutOfRangeException("LineBreakProperty, numOfProperty, " + numOfProperty + " cannot be less than one.");
+ }
+
+ string lineBreakStr = string.Empty;
+ Random rand = new Random(seed);
+ for (int i=0; i < numOfProperty; i++)
+ {
+ lineBreakStr += TextUtil.IntToString(lineBreakCodePoints[rand.Next(0, lineBreakCodePoints.Length)]);
+ }
+
+ return lineBreakStr;
+ }
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/NumberProperty.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/NumberProperty.cs
new file mode 100644
index 0000000..fbc84e0
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/NumberProperty.cs
@@ -0,0 +1,137 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// Collect number code points
+ ///
+ internal class NumberProperty : IStringProperty
+ {
+ ///
+ /// Dictionary to store code point corresponding to culture.
+ ///
+ private Dictionary numberDictionary = new Dictionary();
+
+ private List numberDigitRangeList = new List();
+
+ private int [] numberCodePoints;
+
+ ///
+ /// Define minimum code point needed to be a string has number
+ ///
+ public static readonly int MINNUMOFCODEPOINT = 1;
+
+ ///
+ /// Define NumberProperty class,
+ /// Newline
+ ///
+ public NumberProperty(UnicodeRangeDatabase unicodeDb, Collection expectedRanges)
+ {
+ bool isValid = false;
+
+ foreach (UnicodeRange range in expectedRanges)
+ {
+ if (RangePropertyCollector.BuildPropertyDataList(
+ unicodeDb,
+ range,
+ numberDigitRangeList,
+ "Numbers and Digits",
+ GroupAttributes.GroupName))
+ {
+ isValid = true;
+ }
+ }
+
+ if (InitializeNumberCharDictionary(expectedRanges))
+ {
+ isValid = true;
+ }
+
+ if (!isValid)
+ {
+ throw new ArgumentOutOfRangeException("expectedRanges", "NumberProperty, number ranges are beyond expected range. " +
+ "Refer to latin numberals and point, percent, plus, and minus signs, and comma.");
+ }
+ }
+
+ private bool InitializeNumberCharDictionary(Collection expectedRanges)
+ {
+ bool isValid = false;
+ char [] latin = {'\u0030', '\u0031', '\u0032', '\u0033', '\u0034', '\u0035', '\u0036', '\u0037', '\u0038', '\u0039'};
+ numberDictionary.Add("latin", latin);
+ char [] piont = {'\u002E'};
+ numberDictionary.Add("piont", piont);
+ char [] percent = {'\u0025'};
+ numberDictionary.Add("percent", percent);
+ char [] minus = {'\u002D'};
+ numberDictionary.Add("minus", minus);
+ char [] plus = {'\u002B'};
+ numberDictionary.Add("plus", plus);
+ char [] comma = {'\u002C'};
+ numberDictionary.Add("comma", comma);
+
+ int i = 0;
+ numberCodePoints = new int [latin.Length];
+ foreach (char codePoint in numberDictionary["latin"])
+ {
+ foreach (UnicodeRange range in expectedRanges)
+ {
+ if (codePoint >= range.StartOfUnicodeRange && codePoint <= range.EndOfUnicodeRange)
+ {
+ numberCodePoints[i++] = (int)codePoint;
+ isValid = true;
+ }
+ }
+ }
+ Array.Resize(ref numberCodePoints, i);
+
+ return isValid;
+ }
+
+ ///
+ /// Check if code point is in the property range
+ ///
+ public bool IsInPropertyRange(int codePoint)
+ {
+ bool isIn = false;
+ foreach (int i in numberCodePoints)
+ {
+ if (i == codePoint)
+ {
+ isIn = true;
+ break;
+ }
+ }
+
+ return isIn;
+ }
+
+ ///
+ /// Get number code points
+ ///
+ public string GetRandomCodePoints(int numOfProperty, int seed)
+ {
+ if (numOfProperty < 1)
+ {
+ throw new ArgumentOutOfRangeException("NumberProperty, numOfProperty, " + numOfProperty + " cannot be less than one.");
+ }
+
+ string numStr = string.Empty;
+ Random rand = new Random(seed);
+ for (int i= 0; i < numOfProperty; i++)
+ {
+ int index = rand.Next(0, numberCodePoints.Length);
+ numStr += TextUtil.IntToString(numberCodePoints[index]);
+ }
+
+ return numStr;
+ }
+ }
+}
+
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/PropertyFactory.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/PropertyFactory.cs
new file mode 100644
index 0000000..a598862
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/PropertyFactory.cs
@@ -0,0 +1,228 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// PropertyFactory class stores string property objects
+ ///
+ internal class PropertyFactory
+ {
+ private BidiProperty bidiProperty;
+ private CombiningMarksProperty combiningMarksProperty;
+ private EudcProperty eudcProperty;
+ private LineBreakProperty lineBreakProperty;
+ private NumberProperty numberProperty;
+ private SurrogatePairProperty surrogatePairProperty;
+ private TextNormalizationProperty textNormalizationProperty;
+ private TextSegmentationProperty textSegmentationProperty;
+
+ ///
+ /// Enum all available properties
+ ///
+ public enum PropertyName
+ {
+ ///
+ /// Bidi property
+ ///
+ Bidi = 0,
+
+ ///
+ /// Combining mark
+ ///
+ CombiningMarks,
+
+ ///
+ /// EUDC
+ ///
+ Eudc,
+
+ ///
+ /// Line break
+ ///
+ LineBreak,
+
+ ///
+ /// Number
+ ///
+ Number,
+
+ ///
+ /// Surrogate
+ ///
+ Surrogate,
+
+ ///
+ /// Text normalization
+ ///
+ TextNormalization,
+
+ ///
+ /// Text segmentation
+ ///
+ TextSegmentation
+ }
+
+ private int minNumOfCodePoint;
+
+ private Dictionary propertyDictionary;
+
+ ///
+ /// Create property objects according to string properties
+ ///
+ public PropertyFactory(StringProperties properties, UnicodeRangeDatabase unicodeDb, Collection expectedRanges)
+ {
+ bidiProperty = null;
+ combiningMarksProperty = null;
+ eudcProperty = null;
+ lineBreakProperty = null;
+ numberProperty = null;
+ surrogatePairProperty = null;
+ textNormalizationProperty = null;
+ textSegmentationProperty = null;
+ minNumOfCodePoint = 0;
+ propertyDictionary = new Dictionary();
+ CreateProperties(properties, unicodeDb, expectedRanges);
+ }
+
+ ///
+ /// Check if property object exists
+ ///
+ public bool HasProperty(PropertyName propertyName)
+ {
+ if (PropertyName.Bidi == propertyName)
+ {
+ return null == bidiProperty ? false : true;
+ }
+ else if (PropertyName.CombiningMarks == propertyName)
+ {
+ return null == combiningMarksProperty ? false : true;
+ }
+ else if (PropertyName.Eudc == propertyName)
+ {
+ return null == eudcProperty ? false : true;
+ }
+ else if (PropertyName.LineBreak == propertyName)
+ {
+ return null == lineBreakProperty ? false : true;
+ }
+ else if (PropertyName.Number == propertyName)
+ {
+ return null == numberProperty ? false : true;
+ }
+ else if (PropertyName.Surrogate == propertyName)
+ {
+ return null == surrogatePairProperty ? false : true;
+ }
+ else if (PropertyName.TextNormalization == propertyName)
+ {
+ return null == textNormalizationProperty ? false : true;
+ }
+ else if (PropertyName.TextSegmentation == propertyName)
+ {
+ return null == textSegmentationProperty ? false : true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ private void CreateProperties(StringProperties properties, UnicodeRangeDatabase unicodeDb, Collection expectedRanges)
+ {
+ if (null != properties.HasNumbers)
+ {
+ if ((bool)properties.HasNumbers)
+ {
+ numberProperty = new NumberProperty(unicodeDb, expectedRanges);
+ minNumOfCodePoint += NumberProperty.MINNUMOFCODEPOINT;
+ propertyDictionary.Add(PropertyName.Number, numberProperty);
+ }
+ }
+
+ if (null != properties.IsBidirectional)
+ {
+ if ((bool)properties.IsBidirectional)
+ {
+ bidiProperty = new BidiProperty(unicodeDb, expectedRanges);
+ minNumOfCodePoint += BidiProperty.MINNUMOFCODEPOINT;
+ propertyDictionary.Add(PropertyName.Bidi, bidiProperty);
+ }
+ }
+
+ if (null != properties.NormalizationForm)
+ {
+ textNormalizationProperty = new TextNormalizationProperty(unicodeDb, expectedRanges);
+ minNumOfCodePoint += TextNormalizationProperty.MINNUMOFCODEPOINT;
+ propertyDictionary.Add(PropertyName.TextNormalization, textNormalizationProperty);
+ }
+
+ if (null != properties.MinNumberOfCombiningMarks)
+ {
+ if (0 != properties.MinNumberOfCombiningMarks)
+ {
+ combiningMarksProperty = new CombiningMarksProperty(unicodeDb, expectedRanges);
+ minNumOfCodePoint += CombiningMarksProperty.MINNUMOFCODEPOINT * (int)properties.MinNumberOfCombiningMarks;
+ propertyDictionary.Add(PropertyName.CombiningMarks, combiningMarksProperty);
+ }
+ }
+
+ if (null != properties.MinNumberOfEndUserDefinedCodePoints)
+ {
+ if (0 != properties.MinNumberOfEndUserDefinedCodePoints)
+ {
+ eudcProperty = new EudcProperty(unicodeDb, expectedRanges);
+ minNumOfCodePoint += EudcProperty.MINNUMOFCODEPOINT * (int)properties.MinNumberOfEndUserDefinedCodePoints;
+ propertyDictionary.Add(PropertyName.Eudc, eudcProperty);
+ }
+ }
+
+ if (null != properties.MinNumberOfLineBreaks)
+ {
+ if (0 != properties.MinNumberOfLineBreaks)
+ {
+ lineBreakProperty = new LineBreakProperty(expectedRanges);
+ minNumOfCodePoint += LineBreakProperty.MINNUMOFCODEPOINT * (int)properties.MinNumberOfLineBreaks;
+ propertyDictionary.Add(PropertyName.LineBreak, lineBreakProperty);
+ }
+ }
+
+ if (null != properties.MinNumberOfSurrogatePairs)
+ {
+ if (0 != properties.MinNumberOfSurrogatePairs)
+ {
+ surrogatePairProperty = new SurrogatePairProperty(unicodeDb, expectedRanges);
+ minNumOfCodePoint += SurrogatePairProperty.MINNUMOFCODEPOINT * (int)properties.MinNumberOfSurrogatePairs;
+ propertyDictionary.Add(PropertyName.Surrogate, surrogatePairProperty);
+ }
+ }
+
+ if (null != properties.MinNumberOfTextSegmentationCodePoints)
+ {
+ if (0 != properties.MinNumberOfTextSegmentationCodePoints)
+ {
+ textSegmentationProperty = new TextSegmentationProperty(unicodeDb, expectedRanges);
+ minNumOfCodePoint += TextSegmentationProperty.MINNUMOFCODEPOINT * (int)properties.MinNumberOfTextSegmentationCodePoints;
+ propertyDictionary.Add(PropertyName.TextSegmentation, textSegmentationProperty);
+ }
+ }
+ }
+
+ ///
+ /// Minimum number of code points needed to have to cover non-null properties
+ ///
+ public int MinNumOfCodePoint { get {return minNumOfCodePoint; } }
+
+ ///
+ /// Create property objects according to string properties
+ ///
+ public Dictionary PropertyDictionary { get { return propertyDictionary; } }
+ }
+}
+
+
+
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/RangePropertyCollector.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/RangePropertyCollector.cs
new file mode 100644
index 0000000..73c7513
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/RangePropertyCollector.cs
@@ -0,0 +1,171 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// Collect data according to name and save to PropertyData list
+ ///
+ internal static class RangePropertyCollector
+ {
+ ///
+ /// Get Unicode range according to Unicode chart provided
+ ///
+ public static UnicodeRange GetUnicodeChartRange(UnicodeRangeDatabase unicodeDb, UnicodeChart chart)
+ {
+ foreach (Group script in unicodeDb.Scripts)
+ {
+ if (script.UnicodeChart == chart)
+ {
+ return script.UnicodeRange;
+ }
+
+ if (null != script.SubGroups)
+ {
+ foreach (SubGroup subScript in script.SubGroups)
+ {
+ if (subScript.UnicodeChart == chart)
+ {
+ return subScript.UnicodeRange;
+ }
+ }
+ }
+ }
+
+ foreach (Group symbol in unicodeDb.SymbolsAndPunctuation)
+ {
+ if (symbol.UnicodeChart == chart)
+ {
+ return symbol.UnicodeRange;
+ }
+
+ if (null != symbol.SubGroups)
+ {
+ foreach (SubGroup subSymbol in symbol.SubGroups)
+ {
+ if (subSymbol.UnicodeChart == chart)
+ {
+ return subSymbol.UnicodeRange;
+ }
+ }
+ }
+ }
+
+ throw new ArgumentException(@"Invalid UnicodeChart, " + Enum.GetName(typeof(UnicodeChart), chart) + ". No match in the database.");
+ }
+
+ ///
+ /// Get new range - if expectedRange is smaller, new range is expectedRange. Otherwise, return false
+ ///
+ public static UnicodeRange GetRange(UnicodeRange range, UnicodeRange expectedRange)
+ {
+ if (0 == expectedRange.StartOfUnicodeRange && TextUtil.MaxUnicodePoint == expectedRange.EndOfUnicodeRange)
+ {
+ // don't care if whole Unicode range is given
+ return new UnicodeRange(range.StartOfUnicodeRange,range.EndOfUnicodeRange);
+ }
+
+ if (expectedRange.StartOfUnicodeRange > range.EndOfUnicodeRange || expectedRange.EndOfUnicodeRange < range.StartOfUnicodeRange) return null;
+
+ int low = expectedRange.StartOfUnicodeRange > range.StartOfUnicodeRange ? expectedRange.StartOfUnicodeRange : range.StartOfUnicodeRange;
+ int high = expectedRange.EndOfUnicodeRange < range.EndOfUnicodeRange ? expectedRange.EndOfUnicodeRange : range.EndOfUnicodeRange;
+ return new UnicodeRange(low, high);
+ }
+
+ ///
+ /// Walk through Unicode range database to build up property according to Group attribute
+ ///
+ public static bool BuildPropertyDataList(
+ UnicodeRangeDatabase unicodeDb,
+ UnicodeRange expectedRange,
+ List dataList,
+ string name,
+ GroupAttributes attribute)
+ {
+ bool isAdded = false;
+
+ foreach (Group script in unicodeDb.Scripts)
+ {
+ string scriptAttrib = script.GroupName;
+ if (attribute == GroupAttributes.Name) scriptAttrib = script.Name;
+ else if (attribute == GroupAttributes.Ids) scriptAttrib = script.Ids;
+
+ if (scriptAttrib.Equals(name, StringComparison.OrdinalIgnoreCase))
+ {
+ UnicodeRange range = GetRange(script.UnicodeRange, expectedRange);
+ if (null != range)
+ {
+ dataList.Add(new UnicodeRangeProperty(TextUtil.UnicodeChartType.Script, script.Name, script.Ids, range));
+ isAdded = true;
+ }
+
+ if (null != script.SubGroups)
+ {
+ foreach (SubGroup subScript in script.SubGroups)
+ {
+ range = GetRange(subScript.UnicodeRange, expectedRange);
+ if (null != range)
+ {
+ dataList.Add(new UnicodeRangeProperty(
+ TextUtil.UnicodeChartType.Script,
+ subScript.SubGroupName,
+ subScript.SubIds,
+ range));
+ isAdded = true;
+ }
+ }
+ }
+ }
+ }
+
+ foreach (Group symbol in unicodeDb.SymbolsAndPunctuation)
+ {
+ string symbolAttrib = symbol.GroupName;
+ if (attribute == GroupAttributes.Name) symbolAttrib = symbol.Name;
+ else if (attribute == GroupAttributes.Ids) symbolAttrib = symbol.Ids;
+
+ if (symbolAttrib.Equals(name, StringComparison.OrdinalIgnoreCase))
+ {
+ TextUtil.UnicodeChartType type = TextUtil.UnicodeChartType.Other;
+ if ((symbol.GroupName.ToLower(CultureInfo.InvariantCulture)).Contains("symbols") ||
+ (symbol.Name.ToLower(CultureInfo.InvariantCulture)).Contains("symbols"))
+ {
+ type = TextUtil.UnicodeChartType.Symbol;
+ }
+ else if((symbol.GroupName.ToLower(CultureInfo.InvariantCulture)).Contains("punctuation") ||
+ (symbol.Name.ToLower(CultureInfo.InvariantCulture)).Contains("punctuation"))
+ {
+ type = TextUtil.UnicodeChartType.Punctuation;
+ }
+
+ UnicodeRange range = GetRange(symbol.UnicodeRange, expectedRange);
+ if (null != range)
+ {
+ dataList.Add(new UnicodeRangeProperty(type, symbol.Name, symbol.Ids, range));
+ isAdded = true;
+ }
+
+ if (null != symbol.SubGroups)
+ {
+ foreach (SubGroup subSymbol in symbol.SubGroups)
+ {
+ range = GetRange(subSymbol.UnicodeRange, expectedRange);
+ if (null != range)
+ {
+ dataList.Add(new UnicodeRangeProperty(type, subSymbol.SubGroupName, subSymbol.SubIds, range));
+ isAdded = true;
+ }
+ }
+ }
+ }
+ }
+ return isAdded;
+ }
+ }
+}
+
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/StringFactory.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/StringFactory.cs
new file mode 100644
index 0000000..c110a29
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/StringFactory.cs
@@ -0,0 +1,835 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Text;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// Provides factory methods for generation of text, interesting from the testing point of view.
+ ///
+ ///
+ ///
+ /// The following example demonstrates how to generate two equivalent strings
+ /// of a fixed length (20 Unicode points), with numbers.
+ /// A Unicode code point may correspond to 1 or 2 characters. For more information see :
+ ///
+ /// StringProperties properties = new StringProperties();
+ ///
+ /// properties.MinNumberOfCodePoints = 20;
+ /// properties.MaxNumberOfCodePoints = 20;
+ /// properties.HasNumbers = true;
+ /// properties.UnicodeRanges.Add(new UnicodeRange(0, 0xFFFF));
+ ///
+ /// string s1 = StringFactory.GenerateRandomString(properties, 5678);
+ /// string s2 = StringFactory.GenerateRandomString(properties, 5678);
+ ///
+ ///
+ public static class StringFactory
+ {
+ private static readonly UnicodeRangeDatabase database = new UnicodeRangeDatabase();
+ private static PropertyFactory propertyFactory = null;
+ private static StringProperties properties = null;
+ private static StringProperties cachedProperties = null;
+ private static Collection ranges = new Collection();
+ private static int minNumCodePoints = 0;
+ private static int maxNumCodePoints = 0;
+ private static int numOfCodePoints = 0;
+
+ private static readonly List alphabetRangeList = new List();
+
+ ///
+ /// Returns a string, conforming to the provided.
+ ///
+ /// The properties of the strings to be generated by the factory.
+ /// The random number generator seed.
+ /// A string, conforming to the previously specified properties.
+ public static string GenerateRandomString(StringProperties stringProperties, int seed)
+ {
+ if (null == properties || IsPropertyChanged(stringProperties))
+ {
+ properties = stringProperties;
+ // Make a deep copy of stringProperties to cache it. If user changes any property and calls this API again,
+ // InitializeProperties() is triggered. Otherwise, no need to re initialize properties for optimization.
+ CacheProperties();
+ InitializeProperties();
+ }
+
+ string retStr = string.Empty;
+ Random rand = new Random(seed);
+ numOfCodePoints = rand.Next(minNumCodePoints, maxNumCodePoints);
+
+ int numberOfProperties = propertyFactory.PropertyDictionary.Count;
+ if (0 == numberOfProperties)
+ {
+ for (int i=0; i < numOfCodePoints; i++)
+ {
+ retStr += TextUtil.IntToString(GetNextCodePoint(rand, seed));
+ }
+ return retStr;
+ }
+
+ int quote = numOfCodePoints / propertyFactory.MinNumOfCodePoint;
+ if (0 == quote)
+ {
+ throw new ArgumentOutOfRangeException(
+ "StringFactory, MinNumberOfCodePoints needs to be at least " + numberOfProperties * propertyFactory.MinNumOfCodePoint + ".");
+ }
+
+ Dictionary.KeyCollection keyColl = propertyFactory.PropertyDictionary.Keys;
+ foreach (PropertyFactory.PropertyName name in keyColl)
+ {
+ if (PropertyFactory.PropertyName.Bidi == name)
+ {
+ retStr += GenerateBidiString(quote * BidiProperty.MINNUMOFCODEPOINT, seed);
+ }
+ else if (PropertyFactory.PropertyName.CombiningMarks == name)
+ {
+ retStr += GenerateCombiningMarkString(quote * CombiningMarksProperty.MINNUMOFCODEPOINT * (int)properties.MinNumberOfCombiningMarks, seed);
+ }
+ else if (PropertyFactory.PropertyName.Eudc == name)
+ {
+ retStr += GenerateStringWithEudc(quote * EudcProperty.MINNUMOFCODEPOINT * (int)properties.MinNumberOfEndUserDefinedCodePoints, seed);
+ }
+ else if (PropertyFactory.PropertyName.LineBreak == name)
+ {
+ retStr += GenerateStringWithLineBreak(quote * LineBreakProperty.MINNUMOFCODEPOINT * (int)properties.MinNumberOfLineBreaks, seed);
+ }
+ else if (PropertyFactory.PropertyName.Number == name)
+ {
+ retStr += GenerateStringWithNumber(quote * NumberProperty.MINNUMOFCODEPOINT, seed);
+ }
+ else if (PropertyFactory.PropertyName.Surrogate == name)
+ {
+ retStr += GenerateStringWithSurrogatePair(quote * SurrogatePairProperty.MINNUMOFCODEPOINT * (int)properties.MinNumberOfSurrogatePairs, seed);
+ }
+ else if (PropertyFactory.PropertyName.TextNormalization == name)
+ {
+ retStr += GenerateNormalizedString(quote * TextNormalizationProperty.MINNUMOFCODEPOINT, seed);
+ }
+ else if (PropertyFactory.PropertyName.TextSegmentation == name)
+ {
+ retStr += GenerateStringWithSegmentation(
+ quote * TextSegmentationProperty.MINNUMOFCODEPOINT * (int)properties.MinNumberOfTextSegmentationCodePoints,
+ seed);
+ }
+ }
+
+ if (numOfCodePoints > 0)
+ {
+ for (int i=0; i < numOfCodePoints; i++)
+ {
+ retStr += TextUtil.IntToString(GetNextCodePoint(rand, seed));
+ }
+ }
+
+ if (null != properties.NormalizationForm)
+ {
+ retStr = retStr.Normalize((NormalizationForm)properties.NormalizationForm);
+ }
+
+ return retStr;
+ }
+
+ private static void CacheProperties()
+ {
+ if (null == cachedProperties)
+ {
+ cachedProperties = new StringProperties();
+ }
+
+ cachedProperties.UnicodeRanges.Clear();
+ if (0 != properties.UnicodeRanges.Count)
+ {
+ foreach (UnicodeRange range in properties.UnicodeRanges)
+ {
+ cachedProperties.UnicodeRanges.Add(range);
+ }
+ }
+
+ cachedProperties.HasNumbers = properties.HasNumbers;
+ cachedProperties.IsBidirectional = properties.IsBidirectional;
+ cachedProperties.NormalizationForm = properties.NormalizationForm;
+ cachedProperties.MinNumberOfCombiningMarks = properties.MinNumberOfCombiningMarks;
+ cachedProperties.MinNumberOfCodePoints = properties.MinNumberOfCodePoints;
+ cachedProperties.MaxNumberOfCodePoints = properties.MaxNumberOfCodePoints;
+ cachedProperties.MinNumberOfEndUserDefinedCodePoints = properties.MinNumberOfEndUserDefinedCodePoints;
+ cachedProperties.MinNumberOfLineBreaks = properties.MinNumberOfLineBreaks;
+ cachedProperties.MinNumberOfSurrogatePairs = properties.MinNumberOfSurrogatePairs;
+ cachedProperties.MinNumberOfTextSegmentationCodePoints = properties.MinNumberOfTextSegmentationCodePoints;
+ }
+
+ private static bool IsPropertyChanged(StringProperties stringProperties)
+ {
+ if ((0 == cachedProperties.UnicodeRanges.Count && 0 != stringProperties.UnicodeRanges.Count) ||
+ (0 != cachedProperties.UnicodeRanges.Count && 0 == stringProperties.UnicodeRanges.Count))
+ {
+ return true;
+ }
+ else if (0 != cachedProperties.UnicodeRanges.Count && 0 != stringProperties.UnicodeRanges.Count)
+ {
+ if (cachedProperties.UnicodeRanges.Count != stringProperties.UnicodeRanges.Count)
+ {
+ return true;
+ }
+
+ int i = 0;
+ foreach (UnicodeRange range in cachedProperties.UnicodeRanges)
+ {
+ if (range.StartOfUnicodeRange != stringProperties.UnicodeRanges[i].StartOfUnicodeRange)
+ {
+ return true;
+ }
+
+ if (range.EndOfUnicodeRange != stringProperties.UnicodeRanges[i++].EndOfUnicodeRange)
+ {
+ return true;
+ }
+ }
+ }
+
+ if (cachedProperties.HasNumbers != stringProperties.HasNumbers)
+ {
+ return true;
+ }
+
+ if (cachedProperties.IsBidirectional != stringProperties.IsBidirectional)
+ {
+ return true;
+ }
+
+ if (cachedProperties.NormalizationForm != stringProperties.NormalizationForm)
+ {
+ return true;
+ }
+
+ if (cachedProperties.MinNumberOfCombiningMarks != stringProperties.MinNumberOfCombiningMarks)
+ {
+ return true;
+ }
+
+ if (cachedProperties.MinNumberOfCodePoints != stringProperties.MinNumberOfCodePoints)
+ {
+ return true;
+ }
+
+ if (cachedProperties.MaxNumberOfCodePoints != stringProperties.MaxNumberOfCodePoints)
+ {
+ return true;
+ }
+
+ if (cachedProperties.MinNumberOfEndUserDefinedCodePoints != stringProperties.MinNumberOfEndUserDefinedCodePoints)
+ {
+ return true;
+ }
+
+ if (cachedProperties.MinNumberOfLineBreaks != stringProperties.MinNumberOfLineBreaks)
+ {
+ return true;
+ }
+
+ if (cachedProperties.MinNumberOfSurrogatePairs != stringProperties.MinNumberOfSurrogatePairs)
+ {
+ return true;
+ }
+
+ if (cachedProperties.MinNumberOfTextSegmentationCodePoints != stringProperties.MinNumberOfTextSegmentationCodePoints)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ private static void InitializeProperties()
+ {
+ if (0 != properties.UnicodeRanges.Count)
+ {
+ ranges.Clear();
+ foreach (UnicodeRange range in properties.UnicodeRanges)
+ {
+ ranges.Add(range);
+ }
+ }
+ else
+ {
+ ranges.Add(new UnicodeRange(0, TextUtil.MaxUnicodePoint));
+ }
+
+ // Validation for Unicode ranges provided against each property is done when each property is created
+ propertyFactory = new PropertyFactory(properties, database, ranges);
+
+ // Combining mark property needs Latin alphabet
+ if (propertyFactory.HasProperty(PropertyFactory.PropertyName.CombiningMarks))
+ {
+ InitializeAlphabetRangeList();
+ }
+
+ // Get minimum number of points
+ minNumCodePoints = propertyFactory.MinNumOfCodePoint;
+
+ if (null == properties.MinNumberOfCodePoints && null == properties.MaxNumberOfCodePoints)
+ {
+ maxNumCodePoints = TextUtil.MAXNUMOFCODEPOINT;
+ if (minNumCodePoints > maxNumCodePoints)
+ {
+ throw new ArgumentOutOfRangeException(
+ "StringFactory, maximum number of code points is greater than maximum allowed " + maxNumCodePoints + ".");
+ }
+ }
+ else if (null != properties.MinNumberOfCodePoints && null == properties.MaxNumberOfCodePoints)
+ {
+ minNumCodePoints = (int)properties.MinNumberOfCodePoints;
+ if (minNumCodePoints > TextUtil.MAXNUMOFCODEPOINT)
+ {
+ throw new ArgumentOutOfRangeException(
+ "StringFactory, maximum number of code points allowed is " + TextUtil.MAXNUMOFCODEPOINT + ".");
+ }
+ maxNumCodePoints = TextUtil.MAXNUMOFCODEPOINT;
+ }
+ else if (null == properties.MinNumberOfCodePoints && null != properties.MaxNumberOfCodePoints)
+ {
+ maxNumCodePoints = (int)properties.MaxNumberOfCodePoints;
+ if (maxNumCodePoints < propertyFactory.MinNumOfCodePoint)
+ {
+ throw new ArgumentOutOfRangeException(
+ "StringFactory, minimum number of code points needed is " + propertyFactory.MinNumOfCodePoint + ".");
+ }
+ }
+ else
+ {
+ minNumCodePoints = (int)properties.MinNumberOfCodePoints;
+ maxNumCodePoints = (int)properties.MaxNumberOfCodePoints;
+ if (minNumCodePoints > maxNumCodePoints)
+ {
+ throw new ArgumentOutOfRangeException("StringFactory, MinNumberOfCodePoints, " + minNumCodePoints + " cannot be bigger than " +
+ "MaxNumberOfCodePoints, " + maxNumCodePoints + ".");
+ }
+ }
+ }
+
+ ///
+ /// Get next code point
+ ///
+ private static int GetNextCodePoint(Random rand, int seed)
+ {
+ int ctr = 0;
+ int index = rand.Next(0, ranges.Count);
+ int next = rand.Next(ranges[index].StartOfUnicodeRange, ranges[index].EndOfUnicodeRange);
+ while ((next >= 0xD800 && next <= 0xDBFF) || (next >= 0xDC00 && next <= 0xDFFF))
+ {
+ if (propertyFactory.HasProperty(PropertyFactory.PropertyName.Surrogate))
+ {
+ return SurrogatePairStringToInt(
+ (propertyFactory.PropertyDictionary[PropertyFactory.PropertyName.Surrogate]).GetRandomCodePoints(1, rand.Next()));
+ }
+ next = rand.Next(ranges[index].StartOfUnicodeRange, ranges[index].EndOfUnicodeRange);
+ ctr++;
+ if (TextUtil.MAXNUMITERATION == ctr)
+ {
+ throw new ArgumentOutOfRangeException(
+ "StringFactory, " + ctr + " loop reached." + "GetNextCodePoint aren't able to get code point beyond surrogate pair range. " +
+ "Check UnicodeChart range.");
+ }
+ }
+
+ return next;
+ }
+
+ ///
+ /// Convert a pair of Surrogate from string to UTF32
+ ///
+ private static int SurrogatePairStringToInt(string pair)
+ {
+ int high = Convert.ToInt32(pair[0]);
+ int low = Convert.ToInt32(pair[1]);
+ return (high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000;
+ }
+
+ ///
+ /// Construct bidi string
+ ///
+ private static string GenerateBidiString(int numOfPropertyCodePoints, int seed)
+ {
+ if (numOfPropertyCodePoints < 1)
+ {
+ throw new ArgumentOutOfRangeException("StringFactory, numOfPropertyCodePoints, " + numOfPropertyCodePoints + " cannot be less than one.");
+ }
+ numOfCodePoints -= numOfPropertyCodePoints;
+
+ if (numOfCodePoints < 0)
+ {
+ throw new ArgumentOutOfRangeException("StringFactory, " + numOfCodePoints +
+ "left for the operation. Not enough code point to construct bidi string.");
+ }
+
+ if (numOfPropertyCodePoints < BidiProperty.MINNUMOFCODEPOINT)
+ {
+ throw new ArgumentOutOfRangeException(
+ "StringFactory, minimum bidi string needs " + BidiProperty.MINNUMOFCODEPOINT + " code points.");
+ }
+
+ Random rand = new Random(seed);
+ // Note, numOfBidi is number of property - 1 numOfBidi is 2 code points
+ int numOfBidi = rand.Next(1, numOfPropertyCodePoints / BidiProperty.MINNUMOFCODEPOINT);
+ int left = numOfPropertyCodePoints - numOfBidi * BidiProperty.MINNUMOFCODEPOINT;
+
+ string bidiStr = string.Empty;
+ bidiStr += (propertyFactory.PropertyDictionary[PropertyFactory.PropertyName.Bidi]).GetRandomCodePoints(numOfBidi, seed);
+ if (left <= 0)
+ {
+ return bidiStr;
+ }
+
+ string retStr = string.Empty;
+ int temp = 0;
+
+ // Not using TextUtil.GetRandomCodePoint for more randomness
+ for (int i=1; i <= left; i++)
+ {
+ temp = GetNextCodePoint(rand, seed);
+ retStr += TextUtil.IntToString(temp);
+ }
+ retStr += bidiStr;
+
+ return retStr;
+ }
+
+ ///
+ /// Construct string with combining marks
+ ///
+ private static string GenerateCombiningMarkString(int numOfPropertyCodePoints, int seed)
+ {
+ if (numOfPropertyCodePoints < 1)
+ {
+ throw new ArgumentOutOfRangeException("StringFactory, numOfPropertyCodePoints, " + numOfPropertyCodePoints + " cannot be less than one.");
+ }
+ numOfCodePoints -= numOfPropertyCodePoints;
+
+ if (numOfCodePoints < 0)
+ {
+ throw new ArgumentOutOfRangeException("StringFactory, " + numOfCodePoints +
+ "left for the operation. Not enough code point to construct string with combining mark.");
+ }
+
+ // MinNumberOfCombiningMarks is null or not should have been checked
+ int numOfCombiningMarks = (int)properties.MinNumberOfCombiningMarks;
+
+ if (numOfPropertyCodePoints < numOfCombiningMarks * CombiningMarksProperty.MINNUMOFCODEPOINT)
+ {
+ throw new ArgumentOutOfRangeException(
+ "StringFactory, minimum string cotains combining mark needs " +
+ numOfCombiningMarks * CombiningMarksProperty.MINNUMOFCODEPOINT + " code points.");
+ }
+
+ // Construct combining marks string
+ Random rand = new Random(seed);
+ // Half Latin half combining symbols
+ int numOfLatin = numOfCombiningMarks;
+ int left = numOfPropertyCodePoints - numOfCombiningMarks * CombiningMarksProperty.MINNUMOFCODEPOINT;
+
+ string latinStr = string.Empty;
+ int index = rand.Next(0, alphabetRangeList.Count);
+ // From a random range in alphabetRangeList
+ latinStr += TextUtil.GetRandomCodePoint(alphabetRangeList[index], numOfLatin, null, seed);
+
+ string combiningMarkStr = string.Empty;
+ combiningMarkStr +=
+ (propertyFactory.PropertyDictionary[PropertyFactory.PropertyName.CombiningMarks]).GetRandomCodePoints(numOfCombiningMarks, seed);
+
+ string retStr = string.Empty;
+ int i = 0;
+ foreach (char next in latinStr)
+ {
+ retStr += next;
+ retStr += combiningMarkStr[i++];
+ }
+
+ // Leftover
+ for (i = 0; i < left; i++)
+ {
+ retStr += TextUtil.IntToString(GetNextCodePoint(rand, seed));
+ }
+
+ return retStr;
+ }
+
+ ///
+ /// Construct string with EUDC
+ ///
+ private static string GenerateStringWithEudc(int numOfPropertyCodePoints, int seed)
+ {
+ if (numOfPropertyCodePoints < 1)
+ {
+ throw new ArgumentOutOfRangeException("StringFactory, numOfPropertyCodePoints, " + numOfPropertyCodePoints + " cannot be less than one.");
+ }
+ numOfCodePoints -= numOfPropertyCodePoints;
+
+ if (numOfCodePoints < 0)
+ {
+ throw new ArgumentOutOfRangeException("StringFactory, " + numOfCodePoints +
+ "left for the operation. Not enough code point to construct string with EUDC.");
+ }
+
+ // MinNumberOfEndUserDefinedCodePoints of EUDC is null or not should have been checked
+ int numOfEudc = (int)properties.MinNumberOfEndUserDefinedCodePoints;
+
+ int left = numOfPropertyCodePoints - (numOfEudc * EudcProperty.MINNUMOFCODEPOINT);
+
+ string eudcStr = string.Empty;
+ eudcStr += (propertyFactory.PropertyDictionary[PropertyFactory.PropertyName.Eudc]).GetRandomCodePoints(numOfEudc, seed);
+ if (left <= 0)
+ {
+ return eudcStr;
+ }
+
+ string retStr = string.Empty;
+ Random rand = new Random(seed);
+ // numOfEudc is 0 or not is checked in PropertiesFactory
+ int quote = left / numOfEudc ;
+
+
+ int j = 0;
+ for (int i=1; i <= left; i++)
+ {
+ retStr += TextUtil.IntToString(GetNextCodePoint(rand, seed));
+
+ if (quote > 0 && j < eudcStr.Length)
+ {
+ if (i % quote == 0)
+ {
+ retStr += eudcStr[j++];
+ }
+ }
+ }
+
+ for (int i=j; i < eudcStr.Length; i++)
+ {
+ retStr += eudcStr[i];
+ }
+
+ return retStr;
+ }
+
+
+ ///
+ /// Construct string with Linebreaks
+ ///
+ private static string GenerateStringWithLineBreak(int numOfPropertyCodePoints, int seed)
+ {
+ if (numOfPropertyCodePoints < 1)
+ {
+ throw new ArgumentOutOfRangeException("StringFactory, numOfPropertyCodePoints, " + numOfPropertyCodePoints + " cannot be less than one.");
+ }
+ numOfCodePoints -= numOfPropertyCodePoints;
+
+ if (numOfCodePoints < 0)
+ {
+ throw new ArgumentOutOfRangeException("StringFactory, " + numOfCodePoints +
+ "left for the operation. Not enough code point to construct string with line break.");
+ }
+
+ // MinNumberOfLineBreaks is null or not should have been checked
+ int numOfLineBreaks = (int)properties.MinNumberOfLineBreaks;
+
+ int left = numOfPropertyCodePoints - numOfLineBreaks * LineBreakProperty.MINNUMOFCODEPOINT;
+
+ string lineBreakStr = string.Empty;
+ lineBreakStr += (propertyFactory.PropertyDictionary[PropertyFactory.PropertyName.LineBreak]).GetRandomCodePoints(numOfLineBreaks, seed);
+ if (left <= 0)
+ {
+ return lineBreakStr;
+ }
+
+ string retStr = string.Empty;
+ Random rand = new Random(seed);
+ int quote = left / numOfLineBreaks;
+
+ int j = 0;
+ for (int i=1; i <= left; i++)
+ {
+ retStr += TextUtil.IntToString(GetNextCodePoint(rand, seed));
+
+ if (quote > 0 && j < lineBreakStr.Length)
+ {
+ if (0 == i % quote)
+ {
+ retStr += lineBreakStr[j++];
+ }
+ }
+ }
+
+ for (int i=j; i < lineBreakStr.Length; i++)
+ {
+ retStr += lineBreakStr[i];
+ }
+
+ return retStr;
+ }
+
+ ///
+ /// Construct string with numbers
+ ///
+ private static string GenerateStringWithNumber(int numOfPropertyCodePoints, int seed)
+ {
+ if (numOfPropertyCodePoints < 1)
+ {
+ throw new ArgumentOutOfRangeException("StringFactory, numOfPropertyCodePoints, " + numOfPropertyCodePoints + " cannot be less than one.");
+ }
+ numOfCodePoints -= numOfPropertyCodePoints;
+
+ if (numOfCodePoints < 0)
+ {
+ throw new ArgumentOutOfRangeException("StringFactory, " + numOfCodePoints +
+ "left for the operation. Not enough code point to construct string with number.");
+ }
+
+ int numOfNumbers = 0;
+ Random rand = new Random(seed);
+ numOfNumbers = rand.Next(1, numOfPropertyCodePoints / NumberProperty.MINNUMOFCODEPOINT);
+
+ int left = numOfPropertyCodePoints - numOfNumbers * NumberProperty.MINNUMOFCODEPOINT;
+
+ string numStr = string.Empty;
+ numStr += (propertyFactory.PropertyDictionary[PropertyFactory.PropertyName.Number]).GetRandomCodePoints(numOfNumbers, seed);
+ if (left <= 0)
+ {
+ return numStr;
+ }
+
+ int quote = left / numOfNumbers;
+ string retStr = string.Empty;
+
+ int j = 0;
+ for (int i=1; i <= left; i++)
+ {
+ retStr += TextUtil.IntToString(GetNextCodePoint(rand, seed));
+
+ if (quote > 0 && j < numStr.Length)
+ {
+ if (0 == i % quote)
+ {
+ retStr += numStr[j++];
+ }
+ }
+ }
+
+ for (int i=j; i < numStr.Length; i++)
+ {
+ retStr += numStr[i];
+ }
+
+ return retStr;
+ }
+
+ ///
+ /// Construct string with Surrogate pairs
+ ///
+ private static string GenerateStringWithSurrogatePair(int numOfPropertyCodePoints, int seed)
+ {
+ if (numOfPropertyCodePoints < 1)
+ {
+ throw new ArgumentOutOfRangeException("StringFactory, numOfPropertyCodePoints, " + numOfPropertyCodePoints + " cannot be less than one.");
+ }
+ numOfCodePoints -= numOfPropertyCodePoints;
+
+ if (numOfCodePoints < 0)
+ {
+ throw new ArgumentOutOfRangeException("StringFactory, " + numOfCodePoints +
+ "left for the operation. Not enough code points for surrogate pair string.");
+ }
+
+ int numOfSurrogatePairs = (int)properties.MinNumberOfSurrogatePairs;
+ if (numOfPropertyCodePoints < numOfSurrogatePairs * SurrogatePairProperty.MINNUMOFCODEPOINT)
+ {
+ throw new ArgumentOutOfRangeException(
+ "StringFactory, minimum string cotains surrogate pair needs " +
+ numOfSurrogatePairs * SurrogatePairProperty.MINNUMOFCODEPOINT + " code points.");
+ }
+
+ string surrogatePairStr = string.Empty;
+ surrogatePairStr += (propertyFactory.PropertyDictionary[PropertyFactory.PropertyName.Surrogate]).GetRandomCodePoints(numOfSurrogatePairs, seed);
+ int left = numOfPropertyCodePoints - numOfSurrogatePairs * SurrogatePairProperty.MINNUMOFCODEPOINT;
+ if (left <= 0)
+ {
+ return surrogatePairStr;
+ }
+
+ string retStr = string.Empty;
+ int quote = left / numOfSurrogatePairs;
+ Random rand = new Random(seed);
+
+ int j = 0;
+ for (int i=1; i <= left; i++)
+ {
+ retStr += TextUtil.IntToString(GetNextCodePoint(rand, seed));
+
+ if (quote > 0 && j < surrogatePairStr.Length)
+ {
+ if (0 == i % quote)
+ {
+ retStr += surrogatePairStr[j];
+ retStr += surrogatePairStr[j+1];
+ j += 2;
+ }
+ }
+ }
+
+ for (int i=j; i < surrogatePairStr.Length; i++)
+ {
+ retStr += surrogatePairStr[i];
+ }
+
+ return retStr;
+ }
+
+ ///
+ /// Construct normalized text string
+ ///
+ private static string GenerateNormalizedString(int numOfPropertyCodePoints, int seed)
+ {
+ if (numOfPropertyCodePoints < 1)
+ {
+ throw new ArgumentOutOfRangeException("StringFactory, numOfPropertyCodePoints, " + numOfPropertyCodePoints + " cannot be less than one.");
+ }
+ numOfCodePoints -= numOfPropertyCodePoints;
+
+ if (numOfCodePoints < 0)
+ {
+ throw new ArgumentOutOfRangeException("StringFactory, " + numOfCodePoints +
+ "left for the operation. Not enough code points for normalized string.");
+ }
+
+ Random rand = new Random(seed);
+ int numOfNormalizationCodePoint = rand.Next(1, numOfPropertyCodePoints / TextNormalizationProperty.MINNUMOFCODEPOINT);
+ int left = numOfPropertyCodePoints - numOfNormalizationCodePoint * TextNormalizationProperty.MINNUMOFCODEPOINT;
+
+ string normalizedStr = string.Empty;
+ normalizedStr += (propertyFactory.PropertyDictionary[PropertyFactory.PropertyName.TextNormalization]).GetRandomCodePoints(
+ numOfNormalizationCodePoint,
+ seed);
+ if (left <= 0)
+ {
+ return normalizedStr;
+ }
+
+ string retStr = string.Empty;
+ int quote = left / numOfNormalizationCodePoint;
+
+ int j = 0;
+ for (int i=1; i <= left; i++)
+ {
+ retStr += TextUtil.IntToString(GetNextCodePoint(rand, seed));
+
+ if (quote > 0 && j < normalizedStr.Length)
+ {
+ if (0 == i % quote)
+ {
+ retStr += normalizedStr[j];
+ if (normalizedStr[j] >= 0xD800 && normalizedStr[j] <= 0xDBFF)
+ {
+ retStr += normalizedStr[j+1];
+ j++;
+ }
+ j++;
+ }
+ }
+ }
+
+ for (int i=j; i < normalizedStr.Length; i++)
+ {
+ retStr += normalizedStr[i];
+ }
+
+ return retStr;
+ }
+
+ ///
+ /// Construct string with segmentation code point
+ ///
+ private static string GenerateStringWithSegmentation(int numOfPropertyCodePoints, int seed)
+ {
+ if (numOfPropertyCodePoints < 1)
+ {
+ throw new ArgumentOutOfRangeException("StringFactory, numOfPropertyCodePoints, " + numOfPropertyCodePoints + " cannot be less than one.");
+ }
+ numOfCodePoints -= numOfPropertyCodePoints;
+
+ if (numOfCodePoints < 0)
+ {
+ throw new ArgumentOutOfRangeException("StringFactory, " + numOfCodePoints +
+ "left for the operation. Not enough code point to segementation.");
+ }
+
+ // MinNumberOfTextSegmentationCodePoints is null or not should have been checked
+ int numOfTextSegmentationChars = (int)properties.MinNumberOfTextSegmentationCodePoints;
+ int left = numOfPropertyCodePoints - numOfTextSegmentationChars * TextSegmentationProperty.MINNUMOFCODEPOINT;
+
+ string segmentationStr = string.Empty;
+ segmentationStr += (propertyFactory.PropertyDictionary[PropertyFactory.PropertyName.TextSegmentation]).GetRandomCodePoints(
+ numOfTextSegmentationChars,
+ seed);
+ if (left <= 0)
+ {
+ return segmentationStr;
+ }
+
+ string retStr = string.Empty;
+ Random rand = new Random(seed);
+ int quote = left / numOfTextSegmentationChars;
+
+ int j = 0;
+ for (int i=1; i <= left; i++)
+ {
+ retStr += TextUtil.IntToString(GetNextCodePoint(rand, seed));
+
+ if (quote > 0 && j < segmentationStr.Length)
+ {
+ if (0 == i % quote)
+ {
+ retStr += segmentationStr[j++];
+ }
+ }
+ }
+
+ for (int i=j; i < segmentationStr.Length; i++)
+ {
+ retStr += segmentationStr[i];
+ }
+
+ return retStr;
+ }
+
+ private static void InitializeAlphabetRangeList( )
+ {
+ bool isValid = false;
+
+ foreach (UnicodeRange range in ranges)
+ {
+ UnicodeRange newRange = RangePropertyCollector.GetRange(new UnicodeRange(0x0041, 0x005A), range);
+ if (null != newRange)
+ {
+ alphabetRangeList.Add(newRange);
+ isValid = true;
+ }
+
+ newRange = RangePropertyCollector.GetRange(new UnicodeRange(0x0061, 0x007A), range);
+ if (null != newRange)
+ {
+ alphabetRangeList.Add(newRange);
+ isValid = true;
+ }
+ }
+
+ if (!isValid)
+ {
+ throw new ArgumentOutOfRangeException("StringFactory, Latin alphabet ranges for Combining mark property are beyond expected. " +
+ "Refer to Latin Alphabet range 0x0041 - 0x005A and 0x0061 - 0x007A." + "All " + ranges.Count + " UniCodeRange is in Latin alphabet range");
+ }
+ }
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/StringProperties.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/StringProperties.cs
new file mode 100644
index 0000000..122d9c3
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/StringProperties.cs
@@ -0,0 +1,142 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.ObjectModel;
+using System.Text;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// Contains types for the generation, manipulation and validation of strings and text, for testing purposes.
+ ///
+ // Suppressed the warning that the class is never instantiated.
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812")]
+ [System.Runtime.CompilerServices.CompilerGenerated()]
+ class NamespaceDoc
+ {
+ // Empty class used only for generation of namespace comments.
+ }
+
+ ///
+ /// Defines the desired properties of a character string.
+ /// For more information on character strings, see this article.
+ ///
+ ///
+ /// Note that this class is used as "a filter" when generating character strings with
+ /// . Upon instantiation, all properties except CultureInfo of a
+ /// object (which are all Nullables)
+ /// have null values, which means that the object does not impose any filtering limitations on
+ /// the generated strings.
+ ///
+ /// Setting properties to non-null values means that the value of the property should be taken
+ /// into account by during string generation. For example, setting
+ /// to 10 means "generate strings with up to 10 code points".
+ ///
+ ///
+ ///
+ /// The following example demonstrates how to generate a Cyrillic string that
+ /// contains between 10 and 30 characters.
+ ///
+ /// StringProperties properties = new StringProperties();
+ ///
+ /// properties.MinNumberOfCodePoints = 10;
+ /// properties.MaxNumberOfCodePoints = 30;
+ /// properties.UnicodeRanges.Add(new UnicodeRange(UnicodeChart.Cyrillic));
+ ///
+ /// string s = StringFactory.GenerateRandomString(properties, 1234);
+ ///
+ ///
+ ///
+ ///
+ /// The following example demonstrates how to generate a string which
+ /// contains characters from more than one Unicode chart.
+ ///
+ /// StringProperties sp = new StringProperties();
+ ///
+ /// sp.MinNumberOfCodePoints = 10;
+ /// sp.MaxNumberOfCodePoints = 20;
+ /// sp.UnicodeRanges.Add(new UnicodeRange(UnicodeChart.BraillePatterns));
+ /// sp.UnicodeRanges.Add(new UnicodeRange(UnicodeChart.Cyrillic));
+ ///
+ /// string s = StringFactory.GenerateRandomString(sp, 1234);
+ ///
+ ///
+ public class StringProperties
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public StringProperties()
+ {
+ UnicodeRanges = new Collection();
+ MinNumberOfCombiningMarks = null;
+ HasNumbers = null;
+ IsBidirectional = null;
+ NormalizationForm = null;
+ MinNumberOfCodePoints = MaxNumberOfCodePoints = null;
+ MinNumberOfEndUserDefinedCodePoints = null;
+ MinNumberOfLineBreaks = null;
+ MinNumberOfSurrogatePairs = null;
+ MinNumberOfTextSegmentationCodePoints = null;
+ }
+
+ ///
+ /// Determines whether the string belongs to one or more .
+ ///
+ public Collection UnicodeRanges { get; private set; }
+
+ ///
+ /// Determines whether the string contains formatted numbers.
+ ///
+ public bool? HasNumbers { get; set; }
+
+ ///
+ /// Determines whether the string is bi-directional.
+ ///
+ public bool? IsBidirectional { get; set; }
+
+ ///
+ /// Determines the type of normalization to perform on the string.
+ /// For more information, see this article.
+ ///
+ public NormalizationForm? NormalizationForm { get; set; }
+
+ ///
+ /// Determines the minimum number of combining marks in the string.
+ /// Combining marks (and combining
+ /// characters in general) are characters that are intended to modify other characters (e.g. accents, etc.)
+ ///
+ public int? MinNumberOfCombiningMarks { get; set; }
+
+ ///
+ /// Determines the minimum number of code points (characters) in the string.
+ ///
+ public int? MinNumberOfCodePoints { get; set; }
+
+ ///
+ /// Determines the maximum number of code points (characters) in the string.
+ ///
+ public int? MaxNumberOfCodePoints { get; set; }
+
+ ///
+ /// Determines the minimum number of end-user-defined characters (EUDC) in the string.
+ ///
+ public int? MinNumberOfEndUserDefinedCodePoints { get; set; }
+
+ ///
+ /// Determines the minimum number of line breaks in the string.
+ ///
+ public int? MinNumberOfLineBreaks { get; set; }
+
+ ///
+ /// Determines the minimum number of surrogate pairs in the string.
+ ///
+ public int? MinNumberOfSurrogatePairs { get; set; }
+
+ ///
+ /// Determines the minimum number of text segmentation code points in the string.
+ ///
+ public int? MinNumberOfTextSegmentationCodePoints { get; set; }
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/SubGroup.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/SubGroup.cs
new file mode 100644
index 0000000..c0f47f4
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/SubGroup.cs
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// sub Group DataStructure
+ ///
+ internal class SubGroup
+ {
+ ///
+ /// Define LineBreakDatabase class,
+ /// Newline
+ ///
+ public SubGroup(UnicodeRange range, string name, string ids, UnicodeChart chart)
+ {
+ UnicodeRange = new UnicodeRange(range);
+ SubGroupName = name;
+ SubIds = ids;
+ UnicodeChart = chart;
+ }
+
+ ///
+ /// SubGroupRange property
+ ///
+ public UnicodeRange UnicodeRange { get; set; }
+
+ ///
+ /// SubGroupName property
+ ///
+ public string SubGroupName { get; set; }
+
+ ///
+ /// Enum Chart
+ ///
+ public UnicodeChart UnicodeChart { get; set; }
+
+ ///
+ /// SubIds property
+ ///
+ public string SubIds { get; set; }
+ }
+}
+
+
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/SurrogatePairProperty.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/SurrogatePairProperty.cs
new file mode 100644
index 0000000..08b302f
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/SurrogatePairProperty.cs
@@ -0,0 +1,147 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// Collect surrogate pairs
+ ///
+ internal class SurrogatePairProperty : IStringProperty
+ {
+ private List surrogatePairRangeList = new List();
+ private UnicodeRange surrogateRange;
+
+ private int highMin = 0; // 0xD800 V5.2
+ private int highMax = 0; // 0xDBFF V5.2
+ private int lowMin = 0; // 0xDC00 V5.2
+ private int lowMax = 0; // 0xDFFF V5.2
+
+ ///
+ /// Define minimum code point needed to have surrogate pair
+ ///
+ public static readonly int MINNUMOFCODEPOINT = 2;
+
+ ///
+ /// Define SurrogatePairProperty class
+ /// Newline
+ /// Newline
+ ///
+ public SurrogatePairProperty(UnicodeRangeDatabase unicodeDb, Collection expectedRanges)
+ {
+ bool isValid = false;
+
+ foreach (UnicodeRange range in expectedRanges)
+ {
+ if (RangePropertyCollector.BuildPropertyDataList(
+ unicodeDb,
+ range,
+ surrogatePairRangeList,
+ "Surrogates",
+ GroupAttributes.GroupName))
+ {
+ foreach (UnicodeRangeProperty data in surrogatePairRangeList)
+ {
+ if (data.Name.Equals("High Surrogates", StringComparison.OrdinalIgnoreCase))
+ {
+ highMin = data.Range.StartOfUnicodeRange;
+ highMax = data.Range.EndOfUnicodeRange;
+ }
+ else if (data.Name.Equals("Low Surrogates", StringComparison.OrdinalIgnoreCase))
+ {
+ lowMin = data.Range.StartOfUnicodeRange;
+ lowMax = data.Range.EndOfUnicodeRange;
+ }
+ }
+ isValid = true;
+ }
+
+ surrogateRange = RangePropertyCollector.GetRange(new UnicodeRange(0x10000, TextUtil.MaxUnicodePoint), range);
+ if (null != surrogateRange)
+ {
+ isValid = true;
+ }
+ }
+
+ if (!isValid)
+ {
+ throw new ArgumentOutOfRangeException("expectedRanges", "SurrogatePairProperty, SurrogatePair ranges are beyond expected range. " +
+ "Refert to Surrogates range and UTF32.");
+ }
+ }
+
+ ///
+ /// Check if code point is in the property range
+ ///
+ public bool IsInPropertyRange(int codePoint)
+ {
+ bool isIn = false;
+ if (codePoint > 0xFFFF)
+ {
+ if (null != surrogateRange)
+ {
+ if (codePoint >= surrogateRange.StartOfUnicodeRange && codePoint <= surrogateRange.EndOfUnicodeRange)
+ {
+ isIn = true;
+ }
+ }
+ }
+
+ if (0 != highMin)
+ {
+ if ((codePoint >= highMin && codePoint <= highMax) || (codePoint >= lowMin && codePoint <= lowMax))
+ {
+ isIn = true;
+ }
+ }
+
+ return isIn;
+ }
+
+ ///
+ /// Get random Surrogate pairs
+ ///
+ public string GetRandomCodePoints(int numOfProperty, int seed)
+ {
+ // NumOfProperty means number of pair
+ if (numOfProperty < 1)
+ {
+ throw new ArgumentOutOfRangeException("SurrogatePairProperty, numOfProperty, " + numOfProperty + " cannot be less than one.");
+ }
+
+ string surrogateStr = string.Empty;
+ Random rnd = new Random(seed);
+ for (int i=1; i <= numOfProperty; i++)
+ {
+ if (null != surrogateRange && 0 != highMin)
+ {
+ if (0 == rnd.Next(0, 1))
+ {
+ surrogateStr += Convert.ToChar(rnd.Next(highMin, highMax));
+ surrogateStr += Convert.ToChar(rnd.Next(lowMin, lowMax));
+ }
+ else
+ {
+ surrogateStr += TextUtil.IntToString(rnd.Next(surrogateRange.StartOfUnicodeRange, surrogateRange.EndOfUnicodeRange));
+ }
+ }
+ else if (0 != highMin)
+ {
+ surrogateStr += Convert.ToChar(rnd.Next(highMin, highMax));
+ surrogateStr += Convert.ToChar(rnd.Next(lowMin, lowMax));
+ }
+ else
+ {
+ surrogateStr += TextUtil.IntToString(rnd.Next(surrogateRange.StartOfUnicodeRange, surrogateRange.EndOfUnicodeRange));
+ }
+ }
+
+ return surrogateStr;
+ }
+ }
+}
+
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/TextNormalizationProperty.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/TextNormalizationProperty.cs
new file mode 100644
index 0000000..f09b56a
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/TextNormalizationProperty.cs
@@ -0,0 +1,330 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// TextNormalization property
+ ///
+ internal class TextNormalizationProperty : IStringProperty
+ {
+ ///
+ /// Dictionary to store code points corresponding to culture.
+ ///
+ private Dictionary textNormalizationPropertyDictionary = new Dictionary();
+
+ private List textNormalizationRangeList = new List();
+
+ private int [] codePointsWithDifferentNormalizationForms;
+
+ ///
+ /// Define minimum code point needed to be a text normalization string
+ ///
+ public static readonly int MINNUMOFCODEPOINT = 1;
+
+ ///
+ /// Define SurrogatePairDictionary class
+ /// Newline
+ /// Newline
+ ///
+ public TextNormalizationProperty(UnicodeRangeDatabase unicodeDb, Collection expectedRanges)
+ {
+ bool isValid = false;
+
+ foreach (UnicodeRange range in expectedRanges)
+ {
+ if (RangePropertyCollector.BuildPropertyDataList(
+ unicodeDb,
+ range,
+ textNormalizationRangeList,
+ "Latin",
+ GroupAttributes.Name))
+ {
+ isValid = true;
+ }
+ if (RangePropertyCollector.BuildPropertyDataList(
+ unicodeDb,
+ range,
+ textNormalizationRangeList,
+ "CJK Unified Ideographs (Han)",
+ GroupAttributes.Name))
+ {
+ isValid = true;
+ }
+ if (RangePropertyCollector.BuildPropertyDataList(
+ unicodeDb,
+ range,
+ textNormalizationRangeList,
+ "CJK Compatibility Ideographs",
+ GroupAttributes.Name))
+ {
+ isValid = true;
+ }
+ if (RangePropertyCollector.BuildPropertyDataList(
+ unicodeDb,
+ range,
+ textNormalizationRangeList,
+ "Katakana",
+ GroupAttributes.Name))
+ {
+ isValid = true;
+ }
+ if (RangePropertyCollector.BuildPropertyDataList(
+ unicodeDb,
+ range,
+ textNormalizationRangeList,
+ "Hangul Jamo",
+ GroupAttributes.Name))
+ {
+ isValid = true;
+ }
+ if (RangePropertyCollector.BuildPropertyDataList(
+ unicodeDb,
+ range,
+ textNormalizationRangeList,
+ "Hangul Syllables",
+ GroupAttributes.Name))
+ {
+ isValid = true;
+ }
+ if (RangePropertyCollector.BuildPropertyDataList(
+ unicodeDb,
+ range,
+ textNormalizationRangeList,
+ "Arabic",
+ GroupAttributes.Name))
+ {
+ isValid = true;
+ }
+ if (RangePropertyCollector.BuildPropertyDataList(
+ unicodeDb,
+ range,
+ textNormalizationRangeList,
+ "Greek",
+ GroupAttributes.Name))
+ {
+ isValid = true;
+ }
+ }
+
+
+ if (InitializeTextNormalizationPropertyDictionary(expectedRanges))
+ {
+ isValid = true;
+ }
+
+ if (!isValid)
+ {
+ throw new ArgumentOutOfRangeException("expectedRanges", "TextNormalizationProperty, " +
+ "code points for text normalization ranges are beyond expected range. " + "Refert to Latin, CJK Unified Ideographs (Han) " +
+ "CJK Compatibility Ideographs, Katakana, Hangul Jamo, Hangul Syllables, Arabic, and Greek ranges.");
+ }
+ }
+
+ ///
+ /// Dictionary to store code points corresponding to culture.
+ ///
+ private bool InitializeTextNormalizationPropertyDictionary(Collection expectedRanges)
+ {
+ int [] othersymbols = {0xFFE4, 0x21CD, 0xFFE8, 0xFFED, 0xFFEE, 0x3036, 0x1D15E, 0x1D15F, 0x1D160, 0x1D161, 0x1D162,
+ 0x1D163, 0x1D164, 0x1D1BB, 0x1D1BD, 0x1D1BF, 0x1D1BC, 0x1D1BE, 0x1D1C0};
+ textNormalizationPropertyDictionary.Add("othersymbols", othersymbols);
+
+ int [] modifiersymbols = {0x00B4, 0x0384, 0x1FFD, 0x02DC, 0x00AF, 0xFFE3, 0x02D8, 0x02D9, 0x00A8, 0x1FED, 0x0385,
+ 0x1FEE, 0x1FC1, 0x02DA, 0x02DD, 0x1FBD, 0x1FBF, 0x1FCD, 0x1FCE, 0x1FCF, 0x1FFE, 0x1FDD, 0x1FDE, 0x1FDF, 0x00B8,
+ 0x02DB, 0x1FC0, 0x309B, 0x309C, 0xFF3E, 0x1FEF, 0xFF40};
+ textNormalizationPropertyDictionary.Add("modifiersymbols", modifiersymbols);
+
+ int [] currencysymbols = {0xFE69, 0xFF04, 0xFFE0, 0xFFE1, 0xFFE5, 0xFFE6};
+ textNormalizationPropertyDictionary.Add("currencysymbols", currencysymbols);
+
+ int [] mathsymbols = {0x207A, 0x208A, 0xFB29, 0xFE62, 0xFF0B, 0x2A74, 0xFE64, 0xFF1C, 0x226E, 0x207C, 0x208C, 0xFE66,
+ 0xFF1D, 0x2A75, 0x2A76, 0x2260, 0xFE65, 0xFF1E, 0x226F, 0xFF5C, 0xFF5E, 0xFFE2, 0xFFE9, 0x219A, 0xFFEA, 0xFFEB,
+ 0x219B, 0xFFEC, 0x21AE, 0x21CF, 0x21CE, 0x1D6DB, 0x1D715, 0x1D74F, 0x1D789, 0x1D7C3, 0x2204, 0x1D6C1, 0x1D6FB,
+ 0x1D735, 0x1D76F, 0x1D7A9, 0x2209, 0x220C, 0x2140, 0x207B, 0x208B, 0x2224, 0x2226, 0x222C, 0x222D, 0x2A0C, 0x222F,
+ 0x2230, 0x2241, 0x2244, 0x2247, 0x2249, 0x226D, 0x2262, 0x2270, 0x2271, 0x2274, 0x2275, 0x2278, 0x2279, 0x2280,
+ 0x2281, 0x22E0, 0x22E1, 0x2284, 0x2285, 0x2288, 0x2289, 0x22E2, 0x22E3, 0x22AC, 0x22AD, 0x22AE, 0x22AF, 0x22EA,
+ 0x22EB, 0x22EC, 0x22ED, 0x2ADC};
+ textNormalizationPropertyDictionary.Add("mathsymbols", mathsymbols);
+
+ int [] modifierletter = {0x037A, 0x0374, 0xFF9E, 0xFF9F, 0xFF70};
+ textNormalizationPropertyDictionary.Add("modifierletter", modifierletter);
+
+ int [] otherletter = {0xFE70, 0xFE72, 0xFC5E, 0xFE74, 0xFC5F, 0xFE76, 0xFC60, 0xFE78, 0xFC61, 0xFE7A, 0xFC62, 0xFE7C,
+ 0xFC63, 0xFE7E, 0xFE71, 0xFE77, 0xFCF2, 0xFE79, 0xFCF3, 0xFE7B, 0xFCF4, 0xFE7D, 0xFE7F};
+ textNormalizationPropertyDictionary.Add("otherletter", otherletter);
+
+ int [] nonspacingmark = {0x0340, 0x0341, 0x0344, 0x0343};
+ textNormalizationPropertyDictionary.Add("nonspacingmark", nonspacingmark);
+
+ int [] spaceseparator = {0x00A0, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200A,
+ 0x202F, 0x205F, 0x3000};
+ textNormalizationPropertyDictionary.Add("spaceseparator", spaceseparator);
+
+ int [] decimalnumber = {0xFF10, 0x1D7CE, 0x1D7D8, 0x1D7E2, 0x1D7EC, 0x1D7F6, 0xFF11, 0x1D7CF, 0x1D7D9, 0x1D7E3, 0x1D7ED,
+ 0x200A, 0x1D7F7, 0xFF12, 0x1D7D0, 0x1D7DA, 0x1D7E4, 0x1D7EE, 0x1D7F8, 0xFF13, 0x1D7D1, 0x1D7DB, 0x1D7E5, 0x1D7EF,
+ 0x1D7F9,0xFF14, 0x1D7D2, 0x1D7DC, 0x1D7E6, 0x1D7F0, 0x1D7FA, 0xFF15, 0x1D7D3, 0x1D7DD, 0x1D7E7, 0x1D7F1, 0x1D7FB,
+ 0xFF16, 0x1D7D4, 0x1D7DE, 0x1D7E8, 0x1D7F2, 0x1D7FC, 0xFF17, 0x1D7D5, 0x1D7DF, 0x1D7E9, 0x1D7F3, 0x1D7FD, 0xFF18,
+ 0x1D7D6, 0x1D7E0, 0x1D7EA, 0x1D7F4, 0x1D7FE, 0xFF19, 0x1D7D7, 0x1D7E1, 0x1D7EB, 0x1D7F5, 0x1D7FF};
+ textNormalizationPropertyDictionary.Add("decimalnumber", decimalnumber);
+
+ int [] othernumber = {0x2474, 0x247D, 0x247E, 0x247F, 0x2480, 0x2481, 0x2482, 0x2483, 0x2484, 0x2485, 0x2486, 0x2475, 0x2487, 0x2476,
+ 0x2477, 0x2478, 0x2479, 0x247A, 0x247B, 0x247C, 0x2070, 0x2080, 0x24EA, 0x1F101, 0x1F100, 0x2189,0x00B9, 0x2081, 0x2460, 0x1F102,
+ 0x2488, 0x2469, 0x2491, 0x246A, 0x2492, 0x246B, 0x2493, 0x246C, 0x2494, 0x246D, 0x2495, 0x246E, 0x2496, 0x246F, 0x2497, 0x2470,
+ 0x2498, 0x2499, 0x2472, 0x249A, 0x215F, 0x2152, 0x00BD, 0x2153, 0x00BC, 0x2155, 0x2159, 0x2150, 0x215B, 0x2151, 0x00B2, 0x2082,
+ 0x2461, 0x1F103, 0x2489, 0x2473, 0x249B, 0x3251, 0x3252, 0x3253, 0x3254, 0x3255, 0x3256, 0x3257, 0x3258, 0x3259, 0x2154, 0x2156,
+ 0x00B3, 0x2083, 0x2462, 0x1F104, 0x248A, 0x325A, 0x325B, 0x325C, 0x325D, 0x325E, 0x325F, 0x32B1, 0x32B2, 0x32B3, 0x32B4, 0x00BE,
+ 0x2157, 0x215C, 0x2074, 0x2084, 0x2463, 0x1F105, 0x248B, 0x32B5, 0x32B6, 0x32B7, 0x32B8, 0x32B9, 0x32BA, 0x32BB, 0x32BC, 0x32BD,
+ 0x32BE, 0x2158, 0x2075, 0x2085, 0x2464, 0x1F106, 0x248C, 0x32BF, 0x215A, 0x215D, 0x2076, 0x2086, 0x2465, 0x1F107, 0x248D, 0x2077,
+ 0x2087, 0x2466, 0x1F108, 0x248E, 0x215E, 0x2078, 0x2088, 0x2467, 0x1F109, 0x248F, 0x2079, 0x2089, 0x2468, 0x1F10A, 0x2490};
+ textNormalizationPropertyDictionary.Add("othernumber", othernumber);
+
+ int [] kaithi = {0x1109A, 0x1109C, 0x110AB};
+ textNormalizationPropertyDictionary.Add("kaithi", kaithi);
+
+ int [] balinese = {0x1B06, 0x1B08, 0x1B0A, 0x1B0C, 0x1B0E, 0x1B12, 0x1B3B, 0x1B3D, 0x1B40, 0x1B41, 0x1B43};
+ textNormalizationPropertyDictionary.Add("balinese", balinese);
+
+ int [] tifinagh = {0x2D6F};
+ textNormalizationPropertyDictionary.Add("tifinagh", tifinagh);
+
+ int [] hiragana = {0x3094, 0x304C, 0x304E, 0x3050, 0x3052, 0x3054, 0x3056, 0x3058, 0x305A, 0x305C, 0x305E, 0x3060, 0x3062, 0x3065,
+ 0x3067, 0x3069, 0x3070, 0x3071, 0x3073, 0x3074, 0x3076, 0x3077, 0x3079, 0x307A, 0x1F200, 0x307C, 0x307D, 0x309F, 0x309E};
+ textNormalizationPropertyDictionary.Add("hiragana", hiragana);
+
+ int [] georgian = {0x10FC};
+ textNormalizationPropertyDictionary.Add("georgian", georgian);
+
+ int [] myanmar = {0x1026};
+ textNormalizationPropertyDictionary.Add("myanmar", myanmar);
+
+ int [] tibetan = {0x0F0C, 0x0F69, 0x0F43, 0x0F4D, 0x0F52, 0x0F57, 0x0F5C, 0x0F73, 0x0F75, 0x0F81, 0x0FB9, 0x0F93, 0x0F9D, 0x0FA2, 0x0FA7,
+ 0x0FAC, 0x0F77, 0x0F76, 0x0F79, 0x0F78};
+ textNormalizationPropertyDictionary.Add("tibetan", tibetan);
+
+ int [] lao = {0x0EDC, 0x0EDD, 0x0EB3};
+ textNormalizationPropertyDictionary.Add("lao", lao);
+
+ int [] th = {0x0E33};
+ textNormalizationPropertyDictionary.Add("th", th);
+
+ int [] sinhala = {0x0DDA, 0x0DDC, 0x0DDD, 0x0DDE};
+ textNormalizationPropertyDictionary.Add("sinhala", sinhala);
+
+ int [] malayalam = {0x0D4A, 0x0D4C, 0x0D4B};
+ textNormalizationPropertyDictionary.Add("malayalam", malayalam);
+
+ int [] kannada = {0x0CC0, 0x0CCA, 0x0CCB, 0x0CC7, 0x0CC8};
+ textNormalizationPropertyDictionary.Add("kannada", kannada);
+
+ int [] telugu = {0x0C48};
+ textNormalizationPropertyDictionary.Add("telugu", telugu);
+
+ int [] ta = {0x0B94, 0x0BCA, 0x0BCC, 0x0BCB};
+ textNormalizationPropertyDictionary.Add("ta", ta);
+
+ int [] oriya = {0x0B5C, 0x0B5D, 0x0B4B, 0x0B48, 0x0B4C};
+ textNormalizationPropertyDictionary.Add("oriya", oriya);
+
+ int [] gurmukhi = {0x0A59, 0x0A5A, 0x0A5B, 0x0A5E, 0x0A33, 0x0A36};
+ textNormalizationPropertyDictionary.Add("gurmukhi", gurmukhi);
+
+ int [] bengali = {0x09DC, 0x09DD, 0x09DF, 0x09CB, 0x09CC};
+ textNormalizationPropertyDictionary.Add("bengali", bengali);
+
+ int [] devanagari = {0x0958, 0x0959, 0x095A, 0x095B, 0x095C, 0x095D, 0x0929, 0x095E, 0x095E, 0x0931, 0x0934};
+ textNormalizationPropertyDictionary.Add("devanagari", devanagari);
+
+ int [] he = {0x2135, 0xFB21, 0xFB2E, 0xFB2F, 0xFB30, 0xFB4F, 0x2136, 0xFB31, 0xFB4C, 0x2137, 0xFB32, 0x2138, 0xFB22, 0xFB33, 0xFB23, 0xFB34,
+ 0xFB4B, 0xFB35, 0xFB36, 0xFB38, 0xFB1D, 0xFB39, 0xFB3A, 0xFB24, 0xFB3B, 0xFB4D, 0xFB25, 0xFB3C, 0xFB26, 0xFB3E, 0xFB40, 0xFB41, 0xFB20,
+ 0xFB43, 0xFB44, 0xFB4E, 0xFB46, 0xFB47, 0xFB27, 0xFB48, 0xFB49, 0xFB2C, 0xFB2D, 0xFB2D, 0xFB2B, 0xFB28, 0xFB4A, 0xFB1F};
+ textNormalizationPropertyDictionary.Add("he", he);
+
+ int [] hy = {0x0587, 0xFB14, 0xFB15, 0xFB17, 0xFB13, 0xFB16};
+ textNormalizationPropertyDictionary.Add("hy", hy);
+
+ int [] cyrillic = {0x04D0, 0x04D1, 0x04D2, 0x04D3, 0x0403, 0x0453, 0x0400, 0x0450, 0x04D6, 0x04D7, 0x0401, 0x0451, 0x04C1, 0x04C2, 0x04DC,
+ 0x04DD, 0x04DE, 0x04DF, 0x040D, 0x045D, 0x04E2, 0x04E3, 0x0419, 0x0439, 0x04E4, 0x04E5, 0x040C, 0x045C, 0x1D78, 0x04E6, 0x04E7, 0x04EE,
+ 0xFB20, 0x04EF, 0x040E, 0x045E, 0x04F0, 0x04F1, 0x04F2, 0x04F3, 0x04F4, 0x04F5, 0x04F6, 0x04F7, 0x04F8, 0x04F9, 0x04EC, 0x04ED, 0x0407,
+ 0x0457, 0x0476, 0x0477, 0x04DA, 0x04DB, 0x04EA, 0x04EB};
+ textNormalizationPropertyDictionary.Add("cyrillic", cyrillic);
+
+ int i = 0;
+ bool isValid = false;
+ codePointsWithDifferentNormalizationForms = new int [othersymbols.Length + modifiersymbols.Length + currencysymbols.Length + mathsymbols.Length +
+ modifierletter.Length + otherletter.Length + nonspacingmark.Length + spaceseparator.Length + decimalnumber.Length + othernumber.Length +
+ kaithi.Length + balinese.Length + tifinagh.Length + hiragana.Length + georgian.Length + myanmar.Length + tibetan.Length + lao.Length +
+ th.Length + sinhala.Length + malayalam.Length + kannada.Length + telugu.Length + ta.Length + oriya.Length + gurmukhi.Length + bengali.Length
+ + devanagari.Length + he.Length + hy.Length + cyrillic.Length];
+
+ Dictionary.ValueCollection valueColl = textNormalizationPropertyDictionary.Values;
+ foreach (int [] values in valueColl)
+ {
+ foreach (int codePoint in values)
+ {
+ foreach (UnicodeRange range in expectedRanges)
+ {
+ if (codePoint >= range.StartOfUnicodeRange && codePoint <= range.EndOfUnicodeRange)
+ {
+ codePointsWithDifferentNormalizationForms[i++] = codePoint;
+ isValid = true;
+ }
+ }
+ }
+ }
+ Array.Resize(ref codePointsWithDifferentNormalizationForms, i);
+ Array.Sort(codePointsWithDifferentNormalizationForms);
+ return isValid;
+ }
+
+ ///
+ /// Check if code point is in the property range
+ ///
+ public bool IsInPropertyRange(int codePoint)
+ {
+ bool isIn = false;
+ foreach (UnicodeRangeProperty prop in textNormalizationRangeList)
+ {
+ if (codePoint >= prop.Range.StartOfUnicodeRange && codePoint <= prop.Range.EndOfUnicodeRange)
+ {
+ isIn = true;
+ break;
+ }
+ }
+
+ return isIn;
+ }
+
+ ///
+ /// Get random normalizeable code points
+ ///
+ public string GetRandomCodePoints(int numOfProperty, int seed)
+ {
+ if (numOfProperty < 1)
+ {
+ throw new ArgumentOutOfRangeException(
+ "TextNormalizationProperty, numOfProperty, " + numOfProperty + " cannot be less than one.");
+ }
+
+ string textNormalizationStr = string.Empty;
+ string numStr = string.Empty;
+ Random rand = new Random(seed);
+ for (int i= 0; i < numOfProperty; i++)
+ {
+ int index = rand.Next(0, codePointsWithDifferentNormalizationForms.Length);
+ textNormalizationStr += TextUtil.IntToString(codePointsWithDifferentNormalizationForms[index]);
+ }
+
+ return textNormalizationStr;
+ }
+ }
+}
+
+
+
+
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/TextSegmentationProperty.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/TextSegmentationProperty.cs
new file mode 100644
index 0000000..5878098
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/TextSegmentationProperty.cs
@@ -0,0 +1,247 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// Collect of text segmentation code points
+ ///
+ internal class TextSegmentationProperty : IStringProperty
+ {
+ ///
+ /// Dictionary to store code points corresponding to culture.
+ ///
+ private Dictionary sampleGraphemeClusterDictionary = new Dictionary();
+ private Dictionary graphemeClusterBreakPropertyValuesDictionary = new Dictionary();
+ private Dictionary wordBreakPropertyValuesDictionary = new Dictionary();
+ private Dictionary sentenceBreakPropertyValuesDictionary = new Dictionary();
+
+ private List textSegmentationRangeList = new List();
+
+ private int [] textSegmentationCodePoints;
+
+ ///
+ /// Define minimum code point needed to be a text segmentation string
+ ///
+ public static readonly int MINNUMOFCODEPOINT = 1;
+
+ ///
+ /// Define LineBreakDictionary class,
+ /// Newline
+ ///
+ public TextSegmentationProperty(UnicodeRangeDatabase unicodeDb, Collection expectedRanges)
+ {
+ bool isValid = false;
+
+ foreach (UnicodeRange range in expectedRanges)
+ {
+ if (RangePropertyCollector.BuildPropertyDataList(
+ unicodeDb,
+ range,
+ textSegmentationRangeList,
+ "Controls",
+ GroupAttributes.Name))
+ {
+ isValid = true;
+ }
+ }
+
+ if (InitializeDictionaries(expectedRanges))
+ {
+ isValid = true;
+ }
+
+ if (!isValid)
+ {
+ throw new ArgumentOutOfRangeException("expectedRanges", "TextSegmentationProperty, " +
+ "code points for text segmentation ranges are beyond expected range. " + "Refert to Controls ranges");
+ }
+ }
+
+ private bool InitializeDictionaries(Collection expectedRanges)
+ {
+ char [] ko = {'\u1100', '\u1161', '\u11A8'};
+ sampleGraphemeClusterDictionary.Add("Ko", ko);
+ char [] ta = {'\u0BA8', '\u0BBF'};
+ sampleGraphemeClusterDictionary.Add("ta", ta);
+ char [] th = {'\u0E40', '\u0E01'};
+ sampleGraphemeClusterDictionary.Add("th", th);
+ char [] devanagari = {'\u0937', '\u093F', '\u0915', '\u094D', '\u0937', '\u093F'};
+ sampleGraphemeClusterDictionary.Add("devanagari", devanagari);
+ char [] sk = {'\u0063', '\u0068'};
+ sampleGraphemeClusterDictionary.Add("sk", sk);
+ char [] other = {'\u0067', '\u0308', '\u006B', '\u02B7'};
+ sampleGraphemeClusterDictionary.Add("other", other);
+
+ char [] all = {'\u000D', '\u000A', '\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007', '\u0008', '\u0009', '\u000B',
+ '\u000C', '\u000E', '\u000F', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001A',
+ '\u001B', '\u001C', '\u001D', '\u001E', '\u001F', '\u0020', '\u007F', '\u0080', '\u0081', '\u0082', '\u0083', '\u0084', '\u0085', '\u0086',
+ '\u0087', '\u0088', '\u0089', '\u008A', '\u008B', '\u008C', '\u008D', '\u008E', '\u008F', '\u0090', '\u0091', '\u0092', '\u0093', '\u0094',
+ '\u0095', '\u0096', '\u0097', '\u0098', '\u0099', '\u009A', '\u009B', '\u009C', '\u009D', '\u009E', '\u009F', '\u00A0', '\u00AD', '\u2000',
+ '\u2001', '\u2002', '\u2003', '\u2004', '\u2005', '\u2006', '\u2007', '\u2008', '\u2009', '\u200A'};
+ graphemeClusterBreakPropertyValuesDictionary.Add("all", all);
+ char [] th1 = {'\u0E30', '\u0E32', '\u0E33', '\u0E40', '\u0E41', '\u0E42', '\u0E43', '\u0E44', '\u0E45'};
+ graphemeClusterBreakPropertyValuesDictionary.Add("th", th1);
+ char [] lao = {'\u0EB0', '\u0EB2', '\u0EB3', '\u0EC0', '\u0EC1', '\u0EC2', '\u0EC3', '\u0EC4'};
+ graphemeClusterBreakPropertyValuesDictionary.Add("lao", lao);
+ char [] ko1 = {'\u1100', '\u1101', '\u1102', '\u1103', '\u1104', '\u1105', '\u1106', '\u1107', '\u1108', '\u1109', '\u110A', '\u110B', '\u110C',
+ '\u110D', '\u110E', '\u110F', '\u1110', '\u1111', '\u1112', '\u1113', '\u1114', '\u1115', '\u1116', '\u1117', '\u1118', '\u1119', '\u111A',
+ '\u111B', '\u111C', '\u111D', '\u111E', '\u111F', '\u1120', '\u1121', '\u1122', '\u1123', '\u1124', '\u1125', '\u1126', '\u1127', '\u1128',
+ '\u1129', '\u112A', '\u112B', '\u112C', '\u112D', '\u112E', '\u112F', '\u1130', '\u1131', '\u1132', '\u1133', '\u1134', '\u1135', '\u1136',
+ '\u1137', '\u1138', '\u1139', '\u1140', '\u1141', '\u1142', '\u1143', '\u1144', '\u1145', '\u1146', '\u1147', '\u1148', '\u1149', '\u114A',
+ '\u114B', '\u114C', '\u114D', '\u114E', '\u114F', '\u1150', '\u1151', '\u1152', '\u1153', '\u1154', '\u1155', '\u1156', '\u1157', '\u1158',
+ '\u1159', '\u111F', '\u1160', '\u1161', '\u1162', '\u1163', '\u1164', '\u1165', '\u1166', '\u1167', '\u1168', '\u1169', '\u116A', '\u116B',
+ '\u116C', '\u116D', '\u116E', '\u116F', '\u1170', '\u1171', '\u1172', '\u1173', '\u1174', '\u1175', '\u1176', '\u1177', '\u1178', '\u1179',
+ '\u117A', '\u117B', '\u117C', '\u117D', '\u117E', '\u117F', '\u1180', '\u1181', '\u1182', '\u1183', '\u1184', '\u1185', '\u1186', '\u1187',
+ '\u1188', '\u1189', '\u118A', '\u118B', '\u118C', '\u118D', '\u118E', '\u118F', '\u1190', '\u1191', '\u1192', '\u1193', '\u1194', '\u1195',
+ '\u1196', '\u1197', '\u1198', '\u1199', '\u119A', '\u119B', '\u119C', '\u119E', '\u119F', '\u11A0', '\u11A1', '\u11A2', '\u11A8', '\u11A9',
+ '\u11AA', '\u11AB', '\u11AC', '\u11AD', '\u11AE', '\u11AF', '\u11B0', '\u11B1', '\u11B2', '\u11B3', '\u11B4', '\u11B5', '\u11B6', '\u11B7',
+ '\u11B8', '\u11B9', '\u11BA', '\u11BB', '\u11BC', '\u11BD', '\u11BE', '\u11BF', '\u11C0', '\u11C1', '\u11C2', '\u11C3', '\u11C4', '\u11C5',
+ '\u11C6', '\u11C7', '\u11C8', '\u11C9', '\u11CA', '\u11CB', '\u11CC', '\u11CE', '\u11CF', '\u11D0', '\u11D1', '\u11D2', '\u11D3', '\u11D4',
+ '\u11D5', '\u11D6', '\u11D7', '\u11D8', '\u11D9', '\u11DA', '\u11DB', '\u11DC', '\u11DE', '\u11DF', '\u11F0', '\u11F1', '\u11F2', '\u11F3',
+ '\u11F4', '\u11F5', '\u11F7', '\u11F8', '\u11F9', '\uAC00', '\uAC1C', '\uAC38', '\uAC01', '\uAC02', '\uAC03', '\uAc04'};
+ graphemeClusterBreakPropertyValuesDictionary.Add("ko", ko1);
+
+ char [] all1 = {'\u000A', '\u000D', '\u000B', '\u000C', '\u0020', '\u0027', '\u0085', '\u002D', '\u002E', '\u202F','\u00A0', '\u2028', '\u2029',
+ '\u2000', '\u2001', '\u2002', '\u2003', '\u2004', '\u2005', '\u2006', '\u2007', '\u2008', '\u2009', '\u200A', '\u2010', '\u2011', '\u2018',
+ '\u2019', '\u201B', '\u2024', '\uFE52', '\uFF07', '\uFF0E', '\u00B7', '\u05F4', '\u2027', '\u003A', '\u0387', '\uFE13', '\uFE55', '\uFF1A',
+ '\u066C', '\uFE50', '\uFE54', '\uFE63', '\uFF0D', '\uFF0C', '\uFF1B'};
+ wordBreakPropertyValuesDictionary.Add("all", all1);
+ char [] katakana = {'\u3031', '\u3032', '\u3033', '\u3034', '\u3035', '\u309B', '\u309C', '\u30A0', '\u30FC', '\uFF70'};
+ wordBreakPropertyValuesDictionary.Add("ja", katakana);
+ char [] he = {'\u05F3'};
+ wordBreakPropertyValuesDictionary.Add("he", he);
+ char [] hy = {'\u055A', '\u058A'};
+ wordBreakPropertyValuesDictionary.Add("hy", hy);
+ char [] tibet = {'\u0F0B'};
+ wordBreakPropertyValuesDictionary.Add("tibet", tibet);
+ char [] mongolia = {'\u1806'};
+ wordBreakPropertyValuesDictionary.Add("mongolia", mongolia);
+
+ char [] all2 = {'\u000A', '\u000D', '\u0085', '\u00A0', '\u05F3', '\u2000', '\u2001', '\u2002', '\u2003', '\u2004', '\u2005', '\u2006', '\u2007',
+ '\u2008', '\u2009', '\u200A', '\u2028', '\u2029', '\u002E', '\u2024', '\uFE52', '\uFF0E', '\u002D', '\u003A', '\u055D', '\u060C', '\u060D',
+ '\u07F8', '\u1802', '\u1808', '\u2013', '\u2014', '\u3001', '\uFE10', '\uFE11', '\uFE13', '\uFE31', '\uFE32', '\uFE50', '\uFE51', '\uFE55',
+ '\uFE58', '\uFE63', '\uFF0C', '\uFF0D', '\uFF1A', '\uFF64'};
+ sentenceBreakPropertyValuesDictionary.Add("all", all2);
+
+ bool isValid = false;
+ int i = 0;
+ textSegmentationCodePoints = new int [ko.Length + ta.Length + th.Length + devanagari.Length + sk.Length + other.Length + all.Length + th1.Length
+ + lao.Length + ko1.Length + all1.Length + katakana.Length + he.Length + hy.Length + tibet.Length + mongolia.Length + all2.Length];
+
+ Dictionary.ValueCollection valueColl1 = sampleGraphemeClusterDictionary.Values;
+ foreach (char [] values in valueColl1)
+ {
+ foreach (char codePoint in values)
+ {
+ foreach (UnicodeRange range in expectedRanges)
+ {
+ if (codePoint >= range.StartOfUnicodeRange && codePoint <= range.EndOfUnicodeRange)
+ {
+ textSegmentationCodePoints[i++] = (int)codePoint;
+ isValid = true;
+ }
+ }
+ }
+ }
+
+ Dictionary.ValueCollection valueColl2 = graphemeClusterBreakPropertyValuesDictionary.Values;
+ foreach (char [] values in valueColl2)
+ {
+ foreach (char codePoint in values)
+ {
+ foreach (UnicodeRange range in expectedRanges)
+ {
+ if (codePoint >= range.StartOfUnicodeRange && codePoint <= range.EndOfUnicodeRange)
+ {
+ textSegmentationCodePoints[i++] = (int)codePoint;
+ isValid = true;
+ }
+ }
+ }
+ }
+
+ Dictionary.ValueCollection valueColl3 = wordBreakPropertyValuesDictionary.Values;
+ foreach(char [] values in valueColl3)
+ {
+ foreach (char codePoint in values)
+ {
+ foreach (UnicodeRange range in expectedRanges)
+ {
+ if (codePoint >= range.StartOfUnicodeRange && codePoint <= range.EndOfUnicodeRange)
+ {
+ textSegmentationCodePoints[i++] = (int)codePoint;
+ isValid = true;
+ }
+ }
+ }
+ }
+
+ Dictionary.ValueCollection valueColl4 = sentenceBreakPropertyValuesDictionary.Values;
+ foreach(char [] values in valueColl4)
+ {
+ foreach (char codePoint in values)
+ {
+ foreach (UnicodeRange range in expectedRanges)
+ {
+ if (codePoint >= range.StartOfUnicodeRange && codePoint <= range.EndOfUnicodeRange)
+ {
+ textSegmentationCodePoints[i++] = (int)codePoint;
+ isValid = true;
+ }
+ }
+ }
+ }
+ Array.Resize(ref textSegmentationCodePoints, i);
+ Array.Sort(textSegmentationCodePoints);
+
+ return isValid;
+ }
+
+ ///
+ /// Check if code point is in the property range
+ ///
+ public bool IsInPropertyRange(int codePoint)
+ {
+ bool isIn = false;
+ foreach (UnicodeRangeProperty prop in textSegmentationRangeList)
+ {
+ if (codePoint >= prop.Range.StartOfUnicodeRange && codePoint <= prop.Range.EndOfUnicodeRange)
+ {
+ isIn = true;
+ break;
+ }
+ }
+
+ return isIn;
+ }
+
+ ///
+ /// Get number code points
+ ///
+ public string GetRandomCodePoints(int numOfProperty, int seed)
+ {
+ if (numOfProperty < 1)
+ {
+ throw new ArgumentOutOfRangeException(
+ "TextSegmentationProperty, numOfProperty, " + numOfProperty + " cannot be less than one.");
+ }
+
+ string textSegmentationStr = string.Empty;
+ Random rand = new Random(seed);
+ for (int i= 0; i < numOfProperty; i++)
+ {
+ int index = rand.Next(0, textSegmentationCodePoints.Length);
+ textSegmentationStr += TextUtil.IntToString(textSegmentationCodePoints[index]);
+ }
+
+ return textSegmentationStr;
+ }
+ }
+}
+
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/TextUtil.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/TextUtil.cs
new file mode 100644
index 0000000..a2243ba
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/TextUtil.cs
@@ -0,0 +1,424 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Globalization;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// A collection of const, enum, and struc declaration and static utility function
+ ///
+ internal static class TextUtil
+ {
+ ///
+ /// Define number of scripts in Unicode
+ /// Newline
+ ///
+ public static readonly int NUMOFSCRIPTS = 103;
+
+ ///
+ /// Define number of symbols and punctuation in Unicode
+ /// Newline
+ ///
+ public static readonly int NUMOFSYMBOLSANDPUNCTUATION = 44;
+
+ ///
+ /// Maximum Unicode Point value
+ /// Newline
+ ///
+ public static readonly int MaxUnicodePoint = 0x10FFFF;
+
+ ///
+ /// Maximum number of code points defined for a string to be generated
+ ///
+ public static readonly int MAXNUMOFCODEPOINT = 300;
+
+ ///
+ /// Maximum number iteration used in while loop to guard from infinite loops.
+ ///
+ public static readonly int MAXNUMITERATION = 128;
+
+ ///
+ /// Defined ids to help identifying which ----/region where the script or symbol is used. If id is defined in LCID,
+ /// LCID is used. Otherwise, spell the full name in lower case. '-' is used to omitted.
+ /// Newline
+ ///
+ public enum CultureIds
+ {
+ ///
+ /// Null - don't care
+ ///
+ Null = 0,
+
+ ///
+ /// Can be used in any content
+ ///
+ any,
+
+ ///
+ /// Arabic ----
+ ///
+ ar,
+
+ ///
+ /// Azerbaijani - Cyrillic
+ ///
+ azaz,
+
+ ///
+ /// Bangladesh
+ ///
+ bangladesh,
+
+ ///
+ /// Canada
+ ///
+ ca,
+
+ ///
+ /// Cambodia
+ ///
+ cambodia,
+
+ ///
+ /// Cameroon
+ ///
+ cameroon,
+
+ ///
+ /// Carians
+ ///
+ carians,
+
+ ///
+ /// Cuneiform
+ ///
+ cuneiform,
+
+ ///
+ /// Cyprus
+ ///
+ cyprus,
+
+ ///
+ /// German
+ ///
+ de,
+
+ ///
+ /// Egypt
+ ///
+ eg,
+
+ ///
+ /// Greece
+ ///
+ el,
+
+ ///
+ /// English speaking ----
+ ///
+ en,
+
+ ///
+ /// Ethiopia
+ ///
+ ethiopia,
+
+ ///
+ /// Georgia
+ ///
+ georgia,
+
+ ///
+ /// Glagolitsa
+ ///
+ glagolitsa,
+
+ ///
+ /// Israel
+ ///
+ he,
+
+ ///
+ /// India
+ ///
+ hi,
+
+ ///
+ /// Armenia
+ ///
+ hy,
+
+ ///
+ /// Indonesia
+ ///
+ id,
+
+ ///
+ /// Ireland
+ ///
+ ie,
+
+ ///
+ /// Iran /Percian
+ ///
+ iran,
+
+ ///
+ /// Japan
+ ///
+ ja,
+
+ ///
+ /// Kharoshthi
+ ///
+ kharoshthi,
+
+ ///
+ /// Korea
+ ///
+ ko,
+
+ ///
+ /// Lao
+ ///
+ lao,
+
+ ///
+ /// Latin
+ ///
+ latin,
+
+ ///
+ /// Lycian
+ ///
+ lycia,
+
+ ///
+ /// Maldives
+ ///
+ maldives,
+
+ ///
+ /// Monglian
+ ///
+ mongolia,
+
+ ///
+ /// Myanmar
+ ///
+ myanmar,
+
+ ///
+ /// Nepal
+ ///
+ nepal,
+
+ ///
+ /// N'KO
+ ///
+ nko,
+
+ ///
+ /// Turkic ancient form
+ ///
+ oldturkic,
+
+ ///
+ /// Not classified
+ ///
+ other,
+
+ ///
+ /// Phillippines
+ ///
+ ph,
+
+ ///
+ /// Phaistos
+ ///
+ phaistosdisc,
+
+ ///
+ /// Phoenician
+ ///
+ phoenicia,
+
+ ///
+ /// Samaritan
+ ///
+ samaria,
+
+ ///
+ /// Singapore
+ ///
+ singapore,
+
+ ///
+ /// Somalia
+ ///
+ somalia,
+
+ ///
+ /// Sri Lanka
+ ///
+ srilanka,
+
+ ///
+ /// Serbian - Cyrillic
+ ///
+ srsp,
+
+ ///
+ /// Syloti
+ ///
+ sylotinagri,
+
+ ///
+ /// Syriac scripts
+ ///
+ syriac,
+
+ ///
+ /// Thailand
+ ///
+ th,
+
+ ///
+ /// Tifinagh
+ ///
+ tifinagh,
+
+ ///
+ /// US native languages
+ ///
+ us,
+
+ ///
+ /// Uzbek - Cyrillic
+ ///
+ uzuz,
+
+ ///
+ /// Vai
+ ///
+ vai,
+
+ ///
+ /// Vietnam
+ ///
+ vi,
+
+ ///
+ /// Chinese
+ ///
+ zh,
+
+ ///
+ /// Chinese ----
+ ///
+ zhtw
+ }
+
+ ///
+ /// Unicode character code chart types
+ ///
+ public enum UnicodeChartType
+ {
+ ///
+ /// Unicode character code chart types is Script
+ ///
+ Script=1,
+
+ ///
+ /// Unicode character code chart types is Symbol
+ ///
+ Symbol,
+
+ ///
+ /// Unicode character code chart types is Punctuation
+ ///
+ Punctuation,
+
+ ///
+ /// Unicode character code chart types is Other than three types above
+ ///
+ Other
+ }
+
+ ///
+ /// Get a random Unicode point (points if it is Surrogate) from the given range
+ ///
+ public static string GetRandomCodePoint(UnicodeRange range, int iterations, int [] exclusions, int seed)
+ {
+ Random rand = new Random(seed);
+ int codePoint = 0;
+ string retStr = string.Empty;
+
+ if (null != exclusions)
+ {
+ Array.Sort(exclusions);
+ }
+
+ for (int i=0; i < iterations; i++)
+ {
+ codePoint = rand.Next(range.StartOfUnicodeRange, range.EndOfUnicodeRange);
+ if (null != exclusions)
+ {
+ int index = Array.BinarySearch(exclusions, codePoint);
+ int ctr = 0;
+ while (index >= 0)
+ {
+ codePoint = rand.Next(range.StartOfUnicodeRange, range.EndOfUnicodeRange);
+ index = Array.BinarySearch(exclusions, codePoint);
+ ctr ++;
+ if (MAXNUMITERATION == ctr)
+ {
+ throw new ArgumentOutOfRangeException("TextUtil, " + ctr + " loop has been reached. GetRandomCodePoint may have infinite loop." +
+ " Range " + String.Format(CultureInfo.InvariantCulture, "0x{0:X}", range.StartOfUnicodeRange) + " - " +
+ String.Format(CultureInfo.InvariantCulture,"0x{0:X}", range.EndOfUnicodeRange) + " are likely excluded ");
+ }
+ }
+ }
+
+ if (codePoint > 0xFFFF)
+ {
+ // In case it is surrogate
+ retStr += Convert.ToChar((codePoint - 0x10000)/0x400 + 0xD800);
+ retStr += Convert.ToChar((codePoint - 0x10000)%0x400 + 0xDC00);
+ }
+ else
+ {
+ retStr += Convert.ToChar(codePoint);
+ }
+ }
+
+ return retStr;
+ }
+
+ ///
+ /// Convert int to string
+ ///
+ public static string IntToString(int codePoint)
+ {
+ string retStr = string.Empty;
+
+ if (codePoint > 0xFFFF)
+ {
+ // In case it is surrogate
+ retStr += Convert.ToChar((codePoint - 0x10000)/0x400 + 0xD800);
+ retStr += Convert.ToChar((codePoint - 0x10000)%0x400 + 0xDC00);
+ }
+ else
+ {
+ retStr += Convert.ToChar(codePoint);
+ }
+
+ return retStr;
+ }
+ }
+}
+
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/UnicodeChart.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/UnicodeChart.cs
new file mode 100644
index 0000000..99bea3b
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/UnicodeChart.cs
@@ -0,0 +1,1136 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// Specifies a Unicode character code chart.
+ ///
+ ///
+ ///
+ /// The Unicode standard defines a number of different character subsets, which are called
+ /// Unicode character code charts (or Unicode charts for short). These charts are available
+ /// on http://unicode.org/charts. The charts divide and categorize
+ /// all symbols available in the Unicode range (0x0000 - 0x10FFFF) according to their common characteristics.
+ ///
+ public enum UnicodeChart
+ {
+ ///
+ /// Additional Arrows Chart
+ ///
+ AdditionalArrows,
+ ///
+ /// Additional Shapes Chart
+ ///
+ AdditionalShapes,
+ ///
+ /// Additional Squared Symbols Chart
+ ///
+ AdditionalSquaredSymbols,
+ ///
+ /// Aegean Numbers Chart
+ ///
+ AegeanNumbers,
+ ///
+ /// Alphabetic Presentation Forms Chart
+ ///
+ AlphabeticPresentationForms,
+ ///
+ /// Ancient Greek Musical Notation Chart
+ ///
+ AncientGreekMusicalNotation,
+ ///
+ /// Ancient Greek Numbers Chart
+ ///
+ AncientGreekNumbers,
+ ///
+ /// Ancient Symbols Chart
+ ///
+ AncientSymbols,
+ ///
+ /// APL symbols Chart
+ ///
+ AplSymbols,
+ ///
+ /// Arabic Chart
+ ///
+ Arabic,
+ ///
+ /// Arabic Presentation Forms-A Chart
+ ///
+ ArabicPresentationFormsA,
+ ///
+ /// Arabic Presentation Forms-B Chart
+ ///
+ ArabicPresentationFormsB,
+ ///
+ /// Arabic Supplement Chart
+ ///
+ ArabicSupplement,
+ ///
+ /// Aramaic Imperial Chart
+ ///
+ AramaicImperial,
+ ///
+ /// Armenian Chart
+ ///
+ Armenian,
+ ///
+ /// Armenian Ligatures Chart
+ ///
+ ArmenianLigatures,
+ ///
+ /// Arrows Chart
+ ///
+ Arrows,
+ ///
+ /// ASCII Characters Chart
+ ///
+ AsciiCharacters,
+ ///
+ /// ASCII Digits Chart
+ ///
+ AsciiDigits,
+ ///
+ /// ASCII Punctuation Chart
+ ///
+ AsciiPunctuation,
+ ///
+ /// Same as BMP Chart
+ ///
+ AtEndOf,
+ ///
+ /// Avestan Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Avestan,
+ ///
+ /// Balinese Chart
+ ///
+ Balinese,
+ ///
+ /// Bamum Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Bamum,
+ ///
+ /// Basic operators Division Multiplication Chart
+ ///
+ BasicOperatorsDivisionMultiplication,
+ ///
+ /// Basic operators Plus Factorial Chart
+ ///
+ BasicOperatorsPlusFactorial,
+ ///
+ /// Bengali Chart
+ ///
+ Bengali,
+ ///
+ /// Block Elements Chart
+ ///
+ BlockElements,
+ ///
+ /// BMP Chart
+ ///
+ Bmp,
+ ///
+ /// Bopomofo Chart
+ ///
+ Bopomofo,
+ ///
+ /// Bopomofo Extended Chart
+ ///
+ BopomofoExtended,
+ ///
+ /// Box Drawing Chart
+ ///
+ BoxDrawing,
+ ///
+ /// Braille Patterns Chart
+ ///
+ BraillePatterns,
+ ///
+ /// Buginese Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Buginese,
+ ///
+ /// Buhid Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Buhid,
+ ///
+ /// Byzantine Musical Symbols Chart
+ ///
+ ByzantineMusicalSymbols,
+ ///
+ /// C0 Chart
+ ///
+ C0,
+ ///
+ /// C1 Chart
+ ///
+ C1,
+ ///
+ /// Card suits Chart
+ ///
+ CardSuits,
+ ///
+ /// Carian Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Carian,
+ ///
+ /// Cham Chart
+ ///
+ Cham,
+ ///
+ /// Cherokee Chart
+ ///
+ Cherokee,
+ ///
+ /// Chess/Checkers Chart
+ ///
+ ChessCheckers,
+ ///
+ /// CJK Compatibility Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ CjkCompatibility,
+ ///
+ /// CJK Compatibility Forms Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ CjkCompatibilityForms,
+ ///
+ /// CJK Compatibility Ideographs Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ CjkCompatibilityIdeographs,
+ ///
+ /// CJK Compatibility Ideographs Supplement Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ CjkCompatibilityIdeographsSupplement,
+ ///
+ /// CJK ExtensionA Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ CjkExtensionA,
+ ///
+ /// CJK Extension-B Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ CjkExtensionB,
+ ///
+ /// CJK Extension-C Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ CjkExtensionC,
+ ///
+ /// CJK Radicals / KangXi Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709")]
+ CjkKangXiRadicals,
+ ///
+ /// CJK Radicals Supplement
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ CjkRadicalsSupplement,
+ ///
+ /// CJK Strokes Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ CjkStrokes,
+ ///
+ /// CJK Symbols and Punctuation Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ CjkSymbolsAndPunctuation,
+ ///
+ /// CJK Unified Ideographs Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ CjkUnifiedIdeographs,
+ ///
+ /// Combining Diacritical Marks Chart
+ ///
+ CombiningDiacriticalMarks,
+ ///
+ /// Combining Diacritical Marks for Symbols Chart
+ ///
+ CombiningDiacriticalMarksForSymbols,
+ ///
+ /// Combining Diacritical Marks Supplement Chart
+ ///
+ CombiningDiacriticalMarksSupplement,
+ ///
+ /// Combining HalfMarks Chart
+ ///
+ CombiningHalfMarks,
+ ///
+ /// Common Indic Number Forms Chart
+ ///
+ CommonIndicNumberForms,
+ ///
+ /// Control Pictures Chart
+ ///
+ ControlPictures,
+ ///
+ /// C0 and C1 Chart
+ ///
+ Controls,
+ ///
+ /// Coptic Chart
+ ///
+ Coptic,
+ ///
+ /// Coptic in Greek Block Chart
+ ///
+ CopticInGreekBlock,
+ ///
+ /// Counting Rod Numerals Chart
+ ///
+ CountingRodNumerals,
+ ///
+ /// Cuneiform Chart
+ ///
+ Cuneiform,
+ ///
+ /// Cuneiform Numbers and Punctuation Chart
+ ///
+ CuneiformNumbersAndPunctuation,
+ ///
+ /// Currency Symbols Chart
+ ///
+ CurrencySymbols,
+ ///
+ /// Cypriot Syllabary Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ CypriotSyllabary,
+ ///
+ /// Cyrillic Chart
+ ///
+ Cyrillic,
+ ///
+ /// Cyrillic Extended-A Chart
+ ///
+ CyrillicExtendedA,
+ ///
+ /// Cyrillic Extended-B Chart
+ ///
+ CyrillicExtendedB,
+ ///
+ /// Cyrillic Supplement Chart
+ ///
+ CyrillicSupplement,
+ ///
+ /// Deseret Chart
+ ///
+ Deseret,
+ ///
+ /// Devanagari Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Devanagari,
+ ///
+ /// Devanagari Extended Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ DevanagariExtended,
+ ///
+ /// Dingbats Chart
+ ///
+ Dingbats,
+ ///
+ /// Dollar Sign Chart
+ ///
+ DollarSign,
+ ///
+ /// Domino Tiles Chart
+ ///
+ DominoTiles,
+ ///
+ /// Egyptian Hieroglyphs Chart
+ ///
+ EgyptianHieroglyphs,
+ ///
+ /// Enclosed Alphanumerics Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ EnclosedAlphanumerics,
+ ///
+ /// Enclosed Alphanumeric Supplement Chart
+ ///
+ EnclosedAlphanumericSupplement,
+ ///
+ /// Enclosed CJK Letters and Months Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ EnclosedCjkLettersAndMonths,
+ ///
+ /// Enclosed Ideographic Supplement Chart
+ ///
+ EnclosedIdeographicSupplement,
+ ///
+ /// Ethiopic Chart
+ ///
+ Ethiopic,
+ ///
+ /// Ethiopic Extended Chart
+ ///
+ EthiopicExtended,
+ ///
+ /// Ethiopic Supplement Chart
+ ///
+ EthiopicSupplement,
+ ///
+ /// Euro Sign Chart
+ ///
+ EuroSign,
+ ///
+ /// Floors and ceilings
+ ///
+ FloorsAndCeilings,
+ ///
+ /// Fullwidth ASCII Digits Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ FullwidthAsciiDigits,
+ ///
+ /// Fullwidth ASCII Punctuation Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ FullwidthAsciiPunctuation,
+ ///
+ /// Fullwidth Currency Symbols Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ FullwidthCurrencySymbols,
+ ///
+ /// Fullwidth Latin Letters Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ FullwidthLatinLetters,
+ ///
+ /// Same as Chess/Checkers Chart
+ ///
+ GameSymbols,
+ ///
+ /// General Punctuation Chart
+ ///
+ GeneralPunctuation,
+ ///
+ /// Geometric Shapes Chart
+ ///
+ GeometricShapes,
+ ///
+ /// Georgian Chart
+ ///
+ Georgian,
+ ///
+ /// Georgian Supplement Chart
+ ///
+ GeorgianSupplement,
+ ///
+ /// Glagolitic Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Glagolitic,
+ ///
+ /// Gothic Chart
+ ///
+ Gothic,
+ ///
+ /// Greek Chart
+ ///
+ Greek,
+ ///
+ /// Greek Extended Chart
+ ///
+ GreekExtended,
+ ///
+ /// Gujarati Chart
+ ///
+ Gujarati,
+ ///
+ /// Gurmukhi Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Gurmukhi,
+ ///
+ /// Halfwidth and Fullwidth Forms Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ HalfwidthAndFullwidthForms,
+ ///
+ /// Half width Jamo Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ HalfwidthJamo,
+ ///
+ /// Half width Katakana Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ HalfwidthKatakana,
+ ///
+ /// Hangul Compatibility Jamo Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ HangulCompatibilityJamo,
+ ///
+ /// Hangul Jamo Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ HangulJamo,
+ ///
+ /// Hangul Jamo ExtendedA Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ HangulJamoExtendedA,
+ ///
+ /// Hangul Jamo Extended-B Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ HangulJamoExtendedB,
+ ///
+ /// Hangul Syllables Chart
+ ///
+ HangulSyllables,
+ ///
+ /// Hanunoo Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Hanunoo,
+ ///
+ /// Hebrew Chart
+ ///
+ Hebrew,
+ ///
+ /// Hebrew Presentation Forms Chart
+ ///
+ HebrewPresentationForms,
+ ///
+ /// High Surrogates Chart
+ ///
+ HighSurrogates,
+ ///
+ /// Hiragana Chart
+ ///
+ Hiragana,
+ ///
+ /// Ideographic Description Characters Chart
+ ///
+ IdeographicDescriptionCharacters,
+ ///
+ /// Invisible Operators Chart
+ ///
+ InvisibleOperators,
+ ///
+ /// IPA Extensions Chart
+ ///
+ IpaExtensions,
+ ///
+ /// Japanese Chess Chart
+ ///
+ JapaneseChess,
+ ///
+ /// Javanese Chart
+ ///
+ Javanese,
+ ///
+ /// Kaithi Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Kaithi,
+ ///
+ /// Kanbun Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Kanbun,
+ ///
+ /// Kannada Chart
+ ///
+ Kannada,
+ ///
+ /// Katakana Chart
+ ///
+ Katakana,
+ ///
+ /// Katakana Phonetic Extensions Chart
+ ///
+ KatakanaPhoneticExtensions,
+ ///
+ /// Kayah Li Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709")]
+ KayahLi,
+ ///
+ /// Kharoshthi Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Kharoshthi,
+ ///
+ /// Khmer Chart
+ ///
+ Khmer,
+ ///
+ /// Khmer Symbols Chart
+ ///
+ KhmerSymbols,
+ ///
+ /// Lao Chart
+ ///
+ Lao,
+ ///
+ /// Latin Chart
+ ///
+ Latin,
+ ///
+ /// Latin-1 Punctuation Chart
+ ///
+ Latin1Punctuation,
+ ///
+ /// Latin-1 Supplement Chart
+ ///
+ Latin1Supplement,
+ ///
+ /// Latin Extended-A Chart
+ ///
+ LatinExtendedA,
+ ///
+ /// Latin Extended Additional Chart
+ ///
+ LatinExtendedAdditional,
+ ///
+ /// Latin Extended-B Chart
+ ///
+ LatinExtendedB,
+ ///
+ /// Latin Extended-C Chart
+ ///
+ LatinExtendedC,
+ ///
+ /// Latin Extended-D Chart
+ ///
+ LatinExtendedD,
+ ///
+ /// Latin Ligatures Chart
+ ///
+ LatinLigatures,
+ ///
+ /// Layout Controls Chart
+ ///
+ LayoutControls,
+ ///
+ /// Lepcha Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Lepcha,
+ ///
+ /// Letterlike Symbols Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ LetterlikeSymbols,
+ ///
+ /// Limbu Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Limbu,
+ ///
+ /// Linear B Syllabary and Linear B Ideograms Chart
+ ///
+ LinearB,
+ ///
+ /// Linear B Ideograms Chart
+ ///
+ LinearBIdeograms,
+ ///
+ /// Linear B Syllabary Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ LinearBSyllabary,
+ ///
+ /// Lisu Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Lisu,
+ ///
+ /// Low Surrogates Chart
+ ///
+ LowSurrogates,
+ ///
+ /// Lycian Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Lycian,
+ ///
+ /// Lydian Chart
+ ///
+ Lydian,
+ ///
+ /// Mahjong Tiles Chart
+ ///
+ MahjongTiles,
+ ///
+ /// Malayalam Chart
+ ///
+ Malayalam,
+ ///
+ /// Mark Chart
+ ///
+ Mark,
+ ///
+ /// Mathematical Alphanumeric Symbols Chart
+ ///
+ MathematicalAlphanumericSymbols,
+ ///
+ /// Mathematical Operators Chart
+ ///
+ MathematicalOperators,
+ ///
+ /// Meetei Mayek Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ MeeteiMayek,
+ ///
+ /// Miscellaneous Mathematical SymbolsA Chart
+ ///
+ MiscellaneousMathematicalSymbolsA,
+ ///
+ /// Miscellaneous Mathematical SymbolsB Chart
+ ///
+ MiscellaneousMathematicalSymbolsB,
+ ///
+ /// Miscellaneous Symbols Chart
+ ///
+ MiscellaneousSymbols,
+ ///
+ /// Miscellaneous Symbols and Arrows Chart
+ ///
+ MiscellaneousSymbolsAndArrows,
+ ///
+ /// Miscellaneous Technical Chart
+ ///
+ MiscellaneousTechnical,
+ ///
+ /// Modifier Tone Letters Chart
+ ///
+ ModifierToneLetters,
+ ///
+ /// Mongolian Chart
+ ///
+ Mongolian,
+ ///
+ /// Musical Symbols Chart
+ ///
+ MusicalSymbols,
+ ///
+ /// Myanmar Chart
+ ///
+ Myanmar,
+ ///
+ /// Myanmar Extended-A Chart
+ ///
+ MyanmarExtendedA,
+ ///
+ /// New Tai Lue Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ NewTaiLue,
+ ///
+ /// N'Ko Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709")]
+ NKo,
+ ///
+ /// Number Forms Chart
+ ///
+ NumberForms,
+ ///
+ /// Ogham Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Ogham,
+ ///
+ /// Ol Chiki Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709")]
+ OlChiki,
+ ///
+ /// Old Italic Chart
+ ///
+ OldItalic,
+ ///
+ /// Old Persian Chart
+ ///
+ OldPersian,
+ ///
+ /// Old South Arabian Chart
+ ///
+ OldSouthArabian,
+ ///
+ /// Old Turkic Chart
+ ///
+ OldTurkic,
+ ///
+ /// Optical Character Recognition Chart
+ ///
+ OpticalCharacterRecognition,
+ ///
+ /// Oriya Chart
+ ///
+ Oriya,
+ ///
+ /// Osmanya Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Osmanya,
+ ///
+ /// Pahlavi Inscriptional Chart
+ ///
+ PahlaviInscriptional,
+ ///
+ /// Parthian Inscriptional Chart
+ ///
+ ParthianInscriptional,
+ ///
+ /// Pfennig Chart
+ ///
+ Pfennig,
+ ///
+ /// Phags-Pa Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709")]
+ PhagsPa,
+ ///
+ /// Phaistos Disc Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ PhaistosDisc,
+ ///
+ /// Phoenician Chart
+ ///
+ Phoenician,
+ ///
+ /// Phonetic Extensions Chart
+ ///
+ PhoneticExtensions,
+ ///
+ /// Phonetic Extensions Supplement Chart
+ ///
+ PhoneticExtensionsSupplement,
+ ///
+ /// Plane 1 Chart
+ ///
+ Plane1,
+ ///
+ /// Plane 10 Chart
+ ///
+ Plane10,
+ ///
+ /// Plane 11 Chart
+ ///
+ Plane11,
+ ///
+ /// Plane 12 Chart
+ ///
+ Plane12,
+ ///
+ /// Plane 13 Chart
+ ///
+ Plane13,
+ ///
+ /// Plane 14 Chart
+ ///
+ Plane14,
+ ///
+ /// Plane 15 Chart
+ ///
+ Plane15,
+ ///
+ /// Plane 16 Chart
+ ///
+ Plane16,
+ ///
+ /// Plane 2 Chart
+ ///
+ Plane2,
+ ///
+ /// Plane 3 Chart
+ ///
+ Plane3,
+ ///
+ /// Plane 4 Chart
+ ///
+ Plane4,
+ ///
+ /// Plane 5 Chart
+ ///
+ Plane5,
+ ///
+ /// Plane 6 Chart
+ ///
+ Plane6,
+ ///
+ /// Plane 7 Chart
+ ///
+ Plane7,
+ ///
+ /// Plane 8 Chart
+ ///
+ Plane8,
+ ///
+ /// Plane 9 Chart
+ ///
+ Plane9,
+ ///
+ /// Private Use Area Chart
+ ///
+ PrivateUseArea,
+ ///
+ /// Rejang Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Rejang,
+ ///
+ /// Reserved Range Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1700")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ ReservedRange,
+ ///
+ /// Rial Sign Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ RialSign,
+ ///
+ /// Roman Symbols Chart
+ ///
+ RomanSymbols,
+ ///
+ /// Rumi Numeral Symbols Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ RumiNumeralSymbols,
+ ///
+ /// Runic Chart
+ ///
+ Runic,
+ ///
+ /// Samaritan Chart
+ ///
+ Samaritan,
+ ///
+ /// Saurashtra Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Saurashtra,
+ ///
+ /// Shavian Chart
+ ///
+ Shavian,
+ ///
+ /// Sinhala Chart
+ ///
+ Sinhala,
+ ///
+ /// Small Form Variants Chart
+ ///
+ SmallFormVariants,
+ ///
+ /// Spacing Modifier Letters Chart
+ ///
+ SpacingModifierLetters,
+ ///
+ /// Specials Chart
+ ///
+ Specials,
+ ///
+ /// Sundanese Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Sundanese,
+ ///
+ /// Super and Subscripts Chart
+ ///
+ SuperAndSubscripts,
+ ///
+ /// Superscripts and Subscripts Chart
+ ///
+ SuperscriptsAndSubscripts,
+ ///
+ /// Supplemental Arrows-A Chart
+ ///
+ SupplementalArrowsA,
+ ///
+ /// Supplemental Arrows-B Chart
+ ///
+ SupplementalArrowsB,
+ ///
+ /// Supplemental Mathematical Operators Chart
+ ///
+ SupplementalMathematicalOperators,
+ ///
+ /// Supplemental Punctuation Chart
+ ///
+ SupplementalPunctuation,
+ ///
+ /// Supplementary Private Use Area-A Chart
+ ///
+ SupplementaryPrivateUseAreaA,
+ ///
+ /// Supplementary Private Use Area-B Chart
+ ///
+ SupplementaryPrivateUseAreaB,
+ ///
+ /// Syloti Nagri Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ SylotiNagri,
+ ///
+ /// Syriac Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Syriac,
+ ///
+ /// Tagalog Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Tagalog,
+ ///
+ /// Tagbanwa Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Tagbanwa,
+ ///
+ /// Tags Chart
+ ///
+ Tags,
+ ///
+ /// Tai Le Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709")]
+ TaiLe,
+ ///
+ /// Tai Tham Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ TaiTham,
+ ///
+ /// Tai Viet Chart
+ ///
+ TaiViet,
+ ///
+ /// Tai Xuan Jing Symbols Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ TaiXuanJingSymbols,
+ ///
+ /// Tamil Chart
+ ///
+ Tamil,
+ ///
+ /// Telugu Chart
+ ///
+ Telugu,
+ ///
+ /// Thaana Chart
+ ///
+ Thaana,
+ ///
+ /// Thai Chart
+ ///
+ Thai,
+ ///
+ /// Tibetan Chart
+ ///
+ Tibetan,
+ ///
+ /// Tifinagh Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Tifinagh,
+ ///
+ /// Ugaritic Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Ugaritic,
+ ///
+ /// Unified Canadian Aboriginal Syllabics Chart
+ ///
+ UnifiedCanadianAboriginalSyllabics,
+ ///
+ /// Unified Canadian Aboriginal Syllabics ExtendedChart
+ ///
+ UnifiedCanadianAboriginalSyllabicsExtended,
+ ///
+ /// Vai Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ Vai,
+ ///
+ /// Variation Selectors Chart
+ ///
+ VariationSelectors,
+ ///
+ /// Variation Selectors Supplement Chart
+ ///
+ VariationSelectorsSupplement,
+ ///
+ /// Vedic Extensions Chart
+ ///
+ VedicExtensions,
+ ///
+ /// Vertical Forms Chart
+ ///
+ VerticalForms,
+ ///
+ /// Yen Pound and Cent Chart
+ ///
+ YenPoundAndCent,
+ ///
+ /// Yi Syllables and Yi Radicals Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709")]
+ Yi,
+ ///
+ /// Yijing Hexagram Symbols Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ YijingHexagramSymbols,
+ ///
+ /// Yijing Mono- Di- and Trigrams Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709")]
+ YijingMonoDiAndTrigrams,
+ ///
+ /// Same as Yijing Mono- Di- and Trigrams Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709")]
+ YijingSymbols,
+ ///
+ /// Yi Radicals Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709")]
+ YiRadicals,
+ ///
+ /// Yi Syllables Chart
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709")]
+ YiSyllables,
+ }
+}
+
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/UnicodeRange.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/UnicodeRange.cs
new file mode 100644
index 0000000..5ca36cc
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/UnicodeRange.cs
@@ -0,0 +1,81 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Globalization;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// Represents a Unicode range.
+ /// A UnicodeRange instance can be created by either providing start and end of the
+ /// desired Unicode range or by providing a .
+ ///
+ public class UnicodeRange
+ {
+ ///
+ /// Create a UnicodeRange instance, using the provided UnicodeChart Enum type.
+ ///
+ /// Group name of scripts, symbols or punctuations (e.g. "European Scripts", "Punctuation", etc.)
+ public UnicodeRange(UnicodeChart chart)
+ {
+ UnicodeRange range = RangePropertyCollector.GetUnicodeChartRange(new UnicodeRangeDatabase(), chart);
+ startOfUnicodeRange = range.StartOfUnicodeRange;
+ endOfUnicodeRange = range.EndOfUnicodeRange;
+ }
+
+ ///
+ /// Create a UnicodeRange instance, using the provided start and end of the Unicode range
+ ///
+ /// Start of the Unicode range (e.g. 0x0000, etc.)
+ /// End of the Unicode range (e.g. 0xFFFF, etc.)
+ public UnicodeRange(int start, int end)
+ {
+ Init(start, end);
+ }
+
+ ///
+ /// Copy constructor
+ ///
+ /// A UnicodeRange object to be copied
+ public UnicodeRange(UnicodeRange range)
+ {
+ startOfUnicodeRange = range.StartOfUnicodeRange;
+ endOfUnicodeRange = range.EndOfUnicodeRange;
+ }
+
+ private void Init(int low, int high)
+ {
+ if (low > high)
+ {
+ throw new ArgumentOutOfRangeException ("UnicodeRange, low " + low + " shouldn't be greater than high " + high + " value.");
+ }
+ else if (low < 0)
+ {
+ throw new ArgumentOutOfRangeException ("UnicodeRange, low is" + low + ", cannot be less than 0x0.");
+ }
+ else if (high > TextUtil.MaxUnicodePoint)
+ {
+ throw new ArgumentOutOfRangeException ("UnicodeRange, high cannot be greater than " +
+ String.Format(CultureInfo.InvariantCulture, "0x{0:X}", TextUtil.MaxUnicodePoint) + ".");
+ }
+ startOfUnicodeRange = low;
+ endOfUnicodeRange = high;
+ }
+
+ ///
+ /// Get the start of the Unicode range
+ ///
+ public int StartOfUnicodeRange { get { return startOfUnicodeRange; } }
+
+ ///
+ /// Get the end of the Unicode range
+ ///
+ public int EndOfUnicodeRange { get { return endOfUnicodeRange; } }
+
+ private int startOfUnicodeRange;
+ private int endOfUnicodeRange;
+ }
+}
+
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/UnicodeRangeDatabase.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/UnicodeRangeDatabase.cs
new file mode 100644
index 0000000..a6380f3
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/UnicodeRangeDatabase.cs
@@ -0,0 +1,437 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// Collection of Unicode ranges including Scripts, Symbols, and Punctuation
+ ///
+ internal class UnicodeRangeDatabase
+ {
+ private static readonly Group [] scripts = new Group[TextUtil.NUMOFSCRIPTS];
+ private static readonly Group [] symbolsAndPunctuation = new Group[TextUtil.NUMOFSYMBOLSANDPUNCTUATION];
+
+ ///
+ /// Define UnicodeRangeDatabase class,
+ /// Newline
+ ///
+ public UnicodeRangeDatabase( )
+ {
+ if (null == scripts[0])
+ {
+ InitializeScripts();
+ }
+
+ if (null == symbolsAndPunctuation[0])
+ {
+ InitializeSymbolsAndPunctuation();
+ }
+ }
+
+ ///
+ /// Getter for scripts
+ ///
+ public Group [] Scripts { get { return scripts; } }
+
+ ///
+ /// Getter for symbols and punctuation
+ ///
+ public Group [] SymbolsAndPunctuation { get { return symbolsAndPunctuation; } }
+
+ private static void InitializeScripts()
+ {
+ // For the 3rd parameter ids - all lower cases
+ // If LCID exists, Short String description is used; otherwise, spell out the whole name in lower case.
+ // If it applies to the root culture e.g. zh-cn, zh-hk, zh-tw, the root culture id is used e.g. "zh". '-' is omitted.
+ // If it can be used for various culuture, "any" is used.
+ // If LCID does not exist, full spell of the culture is the id and space is omitted.
+ scripts[0] = new Group(new UnicodeRange(0x0530, 0x058F), "European Scripts", "Armenian", "hy", UnicodeChart.Armenian);
+ scripts[0].SubGroups = new SubGroup[1];
+ scripts[0].SubGroups[0] = new SubGroup(new UnicodeRange(0xFB00, 0xFB4F), "Armenian Ligatures", "hy", UnicodeChart.ArmenianLigatures);
+
+ scripts[1] = new Group(new UnicodeRange(0x2C80, 0x2CFF), "European Scripts", "Coptic", "eg", UnicodeChart.Coptic);
+ scripts[1].SubGroups = new SubGroup[1];
+ scripts[1].SubGroups[0] = new SubGroup(new UnicodeRange(0x0370, 0x03FF), "Coptic in Greek block", "eg,el", UnicodeChart.CopticInGreekBlock);
+
+ scripts[2] = new Group(new UnicodeRange(0x10800, 0x1083F), "European Scripts", "Cypriot Syllabary", "cyprus", UnicodeChart.CypriotSyllabary);
+
+ scripts[3] = new Group(new UnicodeRange(0x0400, 0x04FF), "European Scripts", "Cyrillic", "azaz,srsp,uzuz", UnicodeChart.Cyrillic);
+ scripts[3].SubGroups = new SubGroup[3];
+ scripts[3].SubGroups[0] = new SubGroup(new UnicodeRange(0x0500, 0x052F), "Cyrillic Supplement", "azaz,srsp,uzuz", UnicodeChart.CyrillicSupplement);
+ scripts[3].SubGroups[1] = new SubGroup(new UnicodeRange(0x2DE0, 0x2DFF), "Cyrillic Extended-A", "azaz,srsp,uzuz", UnicodeChart.CyrillicExtendedA);
+ scripts[3].SubGroups[2] = new SubGroup(new UnicodeRange(0xA640, 0xA69F), "Cyrillic Extended-B", "azaz,srsp,uzuz", UnicodeChart.CyrillicExtendedB);
+
+ scripts[4] = new Group(new UnicodeRange(0x10A0, 0x10FF), "European Scripts", "Georgian", "georgia", UnicodeChart.Georgian);
+ scripts[4].SubGroups = new SubGroup[1];
+ scripts[4].SubGroups[0] = new SubGroup(new UnicodeRange(0x2D00, 0x2D2F), "Georgian Supplement", "georgia", UnicodeChart.GeorgianSupplement);
+
+ scripts[5] = new Group(new UnicodeRange(0x2C00, 0x2C5F), "European Scripts", "Glagolitic", "glagolitsa", UnicodeChart.Glagolitic);
+ scripts[6] = new Group(new UnicodeRange(0x10330, 0x1034F), "European Scripts", "Gothic", "de", UnicodeChart.Gothic);
+
+ scripts[7] = new Group(new UnicodeRange(0x0370, 0x03FF), "European Scripts", "Greek", "el", UnicodeChart.Greek);
+ scripts[7].SubGroups = new SubGroup[1];
+ scripts[7].SubGroups[0] = new SubGroup(new UnicodeRange(0x1F00, 0x1FFF), "Greek Extended", "el", UnicodeChart.GreekExtended);
+
+ scripts[8] = new Group(new UnicodeRange(0x0000, 0x007F), "European Scripts", "Latin", "latin", UnicodeChart.Latin);
+ scripts[8].SubGroups = new SubGroup[8];
+ scripts[8].SubGroups[0] = new SubGroup(new UnicodeRange(0x0080, 0x00FF), "Latin-1 Supplement", "latin", UnicodeChart.Latin1Supplement);
+ scripts[8].SubGroups[1] = new SubGroup(new UnicodeRange(0x0100, 0x017F), "Latin Extended-A", "latin", UnicodeChart.LatinExtendedA);
+ scripts[8].SubGroups[2] = new SubGroup(new UnicodeRange(0x0180, 0x024F), "Latin Extended-B", "latin", UnicodeChart.LatinExtendedB);
+ scripts[8].SubGroups[3] = new SubGroup(new UnicodeRange(0x2C60, 0x2C7F), "Latin Extended-C", "latin", UnicodeChart.LatinExtendedC);
+ scripts[8].SubGroups[4] = new SubGroup(new UnicodeRange(0xA720, 0xA7FF), "Latin Extended-D", "latin", UnicodeChart.LatinExtendedD);
+ scripts[8].SubGroups[5] = new SubGroup(new UnicodeRange(0x1E00, 0x1EFF), "Latin Extended Additional", "latin", UnicodeChart.LatinExtendedAdditional);
+ scripts[8].SubGroups[6] = new SubGroup(new UnicodeRange(0xFB00, 0xFB4F), "Latin Ligatures", "latin", UnicodeChart.LatinLigatures);
+ scripts[8].SubGroups[7] = new SubGroup(new UnicodeRange(0xFF00, 0xFFEF), "FullWidth Latin Letters", "latin", UnicodeChart.FullwidthLatinLetters);
+
+ // Linear B doesn't have a range specified. Use the range of both sub groups
+ scripts[9] = new Group(new UnicodeRange(0x10000, 0x100FF), "European Scripts", "Linear B", "other", UnicodeChart.LinearB);
+ scripts[9].SubGroups = new SubGroup[2];
+ scripts[9].SubGroups[0] = new SubGroup(new UnicodeRange(0x10000, 0x1007F), "Linear B Syllabary", "other", UnicodeChart.LinearBSyllabary);
+ scripts[9].SubGroups[1] = new SubGroup(new UnicodeRange(0x10080, 0x100FF), "Linear B Ideograms", "other", UnicodeChart.LinearBIdeograms);
+
+ scripts[10] = new Group(new UnicodeRange(0x1680, 0x169F), "European Scripts", "Ogham", "ie", UnicodeChart.Ogham);
+ scripts[11] = new Group(new UnicodeRange(0x10300, 0x1032F), "European Scripts", "Old Italic", "other", UnicodeChart.OldItalic);
+ scripts[12] = new Group(new UnicodeRange(0x101D0, 0x101FF), "European Scripts", "Phaistos Disc", "phaistosdisc", UnicodeChart.PhaistosDisc);
+ scripts[13] = new Group(new UnicodeRange(0x16A0, 0x16FF), "European Scripts", "Runic", "de", UnicodeChart.Runic);
+ scripts[14] = new Group(new UnicodeRange(0x10450, 0x1047F), "European Scripts", "Shavian", "en", UnicodeChart.Shavian);
+
+ scripts[15] = new Group(new UnicodeRange(0x0250, 0x02AF), "Phonetic Symbols", "IPA Extensions", "latin", UnicodeChart.IpaExtensions);
+
+ scripts[16] = new Group(new UnicodeRange(0x1D00, 0x1D7F), "Phonetic Symbols", "Phonetic Extensions", "latin", UnicodeChart.PhoneticExtensions);
+ scripts[16].SubGroups = new SubGroup[1];
+ scripts[16].SubGroups[0] = new SubGroup(new UnicodeRange(0x1D80, 0x1D8F), "Phonetic Extensions Supplement", "latin", UnicodeChart.PhoneticExtensionsSupplement);
+
+ scripts[17] = new Group(new UnicodeRange(0xA700, 0xA71F), "Phonetic Symbols", "Modifier Tone Letters", "other", UnicodeChart.ModifierToneLetters);
+ scripts[18] = new Group(new UnicodeRange(0x02B0, 0x02FF), "Phonetic Symbols", "Spacing Modifier Letters", "other", UnicodeChart.SpacingModifierLetters);
+ scripts[19] = new Group(new UnicodeRange(0x2070, 0x209F), "Phonetic Symbols", "Superscripts and Subscripts", "any", UnicodeChart.SuperscriptsAndSubscripts);
+
+ scripts[20] = new Group(new UnicodeRange(0x0300, 0x036F), "Combining Diacritics", "Combining Diacritical Marks", "other", UnicodeChart.CombiningDiacriticalMarks);
+ scripts[20].SubGroups = new SubGroup[1];
+ scripts[20].SubGroups[0] = new SubGroup(new UnicodeRange(0x1DC0, 0x1DFF), "Combining Diacritical Marks Supplement", "other", UnicodeChart.CombiningDiacriticalMarksSupplement);
+
+ scripts[21] = new Group(new UnicodeRange(0xFE20, 0xFE2F), "Combining Diacritics", "Combining Half Marks", "other", UnicodeChart.CombiningHalfMarks);
+
+ scripts[22] = new Group(new UnicodeRange(0xA6A0, 0xA6FF), "African Scripts", "Bamum", "cameroon", UnicodeChart.Bamum);
+ scripts[23] = new Group(new UnicodeRange(0x13000, 0x1342F), "African Scripts", "Egyptian Hieroglyphs", "eg", UnicodeChart.EgyptianHieroglyphs);
+
+ scripts[24] = new Group(new UnicodeRange(0x1200, 0x137F), "African Scripts", "Ethiopic", "ethiopia", UnicodeChart.Ethiopic);
+ scripts[24].SubGroups = new SubGroup[2];
+ scripts[24].SubGroups[0] = new SubGroup(new UnicodeRange(0x1380, 0x139F), "Ethiopic Supplement", "ethiopia", UnicodeChart.EthiopicSupplement);
+ scripts[24].SubGroups[1] = new SubGroup(new UnicodeRange(0x2D80, 0x2DDF), "Ethiopic Extended", "ethiopia", UnicodeChart.EthiopicExtended);
+
+ scripts[25] = new Group(new UnicodeRange(0xA700, 0xA71F), "African Scripts", "N'ko", "nko", UnicodeChart.NKo);
+ scripts[26] = new Group(new UnicodeRange(0x10480, 0x104AF), "African Scripts", "Osmanya", "somalia", UnicodeChart.Osmanya);
+ scripts[27] = new Group(new UnicodeRange(0x2D30, 0x2D7F), "African Scripts", "Tifinagh", "tifinagh", UnicodeChart.Tifinagh);
+ scripts[28] = new Group(new UnicodeRange(0xA500, 0xA63F), "African Scripts", "Vai", "vai", UnicodeChart.Vai);
+
+ scripts[29] = new Group(new UnicodeRange(0x0600, 0x06FF), "Middle Eastern Scripts", "Arabic", "ar", UnicodeChart.Arabic);
+ scripts[29].SubGroups = new SubGroup[3];
+ scripts[29].SubGroups[0] = new SubGroup(new UnicodeRange(0x0750, 0x077F), "Arabic Supplement", "ar", UnicodeChart.ArabicSupplement);
+ scripts[29].SubGroups[1] = new SubGroup(new UnicodeRange(0xFB50, 0xFDFF), "Arabic Presentation Forms-A", "ar", UnicodeChart.ArabicPresentationFormsA);
+ scripts[29].SubGroups[2] = new SubGroup(new UnicodeRange(0xFE70, 0xFEFF), "Arabic Presentation Forms-B", "ar", UnicodeChart.ArabicPresentationFormsB);
+
+ scripts[30] = new Group(new UnicodeRange(0x10840, 0x1085F), "Middle Eastern Scripts", "Aramaic, Imperial", "he", UnicodeChart.AramaicImperial);
+ scripts[31] = new Group(new UnicodeRange(0x10B00, 0x10B3F), "Middle Eastern Scripts", "Avestan", "iran", UnicodeChart.Avestan);
+ scripts[32] = new Group(new UnicodeRange(0x102A0, 0x102DF), "Middle Eastern Scripts", "Carian", "carians", UnicodeChart.Carian);
+
+ scripts[33] = new Group(new UnicodeRange(0x12000, 0x123FF), "Middle Eastern Scripts", "Cuneiform", "cuneiform", UnicodeChart.Cuneiform);
+ scripts[33].SubGroups = new SubGroup[3];
+ scripts[33].SubGroups[0] = new SubGroup(new UnicodeRange(0x12400, 0x1247F), "Cuneiform Numbers and Punctuation", "cuneiform", UnicodeChart.CuneiformNumbersAndPunctuation);
+ scripts[33].SubGroups[1] = new SubGroup(new UnicodeRange(0x103A0, 0x103DF), "Old Persian", "cuneiform", UnicodeChart.OldPersian);
+ scripts[33].SubGroups[2] = new SubGroup(new UnicodeRange(0x10380, 0x1039F), "Ugaritic", "cuneiform", UnicodeChart.Ugaritic);
+
+ scripts[34] = new Group(new UnicodeRange(0x0590, 0x05FF), "Middle Eastern Scripts", "Hebrew", "he", UnicodeChart.Hebrew);
+ scripts[34].SubGroups = new SubGroup[1];
+ scripts[34].SubGroups[0] = new SubGroup(new UnicodeRange(0xFB00, 0xFB4F), "Hebrew Presentation Forms", "he", UnicodeChart.HebrewPresentationForms);
+
+ scripts[35] = new Group(new UnicodeRange(0x10280, 0x1029F), "Middle Eastern Scripts", "Lycian", "lycia", UnicodeChart.Lycian);
+ scripts[36] = new Group(new UnicodeRange(0x10920, 0x1093F), "Middle Eastern Scripts", "Lydian", "lycia", UnicodeChart.Lydian);
+ scripts[37] = new Group(new UnicodeRange(0x10A60, 0x10A7F), "Middle Eastern Scripts", "Old South Arabian", "ar", UnicodeChart.OldSouthArabian);
+ scripts[38] = new Group(new UnicodeRange(0x10B60, 0x10B7F), "Middle Eastern Scripts", "Pahlavi, Inscriptional", "iran", UnicodeChart.PahlaviInscriptional);
+ scripts[39] = new Group(new UnicodeRange(0x10B40, 0x10B5FF), "Middle Eastern Scripts", "Parthian, Inscriptional", "iran", UnicodeChart.ParthianInscriptional);
+ scripts[40] = new Group(new UnicodeRange(0x10900, 0x1091F), "Middle Eastern Scripts", "Phoenician", "phoenicia", UnicodeChart.Phoenician);
+ scripts[41] = new Group(new UnicodeRange(0x0800, 0x083F), "Middle Eastern Scripts", "Samaritan", "samaria", UnicodeChart.Samaritan);
+ scripts[42] = new Group(new UnicodeRange(0x0700, 0x074F), "Middle Eastern Scripts", "Syriac", "syriac", UnicodeChart.Syriac);
+
+ scripts[43] = new Group(new UnicodeRange(0x1800, 0x18AF), "Central Asian Scripts", "Mongolian", "mongolia", UnicodeChart.Mongolian);
+ scripts[44] = new Group(new UnicodeRange(0x10C00, 0x10C4F), "Central Asian Scripts", "Old Turkic", "oldturkic", UnicodeChart.OldTurkic);
+ scripts[45] = new Group(new UnicodeRange(0xA840, 0xA87F), "Central Asian Scripts", "Phags-Pa", "zh", UnicodeChart.PhagsPa);
+ scripts[46] = new Group(new UnicodeRange(0x0F00, 0x0FFF), "Central Asian Scripts", "Tibetan", "zh", UnicodeChart.Tibetan);
+
+ scripts[47] = new Group(new UnicodeRange(0x0980, 0x09FF), "South Asian Scripts", "Bengali", "bangladesh,hi", UnicodeChart.Bengali);
+
+ scripts[48] = new Group(new UnicodeRange(0x0900, 0x097F), "South Asian Scripts", "Devanagari", "hi,nepal", UnicodeChart.Devanagari);
+ scripts[48].SubGroups = new SubGroup[1];
+ scripts[48].SubGroups[0] = new SubGroup(new UnicodeRange(0xA8E0, 0xA8FF), "Devanagari Extended", "hi", UnicodeChart.DevanagariExtended);
+
+ scripts[49] = new Group(new UnicodeRange(0x0A80, 0x0AFF), "South Asian Scripts", "Gujarati", "hi", UnicodeChart.Gujarati);
+ scripts[50] = new Group(new UnicodeRange(0x0A00, 0x0A7F), "South Asian Scripts", "Gurmukhi", "hi", UnicodeChart.Gurmukhi);
+ scripts[51] = new Group(new UnicodeRange(0x11080, 0x110CF), "South Asian Scripts", "Kaithi", "hi", UnicodeChart.Kaithi);
+ scripts[52] = new Group(new UnicodeRange(0x0C80, 0x0CFF), "South Asian Scripts", "Kannada", "hi", UnicodeChart.Kannada);
+ scripts[53] = new Group(new UnicodeRange(0x10A00, 0x10A5F), "South Asian Scripts", "Kharoshthi", "kharoshthi", UnicodeChart.Kharoshthi);
+ scripts[54] = new Group(new UnicodeRange(0x1C00, 0x1C4F), "South Asian Scripts", "Lepcha", "zh,nepal,hi", UnicodeChart.Lepcha);
+ scripts[55] = new Group(new UnicodeRange(0x1900, 0x194F), "South Asian Scripts", "Limbu", "nepal", UnicodeChart.Limbu);
+ scripts[56] = new Group(new UnicodeRange(0x0D00, 0x0D7F), "South Asian Scripts", "Malayalam", "hi", UnicodeChart.Malayalam);
+ scripts[57] = new Group(new UnicodeRange(0xABC0, 0xABFF), "South Asian Scripts", "Meetei Mayek", "hi", UnicodeChart.MeeteiMayek);
+ scripts[58] = new Group(new UnicodeRange(0x1C50, 0x1C7F), "South Asian Scripts", "Ol Chiki", "hi", UnicodeChart.OlChiki);
+ scripts[59] = new Group(new UnicodeRange(0x0B00, 0x0B7F), "South Asian Scripts", "Oriya", "hi", UnicodeChart.Oriya);
+ scripts[60] = new Group(new UnicodeRange(0xA880, 0xA8DF), "South Asian Scripts", "Saurashtra", "hi", UnicodeChart.Saurashtra);
+ scripts[61] = new Group(new UnicodeRange(0x0D80, 0x0DFF), "South Asian Scripts", "Sinhala", "srilanka", UnicodeChart.Sinhala);
+ scripts[62] = new Group(new UnicodeRange(0xA800, 0xA82F), "South Asian Scripts", "Syloti Nagri", "sylotinagri", UnicodeChart.SylotiNagri);
+ scripts[63] = new Group(new UnicodeRange(0x0B80, 0x0BFF), "South Asian Scripts", "Tamil", "hi,srilanka,singapore", UnicodeChart.Tamil);
+ scripts[64] = new Group(new UnicodeRange(0x0C00, 0x0C7F), "South Asian Scripts", "Telugu", "hi", UnicodeChart.Telugu);
+ scripts[65] = new Group(new UnicodeRange(0x0780, 0x07BF), "South Asian Scripts", "Thaana", "maldives", UnicodeChart.Thaana);
+ scripts[66] = new Group(new UnicodeRange(0x1CD0, 0x1CFF), "South Asian Scripts", "Vedic Extensions", "hi", UnicodeChart.VedicExtensions);
+
+ scripts[67] = new Group(new UnicodeRange(0x1B00, 0x1B7F), "Southeast Asian Scripts", "Balinese", "id", UnicodeChart.Balinese);
+ scripts[68] = new Group(new UnicodeRange(0x1A00, 0x1A1F), "Southeast Asian Scripts", "Buginese", "id", UnicodeChart.Buginese);
+ scripts[69] = new Group(new UnicodeRange(0xAA00, 0xAA5F), "Southeast Asian Scripts", "Cham", "vi,th,cambodia", UnicodeChart.Cham);
+ scripts[70] = new Group(new UnicodeRange(0xA980, 0xA9DF), "Southeast Asian Scripts", "Javanese", "id", UnicodeChart.Javanese);
+ scripts[71] = new Group(new UnicodeRange(0xA900, 0xA92F), "Southeast Asian Scripts", "Kayah Li", "myanmar", UnicodeChart.KayahLi);
+
+ scripts[72] = new Group(new UnicodeRange(0x1780, 0x17FF), "Southeast Asian Scripts", "Khmer", "cambodia", UnicodeChart.Khmer);
+ scripts[72].SubGroups = new SubGroup[1];
+ scripts[72].SubGroups[0] = new SubGroup(new UnicodeRange(0x17E0, 0x17FF), "Khmer Symbols", "cambodia", UnicodeChart.KhmerSymbols);
+
+ scripts[73] = new Group(new UnicodeRange(0x0E80, 0x0EFF), "Southeast Asian Scripts", "Lao", "lao", UnicodeChart.Lao);
+
+ scripts[74] = new Group(new UnicodeRange(0x1000, 0x109F), "Southeast Asian Scripts", "Myanmar", "myanmar", UnicodeChart.Myanmar);
+ scripts[74].SubGroups = new SubGroup[1];
+ scripts[74].SubGroups[0] = new SubGroup(new UnicodeRange(0xAA60, 0xAA7F), "Myanmar Extended-A", "myanmar", UnicodeChart.MyanmarExtendedA);
+
+ scripts[75] = new Group(new UnicodeRange(0x1980, 0x19DF), "Southeast Asian Scripts", "New Tai Lue", "zh", UnicodeChart.NewTaiLue);
+ scripts[76] = new Group(new UnicodeRange(0xA930, 0xA95F), "Southeast Asian Scripts", "Rejang", "id", UnicodeChart.Rejang);
+ scripts[77] = new Group(new UnicodeRange(0x1B80, 0x1BBF), "Southeast Asian Scripts", "Sundanese", "id", UnicodeChart.Sundanese);
+ scripts[78] = new Group(new UnicodeRange(0x1950, 0x197F), "Southeast Asian Scripts", "Tai Le", "zh", UnicodeChart.TaiLe);
+ scripts[79] = new Group(new UnicodeRange(0x1A20, 0x1AAF), "Southeast Asian Scripts", "Tai Tham", "th", UnicodeChart.TaiTham);
+ scripts[80] = new Group(new UnicodeRange(0xAA80, 0xAADF), "Southeast Asian Scripts", "Tai Viet", "vi", UnicodeChart.TaiViet);
+ scripts[81] = new Group(new UnicodeRange(0x0E00, 0x0E7F), "Southeast Asian Scripts", "Thai", "th", UnicodeChart.Thai);
+
+ scripts[82] = new Group(new UnicodeRange(0x1740, 0x175F), "Philippine Scripts", "Buhid", "ph", UnicodeChart.Buhid);
+ scripts[83] = new Group(new UnicodeRange(0x1720, 0x173F), "Philippine Scripts", "Hanunoo", "ph", UnicodeChart.Hanunoo);
+ scripts[84] = new Group(new UnicodeRange(0x1700, 0x171F), "Philippine Scripts", "Tagalog", "ph", UnicodeChart.Tagalog);
+ scripts[85] = new Group(new UnicodeRange(0x1760, 0x177F), "Philippine Scripts", "Tagbanwa", "ph", UnicodeChart.Tagbanwa);
+
+ scripts[86] = new Group(new UnicodeRange(0x3100, 0x312F), "East Asian Scripts", "Bopomofo", "zhtw", UnicodeChart.Bopomofo);
+ scripts[86].SubGroups = new SubGroup[1];
+ scripts[86].SubGroups[0] = new SubGroup(new UnicodeRange(0x31A0, 0x31BF), "Bopomofo Extended", "zhtw", UnicodeChart.BopomofoExtended);
+
+ scripts[87] = new Group(new UnicodeRange(0x4E00, 0x9FCF), "East Asian Scripts", "CJK Unified Ideographs (Han)", "zh,ja,ko", UnicodeChart.CjkUnifiedIdeographs);
+ scripts[87].SubGroups = new SubGroup[3];
+ scripts[87].SubGroups[0] = new SubGroup(new UnicodeRange(0x3400, 0x4DBF), "CJK Extension-A", "zh,ja,ko", UnicodeChart.CjkExtensionA);
+ scripts[87].SubGroups[1] = new SubGroup(new UnicodeRange(0x20000, 0x2A6DF), "CJK Extension B", "zh,ja,ko", UnicodeChart.CjkExtensionB);
+ scripts[87].SubGroups[2] = new SubGroup(new UnicodeRange(0x2A700, 0x2B73F), "CJK Extension C", "zh,ja,ko", UnicodeChart.CjkExtensionC);
+
+ scripts[88] = new Group(new UnicodeRange(0xF900, 0xFAFF), "East Asian Scripts", "CJK Compatibility Ideographs", "zh,ja,ko", UnicodeChart.CjkCompatibilityIdeographs);
+ scripts[88].SubGroups = new SubGroup[1];
+ scripts[88].SubGroups[0] = new SubGroup(new UnicodeRange(0x2F800, 0x2FA1F), "CJK Compatibility Ideographs Supplement", "zh,ja,ko", UnicodeChart.CjkCompatibilityIdeographsSupplement);
+
+ scripts[89] = new Group(new UnicodeRange(0x2F00, 0x2FDF), "East Asian Scripts", "CJK Radicals // KangXi Radicals", "zh,ja,ko", UnicodeChart.CjkKangXiRadicals);
+ scripts[89].SubGroups = new SubGroup[3];
+ scripts[89].SubGroups[0] = new SubGroup(new UnicodeRange(0x2E80, 0x2EFF), "CJK Radicals Supplement", "zh,ja,ko", UnicodeChart.CjkRadicalsSupplement);
+ scripts[89].SubGroups[1] = new SubGroup(new UnicodeRange(0x2E80, 0x2EFF), "CJK Strokes", "zh,ja,ko", UnicodeChart.CjkStrokes);
+ scripts[89].SubGroups[2] = new SubGroup(new UnicodeRange(0x31C0, 0x31EF), "Ideographic Description Characters", "zh,ja,ko", UnicodeChart.IdeographicDescriptionCharacters);
+
+ scripts[90] = new Group(new UnicodeRange(0x1100, 0x11FF), "East Asian Scripts", "Hangul Jamo", "ko", UnicodeChart.HangulJamo);
+ scripts[90].SubGroups = new SubGroup[4];
+ scripts[90].SubGroups[0] = new SubGroup(new UnicodeRange(0xA960, 0xA97F), "Hangul Jamo Extended-A", "ko", UnicodeChart.HangulJamoExtendedA);
+ scripts[90].SubGroups[1] = new SubGroup(new UnicodeRange(0xD7B0, 0xD7FF), "Hangul Jamo Extended-B", "ko", UnicodeChart.HangulJamoExtendedB);
+ scripts[90].SubGroups[2] = new SubGroup(new UnicodeRange(0x3130, 0x318F), "Hangul Compatibility Jamo", "ko", UnicodeChart.HangulCompatibilityJamo);
+ scripts[90].SubGroups[3] = new SubGroup(new UnicodeRange(0xFF00, 0xFFEF), "Halfwidth Jamo", "ko", UnicodeChart.HalfwidthJamo);
+
+ scripts[91] = new Group(new UnicodeRange(0xAC00, 0xD7AF), "East Asian Scripts", "Hangul Syllables", "ko", UnicodeChart.HangulSyllables);
+ scripts[92] = new Group(new UnicodeRange(0x3040, 0x309F), "East Asian Scripts", "Hiragana", "ja", UnicodeChart.Hiragana);
+
+ scripts[93] = new Group(new UnicodeRange(0x30A0, 0x30FF), "East Asian Scripts", "Katakana", "ja", UnicodeChart.Katakana);
+ scripts[93].SubGroups = new SubGroup[2];
+ scripts[93].SubGroups[0] = new SubGroup(new UnicodeRange(0x31F0, 0x31FF), "Katakana Phonetic Extensions", "ja", UnicodeChart.KatakanaPhoneticExtensions);
+ scripts[93].SubGroups[1] = new SubGroup(new UnicodeRange(0xFF00, 0xFFEF), "Halfwidth Katakana", "ja", UnicodeChart.HalfwidthKatakana);
+
+ scripts[94] = new Group(new UnicodeRange(0x3190, 0x319F), "East Asian Scripts", "Kanbun", "zh,ja", UnicodeChart.Kanbun);
+ scripts[95] = new Group(new UnicodeRange(0xA4D0, 0xA4FF), "East Asian Scripts", "Lisu", "zh", UnicodeChart.Lisu);
+
+ // Yi does not have code range defined. Use the range of both sub groups.
+ scripts[96] = new Group(new UnicodeRange(0xA000, 0xA4CF), "East Asian Scripts", "Yi", "zh", UnicodeChart.Yi);
+ scripts[96].SubGroups = new SubGroup[2];
+ scripts[96].SubGroups[0] = new SubGroup(new UnicodeRange(0xA000, 0xA48F), "Yi Syllables", "zh", UnicodeChart.YiSyllables);
+ scripts[96].SubGroups[1] = new SubGroup(new UnicodeRange(0xA490, 0xA4CF), "Yi Radicals", "zh", UnicodeChart.YiRadicals);
+
+ scripts[97] = new Group(new UnicodeRange(0x13A0, 0x13FF), "----n Scripts", "Cherokee", "us", UnicodeChart.Cherokee);
+ scripts[98] = new Group(new UnicodeRange(0x10440, 0x1044F), "----n Scripts", "Deseret", "us", UnicodeChart.Deseret);
+
+ scripts[99] = new Group(new UnicodeRange(0x1400, 0x167F), "----n Scripts", "Unified Canadian Aboriginal Syllabics", "ca", UnicodeChart.UnifiedCanadianAboriginalSyllabics);
+ scripts[99].SubGroups = new SubGroup[1];
+ scripts[99].SubGroups[0] = new SubGroup(new UnicodeRange(0x18B0, 0x18FF), "UCAS Extended", "ca", UnicodeChart.UnifiedCanadianAboriginalSyllabicsExtended);
+
+ scripts[100] = new Group(new UnicodeRange(0xFB00, 0xFB4F), "Other", "Alphabetic Presentation Forms", "latin,he,hy", UnicodeChart.AlphabeticPresentationForms);
+ scripts[101] = new Group(new UnicodeRange(0xFF00, 0xFFEF), "Other", "Halfwidth and Fullwidth Forms", "latin,ja", UnicodeChart.HalfwidthAndFullwidthForms);
+ scripts[102] = new Group(new UnicodeRange(0x0000, 0x007F), "Other", "ASCII Characters", "latin", UnicodeChart.AsciiCharacters);
+ }
+
+ private static void InitializeSymbolsAndPunctuation()
+ {
+ symbolsAndPunctuation[0] = new Group(new UnicodeRange(0x2000, 0x206F), "Punctuation", "General Punctuation", "any", UnicodeChart.GeneralPunctuation);
+ symbolsAndPunctuation[0].SubGroups = new SubGroup[4];
+ symbolsAndPunctuation[0].SubGroups[0] = new SubGroup(new UnicodeRange(0x0000, 0x007F), "ASCII Punctuation", "latin", UnicodeChart.AsciiPunctuation);
+ symbolsAndPunctuation[0].SubGroups[1] = new SubGroup(new UnicodeRange(0x0080, 0x00FF), "Latin-1 Punctuation", "latin", UnicodeChart.Latin1Punctuation);
+ symbolsAndPunctuation[0].SubGroups[2] = new SubGroup(new UnicodeRange(0xFE50, 0xFE6F), "Small Form Variants", "any", UnicodeChart.SmallFormVariants);
+ symbolsAndPunctuation[0].SubGroups[3] = new SubGroup(new UnicodeRange(0x2E00, 0x2E7F), "Supplemental Punctuation", "other", UnicodeChart.SupplementalPunctuation);
+
+ symbolsAndPunctuation[1] = new Group(new UnicodeRange(0x3000, 0x303F), "Punctuation", "CJK Symbols and Punctuation", "zh,ja,ko", UnicodeChart.CjkSymbolsAndPunctuation);
+ symbolsAndPunctuation[1].SubGroups = new SubGroup[3];
+ symbolsAndPunctuation[1].SubGroups[0] = new SubGroup(new UnicodeRange(0xFE30, 0xFE4F), "CJK Compatibility Forms", "zh,ja,ko", UnicodeChart.CjkCompatibilityForms);
+ symbolsAndPunctuation[1].SubGroups[1] = new SubGroup(new UnicodeRange(0xFF00, 0xFFEF), "Fullwidth ASCII Punctuation", "zh,ja,ko", UnicodeChart.FullwidthAsciiPunctuation);
+ symbolsAndPunctuation[1].SubGroups[2] = new SubGroup(new UnicodeRange(0xFE10, 0xFE1F), "Vertical Forms", "zh,ja,ko", UnicodeChart.VerticalForms);
+
+ symbolsAndPunctuation[2] = new Group(new UnicodeRange(0x2100, 0x214F), "Alphanumeric Symbols", "Letterlike Symbols", "any", UnicodeChart.LetterlikeSymbols);
+ symbolsAndPunctuation[2].SubGroups = new SubGroup[1];
+ symbolsAndPunctuation[2].SubGroups[0] = new SubGroup(new UnicodeRange(0x10190, 0x101CF), "Roman Symbols", "latin", UnicodeChart.RomanSymbols);
+
+ symbolsAndPunctuation[3] = new Group(new UnicodeRange(0x1D400, 0x1D7FF), "Alphanumeric Symbols", "Mathematical Alphanumeric Symbols", "any", UnicodeChart.MathematicalAlphanumericSymbols);
+
+ symbolsAndPunctuation[4] = new Group(new UnicodeRange(0x2460, 0x124FF), "Alphanumeric Symbols", "Enclosed Alphanumerics", "any", UnicodeChart.EnclosedAlphanumerics);
+ symbolsAndPunctuation[4].SubGroups = new SubGroup[1];
+ symbolsAndPunctuation[4].SubGroups[0] = new SubGroup(new UnicodeRange(0x1F100, 0x1F1FF), "Enclosed Alphanumerics Supplement", "any", UnicodeChart.EnclosedAlphanumericSupplement);
+
+ symbolsAndPunctuation[5] = new Group(new UnicodeRange(0x3200, 0x32FF), "Alphanumeric Symbols", "Enclosed CJK Letters and Months", "zh,ja,ko", UnicodeChart.EnclosedCjkLettersAndMonths);
+ symbolsAndPunctuation[5].SubGroups = new SubGroup[1];
+ symbolsAndPunctuation[5].SubGroups[0] = new SubGroup(new UnicodeRange(0x1F200, 0x1F2FF), "Enclosed Ideographic Supplement", "zh,ja,ko", UnicodeChart.EnclosedIdeographicSupplement);
+
+ symbolsAndPunctuation[6] = new Group(new UnicodeRange(0x3300, 0x33FF), "Alphanumeric Symbols", "CJK Compatibility", "zh,ja,ko", UnicodeChart.CjkCompatibility);
+ symbolsAndPunctuation[6].SubGroups = new SubGroup[1];
+ symbolsAndPunctuation[6].SubGroups[0] = new SubGroup(new UnicodeRange(0x2100, 0x214F), "Additional Squared Symbols", "zh,ja,ko", UnicodeChart.AdditionalSquaredSymbols);
+
+ symbolsAndPunctuation[7] = new Group(new UnicodeRange(0x2300, 0x23FF), "Technical Symbols", "APL symbols", "any", UnicodeChart.AplSymbols);
+ symbolsAndPunctuation[8] = new Group(new UnicodeRange(0x2400, 0x243F), "Technical Symbols", "Control Pictures", "any", UnicodeChart.ControlPictures);
+ symbolsAndPunctuation[9] = new Group(new UnicodeRange(0x2300, 0x23FF), "Technical Symbols", "Miscellaneous Technical", "any", UnicodeChart.MiscellaneousTechnical);
+ symbolsAndPunctuation[10] = new Group(new UnicodeRange(0x2440, 0x245F), "Technical Symbols", "Optical Character Recognition (OCR)", "any", UnicodeChart.OpticalCharacterRecognition);
+ symbolsAndPunctuation[11] = new Group(new UnicodeRange(0x20D0, 0x20FF), "Combining Diacritics", "Combining Diacritical Marks for Symbols", "other", UnicodeChart.CombiningDiacriticalMarksForSymbols);
+
+ symbolsAndPunctuation[12] = new Group(new UnicodeRange(0x10100, 0x1013F), "Numbers and Digits", "Aegean", "el", UnicodeChart.AegeanNumbers);
+ symbolsAndPunctuation[13] = new Group(new UnicodeRange(0x10140, 0x1018F), "Numbers and Digits", "Ancient Greek Numbers", "el", UnicodeChart.AncientGreekNumbers);
+
+ symbolsAndPunctuation[14] = new Group(new UnicodeRange(0x0000, 0x007F), "Numbers and Digits", "ASCII Digits", "latin", UnicodeChart.AsciiDigits);
+ symbolsAndPunctuation[14].SubGroups = new SubGroup[1];
+ symbolsAndPunctuation[14].SubGroups[0] = new SubGroup(new UnicodeRange(0xFF00, 0xFFEF), "Fullwidth ASCII Digits", "latin,ja", UnicodeChart.FullwidthAsciiDigits);
+
+ symbolsAndPunctuation[15] = new Group(new UnicodeRange(0xA830, 0xA83F), "Numbers and Digits", "Common Indic Number Forms", "hi", UnicodeChart.CommonIndicNumberForms);
+ symbolsAndPunctuation[16] = new Group(new UnicodeRange(0x1D360, 0x1D37F), "Numbers and Digits", "Counting Rod Numerals", "other", UnicodeChart.CountingRodNumerals);
+ symbolsAndPunctuation[17] = new Group(new UnicodeRange(0x12400, 0x1247F), "Numbers and Digits", "Cuneiform Numbers and Punctuation", "cuneiform", UnicodeChart.CuneiformNumbersAndPunctuation);
+ symbolsAndPunctuation[18] = new Group(new UnicodeRange(0x2150, 0x218F), "Numbers and Digits", "Number Forms", "latin", UnicodeChart.NumberForms);
+ symbolsAndPunctuation[19] = new Group(new UnicodeRange(0x10E60, 0x10E7F), "Numbers and Digits", "Rumi Numeral Symbols", "rumi", UnicodeChart.RumiNumeralSymbols);
+ symbolsAndPunctuation[20] = new Group(new UnicodeRange(0x2070, 0x209F), "Numbers and Digits", "Super and Subscripts", "any", UnicodeChart. SuperAndSubscripts);
+
+ symbolsAndPunctuation[21] = new Group(new UnicodeRange(0x2190, 0x21FF), "Mathematical Symbols", "Arrows", "latin", UnicodeChart.Arrows);
+ symbolsAndPunctuation[21].SubGroups = new SubGroup[3];
+ symbolsAndPunctuation[21].SubGroups[0] = new SubGroup(new UnicodeRange(0x27F0, 0x27FF), "Supplemental Arrows-A", "latin", UnicodeChart.SupplementalArrowsA);
+ symbolsAndPunctuation[21].SubGroups[1] = new SubGroup(new UnicodeRange(0x2900, 0x297F), "Supplemental Arrows-B", "latin", UnicodeChart.SupplementalArrowsB);
+ symbolsAndPunctuation[21].SubGroups[2] = new SubGroup(new UnicodeRange(0x2B00, 0x2BFF), "Additional Arrows", "latin", UnicodeChart.AdditionalArrows);
+
+ symbolsAndPunctuation[22] = new Group(new UnicodeRange(0x1D400, 0x1D7FF), "Mathematical Symbols", "Mathematical Alphanumeric Symbols", "latin", UnicodeChart.MathematicalAlphanumericSymbols);
+ symbolsAndPunctuation[22].SubGroups = new SubGroup[1];
+ symbolsAndPunctuation[22].SubGroups[0] = new SubGroup(new UnicodeRange(0x2100, 0x214F), "Letterlike Symbols", "latin", UnicodeChart.LetterlikeSymbols);
+
+ symbolsAndPunctuation[23] = new Group(new UnicodeRange(0x2200, 0x22FF), "Mathematical Symbols", "Mathematical Operators", "any", UnicodeChart.MathematicalOperators);
+ symbolsAndPunctuation[23].SubGroups = new SubGroup[6];
+ symbolsAndPunctuation[23].SubGroups[0] = new SubGroup(new UnicodeRange(0x0000, 0x007F), "Basic operators: Plus, Factorial,", "any", UnicodeChart.BasicOperatorsPlusFactorial);
+ symbolsAndPunctuation[23].SubGroups[1] = new SubGroup(new UnicodeRange(0x0080, 0x00FF), "Division, Multiplication", "any", UnicodeChart.BasicOperatorsDivisionMultiplication);
+ symbolsAndPunctuation[23].SubGroups[2] = new SubGroup(new UnicodeRange(0x2A00, 0x2AFF), "Supplemental Mathematical Operators", "any", UnicodeChart.SupplementalMathematicalOperators);
+ symbolsAndPunctuation[23].SubGroups[3] = new SubGroup(new UnicodeRange(0x27C0, 0x27EF), "Miscellaneous Mathematical Symbols-A", "any", UnicodeChart.MiscellaneousMathematicalSymbolsA);
+ symbolsAndPunctuation[23].SubGroups[4] = new SubGroup(new UnicodeRange(0x2980, 0x29FF), "Miscellaneous Mathematical Symbols-B", "any", UnicodeChart.MiscellaneousMathematicalSymbolsB);
+ symbolsAndPunctuation[23].SubGroups[5] = new SubGroup(new UnicodeRange(0x2300, 0x23FF), "Floors and Ceilings", "any", UnicodeChart.FloorsAndCeilings);
+
+ symbolsAndPunctuation[24] = new Group(new UnicodeRange(0x25A0, 0x25FF), "Mathematical Symbols", "Geometric Shapes", "any", UnicodeChart.GeometricShapes);
+ symbolsAndPunctuation[24].SubGroups = new SubGroup[3];
+ symbolsAndPunctuation[24].SubGroups[0] = new SubGroup(new UnicodeRange(0x2B00, 0x2BFF), "Additional Shapes", "any", UnicodeChart.AdditionalShapes);
+ symbolsAndPunctuation[24].SubGroups[1] = new SubGroup(new UnicodeRange(0x2500, 0x257F), "Box Drawing", "any", UnicodeChart.BoxDrawing);
+ symbolsAndPunctuation[24].SubGroups[2] = new SubGroup(new UnicodeRange(0x2580, 0x259F), "Block Elements", "any", UnicodeChart.BlockElements);
+
+ symbolsAndPunctuation[25] = new Group(new UnicodeRange(0x10190, 0x101CF), "Other Symbols", "Ancient Symbols", "other", UnicodeChart.AncientSymbols);
+ symbolsAndPunctuation[26] = new Group(new UnicodeRange(0x2800, 0x28FF), "Other Symbols", "Braille Patterns", "other", UnicodeChart.BraillePatterns);
+
+ symbolsAndPunctuation[27] = new Group(new UnicodeRange(0x20A0, 0x20CF), "Other Symbols", "Currency Symbols", "any", UnicodeChart.CurrencySymbols);
+ symbolsAndPunctuation[27].SubGroups = new SubGroup[7];
+ symbolsAndPunctuation[27].SubGroups[0] = new SubGroup(new UnicodeRange(0x0000, 0x007F), "Dollar Sign", "any", UnicodeChart.DollarSign);
+ symbolsAndPunctuation[27].SubGroups[1] = new SubGroup(new UnicodeRange(0x20A0, 0x20CF), "Euro Sign", "any", UnicodeChart.EuroSign);
+ symbolsAndPunctuation[27].SubGroups[2] = new SubGroup(new UnicodeRange(0x0080, 0x00FF), "Yen, Pound and Cent", "any", UnicodeChart.YenPoundAndCent);
+ symbolsAndPunctuation[27].SubGroups[3] = new SubGroup(new UnicodeRange(0xFF00, 0xFFEF), "Fullwidth Currency Symbols", "any", UnicodeChart.FullwidthCurrencySymbols);
+ symbolsAndPunctuation[27].SubGroups[4] = new SubGroup(new UnicodeRange(0x2100, 0x214F), "Mark", "de", UnicodeChart.Mark);
+ symbolsAndPunctuation[27].SubGroups[5] = new SubGroup(new UnicodeRange(0x20A0, 0x20CF), "Pfennig", "de", UnicodeChart.Pfennig);
+ symbolsAndPunctuation[27].SubGroups[6] = new SubGroup(new UnicodeRange(0xFB50, 0xFDFF), "Rial Sign", "iran", UnicodeChart.RialSign);
+
+ symbolsAndPunctuation[28] = new Group(new UnicodeRange(0x2700, 0x27BF), "Other Symbols", "Dingbats", "any", UnicodeChart.Dingbats);
+
+ // Game Symbols range is not defined. Use checker/chess range.
+ symbolsAndPunctuation[29] = new Group(new UnicodeRange(0x2600, 0x26FF), "Other Symbols", "Game Symbols", "other", UnicodeChart.GameSymbols);
+ symbolsAndPunctuation[29].SubGroups = new SubGroup[5];
+ symbolsAndPunctuation[29].SubGroups[0] = new SubGroup(new UnicodeRange(0x2600, 0x26FF), "Chess//Checkers", "other", UnicodeChart.ChessCheckers);
+ symbolsAndPunctuation[29].SubGroups[1] = new SubGroup(new UnicodeRange(0x1F030, 0x1F09F), "Domino Tiles", "other", UnicodeChart.DominoTiles);
+ symbolsAndPunctuation[29].SubGroups[2] = new SubGroup(new UnicodeRange(0x2600, 0x26FF), "Japanese Chess", "ja", UnicodeChart.JapaneseChess);
+ symbolsAndPunctuation[29].SubGroups[3] = new SubGroup(new UnicodeRange(0x1F000, 0x1F02F), "Mahjong Tiles", "ja,zh", UnicodeChart.MahjongTiles);
+ symbolsAndPunctuation[29].SubGroups[4] = new SubGroup(new UnicodeRange(0x2600, 0x26FF), "Card suits", "other", UnicodeChart.CardSuits);
+
+ symbolsAndPunctuation[30] = new Group(new UnicodeRange(0x2600, 0x26FF), "Other Symbols", "Miscellaneous Symbols", "any", UnicodeChart.MiscellaneousSymbols);
+ symbolsAndPunctuation[30].SubGroups = new SubGroup[1];
+ symbolsAndPunctuation[30].SubGroups[0] = new SubGroup(new UnicodeRange(0x2B00, 0x2BFF), "Miscellaneous Symbols and Arrows", "any", UnicodeChart.MiscellaneousSymbolsAndArrows);
+
+ symbolsAndPunctuation[31] = new Group(new UnicodeRange(0x1D100, 0x1D1FF), "Other Symbols", "Musical Symbols", "any", UnicodeChart.MusicalSymbols);
+ symbolsAndPunctuation[31].SubGroups = new SubGroup[2];
+ symbolsAndPunctuation[31].SubGroups[0] = new SubGroup(new UnicodeRange(0x1D200, 0x1D24F), "Ancient Greek Musical Notation", "el", UnicodeChart.AncientGreekMusicalNotation);
+ symbolsAndPunctuation[31].SubGroups[1] = new SubGroup(new UnicodeRange(0x1D000, 0x1D0FF), "Byzantine Musical Symbols", "other", UnicodeChart.ByzantineMusicalSymbols);
+
+ // Yijing Symbols is not defined. Use Yijing Mono-, Di- and Trigrams range.
+ symbolsAndPunctuation[32] = new Group(new UnicodeRange(0x2600, 0x26FF), "Other Symbols", "Yijing Symbols", "zh", UnicodeChart.YijingSymbols);
+ symbolsAndPunctuation[32].SubGroups = new SubGroup[3];
+ symbolsAndPunctuation[32].SubGroups[0] = new SubGroup(new UnicodeRange(0x2600, 0x26FF), "Yijing Mono-, Di- and Trigrams", "zh", UnicodeChart.YijingMonoDiAndTrigrams);
+ symbolsAndPunctuation[32].SubGroups[1] = new SubGroup(new UnicodeRange(0x4DC0, 0x4DFF), "Yijing Hexagram Symbols", "zh", UnicodeChart.YijingHexagramSymbols);
+ symbolsAndPunctuation[32].SubGroups[2] = new SubGroup(new UnicodeRange(0x1D300, 0x1D35F), "Tai Xuan Jing Symbols", "zh", UnicodeChart.TaiXuanJingSymbols);
+
+ // Controls is not defined. Use C0 and C1 range.
+ symbolsAndPunctuation[33] = new Group(new UnicodeRange(0x0000, 0x00FF), "Specials", "Controls", "any", UnicodeChart.Controls);
+ symbolsAndPunctuation[33].SubGroups = new SubGroup[4];
+ symbolsAndPunctuation[33].SubGroups[0] = new SubGroup(new UnicodeRange(0x0000, 0x007F), "C0", "any", UnicodeChart.C0);
+ symbolsAndPunctuation[33].SubGroups[1] = new SubGroup(new UnicodeRange(0x0080, 0x00FF), "C1", "any", UnicodeChart.C1);
+ symbolsAndPunctuation[33].SubGroups[2] = new SubGroup(new UnicodeRange(0x2000, 0x206F), "Layout Controls", "any", UnicodeChart.LayoutControls);
+ symbolsAndPunctuation[33].SubGroups[3] = new SubGroup(new UnicodeRange(0x2000, 0x206F), "Invisible Operators", "any", UnicodeChart.InvisibleOperators);
+
+ symbolsAndPunctuation[34] = new Group(new UnicodeRange(0xFFF0, 0xFFFF), "Specials", "Specials", "any", UnicodeChart.Specials);
+ symbolsAndPunctuation[35] = new Group(new UnicodeRange(0xE0000, 0xE007F), "Specials", "Tags", "any", UnicodeChart.Tags);
+
+ symbolsAndPunctuation[36] = new Group(new UnicodeRange(0xFE00, 0xFE0F), "Specials", "Variation Selectors", "any", UnicodeChart.VariationSelectors);
+ symbolsAndPunctuation[36].SubGroups = new SubGroup[1];
+ symbolsAndPunctuation[36].SubGroups[0] = new SubGroup(new UnicodeRange(0xE0100, 0xE01EF), "Variation Selectors Supplement", "any", UnicodeChart.VariationSelectorsSupplement);
+
+ symbolsAndPunctuation[37] = new Group(new UnicodeRange(0xE000, 0xF8FF), "Private Use", "Private Use Area", "any", UnicodeChart.PrivateUseArea);
+ symbolsAndPunctuation[38] = new Group(new UnicodeRange(0xF0000, 0xFFFFD), "Private Use", "Supplementary Private Use Area-A", "any", UnicodeChart.SupplementaryPrivateUseAreaA);
+ symbolsAndPunctuation[39] = new Group(new UnicodeRange(0x100000, 0x10FFFD), "Private Use", "Supplementary Private Use Area-B", "any", UnicodeChart.SupplementaryPrivateUseAreaB);
+
+ symbolsAndPunctuation[40] = new Group(new UnicodeRange(0xD800, 0xDBFF), "Surrogates", "High Surrogates", "zh", UnicodeChart.HighSurrogates);
+ symbolsAndPunctuation[41] = new Group(new UnicodeRange(0xDC00, 0xDFFF), "Surrogates", "Low Surrogates", "zh", UnicodeChart.LowSurrogates);
+
+ symbolsAndPunctuation[42] = new Group(new UnicodeRange(0xFB50, 0xFDFF), "Noncharacters in UnicodeCharts", "Reserved range", "any", UnicodeChart.ReservedRange);
+
+ // at end of... is not defined. Use BMP range.
+ symbolsAndPunctuation[43] = new Group(new UnicodeRange(0xFFF0, 0xFFFF), "Noncharacters in UnicodeCharts", "at end of...", "any", UnicodeChart.AtEndOf);
+ symbolsAndPunctuation[43].SubGroups = new SubGroup[17];
+ symbolsAndPunctuation[43].SubGroups[0] = new SubGroup(new UnicodeRange(0xFFF0, 0xFFFF), "BMP", "any", UnicodeChart.Bmp);
+ symbolsAndPunctuation[43].SubGroups[1] = new SubGroup(new UnicodeRange(0x1FF80, 0x1FFFF), "Plane 1", "any", UnicodeChart.Plane1);
+ symbolsAndPunctuation[43].SubGroups[2] = new SubGroup(new UnicodeRange(0x2FF80, 0x2FFFF), "Plane 2", "any", UnicodeChart.Plane2);
+ symbolsAndPunctuation[43].SubGroups[3] = new SubGroup(new UnicodeRange(0x3FF80, 0x3FFFF), "Plane 3", "any", UnicodeChart.Plane3);
+ symbolsAndPunctuation[43].SubGroups[4] = new SubGroup(new UnicodeRange(0x4FF80, 0x4FFFF), "Plane 4", "any", UnicodeChart.Plane4);
+ symbolsAndPunctuation[43].SubGroups[5] = new SubGroup(new UnicodeRange(0x5FF80, 0x5FFFF), "Plane 5", "any", UnicodeChart.Plane5);
+ symbolsAndPunctuation[43].SubGroups[6] = new SubGroup(new UnicodeRange(0x6FF80, 0x6FFFF), "Plane 6", "any", UnicodeChart.Plane6);
+ symbolsAndPunctuation[43].SubGroups[7] = new SubGroup(new UnicodeRange(0x7FF80, 0x7FFFF), "Plane 7", "any", UnicodeChart.Plane7);
+ symbolsAndPunctuation[43].SubGroups[8] = new SubGroup(new UnicodeRange(0x8FF80, 0x8FFFF), "Plane 8", "any", UnicodeChart.Plane8);
+ symbolsAndPunctuation[43].SubGroups[9] = new SubGroup(new UnicodeRange(0x9FF80, 0x9FFFF), "Plane 9", "any", UnicodeChart.Plane9);
+ symbolsAndPunctuation[43].SubGroups[10] = new SubGroup(new UnicodeRange(0xAFF80, 0xAFFFF), "Plane 10", "any", UnicodeChart.Plane10);
+ symbolsAndPunctuation[43].SubGroups[11] = new SubGroup(new UnicodeRange(0xBFF80, 0xBFFFF), "Plane 11", "any", UnicodeChart.Plane11);
+ symbolsAndPunctuation[43].SubGroups[12] = new SubGroup(new UnicodeRange(0xCFF80, 0xCFFFF), "Plane 12", "any", UnicodeChart.Plane12);
+ symbolsAndPunctuation[43].SubGroups[13] = new SubGroup(new UnicodeRange(0xdFF80, 0xDFFFF), "Plane 13", "any", UnicodeChart.Plane13);
+ symbolsAndPunctuation[43].SubGroups[14] = new SubGroup(new UnicodeRange(0xEFF80, 0xEFFFF), "Plane 14", "any", UnicodeChart.Plane14);
+ symbolsAndPunctuation[43].SubGroups[15] = new SubGroup(new UnicodeRange(0xFFF80, 0xFFFFF), "Plane 15", "any", UnicodeChart.Plane15);
+ symbolsAndPunctuation[43].SubGroups[16] = new SubGroup(new UnicodeRange(0x10FF80, 0x10FFFF), "Plane 16", "any", UnicodeChart.Plane16);
+ }
+ }
+}
+
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Text/UnicodeRangeProperty.cs b/src/dotnetCampus.UITest.WPFTestHelper/Text/UnicodeRangeProperty.cs
new file mode 100644
index 0000000..45c727f
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Text/UnicodeRangeProperty.cs
@@ -0,0 +1,58 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace dotnetCampus.UITest.WPFTestHelper.Text
+{
+ ///
+ /// UnicodeRangeProperty class to store data for a property
+ ///
+ internal class UnicodeRangeProperty
+ {
+ private TextUtil.UnicodeChartType type;
+ public string cultureIds;
+
+ ///
+ /// constructor of PropertyData stuct
+ ///
+ public UnicodeRangeProperty(TextUtil.UnicodeChartType type, string name, string ids, UnicodeRange range)
+ {
+ Type = type;
+ Name = name;
+ CultureIDs = ids;
+ Range = new UnicodeRange(range.StartOfUnicodeRange, range.EndOfUnicodeRange);
+ }
+
+ ///
+ /// Default constructor to null or zero all attributes
+ ///
+ public UnicodeRangeProperty()
+ {
+ Type = TextUtil.UnicodeChartType.Other;
+ Name = null;
+ CultureIDs = null;
+ Range = new UnicodeRange(0, 0);
+ }
+
+ ///
+ /// type of the property
+ ///
+ public TextUtil.UnicodeChartType Type { set { type = value; } }
+
+ ///
+ /// name of the property
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// culuture IDs for the property
+ ///
+ public string CultureIDs { set { cultureIds = value; } }
+
+ ///
+ /// UnicodeRange for the property
+ ///
+ public UnicodeRange Range { get; set; }
+ }
+}
+
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Theming/HwndInfo.cs b/src/dotnetCampus.UITest.WPFTestHelper/Theming/HwndInfo.cs
new file mode 100644
index 0000000..9d58923
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Theming/HwndInfo.cs
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Theming
+{
+ internal struct HwndInfo
+ {
+ public HwndInfo(IntPtr hWnd)
+ {
+ this.hWnd = hWnd;
+ NativeMethods.GetWindowThreadProcessId(hWnd, out ProcessId);
+ }
+
+ ///
+ /// Hwnd
+ ///
+ public IntPtr hWnd;
+
+ ///
+ /// Process ID of Hwnd
+ ///
+ public int ProcessId;
+
+
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Theming/NativeMethods.cs b/src/dotnetCampus.UITest.WPFTestHelper/Theming/NativeMethods.cs
new file mode 100644
index 0000000..c822edf
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Theming/NativeMethods.cs
@@ -0,0 +1,78 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Theming
+{
+ internal static class NativeMethods
+ {
+ #region Const data
+
+ private const string Gdi32Dll = "GDI32.dll";
+ private const string User32Dll = "User32.dll";
+
+ public const int SW_HIDE = 0;
+ public const int SW_SHOWNORMAL = 1;
+ public const int SW_NORMAL = 1;
+ public const int SW_SHOWMINIMIZED = 2;
+ public const int SW_SHOWMAXIMIZED = 3;
+ public const int SW_MAXIMIZE = 3;
+ public const int SW_SHOWNOACTIVATE = 4;
+ public const int SW_SHOW = 5;
+ public const int SW_MINIMIZE = 6;
+ public const int SW_SHOWMINNOACTIVE = 7;
+ public const int SW_SHOWNA = 8;
+ public const int SW_RESTORE = 9;
+ public const int SW_SHOWDEFAULT = 10;
+ public const int SW_FORCEMINIMIZE = 11;
+ public const int SW_MAX = 11;
+
+ #endregion Const data
+
+ #region Methods
+
+ [DllImport(User32Dll)]
+ public static extern IntPtr GetForegroundWindow();
+
+ [DllImport(User32Dll)]
+ public static extern bool BringWindowToTop(IntPtr hWnd);
+
+ [DllImport(User32Dll, EntryPoint = "IsWindowVisible", PreserveSig = true)]
+ public static extern bool IsWindowVisible(IntPtr hWnd);
+
+ [DllImport(User32Dll)]
+ public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
+
+ [DllImport(User32Dll)]
+ public static extern void SetForegroundWindow(IntPtr hWnd);
+
+ [DllImport(User32Dll)]
+ public static extern bool CloseWindow(IntPtr hWnd);
+
+ [DllImport(User32Dll)]
+ public static extern bool DestroyWindow(IntPtr hWnd);
+
+ [DllImport(User32Dll, EntryPoint = "GetClassName")]
+ public static extern int GetClassName(IntPtr hwnd, [MarshalAs(UnmanagedType.LPStr)] StringBuilder buf, int nMaxCount);
+
+ [DllImport(User32Dll, EntryPoint = "GetWindowText")]
+ public static extern int GetWindowText(IntPtr hwnd, [MarshalAs(UnmanagedType.LPStr)] StringBuilder buf, int nMaxCount);
+
+ [DllImport(User32Dll, EntryPoint = "EnumThreadWindows", PreserveSig = true, SetLastError = true)]
+ public static extern bool EnumThreadWindows(int threadId, EnumProcCallback callback, IntPtr lParam);
+
+ [DllImport(User32Dll, EntryPoint = "EnumChildWindows", PreserveSig = true, SetLastError = true)]
+ public static extern bool EnumChildWindows(IntPtr hWndParent, EnumProcCallback callback, IntPtr lParam);
+
+ public delegate bool EnumProcCallback(IntPtr hwnd, IntPtr lParam);
+
+ [DllImport(User32Dll)]
+ public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int processId);
+
+ #endregion Methods
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Theming/Theme.cs b/src/dotnetCampus.UITest.WPFTestHelper/Theming/Theme.cs
new file mode 100644
index 0000000..2847a30
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Theming/Theme.cs
@@ -0,0 +1,839 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using dotnetCampus.UITest.WPFTestHelper.Input;
+using Microsoft.Win32;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Theming
+{
+ ///
+ /// Enables changing of the Theme configuration of the system.
+ ///
+ ///
+ ///
+ /// The following example demonstrates changing the OS theme to each available
+ /// system theme to verify a control's appearance.
+ ///
+ ///
+ /// Theme originalTheme = Theme.GetCurrent();
+ ///
+ /// try
+ /// {
+ /// Theme[] availableThemes = Theme.GetAvailableSystemThemes();
+ /// foreach (Theme theme in availableThemes)
+ /// {
+ /// Theme.SetCurrent(theme);
+ /// VerifyMyControlAppearance(theme);
+ /// }
+ /// }
+ /// finally
+ /// {
+ /// Theme.SetCurrent(originalTheme);
+ /// }
+ ///
+ ///
+ public class Theme
+ {
+ #region Private Data
+
+ private readonly static string ThemeProcessName;
+ private readonly static string ThemeDir = Environment.ExpandEnvironmentVariables(@"%WINDIR%\Resources\Themes");
+ private readonly static string AccessibleThemesDir = Environment.ExpandEnvironmentVariables(@"%WINDIR%\Resources\Ease of Access Themes");
+
+ #endregion Private Data
+
+ #region Enums
+
+ ///
+ /// Enum for High Contrast theme names. These
+ /// do not work on Vista - ok to use on Win7+.
+ ///
+ public enum HighContrastTheme
+ {
+ ///
+ /// High Contrast #1
+ /// Background: Black
+ /// Foreground: Yellow
+ ///
+ hc1,
+ ///
+ /// High Contrast #2
+ /// Background: Black
+ /// Foreground: Green
+ ///
+ hc2,
+ ///
+ /// High Contrast Black
+ /// Background: Black
+ /// Foreground: White
+ ///
+ hcblack,
+ ///
+ /// High Contrast White
+ /// Background: White
+ /// Foreground: Black
+ ///
+ hcwhite
+ }
+
+ #endregion
+
+ #region Constructors
+
+ static Theme()
+ {
+ // 6.1 (Vista) and below have a different process for Theme automation
+ if (Environment.OSVersion.Version < new Version("6.1"))
+ {
+ ThemeProcessName = "rundll32";
+ }
+ else
+ {
+ ThemeProcessName = "explorer";
+ }
+ }
+
+ ///
+ /// No default constructor for Theme class
+ ///
+ private Theme()
+ {
+ }
+
+ ///
+ /// Constructor that takes a file and retrieves the rest
+ /// of the info directly from the theme file.
+ ///
+ private Theme(FileInfo path)
+ {
+ Path = path;
+ IsEnabled = true;
+ SetThemeProperties(this);
+ }
+
+ private Theme(FileInfo path, string name, string style, bool isEnabled)
+ {
+ Path = path;
+ Name = name;
+ Style = style;
+ IsEnabled = isEnabled;
+ }
+
+ #endregion Constructors
+
+ #region Public Static Members
+
+ ///
+ /// Returns the current OS theme.
+ ///
+ /// Returns the current OS theme.
+ public static Theme GetCurrent()
+ {
+ string themeFilename = GetCurrentThemePath();
+ string style = GetCurrentThemeStyle();
+ bool isEnabled = GetCurrentThemeIsEnabled();
+ string themeName = string.Empty;
+ if (!string.IsNullOrEmpty(themeFilename))
+ {
+ themeName = System.IO.Path.GetFileNameWithoutExtension(themeFilename);
+ }
+
+ return new Theme(new FileInfo(themeFilename), themeName, style, isEnabled);
+ }
+
+ ///
+ /// Sets the current OS theme with the given theme parameter.
+ ///
+ /// The theme to set the OS theme to.
+ public static void SetCurrent(Theme theme)
+ {
+ lock (typeof(Theme))
+ {
+ EnsureTheme(theme);
+
+ // only change the theme if we are in a different theme
+ if (GetCurrent().Path.FullName != theme.Path.FullName)
+ {
+ // Copy the file to another location before setting the theme.
+ // The reason for this is that if the custom theme is left after the test
+ // executes and a test harness possibly deletes the current theme, the system
+ // issues an error that prevents the system from restoring the default theme
+ string destFilename = System.IO.Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.InternetCache),
+ System.IO.Path.GetFileName(theme.Path.FullName));
+ File.Copy(theme.Path.FullName, destFilename, true);
+
+ // programmatically setting theme behavior is different for 6.1 and up
+ if (Environment.OSVersion.Version < new Version("6.1"))
+ {
+ MonitorProcess(ThemeProcessName, theme.Path.FullName);
+ }
+ else
+ {
+ SetThemeThroughExplorer(theme.Path.FullName);
+ }
+ }
+
+ WaitForThemeSet(theme.Path.FullName);
+ }
+ }
+
+ ///
+ /// Sets the current OS theme with the given fileInfo parameter.
+ ///
+ /// The fileInfo to set the theme to.
+ public static void SetCurrent(FileInfo fileInfo)
+ {
+ SetCurrent(new Theme(fileInfo));
+ }
+
+ ///
+ /// Sets the current OS theme using the given HighContrastTheme parameter
+ ///
+ ///
+ public static void SetCurrent(HighContrastTheme theme)
+ {
+ var themes = new List(Theme.GetAccessibleThemes());
+
+ var hcTheme = themes.Find((Theme t) =>
+ {
+ return (string.Equals(t.Name, theme.ToString(), StringComparison.InvariantCultureIgnoreCase));
+
+ });
+
+ if (hcTheme == null)
+ {
+ throw new NotSupportedException($"Theme {theme.ToString()} is not supported");
+ }
+
+ Theme.SetCurrent(hcTheme);
+ }
+
+ ///
+ /// Sets the current OS theme with the given theme parameter
+ ///
+ ///
+ ///
+ public static ThemeSwitcher SetTheme(Theme theme)
+ {
+ return new ThemeSwitcher(theme);
+ }
+
+ ///
+ /// Sets the current OS theme using the given HighContrastTheme parameter.
+ ///
+ ///
+ ///
+ public static ThemeSwitcher SetTheme(HighContrastTheme theme)
+ {
+ return new ThemeSwitcher(theme);
+ }
+
+ ///
+ /// Returns all the available system themes on the OS.
+ ///
+ /// Returns all the available system themes
+ public static Theme[] GetAvailableSystemThemes()
+ {
+ return GetThemesFromDirectory(ThemeDir);
+ }
+
+ ///
+ /// Returns all the available accessible themes on the OS
+ ///
+ ///
+ public static Theme[] GetAccessibleThemes()
+ {
+ try
+ {
+ return GetThemesFromDirectory(AccessibleThemesDir);
+ }
+ catch (Exception e)
+ {
+ throw new PlatformNotSupportedException("This method is not supported on Vista", e);
+ }
+ }
+
+
+ #endregion Public Static Members
+
+ #region Public Properties
+
+ ///
+ /// Gets the theme name.
+ ///
+ public string Name
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets the path of the theme.
+ ///
+ ///
+ /// Should be of type *.theme.
+ ///
+ public FileInfo Path
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets the color style of the theme.
+ ///
+ public string Style
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets the IsEnabled state of the theme.
+ ///
+ public bool IsEnabled
+ {
+ get;
+ private set;
+ }
+
+ #endregion Public Properties
+
+ #region Private Members
+
+ private static string GetCurrentThemePath()
+ {
+ // 6.1 (Vista) and below have a different process for Theme automation
+ if (Environment.OSVersion.Version < new Version("6.1"))
+ {
+ using (RegistryKey currentTheme = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Plus!\Themes\Current"))
+ {
+ if (currentTheme == null)
+ {
+ return null;
+ }
+
+ return currentTheme.GetValue(null) as string;
+ }
+ }
+ else
+ {
+ string themeFilename = Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes", "CurrentTheme", "") as string;
+ if (!String.IsNullOrEmpty(themeFilename))
+ {
+ return themeFilename.ToLowerInvariant();
+ }
+
+ return null;
+ }
+ }
+
+ private static string GetCurrentThemeStyle()
+ {
+ string themeStyle = Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\ThemeManager", "ColorName", "") as string;
+ if (!string.IsNullOrEmpty(themeStyle))
+ {
+ return themeStyle.ToLowerInvariant();
+ }
+
+ return null;
+ }
+
+ private static bool GetCurrentThemeIsEnabled()
+ {
+ string themeActive = (string)Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\ThemeManager", "ThemeActive", string.Empty);
+ return string.Compare(themeActive, "1") == 0;
+ }
+
+ private static void SetThemeProperties(Theme theme)
+ {
+ EnsureTheme(theme);
+
+ // find the [visualstyles]colorstyle property in the the theme file
+ string themeStyle = string.Empty;
+ using (var fs = new FileStream(theme.Path.FullName, FileMode.Open, FileAccess.Read))
+ {
+ using (var sr = new StreamReader(fs))
+ {
+ var line = sr.ReadLine();
+ while (!sr.EndOfStream && line != null)
+ {
+ if (line.ToLowerInvariant() == "[visualstyles]")
+ {
+ line = sr.ReadLine();
+ while (!sr.EndOfStream && !string.IsNullOrEmpty(line))
+ {
+ if (line.ToLowerInvariant().Contains("colorstyle"))
+ {
+ var keyValuePair = line.Split(new string[] { "=" }, StringSplitOptions.RemoveEmptyEntries);
+ if (keyValuePair != null && keyValuePair.Length == 2)
+ {
+ themeStyle = keyValuePair[1];
+ break;
+ }
+ }
+
+ line = sr.ReadLine();
+ }
+
+ break;
+ }
+
+ line = sr.ReadLine();
+ }
+ }
+ }
+
+ if (!string.IsNullOrEmpty(themeStyle))
+ {
+ theme.Style = themeStyle;
+ }
+
+ theme.Name = System.IO.Path.GetFileNameWithoutExtension(theme.Path.FullName).ToLowerInvariant();
+ }
+
+ private static void EnsureTheme(Theme theme)
+ {
+ if (theme == null)
+ {
+ throw new ArgumentException("theme cannot be null.");
+ }
+
+ if (theme.Path != null && string.IsNullOrEmpty(theme.Path.FullName))
+ {
+ throw new ArgumentException("theme path cannot be null or empty.");
+ }
+
+ if (!File.Exists(theme.Path.FullName))
+ {
+ throw new ArgumentException("The theme '" + theme.Path.FullName + "' does not exist.", theme.Path.FullName);
+ }
+
+ if (System.IO.Path.GetExtension(theme.Path.FullName).ToLowerInvariant() != ".theme")
+ {
+ throw new ArgumentException("The theme file must have a .theme extention.", "filename");
+ }
+ }
+
+ private static void WaitForThemeSet(string themeToBeSet)
+ {
+ int counter = 0;
+ int max = 20;
+
+ do
+ {
+ Thread.Sleep(1500);
+ counter++;
+ } while (counter < max && Theme.GetCurrent().Path.FullName.ToLower() != themeToBeSet.ToLower());
+ }
+
+ ///
+ /// For the case of OS versons below 6.1 the process for setting
+ /// the theme is as follows:
+ ///
+ /// 1. launch the .theme file
+ /// 2. wait for the new rundll32 to start.
+ /// 3. press enter to select the theme and close the dialog
+ ///
+ private static void MonitorProcess(string processName, string themeFilename)
+ {
+ // Kill all processes with name = processName.
+ // This will help ensure that FindProcessName will always return the
+ // one unique process that we should wait on.
+ KillProcesses(processName);
+
+ // Get the active window since the window activation will be lost
+ IntPtr activehWnd = IntPtr.Zero;
+ ManualResetEvent waitEvent = new ManualResetEvent(false);
+ System.Timers.Timer timer = null;
+ bool enterFlag = true;
+
+ try
+ {
+ // initialize the wait event
+ waitEvent.Reset();
+ activehWnd = NativeMethods.GetForegroundWindow();
+
+ // set the actual theme which launches the rundll32.exe window
+ Process themeChangeProcess = new Process();
+ themeChangeProcess.StartInfo.FileName = themeFilename;
+ themeChangeProcess.Start();
+
+ bool themeSet = false;
+
+ // wait for the window to activate
+ timer = new System.Timers.Timer(1500);
+ timer.Elapsed += (s, e) =>
+ {
+ if (enterFlag)
+ {
+ enterFlag = false;
+
+ // find the process to monitor
+ Process process = FindProcessFromName(processName);
+ if (process != null)
+ {
+ process.Refresh();
+
+ HwndInfo[] topLevelWindows = WindowEnumerator.GetTopLevelVisibleWindows(process);
+ if (topLevelWindows.Length > 0)
+ {
+ // In case the dialog was opened previously
+ NativeMethods.SetForegroundWindow(topLevelWindows[0].hWnd);
+ Thread.Sleep(1000);
+
+ // The process monitor calls back more than once, only do this action once.
+ if (!themeSet)
+ {
+ themeSet = true;
+
+ // press enter to confirm and set the theme
+ Keyboard.Type(Key.Return);
+ }
+ }
+ }
+ else
+ {
+ // For good measure
+ Thread.Sleep(1000);
+ waitEvent.Set();
+ }
+
+ enterFlag = true;
+ }
+ };
+ timer.Start();
+
+ waitEvent.WaitOne(60000, false);
+ }
+ finally
+ {
+ enterFlag = false;
+
+ if (timer != null)
+ {
+ timer.Stop();
+ timer.Dispose();
+ timer = null;
+ }
+
+ // Restore the active window
+ if (activehWnd != IntPtr.Zero)
+ {
+ NativeMethods.SetForegroundWindow(activehWnd);
+ }
+ }
+ }
+
+ private static List FindProcessesFromName(string processName)
+ {
+ Process[] currentProcesses = new Process[0];
+ List result = new List();
+
+ try
+ { // Workaround whidbey
+ currentProcesses = Process.GetProcesses();
+ }
+ catch (Win32Exception)
+ {
+ //Unable to enumerate Process Moduals (this is probobly a whidbey
+ }
+
+ foreach (Process process in currentProcesses)
+ {
+ // Get the name and ensure that the process is still running
+ string procName;
+ try
+ {
+ procName = process.ProcessName;
+ // Query to process to ensure that we have access to it and it has not exited
+ if (process.HasExited)
+ {
+ continue;
+ }
+ }
+ catch (InvalidOperationException)
+ {
+ // the process has exited
+ continue;
+ }
+ catch (Win32Exception)
+ {
+ // strange whidbey
+
+ continue;
+ }
+
+ if (string.Equals(procName, processName, StringComparison.InvariantCultureIgnoreCase))
+ {
+ result.Add(process);
+ }
+ }
+
+ return result;
+ }
+
+ private static Process FindProcessFromName(string processName)
+ {
+ List processes = FindProcessesFromName(processName);
+
+ if ((processes.Count != 1) || (processes[0].HasExited))
+ {
+ throw new Exception(string.Format("Unique process with name: {0} not found", processName));
+ }
+
+ return processes[0];
+ }
+
+ private static void KillProcesses(string processName)
+ {
+ List processes = FindProcessesFromName(processName);
+
+ foreach (Process process in processes)
+ {
+ if (process.HasExited)
+ {
+ continue;
+ }
+
+ process.Kill();
+ }
+ }
+
+
+
+ ///
+ /// For the case of OS versons 6.1 and above the process for setting
+ /// the theme is as follows:
+ ///
+ /// 1. launch the .theme file (theme is automatically selected)
+ /// 2. wait for explorer to launch the 'personalization' child window
+ /// 3. close the 'personalization' window
+ ///
+ /// Unlike MonitorProcess, explorer.exe is already running and a subwindow is launched for
+ /// this process.
+ ///
+ private static void SetThemeThroughExplorer(string themeFilename)
+ {
+ // Get the active window since the window activation will be lost
+ // by lauching the ControlPanel.Personalization window.
+ IntPtr activehWnd = IntPtr.Zero;
+ ManualResetEvent waitEvent = new ManualResetEvent(false);
+ System.Timers.Timer timer = null;
+ bool enterFlag = true;
+
+ try
+ {
+ // initialize the wait event
+ waitEvent.Reset();
+ activehWnd = NativeMethods.GetForegroundWindow();
+
+ // set the actual theme which launches the personalization window
+ Process themeChangeProcess = new Process();
+ themeChangeProcess.StartInfo.FileName = themeFilename;
+ themeChangeProcess.StartInfo.UseShellExecute = true;
+ themeChangeProcess.Start();
+
+ // wait for the window to activate
+ timer = new System.Timers.Timer(1500);
+ timer.Elapsed += (s, e) =>
+ {
+ if (enterFlag)
+ {
+ enterFlag = false;
+
+ foreach (var process in Process.GetProcesses())
+ {
+ if (process.ProcessName.ToLower() == ThemeProcessName)
+ {
+ IntPtr hWnd = WindowEnumerator.FindFirstWindowWithCaption(process, "personalization");
+ if (hWnd != IntPtr.Zero)
+ {
+ // first make sure it's active
+ int maxCounter = 20;
+ int counter = 0;
+ IntPtr foregroundHWnd = NativeMethods.GetForegroundWindow();
+ Console.WriteLine("Thread: " + Thread.CurrentThread.ManagedThreadId + ", foregourndHWnd: " + foregroundHWnd + ", hWnd: " + hWnd);
+ while ((foregroundHWnd != hWnd || !NativeMethods.IsWindowVisible(hWnd)) &&
+ counter < maxCounter &&
+ timer != null)
+ {
+ Console.WriteLine("Thread: " + Thread.CurrentThread.ManagedThreadId + ", restore the window. hWnd: " + hWnd + ", foregroundHWnd: " + foregroundHWnd);
+
+ NativeMethods.ShowWindow(hWnd, NativeMethods.SW_RESTORE);
+ Thread.Sleep(500);
+
+ NativeMethods.BringWindowToTop(hWnd);
+ Thread.Sleep(500);
+
+ foregroundHWnd = NativeMethods.GetForegroundWindow();
+ counter++;
+ }
+
+ if (foregroundHWnd == hWnd)
+ {
+ timer.Stop();
+
+ CloseAndWaitForWindow();
+ waitEvent.Set();
+ }
+
+ break;
+ }
+ }
+ }
+
+ enterFlag = true;
+ }
+ };
+ timer.Start();
+
+ waitEvent.WaitOne(60000, false);
+ }
+ finally
+ {
+ enterFlag = false;
+
+ if (timer != null)
+ {
+ timer.Stop();
+ timer.Dispose();
+ timer = null;
+ }
+
+ // Restore the active window
+ if (activehWnd != IntPtr.Zero)
+ {
+ NativeMethods.SetForegroundWindow(activehWnd);
+ }
+ }
+ }
+
+ private static void CloseAndWaitForWindow()
+ {
+ // close the window
+ Keyboard.Press(Key.Alt);
+ Keyboard.Press(Key.F4);
+ Keyboard.Release(Key.F4);
+ Keyboard.Release(Key.Alt);
+
+ int counter = 0;
+ int max = 20;
+ bool found = true;
+ while (counter < max && found)
+ {
+ Thread.Sleep(1000);
+ counter++;
+
+ found = false;
+ foreach (var process in Process.GetProcesses())
+ {
+ if (process.ProcessName.ToLower() == ThemeProcessName)
+ {
+ IntPtr hWnd = WindowEnumerator.FindFirstWindowWithCaption(process, "personalization");
+ if (hWnd != IntPtr.Zero)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private static Theme[] GetThemesFromDirectory(string directory)
+ {
+ string[] files = Directory.GetFiles(directory, "*.theme");
+ Theme[] themes = new Theme[files.Length];
+ for (int i = 0; i < files.Length; i++)
+ {
+ themes[i] = new Theme(new FileInfo(files[i]));
+ }
+
+ return themes;
+ }
+
+ #endregion
+
+ #region Public Types
+
+ ///
+ /// Helper class capable of reverting to original theme
+ /// using the IDispose pattern
+ ///
+ public class ThemeSwitcher: IDisposable
+ {
+
+ #region IDisposable Support
+
+ private bool _disposed = false; // To detect redundant calls
+
+ ///
+ ///
+ ///
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+ Theme.SetCurrent(_originalTheme);
+ }
+ }
+
+ ///
+ ///
+ ///
+ ~ThemeSwitcher()
+ {
+ Dispose(false);
+ }
+
+ ///
+ ///
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ #endregion
+
+ ///
+ ///
+ ///
+ ///
+ public ThemeSwitcher(Theme theme):this()
+ {
+ Theme.SetCurrent(theme);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public ThemeSwitcher(HighContrastTheme theme): this()
+ {
+ Theme.SetCurrent(theme);
+ }
+
+ private ThemeSwitcher()
+ {
+ _originalTheme = Theme.GetCurrent();
+ }
+
+ Theme _originalTheme;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/Theming/WindowEnumerator.cs b/src/dotnetCampus.UITest.WPFTestHelper/Theming/WindowEnumerator.cs
new file mode 100644
index 0000000..877bd3f
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/Theming/WindowEnumerator.cs
@@ -0,0 +1,182 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Text;
+
+namespace dotnetCampus.UITest.WPFTestHelper.Theming
+{
+ ///
+ /// Class for Enumerating Window Handles
+ ///
+ internal static class WindowEnumerator
+ {
+ internal static HwndInfo[] GetTopLevelVisibleWindows(Process process)
+ {
+ ProcessThreadCollection threads;
+
+ try
+ {
+ threads = process.Threads;
+ }
+ catch (InvalidOperationException)
+ {
+ //the process has exited
+ return new HwndInfo[0];
+ }
+ catch (Win32Exception)
+ {
+ //The process has already exited or access is denied (this is probobly a whidbey
+ return new HwndInfo[0];
+ }
+
+ var list = new List();
+ foreach (ProcessThread thread in process.Threads)
+ {
+ NativeMethods.EnumThreadWindows(
+ thread.Id,
+ (IntPtr hWnd, IntPtr lParam) =>
+ {
+ if (NativeMethods.IsWindowVisible(hWnd))
+ {
+ list.Add(new HwndInfo(hWnd));
+ }
+
+ return true;
+ },
+ IntPtr.Zero);
+ }
+
+ return list.ToArray();
+ }
+
+ internal static HwndInfo[] GetVisibleWindows(Process process)
+ {
+ var list = new List();
+ var windows = GetTopLevelVisibleWindows(process);
+ foreach (HwndInfo window in windows)
+ {
+ if (NativeMethods.IsWindowVisible(window.hWnd))
+ {
+ list.Add(new HwndInfo(window.hWnd));
+ }
+
+ // search child windows
+ NativeMethods.EnumChildWindows(
+ window.hWnd,
+ (IntPtr hWnd, IntPtr lParam) =>
+ {
+ if (NativeMethods.IsWindowVisible(hWnd))
+ {
+ list.Add(new HwndInfo(hWnd));
+ }
+
+ return true;
+ },
+ IntPtr.Zero);
+
+ }
+
+ return list.ToArray();
+ }
+
+ internal static IntPtr FindFirstWindowWithClassName(Process process, string classname)
+ {
+ var foundhWnd = IntPtr.Zero;
+ var windows = GetTopLevelVisibleWindows(process);
+ foreach (HwndInfo window in windows)
+ {
+ if (IsVisibleWithClassName(window.hWnd, classname))
+ {
+ return window.hWnd;
+ }
+
+ //search child windows
+ NativeMethods.EnumChildWindows(
+ window.hWnd,
+ (IntPtr hWnd, IntPtr lParam) =>
+ {
+ if (IsVisibleWithClassName(hWnd, classname))
+ {
+ foundhWnd = hWnd;
+ return false;
+ }
+ return true;
+ },
+ IntPtr.Zero);
+
+ }
+
+ return foundhWnd;
+ }
+
+ internal static bool IsVisibleWithClassName(IntPtr hWnd, string classname)
+ {
+ var sb = new StringBuilder(256);
+ NativeMethods.GetClassName(hWnd, sb, 256);
+ return (NativeMethods.IsWindowVisible(hWnd) && sb.ToString().Contains(classname));
+ }
+
+ internal static IntPtr FindFirstWindowWithCaption(Process process, string caption)
+ {
+ IntPtr foundhWnd = IntPtr.Zero;
+ HwndInfo[] windows = GetTopLevelVisibleWindows(process);
+ foreach (HwndInfo window in windows)
+ {
+ if (IsVisibleWithCaption(window.hWnd, caption))
+ {
+ return window.hWnd;
+ }
+
+ //search child windows
+ NativeMethods.EnumChildWindows(
+ window.hWnd,
+ (IntPtr hWnd, IntPtr lParam) =>
+ {
+ if (IsVisibleWithCaption(hWnd, caption))
+ {
+ foundhWnd = hWnd;
+ return false;
+ }
+
+ return true;
+ },
+ IntPtr.Zero);
+ }
+
+ return foundhWnd;
+ }
+
+ internal static bool IsVisibleWithCaption(IntPtr hWnd, string caption)
+ {
+ var sb = new StringBuilder(256);
+ NativeMethods.GetWindowText(hWnd, sb, 256);
+ return (NativeMethods.IsWindowVisible(hWnd) && sb.ToString().ToLower().Contains(caption.ToLower()));
+ }
+
+ internal static HwndInfo[] GetOutOfProcessVisibleChildWindows(IntPtr parenthWnd)
+ {
+ var parentWindow = new HwndInfo(parenthWnd);
+ var list = new List();
+ NativeMethods.EnumChildWindows(
+ parenthWnd,
+ (IntPtr hWnd, IntPtr lParam) =>
+ {
+ var childWindow = new HwndInfo(hWnd);
+ if (NativeMethods.IsWindowVisible(hWnd) && parentWindow.ProcessId != childWindow.ProcessId)
+ {
+ list.Add(childWindow);
+ }
+
+ return true;
+ },
+ IntPtr.Zero);
+
+ return list.ToArray();
+ }
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/VariationGeneration/CandidateCoverage.cs b/src/dotnetCampus.UITest.WPFTestHelper/VariationGeneration/CandidateCoverage.cs
new file mode 100644
index 0000000..cd49f55
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/VariationGeneration/CandidateCoverage.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+namespace dotnetCampus.UITest.WPFTestHelper.VariationGeneration
+{
+ ///
+ /// Pairs a combination in consideration for addition to a variation with how many combinations it will cover
+ ///
+ internal class CandidateCoverage
+ {
+ public ValueCombination Value { get; set; }
+ public int CoverageCount { get; set; }
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/VariationGeneration/Constraint.cs b/src/dotnetCampus.UITest.WPFTestHelper/VariationGeneration/Constraint.cs
new file mode 100644
index 0000000..f165c7b
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/VariationGeneration/Constraint.cs
@@ -0,0 +1,78 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+using System;
+using System.Linq.Expressions;
+using dotnetCampus.UITest.WPFTestHelper.VariationGeneration.Constraints;
+
+namespace dotnetCampus.UITest.WPFTestHelper.VariationGeneration
+{
+ ///
+ /// Represents a relationship between parameters and their values, or other constraints.
+ ///
+ ///
+ /// Exhaustively testing all possible inputs to any nontrivial software component is generally not possible
+ /// because of the enormous number of variations. Combinatorial testing is one approach that achieves high coverage
+ /// with a much smaller set of variations. Pairwise, the most common combinatorial strategy, tests every possible
+ /// pair of values. Higher orders of combinations (three-wise, four-wise, and so on) can also be used for higher coverage
+ /// at the expense of more variations. See Pairwise Testing and
+ ///
+ /// Pairwise Testing in Real World for more resources.
+ ///
+ /// Ideally, all parameters in a model are independent; however, this is generally not the case. Constraints define
+ /// combinations of values that are impossible in the variations produced by the using
+ /// combinatorial testing techniques.
+ ///
+
+ public abstract class Constraint where T : new()
+ {
+ ///
+ /// Calculates the exclusions for this constraint.
+ ///
+ /// The model
+ ///
+ /// A table containing the interaction between parameters for this constraint. All values are marked Excluded or Covered.
+ ///
+ internal abstract ParameterInteraction GetExcludedCombinations(Model model);
+
+ ///
+ /// Calcultes whether the specified value satisfies the constraint or has insufficient data to do so.
+ ///
+ /// The model.
+ /// The value.
+ /// The calculated result.
+ internal abstract ConstraintSatisfaction SatisfiesContraint(Model model, ValueCombination combination);
+
+ ///
+ /// Holds a precalculated to avoid recalculation.
+ ///
+ internal ParameterInteraction CachedInteraction { get; set; }
+
+ ///
+ /// Clears CachedInteraction for the constraint and any children.
+ ///
+ internal abstract void ClearCache();
+
+ ///
+ /// Creates a new predicate for an if-then-else constraint.
+ ///
+ /// The test as an expression.
+ /// The predicate.
+ public static IfPredicate If(Expression> predicate)
+ {
+ return new IfPredicate(predicate);
+ }
+
+ ///
+ /// Creates a new ConditionalConstraint.
+ ///
+ /// The test as an expression.
+ /// The constraint.
+ public static ConditionalConstraint Conditional(Expression> predicate)
+ {
+ return new ConditionalConstraint(predicate);
+ }
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/VariationGeneration/ConstraintExtensions.cs b/src/dotnetCampus.UITest.WPFTestHelper/VariationGeneration/ConstraintExtensions.cs
new file mode 100644
index 0000000..fe2e064
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/VariationGeneration/ConstraintExtensions.cs
@@ -0,0 +1,41 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+using System;
+using System.Linq.Expressions;
+using dotnetCampus.UITest.WPFTestHelper.VariationGeneration.Constraints;
+
+namespace dotnetCampus.UITest.WPFTestHelper.VariationGeneration
+{
+ ///
+ /// Holds extension methods that construct if-then-else constraints.
+ ///
+ public static class ConstraintExtensions
+ {
+ ///
+ /// Constructs an if-then constraint.
+ ///
+ /// Type of the variation being acted on.
+ /// The "if" portion of the if-then.
+ /// The test of the "then".
+ /// The if-then constraint.
+ public static IfThenConstraint Then(this IfPredicate ifPredicate, Expression> predicate) where T : new()
+ {
+ return new IfThenConstraint(ifPredicate, predicate);
+ }
+
+ ///
+ /// Constructs an if-then-else constraint.
+ ///
+ /// Type of the variation being acted on.
+ /// The "if-then" portion of the if-then-else.
+ /// The test of the "else".
+ /// The if-then-else constraint.
+ public static IfThenElseConstraint Else(this IfThenConstraint ifThenConstraint, Expression> predicate) where T : new()
+ {
+ return new IfThenElseConstraint(ifThenConstraint, predicate);
+ }
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/VariationGeneration/ConstraintSatisfaction.cs b/src/dotnetCampus.UITest.WPFTestHelper/VariationGeneration/ConstraintSatisfaction.cs
new file mode 100644
index 0000000..6cf9aae
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/VariationGeneration/ConstraintSatisfaction.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+namespace dotnetCampus.UITest.WPFTestHelper.VariationGeneration
+{
+ internal enum ConstraintSatisfaction
+ {
+ Satisfied,
+ Unsatisfied,
+ InsufficientData
+ }
+}
diff --git a/src/dotnetCampus.UITest.WPFTestHelper/VariationGeneration/Constraints/CachedExpressionConstraintData.cs b/src/dotnetCampus.UITest.WPFTestHelper/VariationGeneration/Constraints/CachedExpressionConstraintData.cs
new file mode 100644
index 0000000..e4ab928
--- /dev/null
+++ b/src/dotnetCampus.UITest.WPFTestHelper/VariationGeneration/Constraints/CachedExpressionConstraintData.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+using System.Collections.Generic;
+
+namespace dotnetCampus.UITest.WPFTestHelper.VariationGeneration.Constraints
+{
+ class CachedExpressionConstraintData
+ {
+ public IList