[WIP] Refactor converters and dependant components.

pull/2/head
Matija Koželj 2 years ago
parent 6f91dacb0c
commit be914e4b2e

@ -0,0 +1,226 @@
# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true
# C# files
[*.cs]
#### Core EditorConfig Options ####
# Indentation and spacing
indent_size = 3
indent_style = tab
tab_width = 3
# New line preferences
end_of_line = crlf
insert_final_newline = false
#### .NET Coding Conventions ####
# Organize usings
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false
file_header_template = unset
# this. and Me. preferences
dotnet_style_qualification_for_event = false:suggestion
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members
# Expression-level preferences
dotnet_style_coalesce_expression = true
dotnet_style_collection_initializer = true
dotnet_style_explicit_tuple_names = true
dotnet_style_namespace_match_folder = true
dotnet_style_null_propagation = true
dotnet_style_object_initializer = true
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true:suggestion
dotnet_style_prefer_compound_assignment = true
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
dotnet_style_prefer_inferred_anonymous_type_member_names = true
dotnet_style_prefer_inferred_tuple_names = false
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
dotnet_style_prefer_simplified_boolean_expressions = true
dotnet_style_prefer_simplified_interpolation = true
# Field preferences
dotnet_style_readonly_field = true
# Parameter preferences
dotnet_code_quality_unused_parameters = all
# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = 0
# New line preferences
dotnet_style_allow_multiple_blank_lines_experimental = false:suggestion
dotnet_style_allow_statement_immediately_after_block_experimental = false:suggestion
#### C# Coding Conventions ####
# var preferences
csharp_style_var_elsewhere = true:warning
csharp_style_var_for_built_in_types = true:warning
csharp_style_var_when_type_is_apparent = true:warning
# Expression-bodied members
csharp_style_expression_bodied_accessors = when_on_single_line:suggestion
csharp_style_expression_bodied_constructors = when_on_single_line:suggestion
csharp_style_expression_bodied_indexers = when_on_single_line:suggestion
csharp_style_expression_bodied_lambdas = when_on_single_line:suggestion
csharp_style_expression_bodied_local_functions = when_on_single_line:suggestion
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
csharp_style_expression_bodied_operators = when_on_single_line:suggestion
csharp_style_expression_bodied_properties = when_on_single_line:suggestion
# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true
csharp_style_pattern_matching_over_is_with_cast_check = true
csharp_style_prefer_extended_property_pattern = true
csharp_style_prefer_not_pattern = true
csharp_style_prefer_pattern_matching = true:suggestion
csharp_style_prefer_switch_expression = true
# Null-checking preferences
csharp_style_conditional_delegate_call = true
# Modifier preferences
csharp_prefer_static_local_function = true
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
csharp_style_prefer_readonly_struct = true
# Code-block preferences
csharp_prefer_braces = when_multiline:suggestion
csharp_prefer_simple_using_statement = true
csharp_style_namespace_declarations = file_scoped:warning
csharp_style_prefer_method_group_conversion = true:suggestion
csharp_style_prefer_top_level_statements = false:suggestion
# Expression-level preferences
csharp_prefer_simple_default_expression = true
csharp_style_deconstructed_variable_declaration = true
csharp_style_implicit_object_creation_when_type_is_apparent = true
csharp_style_inlined_variable_declaration = true
csharp_style_prefer_index_operator = true
csharp_style_prefer_local_over_anonymous_function = true
csharp_style_prefer_null_check_over_type_check = true
csharp_style_prefer_range_operator = true
csharp_style_prefer_tuple_swap = true
csharp_style_prefer_utf8_string_literals = true
csharp_style_throw_expression = true
csharp_style_unused_value_assignment_preference = discard_variable
csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:suggestion
# New line preferences
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:suggestion
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:suggestion
csharp_style_allow_embedded_statements_on_same_line_experimental = false:suggestion
#### C# Formatting Rules ####
# New line preferences
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = no_change
csharp_indent_switch_labels = true
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case

@ -83,7 +83,7 @@ public partial class Autocomplete<T> : InputBase<T>, IDisposable
_toStringFunc = value; _toStringFunc = value;
Converter = new LambdaConverter<T, string>(_toStringFunc ?? (x => x?.ToString()), null); SetConverter(new LambdaConverter<T, string>(_toStringFunc ?? (x => x?.ToString()), null));
} }
} }
/// <summary> /// <summary>

@ -1,4 +1,5 @@
using Connected.Extensions; using Connected.Extensions;
using Connected.Utilities.BindingConverters;
namespace Connected.Components; namespace Connected.Components;

@ -68,12 +68,7 @@ public abstract class FormComponent<T, U> : UIComponent, IFormComponent, IDispos
/// <summary> /// <summary>
/// The generic converter of the component. /// The generic converter of the component.
/// </summary> /// </summary>
[Parameter] public Converter<T, U> Converter => _converter;
public Converter<T, U> Converter
{
get => _converter;
set => SetConverter(value);
}
/// <summary> /// <summary>
/// The culture of the component. Also sets the culture of the <see cref="Converter"/> . /// The culture of the component. Also sets the culture of the <see cref="Converter"/> .

@ -1,5 +1,6 @@
using System.Timers; using System.Timers;
using Connected.Annotations; using Connected.Annotations;
using Connected.Utilities.BindingConverters;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace Connected.Components; namespace Connected.Components;

@ -1,4 +1,5 @@
using Connected.Extensions; using Connected.Extensions;
using Connected.Utilities.BindingConverters;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace Connected.Components; namespace Connected.Components;
@ -10,7 +11,7 @@ public partial class RangeInput<T> : InputBase<Range<T>>
public RangeInput() public RangeInput()
{ {
Value = new Range<T>(); Value = new Range<T>();
Converter = new RangeConverter<T>(); SetConverter(new RangeConverter<T>());
} }
protected string Classname => InputCssHelper.GetClassname(this, protected string Classname => InputCssHelper.GetClassname(this,

@ -8,6 +8,7 @@ using System.Globalization;
using Connected.Annotations; using Connected.Annotations;
using Connected.Services; using Connected.Services;
using Connected.Utilities; using Connected.Utilities;
using Connected.Utilities.BindingConverters;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
@ -208,7 +209,7 @@ public partial class NumericField<T> : DebouncedInput<T>
return (T)(object)Convert.ToInt64(FromInt64(Value) + FromInt64(Step) * factor); return (T)(object)Convert.ToInt64(FromInt64(Value) + FromInt64(Step) * factor);
if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?)) if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?))
return (T)(object)Convert.ToUInt64(FromUInt64(Value) + FromUInt64(Step) * factor); return (T)(object)Convert.ToUInt64(FromUInt64(Value) + FromUInt64(Step) * factor);
return Num.To<T>(Num.From(Value) + Num.From(Step) * factor); return Number.To<T>(Number.From(Value) + Number.From(Step) * factor);
} }
/// <summary> /// <summary>

@ -2,6 +2,7 @@
using Connected.Annotations; using Connected.Annotations;
using Connected.Extensions; using Connected.Extensions;
using Connected.Utilities; using Connected.Utilities;
using Connected.Utilities.BindingConverters;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace Connected.Components; namespace Connected.Components;

@ -293,7 +293,7 @@ public partial class Select<T> : InputBase<T>, ISelect, IShadowSelect
if (_toStringFunc == value) if (_toStringFunc == value)
return; return;
_toStringFunc = value; _toStringFunc = value;
Converter = new LambdaConverter<T, string>(_toStringFunc ?? (x => x?.ToString()), null); SetConverter(new LambdaConverter<T, string>(_toStringFunc ?? (x => x?.ToString()), null));
} }
} }

@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Dynamic;
using System.Globalization; using System.Globalization;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Connected.Annotations; using Connected.Annotations;
@ -20,7 +19,7 @@ public partial class TimePicker : Picker<TimeSpan?>
{ {
_timeFormat = format24Hours; _timeFormat = format24Hours;
Converter = new LambdaConverter<TimeSpan?, string>((e) => OnSet(e), (e) => OnGet(e)); SetConverter(new LambdaConverter<TimeSpan?, string>((e) => OnSet(e), (e) => OnGet(e)));
AdornmentIcon = Icons.Material.Filled.AccessTime; AdornmentIcon = Icons.Material.Filled.AccessTime;
AdornmentAriaLabel = "Open Time Picker"; AdornmentAriaLabel = "Open Time Picker";

@ -1,38 +1,48 @@
using System.ComponentModel; using System.ComponentModel;
namespace Connected namespace Connected;
public enum InputType
{ {
public enum InputType
{
[Description("text")] [Description("text")]
Text, Text,
[Description("password")] [Description("password")]
Password, Password,
[Description("email")] [Description("email")]
Email, Email,
[Description("hidden")] [Description("hidden")]
Hidden, Hidden,
[Description("number")] [Description("number")]
Number, Number,
[Description("search")] [Description("search")]
Search, Search,
[Description("tel")] [Description("tel")]
Telephone, Telephone,
[Description("url")] [Description("url")]
Url, Url,
[Description("color")] [Description("color")]
Color, Color,
[Description("date")] [Description("date")]
Date, Date,
[Description("datetime-local")] [Description("datetime-local")]
DateTimeLocal, DateTimeLocal,
[Description("month")] [Description("month")]
Month, Month,
[Description("time")] [Description("time")]
Time, Time,
[Description("week")] [Description("week")]
Week Week
}
} }

@ -67,7 +67,7 @@ public class Converter<TSourceType, TDestinationType>
/// </summary> /// </summary>
/// <typeparam name="TSourceType">The source type</typeparam> /// <typeparam name="TSourceType">The source type</typeparam>
/// <typeparam name="TDestinationType">The destination type</typeparam> /// <typeparam name="TDestinationType">The destination type</typeparam>
public class LambdaConverter<TSourceType, TDestinationType> :Converter<TSourceType, TDestinationType> public class LambdaConverter<TSourceType, TDestinationType> : Converter<TSourceType, TDestinationType>
{ {
private readonly Func<TSourceType, TDestinationType?>? _convertFunction; private readonly Func<TSourceType, TDestinationType?>? _convertFunction;
private readonly Func<TDestinationType, TSourceType?>? _convertBackFunction; private readonly Func<TDestinationType, TSourceType?>? _convertBackFunction;

@ -1,54 +1,8 @@
using Connected.Extensions; using System.Globalization;
using System.Globalization;
namespace Connected namespace Connected;
{
public static class Converters
{
public static CultureInfo DefaultCulture = CultureInfo.CurrentUICulture;
#region --> Date converters
public static LambdaConverter<DateTime, string> IsoDate
=> new(SetIsoDate, GetIsoDate);
private static DateTime GetIsoDate(string value)
{
if (DateTime.TryParse(value, out var dateTime))
return dateTime;
return DateTime.MinValue;
}
private static string SetIsoDate(DateTime value)
{
return value.ToIsoDateString();
}
public static LambdaConverter<DateTime?, string?> NullableIsoDate
=> new(SetNullableIsoDate, GetNullableIsoDate);
private static DateTime? GetNullableIsoDate(string? value) public static class Converters
{ {
if (DateTime.TryParse(value, out var dateTime)) public static CultureInfo DefaultCulture { get; set; } = CultureInfo.CurrentUICulture;
return dateTime;
return null;
}
private static string SetNullableIsoDate(DateTime? value)
{
return value.ToIsoDateString();
}
public static DateConverter DateFormat(string format)
{
format ??= "yyyy-MM-dd";
return new DateConverter(format);
}
public static DateConverter DateFormat(string format, CultureInfo culture)
{
return new DateConverter(format) { Culture = culture };
}
#endregion
}
} }

@ -1,106 +1,29 @@
using System; namespace Connected;
namespace Connected /// <summary>
/// A ready made DateTime? to string binding converter with configurable date format and culture
/// </summary>
public class NullableDateConverter : ToStringConverter<DateTime?>
{ {
/// <summary>
/// A ready made DateTime? to string binding converter with configurable date format and culture
/// </summary>
public class NullableDateConverter : ToStringConverter<DateTime?>
{
public string DateFormat { get; set; } public string DateFormat { get; set; }
public NullableDateConverter(string format = "yyyy-MM-dd") public NullableDateConverter(string format = "yyyy-MM-dd") => DateFormat = format;
{
DateFormat = format;
}
protected override string? ConvertValue(DateTime? value)
{
return OnSet(value);
}
protected override DateTime? ConvertValueBack(string? value)
{
return OnGet(value);
}
private DateTime? OnGet(string arg)
{
try
{
return DateTime.ParseExact(arg, DateFormat, Culture);
}
catch (FormatException e)
{
TriggerError(e.Message);
return null;
}
}
private string OnSet(DateTime? arg)
{
if (arg == null)
return null;
try
{
return arg.Value.ToString(DateFormat, Culture);
}
catch (FormatException e)
{
TriggerError(e.Message);
return null;
}
}
}
/// <summary> protected override string? ConvertValue(DateTime? value) => value?.ToString(DateFormat, Culture);
/// A ready made DateTime to string binding converter with configurable date format and culture
/// </summary>
public class DateConverter : ToStringConverter<DateTime>
{
public string DateFormat { get; set; } = "yyyy-MM-dd";
public DateConverter(string format) protected override DateTime? ConvertValueBack(string? value) => DateTime.ParseExact(value, DateFormat, Culture);
{ }
DateFormat = format;
}
protected override string? ConvertValue(DateTime value)
{
return OnSet(value);
}
protected override DateTime ConvertValueBack(string value) /// <summary>
{ /// A ready made DateTime to string binding converter with configurable date format and culture
return OnGet(value); /// </summary>
} public class DateConverter : ToStringConverter<DateTime>
{
public string DateFormat { get; set; }
private DateTime OnGet(string arg) public DateConverter(string format = "yyyy-MM-dd") => DateFormat = format;
{
try
{
return DateTime.ParseExact(arg, DateFormat, Culture);
}
catch (FormatException e)
{
TriggerError(e.Message);
return default;
}
}
private string OnSet(DateTime arg) protected override string? ConvertValue(DateTime value) => value.ToString(DateFormat, Culture);
{
try
{
return arg.ToString(DateFormat, Culture);
}
catch (FormatException e)
{
TriggerError(e.Message);
return null;
}
}
}
protected override DateTime ConvertValueBack(string? value) => DateTime.ParseExact(value, DateFormat, Culture);
} }

@ -1,156 +1,200 @@
using System; using System.Globalization;
using System.Globalization;
namespace Connected namespace Connected;
{
/// <summary>
/// A universal T to string binding converter
/// </summary>
public class DefaultConverter<T> : ToStringConverter<T>
{
public DefaultConverter()
{
}
protected override string? ConvertValue(T? value) /// <summary>
{ /// A universal T to string binding converter
return ConvertToString(value); /// </summary>
} public class DefaultConverter<T> : ToStringConverter<T>
{
protected override string? ConvertValue(T? value) => ConvertToString(value);
protected override T? ConvertValueBack(string? value) protected override T? ConvertValueBack(string? value) => ConvertFromString(value);
{
return ConvertFromString(value);
}
public string DefaultTimeSpanFormat { get; set; } = "c"; public string DefaultTimeSpanFormat { get; set; } = "c";
protected virtual T ConvertFromString(string value) protected virtual T? ConvertFromString(string? value)
{ {
try try
{ {
// string /*
if (typeof(T) == typeof(string)) * This is important, or otherwise all the TryParse down there might fail.
return (T)(object)value; */
// this is important, or otherwise all the TryParse down there might fail.
if (string.IsNullOrEmpty(value)) if (string.IsNullOrEmpty(value))
return default(T); {
// char return default;
}
/*
* String
*/
else if (typeof(T) == typeof(string))
{
return (T)(object)value;
}
/*
* Char
*/
else if (typeof(T) == typeof(char) || typeof(T) == typeof(char?)) else if (typeof(T) == typeof(char) || typeof(T) == typeof(char?))
{ {
return (T)(object)value[0]; return (T)(object)value[0];
} }
// bool /*
* Bool
*/
else if (typeof(T) == typeof(bool) || typeof(T) == typeof(bool?)) else if (typeof(T) == typeof(bool) || typeof(T) == typeof(bool?))
{ {
var lowerValue = value.ToLowerInvariant(); var lowerValue = value.ToLowerInvariant();
if (lowerValue is "true" or "on") if (lowerValue is "true" or "on")
return (T)(object)true; return (T)(object)true;
if (lowerValue is "false" or "off") if (lowerValue is "false" or "off")
return (T)(object)false; return (T)(object)false;
TriggerError("Not a valid boolean"); TriggerError("Not a valid boolean");
} }
// sbyte /*
* Sbyte
*/
else if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(sbyte?)) else if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(sbyte?))
{ {
if (sbyte.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) if (sbyte.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue; return (T)(object)parsedValue;
TriggerError("Not a valid number"); TriggerError("Not a valid number");
} }
// byte /*
* Byte
*/
else if (typeof(T) == typeof(byte) || typeof(T) == typeof(byte?)) else if (typeof(T) == typeof(byte) || typeof(T) == typeof(byte?))
{ {
if (byte.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) if (byte.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue; return (T)(object)parsedValue;
TriggerError("Not a valid number"); TriggerError("Not a valid number");
} }
// short /*
* Short
*/
else if (typeof(T) == typeof(short) || typeof(T) == typeof(short?)) else if (typeof(T) == typeof(short) || typeof(T) == typeof(short?))
{ {
if (short.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) if (short.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue; return (T)(object)parsedValue;
TriggerError("Not a valid number"); TriggerError("Not a valid number");
} }
// ushort /*
* Ushort
*/
else if (typeof(T) == typeof(ushort) || typeof(T) == typeof(ushort?)) else if (typeof(T) == typeof(ushort) || typeof(T) == typeof(ushort?))
{ {
if (ushort.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) if (ushort.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue; return (T)(object)parsedValue;
TriggerError("Not a valid number"); TriggerError("Not a valid number");
} }
// int /*
* Int
*/
else if (typeof(T) == typeof(int) || typeof(T) == typeof(int?)) else if (typeof(T) == typeof(int) || typeof(T) == typeof(int?))
{ {
if (int.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) if (int.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue; return (T)(object)parsedValue;
TriggerError("Not a valid number"); TriggerError("Not a valid number");
} }
// uint /*
* Uint
*/
else if (typeof(T) == typeof(uint) || typeof(T) == typeof(uint?)) else if (typeof(T) == typeof(uint) || typeof(T) == typeof(uint?))
{ {
if (uint.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) if (uint.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue; return (T)(object)parsedValue;
TriggerError("Not a valid number"); TriggerError("Not a valid number");
} }
// long /*
* Long
*/
else if (typeof(T) == typeof(long) || typeof(T) == typeof(long?)) else if (typeof(T) == typeof(long) || typeof(T) == typeof(long?))
{ {
if (long.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) if (long.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue; return (T)(object)parsedValue;
TriggerError("Not a valid number"); TriggerError("Not a valid number");
} }
// ulong /*
* Ulong
*/
else if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?)) else if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?))
{ {
if (ulong.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue)) if (ulong.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands, Culture, out var parsedValue))
return (T)(object)parsedValue; return (T)(object)parsedValue;
TriggerError("Not a valid number"); TriggerError("Not a valid number");
} }
// float /*
* Float
*/
else if (typeof(T) == typeof(float) || typeof(T) == typeof(float?)) else if (typeof(T) == typeof(float) || typeof(T) == typeof(float?))
{ {
if (float.TryParse(value, NumberStyles.Any, Culture, out var parsedValue)) if (float.TryParse(value, NumberStyles.Any, Culture, out var parsedValue))
return (T)(object)parsedValue; return (T)(object)parsedValue;
TriggerError("Not a valid number"); TriggerError("Not a valid number");
} }
// double /*
* Double
*/
else if (typeof(T) == typeof(double) || typeof(T) == typeof(double?)) else if (typeof(T) == typeof(double) || typeof(T) == typeof(double?))
{ {
if (double.TryParse(value, NumberStyles.Any, Culture, out var parsedValue)) if (double.TryParse(value, NumberStyles.Any, Culture, out var parsedValue))
return (T)(object)parsedValue; return (T)(object)parsedValue;
TriggerError("Not a valid number"); TriggerError("Not a valid number");
} }
// decimal /*
* Decimal
*/
else if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?)) else if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?))
{ {
if (decimal.TryParse(value, NumberStyles.Any, Culture, out var parsedValue)) if (decimal.TryParse(value, NumberStyles.Any, Culture, out var parsedValue))
return (T)(object)parsedValue; return (T)(object)parsedValue;
TriggerError("Not a valid number"); TriggerError("Not a valid number");
} }
// guid /*
* Guid
*/
else if (typeof(T) == typeof(Guid) || typeof(T) == typeof(Guid?)) else if (typeof(T) == typeof(Guid) || typeof(T) == typeof(Guid?))
{ {
if (Guid.TryParse(value, out var parsedValue)) if (Guid.TryParse(value, out var parsedValue))
return (T)(object)parsedValue; return (T)(object)parsedValue;
TriggerError("Not a valid GUID"); TriggerError("Not a valid GUID");
} }
// enum /*
* Enum
*/
else if (IsNullableEnum(typeof(T))) else if (IsNullableEnum(typeof(T)))
{ {
var enum_type = Nullable.GetUnderlyingType(typeof(T)); var enum_type = Nullable.GetUnderlyingType(typeof(T));
if (Enum.TryParse(enum_type, value, out var parsedValue)) if (Enum.TryParse(enum_type, value, out var parsedValue))
return (T)parsedValue; return (T)parsedValue;
TriggerError("Not a value of " + enum_type.Name); TriggerError("Not a value of " + enum_type.Name);
} }
else if (typeof(T).IsEnum) else if (typeof(T).IsEnum)
{ {
if (Enum.TryParse(typeof(T), value, out var parsedValue)) if (Enum.TryParse(typeof(T), value, out var parsedValue))
return (T)parsedValue; return (T)parsedValue;
TriggerError("Not a value of " + typeof(T).Name); TriggerError("Not a value of " + typeof(T).Name);
} }
// datetime /*
* DateTime
*/
else if (typeof(T) == typeof(DateTime) || typeof(T) == typeof(DateTime?)) else if (typeof(T) == typeof(DateTime) || typeof(T) == typeof(DateTime?))
{ {
try try
@ -162,7 +206,9 @@ namespace Connected
TriggerError("Not a valid date time"); TriggerError("Not a valid date time");
} }
} }
// timespan /*
* Timespan
*/
else if (typeof(T) == typeof(TimeSpan) || typeof(T) == typeof(TimeSpan?)) else if (typeof(T) == typeof(TimeSpan) || typeof(T) == typeof(TimeSpan?))
{ {
try try
@ -184,84 +230,172 @@ namespace Connected
TriggerError("Conversion error: " + e.Message); TriggerError("Conversion error: " + e.Message);
} }
return default(T); return default;
} }
protected virtual string ConvertToString(T arg) protected virtual string? ConvertToString(T? arg)
{ {
if (arg == null) /*
return null; // <-- this catches all nullable values which are null. no nullchecks necessary below! * This catches all null values. No additional null checks necessary.
*/
if (arg is null)
return null;
try try
{ {
// string /*
* String
*/
if (typeof(T) == typeof(string)) if (typeof(T) == typeof(string))
{
return (string)(object)arg; return (string)(object)arg;
// char }
if (typeof(T) == typeof(char)) /*
* Char
*/
else if (typeof(T) == typeof(char))
{
return ((char)(object)arg).ToString(Culture); return ((char)(object)arg).ToString(Culture);
if (typeof(T) == typeof(char?)) }
else if (typeof(T) == typeof(char?))
{
return ((char?)(object)arg).Value.ToString(Culture); return ((char?)(object)arg).Value.ToString(Culture);
// bool }
if (typeof(T) == typeof(bool)) /*
* Bool
*/
else if (typeof(T) == typeof(bool))
{
return ((bool)(object)arg).ToString(CultureInfo.InvariantCulture); return ((bool)(object)arg).ToString(CultureInfo.InvariantCulture);
if (typeof(T) == typeof(bool?)) }
else if (typeof(T) == typeof(bool?))
{
return ((bool?)(object)arg).Value.ToString(CultureInfo.InvariantCulture); return ((bool?)(object)arg).Value.ToString(CultureInfo.InvariantCulture);
// sbyte }
if (typeof(T) == typeof(sbyte)) /*
* Sbyte
*/
else if (typeof(T) == typeof(sbyte))
{
return ((sbyte)(object)arg).ToString(Format, Culture); return ((sbyte)(object)arg).ToString(Format, Culture);
if (typeof(T) == typeof(sbyte?)) }
else if (typeof(T) == typeof(sbyte?))
{
return ((sbyte?)(object)arg).Value.ToString(Format, Culture); return ((sbyte?)(object)arg).Value.ToString(Format, Culture);
// byte }
if (typeof(T) == typeof(byte)) /*
* Byte
*/
else if (typeof(T) == typeof(byte))
{
return ((byte)(object)arg).ToString(Format, Culture); return ((byte)(object)arg).ToString(Format, Culture);
if (typeof(T) == typeof(byte?)) }
else if (typeof(T) == typeof(byte?))
{
return ((byte?)(object)arg).Value.ToString(Format, Culture); return ((byte?)(object)arg).Value.ToString(Format, Culture);
// short }
if (typeof(T) == typeof(short)) /*
* Short
*/
else if (typeof(T) == typeof(short))
{
return ((short)(object)arg).ToString(Format, Culture); return ((short)(object)arg).ToString(Format, Culture);
if (typeof(T) == typeof(short?)) }
else if (typeof(T) == typeof(short?))
{
return ((short?)(object)arg).Value.ToString(Format, Culture); return ((short?)(object)arg).Value.ToString(Format, Culture);
// ushort }
if (typeof(T) == typeof(ushort)) /*
* Ushort
*/
else if (typeof(T) == typeof(ushort))
{
return ((ushort)(object)arg).ToString(Format, Culture); return ((ushort)(object)arg).ToString(Format, Culture);
if (typeof(T) == typeof(ushort?)) }
else if (typeof(T) == typeof(ushort?))
{
return ((ushort?)(object)arg).Value.ToString(Format, Culture); return ((ushort?)(object)arg).Value.ToString(Format, Culture);
// int }
/*
* Int
*/
else if (typeof(T) == typeof(int)) else if (typeof(T) == typeof(int))
{
return ((int)(object)arg).ToString(Format, Culture); return ((int)(object)arg).ToString(Format, Culture);
}
else if (typeof(T) == typeof(int?)) else if (typeof(T) == typeof(int?))
{
return ((int?)(object)arg).Value.ToString(Format, Culture); return ((int?)(object)arg).Value.ToString(Format, Culture);
// uint }
/*
* Uint
*/
else if (typeof(T) == typeof(uint)) else if (typeof(T) == typeof(uint))
{
return ((uint)(object)arg).ToString(Format, Culture); return ((uint)(object)arg).ToString(Format, Culture);
}
else if (typeof(T) == typeof(uint?)) else if (typeof(T) == typeof(uint?))
{
return ((uint?)(object)arg).Value.ToString(Format, Culture); return ((uint?)(object)arg).Value.ToString(Format, Culture);
// long }
/*
* Long
*/
else if (typeof(T) == typeof(long)) else if (typeof(T) == typeof(long))
{
return ((long)(object)arg).ToString(Format, Culture); return ((long)(object)arg).ToString(Format, Culture);
}
else if (typeof(T) == typeof(long?)) else if (typeof(T) == typeof(long?))
{
return ((long?)(object)arg).Value.ToString(Format, Culture); return ((long?)(object)arg).Value.ToString(Format, Culture);
// ulong }
/*
* Ulong
*/
else if (typeof(T) == typeof(ulong)) else if (typeof(T) == typeof(ulong))
{
return ((ulong)(object)arg).ToString(Format, Culture); return ((ulong)(object)arg).ToString(Format, Culture);
}
else if (typeof(T) == typeof(ulong?)) else if (typeof(T) == typeof(ulong?))
{
return ((ulong?)(object)arg).Value.ToString(Format, Culture); return ((ulong?)(object)arg).Value.ToString(Format, Culture);
// float }
/*
* Float
*/
else if (typeof(T) == typeof(float)) else if (typeof(T) == typeof(float))
{
return ((float)(object)arg).ToString(Format, Culture); return ((float)(object)arg).ToString(Format, Culture);
}
else if (typeof(T) == typeof(float?)) else if (typeof(T) == typeof(float?))
{
return ((float?)(object)arg).Value.ToString(Format, Culture); return ((float?)(object)arg).Value.ToString(Format, Culture);
// double }
/*
* Double
*/
else if (typeof(T) == typeof(double)) else if (typeof(T) == typeof(double))
{
return ((double)(object)arg).ToString(Format, Culture); return ((double)(object)arg).ToString(Format, Culture);
}
else if (typeof(T) == typeof(double?)) else if (typeof(T) == typeof(double?))
{
return ((double?)(object)arg).Value.ToString(Format, Culture); return ((double?)(object)arg).Value.ToString(Format, Culture);
// decimal }
/*
* Decimal
*/
else if (typeof(T) == typeof(decimal)) else if (typeof(T) == typeof(decimal))
{
return ((decimal)(object)arg).ToString(Format, Culture); return ((decimal)(object)arg).ToString(Format, Culture);
}
else if (typeof(T) == typeof(decimal?)) else if (typeof(T) == typeof(decimal?))
{
return ((decimal?)(object)arg).Value.ToString(Format, Culture); return ((decimal?)(object)arg).Value.ToString(Format, Culture);
// guid }
/*
* Guid
*/
else if (typeof(T) == typeof(Guid)) else if (typeof(T) == typeof(Guid))
{ {
var value = (Guid)(object)arg; var value = (Guid)(object)arg;
@ -272,7 +406,9 @@ namespace Connected
var value = (Guid?)(object)arg; var value = (Guid?)(object)arg;
return value.Value.ToString(); return value.Value.ToString();
} }
// enum /*
* Enum
*/
else if (IsNullableEnum(typeof(T))) else if (IsNullableEnum(typeof(T)))
{ {
var value = (Enum)(object)arg; var value = (Enum)(object)arg;
@ -283,7 +419,9 @@ namespace Connected
var value = (Enum)(object)arg; var value = (Enum)(object)arg;
return value.ToString(); return value.ToString();
} }
// datetime /*
* DateTime
*/
else if (typeof(T) == typeof(DateTime)) else if (typeof(T) == typeof(DateTime))
{ {
var value = (DateTime)(object)arg; var value = (DateTime)(object)arg;
@ -294,7 +432,9 @@ namespace Connected
var value = (DateTime?)(object)arg; var value = (DateTime?)(object)arg;
return value.Value.ToString(Format ?? Culture.DateTimeFormat.ShortDatePattern, Culture); return value.Value.ToString(Format ?? Culture.DateTimeFormat.ShortDatePattern, Culture);
} }
// timespan /*
* Timespan
*/
else if (typeof(T) == typeof(TimeSpan)) else if (typeof(T) == typeof(TimeSpan))
{ {
var value = (TimeSpan)(object)arg; var value = (TimeSpan)(object)arg;
@ -305,6 +445,7 @@ namespace Connected
var value = (TimeSpan?)(object)arg; var value = (TimeSpan?)(object)arg;
return value.Value.ToString(Format ?? DefaultTimeSpanFormat, Culture); return value.Value.ToString(Format ?? DefaultTimeSpanFormat, Culture);
} }
return arg.ToString(); return arg.ToString();
} }
catch (FormatException e) catch (FormatException e)
@ -317,7 +458,7 @@ namespace Connected
public static bool IsNullableEnum(Type t) public static bool IsNullableEnum(Type t)
{ {
var u = Nullable.GetUnderlyingType(t); var u = Nullable.GetUnderlyingType(t);
return (u != null) && u.IsEnum;
} return (u is not null) && u.IsEnum;
} }
} }

@ -0,0 +1,17 @@
using Connected.Extensions;
namespace Connected;
public class IsoDateConverter : Converter<DateTime, string>
{
protected override string ConvertValue(DateTime value) => value.ToIsoDateString();
protected override DateTime ConvertValueBack(string? value) => DateTime.TryParse(value, out var dateTime) ? dateTime : DateTime.MinValue;
}
public class NullableIsoDateConverter : Converter<DateTime?, string?>
{
protected override string? ConvertValue(DateTime? value) => value?.ToIsoDateString();
protected override DateTime? ConvertValueBack(string? value) => DateTime.TryParse(value, out var dateTime) ? dateTime : null;
}

@ -1,199 +1,295 @@
using System; using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
namespace Connected namespace Connected;
/// <summary>
/// A universal T to double binding converter
///
/// Note: currently not in use. Should we ever use it, remove
/// the [ExcludeFromCodeCoverage] attribute
/// </summary>
[ExcludeFromCodeCoverage]
public class NumericConverter<T> : Converter<T, double>
{ {
/// <summary> protected override double ConvertValue(T? value)
/// A universal T to double binding converter
///
/// Note: currently not in use. Should we ever use it, remove
/// the [ExcludeFromCodeCoverage] attribute
/// </summary>
[ExcludeFromCodeCoverage]
public class NumericConverter<T> : Converter<T, double>
{ {
if (value is null)
public NumericConverter()
{ {
return double.NaN;
} }
/*
protected override double ConvertValue(T? value) * Double
*/
else if (typeof(T) == typeof(double))
{ {
return OnSet(value); return (double)(object)value;
} }
else if (typeof(T) == typeof(double?))
protected override T? ConvertValueBack(double value)
{ {
return OnGet(value); return ((double?)(object)value).Value;
} }
/*
private T OnGet(double value) * String
*/
else if (typeof(T) == typeof(string))
{ {
try return double.Parse((string)(object)value, NumberStyles.Any, Culture);
}
/*
* SByte
*/
else if (typeof(T) == typeof(sbyte))
{ {
// double return System.Convert.ToDouble((sbyte)(object)value);
if (typeof(T) == typeof(double) || typeof(T) == typeof(double?)) }
return (T)(object)value; else if (typeof(T) == typeof(sbyte?))
// string
else if (typeof(T) == typeof(string))
return (T)(object)value.ToString(Culture);
// sbyte
else if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(sbyte?))
return (T)(object)System.Convert.ToSByte(value);
// byte
else if (typeof(T) == typeof(byte) || typeof(T) == typeof(byte?))
return (T)(object)System.Convert.ToByte(value);
// short
else if (typeof(T) == typeof(short) || typeof(T) == typeof(short?))
return (T)(object)System.Convert.ToInt16(value);
// ushort
else if (typeof(T) == typeof(ushort) || typeof(T) == typeof(ushort?))
return (T)(object)System.Convert.ToUInt16(value);
// int
else if (typeof(T) == typeof(int) || typeof(T) == typeof(int?))
return (T)(object)System.Convert.ToInt32(value);
// uint
else if (typeof(T) == typeof(uint) || typeof(T) == typeof(uint?))
return (T)(object)System.Convert.ToUInt32(value);
// long
else if (typeof(T) == typeof(long) || typeof(T) == typeof(long?))
return (T)(object)System.Convert.ToInt64(value);
// ulong
else if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?))
return (T)(object)System.Convert.ToUInt64(value);
// float
else if (typeof(T) == typeof(float) || typeof(T) == typeof(float?))
return (T)(object)System.Convert.ToSingle(value);
// decimal
else if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?))
return (T)(object)System.Convert.ToDecimal(value);
else
{ {
TriggerError($"Conversion to type {typeof(T)} not implemented"); return System.Convert.ToDouble(((sbyte?)(object)value).Value);
} }
/*
* Byte
*/
else if (typeof(T) == typeof(byte))
{
return System.Convert.ToDouble((byte)(object)value);
} }
catch (Exception e) else if (typeof(T) == typeof(byte?))
{ {
TriggerError("Conversion error: " + e.Message); return System.Convert.ToDouble(((byte?)(object)value).Value);
return default(T);
} }
return default(T); /*
* Short
*/
else if (typeof(T) == typeof(short))
{
return System.Convert.ToDouble((short)(object)value);
} }
else if (typeof(T) == typeof(short?))
private double OnSet(T arg)
{ {
if (arg == null) return System.Convert.ToDouble(((short?)(object)value).Value);
return double.NaN; // <-- this catches all nullable values which are null. no nullchecks necessary below! }
try /*
* Ushort
*/
else if (typeof(T) == typeof(ushort))
{ {
// double return System.Convert.ToDouble((ushort)(object)value);
if (typeof(T) == typeof(double)) }
return (double)(object)arg; else if (typeof(T) == typeof(ushort?))
else if (typeof(T) == typeof(double?)) {
return ((double?)(object)arg).Value; return System.Convert.ToDouble(((ushort?)(object)value).Value);
// string }
if (typeof(T) == typeof(string)) /*
return double.Parse((string)(object)arg, NumberStyles.Any, Culture); * Int
// sbyte */
if (typeof(T) == typeof(sbyte))
return System.Convert.ToDouble((sbyte)(object)arg);
if (typeof(T) == typeof(sbyte?))
return System.Convert.ToDouble(((sbyte?)(object)arg).Value);
// byte
if (typeof(T) == typeof(byte))
return System.Convert.ToDouble((byte)(object)arg);
if (typeof(T) == typeof(byte?))
return System.Convert.ToDouble(((byte?)(object)arg).Value);
// short
if (typeof(T) == typeof(short))
return System.Convert.ToDouble((short)(object)arg);
if (typeof(T) == typeof(short?))
return System.Convert.ToDouble(((short?)(object)arg).Value);
// ushort
if (typeof(T) == typeof(ushort))
return System.Convert.ToDouble((ushort)(object)arg);
if (typeof(T) == typeof(ushort?))
return System.Convert.ToDouble(((ushort?)(object)arg).Value);
// int
else if (typeof(T) == typeof(int)) else if (typeof(T) == typeof(int))
return System.Convert.ToDouble((int)(object)arg); {
return System.Convert.ToDouble((int)(object)value);
}
else if (typeof(T) == typeof(int?)) else if (typeof(T) == typeof(int?))
return System.Convert.ToDouble(((int?)(object)arg).Value); {
// uint return System.Convert.ToDouble(((int?)(object)value).Value);
}
/*
* Uint
*/
else if (typeof(T) == typeof(uint)) else if (typeof(T) == typeof(uint))
return System.Convert.ToDouble((uint)(object)arg); {
return System.Convert.ToDouble((uint)(object)value);
}
else if (typeof(T) == typeof(uint?)) else if (typeof(T) == typeof(uint?))
return System.Convert.ToDouble(((uint?)(object)arg).Value); {
// long return System.Convert.ToDouble(((uint?)(object)value).Value);
}
/*
* Long
*/
else if (typeof(T) == typeof(long)) else if (typeof(T) == typeof(long))
return System.Convert.ToDouble((long)(object)arg); {
return System.Convert.ToDouble((long)(object)value);
}
else if (typeof(T) == typeof(long?)) else if (typeof(T) == typeof(long?))
return System.Convert.ToDouble(((long?)(object)arg).Value); {
// ulong return System.Convert.ToDouble(((long?)(object)value).Value);
}
/*
* Ulong
*/
else if (typeof(T) == typeof(ulong)) else if (typeof(T) == typeof(ulong))
return System.Convert.ToDouble((ulong)(object)arg); {
return System.Convert.ToDouble((ulong)(object)value);
}
else if (typeof(T) == typeof(ulong?)) else if (typeof(T) == typeof(ulong?))
return System.Convert.ToDouble(((ulong?)(object)arg).Value); {
// float return System.Convert.ToDouble(((ulong?)(object)value).Value);
}
/*
* Float
*/
else if (typeof(T) == typeof(float)) else if (typeof(T) == typeof(float))
return System.Convert.ToDouble((float)(object)arg); {
return System.Convert.ToDouble((float)(object)value);
}
else if (typeof(T) == typeof(float?)) else if (typeof(T) == typeof(float?))
return System.Convert.ToDouble(((float?)(object)arg).Value); {
// decimal return System.Convert.ToDouble(((float?)(object)value).Value);
}
/*
* Deimal
*/
else if (typeof(T) == typeof(decimal)) else if (typeof(T) == typeof(decimal))
return System.Convert.ToDouble((decimal)(object)arg); {
return System.Convert.ToDouble((decimal)(object)value);
}
else if (typeof(T) == typeof(decimal?)) else if (typeof(T) == typeof(decimal?))
return System.Convert.ToDouble(((decimal?)(object)arg).Value); {
return System.Convert.ToDouble(((decimal?)(object)value).Value);
}
else else
{ {
TriggerError("Unable to convert to double from type " + typeof(T).Name); TriggerError("Unable to convert to double from type " + typeof(T).Name);
return double.NaN; return double.NaN;
} }
} }
catch (FormatException e)
protected override T? ConvertValueBack(double value)
{ {
TriggerError("Conversion error: " + e.Message); /*
return double.NaN; * Double
*/
if (typeof(T) == typeof(double) || typeof(T) == typeof(double?))
{
return (T)(object)value;
}
/*
* String
*/
else if (typeof(T) == typeof(string))
{
return (T)(object)value.ToString(Culture);
}
/*
* Sbyte
*/
else if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(sbyte?))
{
return (T)(object)System.Convert.ToSByte(value);
}
/*
* Byte
*/
else if (typeof(T) == typeof(byte) || typeof(T) == typeof(byte?))
{
return (T)(object)System.Convert.ToByte(value);
}
/*
* Short
*/
else if (typeof(T) == typeof(short) || typeof(T) == typeof(short?))
{
return (T)(object)System.Convert.ToInt16(value);
}
/*
* Ushort
*/
else if (typeof(T) == typeof(ushort) || typeof(T) == typeof(ushort?))
{
return (T)(object)System.Convert.ToUInt16(value);
}
/*
* Int
*/
else if (typeof(T) == typeof(int) || typeof(T) == typeof(int?))
{
return (T)(object)System.Convert.ToInt32(value);
}
/*
* Uint
*/
else if (typeof(T) == typeof(uint) || typeof(T) == typeof(uint?))
{
return (T)(object)System.Convert.ToUInt32(value);
}
/*
* Long
*/
else if (typeof(T) == typeof(long) || typeof(T) == typeof(long?))
{
return (T)(object)System.Convert.ToInt64(value);
}
/*
* Ulong
*/
else if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?))
{
return (T)(object)System.Convert.ToUInt64(value);
}
/*
* Float
*/
else if (typeof(T) == typeof(float) || typeof(T) == typeof(float?))
{
return (T)(object)System.Convert.ToSingle(value);
}
/*
* Decimal
*/
else if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?))
{
return (T)(object)System.Convert.ToDecimal(value);
}
else
{
TriggerError($"Conversion to type {typeof(T)} not implemented");
return default;
} }
} }
#region Floating Point comparison
#region --> Floating Point comparison private const double MinNormal = 2.2250738585072014E-308d;
const double MinNormal = 2.2250738585072014E-308d;
public static bool AreEqual(double a, double b, double epsilon = MinNormal) public static bool AreEqual(double a, double b, double epsilon = MinNormal)
{ {
// Copyright (c) Michael Borgwardt /*
* Copyright (c) Michael Borgwardt
*/
var absA = Math.Abs(a); var absA = Math.Abs(a);
var absB = Math.Abs(b); var absB = Math.Abs(b);
var diff = Math.Abs(a - b); var diff = Math.Abs(a - b);
/*
* Shortcut, handles infinities
*/
if (a.Equals(b)) if (a.Equals(b))
{ // shortcut, handles infinities {
return true; return true;
} }
else if (a == 0 || b == 0 || absA + absB < MinNormal) else if (a == 0 || b == 0 || absA + absB < MinNormal)
{ {
// a or b is zero or both are extremely close to it /*
// relative error is less meaningful here * a or b is zero or both are extremely close to it
return diff < (epsilon * MinNormal); * relative error is less meaningful here
*/
return diff < epsilon * MinNormal;
} }
else else
{ // use relative error {
/*
* Use relative error
*/
return diff / (absA + absB) < epsilon; return diff / (absA + absB) < epsilon;
} }
} }
#endregion #endregion
} }
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
internal static class Num internal static class Number
{ {
public static T To<T>(double d) public static T? To<T>(double d)
{ {
if (typeof(T) == typeof(sbyte) && d >= sbyte.MinValue && sbyte.MaxValue >= d) if (typeof(T) == typeof(sbyte) && d >= sbyte.MinValue && sbyte.MaxValue >= d)
return (T)(object)Convert.ToSByte(d); return (T)(object)Convert.ToSByte(d);
@ -239,6 +335,7 @@ namespace Connected
return (T)(object)Convert.ToDouble(d); return (T)(object)Convert.ToDouble(d);
if (typeof(T) == typeof(decimal?) && (decimal)d >= decimal.MinValue && decimal.MaxValue >= (decimal)d) if (typeof(T) == typeof(decimal?) && (decimal)d >= decimal.MinValue && decimal.MaxValue >= (decimal)d)
return (T)(object)Convert.ToDecimal(d); return (T)(object)Convert.ToDecimal(d);
return default; return default;
} }
public static double From<T>(T v) public static double From<T>(T v)
@ -287,7 +384,7 @@ namespace Connected
return Convert.ToDouble((double?)(object)v); return Convert.ToDouble((double?)(object)v);
if (typeof(T) == typeof(decimal?)) if (typeof(T) == typeof(decimal?))
return Convert.ToDouble((decimal?)(object)v); return Convert.ToDouble((decimal?)(object)v);
return default; return default;
} }
}
} }

@ -1,65 +1,37 @@
using Connected.Components; using Connected.Components;
namespace Connected; namespace Connected.Utilities.BindingConverters;
public class RangeConverter<T> : ToStringConverter<Range<T>> public class RangeConverter<T> : ToStringConverter<Range<T>>
{ {
readonly DefaultConverter<T> _converter; private readonly DefaultConverter<T> _converter;
public RangeConverter() public RangeConverter() => _converter = new DefaultConverter<T>();
{
_converter = new DefaultConverter<T>();
}
protected override string? ConvertValue(Range<T>? value) protected override string? ConvertValue(Range<T>? value) => value is not null
{ ? Join(_converter.Convert(value.Start), _converter.Convert(value.End))
return OnSet(value); : string.Empty;
}
protected override Range<T>? ConvertValueBack(string? value) protected override Range<T>? ConvertValueBack(string? value)
{ {
return OnGet(value); return !Split(value, out var valueStart, out var valueEnd)
? null
: new Range<T>(_converter.ConvertBack(valueStart), _converter.ConvertBack(valueEnd));
} }
private Range<T> OnGet(string value) public static string Join(string? valueStart, string? valueEnd) => string.IsNullOrEmpty(valueStart) && string.IsNullOrEmpty(valueEnd) ? string.Empty : $"[{valueStart};{valueEnd}]";
{
if (!Split(value, out var valueStart, out var valueEnd))
return null;
return new Range<T>(_converter.ConvertBack(valueStart), _converter.ConvertBack(valueEnd));
}
private string OnSet(Range<T> arg) public static bool Split(string? value, out string? valueStart, out string? valueEnd)
{
if (arg == null)
return string.Empty;
return Join(_converter.Convert(arg.Start), _converter.Convert(arg.End));
}
public static string Join(string valueStart, string valueEnd)
{
if (string.IsNullOrEmpty(valueStart) && string.IsNullOrEmpty(valueEnd))
return string.Empty;
return $"[{valueStart};{valueEnd}]";
}
public static bool Split(string value, out string valueStart, out string valueEnd)
{ {
valueStart = valueEnd = string.Empty; valueStart = valueEnd = string.Empty;
if (string.IsNullOrEmpty(value) || value[0] != '[' || value[^1] != ']') if (string.IsNullOrEmpty(value) || value[0] != '[' || value[^1] != ']')
{
return false; return false;
}
var idx = value.IndexOf(';'); var idx = value.IndexOf(';');
if (idx < 1) if (idx < 1)
{
return false; return false;
}
valueStart = value[1..idx]; valueStart = value[1..idx];
valueEnd = value[(idx + 1)..^1]; valueEnd = value[(idx + 1)..^1];

Loading…
Cancel
Save