fix(ui): stop NumericUpDown from writing null into non-nullable settings
Clearing the text box to type a new value sets Value to null, which the TwoWay binding then wrote into an int/decimal target -- InvalidCastException on the normal way of editing eight settings fields. KeepLastNumberConverter maps that null to BindingOperations.DoNothing so the source keeps its last value.
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
<converters:BoolToItalicConverter x:Key="BoolToItalic"/>
|
||||
<converters:BoolToDraftOpacityConverter x:Key="BoolToDraftOpacity"/>
|
||||
<converters:LogKindForegroundConverter x:Key="LogKindForeground"/>
|
||||
<converters:KeepLastNumberConverter x:Key="KeepLastNumber"/>
|
||||
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Globalization;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace ClaudeDo.Ui.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// For <c>NumericUpDown.Value</c> bound to a non-nullable numeric property. The control's Value is
|
||||
/// <c>decimal?</c> and goes null the moment the text box is empty — which is exactly what happens
|
||||
/// while the user clears a value to type a new one. Writing that null into an <c>int</c>/<c>decimal</c>
|
||||
/// target throws <see cref="InvalidCastException"/>, so swallow it and leave the source untouched
|
||||
/// until a real number arrives.
|
||||
/// </summary>
|
||||
public class KeepLastNumberConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> value is null ? null : System.Convert.ToDecimal(value, culture);
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is null) return BindingOperations.DoNothing;
|
||||
return System.Convert.ChangeType(value, Nullable.GetUnderlyingType(targetType) ?? targetType, culture);
|
||||
}
|
||||
}
|
||||
@@ -135,7 +135,7 @@
|
||||
<ComboBox Grid.Column="2" ItemsSource="{Binding EffortLevels}"
|
||||
SelectedItem="{Binding Effort, Mode=TwoWay}"
|
||||
HorizontalAlignment="Stretch"/>
|
||||
<NumericUpDown Grid.Column="4" Value="{Binding MaxTurns, Mode=TwoWay}"
|
||||
<NumericUpDown Grid.Column="4" Value="{Binding MaxTurns, Mode=TwoWay, Converter={StaticResource KeepLastNumber}}"
|
||||
Minimum="1" Maximum="200" Increment="1" FormatString="0"/>
|
||||
</Grid>
|
||||
<TextBlock Classes="meta" Opacity="0.6" TextWrapping="Wrap"
|
||||
@@ -147,7 +147,7 @@
|
||||
</ItemsControl>
|
||||
<StackPanel Spacing="4" Margin="0,4,0,0">
|
||||
<TextBlock Classes="eyebrow" Text="{loc:Tr settings.general.maxTurnsCeiling}"/>
|
||||
<NumericUpDown Value="{Binding General.MaxTurnsCeiling, Mode=TwoWay}"
|
||||
<NumericUpDown Value="{Binding General.MaxTurnsCeiling, Mode=TwoWay, Converter={StaticResource KeepLastNumber}}"
|
||||
Minimum="1" Maximum="200" Increment="10" FormatString="0"
|
||||
HorizontalAlignment="Left" Width="140"/>
|
||||
<TextBlock Text="{loc:Tr settings.general.maxTurnsCeilingHint}"
|
||||
@@ -156,7 +156,7 @@
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Classes="field-label" Text="{loc:Tr settings.general.maxParallelExecutions}"/>
|
||||
<NumericUpDown Value="{Binding General.MaxParallelExecutions, Mode=TwoWay}"
|
||||
<NumericUpDown Value="{Binding General.MaxParallelExecutions, Mode=TwoWay, Converter={StaticResource KeepLastNumber}}"
|
||||
Minimum="1" Maximum="20" Increment="1" FormatString="0"
|
||||
HorizontalAlignment="Left" Width="140"/>
|
||||
<TextBlock Text="{loc:Tr settings.general.maxParallelExecutionsHint}"
|
||||
@@ -169,13 +169,13 @@
|
||||
<StackPanel Orientation="Horizontal" Spacing="16">
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Classes="eyebrow" Text="{loc:Tr settings.general.usageGateFiveHourPct}"/>
|
||||
<NumericUpDown Value="{Binding General.UsageGateFiveHourPct, Mode=TwoWay}"
|
||||
<NumericUpDown Value="{Binding General.UsageGateFiveHourPct, Mode=TwoWay, Converter={StaticResource KeepLastNumber}}"
|
||||
Minimum="0" Maximum="100" Increment="5" FormatString="0"
|
||||
HorizontalAlignment="Left" Width="140"/>
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Classes="eyebrow" Text="{loc:Tr settings.general.usageGateSevenDayPct}"/>
|
||||
<NumericUpDown Value="{Binding General.UsageGateSevenDayPct, Mode=TwoWay}"
|
||||
<NumericUpDown Value="{Binding General.UsageGateSevenDayPct, Mode=TwoWay, Converter={StaticResource KeepLastNumber}}"
|
||||
Minimum="0" Maximum="100" Increment="5" FormatString="0"
|
||||
HorizontalAlignment="Left" Width="140"/>
|
||||
</StackPanel>
|
||||
@@ -234,7 +234,7 @@
|
||||
<CheckBox IsChecked="{Binding Worktrees.WorktreeAutoCleanupEnabled, Mode=TwoWay}"
|
||||
Content="{loc:Tr settings.worktrees.autoCleanup}"
|
||||
VerticalAlignment="Center"/>
|
||||
<NumericUpDown Value="{Binding Worktrees.WorktreeAutoCleanupDays, Mode=TwoWay}"
|
||||
<NumericUpDown Value="{Binding Worktrees.WorktreeAutoCleanupDays, Mode=TwoWay, Converter={StaticResource KeepLastNumber}}"
|
||||
Width="130" Minimum="1" Maximum="365" Increment="1" FormatString="0"
|
||||
IsEnabled="{Binding Worktrees.WorktreeAutoCleanupEnabled}"/>
|
||||
<TextBlock Classes="body" Text="{loc:Tr settings.worktrees.days}" VerticalAlignment="Center"/>
|
||||
@@ -384,7 +384,7 @@
|
||||
<StackPanel Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
||||
<TextBlock Classes="field-label" Text="{loc:Tr settings.prime.dailyPrepMaxTasks}" VerticalAlignment="Center"/>
|
||||
<NumericUpDown Minimum="1" Maximum="50" Increment="1" Width="100" FormatString="0"
|
||||
Value="{Binding Prime.DailyPrepMaxTasks, Mode=TwoWay}"/>
|
||||
Value="{Binding Prime.DailyPrepMaxTasks, Mode=TwoWay, Converter={StaticResource KeepLastNumber}}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
@@ -466,7 +466,7 @@
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Classes="field-label" Text="{loc:Tr settings.onlineInbox.pollIntervalLabel}"/>
|
||||
<NumericUpDown Value="{Binding OnlineInbox.PollIntervalSeconds, Mode=TwoWay}"
|
||||
<NumericUpDown Value="{Binding OnlineInbox.PollIntervalSeconds, Mode=TwoWay, Converter={StaticResource KeepLastNumber}}"
|
||||
Minimum="10" Maximum="3600" Increment="10" FormatString="0"
|
||||
HorizontalAlignment="Left" Width="140"/>
|
||||
</StackPanel>
|
||||
|
||||
Reference in New Issue
Block a user