Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 55 additions & 17 deletions src/MahApps.Metro/Controls/NumericUpDown.cs
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,19 @@ public override void OnApplyTemplate()
{
base.OnApplyTemplate();

// Clean up old event handlers to prevent memory leaks
if (this.repeatUp != null)
{
this.repeatUp.Click -= this.OnRepeatUpClick;
this.repeatUp.PreviewMouseUp -= this.OnRepeatButtonMouseUp;
}

if (this.repeatDown != null)
{
this.repeatDown.Click -= this.OnRepeatDownClick;
this.repeatDown.PreviewMouseUp -= this.OnRepeatButtonMouseUp;
}

this.repeatUp = this.GetTemplateChild(PART_NumericUp) as RepeatButton;
this.repeatDown = this.GetTemplateChild(PART_NumericDown) as RepeatButton;

Expand All @@ -1010,17 +1023,33 @@ public override void OnApplyTemplate()

this.ToggleReadOnlyMode(this.IsReadOnly);

this.repeatUp.Click += (_, _) => { this.ChangeValueWithSpeedUp(true); };
this.repeatDown.Click += (_, _) => { this.ChangeValueWithSpeedUp(false); };
// Use named methods for event handlers so they can be properly removed
this.repeatUp.Click += this.OnRepeatUpClick;
this.repeatDown.Click += this.OnRepeatDownClick;

this.repeatUp.PreviewMouseUp += (_, _) => this.ResetInternal();
this.repeatDown.PreviewMouseUp += (_, _) => this.ResetInternal();
this.repeatUp.PreviewMouseUp += this.OnRepeatButtonMouseUp;
this.repeatDown.PreviewMouseUp += this.OnRepeatButtonMouseUp;

this.OnValueChanged(this.Value, this.Value);

this.scrollViewer = null;
}

private void OnRepeatUpClick(object sender, RoutedEventArgs e)
{
this.ChangeValueWithSpeedUp(true);
}

private void OnRepeatDownClick(object sender, RoutedEventArgs e)
{
this.ChangeValueWithSpeedUp(false);
}

private void OnRepeatButtonMouseUp(object sender, MouseButtonEventArgs e)
{
this.ResetInternal();
}

/// <summary>
/// Creates AutomationPeer (<see cref="UIElement.OnCreateAutomationPeer"/>)
/// </summary>
Expand Down Expand Up @@ -1170,9 +1199,10 @@ protected void OnPreviewTextInput(object sender, TextCompositionEventArgs e)
var textBox = (TextBox)sender;
var fullText = textBox.Text.Remove(textBox.SelectionStart, textBox.SelectionLength).Insert(textBox.CaretIndex, e.Text);
var textIsValid = this.ValidateText(fullText, out var convertedValue);
// Value must be valid and not coerced
var coerceValue = CoerceValue(this, convertedValue as double?);
e.Handled = !textIsValid || !coerceValue.isValid;

// Allow typing intermediate values (e.g., typing "5" when minimum is 50)
// Only block input if the text format is invalid, not if it would be coerced
e.Handled = !textIsValid;
this.manualChange = !e.Handled;
}

Expand Down Expand Up @@ -1338,16 +1368,25 @@ private static bool TryFormatHexadecimal(double newValue, string format, Culture
var match = RegexStringFormatHexadecimal.Match(format);
if (match.Success)
{
// Validate value is within int range and has no decimal part
if (newValue < int.MinValue || newValue > int.MaxValue || Math.Abs(newValue % 1) > double.Epsilon)
{
output = null;
return false;
}

var intValue = (int)newValue;

if (match.Groups["simpleHEX"].Success)
{
// HEX DOES SUPPORT INT ONLY.
output = ((int)newValue).ToString(match.Groups["simpleHEX"].Value, culture);
output = intValue.ToString(match.Groups["simpleHEX"].Value, culture);
return true;
}

if (match.Groups["complexHEX"].Success)
{
output = string.Format(culture, match.Groups["complexHEX"].Value, (int)newValue);
output = string.Format(culture, match.Groups["complexHEX"].Value, intValue);
return true;
}
}
Expand Down Expand Up @@ -1450,7 +1489,9 @@ private void SetValueTo(double newValue)
value = this.Minimum;
}

this.SetCurrentValue(ValueProperty, CoerceValue(this, value).value);
// Remove manual coercion - the dependency property system will handle it automatically
// via the CoerceValueCallback registered on ValueProperty
this.SetCurrentValue(ValueProperty, value);
}

private void EnableDisableUpDown()
Expand Down Expand Up @@ -1494,10 +1535,7 @@ private void OnTextBoxKeyDown(object sender, KeyEventArgs e)
/// <param name="textBox">The TextBox which will be used for the correction</param>
/// <param name="mode">The decimal correction mode.</param>
/// <param name="culture">The culture with the decimal-point information.</param>
/// <remarks>
/// Typical "async-void" pattern as "fire-and-forget" behavior.
/// </remarks>
private static async void SimulateDecimalPointKeyPress(TextBoxBase textBox, DecimalPointCorrectionMode mode, CultureInfo culture)
private static void SimulateDecimalPointKeyPress(TextBoxBase textBox, DecimalPointCorrectionMode mode, CultureInfo culture)
{
// Select the proper decimal-point string upon the context
string? replace;
Expand Down Expand Up @@ -1527,8 +1565,6 @@ private static async void SimulateDecimalPointKeyPress(TextBoxBase textBox, Deci

TextCompositionManager.StartComposition(tc);
}

await Task.FromResult(true);
}

private void OnTextBoxLostFocus(object? sender, RoutedEventArgs e)
Expand Down Expand Up @@ -1590,7 +1626,9 @@ private void ChangeValueFromTextInput(string text)
}
}

this.OnValueChanged(oldValue, this.Value);
// REMOVED: this.OnValueChanged(oldValue, this.Value);
// SetValueTo already triggers ValueProperty change which calls OnValueChanged
// Calling it again causes double event firing

this.manualChange = false;
}
Expand Down
3 changes: 2 additions & 1 deletion src/MahApps.Metro/Controls/TimePicker/DateTimePicker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,8 @@ protected override void SetSelectedDateTime()
return;
}

if (DateTime.TryParse(this.textBox.Text, this.SpecificCultureInfo, System.Globalization.DateTimeStyles.None, out var dateTime))
// Use AssumeLocal to ensure consistent DateTimeKind.Local
if (DateTime.TryParse(this.textBox.Text, this.SpecificCultureInfo, System.Globalization.DateTimeStyles.AssumeLocal, out var dateTime))
{
this.SetCurrentValue(SelectedDateTimeProperty, dateTime);
this.SetCurrentValue(DisplayDateProperty, dateTime);
Expand Down