/* Copyright (C) 2014-2019 de4dot@gmail.com This file is part of dnSpy dnSpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. dnSpy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with dnSpy. If not, see . */ using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using dnSpy.Contracts.DnSpy.Properties; using dnSpy.Contracts.Utilities; namespace dnSpy.Contracts.MVVM { struct CachedValidationError { readonly Func checkError; bool errorMsgValid; string? errorMsg; public bool HasError { get { CheckError(); return !string.IsNullOrEmpty(errorMsg); } } public string? ErrorMessage { get { CheckError(); return errorMsg; } } public CachedValidationError(Func checkError) { this.checkError = checkError ?? throw new ArgumentNullException(nameof(checkError)); errorMsgValid = false; errorMsg = null; } public void Invalidate() => errorMsgValid = false; void CheckError() { if (errorMsgValid) return; errorMsg = checkError(); errorMsgValid = true; } } /// /// Data field base class /// public abstract class DataFieldVM : ViewModelBase { readonly Action onUpdated; CachedValidationError cachedError; /// /// Gets/sets the value /// public abstract object? ObjectValue { get; set; } /// /// Gets the string representation of the value. This could be an invalid string. Use /// to check whether it's valid. /// public string StringValue { get => stringValue; set { if (value is null) throw new ArgumentNullException(nameof(value)); if (stringValue != value) ForceWriteStringValue(value); } } string stringValue = string.Empty; /// /// Must only be called from the constructor /// /// Initial protected void WriteStringValueFromConstructor(string value) { Debug.Assert(stringValue == string.Empty); stringValue = value; } /// /// Force writing a new even if nothing changed /// /// New value protected void ForceWriteStringValue(string value) { stringValue = value; cachedError.Invalidate(); OnStringValueChanged(); OnPropertyChanged(nameof(StringValue)); onUpdated(this); } /// /// Revalidates the field for errors /// protected void Revalidate() { cachedError.Invalidate(); HasErrorUpdated(); } /// /// Called when gets updated /// protected virtual void OnStringValueChanged() { } /// /// true if the value is null ( is empty) /// public bool IsNull => string.IsNullOrWhiteSpace(StringValue); /// /// Constructor /// /// Called when value gets updated protected DataFieldVM(Action onUpdated) { this.onUpdated = onUpdated ?? throw new ArgumentNullException(nameof(onUpdated)); cachedError = new CachedValidationError(() => Validate()); } /// /// Validates the data. Returns null or an empty string if there was no error, /// or an error string that can be shown to the user. /// /// protected abstract string? Validate(); /// /// Converts the string to the target value. Returns null or an empty string if /// there were no errors, else an error string that can be shown to the user. /// /// Result /// public abstract string? ConvertToObjectValue(out object value); /// /// Checks the string for errors /// /// Property name /// protected override string? Verify(string columnName) { if (columnName == nameof(StringValue)) return cachedError.ErrorMessage; return string.Empty; } /// /// true if there's at least one error /// public override bool HasError => cachedError.HasError; } /// /// Data field base class /// /// Type of data public abstract class DataFieldVM : DataFieldVM { /// /// Gets/sets the value /// public override object? ObjectValue { get => Value!; set => Value = (T)value!; } /// /// Gets/sets the value /// public T Value { get { var s = ConvertToValue(out var value); if (string.IsNullOrEmpty(s)) return value; throw new FormatException(s); } set => SetValue(value); } /// /// Constructor /// /// Called when value gets updated protected DataFieldVM(Action onUpdated) : base(onUpdated) { } /// /// Must only be called from the constructor /// /// Initial value protected void SetValueFromConstructor(T value) => WriteStringValueFromConstructor(OnNewValue(value)); /// /// Writes a new value /// /// Value protected void SetValue(T value) => StringValue = OnNewValue(value); /// /// Converts to a string /// /// New value /// protected abstract string OnNewValue(T value); /// /// Converts the current string to the real value. Returns null or an empty string if /// there were no errors, else an error string that can be shown to the user. /// /// Result /// protected abstract string? ConvertToValue(out T value); /// /// Converts the string to the target value. Returns null or an empty string if /// there were no errors, else an error string that can be shown to the user. /// /// Result /// public override string? ConvertToObjectValue(out object value) { var error = ConvertToValue(out var v); Debug2.Assert(v is not null); value = v; return error; } /// /// Validates the data. Returns null or an empty string if there was no error, /// or an error string that can be shown to the user. /// /// protected override string? Validate() { try { return ConvertToValue(out var value); } catch (Exception ex) { Debug.Fail("Exception caught in Validate(). ConvertToValue() should return an error string instead of throwing for performance reasons! Throwing is SLOOOOW!"); if (!string.IsNullOrEmpty(ex.Message)) return ex.Message; return string.Format(dnSpy_Contracts_DnSpy_Resources.CouldNotConvert, StringValue); } } } /// /// Number base class /// /// Real type /// If real type is a nullable type, this should be non-nullable type public abstract class NumberDataFieldVM : DataFieldVM { /// /// true to always use decimal, false to never use decimal (except if it's just one digit), /// and null to use decimal or hex depending on what number it is. /// public bool? UseDecimal { get => useDecimal; set { if (useDecimal != value) { useDecimal = value; if (!HasError) ForceWriteStringValue(OnNewValue(Value)); } } } bool? useDecimal; /// /// Gets/sets the minimum value /// public U Min { get => min; set { min = value; Revalidate(); } } U min; /// /// Gets/sets the maximum value /// public U Max { get => max; set { max = value; Revalidate(); } } U max; /// /// Constructor /// /// Called when value gets updated /// Minimum value /// Maximum value /// true to use decimal, false to use hex, or null if it depends on the value protected NumberDataFieldVM(Action onUpdated, U min, U max, bool? useDecimal) : base(onUpdated) { this.min = min; this.max = max; this.useDecimal = useDecimal; } } /// /// Nullable /// public class NullableGuidVM : DataFieldVM { /// /// Constructor /// /// Called when value gets updated public NullableGuidVM(Action onUpdated) : this(null, onUpdated) { } /// /// Constructor /// /// Initial value /// Called when value gets updated public NullableGuidVM(Guid? value, Action onUpdated) : base(onUpdated) => SetValueFromConstructor(value); /// protected override string OnNewValue(Guid? value) => value is null ? string.Empty : value.Value.ToString(); /// protected override string? ConvertToValue(out Guid? value) { string? error = null; if (IsNull) value = null; else value = ParseGuid(StringValue, out error); return error; } internal static Guid ParseGuid(string s, out string? error) { if (Guid.TryParse(s, out var res)) { error = null; return res; } error = dnSpy_Contracts_DnSpy_Resources.InvalidGuid; return Guid.Empty; } } /// /// Hex string /// public class HexStringVM : DataFieldVM> { /// /// Gets/sets whether to use upper case hex digits /// public bool UppercaseHex { get => uppercaseHex; set => uppercaseHex = value; } bool uppercaseHex = true; /// /// Constructor /// /// Called when value gets updated public HexStringVM(Action onUpdated) : this(null, onUpdated) { } /// /// Constructor /// /// Initial value /// Called when value gets updated public HexStringVM(IList? value, Action onUpdated) : base(onUpdated) => SetValueFromConstructor(value!);// can be null, but we can't use 'T?' /// protected override string OnNewValue(IList value) => SimpleTypeConverter.ByteArrayToString(value, UppercaseHex); /// protected override string? ConvertToValue(out IList value) { // It will be null only when there's an error, so force it with '!'. 'value' can't be nullable since it's overriding a generic method // with a generic parameter that can be a struct or a class. value = SimpleTypeConverter.ParseByteArray(StringValue, out var error)!; return error; } } /// /// Nullable /// public class NullableBooleanVM : DataFieldVM { /// /// Constructor /// /// Called when value gets updated public NullableBooleanVM(Action onUpdated) : this(false, onUpdated) { } /// /// Constructor /// /// Initial value /// Called when value gets updated public NullableBooleanVM(bool? value, Action onUpdated) : base(onUpdated) => SetValueFromConstructor(value); /// protected override string OnNewValue(bool? value) => value is null ? string.Empty : SimpleTypeConverter.ToString(value.Value); /// protected override string? ConvertToValue(out bool? value) { string? error = null; if (IsNull) value = null; else value = SimpleTypeConverter.ParseBoolean(StringValue, out error); return error; } } /// /// Nullable /// public class NullableSByteVM : NumberDataFieldVM { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public NullableSByteVM(Action onUpdated, bool? useDecimal = null) : this(null, onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public NullableSByteVM(sbyte? value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, sbyte.MinValue, sbyte.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(sbyte? value) => value is null ? string.Empty : SimpleTypeConverter.ToString(value.Value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out sbyte? value) { string? error = null; if (IsNull) value = null; else value = SimpleTypeConverter.ParseSByte(StringValue, Min, Max, out error); return error; } } /// /// Nullable /// public class NullableByteVM : NumberDataFieldVM { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public NullableByteVM(Action onUpdated, bool? useDecimal = null) : this(null, onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public NullableByteVM(byte? value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, byte.MinValue, byte.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(byte? value) => value is null ? string.Empty : SimpleTypeConverter.ToString(value.Value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out byte? value) { string? error = null; if (IsNull) value = null; else value = SimpleTypeConverter.ParseByte(StringValue, Min, Max, out error); return error; } } /// /// Nullable /// public class NullableInt16VM : NumberDataFieldVM { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public NullableInt16VM(Action onUpdated, bool? useDecimal = null) : this(null, onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public NullableInt16VM(short? value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, short.MinValue, short.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(short? value) => value is null ? string.Empty : SimpleTypeConverter.ToString(value.Value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out short? value) { string? error = null; if (IsNull) value = null; else value = SimpleTypeConverter.ParseInt16(StringValue, Min, Max, out error); return error; } } /// /// Nullable /// public class NullableUInt16VM : NumberDataFieldVM { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public NullableUInt16VM(Action onUpdated, bool? useDecimal = null) : this(null, onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public NullableUInt16VM(ushort? value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, ushort.MinValue, ushort.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(ushort? value) => value is null ? string.Empty : SimpleTypeConverter.ToString(value.Value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out ushort? value) { string? error = null; if (IsNull) value = null; else value = SimpleTypeConverter.ParseUInt16(StringValue, Min, Max, out error); return error; } } /// /// Nullable /// public class NullableInt32VM : NumberDataFieldVM { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public NullableInt32VM(Action onUpdated, bool? useDecimal = null) : this(null, onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public NullableInt32VM(int? value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, int.MinValue, int.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(int? value) => value is null ? string.Empty : SimpleTypeConverter.ToString(value.Value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out int? value) { string? error = null; if (IsNull) value = null; else value = SimpleTypeConverter.ParseInt32(StringValue, Min, Max, out error); return error; } } /// /// Nullable /// public class NullableUInt32VM : NumberDataFieldVM { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public NullableUInt32VM(Action onUpdated, bool? useDecimal = null) : this(null, onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public NullableUInt32VM(uint? value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, uint.MinValue, uint.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(uint? value) => value is null ? string.Empty : SimpleTypeConverter.ToString(value.Value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out uint? value) { string? error = null; if (IsNull) value = null; else value = SimpleTypeConverter.ParseUInt32(StringValue, Min, Max, out error); return error; } } /// /// Nullable /// public class NullableInt64VM : NumberDataFieldVM { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public NullableInt64VM(Action onUpdated, bool? useDecimal = null) : this(null, onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public NullableInt64VM(long? value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, long.MinValue, long.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(long? value) => value is null ? string.Empty : SimpleTypeConverter.ToString(value.Value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out long? value) { string? error = null; if (IsNull) value = null; else value = SimpleTypeConverter.ParseInt64(StringValue, Min, Max, out error); return error; } } /// /// Nullable /// public class NullableUInt64VM : NumberDataFieldVM { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public NullableUInt64VM(Action onUpdated, bool? useDecimal = null) : this(null, onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public NullableUInt64VM(ulong? value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, ulong.MinValue, ulong.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(ulong? value) => value is null ? string.Empty : SimpleTypeConverter.ToString(value.Value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out ulong? value) { string? error = null; if (IsNull) value = null; else value = SimpleTypeConverter.ParseUInt64(StringValue, Min, Max, out error); return error; } } /// /// /// public class BooleanVM : DataFieldVM { /// /// Constructor /// /// Called when value gets updated public BooleanVM(Action onUpdated) : this(false, onUpdated) { } /// /// Constructor /// /// Initial value /// Called when value gets updated public BooleanVM(bool value, Action onUpdated) : base(onUpdated) => SetValueFromConstructor(value); /// protected override string OnNewValue(bool value) => SimpleTypeConverter.ToString(value); /// protected override string? ConvertToValue(out bool value) { value = SimpleTypeConverter.ParseBoolean(StringValue, out var error); return error; } } /// /// /// public class CharVM : DataFieldVM { /// /// Constructor /// /// Called when value gets updated public CharVM(Action onUpdated) : this((char)0, onUpdated) { } /// /// Constructor /// /// Initial value /// Called when value gets updated public CharVM(char value, Action onUpdated) : base(onUpdated) => SetValueFromConstructor(value); /// protected override string OnNewValue(char value) => SimpleTypeConverter.ToString(value); /// protected override string? ConvertToValue(out char value) { value = SimpleTypeConverter.ParseChar(StringValue, out var error); return error; } } /// /// /// public class ByteVM : NumberDataFieldVM { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public ByteVM(Action onUpdated, bool? useDecimal = null) : this(0, onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public ByteVM(byte value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, byte.MinValue, byte.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(byte value) => SimpleTypeConverter.ToString(value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out byte value) { value = SimpleTypeConverter.ParseByte(StringValue, Min, Max, out var error); return error; } } /// /// /// public class UInt16VM : NumberDataFieldVM { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public UInt16VM(Action onUpdated, bool? useDecimal = null) : this(0, onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public UInt16VM(ushort value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, ushort.MinValue, ushort.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(ushort value) => SimpleTypeConverter.ToString(value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out ushort value) { value = SimpleTypeConverter.ParseUInt16(StringValue, Min, Max, out var error); return error; } } /// /// /// public class UInt32VM : NumberDataFieldVM { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public UInt32VM(Action onUpdated, bool? useDecimal = null) : this(0, onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public UInt32VM(uint value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, uint.MinValue, uint.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(uint value) => SimpleTypeConverter.ToString(value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out uint value) { value = SimpleTypeConverter.ParseUInt32(StringValue, Min, Max, out var error); return error; } } /// /// /// public class UInt64VM : NumberDataFieldVM { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public UInt64VM(Action onUpdated, bool? useDecimal = null) : this(0, onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public UInt64VM(ulong value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, ulong.MinValue, ulong.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(ulong value) => SimpleTypeConverter.ToString(value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out ulong value) { value = SimpleTypeConverter.ParseUInt64(StringValue, Min, Max, out var error); return error; } } /// /// /// public class SByteVM : NumberDataFieldVM { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public SByteVM(Action onUpdated, bool? useDecimal = null) : this(0, onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public SByteVM(sbyte value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, sbyte.MinValue, sbyte.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(sbyte value) => SimpleTypeConverter.ToString(value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out sbyte value) { value = SimpleTypeConverter.ParseSByte(StringValue, Min, Max, out var error); return error; } } /// /// /// public class Int16VM : NumberDataFieldVM { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public Int16VM(Action onUpdated, bool? useDecimal = null) : this(0, onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public Int16VM(short value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, short.MinValue, short.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(short value) => SimpleTypeConverter.ToString(value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out short value) { value = SimpleTypeConverter.ParseInt16(StringValue, Min, Max, out var error); return error; } } /// /// /// public class Int32VM : NumberDataFieldVM { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public Int32VM(Action onUpdated, bool? useDecimal = null) : this(0, onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public Int32VM(int value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, int.MinValue, int.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(int value) => SimpleTypeConverter.ToString(value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out int value) { value = SimpleTypeConverter.ParseInt32(StringValue, Min, Max, out var error); return error; } } /// /// /// public class Int64VM : NumberDataFieldVM { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public Int64VM(Action onUpdated, bool? useDecimal = null) : this(0, onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public Int64VM(long value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, long.MinValue, long.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(long value) => SimpleTypeConverter.ToString(value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out long value) { value = SimpleTypeConverter.ParseInt64(StringValue, Min, Max, out var error); return error; } } /// /// /// public class SingleVM : DataFieldVM { /// /// Constructor /// /// Called when value gets updated public SingleVM(Action onUpdated) : this(0, onUpdated) { } /// /// Constructor /// /// Initial value /// Called when value gets updated public SingleVM(float value, Action onUpdated) : base(onUpdated) => SetValueFromConstructor(value); /// protected override string OnNewValue(float value) => SimpleTypeConverter.ToString(value); /// protected override string? ConvertToValue(out float value) { value = SimpleTypeConverter.ParseSingle(StringValue, out var error); return error; } } /// /// /// public class DoubleVM : DataFieldVM { /// /// Constructor /// /// Called when value gets updated public DoubleVM(Action onUpdated) : this(0, onUpdated) { } /// /// Constructor /// /// Initial value /// Called when value gets updated public DoubleVM(double value, Action onUpdated) : base(onUpdated) => SetValueFromConstructor(value); /// protected override string OnNewValue(double value) => SimpleTypeConverter.ToString(value); /// protected override string? ConvertToValue(out double value) { value = SimpleTypeConverter.ParseDouble(StringValue, out var error); return error; } } /// /// /// public class StringVM : DataFieldVM { readonly bool allowNullString; /// /// Constructor /// /// Called when value gets updated /// true to allow null strings public StringVM(Action onUpdated, bool allowNullString = false) : this(string.Empty, onUpdated, allowNullString) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to allow null strings public StringVM(string value, Action onUpdated, bool allowNullString = false) : base(onUpdated) { this.allowNullString = allowNullString; SetValueFromConstructor(value); } /// protected override string OnNewValue(string value) => SimpleTypeConverter.ToString(value, allowNullString); /// protected override string? ConvertToValue(out string value) { value = SimpleTypeConverter.ParseString(StringValue, allowNullString, out var error)!; return error; } } /// /// /// public class DecimalVM : DataFieldVM { /// /// Constructor /// /// Called when value gets updated public DecimalVM(Action onUpdated) : this(0, onUpdated) { } /// /// Constructor /// /// Initial value /// Called when value gets updated public DecimalVM(decimal value, Action onUpdated) : base(onUpdated) => SetValueFromConstructor(value); /// protected override string OnNewValue(decimal value) => SimpleTypeConverter.ToString(value); /// protected override string? ConvertToValue(out decimal value) { value = SimpleTypeConverter.ParseDecimal(StringValue, out var error); return error; } } /// /// /// public class DateTimeVM : DataFieldVM { /// /// Constructor /// /// Called when value gets updated public DateTimeVM(Action onUpdated) : this(DateTime.Now, onUpdated) { } /// /// Constructor /// /// Initial value /// Called when value gets updated public DateTimeVM(DateTime value, Action onUpdated) : base(onUpdated) => SetValueFromConstructor(value); /// protected override string OnNewValue(DateTime value) => SimpleTypeConverter.ToString(value); /// protected override string? ConvertToValue(out DateTime value) { value = SimpleTypeConverter.ParseDateTime(StringValue, out var error); return error; } } /// /// /// public class TimeSpanVM : DataFieldVM { /// /// Constructor /// /// Called when value gets updated public TimeSpanVM(Action onUpdated) : this(TimeSpan.Zero, onUpdated) { } /// /// Constructor /// /// Initial value /// Called when value gets updated public TimeSpanVM(TimeSpan value, Action onUpdated) : base(onUpdated) => SetValueFromConstructor(value); /// protected override string OnNewValue(TimeSpan value) => SimpleTypeConverter.ToString(value); /// protected override string? ConvertToValue(out TimeSpan value) { value = SimpleTypeConverter.ParseTimeSpan(StringValue, out var error); return error; } } /// /// /// public class GuidVM : DataFieldVM { /// /// Constructor /// /// Called when value gets updated public GuidVM(Action onUpdated) : this(new Guid(), onUpdated) { } /// /// Constructor /// /// Initial value /// Called when value gets updated public GuidVM(Guid value, Action onUpdated) : base(onUpdated) => SetValueFromConstructor(value); /// protected override string OnNewValue(Guid value) => value.ToString(); /// protected override string? ConvertToValue(out Guid value) { value = NullableGuidVM.ParseGuid(StringValue, out var error); return error; } } /// /// List of s /// public class BooleanListDataFieldVM : DataFieldVM> { /// /// Constructor /// /// Called when value gets updated public BooleanListDataFieldVM(Action onUpdated) : this(Array.Empty(), onUpdated) { } /// /// Constructor /// /// Initial value /// Called when value gets updated public BooleanListDataFieldVM(IList value, Action onUpdated) : base(onUpdated) => SetValueFromConstructor(value); /// protected override string OnNewValue(IList value) => SimpleTypeConverter.ToString(value); /// protected override string? ConvertToValue(out IList value) { value = SimpleTypeConverter.ParseBooleanList(StringValue, out var error)!; return error; } } /// /// List of s /// public class CharListDataFieldVM : DataFieldVM> { /// /// Constructor /// /// Called when value gets updated public CharListDataFieldVM(Action onUpdated) : this(Array.Empty(), onUpdated) { } /// /// Constructor /// /// Initial value /// Called when value gets updated public CharListDataFieldVM(IList value, Action onUpdated) : base(onUpdated) => SetValueFromConstructor(value); /// protected override string OnNewValue(IList value) => SimpleTypeConverter.ToString(value); /// protected override string? ConvertToValue(out IList value) { value = SimpleTypeConverter.ParseCharList(StringValue, out var error)!; return error; } } /// /// List of s /// public class ByteListDataFieldVM : NumberDataFieldVM, byte> { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public ByteListDataFieldVM(Action onUpdated, bool? useDecimal = null) : this(Array.Empty(), onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public ByteListDataFieldVM(IList value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, byte.MinValue, byte.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(IList value) => SimpleTypeConverter.ToString(value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out IList value) { value = SimpleTypeConverter.ParseByteList(StringValue, Min, Max, out var error)!; return error; } } /// /// List of s /// public class UInt16ListDataFieldVM : NumberDataFieldVM, ushort> { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public UInt16ListDataFieldVM(Action onUpdated, bool? useDecimal = null) : this(Array.Empty(), onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public UInt16ListDataFieldVM(IList value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, ushort.MinValue, ushort.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(IList value) => SimpleTypeConverter.ToString(value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out IList value) { value = SimpleTypeConverter.ParseUInt16List(StringValue, Min, Max, out var error)!; return error; } } /// /// List of s /// public class UInt32ListDataFieldVM : NumberDataFieldVM, uint> { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public UInt32ListDataFieldVM(Action onUpdated, bool? useDecimal = null) : this(Array.Empty(), onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public UInt32ListDataFieldVM(IList value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, uint.MinValue, uint.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(IList value) => SimpleTypeConverter.ToString(value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out IList value) { value = SimpleTypeConverter.ParseUInt32List(StringValue, Min, Max, out var error)!; return error; } } /// /// List of s /// public class UInt64ListDataFieldVM : NumberDataFieldVM, ulong> { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public UInt64ListDataFieldVM(Action onUpdated, bool? useDecimal = null) : this(Array.Empty(), onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public UInt64ListDataFieldVM(IList value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, ulong.MinValue, ulong.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(IList value) => SimpleTypeConverter.ToString(value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out IList value) { value = SimpleTypeConverter.ParseUInt64List(StringValue, Min, Max, out var error)!; return error; } } /// /// List of s /// public class SByteListDataFieldVM : NumberDataFieldVM, sbyte> { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public SByteListDataFieldVM(Action onUpdated, bool? useDecimal = null) : this(Array.Empty(), onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public SByteListDataFieldVM(IList value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, sbyte.MinValue, sbyte.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(IList value) => SimpleTypeConverter.ToString(value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out IList value) { value = SimpleTypeConverter.ParseSByteList(StringValue, Min, Max, out var error)!; return error; } } /// /// List of s /// public class Int16ListDataFieldVM : NumberDataFieldVM, short> { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public Int16ListDataFieldVM(Action onUpdated, bool? useDecimal = null) : this(Array.Empty(), onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public Int16ListDataFieldVM(IList value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, short.MinValue, short.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(IList value) => SimpleTypeConverter.ToString(value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out IList value) { value = SimpleTypeConverter.ParseInt16List(StringValue, Min, Max, out var error)!; return error; } } /// /// List of s /// public class Int32ListDataFieldVM : NumberDataFieldVM, int> { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public Int32ListDataFieldVM(Action onUpdated, bool? useDecimal = null) : this(Array.Empty(), onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public Int32ListDataFieldVM(IList value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, int.MinValue, int.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(IList value) => SimpleTypeConverter.ToString(value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out IList value) { value = SimpleTypeConverter.ParseInt32List(StringValue, Min, Max, out var error)!; return error; } } /// /// List of s /// public class Int64ListDataFieldVM : NumberDataFieldVM, long> { /// /// Constructor /// /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public Int64ListDataFieldVM(Action onUpdated, bool? useDecimal = null) : this(Array.Empty(), onUpdated, useDecimal) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to use decimal, false to use hex, or null if it depends on the value public Int64ListDataFieldVM(IList value, Action onUpdated, bool? useDecimal = null) : base(onUpdated, long.MinValue, long.MaxValue, useDecimal) => SetValueFromConstructor(value); /// protected override string OnNewValue(IList value) => SimpleTypeConverter.ToString(value, Min, Max, UseDecimal); /// protected override string? ConvertToValue(out IList value) { value = SimpleTypeConverter.ParseInt64List(StringValue, Min, Max, out var error)!; return error; } } /// /// List of s /// public class SingleListDataFieldVM : DataFieldVM> { /// /// Constructor /// /// Called when value gets updated public SingleListDataFieldVM(Action onUpdated) : this(Array.Empty(), onUpdated) { } /// /// Constructor /// /// Initial value /// Called when value gets updated public SingleListDataFieldVM(IList value, Action onUpdated) : base(onUpdated) => SetValueFromConstructor(value); /// protected override string OnNewValue(IList value) => SimpleTypeConverter.ToString(value); /// protected override string? ConvertToValue(out IList value) { value = SimpleTypeConverter.ParseSingleList(StringValue, out var error)!; return error; } } /// /// List of s /// public class DoubleListDataFieldVM : DataFieldVM> { /// /// Constructor /// /// Called when value gets updated public DoubleListDataFieldVM(Action onUpdated) : this(Array.Empty(), onUpdated) { } /// /// Constructor /// /// Initial value /// Called when value gets updated public DoubleListDataFieldVM(IList value, Action onUpdated) : base(onUpdated) => SetValueFromConstructor(value); /// protected override string OnNewValue(IList value) => SimpleTypeConverter.ToString(value); /// protected override string? ConvertToValue(out IList value) { value = SimpleTypeConverter.ParseDoubleList(StringValue, out var error)!; return error; } } /// /// List of s /// public class StringListDataFieldVM : DataFieldVM> { readonly bool allowNullString; /// /// Constructor /// /// Called when value gets updated /// true to allow null strings public StringListDataFieldVM(Action onUpdated, bool allowNullString = true) : this(Array.Empty(), onUpdated, allowNullString) { } /// /// Constructor /// /// Initial value /// Called when value gets updated /// true to allow null strings public StringListDataFieldVM(IList value, Action onUpdated, bool allowNullString = true) : base(onUpdated) { this.allowNullString = allowNullString; SetValueFromConstructor(value); } /// protected override string OnNewValue(IList value) => SimpleTypeConverter.ToString(value, allowNullString); /// protected override string? ConvertToValue(out IList value) { value = SimpleTypeConverter.ParseStringList(StringValue, allowNullString, out var error)!; return error; } } /// /// Uses the default converter to convert the type to/from a string /// /// Type public class DefaultConverterVM : DataFieldVM { static readonly TypeConverter converter = TypeDescriptor.GetConverter(typeof(T)); static DefaultConverterVM() { if (!converter.CanConvertTo(null, typeof(string))) throw new InvalidOperationException($"Converter can't convert a {typeof(T)} to a string"); if (!converter.CanConvertFrom(null, typeof(string))) throw new InvalidOperationException($"Converter can't convert a string to a {typeof(T)}"); } /// /// Constructor /// /// Called when value gets updated public DefaultConverterVM(Action onUpdated) : this(default!, onUpdated) { } /// /// Constructor /// /// Initial value /// Called when value gets updated public DefaultConverterVM(T value, Action onUpdated) : base(onUpdated) => SetValueFromConstructor(value); /// protected override string OnNewValue(T value) => (string)converter.ConvertTo(null, CultureInfo.InvariantCulture, value, typeof(string)); /// protected override string? ConvertToValue(out T value) { string error; try { value = (T)converter.ConvertFrom(null, CultureInfo.InvariantCulture, StringValue); error = string.Empty; } catch (Exception ex) { value = default!; error = string.Format(dnSpy_Contracts_DnSpy_Resources.ValueMustBeType, typeof(T).FullName, ex.Message); } return error; } } }