├── .github └── workflows │ ├── build.yml │ └── pull-request.yml ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── LICENSE.md ├── README.md ├── TAlex.WPF.Controls.Tests ├── Converters │ ├── BooleanToVisibilityConverterTests.cs │ ├── CamelTextToRegularTextConverterTests.cs │ ├── ColorToBrushConverterTests.cs │ ├── DoubleToStringConverterTests.cs │ ├── IntToDecimalConverterTests.cs │ ├── IsNotNullToBoolConverterTests.cs │ └── NotEmptyStringToBoolConverterTests.cs ├── Helpers │ └── WPFVisualHelperTests.cs ├── Media │ └── ColorUtilitiesTests.cs └── TAlex.WPF.Controls.Tests.csproj ├── TAlex.WPF.Controls.sln ├── TAlex.WPF.Controls ├── CommonDialogs │ ├── ColorPickerDialog.xaml │ ├── ColorPickerDialog.xaml.cs │ ├── FolderBrowserDialog.cs │ ├── FontChooserDialog.xaml │ ├── FontChooserDialog.xaml.cs │ ├── InsertHyperlinkDialog.xaml │ ├── InsertHyperlinkDialog.xaml.cs │ ├── InsertImageDialog.xaml │ ├── InsertImageDialog.xaml.cs │ ├── InsertTableDialog.xaml │ └── InsertTableDialog.xaml.cs ├── Controls │ ├── BusyIndicator.cs │ ├── ColorChip.cs │ ├── ColorComboBox.xaml │ ├── ColorComboBox.xaml.cs │ ├── HtmlRichEditor.xaml │ ├── HtmlRichEditor.xaml.cs │ ├── ImageEx.cs │ └── NumericUpDown.cs ├── Converters │ ├── BooleanToVisibilityConverter.cs │ ├── CamelTextToRegularTextConverter.cs │ ├── ColorToBrushConverter.cs │ ├── ConverterBase.cs │ ├── DoubleToStringConverter.cs │ ├── EnumToBooleanConverter.cs │ ├── Int32ToDecimalConverter.cs │ ├── Int32ToDoubleConverter.cs │ ├── IsNotNullToBooleanConverter.cs │ ├── NotEmptyStringToBooleanConverter.cs │ └── WidthUnitToMaxWidthConverter.cs ├── Helpers │ ├── EnumerableExtensions.cs │ └── WPFVisualHelper.cs ├── Media │ ├── ColorItem.cs │ ├── ColorUtilities.cs │ └── HsvColor.cs ├── Models │ └── HtmlObject.cs ├── Properties │ └── AssemblyInfo.cs ├── Resources │ └── Fonts │ │ └── GLYPHICONS Halflings.ttf ├── Services │ ├── DragAndDrop │ │ ├── DragAdorner.cs │ │ ├── DragAndDropService.cs │ │ ├── InsertionAdorner.cs │ │ └── ItemsControlDragDropManager.cs │ └── PushBinding │ │ ├── FreezableBinding.cs │ │ ├── PushBinding.cs │ │ ├── PushBindingCollection.cs │ │ └── PushBindingManager.cs ├── TAlex.WPF.Controls.csproj ├── Themes │ ├── BusyIndicator.xaml │ ├── ColorChip.xaml │ ├── Generic.xaml │ ├── NumericUpDown.xaml │ └── NumericUpDownTwilight.xaml ├── Theming │ ├── IThemeManager.cs │ ├── ThemeInfo.cs │ └── ThemeLocator.cs └── dlls │ └── MSHTML.dll └── TAlex.WPFControlsDemo ├── App.xaml ├── App.xaml.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs └── TAlex.WPFControlsDemo.csproj /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: windows-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Setup .NET 15 | uses: actions/setup-dotnet@v3 16 | with: 17 | dotnet-version: | 18 | 3.1.x 19 | 6.0.x 20 | - name: Clean 21 | run: dotnet clean --configuration Release && dotnet nuget locals all --clear 22 | - name: Build 23 | run: dotnet build --configuration Release 24 | - name: Test 25 | run: dotnet test --configuration Release --no-build --verbosity normal 26 | - name: Publish 27 | run: dotnet nuget push **/*.nupkg 28 | --api-key ${{ secrets.NUGET_DEPLOY_KEY }} 29 | --source https://api.nuget.org/v3/index.json 30 | --skip-duplicate 31 | --no-symbols true 32 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: windows-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Setup .NET 15 | uses: actions/setup-dotnet@v3 16 | with: 17 | dotnet-version: | 18 | 3.1.x 19 | 6.0.x 20 | - name: Clean 21 | run: dotnet clean --configuration Release && dotnet nuget locals all --clear 22 | - name: Build 23 | run: dotnet build --configuration Release 24 | - name: Test 25 | run: dotnet test --configuration Release --no-build --verbosity normal 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #OS junk files 2 | [Tt]humbs.db 3 | *.DS_Store 4 | 5 | #Visual Studio files 6 | *.[Oo]bj 7 | *.user 8 | *.aps 9 | *.pch 10 | *.vspscc 11 | *.vssscc 12 | *_i.c 13 | *_p.c 14 | *.ncb 15 | *.suo 16 | *.tlb 17 | *.tlh 18 | *.bak 19 | *.[Cc]ache 20 | *.ilk 21 | *.log 22 | *.lib 23 | *.sbr 24 | *.sdf 25 | *.opensdf 26 | *.unsuccessfulbuild 27 | ipch/ 28 | obj/ 29 | [Bb]in 30 | [Dd]ebug*/ 31 | [Rr]elease*/ 32 | Ankh.NoLoad 33 | 34 | #MonoDevelop 35 | *.pidb 36 | *.userprefs 37 | 38 | #Tooling 39 | _ReSharper*/ 40 | *.resharper 41 | [Tt]est[Rr]esult* 42 | 43 | #Project files 44 | [Bb]uild/ 45 | _[Bb]uild/ 46 | 47 | #Subversion files 48 | .svn 49 | 50 | # Office Temp Files 51 | ~$* 52 | 53 | #NuGet 54 | *.nupkg 55 | [Nn]u[Gg]et.exe 56 | packages/ 57 | 58 | #ncrunch 59 | *ncrunch* 60 | *crunch*.local.xml 61 | 62 | # visual studio database projects 63 | *.dbmdl 64 | 65 | # Visual Studio 66 | .vs/ -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/TAlex.WPFControlsDemo/bin/Debug/netcoreapp3.1/WPFControlsDemo.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/TAlex.WPFControlsDemo", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach", 24 | "processId": "${command:pickProcess}" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/TAlex.WPFControlsDemo/TAlex.WPFControlsDemo.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/TAlex.WPFControlsDemo/TAlex.WPFControlsDemo.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/TAlex.WPFControlsDemo/TAlex.WPFControlsDemo.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Alex Titarenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WPF Controls 2 | ![Build](https://github.com/alex-titarenko/wpfcontrols/workflows/Build/badge.svg?branch=main) 3 | [![NuGet Version](http://img.shields.io/nuget/v/TAlex.WPF.Controls.svg?style=flat)](https://www.nuget.org/packages/TAlex.WPF.Controls/) [![NuGet Downloads](http://img.shields.io/nuget/dt/TAlex.WPF.Controls.svg?style=flat)](https://www.nuget.org/packages/TAlex.WPF.Controls/) 4 | 5 | Set of Controls for WPF. 6 | 7 | ## Features 8 | 9 | #### Controls 10 | * **ColorChip** - color picker control with advance functionality like in Photoshop. 11 | * **ColorComboBox** - combo box with predefined list of standard html colors with ability to choose custom color. 12 | * **HtmlRichEditor** - rich html visual editor which allow to format your text in WYSIWYG mode. Supporting inserting images, tables, font decorations and etc. 13 | * **ImageEx** - extends standard image control to be able play animation from sets of usual images like png. 14 | * **NumericUpDown** - control provides a TextBox with button spinners that allow incrementing and decrementing numeric values by using the spinner buttons, keyboard up/down arrows, or mouse wheel. 15 | * **BusyIndicator** - helps to notify user when your application is busy with appropriate indicator. 16 | 17 | #### Converters 18 | * **BooleanToVisibilityConverter** - converts boolean value to Visibility and vice versa. 19 | * **CamelTextToRegularTextConverter** - converts camel case text like *"HelloWorld"* to regual text like *"Hello World"* 20 | * **ColorToBrushConverter** - converts color to brush. 21 | * **DoubleToStringConverter** - converts double value to string and vice versa. 22 | * **EnumToBooleanConverter** - converts any Enum value to boolean and vice verse. 23 | * **Int32ToDecimalConverter** - converts Int32 numeric value to it's equivalent decimal value. 24 | * **Int32ToDoubleConverter** - converts Int32 numeric value to it's equivalent double value. 25 | * **IsNotNullToBooleanConverter** - converts any not nullable object to true and vice versa. 26 | * **NotEmptyStringToBooleanConverter** - converts any not empty string to true and vice versa. 27 | 28 | 29 | ## Get it on NuGet! 30 | 31 | Install-Package TAlex.WPF.Controls 32 | 33 | ## License 34 | WPFControls is under the [MIT license](LICENSE.md). 35 | -------------------------------------------------------------------------------- /TAlex.WPF.Controls.Tests/Converters/BooleanToVisibilityConverterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Windows; 6 | using TAlex.WPF.Converters; 7 | using NUnit.Framework; 8 | 9 | 10 | namespace TAlex.WPF.Controls.Tests.Converters 11 | { 12 | [TestFixture] 13 | public class BooleanToVisibilityConverterTests 14 | { 15 | protected BooleanToVisibilityConverter Target; 16 | 17 | [SetUp] 18 | public void SetUp() 19 | { 20 | Target = new BooleanToVisibilityConverter(); 21 | } 22 | 23 | 24 | #region Convert 25 | 26 | [Test] 27 | public void Convert_Null_Collapsed() 28 | { 29 | //arrange 30 | Visibility expected = Visibility.Collapsed; 31 | 32 | //action 33 | Visibility actual = (Visibility)Target.Convert((bool?)null, typeof(Visibility), null, null); 34 | 35 | //assert 36 | Assert.AreEqual(expected, actual); 37 | } 38 | 39 | [Test] 40 | public void Convert_True_Visible() 41 | { 42 | //arrange 43 | Visibility expected = Visibility.Visible; 44 | 45 | //action 46 | Visibility actual = (Visibility)Target.Convert(true, typeof(Visibility), null, null); 47 | 48 | //assert 49 | Assert.AreEqual(expected, actual); 50 | } 51 | 52 | [Test] 53 | public void Convert_False_Collapsed() 54 | { 55 | //arrange 56 | Visibility expected = Visibility.Collapsed; 57 | 58 | //action 59 | Visibility actual = (Visibility)Target.Convert(false, typeof(Visibility), null, null); 60 | 61 | //assert 62 | Assert.AreEqual(expected, actual); 63 | } 64 | 65 | [Test] 66 | public void Convert_FalseWithUseHidden_Hidden() 67 | { 68 | //arrange 69 | Target.UseHidden = true; 70 | Visibility expected = Visibility.Hidden; 71 | 72 | //action 73 | Visibility actual = (Visibility)Target.Convert(false, typeof(Visibility), null, null); 74 | 75 | //assert 76 | Assert.AreEqual(expected, actual); 77 | } 78 | 79 | #endregion 80 | 81 | #region ConvertBack 82 | 83 | [Test] 84 | public void ConvertBack_Visible_True() 85 | { 86 | //arrange 87 | bool expected = true; 88 | 89 | //action 90 | bool actual = (bool)Target.ConvertBack(Visibility.Visible, typeof(bool), null, null); 91 | 92 | //assert 93 | Assert.AreEqual(expected, actual); 94 | } 95 | 96 | [Test] 97 | public void ConvertBack_HiddenOrCollapsed_False([Values(Visibility.Hidden, Visibility.Collapsed)]Visibility visibility) 98 | { 99 | //arrange 100 | bool expected = false; 101 | 102 | //action 103 | bool actual = (bool)Target.ConvertBack(visibility, typeof(bool), null, null); 104 | 105 | //assert 106 | Assert.AreEqual(expected, actual); 107 | } 108 | 109 | #endregion 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /TAlex.WPF.Controls.Tests/Converters/CamelTextToRegularTextConverterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using TAlex.WPF.Converters; 6 | using NUnit.Framework; 7 | 8 | 9 | namespace TAlex.WPF.Controls.Tests.Converters 10 | { 11 | [TestFixture] 12 | public class CamelTextToRegularTextConverterTests 13 | { 14 | protected CamelTextToRegularTextConverter target; 15 | 16 | [SetUp] 17 | public void SetUp() 18 | { 19 | target = new CamelTextToRegularTextConverter(); 20 | } 21 | 22 | 23 | #region Convert 24 | 25 | [Test] 26 | public void Convert_CamelText_RegularText() 27 | { 28 | //arrange 29 | string expected = "Some Text"; 30 | 31 | //action 32 | string actual = target.Convert("SomeText", typeof(string), null, null) as String; 33 | 34 | //assert 35 | Assert.AreEqual(expected, actual); 36 | } 37 | 38 | [Test] 39 | public void Convert_Acronym_Acronym() 40 | { 41 | //arrange 42 | string expected = "IBM"; 43 | 44 | //action 45 | string actual = target.Convert("IBM", typeof(string), null, null) as String; 46 | 47 | //assert 48 | Assert.AreEqual(expected, actual); 49 | } 50 | 51 | [Test] 52 | public void Convert_MixedText_RegularTextWithAcronym() 53 | { 54 | //arrange 55 | string expected = "Learn WCF In Six Easy Months"; 56 | 57 | //action 58 | string actual = target.Convert("LearnWCFInSixEasyMonths", typeof(string), null, null) as String; 59 | 60 | //assert 61 | Assert.AreEqual(expected, actual); 62 | } 63 | 64 | [Test] 65 | public void Convert_AlphaNumericText_RegularText() 66 | { 67 | //arrange 68 | string expected = "P346 Sid"; 69 | 70 | //action 71 | string actual = target.Convert("P346Sid", typeof(string), null, null) as String; 72 | 73 | //assert 74 | Assert.AreEqual(expected, actual); 75 | } 76 | 77 | #endregion 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /TAlex.WPF.Controls.Tests/Converters/ColorToBrushConverterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Windows.Media; 6 | using TAlex.WPF.Converters; 7 | using NUnit.Framework; 8 | 9 | 10 | namespace TAlex.WPF.Controls.Tests.Converters 11 | { 12 | [TestFixture] 13 | public class ColorToBrushConverterTests 14 | { 15 | protected ColorToBrushConverter Target; 16 | 17 | [SetUp] 18 | public void SetUp() 19 | { 20 | Target = new ColorToBrushConverter(); 21 | } 22 | 23 | 24 | #region Convert 25 | 26 | [Test] 27 | public void Convert_Null_Null() 28 | { 29 | //action 30 | SolidColorBrush actual = Target.Convert(null, typeof(Brush), null, null) as SolidColorBrush; 31 | 32 | //assert 33 | Assert.IsNull(actual); 34 | } 35 | 36 | [Test] 37 | public void Convert_Color_AppropriateBrush() 38 | { 39 | //arrange 40 | SolidColorBrush expected = Brushes.Orange; 41 | 42 | //action 43 | SolidColorBrush actual = Target.Convert(Colors.Orange, typeof(Brush), null, null) as SolidColorBrush; 44 | 45 | //assert 46 | Assert.AreEqual(expected.Color, actual.Color); 47 | } 48 | 49 | #endregion 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /TAlex.WPF.Controls.Tests/Converters/DoubleToStringConverterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Globalization; 6 | using System.Windows.Controls; 7 | using TAlex.WPF.Converters; 8 | using NUnit.Framework; 9 | 10 | 11 | namespace TAlex.WPF.Controls.Tests.Converters 12 | { 13 | [TestFixture] 14 | public class DoubleToStringConverterTests 15 | { 16 | protected DoubleToStringConverter Target; 17 | 18 | [SetUp] 19 | public void SetUp() 20 | { 21 | Target = new DoubleToStringConverter(); 22 | } 23 | 24 | 25 | #region Convert 26 | 27 | [Test] 28 | public void Convert_DoubleNumber_AppropriateString() 29 | { 30 | //arrange 31 | string expected = "36.457"; 32 | 33 | //action 34 | string actual = Target.Convert(36.457, typeof(String), null, CultureInfo.InvariantCulture) as String; 35 | 36 | //assert 37 | Assert.AreEqual(expected, actual); 38 | } 39 | 40 | #endregion 41 | 42 | #region ConvertBack 43 | 44 | [Test] 45 | public void ConvertBack_NumberString_AppropriateDoubleNumber() 46 | { 47 | //arrange 48 | double expected = 36.457; 49 | 50 | //action 51 | double actual = (double)Target.ConvertBack("36.457", typeof(Double), null, CultureInfo.InvariantCulture); 52 | 53 | //assert 54 | Assert.AreEqual(expected, actual); 55 | } 56 | 57 | [Test] 58 | public void ConvertBack_NotNumberString_ValidationResult() 59 | { 60 | //action 61 | object actual = Target.ConvertBack("test", typeof(Double), null, CultureInfo.InvariantCulture); 62 | 63 | //assert 64 | Assert.IsInstanceOf(actual); 65 | } 66 | 67 | #endregion 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /TAlex.WPF.Controls.Tests/Converters/IntToDecimalConverterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using TAlex.WPF.Converters; 6 | using NUnit.Framework; 7 | 8 | 9 | namespace TAlex.WPF.Controls.Tests.Converters 10 | { 11 | [TestFixture] 12 | public class IntToDecimalConverterTests 13 | { 14 | protected Int32ToDecimalConverter Target; 15 | 16 | [SetUp] 17 | public void SetUp() 18 | { 19 | Target = new Int32ToDecimalConverter(); 20 | } 21 | 22 | 23 | #region Convert 24 | 25 | [Test] 26 | public void Convert_IntNumber_AppropriateDecimal() 27 | { 28 | //arrange 29 | decimal expected = 346; 30 | 31 | //action 32 | decimal actual = (decimal)Target.Convert(346, typeof(Decimal), null, null); 33 | 34 | //assert 35 | Assert.AreEqual(expected, actual); 36 | } 37 | 38 | #endregion 39 | 40 | #region ConvertBack 41 | 42 | [Test] 43 | public void ConvertBack_Decimal_AppropriateIntNumber() 44 | { 45 | //arrange 46 | int expected = 346; 47 | 48 | //action 49 | int actual = (int)Target.ConvertBack(346M, typeof(int), null, null); 50 | 51 | //assert 52 | Assert.AreEqual(expected, actual); 53 | } 54 | 55 | #endregion 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /TAlex.WPF.Controls.Tests/Converters/IsNotNullToBoolConverterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using TAlex.WPF.Converters; 6 | using NUnit.Framework; 7 | 8 | 9 | namespace TAlex.WPF.Controls.Tests.Converters 10 | { 11 | [TestFixture] 12 | public class IsNotNullToBoolConverterTests 13 | { 14 | protected IsNotNullToBooleanConverter Target; 15 | 16 | [SetUp] 17 | public void SetUp() 18 | { 19 | Target = new IsNotNullToBooleanConverter(); 20 | } 21 | 22 | 23 | #region Convert 24 | 25 | [Test] 26 | public void Convert_SomeString_True() 27 | { 28 | //arrange 29 | bool expected = true; 30 | 31 | //action 32 | bool actual = (bool)Target.Convert("Some string", typeof(Boolean), null, null); 33 | 34 | //assert 35 | Assert.AreEqual(expected, actual); 36 | } 37 | 38 | [Test] 39 | public void Convert_Null_False() 40 | { 41 | //arrange 42 | bool expected = false; 43 | 44 | //action 45 | bool actual = (bool)Target.Convert(null, typeof(Boolean), null, null); 46 | 47 | //assert 48 | Assert.AreEqual(expected, actual); 49 | } 50 | 51 | [Test] 52 | public void Convert_IsReversedWithNull_True() 53 | { 54 | //arrange 55 | Target.IsReversed = true; 56 | 57 | //action 58 | bool actual = (bool)Target.Convert(null, typeof(Boolean), null, null); 59 | 60 | //assert 61 | Assert.AreEqual(true, actual); 62 | } 63 | 64 | [Test] 65 | public void Convert_IsReversedWithNumber_False() 66 | { 67 | //arrange 68 | Target.IsReversed = true; 69 | 70 | //action 71 | bool actual = (bool)Target.Convert(5, typeof(Boolean), null, null); 72 | 73 | //assert 74 | Assert.AreEqual(false, actual); 75 | } 76 | 77 | #endregion 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /TAlex.WPF.Controls.Tests/Converters/NotEmptyStringToBoolConverterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using TAlex.WPF.Converters; 6 | using NUnit.Framework; 7 | 8 | 9 | namespace TAlex.WPF.Controls.Tests.Converters 10 | { 11 | [TestFixture] 12 | public class NotEmptyStringToBoolConverterTests 13 | { 14 | protected NotEmptyStringToBooleanConverter Target; 15 | 16 | [SetUp] 17 | public void SetUp() 18 | { 19 | Target = new NotEmptyStringToBooleanConverter(); 20 | } 21 | 22 | 23 | #region Convert 24 | 25 | [Test] 26 | public void Convert_SomeText_True() 27 | { 28 | //arrange 29 | bool expected = true; 30 | 31 | //action 32 | bool actual = (bool)Target.Convert("Some text", typeof(bool), null, null); 33 | 34 | //assert 35 | Assert.AreEqual(expected, actual); 36 | } 37 | 38 | [TestCase("")] 39 | [TestCase(" \t ")] 40 | [TestCase(null)] 41 | public void Convert_Whitespaces_False(string value) 42 | { 43 | //arrange 44 | bool expected = false; 45 | 46 | //action 47 | bool actual = (bool)Target.Convert(value, typeof(bool), null, null); 48 | 49 | //assert 50 | Assert.AreEqual(expected, actual); 51 | } 52 | 53 | [Test] 54 | public void Convert_IsReversedWithEmptyString_True() 55 | { 56 | //arrange 57 | Target.IsReversed = true; 58 | 59 | //action 60 | bool actual = (bool)Target.Convert(String.Empty, typeof(bool), null, null); 61 | 62 | //assert 63 | Assert.AreEqual(true, actual); 64 | } 65 | 66 | [Test] 67 | public void Convert_IsReversedWithSomeText_False() 68 | { 69 | //arrange 70 | Target.IsReversed = true; 71 | 72 | //action 73 | bool actual = (bool)Target.Convert("Universe", typeof(bool), null, null); 74 | 75 | //assert 76 | Assert.AreEqual(false, actual); 77 | } 78 | 79 | #endregion 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /TAlex.WPF.Controls.Tests/Helpers/WPFVisualHelperTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Xaml; 6 | using System.Windows; 7 | using System.Windows.Documents; 8 | using System.Windows.Controls; 9 | using TAlex.WPF.Helpers; 10 | using NUnit.Framework; 11 | 12 | 13 | namespace TAlex.WPF.Controls.Tests.Helpers 14 | { 15 | [TestFixture] 16 | public class WPFVisualHelperTests 17 | { 18 | #region FindAncestor 19 | 20 | [Test, Ignore("STAThread attribute is not supported in .NET Core")] 21 | public void FindAncestor_SomeDependencyObject_Ancestor() 22 | { 23 | //arrange 24 | FrameworkElement visualTree = XamlServices.Parse(@" 25 | 26 | 27 | 28 | 29 | 30 | 31 | ") as FrameworkElement; 32 | 33 | //action 34 | DependencyObject foundElement = WPFVisualHelper.FindAncestor(visualTree.FindName("itemImage") as DependencyObject); 35 | 36 | //assert 37 | Assert.IsNotNull(foundElement); 38 | } 39 | 40 | [Test, Ignore("STAThread attribute is not supported in .NET Core")] 41 | public void FindAncestor_FindNotExistingAncestor_Null() 42 | { 43 | //arrange 44 | FrameworkElement visualTree = XamlServices.Parse(@" 45 | 46 | 47 | 48 | 49 | 50 | 51 | ") as FrameworkElement; 52 | 53 | //action 54 | DependencyObject notFoundElement = WPFVisualHelper.FindAncestor(visualTree.FindName("itemImage") as DependencyObject); 55 | 56 | //assert 57 | Assert.IsNull(notFoundElement); 58 | } 59 | 60 | [Test] 61 | public void FindAncestor_NotVisualElement_Null() 62 | { 63 | //arrange 64 | DependencyObject visualTree = XamlServices.Parse(@" 65 | 66 | Some Text 67 | 68 | ") as DependencyObject; 69 | 70 | //action 71 | DependencyObject item = WPFVisualHelper.FindAncestor(visualTree); 72 | 73 | //assert 74 | Assert.IsNull(item); 75 | } 76 | 77 | #endregion 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /TAlex.WPF.Controls.Tests/Media/ColorUtilitiesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Windows.Media; 6 | using TAlex.WPF.Media; 7 | using NUnit.Framework; 8 | 9 | 10 | namespace TAlex.WPF.Controls.Tests.Media 11 | { 12 | [TestFixture] 13 | public class ColorUtilitiesTests 14 | { 15 | #region GetHexCode 16 | 17 | [Test] 18 | public void GetHexCode_Color_ColorString() 19 | { 20 | //arrange 21 | string expected = "#FFFF4500"; 22 | 23 | //action 24 | string actual = ColorUtilities.GetHexCode(Colors.OrangeRed); 25 | 26 | //assert 27 | Assert.AreEqual(expected, actual); 28 | } 29 | 30 | #endregion 31 | 32 | #region ColorToBgra32 33 | 34 | [Test] 35 | public void ColorToBgra32_Color_IntColor() 36 | { 37 | //arrange 38 | int expected; 39 | unchecked { expected = (int)0xFF98FB98; } 40 | 41 | //action 42 | int actual = ColorUtilities.ColorToBgra32(Colors.PaleGreen); 43 | 44 | //assert 45 | Assert.AreEqual(expected, actual); 46 | } 47 | 48 | #endregion 49 | 50 | #region IsKnownColor 51 | 52 | [Test] 53 | public void IsKnownColorTest_KnownColor_True() 54 | { 55 | //arrange 56 | bool expected = true; 57 | string expectedName = "Pale Goldenrod"; 58 | string actualName; 59 | 60 | //action 61 | bool actual = ColorUtilities.IsKnownColor(Colors.PaleGoldenrod, out actualName); 62 | 63 | //assert 64 | Assert.AreEqual(expected, actual); 65 | Assert.AreEqual(expectedName, actualName); 66 | } 67 | 68 | [Test] 69 | public void IsKnownColorTest_NotKnownColor_False() 70 | { 71 | //arrange 72 | bool expected = false; 73 | string actualName = null; 74 | 75 | //action 76 | bool actual = ColorUtilities.IsKnownColor(Color.FromArgb(255, 35, 122, 108), out actualName); 77 | 78 | //assert 79 | Assert.AreEqual(expected, actual); 80 | Assert.IsNull(actualName); 81 | } 82 | 83 | #endregion 84 | 85 | #region ParseColor 86 | 87 | [Test] 88 | public void ParseColorTest_Null_ThrowException() 89 | { 90 | //action 91 | TestDelegate action = () => ColorUtilities.ParseColor(null); 92 | 93 | // assert 94 | Assert.Throws(action); 95 | } 96 | 97 | [Test] 98 | public void ParseColorTest_EmptyString_ThrowException() 99 | { 100 | //action 101 | TestDelegate action = () => ColorUtilities.ParseColor(String.Empty); 102 | 103 | // assert 104 | Assert.Throws(action); 105 | } 106 | 107 | [Test] 108 | public void ParseColorTest_IncorrectFormat_ThrowException() 109 | { 110 | //action 111 | TestDelegate action = () => ColorUtilities.ParseColor("Some String"); 112 | 113 | // assert 114 | Assert.Throws(action); 115 | } 116 | 117 | [TestCase("#FF40E0D0", 255, 64, 224, 208)] 118 | [TestCase("#FF40E0D", 15, 244, 14, 13)] 119 | [TestCase("#FF40E0", 255, 255, 64, 224)] 120 | [TestCase("#FF40E", 255, 15, 244, 14)] 121 | [TestCase("#FF40", 255, 0, 255, 64)] 122 | [TestCase("#FF4", 255, 0, 15, 244)] 123 | [TestCase("#FF", 255, 0, 0, 255)] 124 | [TestCase("#F", 255, 0, 0, 15)] 125 | public void ParseColorTest_HexValueString_Color(string s, byte a, byte r, byte g, byte b) 126 | { 127 | //arrange 128 | Color expected = Color.FromArgb(a, r, g, b); 129 | 130 | //action 131 | Color actual = ColorUtilities.ParseColor(s); 132 | 133 | //assert 134 | Assert.AreEqual(expected, actual); 135 | } 136 | 137 | [TestCase("OrangeRed", 255, 255, 69, 0)] 138 | [TestCase("ORANGE RED", 255, 255, 69, 0)] 139 | public void ParseColorTest_KnownNameColorString_Color(string s, byte a, byte r, byte g, byte b) 140 | { 141 | //arrange 142 | Color expected = Color.FromArgb(a, r, g, b); 143 | 144 | //action 145 | Color actual = ColorUtilities.ParseColor(s); 146 | 147 | //assert 148 | Assert.AreEqual(expected, actual); 149 | } 150 | 151 | #endregion 152 | 153 | #region TryParseColor 154 | 155 | [Test] 156 | public void TryParseColor_HexColorString_TrueAndColor() 157 | { 158 | //arrange 159 | Color expectedColor = Colors.Green; 160 | bool expected = true; 161 | Color actualColor; 162 | 163 | //action 164 | bool actual = ColorUtilities.TryParseColor("#FF008000", out actualColor); 165 | 166 | //assert 167 | Assert.AreEqual(expected, actual); 168 | Assert.AreEqual(expectedColor, actualColor); 169 | } 170 | 171 | [Test] 172 | public void TryParseColor_IncorrectFormatString_FalseAndTransparentColor() 173 | { 174 | //arrange 175 | Color expectedColor = Colors.Transparent; 176 | bool expected = false; 177 | Color actualColor; 178 | 179 | //action 180 | bool actual = ColorUtilities.TryParseColor("Some String", out actualColor); 181 | 182 | //assert 183 | Assert.AreEqual(expected, actual); 184 | Assert.AreEqual(expectedColor, actualColor); 185 | } 186 | 187 | #endregion 188 | 189 | #region ColorToBgra32 190 | 191 | [Test] 192 | public void ColorToBgra32Test_ColorChannelValues_BgraIntColor() 193 | { 194 | //arrange 195 | int expected; 196 | unchecked { expected = (int)0xFF98FB98; } 197 | Color c = Colors.PaleGreen; 198 | 199 | //action 200 | int actual = ColorUtilities.ColorToBgra32(c.R, c.G, c.B); 201 | 202 | //assert 203 | Assert.AreEqual(expected, actual); 204 | } 205 | 206 | #endregion 207 | 208 | #region RgbToHsv 209 | 210 | [Test] 211 | public void RgbToHsv_RgbColor_HsvColor() 212 | { 213 | //arrange 214 | Color rgbColor = Colors.Azure; 215 | 216 | //action 217 | HsvColor hsvColor = ColorUtilities.RgbToHsv(rgbColor); 218 | 219 | //assert 220 | Assert.AreEqual(rgbColor, ColorUtilities.HsvToRgb(hsvColor)); 221 | } 222 | 223 | #endregion 224 | 225 | #region HsvToRgb 226 | 227 | [Test] 228 | public void HsvToRgb_HsvColor_RgbColor() 229 | { 230 | //arrange 231 | HsvColor hsvColor = new HsvColor(180, 0.05882354003329282, 1); 232 | 233 | //action 234 | Color rgbColor = ColorUtilities.HsvToRgb(hsvColor); 235 | 236 | //assert 237 | Assert.AreEqual(hsvColor, ColorUtilities.RgbToHsv(rgbColor)); 238 | } 239 | 240 | #endregion 241 | 242 | #region FuzzyColorEquals 243 | 244 | [Test] 245 | public void FuzzyColorEquals_RgbFuzzyEqualColors_True() 246 | { 247 | //arrange 248 | bool expected = true; 249 | Color color1 = Color.FromScRgb(1f, 0.1f, 0.5f, 1f); 250 | Color color2 = Color.FromScRgb(1f, 0.101f, 0.5f, 1f); 251 | 252 | //action 253 | bool actual = ColorUtilities.FuzzyColorEquals(color1, color2); 254 | 255 | //assert 256 | Assert.AreNotEqual(color1, color2); 257 | Assert.AreEqual(expected, actual); 258 | } 259 | 260 | [Test] 261 | public void FuzzyColorEquals_RgbFuzzyUnequalColors_False() 262 | { 263 | //arrange 264 | bool expected = false; 265 | Color color1 = Color.FromScRgb(1f, 0.1f, 0.5f, 1f); 266 | Color color2 = Color.FromScRgb(1f, 0.2f, 0.5f, 1f); 267 | 268 | //action 269 | bool actual = ColorUtilities.FuzzyColorEquals(color1, color2); 270 | 271 | //assert 272 | Assert.AreNotEqual(color1, color2); 273 | Assert.AreEqual(expected, actual); 274 | } 275 | 276 | [Test] 277 | public void FuzzyColorEquals_HsvFuzzyEqualColors_True() 278 | { 279 | //arrange 280 | bool expected = true; 281 | HsvColor color1 = new HsvColor(155, 0.5, 0.8); 282 | HsvColor color2 = new HsvColor(155.346, 0.504, 0.8); 283 | 284 | //action 285 | bool actual = ColorUtilities.FuzzyColorEquals(color1, color2); 286 | 287 | //assert 288 | Assert.AreNotEqual(color1, color2); 289 | Assert.AreEqual(expected, actual); 290 | } 291 | 292 | [Test] 293 | public void FuzzyColorEquals_HsvFuzzyUnequalColors_False() 294 | { 295 | //arrange 296 | bool expected = false; 297 | HsvColor color1 = new HsvColor(155, 0.5, 0.8); 298 | HsvColor color2 = new HsvColor(156, 0.5, 0.8); 299 | 300 | //action 301 | bool actual = ColorUtilities.FuzzyColorEquals(color1, color2); 302 | 303 | //assert 304 | Assert.AreNotEqual(color1, color2); 305 | Assert.AreEqual(expected, actual); 306 | } 307 | 308 | #endregion 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /TAlex.WPF.Controls.Tests/TAlex.WPF.Controls.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | TAlex.WPF.Controls.Tests 6 | TAlex.WPF.Controls.Tests 7 | true 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /TAlex.WPF.Controls.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TAlex.WPFControlsDemo", "TAlex.WPFControlsDemo\TAlex.WPFControlsDemo.csproj", "{22849379-DEF7-410A-AC23-41596B2B3981}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TAlex.WPF.Controls", "TAlex.WPF.Controls\TAlex.WPF.Controls.csproj", "{5FD73C9D-35C2-4E7A-8CB6-B9BAFFF6DA0F}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{6A8B470F-A5EC-4762-B0FB-D72E98E470CD}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TAlex.WPF.Controls.Tests", "TAlex.WPF.Controls.Tests\TAlex.WPF.Controls.Tests.csproj", "{5C7DD595-69C7-4C79-BAC1-7ADB7B369C10}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{1788FBFF-7299-40CE-986A-002BB22280E0}" 15 | ProjectSection(SolutionItems) = preProject 16 | .nuget\NuGet.Config = .nuget\NuGet.Config 17 | .nuget\NuGet.targets = .nuget\NuGet.targets 18 | EndProjectSection 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {22849379-DEF7-410A-AC23-41596B2B3981}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {22849379-DEF7-410A-AC23-41596B2B3981}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {22849379-DEF7-410A-AC23-41596B2B3981}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {22849379-DEF7-410A-AC23-41596B2B3981}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {5FD73C9D-35C2-4E7A-8CB6-B9BAFFF6DA0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {5FD73C9D-35C2-4E7A-8CB6-B9BAFFF6DA0F}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {5FD73C9D-35C2-4E7A-8CB6-B9BAFFF6DA0F}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {5FD73C9D-35C2-4E7A-8CB6-B9BAFFF6DA0F}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {5C7DD595-69C7-4C79-BAC1-7ADB7B369C10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {5C7DD595-69C7-4C79-BAC1-7ADB7B369C10}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {5C7DD595-69C7-4C79-BAC1-7ADB7B369C10}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {5C7DD595-69C7-4C79-BAC1-7ADB7B369C10}.Release|Any CPU.Build.0 = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(SolutionProperties) = preSolution 40 | HideSolutionNode = FALSE 41 | EndGlobalSection 42 | GlobalSection(NestedProjects) = preSolution 43 | {5C7DD595-69C7-4C79-BAC1-7ADB7B369C10} = {6A8B470F-A5EC-4762-B0FB-D72E98E470CD} 44 | EndGlobalSection 45 | EndGlobal 46 | -------------------------------------------------------------------------------- /TAlex.WPF.Controls/CommonDialogs/ColorPickerDialog.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | R: 117 | 118 | 119 | G: 120 | 121 | 122 | B: 123 | 124 | 125 | H: 126 | 127 | 128 | S: 129 | 130 | 131 | B: 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /TAlex.WPF.Controls/CommonDialogs/InsertHyperlinkDialog.xaml: -------------------------------------------------------------------------------- 1 |  8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 |