Skip to content

Commit

Permalink
[ImageResizer] Fix issues with blank Width and Height controls (#37373)
Browse files Browse the repository at this point in the history
* Allow custom preset's dimensions to be blank in the UI while still persisted as 0.

* XAML formatting - reorder namespaces.

* Add "(auto)" text to zero-value Width/Height in Settings. Ensure Width and Height fields in flyout are formatted to empty when their value is 0.
  • Loading branch information
daverayment authored Feb 25, 2025
1 parent 744316c commit a5a354a
Show file tree
Hide file tree
Showing 11 changed files with 309 additions and 27 deletions.
2 changes: 2 additions & 0 deletions src/modules/imageresizer/ui/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
<v:VisibilityBoolConverter x:Key="VisibilityBoolConverter" />
<v:EnumToIntConverter x:Key="EnumToIntConverter" />
<v:AccessTextToTextConverter x:Key="AccessTextToTextConverter" />
<v:NumberBoxValueConverter x:Key="NumberBoxValueConverter" />
<v:ZeroToEmptyStringNumberFormatter x:Key="ZeroToEmptyStringNumberFormatter" />
</ResourceDictionary>
</Application.Resources>
</Application>
66 changes: 46 additions & 20 deletions src/modules/imageresizer/ui/Views/AutoDoubleConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,55 @@

using ImageResizer.Properties;

namespace ImageResizer.Views
namespace ImageResizer.Views;

/// <summary>
/// Converts between double and string for text-based controls bound to Width or Height fields.
/// Optionally returns localized "Auto" text when the underlying value is 0, letting the UI show,
/// for example "(auto) x 1024 pixels".
/// </summary>
[ValueConversion(typeof(double), typeof(string))]
internal class AutoDoubleConverter : IValueConverter
{
[ValueConversion(typeof(double), typeof(string))]
internal class AutoDoubleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
/// <summary>
/// Converts a double to a string, optionally showing "Auto" for 0 values. NaN values are
/// converted to empty strings.
/// </summary>
/// <param name="value">The value to convert from <see cref="double"/> to
/// <see cref="string"/>.</param>
/// <param name="targetType">The conversion target type. <see cref="string"/> here.</param>
/// <param name="parameter">Set to "Auto" to return the localized "Auto" string if the
/// value is 0.</param>
/// <param name="culture">The <see cref="CultureInfo"/> to use for the number formatting.
/// </param>
/// <returns>The string representation of the passed-in value.</returns>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
value switch
{
var d = (double)value;
double d => d switch
{
double.NaN => "0",
0 => (string)parameter == "Auto" ? Resources.Input_Auto : "0",
_ => d.ToString(culture),
},

return d != 0
? d.ToString(culture)
: (string)parameter == "Auto"
? Resources.Input_Auto
: string.Empty;
}
_ => "0",
};

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
/// <summary>
/// Converts the string representation back to a double, returning 0 if the string is empty,
/// null or not a valid number in the specified culture.
/// </summary>
/// <param name="value">The string value to convert.</param>
/// <param name="targetType">The conversion target type. <see cref="double"/> here.</param>
/// <param name="parameter">Converter parameter. Unused.</param>
/// <param name="culture">The <see cref="CultureInfo"/> to use for the text parsing.</param>
/// <returns>The corresponding double value.</returns>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
value switch
{
var text = (string)value;

return !string.IsNullOrEmpty(text)
? double.Parse(text, culture)
: 0;
}
}
null or "" => 0,
string text when double.TryParse(text, NumberStyles.Any, culture, out double result) => result,
_ => 0,
};
}
11 changes: 10 additions & 1 deletion src/modules/imageresizer/ui/Views/InputPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:m="clr-namespace:ImageResizer.Models"
xmlns:p="clr-namespace:ImageResizer.Properties"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml">
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:v="clr-namespace:ImageResizer.Views">

<Grid>
<Grid.RowDefinitions>
Expand Down Expand Up @@ -114,8 +115,12 @@
KeyDown="Button_KeyDown"
Minimum="0"
SpinButtonPlacementMode="Inline">
<ui:NumberBox.NumberFormatter>
<v:ZeroToEmptyStringNumberFormatter />
</ui:NumberBox.NumberFormatter>
<ui:NumberBox.Value>
<Binding
Converter="{StaticResource NumberBoxValueConverter}"
ElementName="SizeComboBox"
Mode="TwoWay"
Path="SelectedValue.Width"
Expand Down Expand Up @@ -143,8 +148,12 @@
Minimum="0"
SpinButtonPlacementMode="Inline"
Visibility="{Binding ElementName=SizeComboBox, Path=SelectedValue.ShowHeight, Converter={StaticResource BoolValueConverter}}">
<ui:NumberBox.NumberFormatter>
<v:ZeroToEmptyStringNumberFormatter />
</ui:NumberBox.NumberFormatter>
<ui:NumberBox.Value>
<Binding
Converter="{StaticResource NumberBoxValueConverter}"
ElementName="SizeComboBox"
Mode="TwoWay"
Path="SelectedValue.Height"
Expand Down
32 changes: 32 additions & 0 deletions src/modules/imageresizer/ui/Views/NumberBoxValueConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Globalization;
using System.Windows.Data;

namespace ImageResizer.Views;

public class NumberBoxValueConverter : IValueConverter
{
/// <summary>
/// Converts the underlying double value to a display-friendly format. Ensures that NaN values
/// are not propagated to the UI.
/// </summary>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
value is double d && double.IsNaN(d) ? 0 : value;

/// <summary>
/// Converts the user input back to the underlying double value. If the input is not a valid
/// number, 0 is returned.
/// </summary>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
value switch
{
null => 0,
double d when double.IsNaN(d) => 0,
string str when !double.TryParse(str, out _) => 0,
_ => value,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Globalization;
using Wpf.Ui.Controls;

namespace ImageResizer.Views;

public class ZeroToEmptyStringNumberFormatter : INumberFormatter, INumberParser
{
public string FormatDouble(double? value) => value switch
{
null => string.Empty,
0 => string.Empty,
_ => value.Value.ToString(CultureInfo.CurrentCulture),
};

public double? ParseDouble(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return 0;
}

return double.TryParse(value, NumberStyles.Any, CultureInfo.CurrentCulture, out double result) ? result : 0;
}

public string FormatInt(int? value) => throw new NotImplementedException();

public string FormatUInt(uint? value) => throw new NotImplementedException();

public int? ParseInt(string value) => throw new NotImplementedException();

public uint? ParseUInt(string value) => throw new NotImplementedException();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Globalization;
using Microsoft.UI.Xaml.Data;

namespace Microsoft.PowerToys.Settings.UI.Converters;

/// <summary>
/// Converts between double and string for text-based controls bound to Width or Height fields.
/// Optionally returns localized "Auto" text when the underlying value is 0, letting the UI show,
/// for example "(auto) x 1024 pixels".
/// </summary>
public sealed partial class ImageResizerDoubleToAutoConverter : IValueConverter
{
private static readonly string AutoText =
Helpers.ResourceLoaderInstance.ResourceLoader.GetString("ImageResizer_AutoText");

/// <summary>
/// Converts a double to a string, optionally showing "Auto" for 0 values. NaN values are
/// converted to empty strings.
/// </summary>
/// <param name="value">The value to convert from <see cref="double"/> to
/// <see cref="string"/>.</param>
/// <param name="targetType">The conversion target type. <see cref="string"/> here.</param>
/// <param name="parameter">Set to "Auto" to return the localized "Auto" string if the
/// value is 0.</param>
/// <param name="language">Ignored.</param>
/// <returns>The string representation of the passed-in value.</returns>
public object Convert(object value, Type targetType, object parameter, string language) =>
value switch
{
double d => d switch
{
double.NaN => "0",
0 => (string)parameter == "Auto" ? AutoText : "0",
_ => d.ToString(CultureInfo.CurrentCulture),
},

_ => "0",
};

/// <summary>
/// Converts the string representation back to a double, returning 0 if the string is empty,
/// null or not a valid number in the specified culture.
/// </summary>
/// <param name="value">The string value to convert.</param>
/// <param name="targetType">The conversion target type. <see cref="double"/> here.</param>
/// <param name="parameter">Converter parameter. Unused.</param>
/// <param name="language">Ignored.</param>
/// <returns>The corresponding double value.</returns>
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
value switch
{
null or "" => 0.0,
string text when double.TryParse(text, NumberStyles.Any, CultureInfo.CurrentCulture, out double result) => result,
_ => 0.0,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using Microsoft.UI.Xaml.Data;

namespace Microsoft.PowerToys.Settings.UI.Converters;

public partial class ImageResizerNumberBoxValueConverter : IValueConverter
{
/// <summary>
/// Converts the underlying double value to a display-friendly format. Ensures that NaN values
/// are not propagated to the UI.
/// </summary>
public object Convert(object value, Type targetType, object parameter, string language) =>
value is double d && double.IsNaN(d) ? 0.0 : value;

/// <summary>
/// Converts the user input back to the underlying double value. If the input is not a valid
/// number, a double with value 0 is returned.
/// </summary>
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
value switch
{
null => 0.0,
double d when double.IsNaN(d) => 0.0,
string str when !double.TryParse(str, out _) => 0.0,
_ => value,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Globalization;
using Microsoft.UI.Xaml.Controls;

namespace Microsoft.PowerToys.Settings.UI.Converters;

public partial class ImageResizerZeroToEmptyStringNumberFormatter
{
public string Format(long value) => throw new NotImplementedException();

public string Format(ulong value) => throw new NotImplementedException();

public string Format(double value) => throw new NotImplementedException();

public string FormatDouble(double? value) => value switch
{
null => string.Empty,
0 => string.Empty,
_ => value.Value.ToString(CultureInfo.CurrentCulture),
};

public double? ParseDouble(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return 0.0;
}

return double.TryParse(text, NumberStyles.Any, CultureInfo.CurrentCulture, out double result) ? result : 0.0;
}

public long? ParseInt(string text) => throw new NotImplementedException();

public ulong? ParseUInt(string text) => throw new NotImplementedException();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

namespace Microsoft.PowerToys.Settings.UI.Controls;

public partial class ImageResizerDimensionsNumberBox : NumberBox
{
public ImageResizerDimensionsNumberBox()
{
this.Loaded += (_, _) => UpdateDisplayText();

this.ValueChanged += (_, _) => UpdateDisplayText();

this.GotFocus += (s, e) =>
{
// Show "0" in the UI when focused on the empty value. This ensures that the spinbutton
// controls are usable.
if (Value is double.NaN)
{
Value = 0.0;
}
};

this.LostFocus += (_, _) => UpdateDisplayText();
}

private void UpdateDisplayText()
{
if (FocusState == FocusState.Unfocused && Value == 0)
{
Text = string.Empty;
}
}
}
Loading

0 comments on commit a5a354a

Please sign in to comment.