├── .gitignore
├── CurrencyTextBoxSample.png
├── LICENSE
├── Logo.docx
├── Logo.png
├── README.md
├── example.gif
└── src
├── CurrencyTextBoxControl.sln
├── CurrencyTextBoxControl
├── CurrencyTextBox.cs
├── CurrencyTextBox.csproj
├── KeyValidator.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
├── Themes
│ └── Generic.xaml
└── license.txt
└── CurrencyTextBoxExample
├── App.xaml
├── App.xaml.cs
├── CurrencyTextBox.Sample.csproj
├── MainWindow.xaml
├── MainWindow.xaml.cs
└── Properties
├── AssemblyInfo.cs
├── Resources.Designer.cs
├── Resources.resx
├── Settings.Designer.cs
└── Settings.settings
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 |
24 | # Visual Studio 2015 cache/options directory
25 | .vs/
26 | # Uncomment if you have tasks that create the project's static files in wwwroot
27 | #wwwroot/
28 |
29 | # MSTest test Results
30 | [Tt]est[Rr]esult*/
31 | [Bb]uild[Ll]og.*
32 |
33 | # NUNIT
34 | *.VisualState.xml
35 | TestResult.xml
36 |
37 | # Build Results of an ATL Project
38 | [Dd]ebugPS/
39 | [Rr]eleasePS/
40 | dlldata.c
41 |
42 | # DNX
43 | project.lock.json
44 | artifacts/
45 |
46 | *_i.c
47 | *_p.c
48 | *_i.h
49 | *.ilk
50 | *.meta
51 | *.obj
52 | *.pch
53 | *.pdb
54 | *.pgc
55 | *.pgd
56 | *.rsp
57 | *.sbr
58 | *.tlb
59 | *.tli
60 | *.tlh
61 | *.tmp
62 | *.tmp_proj
63 | *.log
64 | *.vspscc
65 | *.vssscc
66 | .builds
67 | *.pidb
68 | *.svclog
69 | *.scc
70 |
71 | # Chutzpah Test files
72 | _Chutzpah*
73 |
74 | # Visual C++ cache files
75 | ipch/
76 | *.aps
77 | *.ncb
78 | *.opendb
79 | *.opensdf
80 | *.sdf
81 | *.cachefile
82 |
83 | # Visual Studio profiler
84 | *.psess
85 | *.vsp
86 | *.vspx
87 | *.sap
88 |
89 | # TFS 2012 Local Workspace
90 | $tf/
91 |
92 | # Guidance Automation Toolkit
93 | *.gpState
94 |
95 | # ReSharper is a .NET coding add-in
96 | _ReSharper*/
97 | *.[Rr]e[Ss]harper
98 | *.DotSettings.user
99 |
100 | # JustCode is a .NET coding add-in
101 | .JustCode
102 |
103 | # TeamCity is a build add-in
104 | _TeamCity*
105 |
106 | # DotCover is a Code Coverage Tool
107 | *.dotCover
108 |
109 | # NCrunch
110 | _NCrunch_*
111 | .*crunch*.local.xml
112 | nCrunchTemp_*
113 |
114 | # MightyMoose
115 | *.mm.*
116 | AutoTest.Net/
117 |
118 | # Web workbench (sass)
119 | .sass-cache/
120 |
121 | # Installshield output folder
122 | [Ee]xpress/
123 |
124 | # DocProject is a documentation generator add-in
125 | DocProject/buildhelp/
126 | DocProject/Help/*.HxT
127 | DocProject/Help/*.HxC
128 | DocProject/Help/*.hhc
129 | DocProject/Help/*.hhk
130 | DocProject/Help/*.hhp
131 | DocProject/Help/Html2
132 | DocProject/Help/html
133 |
134 | # Click-Once directory
135 | publish/
136 |
137 | # Publish Web Output
138 | *.[Pp]ublish.xml
139 | *.azurePubxml
140 | # TODO: Comment the next line if you want to checkin your web deploy settings
141 | # but database connection strings (with potential passwords) will be unencrypted
142 | *.pubxml
143 | *.publishproj
144 |
145 | # NuGet Packages
146 | *.nupkg
147 | # The packages folder can be ignored because of Package Restore
148 | **/packages/*
149 | # except build/, which is used as an MSBuild target.
150 | !**/packages/build/
151 | # Uncomment if necessary however generally it will be regenerated when needed
152 | #!**/packages/repositories.config
153 | # NuGet v3's project.json files produces more ignoreable files
154 | *.nuget.props
155 | *.nuget.targets
156 |
157 | # Microsoft Azure Build Output
158 | csx/
159 | *.build.csdef
160 |
161 | # Microsoft Azure Emulator
162 | ecf/
163 | rcf/
164 |
165 | # Microsoft Azure ApplicationInsights config file
166 | ApplicationInsights.config
167 |
168 | # Windows Store app package directory
169 | AppPackages/
170 | BundleArtifacts/
171 |
172 | # Visual Studio cache files
173 | # files ending in .cache can be ignored
174 | *.[Cc]ache
175 | # but keep track of directories ending in .cache
176 | !*.[Cc]ache/
177 |
178 | # Others
179 | ClientBin/
180 | ~$*
181 | *~
182 | *.dbmdl
183 | *.dbproj.schemaview
184 | *.pfx
185 | *.publishsettings
186 | node_modules/
187 | orleans.codegen.cs
188 |
189 | # RIA/Silverlight projects
190 | Generated_Code/
191 |
192 | # Backup & report files from converting an old project file
193 | # to a newer Visual Studio version. Backup files are not needed,
194 | # because we have git ;-)
195 | _UpgradeReport_Files/
196 | Backup*/
197 | UpgradeLog*.XML
198 | UpgradeLog*.htm
199 |
200 | # SQL Server files
201 | *.mdf
202 | *.ldf
203 |
204 | # Business Intelligence projects
205 | *.rdl.data
206 | *.bim.layout
207 | *.bim_*.settings
208 |
209 | # Microsoft Fakes
210 | FakesAssemblies/
211 |
212 | # GhostDoc plugin setting file
213 | *.GhostDoc.xml
214 |
215 | # Node.js Tools for Visual Studio
216 | .ntvs_analysis.dat
217 |
218 | # Visual Studio 6 build log
219 | *.plg
220 |
221 | # Visual Studio 6 workspace options file
222 | *.opt
223 |
224 | # Visual Studio LightSwitch build output
225 | **/*.HTMLClient/GeneratedArtifacts
226 | **/*.DesktopClient/GeneratedArtifacts
227 | **/*.DesktopClient/ModelManifest.xml
228 | **/*.Server/GeneratedArtifacts
229 | **/*.Server/ModelManifest.xml
230 | _Pvt_Extensions
231 |
232 | # Paket dependency manager
233 | .paket/paket.exe
234 |
235 | # FAKE - F# Make
236 | .fake/
237 |
--------------------------------------------------------------------------------
/CurrencyTextBoxSample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abbaye/WpfCurrencyTextbox/621964db83c7bd88f368121669d54fdf5b37370a/CurrencyTextBoxSample.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Adam Anderson, Derek Tremblay
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.
22 |
--------------------------------------------------------------------------------
/Logo.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abbaye/WpfCurrencyTextbox/621964db83c7bd88f368121669d54fdf5b37370a/Logo.docx
--------------------------------------------------------------------------------
/Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abbaye/WpfCurrencyTextbox/621964db83c7bd88f368121669d54fdf5b37370a/Logo.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://www.nuget.org/packages/CurrencyTextBox/)
4 | [](https://www.microsoft.com/net/download/windows)
5 | [](https://github.com/abbaye/wpf-currency-textbox/blob/ExpendedFunctionality/LICENSE)
6 |
7 | A Wpf TextBox for entering a currency/numeric/percent value, similar to how a cash register works.
8 |
9 | ## Samples of use screenshot
10 | 
11 |
12 | ## Features
13 | - Numbers typed are pushed in from the right. If we start with the default value 0.00, and start typing the numbers 123, the value updates as such: 0.00 => 0.01 => 0.12 => 1.23
14 | - You can set Maximum and Minimum value.
15 | - If we press the backspace key, the numbers are shifted right: 1.23 => 0.12 => 0.01 => 0.00
16 | - If we press the delete key, the value is reset to 0.00.
17 | - If we press the minus key, the value becomes negative.
18 | - If we press down/up key, the value are increase/decrease
19 | - Support stringformat C to C6.
20 | - Support stringformat N to N6.
21 | - Support stringformat P to P6.
22 | - Copy and paste decimal value. ctrl+c / ctrl+v
23 | - Undo/Redo value with default ctrl+z / ctrl+y
24 | - This control's template can be customized to change the appearance.
25 | - Supports data validation.
26 | - Auto color (Red/Black) when negative or positive value
27 | - ...
28 |
29 | ## Release Notes
30 | New in version 1.4.0
31 | - ADD support of percent string format (P to P6)
32 |
33 | New in version 1.3.1
34 | - BUG FIX When popup closed the window do not freeze
35 | - Little desing update un popup
36 | - Add PopupClosed event
37 |
38 | New in version 1.3.0
39 | - Add support of Undolimit
40 | - Add functionality ADD/REMOVE popup (Enter Key when CanShowAddPanel = true)
41 |
42 | New in version 1.2.0
43 | - Add support of numeric value (Stringformat N to N6)
44 |
45 | New in version 1.1.1
46 | - Validate MinimumValue / MaximumValue dependency properties
47 | - Validate StringFormat dependency property
48 |
49 | New in version 1.1.0
50 | - Add support of Undo/Redo
51 | - Add Copy/Paste
52 |
53 | ## How to use
54 | Add a reference to `CurrencyTextBoxControl.dll` from your project, then add the following namespace to your XAML:
55 |
56 | ```xaml
57 | xmlns:currency="clr-namespace:CurrencyTextBoxControl;assembly=CurrencyTextBoxControl"
58 | ```
59 |
60 | Insert the control like this:
61 |
62 | ```xaml
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
77 |
78 |
79 | ```
80 |
--------------------------------------------------------------------------------
/example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abbaye/WpfCurrencyTextbox/621964db83c7bd88f368121669d54fdf5b37370a/example.gif
--------------------------------------------------------------------------------
/src/CurrencyTextBoxControl.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29613.14
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CurrencyTextBox", "CurrencyTextBoxControl\CurrencyTextBox.csproj", "{144A2EE7-09EC-4618-BA94-6593205962C9}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CurrencyTextBox.Sample", "CurrencyTextBoxExample\CurrencyTextBox.Sample.csproj", "{13F3D236-4FC1-433F-9F2E-E0932035A5AD}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Debug|Mixed Platforms = Debug|Mixed Platforms
14 | Debug|x86 = Debug|x86
15 | Release|Any CPU = Release|Any CPU
16 | Release|Mixed Platforms = Release|Mixed Platforms
17 | Release|x86 = Release|x86
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {144A2EE7-09EC-4618-BA94-6593205962C9}.Debug|Any CPU.ActiveCfg = Release|Any CPU
21 | {144A2EE7-09EC-4618-BA94-6593205962C9}.Debug|Any CPU.Build.0 = Release|Any CPU
22 | {144A2EE7-09EC-4618-BA94-6593205962C9}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
23 | {144A2EE7-09EC-4618-BA94-6593205962C9}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
24 | {144A2EE7-09EC-4618-BA94-6593205962C9}.Debug|x86.ActiveCfg = Debug|Any CPU
25 | {144A2EE7-09EC-4618-BA94-6593205962C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
26 | {144A2EE7-09EC-4618-BA94-6593205962C9}.Release|Any CPU.Build.0 = Release|Any CPU
27 | {144A2EE7-09EC-4618-BA94-6593205962C9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
28 | {144A2EE7-09EC-4618-BA94-6593205962C9}.Release|Mixed Platforms.Build.0 = Release|Any CPU
29 | {144A2EE7-09EC-4618-BA94-6593205962C9}.Release|x86.ActiveCfg = Release|Any CPU
30 | {13F3D236-4FC1-433F-9F2E-E0932035A5AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {13F3D236-4FC1-433F-9F2E-E0932035A5AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {13F3D236-4FC1-433F-9F2E-E0932035A5AD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
33 | {13F3D236-4FC1-433F-9F2E-E0932035A5AD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
34 | {13F3D236-4FC1-433F-9F2E-E0932035A5AD}.Debug|x86.ActiveCfg = Debug|Any CPU
35 | {13F3D236-4FC1-433F-9F2E-E0932035A5AD}.Debug|x86.Build.0 = Debug|Any CPU
36 | {13F3D236-4FC1-433F-9F2E-E0932035A5AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {13F3D236-4FC1-433F-9F2E-E0932035A5AD}.Release|Any CPU.Build.0 = Release|Any CPU
38 | {13F3D236-4FC1-433F-9F2E-E0932035A5AD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
39 | {13F3D236-4FC1-433F-9F2E-E0932035A5AD}.Release|Mixed Platforms.Build.0 = Release|Any CPU
40 | {13F3D236-4FC1-433F-9F2E-E0932035A5AD}.Release|x86.ActiveCfg = Release|Any CPU
41 | {13F3D236-4FC1-433F-9F2E-E0932035A5AD}.Release|x86.Build.0 = Release|Any CPU
42 | EndGlobalSection
43 | GlobalSection(SolutionProperties) = preSolution
44 | HideSolutionNode = FALSE
45 | EndGlobalSection
46 | GlobalSection(ExtensibilityGlobals) = postSolution
47 | SolutionGuid = {9D14C0C5-4885-4C52-8533-417678AC2954}
48 | EndGlobalSection
49 | EndGlobal
50 |
--------------------------------------------------------------------------------
/src/CurrencyTextBoxControl/CurrencyTextBox.cs:
--------------------------------------------------------------------------------
1 | // Fork from mtusk CurrencyTextBox
2 | // https://github.com/mtusk/wpf-currency-textbox
3 | //
4 | // Fork 2016-2019 by Derek Tremblay (Abbaye)
5 | // derektremblay666@gmail.com
6 |
7 | using System;
8 | using System.Collections.Generic;
9 | using System.Globalization;
10 | using System.Windows;
11 | using System.Windows.Controls;
12 | using System.Windows.Controls.Primitives;
13 | using System.Windows.Data;
14 | using System.Windows.Input;
15 | using System.Windows.Markup;
16 | using System.Windows.Media;
17 |
18 | namespace CurrencyTextBoxControl
19 | {
20 | [ContentProperty("Number")]
21 | public class CurrencyTextBox : TextBox
22 | {
23 | #region Global variables / Event
24 |
25 | private readonly List _undoList = new List();
26 | private readonly List _redoList = new List();
27 | private Popup _popup;
28 | private Label _popupLabel;
29 | private decimal _numberBeforePopup;
30 |
31 | //Event
32 | public event EventHandler PopupClosed;
33 | public event EventHandler NumberChanged;
34 | #endregion Global variables
35 |
36 | #region Constructor
37 | static CurrencyTextBox() =>
38 | DefaultStyleKeyProperty.OverrideMetadata(
39 | typeof(CurrencyTextBox),
40 | new FrameworkPropertyMetadata(typeof(CurrencyTextBox)));
41 |
42 | public override void OnApplyTemplate()
43 | {
44 | base.OnApplyTemplate();
45 |
46 | // Bind Text to Number with the specified StringFormat
47 | var textBinding = new Binding
48 | {
49 | Path = new PropertyPath(nameof(Number)),
50 | RelativeSource = new RelativeSource(RelativeSourceMode.Self),
51 | UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
52 | StringFormat = StringFormat,
53 | ConverterCulture = Culture
54 | };
55 |
56 | BindingOperations.SetBinding(this, TextProperty, textBinding);
57 |
58 | // Disable copy/paste
59 | DataObject.AddCopyingHandler(this, CopyPasteEventHandler);
60 | DataObject.AddPastingHandler(this, CopyPasteEventHandler);
61 |
62 | //Events
63 | CaretIndex = Text.LastIndexOfAny(new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }) + 1;
64 |
65 | PreviewKeyDown += TextBox_PreviewKeyDown;
66 | PreviewMouseDown += TextBox_PreviewMouseDown;
67 | PreviewMouseUp += TextBox_PreviewMouseUp;
68 | TextChanged += TextBox_TextChanged;
69 |
70 | //Disable contextmenu
71 | ContextMenu = null;
72 |
73 | //Set Touch Keyboard to Numeric Pad - pbrombach@gmail.com 24-12-2019
74 | InputScope scope = new InputScope();
75 | scope.Names.Add(new InputScopeName
76 | {
77 | NameValue = InputScopeNameValue.Number
78 | });
79 | InputScope = scope;
80 |
81 | }
82 | #endregion Constructor
83 |
84 | #region Dependency Properties
85 |
86 | public static readonly DependencyProperty CultureProperty = DependencyProperty.Register(
87 | nameof(Culture), typeof(CultureInfo), typeof(CurrencyTextBox),
88 | new PropertyMetadata(CultureInfo.CurrentCulture, CulturePropertyChanged));
89 |
90 | private static void CulturePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
91 | {
92 | var textBinding = new Binding
93 | {
94 | Path = new PropertyPath("Number"),
95 | RelativeSource = new RelativeSource(RelativeSourceMode.Self),
96 | UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
97 | StringFormat = (string)d.GetValue(StringFormatProperty),
98 | ConverterCulture = (CultureInfo)e.NewValue
99 | };
100 |
101 | BindingOperations.SetBinding(d, TextProperty, textBinding);
102 | }
103 |
104 | public CultureInfo Culture
105 | {
106 | get => (CultureInfo)GetValue(CultureProperty);
107 | set => SetValue(CultureProperty, value);
108 | }
109 |
110 | public static readonly DependencyProperty NumberProperty = DependencyProperty.Register(
111 | nameof(Number), typeof(decimal), typeof(CurrencyTextBox),
112 | new FrameworkPropertyMetadata(0M, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
113 | NumberPropertyChanged, NumberPropertyCoerceValue), NumberPropertyValidated);
114 |
115 | private static bool NumberPropertyValidated(object value) => value is decimal;
116 |
117 | private static object NumberPropertyCoerceValue(DependencyObject d, object baseValue)
118 | {
119 | if (!(d is CurrencyTextBox ctb)) return baseValue;
120 |
121 | var value = (decimal)baseValue;
122 |
123 | //Check maximum value
124 | if (value > ctb.MaximumValue && ctb.MaximumValue > 0)
125 | return ctb.MaximumValue;
126 |
127 | if (value < ctb.MinimumValue && ctb.MinimumValue < 0)
128 | return ctb.MinimumValue;
129 |
130 | return value;
131 | }
132 |
133 | private static void NumberPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
134 | {
135 | if (!(d is CurrencyTextBox ctb)) return;
136 |
137 | //Update IsNegative
138 | ctb.SetValue(IsNegativeProperty, ctb.Number < 0);
139 |
140 | //Launch event
141 | ctb.NumberChanged?.Invoke(ctb, new EventArgs());
142 | }
143 |
144 | public decimal Number
145 | {
146 | get => (decimal)GetValue(NumberProperty);
147 | set => SetValue(NumberProperty, value);
148 | }
149 |
150 | public static readonly DependencyProperty IsNegativeProperty =
151 | DependencyProperty.Register(nameof(IsNegative), typeof(bool), typeof(CurrencyTextBox), new PropertyMetadata(false));
152 |
153 | public bool IsNegative => (bool)GetValue(IsNegativeProperty);
154 |
155 | public bool IsCalculPanelMode
156 | {
157 | get => (bool)GetValue(IsCalculPanelModeProperty);
158 | set => SetValue(IsCalculPanelModeProperty, value);
159 | }
160 |
161 | // Using a DependencyProperty as the backing store for IsCalculPanelMode. This enables animation, styling, binding, etc...
162 | public static readonly DependencyProperty IsCalculPanelModeProperty =
163 | DependencyProperty.Register(nameof(IsCalculPanelMode), typeof(bool), typeof(CurrencyTextBox), new PropertyMetadata(false));
164 |
165 | public bool CanShowAddPanel
166 | {
167 | get => (bool)GetValue(CanShowAddPanelProperty);
168 | set => SetValue(CanShowAddPanelProperty, value);
169 | }
170 |
171 | ///
172 | /// Set for enabling the calcul panel
173 | ///
174 | public static readonly DependencyProperty CanShowAddPanelProperty =
175 | DependencyProperty.Register(nameof(CanShowAddPanel), typeof(bool), typeof(CurrencyTextBox), new PropertyMetadata(false));
176 |
177 | public static readonly DependencyProperty MaximumValueProperty =
178 | DependencyProperty.Register(nameof(MaximumValue), typeof(decimal), typeof(CurrencyTextBox),
179 | new FrameworkPropertyMetadata(0M, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
180 | MaximumValuePropertyChanged, MaximumCoerceValue), MaximumValidateValue);
181 |
182 | private static bool MaximumValidateValue(object value) => (decimal)value <= decimal.MaxValue / 2;
183 |
184 | private static object MaximumCoerceValue(DependencyObject d, object baseValue)
185 | {
186 | var ctb = d as CurrencyTextBox;
187 |
188 | if (ctb.MaximumValue > decimal.MaxValue / 2)
189 | return decimal.MaxValue / 2;
190 |
191 | return baseValue;
192 | }
193 |
194 | private static void MaximumValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
195 | {
196 | var ctb = d as CurrencyTextBox;
197 |
198 | if (ctb.Number > (decimal)e.NewValue)
199 | ctb.Number = (decimal)e.NewValue;
200 | }
201 |
202 | public decimal MaximumValue
203 | {
204 | get => (decimal)GetValue(MaximumValueProperty);
205 | set => SetValue(MaximumValueProperty, value);
206 | }
207 |
208 | public decimal MinimumValue
209 | {
210 | get => (decimal)GetValue(MinimumValueProperty);
211 | set => SetValue(MinimumValueProperty, value);
212 | }
213 |
214 | public static readonly DependencyProperty MinimumValueProperty =
215 | DependencyProperty.Register(nameof(MinimumValue), typeof(decimal), typeof(CurrencyTextBox),
216 | new FrameworkPropertyMetadata(0M, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
217 | MinimumValuePropertyChanged, MinimumCoerceValue), MinimumValidateValue);
218 |
219 | private static bool MinimumValidateValue(object value) =>
220 | (decimal)value >= decimal.MinValue / 2; //&& (decimal)value <= 0;
221 |
222 | private static object MinimumCoerceValue(DependencyObject d, object baseValue)
223 | {
224 | var ctb = d as CurrencyTextBox;
225 |
226 | if (ctb.MinimumValue < decimal.MinValue / 2)
227 | return decimal.MinValue / 2;
228 |
229 | return baseValue;
230 | }
231 |
232 | private static void MinimumValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
233 | {
234 | var ctb = d as CurrencyTextBox;
235 |
236 | if (ctb.Number < (decimal)e.NewValue)
237 | ctb.Number = (decimal)e.NewValue;
238 | }
239 |
240 | public static readonly DependencyProperty StringFormatProperty = DependencyProperty.Register(
241 | nameof(StringFormat), typeof(string), typeof(CurrencyTextBox),
242 | new FrameworkPropertyMetadata("C2", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
243 | StringFormatPropertyChanged, StringFormatCoerceValue), StringFormatValidateValue);
244 |
245 | private static object StringFormatCoerceValue(DependencyObject d, object baseValue) =>
246 | ((string)baseValue).ToUpper();
247 |
248 | ///
249 | /// Validate the StringFormat
250 | ///
251 | private static bool StringFormatValidateValue(object value)
252 | {
253 | var val = value.ToString().ToUpper();
254 |
255 | return val == "C0" || val == "C" || val == "C1" || val == "C2" || val == "C3" || val == "C4" || val == "C5" || val == "C6" ||
256 | val == "N0" || val == "N" || val == "N1" || val == "N2" || val == "N3" || val == "N4" || val == "N5" || val == "N6" ||
257 | val == "P0" || val == "P" || val == "P1" || val == "P2" || val == "P3" || val == "P4" || val == "P5" || val == "P6";
258 | }
259 |
260 | public string StringFormat
261 | {
262 | get => (string)GetValue(StringFormatProperty);
263 | set => SetValue(StringFormatProperty, value);
264 | }
265 |
266 | private static void StringFormatPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
267 | {
268 | // Update the Text binding with the new StringFormat
269 | var textBinding = new Binding
270 | {
271 | Path = new PropertyPath("Number"),
272 | RelativeSource = new RelativeSource(RelativeSourceMode.Self),
273 | UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
274 | StringFormat = (string)e.NewValue,
275 | ConverterCulture = (CultureInfo)obj.GetValue(CultureProperty)
276 | };
277 |
278 | BindingOperations.SetBinding(obj, TextProperty, textBinding);
279 | }
280 |
281 | public int UpDownRepeat
282 | {
283 | get => (int)GetValue(UpDownRepeatProperty);
284 | set => SetValue(UpDownRepeatProperty, value);
285 | }
286 |
287 | ///
288 | /// Set the Up/down value when key repeated
289 | ///
290 | public static readonly DependencyProperty UpDownRepeatProperty =
291 | DependencyProperty.Register(nameof(UpDownRepeat), typeof(int), typeof(CurrencyTextBox), new PropertyMetadata(10));
292 |
293 | #endregion Dependency Properties
294 |
295 |
296 | public void Insert(Key K)
297 | {
298 | AddUndoInList(Number);
299 | InsertKey(K);
300 | }
301 |
302 |
303 | #region Events
304 | private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
305 | {
306 | var tb = sender as TextBox;
307 | tb.CaretIndex = tb.Text.LastIndexOfAny(new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }) + 1;
308 |
309 | //if (Number < 0 && tb.Text.EndsWith(")"))
310 | // tb.CaretIndex = tb.Text.Length - 1;
311 | //else if (tb.Text.EndsWith("%"))
312 | // tb.CaretIndex = tb.Text.Length - 2;
313 | //else
314 | // tb.CaretIndex = tb.Text.Length;
315 | }
316 |
317 | private void TextBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
318 | {
319 | // Prevent changing the caret index
320 | e.Handled = true;
321 | (sender as TextBox).Focus();
322 | }
323 |
324 | private void TextBox_PreviewMouseUp(object sender, MouseButtonEventArgs e)
325 | {
326 | // Prevent changing the caret index
327 | e.Handled = true;
328 | (sender as TextBox).Focus();
329 | }
330 |
331 | ///
332 | /// Action when is key pressed
333 | ///
334 | private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
335 | {
336 | if (IsReadOnly)
337 | {
338 | e.Handled = true;
339 | return;
340 | }
341 |
342 | if (KeyValidator.IsNumericKey(e.Key))
343 | {
344 | e.Handled = true;
345 |
346 | AddUndoInList(Number);
347 | InsertKey(e.Key);
348 | }
349 | else if (KeyValidator.IsBackspaceKey(e.Key))
350 | {
351 | e.Handled = true;
352 |
353 | AddUndoInList(Number);
354 | RemoveRightMostDigit();
355 | }
356 | else if (KeyValidator.IsUpKey(e.Key))
357 | {
358 | e.Handled = true;
359 |
360 | AddUndoInList(Number);
361 | AddOneDigit();
362 |
363 | //if the key is repeated add more digit
364 | if (e.IsRepeat)
365 | {
366 | AddUndoInList(Number);
367 | AddOneDigit(UpDownRepeat);
368 | }
369 | }
370 | else if (KeyValidator.IsDownKey(e.Key))
371 | {
372 | e.Handled = true;
373 |
374 | AddUndoInList(Number);
375 | SubstractOneDigit();
376 |
377 | //if the key is repeated substract more digit
378 | if (e.IsRepeat)
379 | {
380 | AddUndoInList(Number);
381 | SubstractOneDigit(UpDownRepeat);
382 | }
383 | }
384 | else if (KeyValidator.IsCtrlZKey(e.Key))
385 | {
386 | e.Handled = true;
387 |
388 | Undo();
389 | }
390 | else if (KeyValidator.IsCtrlYKey(e.Key))
391 | {
392 | e.Handled = true;
393 |
394 | Redo();
395 | }
396 | else if (KeyValidator.IsEnterKey(e.Key))
397 | {
398 | if (!IsCalculPanelMode)
399 | {
400 | AddUndoInList(Number);
401 | ShowAddPopup();
402 | }
403 | else
404 | {
405 | ((Popup)((Grid)Parent).Parent).IsOpen = false;
406 |
407 | if (PopupClosed != null)
408 | {
409 | e.Handled = true;
410 | PopupClosed(this, new EventArgs());
411 | }
412 | }
413 | }
414 | else if (KeyValidator.IsDeleteKey(e.Key))
415 | {
416 | e.Handled = true;
417 |
418 | AddUndoInList(Number);
419 | Clear();
420 | }
421 | else if (KeyValidator.IsSubstractKey(e.Key))
422 | {
423 | e.Handled = true;
424 |
425 | AddUndoInList(Number);
426 | InvertValue();
427 | }
428 | else if (KeyValidator.IsIgnoredKey(e.Key))
429 | {
430 | e.Handled = false;
431 | }
432 | else if (KeyValidator.IsCtrlCKey(e.Key))
433 | {
434 | e.Handled = true;
435 |
436 | CopyToClipBoard();
437 | }
438 | else if (KeyValidator.IsCtrlVKey(e.Key))
439 | {
440 | e.Handled = true;
441 |
442 | AddUndoInList(Number);
443 | PasteFromClipBoard();
444 | }
445 | else
446 | {
447 | e.Handled = true;
448 | }
449 | }
450 |
451 | // cancel copy and paste
452 | private void CopyPasteEventHandler(object sender, DataObjectEventArgs e) => e.CancelCommand();
453 |
454 | #endregion
455 |
456 | #region Private Methods
457 | private void Ctb_NumberChanged(object sender, EventArgs e)
458 | {
459 | var ctb = sender as CurrencyTextBox;
460 |
461 | Number = _numberBeforePopup + ctb.Number;
462 |
463 | _popupLabel.Content = ctb.Number >= 0 ? "+" : "-";
464 | }
465 |
466 | ///
467 | /// Insert number from key
468 | ///
469 | private void InsertKey(Key key)
470 | {
471 | //Max length fix
472 | if (MaxLength != 0 && Number.ToString(Culture).Length > MaxLength)
473 | return;
474 |
475 | try
476 | {
477 | // Push the new number from the right
478 | if (KeyValidator.IsNumericKey(key))
479 | Number = Number < 0
480 | ? Number * 10M - GetDigitFromKey(key) / GetDivider()
481 | : Number * 10M + GetDigitFromKey(key) / GetDivider();
482 | }
483 | catch (OverflowException)
484 | {
485 | Number = Number < 0 ? decimal.MinValue : decimal.MaxValue;
486 | }
487 |
488 | }
489 |
490 | ///
491 | /// Get the digit from key
492 | ///
493 | private static decimal GetDigitFromKey(Key key)
494 | {
495 | switch (key)
496 | {
497 | case Key.D0:
498 | case Key.NumPad0: return 0M;
499 | case Key.D1:
500 | case Key.NumPad1: return 1M;
501 | case Key.D2:
502 | case Key.NumPad2: return 2M;
503 | case Key.D3:
504 | case Key.NumPad3: return 3M;
505 | case Key.D4:
506 | case Key.NumPad4: return 4M;
507 | case Key.D5:
508 | case Key.NumPad5: return 5M;
509 | case Key.D6:
510 | case Key.NumPad6: return 6M;
511 | case Key.D7:
512 | case Key.NumPad7: return 7M;
513 | case Key.D8:
514 | case Key.NumPad8: return 8M;
515 | case Key.D9:
516 | case Key.NumPad9: return 9M;
517 | default: throw new ArgumentOutOfRangeException($"Invalid key: {key}");
518 | }
519 | }
520 |
521 | ///
522 | /// Get a decimal for adjust digit when a key was inserted
523 | ///
524 | private decimal GetDivider()
525 | {
526 | switch (GetBindingExpression(TextProperty).ParentBinding.StringFormat)
527 | {
528 | case "N0":
529 | case "C0": return 1M;
530 | case "N":
531 | case "C": return 100M;
532 | case "N1":
533 | case "C1": return 10M;
534 | case "N2":
535 | case "C2": return 100M;
536 | case "N3":
537 | case "C3": return 1000M;
538 | case "N4":
539 | case "C4": return 10000M;
540 | case "N5":
541 | case "C5": return 100000M;
542 | case "N6":
543 | case "C6": return 1000000M;
544 | case "P0": return 100M;
545 | case "P": return 10000M;
546 | case "P1": return 1000M;
547 | case "P2": return 10000M;
548 | case "P3": return 100000M;
549 | case "P4": return 1000000M;
550 | case "P5": return 10000000M;
551 | case "P6": return 100000000M;
552 | default: return 1M;
553 | }
554 | }
555 |
556 | ///
557 | /// Get the number of digit .
558 | ///
559 | private int GetDigitCount()
560 | {
561 | if (string.IsNullOrEmpty(StringFormat)) return 1;
562 | if (string.Equals("N", StringFormat, StringComparison.OrdinalIgnoreCase)) return 2;
563 | if (string.Equals("C", StringFormat, StringComparison.OrdinalIgnoreCase)) return 2;
564 |
565 | var s = StringFormat.Substring(StringFormat.Length - 1, 1);
566 | int resp;
567 | if (int.TryParse(s, NumberStyles.Integer, Culture, out resp)) return resp;
568 |
569 | return 1;
570 | }
571 |
572 | ///
573 | /// Delete the right digit of number property
574 | ///
575 | private void RemoveRightMostDigit()
576 | {
577 | try
578 | {
579 | bool isNegative = Number < 0;
580 | var digitCount = GetDigitCount();
581 | string decimalSeparator = !string.IsNullOrEmpty(StringFormat) && StringFormat.StartsWith("C", StringComparison.OrdinalIgnoreCase)
582 | ? Culture.NumberFormat.CurrencyDecimalSeparator : Culture.NumberFormat.NumberDecimalSeparator;
583 |
584 | string numberString = Math.Abs(Number).ToString("#.###########", Culture);
585 | numberString = numberString.Substring(0, numberString.Length - 1);
586 | numberString = numberString.Replace(decimalSeparator, string.Empty);
587 | numberString = numberString.PadLeft(digitCount + 1, '0');
588 |
589 | numberString = (isNegative ? Culture.NumberFormat.NegativeSign : string.Empty) +
590 | numberString.Substring(0, numberString.Length - digitCount) +
591 | Culture.NumberFormat.NumberDecimalSeparator +
592 | numberString.Substring(numberString.Length - digitCount);
593 |
594 | Number = Convert.ToDecimal(numberString, Culture);
595 | }
596 | catch
597 | {
598 | Clear();
599 | }
600 | }
601 | #endregion Privates methodes
602 |
603 | #region Undo/Redo
604 |
605 | ///
606 | /// Add undo to the list
607 | ///
608 | private void AddUndoInList(decimal number, bool clearRedo = true)
609 | {
610 | //Clear first item when undolimit is reach
611 | if (_undoList.Count == UndoLimit)
612 | _undoList.RemoveRange(0, 1);
613 |
614 | //Add item to undo list
615 | _undoList.Add(number);
616 |
617 | //Clear redo when needed
618 | if (clearRedo)
619 | _redoList.Clear();
620 | }
621 |
622 | ///
623 | /// Undo the to the previous value
624 | ///
625 | public new void Undo()
626 | {
627 | if (!CanUndo()) return;
628 |
629 | Number = _undoList[_undoList.Count - 1];
630 |
631 | _redoList.Add(_undoList[_undoList.Count - 1]);
632 | _undoList.RemoveAt(_undoList.Count - 1);
633 | }
634 |
635 | ///
636 | /// Redo to the value previously undone. The list is clear when key is handled
637 | ///
638 | public new void Redo()
639 | {
640 | if (_redoList.Count <= 0) return;
641 |
642 | AddUndoInList(Number, false);
643 | Number = _redoList[_redoList.Count - 1];
644 | _redoList.RemoveAt(_redoList.Count - 1);
645 | }
646 |
647 | ///
648 | /// Get or set for indicate if control CanUndo
649 | ///
650 | public new bool IsUndoEnabled { get; set; } = true;
651 |
652 | ///
653 | /// Clear the undo list
654 | ///
655 | public void ClearUndoList() => _undoList.Clear();
656 |
657 | ///
658 | /// Check if the control can undone to a previous value
659 | ///
660 | ///
661 | public new bool CanUndo() => IsUndoEnabled && _undoList.Count > 0;
662 |
663 | #endregion Undo/Redo
664 |
665 | #region Public Methods
666 |
667 | ///
668 | /// Reset the number to zero.
669 | ///
670 | public new void Clear() => Number = 0M;
671 |
672 | ///
673 | /// Set number to positive
674 | ///
675 | public void SetPositive() { if (Number < 0) Number *= -1; }
676 |
677 | ///
678 | /// Set number to negative
679 | ///
680 | public void SetNegative() { if (Number > 0) Number *= -1; }
681 |
682 | ///
683 | /// Alternate value to Negative-Positive and Positive-Negative
684 | ///
685 | public void InvertValue() => Number *= -1;
686 |
687 | ///
688 | /// Add one digit to the property number
689 | ///
690 | /// Repeat add
691 | public void AddOneDigit(int repeat = 1)
692 | {
693 | for (var i = 0; i < repeat; i++)
694 | switch (GetBindingExpression(TextProperty).ParentBinding.StringFormat)
695 | {
696 | case "P0":
697 | Number = decimal.Add(Number, 0.01M);
698 | break;
699 | case "N0":
700 | case "C0":
701 | Number = decimal.Add(Number, 1M);
702 | break;
703 | case "P":
704 | Number = decimal.Add(Number, 0.0001M);
705 | break;
706 | case "N":
707 | case "C":
708 | Number = decimal.Add(Number, 0.01M);
709 | break;
710 | case "P1":
711 | Number = decimal.Add(Number, 0.001M);
712 | break;
713 | case "N1":
714 | case "C1":
715 | Number = decimal.Add(Number, 0.1M);
716 | break;
717 | case "P2":
718 | Number = decimal.Add(Number, 0.0001M);
719 | break;
720 | case "N2":
721 | case "C2":
722 | Number = decimal.Add(Number, 0.01M);
723 | break;
724 | case "P3":
725 | Number = decimal.Add(Number, 0.00001M);
726 | break;
727 | case "N3":
728 | case "C3":
729 | Number = decimal.Add(Number, 0.001M);
730 | break;
731 | case "P4":
732 | Number = decimal.Add(Number, 0.000001M);
733 | break;
734 | case "N4":
735 | case "C4":
736 | Number = decimal.Add(Number, 0.0001M);
737 | break;
738 | case "P5":
739 | Number = decimal.Add(Number, 0.0000001M);
740 | break;
741 | case "N5":
742 | case "C5":
743 | Number = decimal.Add(Number, 0.00001M);
744 | break;
745 | case "P6":
746 | Number = decimal.Add(Number, 0.00000001M);
747 | break;
748 | case "N6":
749 | case "C6":
750 | Number = decimal.Add(Number, 0.000001M);
751 | break;
752 | }
753 | }
754 |
755 | ///
756 | /// Substract one digit to the property number
757 | ///
758 | /// Repeat substract
759 | public void SubstractOneDigit(int repeat = 1)
760 | {
761 | for (var i = 0; i < repeat; i++)
762 | switch (GetBindingExpression(TextProperty).ParentBinding.StringFormat)
763 | {
764 | case "P0":
765 | Number = decimal.Subtract(Number, 0.01M);
766 | break;
767 | case "N0":
768 | case "C0":
769 | Number = decimal.Subtract(Number, 1M);
770 | break;
771 | case "P":
772 | Number = decimal.Subtract(Number, 0.0001M);
773 | break;
774 | case "N":
775 | case "C":
776 | Number = decimal.Subtract(Number, 0.01M);
777 | break;
778 | case "P1":
779 | Number = decimal.Subtract(Number, 0.001M);
780 | break;
781 | case "N1":
782 | case "C1":
783 | Number = decimal.Subtract(Number, 0.1M);
784 | break;
785 | case "P2":
786 | Number = decimal.Subtract(Number, 0.0001M);
787 | break;
788 | case "N2":
789 | case "C2":
790 | Number = decimal.Subtract(Number, 0.01M);
791 | break;
792 | case "P3":
793 | Number = decimal.Subtract(Number, 0.00001M);
794 | break;
795 | case "N3":
796 | case "C3":
797 | Number = decimal.Subtract(Number, 0.001M);
798 | break;
799 | case "P4":
800 | Number = decimal.Subtract(Number, 0.000001M);
801 | break;
802 | case "N4":
803 | case "C4":
804 | Number = decimal.Subtract(Number, 0.0001M);
805 | break;
806 | case "P5":
807 | Number = decimal.Subtract(Number, 0.0000001M);
808 | break;
809 | case "N5":
810 | case "C5":
811 | Number = decimal.Subtract(Number, 0.00001M);
812 | break;
813 | case "P6":
814 | Number = decimal.Subtract(Number, 0.00000001M);
815 | break;
816 | case "N6":
817 | case "C6":
818 | Number = decimal.Subtract(Number, 0.000001M);
819 | break;
820 | }
821 | }
822 |
823 | #endregion Other function
824 |
825 | #region Clipboard
826 | ///
827 | /// Paste if is a number on clipboard
828 | ///
829 | private void PasteFromClipBoard()
830 | {
831 | try
832 | {
833 | switch (GetBindingExpression(TextProperty).ParentBinding.StringFormat)
834 | {
835 | case "P0":
836 | case "P":
837 | case "P1":
838 | case "P2":
839 | case "P3":
840 | case "P4":
841 | case "P5":
842 | case "P6":
843 | Number = decimal.Parse(Clipboard.GetText());
844 | break;
845 | default:
846 | Number = Math.Round(decimal.Parse(Clipboard.GetText()), GetDigitCount());
847 | break;
848 | }
849 | }
850 | catch
851 | {
852 | // ignored
853 | }
854 | }
855 |
856 | ///
857 | /// Copy the property Number to Control
858 | ///
859 | private void CopyToClipBoard()
860 | {
861 | Clipboard.Clear();
862 | Clipboard.SetText(Number.ToString(Culture));
863 | }
864 | #endregion Clipboard
865 |
866 | #region Add/remove value Popup
867 | ///
868 | /// Show popup for add/remove value
869 | ///
870 | private void ShowAddPopup()
871 | {
872 | if (!CanShowAddPanel) return;
873 |
874 | //Initialize somes Child object
875 | var grid = new Grid { Background = Brushes.White };
876 |
877 | var ctbPopup = new CurrencyTextBox
878 | {
879 | CanShowAddPanel = false,
880 | IsCalculPanelMode = true,
881 | StringFormat = StringFormat,
882 | Background = Brushes.WhiteSmoke
883 | };
884 |
885 | _popup = new Popup
886 | {
887 | Width = ActualWidth,
888 | Height = 32,
889 | PopupAnimation = PopupAnimation.Fade,
890 | Placement = PlacementMode.Bottom,
891 | PlacementTarget = this,
892 | StaysOpen = false,
893 | Child = grid,
894 | IsOpen = true
895 | };
896 |
897 | //Set object properties
898 | ctbPopup.NumberChanged += Ctb_NumberChanged;
899 | ctbPopup.PopupClosed += CtbPopup_PopupClosed;
900 |
901 | _numberBeforePopup = Number;
902 | _popupLabel = new Label { Content = "+" };
903 |
904 | //ColumnDefinition
905 | var c1 = new ColumnDefinition { Width = new GridLength(20, GridUnitType.Auto) };
906 | var c2 = new ColumnDefinition { Width = new GridLength(80, GridUnitType.Star) };
907 | grid.ColumnDefinitions.Add(c1);
908 | grid.ColumnDefinitions.Add(c2);
909 | Grid.SetColumn(_popupLabel, 0);
910 | Grid.SetColumn(ctbPopup, 1);
911 |
912 | //Add object
913 | grid.Children.Add(_popupLabel);
914 | grid.Children.Add(ctbPopup);
915 |
916 | ctbPopup.Focus();
917 | }
918 |
919 | private void CtbPopup_PopupClosed(object sender, EventArgs e) => Focus();
920 | #endregion Add/remove value Popup
921 | }
922 | }
--------------------------------------------------------------------------------
/src/CurrencyTextBoxControl/CurrencyTextBox.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.0;net40;net45;net47
5 | true
6 | CurrencyTextbox
7 | CurrencyTextbox
8 | CurrencyTextbox user control
9 | true
10 | Derek Tremblay (derektremblay666@gmail.com)
11 | Derek Tremblay (derektremblay666@gmail.com)
12 |
13 | .NET FRAMEWORK 4.5/4.7 AND .NET CORE 3.0
14 | A WPF TextBox for entering a Currency/Numeric/Percent value, similar to how a cash register works.
15 | Numbers typed are pushed in from the right.
16 |
17 |
18 | CurrencyTextbox
19 | MIT
20 | 2.0.2.0
21 | 2.0.2.0
22 | 2.0.2
23 | true
24 | wpf-controls;currency;user-interface
25 |
26 | preview
27 | https://github.com/abbaye/WpfCurrencyTextbox
28 | false
29 |
30 | license.txt
31 |
32 |
33 |
34 | TRACE;DEBUG
35 | x64
36 |
37 |
38 |
39 |
40 | True
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/CurrencyTextBoxControl/KeyValidator.cs:
--------------------------------------------------------------------------------
1 | using System.Windows.Input;
2 |
3 | namespace CurrencyTextBoxControl
4 | {
5 | public static class KeyValidator
6 | {
7 | ///
8 | /// Check if is a numeric key as pressed
9 | ///
10 | public static bool IsNumericKey(Key key) => key == Key.D0 || key == Key.D1 || key == Key.D2 || key == Key.D3 || key == Key.D4 || key == Key.D5 || key == Key.D6 || key == Key.D7 || key == Key.D8 || key == Key.D9 ||
11 | key == Key.NumPad0 || key == Key.NumPad1 || key == Key.NumPad2 || key == Key.NumPad3 || key == Key.NumPad4 || key == Key.NumPad5 || key == Key.NumPad6 || key == Key.NumPad7 || key == Key.NumPad8 || key == Key.NumPad9;
12 |
13 | public static bool IsBackspaceKey(Key key) => key == Key.Back;
14 |
15 | public static bool IsSubstractKey(Key key) => key == Key.Subtract || key == Key.OemMinus;
16 |
17 | public static bool IsDeleteKey(Key key) => key == Key.Delete;
18 |
19 | public static bool IsIgnoredKey(Key key) => key == Key.Tab;
20 |
21 | public static bool IsUpKey(Key key) => key == Key.Up;
22 |
23 | public static bool IsDownKey(Key key) => key == Key.Down;
24 |
25 | public static bool IsEnterKey(Key key) => key == Key.Enter;
26 |
27 | public static bool IsCtrlCKey(Key key) => key == Key.C && Keyboard.Modifiers == ModifierKeys.Control;
28 |
29 | public static bool IsCtrlZKey(Key key) => key == Key.Z && Keyboard.Modifiers == ModifierKeys.Control;
30 |
31 | public static bool IsCtrlYKey(Key key) => key == Key.Y && Keyboard.Modifiers == ModifierKeys.Control;
32 |
33 | public static bool IsCtrlVKey(Key key) => key == Key.V && Keyboard.Modifiers == ModifierKeys.Control;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/CurrencyTextBoxControl/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 | using System.Windows;
3 |
4 |
5 | // Setting ComVisible to false makes the types in this assembly not visible
6 | // to COM components. If you need to access a type in this assembly from
7 | // COM, set the ComVisible attribute to true on that type.
8 | [assembly: ComVisible(false)]
9 |
10 | //In order to begin building localizable applications, set
11 | //CultureYouAreCodingWith in your .csproj file
12 | //inside a . For example, if you are using US english
13 | //in your source files, set the to en-US. Then uncomment
14 | //the NeutralResourceLanguage attribute below. Update the "en-US" in
15 | //the line below to match the UICulture setting in the project file.
16 |
17 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
18 |
19 |
20 | [assembly: ThemeInfo(
21 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
22 | //(used if a resource is not found in the page,
23 | // or application resource dictionaries)
24 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
25 | //(used if a resource is not found in the page,
26 | // app, or any theme specific resource dictionaries)
27 | )]
28 |
29 |
--------------------------------------------------------------------------------
/src/CurrencyTextBoxControl/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // Ce code a été généré par un outil.
4 | // Version du runtime :4.0.30319.42000
5 | //
6 | // Les modifications apportées à ce fichier peuvent provoquer un comportement incorrect et seront perdues si
7 | // le code est régénéré.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace CurrencyTextBoxControl.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// Une classe de ressource fortement typée destinée, entre autres, à la consultation des chaînes localisées.
17 | ///
18 | // Cette classe a été générée automatiquement par la classe StronglyTypedResourceBuilder
19 | // à l'aide d'un outil, tel que ResGen ou Visual Studio.
20 | // Pour ajouter ou supprimer un membre, modifiez votre fichier .ResX, puis réexécutez ResGen
21 | // avec l'option /str ou régénérez votre projet VS.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Retourne l'instance ResourceManager mise en cache utilisée par cette classe.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CurrencyTextBoxControl.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Remplace la propriété CurrentUICulture du thread actuel pour toutes
51 | /// les recherches de ressources à l'aide de cette classe de ressource fortement typée.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/CurrencyTextBoxControl/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/src/CurrencyTextBoxControl/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // Ce code a été généré par un outil.
4 | // Version du runtime :4.0.30319.42000
5 | //
6 | // Les modifications apportées à ce fichier peuvent provoquer un comportement incorrect et seront perdues si
7 | // le code est régénéré.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace CurrencyTextBoxControl.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.3.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/CurrencyTextBoxControl/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/CurrencyTextBoxControl/Themes/Generic.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
15 |
--------------------------------------------------------------------------------
/src/CurrencyTextBoxControl/license.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Adam Anderson, Derek Tremblay
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.
--------------------------------------------------------------------------------
/src/CurrencyTextBoxExample/App.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/CurrencyTextBoxExample/App.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace CurrencyTextBoxExample
2 | {
3 | ///
4 | /// Interaction logic for App.xaml
5 | ///
6 | public partial class App
7 | {
8 |
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/CurrencyTextBoxExample/CurrencyTextBox.Sample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | netcoreapp3.0;net40;net45;net47
6 | true
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/CurrencyTextBoxExample/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
18 |
19 |
29 |
30 |
37 |
38 |
39 |
40 |
46 |
56 |
57 |
63 |
74 |
75 |
76 |
77 |
84 |
91 |
96 |
104 |
112 |
123 |
124 |
129 |
137 |
145 |
156 |
157 |
163 |
168 |
178 |
183 |
184 |
192 |
200 |
209 |
210 |
211 |
--------------------------------------------------------------------------------
/src/CurrencyTextBoxExample/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.ComponentModel;
3 | using System.Globalization;
4 | using System.Linq;
5 | using System.Windows;
6 |
7 | namespace CurrencyTextBoxExample
8 | {
9 | public partial class MainWindow : IDataErrorInfo
10 | {
11 | public decimal Number { get; set; } = 1.23M;
12 |
13 | private List _cultureInfos;
14 |
15 | public List CultureInfos
16 | {
17 | get => CultureInfo.GetCultures(CultureTypes.AllCultures).OrderBy(x => x.EnglishName).ToList();
18 | }
19 |
20 | private List _stringFormats;
21 | public List StringFormats
22 | {
23 | get => _stringFormats ?? (_stringFormats = new List
24 | {
25 | "C0",
26 | "C",
27 | "C1",
28 | "C2",
29 | "C3",
30 | "C4",
31 | "C5",
32 | "C6",
33 | "N0",
34 | "N",
35 | "N1",
36 | "N2",
37 | "N3",
38 | "N4",
39 | "N5",
40 | "N6",
41 | "P0",
42 | "P",
43 | "P1",
44 | "P2",
45 | "P3",
46 | "P4",
47 | "P5",
48 | "P6"
49 | });
50 |
51 | set => _stringFormats = value;
52 | }
53 |
54 | public MainWindow()
55 | {
56 | InitializeComponent();
57 | DataContext = this;
58 | }
59 |
60 | public string Error => throw new System.NotImplementedException();
61 |
62 | public string this[string columnName] => columnName == "Number" && (Number < 0 || Number > 10)
63 | ? "Number must be between zero and ten."
64 | : null;
65 |
66 | private void Button_Click(object sender, RoutedEventArgs e) => MessageBox.Show("Default Button activated!");
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/CurrencyTextBoxExample/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 | using System.Windows;
3 |
4 | // Setting ComVisible to false makes the types in this assembly not visible
5 | // to COM components. If you need to access a type in this assembly from
6 | // COM, set the ComVisible attribute to true on that type.
7 | [assembly: ComVisible(false)]
8 |
9 |
10 | [assembly: ThemeInfo(
11 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
12 | //(used if a resource is not found in the page,
13 | // or application resource dictionaries)
14 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
15 | //(used if a resource is not found in the page,
16 | // app, or any theme specific resource dictionaries)
17 | )]
18 |
19 |
--------------------------------------------------------------------------------
/src/CurrencyTextBoxExample/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // Ce code a été généré par un outil.
4 | // Version du runtime :4.0.30319.42000
5 | //
6 | // Les modifications apportées à ce fichier peuvent provoquer un comportement incorrect et seront perdues si
7 | // le code est régénéré.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace CurrencyTextBoxExample.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// Une classe de ressource fortement typée destinée, entre autres, à la consultation des chaînes localisées.
17 | ///
18 | // Cette classe a été générée automatiquement par la classe StronglyTypedResourceBuilder
19 | // à l'aide d'un outil, tel que ResGen ou Visual Studio.
20 | // Pour ajouter ou supprimer un membre, modifiez votre fichier .ResX, puis réexécutez ResGen
21 | // avec l'option /str ou régénérez votre projet VS.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Retourne l'instance ResourceManager mise en cache utilisée par cette classe.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CurrencyTextBoxExample.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Remplace la propriété CurrentUICulture du thread actuel pour toutes
51 | /// les recherches de ressources à l'aide de cette classe de ressource fortement typée.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/CurrencyTextBoxExample/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/src/CurrencyTextBoxExample/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // Ce code a été généré par un outil.
4 | // Version du runtime :4.0.30319.42000
5 | //
6 | // Les modifications apportées à ce fichier peuvent provoquer un comportement incorrect et seront perdues si
7 | // le code est régénéré.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace CurrencyTextBoxExample.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.3.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/CurrencyTextBoxExample/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------