Продолжение рефакторинга (вынесение WPF в отдельный проект)

This commit is contained in:
2025-10-02 17:25:48 +04:00
parent 2fa94851cb
commit f5b311e025
161 changed files with 899 additions and 559 deletions
@@ -0,0 +1,22 @@
namespace Pf2e.WPF.EntityViewer.EntityViewStructure;
/// <summary>
/// Тип лестницы глубины.
/// </summary>
internal enum LadderType
{
/// <summary>
/// Пустой элемент: [ ].
/// </summary>
Empty,
/// <summary>
/// Последний элемент: [└].
/// </summary>
Last,
/// <summary>
/// Проходной элемент: [├] или [│].
/// </summary>
Through,
}
@@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Pf2e.WPF.EntityViewer.EntityViewStructure;
/// <summary>
/// Класс для построения структуры данных класса.
/// </summary>
internal class StructureBuilder
{
/// <summary>
/// Список обработанных объектов.
/// </summary>
private readonly List<StructureProcessedObject> _processedObjects = [];
/// <summary>
/// Создание поля структуры.
/// </summary>
/// <param name="name">Название поля.</param>
/// <param name="value">Значение поля.</param>
/// <param name="depth">Глубина, на котором поле отображается.</param>
/// <returns>Поле структуры.</returns>
public StructureFieldComplex Create(string name, object value, int depth = 0)
{
_ = RegisterProcessed(value);
StructureFieldComplex field = new(this, value, name, depth);
CompleteProcessed(value, field);
return field;
}
/// <summary>
/// Регистрация обработанного объекта.
/// </summary>
/// <param name="value">Регистрируемый объект.</param>
/// <returns>`true`, если объект был добавлен; `false`, если объект был добавлен ранее.</returns>
public bool RegisterProcessed(object value)
{
if (_processedObjects.Any(p => p.Value == value))
{
return false;
}
StructureProcessedObject processed = new(value);
_processedObjects.Add(processed);
return true;
}
/// <summary>
/// Дополнить обработанный объект.
/// </summary>
/// <param name="value">Значение.</param>
/// <param name="field">Поле объекта.</param>
public void CompleteProcessed(object value, StructureFieldComplex field)
{
StructureProcessedObject? q = _processedObjects.FirstOrDefault(p => p.Value == value);
if (q == null)
{
return;
}
q.Field = field;
}
/// <summary>
/// Выбор поля структуры по значению.
/// </summary>
/// <param name="name">Название поля.</param>
/// <param name="value">Значение поля.</param>
/// <param name="depth">Глубина, на котором поле отображается.</param>
/// <returns></returns>
public StructureField Resolve(string name, object? value, int depth)
{
switch (GetFieldType(value))
{
case StructureFieldType.Null:
return new StructureFieldNull(this, name, depth);
case StructureFieldType.Primitive:
return new StructureFieldPrimitive(this, value!, name, depth);
case StructureFieldType.Enum:
return new StructureFieldEnum(this, value!, name, depth);
case StructureFieldType.Reference:
break;
case StructureFieldType.Complex:
default:
StructureProcessedObject? processed = _processedObjects.FirstOrDefault(p =>
p.Value == value
);
if (processed is null)
{
return Create(name, value!, depth);
}
StructureFieldReference reference = new(this, name, null, depth);
processed.AddReference(reference);
return reference;
}
throw new("Impossible");
}
/// <summary>
/// Получить тип поля структуры по значению.
/// </summary>
/// <param name="value">Значение поля.</param>
/// <returns>Тип поля структуры.</returns>
public static StructureFieldType GetFieldType(object? value)
{
if (value is null)
{
return StructureFieldType.Null;
}
Type type = value.GetType();
if (type.IsEnum)
{
return StructureFieldType.Enum;
}
if (type.IsPrimitive || type == typeof(string))
{
return StructureFieldType.Primitive;
}
return StructureFieldType.Complex;
}
}
@@ -0,0 +1,248 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Pf2e.WPF.EntityViewer.EntityViewStructure;
/// <summary>
/// Базовый класс для полей структуры.
/// </summary>
/// <param name="builder">Класс построения структуры данных.</param>
/// <param name="name">Название поля.</param>
/// <param name="depth">Глубина поля.</param>
internal abstract class StructureField(StructureBuilder builder, string name, int depth)
: INotifyPropertyChanged
{
/// <summary>
/// Название поля.
/// </summary>
public string Name { get; } = name;
/// <summary>
/// Тип поля структуры.
/// </summary>
public abstract StructureFieldType FieldType { get; }
/// <summary>
/// Класс построения структуры данных.
/// </summary>
public StructureBuilder Builder { get; } = builder;
/// <summary>
/// Флаг, указывающий, отображается ли поле.
/// </summary>
public virtual bool Visible
{
get => _visible;
set
{
_visible = value;
OnPropertyChanged();
}
}
private bool _visible = true;
/// <summary>
/// Глубина поля структуры.
/// </summary>
public int Depth { get; } = depth;
/// <summary>
/// Получить строку, представляющую тип поля.
/// </summary>
/// <returns>Строка, представляющая тип поля.</returns>
public abstract string GetTypeString();
/// <summary>
/// Получить строку, представляющую значение поля.
/// </summary>
/// <returns>Строка, представляющая значение поля.</returns>
public abstract string GetValueString();
/// <summary>
/// Получить набор строк, используя результат функция.
/// </summary>
/// <typeparam name="T">Тип результирующей строки.</typeparam>
/// <param name="func">Функция, оборажения поля в результирующую строку.</param>
/// <returns>Коллекция, содержащая набор строк.</returns>
public virtual ICollection<T> GetRows<T>(Func<StructureField, T> func)
{
StructureField[] q = [this];
return q.Select(func).ToList();
}
/// <summary>
/// Событие изменения поля.
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// Запустить событие изменения поля.
/// </summary>
/// <param name="propertyName">Название поля.</param>
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new(propertyName));
}
}
/// <summary>
/// Поле структуры с перечислением.
/// </summary>
/// <param name="builder">Класс построения структуры данных.</param>
/// <param name="value">Значение поля.</param>
/// <param name="name">Название поля.</param>
/// <param name="depth">Глубина поля.</param>
internal class StructureFieldEnum(StructureBuilder builder, object value, string name, int depth)
: StructureField(builder, name, depth)
{
/// <inheritdoc/>
public override StructureFieldType FieldType => StructureFieldType.Enum;
/// <summary>
/// Значение поля.
/// </summary>
public readonly object Value = value;
/// <summary>
/// Тип значения поля.
/// </summary>
public readonly Type Type = value.GetType();
/// <inheritdoc/>
public override string GetTypeString() => "enum";
/// <inheritdoc/>
public override string GetValueString() => $"{Type.Name}.{Value}";
}
/// <summary>
/// Пустое поле структуры.
/// </summary>
/// <param name="builder">Класс построения структуры данных.</param>
/// <param name="name">Название поля.</param>
/// <param name="depth">Глубина поля.</param>
internal class StructureFieldNull(StructureBuilder builder, string name, int depth)
: StructureField(builder, name, depth)
{
/// <inheritdoc/>
public override StructureFieldType FieldType => StructureFieldType.Null;
/// <inheritdoc/>
public override string GetTypeString() => "null";
/// <inheritdoc/>
public override string GetValueString() => "";
}
/// <summary>
/// Простое поле структуры.
/// </summary>
/// <param name="builder">Класс построения структуры данных.</param>
/// <param name="value">Значение поля.</param>
/// <param name="name">Название поля.</param>
/// <param name="depth">Глубина поля.</param>
internal class StructureFieldPrimitive(
StructureBuilder builder,
object value,
string name,
int depth
) : StructureField(builder, name, depth)
{
/// <inheritdoc/>
public override StructureFieldType FieldType => StructureFieldType.Primitive;
/// <summary>
/// Значение поля.
/// </summary>
public readonly object Value = value;
/// <summary>
/// Тип значения поля.
/// </summary>
public readonly Type Type = value.GetType();
/// <inheritdoc/>
public override string GetTypeString()
{
return Type.GetTypeCode(Type) switch
{
TypeCode.Boolean => "bool",
TypeCode.Int32 => "int",
TypeCode.String => "string",
TypeCode.Double => "double",
_ => Type.Name,
};
}
/// <inheritdoc/>
public override string GetValueString()
{
if (Type == typeof(string))
{
return "\"" + ((string)Value).Replace("\"", "\\\"") + "\"";
}
return Value.ToString() ?? string.Empty;
}
}
/// <summary>
/// Поле структуры с ссылкой на объект.
/// </summary>
/// <param name="builder">Класс построения структуры данных.</param>
/// <param name="name">Название поля.</param>
/// <param name="reference">Ссылка на объект.</param>
/// <param name="depth">Глубина поля.</param>
internal class StructureFieldReference(
StructureBuilder builder,
string name,
StructureFieldComplex? reference,
int depth
) : StructureField(builder, name, depth)
{
/// <summary>
/// Ссылка на объект.
/// </summary>
public StructureFieldComplex? Reference = reference;
/// <inheritdoc/>
public override StructureFieldType FieldType => StructureFieldType.Reference;
/// <inheritdoc/>
public override string GetTypeString() => Reference?.GetTypeString() ?? "???";
/// <inheritdoc/>
public override string GetValueString() => "<Reference>";
}
/// <summary>
/// Поле структуры пропускаемого объекта.
/// </summary>
/// <param name="builder">Класс построения структуры данных.</param>
/// <param name="name">Название поля.</param>
/// <param name="reference">Ссылка на объект.</param>
/// <param name="depth">Глубина поля.</param>
internal class StructureFieldIgnored(
StructureBuilder builder,
string name,
StructureFieldComplex? reference,
int depth
) : StructureField(builder, name, depth)
{
/// <summary>
/// Ссылка на объект.
/// </summary>
public StructureFieldComplex? Reference = reference;
/// <inheritdoc/>
public override StructureFieldType FieldType => StructureFieldType.Reference;
/// <inheritdoc/>
public override string GetTypeString() => Reference?.GetTypeString() ?? "???";
/// <inheritdoc/>
public override string GetValueString() => "<Ignored>";
}
@@ -0,0 +1,239 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using LinqToDB.Extensions;
using Pf2e.Database.Model.Common.I18n;
namespace Pf2e.WPF.EntityViewer.EntityViewStructure;
/// <summary>
/// Поле стркутуры с объектом.
/// </summary>
internal class StructureFieldComplex : StructureField
{
/// <inheritdoc/>
public override StructureFieldType FieldType => StructureFieldType.Complex;
/// <summary>
/// Набор полей объекта.
/// </summary>
public readonly List<StructureField> Fields = [];
/// <summary>
/// Тип значения поля.
/// </summary>
public readonly Type Type;
/// <summary>
/// Флаг, указывающий, отображается ли поля объекта.
/// </summary>
public bool Collapsed
{
get => _collapsed;
set
{
_collapsed = value;
UpdateFieldVisibility();
OnPropertyChanged();
}
}
private bool _collapsed;
/// <summary>
/// Флаг, указывающий, что у этот элемент можно свернуть.
/// </summary>
public bool Collapsable
{
get => Fields.Count > 0;
}
/// <summary>
/// Значение поля идентификатора элемента.
/// </summary>
public string? IdValue { get; private set; }
/// <inheritdoc/>
public override bool Visible
{
get => base.Visible;
set
{
base.Visible = value;
UpdateFieldVisibility();
}
}
/// <summary>
/// Конструктор класса поля структуры с объектом.
/// </summary>
/// <param name="builder">Класс построения структуры данных.</param>
/// <param name="value">Значение поля.</param>
/// <param name="name">Название поля.</param>
/// <param name="depth">Глубина поля.</param>
public StructureFieldComplex(StructureBuilder builder, object value, string name, int depth)
: base(builder, name, depth)
{
Type = value.GetType();
SetFields((dynamic)value);
}
/// <summary>
/// Обновить видимость полей структуры.
/// </summary>
private void UpdateFieldVisibility()
{
foreach (StructureField field in Fields)
{
field.Visible = Visible && !Collapsed;
}
}
/// <summary>
/// Установить поля структуры `LocaleString`.
/// </summary>
/// <param name="localeString">Объект строки локализации.</param>
private void SetFields(LocaleString localeString)
{
SetFieldsFromRecords(
localeString
.Select(field => new FieldRecord(field.Locale.ToString(), field.Value))
.ToList()
);
}
/// <summary>
/// Установить поля структуры `IEnumerable`.
/// </summary>
/// <param name="enumerable">Объект-перечисление.</param>
private void SetFields(IEnumerable enumerable)
{
int i = 0;
List<FieldRecord> records = [];
foreach (object? item in enumerable)
{
records.Add(new($"[{i++}]", item));
}
SetFieldsFromRecords(records);
}
/// <summary>
/// Установить поля структуры общего объекта.
/// </summary>
/// <param name="value">Объект.</param>
private void SetFields(object value)
{
Type type = value.GetType();
PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var records = properties
.Where(property => property.GetAttribute<Newtonsoft.Json.JsonIgnoreAttribute>() is null)
.Select(property => new FieldRecord(property.Name, property.GetValue(value, null)))
.ToList();
var idRecord = records.FirstOrDefault(record => record.Name == "Id");
if (idRecord is not null && idRecord.Value is not null)
{
records.Remove(idRecord);
IdValue = idRecord.Value.ToString();
}
SetFieldsFromRecords(records);
}
/// <summary>
/// Установить поля структуры из списка записей.
/// </summary>
/// <param name="records"></param>
private void SetFieldsFromRecords(List<FieldRecord> records)
{
foreach (
FieldRecord record in records.Where(record =>
record.FieldType == StructureFieldType.Complex
)
)
{
record.Allowed = Builder.RegisterProcessed(record.Value!);
}
foreach (FieldRecord record in records)
{
Fields.Add(
record is { FieldType: StructureFieldType.Complex, Allowed: true }
? Builder.Create(record.Name, record.Value!, Depth + 1)
: Builder.Resolve(record.Name, record.Value!, Depth + 1)
);
}
}
/// <summary>
/// Запись поля структуры.
/// </summary>
/// <param name="name">Название поля.</param>
/// <param name="value">Значение поля.</param>
private class FieldRecord(string name, object? value)
{
/// <summary>
/// Название поля.
/// </summary>
public readonly string Name = name;
/// <summary>
/// Значение поля.
/// </summary>
public readonly object? Value = value;
/// <summary>
/// Тип поля структуры.
/// </summary>
public StructureFieldType FieldType => StructureBuilder.GetFieldType(Value);
/// <summary>
/// Флаг, указывающий, использовать ли поле.
/// </summary>
public bool Allowed = true;
}
/// <inheritdoc/>
public override string GetTypeString()
{
if (Type.IsGenericEnumerableType())
{
Type elementType = Type.GetGenericArguments().Single();
return $"{elementType.Name}[]";
}
else
{
return Type.Name;
}
}
/// <inheritdoc/>
public override string GetValueString()
{
if (Type.IsGenericEnumerableType())
{
return Fields.Count.ToString() + (Fields.Count == 1 ? " item" : " items");
}
if (IdValue is not null)
{
return $"#{IdValue}";
}
return "";
}
/// <inheritdoc/>
public override ICollection<T> GetRows<T>(Func<StructureField, T> func)
{
return [.. base.GetRows(func), .. Fields.SelectMany(f => f.GetRows(func)).ToList()];
}
}
@@ -0,0 +1,32 @@
namespace Pf2e.WPF.EntityViewer.EntityViewStructure;
/// <summary>
/// Тип поля структуры.
/// </summary>
internal enum StructureFieldType
{
/// <summary>
/// Пустое поле.
/// </summary>
Null,
/// <summary>
/// Простое поле.
/// </summary>
Primitive,
/// <summary>
/// Поле с перечислением.
/// </summary>
Enum,
/// <summary>
/// Поле с объектом.
/// </summary>
Complex,
/// <summary>
/// Поле с ссылкой на объект.
/// </summary>
Reference,
}
@@ -0,0 +1,39 @@
using System.Collections.Generic;
namespace Pf2e.WPF.EntityViewer.EntityViewStructure;
/// <summary>
/// Класс для хранения обработанного объекта.
/// </summary>
/// <param name="value">Объект.</param>
internal class StructureProcessedObject(object value)
{
public readonly object Value = value;
private StructureFieldComplex? _field;
public StructureFieldComplex? Field
{
get => _field;
set
{
_field = value;
foreach (StructureFieldReference reference in _references)
{
reference.Reference = Field;
}
}
}
private readonly List<StructureFieldReference> _references = [];
public void AddReference(StructureFieldReference reference)
{
_references.Add(reference);
if (_field is not null)
{
reference.Reference = Field;
}
}
}
@@ -0,0 +1,271 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using FontAwesome.Sharp;
using Color = System.Windows.Media.Color;
namespace Pf2e.WPF.EntityViewer.EntityViewStructure;
/// <summary>
/// Строка структуры, представляющая поле сущности.
/// </summary>
internal class StructureRow : INotifyPropertyChanged
{
/// <summary>
/// Отображаемая строка поля сущности.
/// </summary>
public StructureField Field { get; }
/// <summary>
/// Список типов лестницы.
/// </summary>
public List<LadderType> Ladder { get; set; } = [];
/// <summary>
/// Флаг наличия кнопки.
/// </summary>
public bool HasButton =>
Field.FieldType == StructureFieldType.Complex && ((StructureFieldComplex)Field).Collapsable;
/// <summary>
/// Флаг, указывающий, отображается ли строка.
/// </summary>
public bool Visible => Field.Visible;
/// <summary>
/// Флаг, указывающий, отображается ли строка.
/// </summary>
public Visibility ItemVisibility => Visible ? Visibility.Visible : Visibility.Collapsed;
/// <summary>
/// Флаг, указывающий, отображается ли строка.
/// </summary>
public Visibility ButtonVisibility => HasButton ? Visibility.Visible : Visibility.Collapsed;
/// <summary>
/// Иконка кнопки.
/// </summary>
public string ButtonIcon =>
HasButton && !((StructureFieldComplex)Field).Collapsed
? IconChar.MinusSquare.ToChar()
: IconChar.PlusSquare.ToChar();
/// <summary>
/// Глубина отображения поля.
/// </summary>
public int Depth => Field.Depth;
/// <summary>
/// Строковое представление названия поля.
/// </summary>
public string Name => Field.Name;
/// <summary>
/// Строковое представление типа поля.
/// </summary>
public string Type
{
get
{
string result = Field.GetTypeString();
if (result != "")
{
result = $@"({result})";
}
return result;
}
}
/// <summary>
/// Строковое представление значения поля.
/// </summary>
public string Value => Field.GetValueString().Replace("\r", "").Replace("\n", "");
/// <summary>
/// Цвет значения
/// </summary>
public Color? AccentColor =>
Field switch
{
StructureFieldPrimitive primitive => System.Type.GetTypeCode(primitive.Type) switch
{
TypeCode.Boolean => Colors.DarkGoldenrod,
TypeCode.Int32 => Colors.Blue,
TypeCode.Double => Colors.Aqua,
TypeCode.String => Colors.Green,
_ => null,
},
StructureFieldEnum => Colors.Red,
StructureFieldNull => Colors.Transparent,
_ => null,
};
/// <summary>
/// Команда переключения.
/// </summary>
public ICommand CollapseCommand { get; }
/// <summary>
/// Кониструктор класса <see cref="StructureRow"/>.
/// </summary>
/// <param name="field">Поле.</param>
public StructureRow(StructureField field)
{
Field = field;
CollapseCommand = new CollapseCommand(this);
Field.PropertyChanged += Field_PropertyChanged;
}
private void Field_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(Field));
OnPropertyChanged(nameof(Visible));
OnPropertyChanged(nameof(ItemVisibility));
OnPropertyChanged(nameof(ButtonVisibility));
OnPropertyChanged(nameof(ButtonIcon));
}
/// <summary>
/// Событие изменения поля.
/// </summary>
public event Action<StructureRow, StructureField>? OnChange;
/// <summary>
/// Изменение поля.
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// При изменении поля.
/// </summary>
/// <param name="propertyName">Название поля.</param>
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new(propertyName));
}
}
/// <summary>
/// Команда для сворачивания/разворачивания поля структуры.
/// </summary>
/// <param name="structureRow">Строка структуры, содержащая поле.</param>
internal class CollapseCommand(StructureRow structureRow) : ICommand
{
/// <summary>
/// Строка структуры, содержащая поле, для которого выполняется команда.
/// </summary>
private StructureRow StructureRow { get; } = structureRow;
/// <summary>
/// Событие, вызываемое при изменении возможности выполнения команды.
/// </summary>
public event EventHandler? CanExecuteChanged;
/// <summary>
/// Проверяет, может ли команда быть выполнена с указанным параметром.
/// </summary>
/// <param name="parameter">Параметр, передаваемый команде.</param>
/// <returns><c>true</c>, если команда может быть выполнена; в противном случае <c>false</c>.</returns>
public bool CanExecute(object? parameter)
{
return true;
}
/// <summary>
/// Выполняет команду с указанным параметром.
/// </summary>
/// <param name="parameter">Параметр, передаваемый команде.</param>
public void Execute(object? parameter)
{
StructureFieldComplex field = (StructureFieldComplex)StructureRow.Field;
field.Collapsed = !field.Collapsed;
}
}
/// <summary>
/// Преобразователь, который адаптирует цвет в зависимости от типа целевого свойства.
/// </summary>
public class ColorAdapterConverter : IMultiValueConverter
{
/// <summary>
/// Смешивает два компнента цвета с заданным коэффициентом смешивания.
/// </summary>
/// <param name="a">Первый компонент цвета.</param>
/// <param name="b">Второй компонент цвета.</param>
/// <param name="rate">Коэффициент смешивания, где 0.0 означает полный компонент <paramref name="a"/>, а 1.0 означает полный компонент <paramref name="b"/>.</param>
/// <returns>Смешанный компонент цвета.</returns>
private byte Mix(byte a, byte b, double rate) =>
System.Convert.ToByte(Math.Round(a * rate + b * (1 - rate)));
/// <summary>
/// Преобразует массив значений в целевой тип, усредняя цвета.
/// </summary>
/// <param name="value">Массив значений, содержащий цвета.</param>
/// <param name="targetType">Целевой тип, в который нужно преобразовать значения.</param>
/// <param name="parameter">Параметр преобразования, не используется.</param>
/// <param name="culture">Культура, используемая для преобразования, не используется.</param>
/// <returns>Результат преобразования в целевой тип, который может быть <c>null</c>, если преобразование невозможно.</returns>
public object? Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
{
Color textColor = SystemColors.WindowTextColor;
double a = textColor.A;
double r = textColor.R;
double g = textColor.G;
double b = textColor.B;
int n = 1 + value.Length;
foreach (object item in value)
{
if (item is not Color color)
{
n--;
continue;
}
a += color.A;
r += color.R;
g += color.G;
b += color.B;
}
Color resultColor = Color.FromArgb(
System.Convert.ToByte(Math.Round(a / n)),
System.Convert.ToByte(Math.Round(r / n)),
System.Convert.ToByte(Math.Round(g / n)),
System.Convert.ToByte(Math.Round(b / n))
);
Dictionary<Type, Func<object>> @switch = new()
{
{ typeof(Brush), () => new SolidColorBrush(resultColor) },
};
return @switch.TryGetValue(targetType, out Func<object>? action) ? action() : null;
}
/// <summary>
/// Преобразует значение обратно в массив типов, но не реализован.
/// </summary>
/// <param name="value"></param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public object[] ConvertBack(
object value,
Type[] targetType,
object parameter,
CultureInfo culture
)
{
throw new NotImplementedException();
}
}