Skip to content

Commit 9a82d57

Browse files
AsivaprasadamArjun SivaprasadamKeboo
authored
Pull Request: Adding New Control NumericUpDown Control to Material Design XAML Toolkit (#3175)
* Adding bare bones for the NumericUpDown control! - Auto generated control template added. - Auto generated style from Generic.xaml moved to the specific style file (MaterialDesignTheme.NumericUpDown.xaml). * Almost finalized custom control and initial styling added! * Changes to display initial design on the MainDemo! * Some working changes from tonight's live stream * Cleaning up the numeric up/down control Adding UI tests * Forwarding alignment properties * Removed commands Reverted attached debugger change in base UI tests class * Implemented coercion calls for Min/Max The behavior should match that of Slider. * Allow for custom button content * Adding CustomContent example to Demo app --------- Co-authored-by: Arjun Sivaprasadam <asivaprasadam@chargerind.com> Co-authored-by: Kevin Bost <kitokeboo@gmail.com>
1 parent e4c504a commit 9a82d57

File tree

9 files changed

+509
-3
lines changed

9 files changed

+509
-3
lines changed

src/MainDemo.Wpf/Domain/MainWindowViewModel.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.ComponentModel;
33
using System.Configuration;
44
using System.Windows.Data;
5+
56
using MaterialDesignThemes.Wpf;
67
using MaterialDesignThemes.Wpf.Transitions;
78

@@ -37,7 +38,7 @@ public MainWindowViewModel(ISnackbarMessageQueue snackbarMessageQueue, string? s
3738
SelectedItem = DemoItems.FirstOrDefault(di => string.Equals(di.Name, startupPage, StringComparison.CurrentCultureIgnoreCase)) ?? DemoItems.First();
3839
_demoItemsView = CollectionViewSource.GetDefaultView(DemoItems);
3940
_demoItemsView.Filter = DemoItemsFilter;
40-
41+
4142

4243
HomeCommand = new AnotherCommandImplementation(
4344
_ =>
@@ -467,8 +468,15 @@ private static IEnumerable<DemoItem> GenerateDemoItems(ISnackbarMessageQueue sna
467468
new[]
468469
{
469470
DocumentationLink.DemoPageLink<PopupBox>(),
470-
DocumentationLink.StyleLink("PopupBox"),
471+
DocumentationLink.StyleLink("PopupBox"),
471472
});
473+
474+
yield return new DemoItem(nameof(NumericUpDown), typeof(NumericUpDown), new[]
475+
{
476+
DocumentationLink.DemoPageLink<NumericUpDown>(),
477+
DocumentationLink.StyleLink(nameof(NumericUpDown)),
478+
DocumentationLink.ApiLink<NumericUpDown>()
479+
});
472480
}
473481

474482
private bool DemoItemsFilter(object obj)

src/MainDemo.Wpf/NumericUpDown.xaml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<UserControl x:Class="MaterialDesignDemo.NumericUpDown"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5+
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
6+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
7+
xmlns:smtx="clr-namespace:ShowMeTheXAML;assembly=ShowMeTheXAML"
8+
d:DesignHeight="800"
9+
d:DesignWidth="1000"
10+
mc:Ignorable="d">
11+
12+
<UserControl.Resources>
13+
<ResourceDictionary>
14+
<ResourceDictionary.MergedDictionaries>
15+
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.NumericUpDown.xaml" />
16+
</ResourceDictionary.MergedDictionaries>
17+
</ResourceDictionary>
18+
</UserControl.Resources>
19+
20+
<DockPanel MinHeight="2000">
21+
<TextBlock DockPanel.Dock="Top"
22+
Style="{StaticResource MaterialDesignHeadline5TextBlock}"
23+
Text="NumericUpDown Control" />
24+
25+
<StackPanel Margin="0,16,0,0"
26+
DockPanel.Dock="Top"
27+
Orientation="Horizontal">
28+
<smtx:XamlDisplay Margin="10,5"
29+
VerticalAlignment="Top"
30+
UniqueKey="numericUpDown_default">
31+
<materialDesign:NumericUpDown />
32+
</smtx:XamlDisplay>
33+
34+
<smtx:XamlDisplay Margin="10,5"
35+
VerticalAlignment="Top"
36+
UniqueKey="numericUpDown_customButtonContent">
37+
<materialDesign:NumericUpDown>
38+
<materialDesign:NumericUpDown.IncreaseContent>
39+
<Border BorderBrush="{DynamicResource MaterialDesign.Brush.Foreground}" BorderThickness="1" CornerRadius="20">
40+
<materialDesign:PackIcon Kind="ArrowUp" />
41+
</Border>
42+
</materialDesign:NumericUpDown.IncreaseContent>
43+
<materialDesign:NumericUpDown.DecreaseContent>
44+
<Border BorderBrush="{DynamicResource MaterialDesign.Brush.Foreground}" BorderThickness="1" CornerRadius="20">
45+
<materialDesign:PackIcon Kind="ArrowDown" />
46+
</Border>
47+
</materialDesign:NumericUpDown.DecreaseContent>
48+
</materialDesign:NumericUpDown>
49+
</smtx:XamlDisplay>
50+
51+
</StackPanel>
52+
53+
</DockPanel>
54+
</UserControl>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace MaterialDesignDemo;
2+
3+
public partial class NumericUpDown : UserControl
4+
{
5+
public NumericUpDown() => InitializeComponent();
6+
7+
}
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
using System.ComponentModel;
2+
using System.Globalization;
3+
using System.Windows.Automation.Peers;
4+
5+
namespace MaterialDesignThemes.Wpf;
6+
7+
[TemplatePart(Name = IncreaseButtonPartName, Type = typeof(RepeatButton))]
8+
[TemplatePart(Name = DecreaseButtonPartName, Type = typeof(RepeatButton))]
9+
[TemplatePart(Name = TextFieldBoxPartName, Type = typeof(TextBox))]
10+
public class NumericUpDown : Control
11+
{
12+
public const string IncreaseButtonPartName = "PART_IncreaseButton";
13+
public const string DecreaseButtonPartName = "PART_DecreaseButton";
14+
public const string TextFieldBoxPartName = "PART_TextBoxField";
15+
16+
private TextBox? _textBoxField;
17+
private RepeatButton? _decreaseButton;
18+
private RepeatButton? _increaseButton;
19+
20+
static NumericUpDown()
21+
{
22+
DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown), new FrameworkPropertyMetadata(typeof(NumericUpDown)));
23+
}
24+
25+
#region DependencyProperties
26+
27+
28+
29+
public object? IncreaseContent
30+
{
31+
get => GetValue(IncreaseContentProperty);
32+
set => SetValue(IncreaseContentProperty, value);
33+
}
34+
35+
// Using a DependencyProperty as the backing store for IncreaseContent. This enables animation, styling, binding, etc...
36+
public static readonly DependencyProperty IncreaseContentProperty =
37+
DependencyProperty.Register(nameof(IncreaseContent), typeof(object), typeof(NumericUpDown), new PropertyMetadata(null));
38+
39+
public object? DecreaseContent
40+
{
41+
get => GetValue(DecreaseContentProperty);
42+
set => SetValue(DecreaseContentProperty, value);
43+
}
44+
45+
// Using a DependencyProperty as the backing store for DecreaseContent. This enables animation, styling, binding, etc...
46+
public static readonly DependencyProperty DecreaseContentProperty =
47+
DependencyProperty.Register(nameof(DecreaseContent), typeof(object), typeof(NumericUpDown), new PropertyMetadata(null));
48+
49+
#region DependencyProperty : MinimumProperty
50+
public int Minimum
51+
{
52+
get => (int)GetValue(MinimumProperty);
53+
set => SetValue(MinimumProperty, value);
54+
}
55+
public static readonly DependencyProperty MinimumProperty =
56+
DependencyProperty.Register(nameof(Minimum), typeof(int), typeof(NumericUpDown), new PropertyMetadata(int.MinValue, OnMinimumChanged));
57+
58+
private static void OnMinimumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
59+
{
60+
NumericUpDown ctrl = (NumericUpDown)d;
61+
ctrl.CoerceValue(ValueProperty);
62+
ctrl.CoerceValue(MaximumProperty);
63+
}
64+
65+
#endregion DependencyProperty : MinimumProperty
66+
67+
#region DependencyProperty : MaximumProperty
68+
69+
public int Maximum
70+
{
71+
get => (int)GetValue(MaximumProperty);
72+
set => SetValue(MaximumProperty, value);
73+
}
74+
75+
public static readonly DependencyProperty MaximumProperty =
76+
DependencyProperty.Register(nameof(Maximum), typeof(int), typeof(NumericUpDown), new PropertyMetadata(int.MaxValue, OnMaximumChanged, CoerceMaximum));
77+
78+
private static void OnMaximumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
79+
{
80+
NumericUpDown ctrl = (NumericUpDown)d;
81+
ctrl.CoerceValue(ValueProperty);
82+
}
83+
84+
private static object? CoerceMaximum(DependencyObject d, object? value)
85+
{
86+
if (d is NumericUpDown numericUpDown &&
87+
value is int numericValue)
88+
{
89+
return Math.Max(numericUpDown.Minimum, numericValue);
90+
}
91+
return value;
92+
}
93+
94+
#endregion DependencyProperty : MaximumProperty
95+
96+
#region DependencyProperty : ValueProperty
97+
public int Value
98+
{
99+
get => (int)GetValue(ValueProperty);
100+
set => SetValue(ValueProperty, value);
101+
}
102+
103+
public static readonly DependencyProperty ValueProperty =
104+
DependencyProperty.Register(nameof(Value), typeof(int), typeof(NumericUpDown), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnNumericValueChanged, CoerceNumericValue));
105+
106+
private static void OnNumericValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
107+
{
108+
if (d is NumericUpDown numericUpDown)
109+
{
110+
var args = new RoutedPropertyChangedEventArgs<int>((int)e.OldValue, (int)e.NewValue)
111+
{
112+
RoutedEvent = ValueChangedEvent
113+
};
114+
numericUpDown.RaiseEvent(args);
115+
if (numericUpDown._textBoxField is { } textBox)
116+
{
117+
textBox.Text = ((int)e.NewValue).ToString(CultureInfo.CurrentUICulture);
118+
}
119+
120+
if (numericUpDown._increaseButton is { } increaseButton)
121+
{
122+
increaseButton.IsEnabled = numericUpDown.Value != numericUpDown.Maximum;
123+
}
124+
125+
if (numericUpDown._decreaseButton is { } decreaseButton)
126+
{
127+
decreaseButton.IsEnabled = numericUpDown.Value != numericUpDown.Minimum;
128+
}
129+
}
130+
}
131+
132+
private static object? CoerceNumericValue(DependencyObject d, object? value)
133+
{
134+
if (d is NumericUpDown numericUpDown &&
135+
value is int numericValue)
136+
{
137+
numericValue = Math.Min(numericUpDown.Maximum, numericValue);
138+
numericValue = Math.Max(numericUpDown.Minimum, numericValue);
139+
return numericValue;
140+
}
141+
return value;
142+
}
143+
#endregion ValueProperty
144+
145+
#region DependencyProperty : AllowChangeOnScroll
146+
147+
public bool AllowChangeOnScroll
148+
{
149+
get => (bool)GetValue(AllowChangeOnScrollProperty);
150+
set => SetValue(AllowChangeOnScrollProperty, value);
151+
}
152+
153+
public static readonly DependencyProperty AllowChangeOnScrollProperty =
154+
DependencyProperty.Register(nameof(AllowChangeOnScroll), typeof(bool), typeof(NumericUpDown), new PropertyMetadata(false));
155+
156+
#endregion
157+
158+
#endregion DependencyProperties
159+
160+
#region Event : ValueChangedEvent
161+
[Category("Behavior")]
162+
public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(nameof(ValueChanged), RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<int>), typeof(NumericUpDown));
163+
164+
public event RoutedPropertyChangedEventHandler<int> ValueChanged
165+
{
166+
add => AddHandler(ValueChangedEvent, value);
167+
remove => RemoveHandler(ValueChangedEvent, value);
168+
}
169+
#endregion Event : ValueChangedEvent
170+
171+
public override void OnApplyTemplate()
172+
{
173+
if (_increaseButton != null)
174+
_increaseButton.Click -= IncreaseButtonOnClick;
175+
176+
if (_decreaseButton != null)
177+
_decreaseButton.Click -= DecreaseButtonOnClick;
178+
if (_textBoxField != null)
179+
_textBoxField.TextChanged -= OnTextBoxFocusLost;
180+
181+
_increaseButton = GetTemplateChild(IncreaseButtonPartName) as RepeatButton;
182+
_decreaseButton = GetTemplateChild(DecreaseButtonPartName) as RepeatButton;
183+
_textBoxField = GetTemplateChild(TextFieldBoxPartName) as TextBox;
184+
185+
if (_increaseButton != null)
186+
_increaseButton.Click += IncreaseButtonOnClick;
187+
188+
if (_decreaseButton != null)
189+
_decreaseButton.Click += DecreaseButtonOnClick;
190+
191+
if (_textBoxField != null)
192+
{
193+
_textBoxField.LostFocus += OnTextBoxFocusLost;
194+
_textBoxField.Text = Value.ToString(CultureInfo.CurrentUICulture);
195+
}
196+
197+
base.OnApplyTemplate();
198+
}
199+
200+
private void OnTextBoxFocusLost(object sender, EventArgs e)
201+
{
202+
if (_textBoxField is { } textBoxField)
203+
{
204+
if (int.TryParse(textBoxField.Text, NumberStyles.Integer, CultureInfo.CurrentUICulture, out int numericValue))
205+
{
206+
SetCurrentValue(ValueProperty, numericValue);
207+
}
208+
else
209+
{
210+
textBoxField.Text = Value.ToString(CultureInfo.CurrentUICulture);
211+
}
212+
}
213+
}
214+
215+
private void IncreaseButtonOnClick(object sender, RoutedEventArgs e) => OnIncrease();
216+
217+
private void DecreaseButtonOnClick(object sender, RoutedEventArgs e) => OnDecrease();
218+
219+
private void OnIncrease()
220+
{
221+
SetCurrentValue(ValueProperty, Value + 1);
222+
}
223+
224+
private void OnDecrease()
225+
{
226+
SetCurrentValue(ValueProperty, Value - 1);
227+
}
228+
229+
protected override void OnPreviewKeyDown(KeyEventArgs e)
230+
{
231+
if (e.Key == Key.Up)
232+
{
233+
OnIncrease();
234+
e.Handled = true;
235+
}
236+
else if (e.Key == Key.Down)
237+
{
238+
OnDecrease();
239+
e.Handled = true;
240+
}
241+
base.OnPreviewKeyDown(e);
242+
}
243+
244+
protected override void OnPreviewMouseWheel(MouseWheelEventArgs e)
245+
{
246+
if (IsKeyboardFocusWithin && AllowChangeOnScroll)
247+
{
248+
if (e.Delta > 0)
249+
{
250+
OnIncrease();
251+
}
252+
else if (e.Delta < 0)
253+
{
254+
OnDecrease();
255+
}
256+
e.Handled = true;
257+
}
258+
base.OnPreviewMouseWheel(e);
259+
}
260+
}

src/MaterialDesignThemes.Wpf/Themes/Generic.xaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Flipper.xaml" />
2525
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.FlipperClassic.xaml" />
2626
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.ColorPicker.xaml" />
27+
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.NumericUpDown.xaml" />
2728
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
2829
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.RatingBar.xaml" />
2930
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.TimePicker.xaml" />

0 commit comments

Comments
 (0)