diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs
index ce1d0bc..e05d7b3 100644
--- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs
+++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControl.cs
@@ -1,5 +1,6 @@
using System.ComponentModel;
using Pf2eDataScrapper.Helpers;
+using Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
namespace Pf2eDataScrapper.Forms.Controls.EntityTreeViewer;
diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs
index 672bd51..f8c204f 100644
--- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs
+++ b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/EntityTreeViewerControlRow.cs
@@ -1,4 +1,5 @@
using FontAwesome.Sharp;
+using Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
using Font = System.Drawing.Font;
using FontStyle = System.Drawing.FontStyle;
using Image = System.Drawing.Image;
diff --git a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/StructureBuilder.cs b/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/StructureBuilder.cs
deleted file mode 100644
index 731b64e..0000000
--- a/Pf2eDataScrapper/Forms/Controls/EntityTreeViewer/StructureBuilder.cs
+++ /dev/null
@@ -1,627 +0,0 @@
-using System.Collections;
-using System.Reflection;
-using LinqToDB.Extensions;
-using Pf2eModel.Model.Common.I18n;
-
-namespace Pf2eDataScrapper.Forms.Controls.EntityTreeViewer;
-
-///
-/// Класс для построения структуры данных класса.
-///
-internal class StructureBuilder
-{
- ///
- /// Список обработанных объектов.
- ///
- private readonly List _processedObjects = [];
-
- ///
- /// Создание поля структуры.
- ///
- /// Название поля.
- /// Значение поля.
- /// Глубина, на котором поле отображается.
- /// Поле структуры.
- public StructureFieldComplex Create(string name, object value, int depth = 0)
- {
- _ = RegisterProcessed(value);
-
- StructureFieldComplex field = new(this, value, name, depth);
-
- CompleteProcessed(value, field);
-
- return field;
- }
-
- ///
- /// Регистрация обработанного объекта.
- ///
- /// Регистрируемый объект.
- /// `true`, если объект был добавлен; `false`, если объект был добавлен ранее.
- public bool RegisterProcessed(object value)
- {
- if (_processedObjects.Any(p => p.Value == value))
- {
- return false;
- }
-
- StructureProcessedObject processed = new(value);
-
- _processedObjects.Add(processed);
-
- return true;
- }
-
- ///
- /// Дополнить обработанный объект.
- ///
- /// Значение.
- /// Поле объекта.
- public void CompleteProcessed(object value, StructureFieldComplex field)
- {
- StructureProcessedObject? q = _processedObjects.FirstOrDefault(p => p.Value == value);
-
- if (q == null)
- {
- return;
- }
-
- q.Field = field;
- }
-
- ///
- /// Выбор поля структуры по значению.
- ///
- /// Название поля.
- /// Значение поля.
- /// Глубина, на котором поле отображается.
- ///
- 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 StructureFieldReference(
- this,
- name,
- null,
- depth
- );
- processed.AddReference(reference);
- return reference;
- }
-
- throw new("Impossible");
- }
-
- ///
- /// Получить тип поля структуры по значению.
- ///
- /// Значение поля.
- /// Тип поля структуры.
- 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;
- }
-}
-
-///
-/// Класс для хранения обработанного объекта.
-///
-/// Объект.
-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 _references = [];
-
- public void AddReference(StructureFieldReference reference)
- {
- _references.Add(reference);
-
- if (_field is not null)
- {
- reference.Reference = Field;
- }
- }
-}
-
-///
-/// Тип поля структуры.
-///
-internal enum StructureFieldType
-{
- ///
- /// Пустое поле.
- ///
- Null,
-
- ///
- /// Простое поле.
- ///
- Primitive,
-
- ///
- /// Поле с перечислением.
- ///
- Enum,
-
- ///
- /// Поле с объектом.
- ///
- Complex,
-
- ///
- /// Поле с ссылкой на объект.
- ///
- Reference,
-}
-
-///
-/// Тип лестницы глубины.
-///
-internal enum LadderType
-{
- ///
- /// Пустой элемент: [ ].
- ///
- Empty,
-
- ///
- /// Последний элемент: [└].
- ///
- Last,
-
- ///
- /// Проходной элемент: [├] или [│].
- ///
- Through,
-}
-
-///
-/// Базовый класс для полей структуры.
-///
-/// Класс построения структуры данных.
-/// Название поля.
-/// Глубина поля.
-internal abstract class StructureField(StructureBuilder builder, string name, int depth)
-{
- ///
- /// Название поля.
- ///
- public readonly string Name = name;
-
- ///
- /// Тип поля структуры.
- ///
- public abstract StructureFieldType FieldType { get; }
-
- ///
- /// Класс построения структуры данных.
- ///
- public readonly StructureBuilder Builder = builder;
-
- ///
- /// Флаг, указывающий, отображается ли поле.
- ///
- public virtual bool Visible { get; set; } = true;
-
- ///
- /// Глубина поля структуры.
- ///
- public readonly int Depth = depth;
-
- ///
- /// Получить строку, представляющую тип поля.
- ///
- /// Строка, представляющая тип поля.
- public abstract string GetTypeString();
-
- ///
- /// Получить строку, представляющую значение поля.
- ///
- /// Строка, представляющая значение поля.
- public abstract string GetValueString();
-
- ///
- /// Получить набор строк, используя результат функция.
- ///
- /// Тип результирующей строки.
- /// Функция, оборажения поля в результирующую строку.
- /// Коллекция, содержащая набор строк.
- public virtual ICollection GetRows(Func func)
- {
- StructureField[] q = [this];
- return q.Select(func).ToList();
- }
-}
-
-///
-/// Пустое поле структуры.
-///
-/// Класс построения структуры данных.
-/// Название поля.
-/// Глубина поля.
-internal class StructureFieldNull(StructureBuilder builder, string name, int depth)
- : StructureField(builder, name, depth)
-{
- ///
- public override StructureFieldType FieldType => StructureFieldType.Null;
-
- ///
- public override string GetTypeString() => "null";
-
- ///
- public override string GetValueString() => "";
-}
-
-///
-/// Простое поле структуры.
-///
-/// Класс построения структуры данных.
-/// Значение поля.
-/// Название поля.
-/// Глубина поля.
-internal class StructureFieldPrimitive(
- StructureBuilder builder,
- object value,
- string name,
- int depth
-) : StructureField(builder, name, depth)
-{
- ///
- public override StructureFieldType FieldType => StructureFieldType.Primitive;
-
- ///
- /// Значение поля.
- ///
- public readonly object Value = value;
-
- ///
- /// Тип значения поля.
- ///
- public readonly Type Type = value.GetType();
-
- ///
- public override string GetTypeString()
- {
- return Type.GetTypeCode(Type) switch
- {
- TypeCode.Boolean => "bool",
- TypeCode.Int32 => "int",
- TypeCode.String => "string",
- TypeCode.Double => "double",
- _ => Type.Name,
- };
- }
-
- ///
- public override string GetValueString()
- {
- if (Type == typeof(string))
- {
- return "\"" + ((string)Value).Replace("\"", "\\\"") + "\"";
- }
-
- return Value.ToString() ?? string.Empty;
- }
-}
-
-///
-/// Поле структуры с перечислением.
-///
-/// Класс построения структуры данных.
-/// Значение поля.
-/// Название поля.
-/// Глубина поля.
-internal class StructureFieldEnum(StructureBuilder builder, object value, string name, int depth)
- : StructureField(builder, name, depth)
-{
- ///
- public override StructureFieldType FieldType => StructureFieldType.Enum;
-
- ///
- /// Значение поля.
- ///
- public readonly object Value = value;
-
- ///
- /// Тип значения поля.
- ///
- public readonly Type Type = value.GetType();
-
- ///
- public override string GetTypeString() => "enum";
-
- ///
- public override string GetValueString() => $"{Type.Name}.{Value}";
-}
-
-///
-/// Поле стркутуры с объектом.
-///
-internal class StructureFieldComplex : StructureField
-{
- ///
- public override StructureFieldType FieldType => StructureFieldType.Complex;
-
- ///
- /// Набор полей объекта.
- ///
- public readonly List Fields = [];
-
- ///
- /// Тип значения поля.
- ///
- public readonly Type Type;
-
- ///
- /// Флаг, указывающий, отображается ли поля объекта.
- ///
- public bool Collapsed
- {
- get => _collapsed;
- set
- {
- _collapsed = value;
- UpdateFieldVisibility();
- }
- }
- private bool _collapsed;
-
- ///
- public override bool Visible
- {
- get => _visible;
- set
- {
- _visible = value;
- UpdateFieldVisibility();
- }
- }
- private bool _visible = true;
-
- ///
- /// Конструктор класса поля структуры с объектом.
- ///
- /// Класс построения структуры данных.
- /// Значение поля.
- /// Название поля.
- /// Глубина поля.
- public StructureFieldComplex(StructureBuilder builder, object value, string name, int depth)
- : base(builder, name, depth)
- {
- Type = value.GetType();
-
- SetFields((dynamic)value);
- }
-
- ///
- /// Обновить видимость полей структуры.
- ///
- private void UpdateFieldVisibility()
- {
- foreach (StructureField field in Fields)
- {
- field.Visible = Visible && !Collapsed;
- }
- }
-
- ///
- /// Установить поля структуры `LocaleString`.
- ///
- /// Объект строки локализации.
- private void SetFields(LocaleString localeString)
- {
- SetFieldsFromRecords(
- localeString
- .Select(field => new FieldRecord(field.Locale.ToString(), field.Value))
- .ToList()
- );
- }
-
- ///
- /// Установить поля структуры `IEnumerable`.
- ///
- /// Объект-перечисление.
- private void SetFields(IEnumerable enumerable)
- {
- int i = 0;
-
- List records = [];
-
- foreach (object? item in enumerable)
- {
- records.Add(new($"[{i++}]", item));
- }
-
- SetFieldsFromRecords(records);
- }
-
- ///
- /// Установить поля структуры общего объекта.
- ///
- /// Объект.
- private void SetFields(object value)
- {
- Type type = value.GetType();
- PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
-
- SetFieldsFromRecords(
- properties
- .Where(property =>
- property.GetAttribute() is null
- )
- .Select(property => new FieldRecord(property.Name, property.GetValue(value, null)))
- .ToList()
- );
- }
-
- ///
- /// Установить поля структуры из списка записей.
- ///
- ///
- private void SetFieldsFromRecords(List 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)
- );
- }
- }
-
- ///
- /// Запись поля структуры.
- ///
- /// Название поля.
- /// Значение поля.
- private class FieldRecord(string name, object? value)
- {
- ///
- /// Название поля.
- ///
- public readonly string Name = name;
-
- ///
- /// Значение поля.
- ///
- public readonly object? Value = value;
-
- ///
- /// Тип поля структуры.
- ///
- public StructureFieldType FieldType => StructureBuilder.GetFieldType(Value);
-
- ///
- /// Флаг, указывающий, использовать ли поле.
- ///
- public bool Allowed = true;
- }
-
- ///
- public override string GetTypeString()
- {
- if (Type.IsGenericEnumerableType())
- {
- Type elementType = Type.GetGenericArguments().Single();
-
- return $"{elementType.Name}[]";
- }
- else
- {
- return Type.Name;
- }
- }
-
- ///
- public override string GetValueString()
- {
- if (Type.IsGenericEnumerableType())
- {
- return Fields.Count.ToString() + (Fields.Count == 1 ? " item" : " items");
- }
- return "";
- }
-
- ///
- public override ICollection GetRows(Func func)
- {
- return [.. base.GetRows(func), .. Fields.SelectMany(f => f.GetRows(func)).ToList()];
- }
-}
-
-///
-/// Поле структуры с ссылкой на объект.
-///
-/// Класс построения структуры данных.
-/// Название поля.
-/// Ссылка на объект.
-/// Глубина поля.
-internal class StructureFieldReference(
- StructureBuilder builder,
- string name,
- StructureFieldComplex? reference,
- int depth
-) : StructureField(builder, name, depth)
-{
- ///
- /// Ссылка на объект.
- ///
- public StructureFieldComplex? Reference = reference;
-
- ///
- public override StructureFieldType FieldType => StructureFieldType.Reference;
-
- ///
- public override string GetTypeString() => Reference?.GetTypeString() ?? "???";
-
- ///
- public override string GetValueString() => "";
-}
diff --git a/Pf2eDataScrapper/ImportApplication.cs b/Pf2eDataScrapper/ImportApplication.cs
index 3111401..cb6de5d 100644
--- a/Pf2eDataScrapper/ImportApplication.cs
+++ b/Pf2eDataScrapper/ImportApplication.cs
@@ -1,5 +1,6 @@
using System.ComponentModel;
using System.Windows;
+using System.Windows.Forms.Integration;
using Microsoft.Extensions.DependencyInjection;
using Pf2eDataScrapper.App;
using Pf2eDataScrapper.Helpers;
@@ -76,11 +77,12 @@ internal sealed class ImportApplication : ApplicationContext
case ApplicationStage.InitialSettings:
_startFormWpf = ActivatorUtilities.CreateInstance(ServiceProvider);
- _startFormWpf.WindowStartupLocation = WindowStartupLocation.CenterOwner;
+ _startFormWpf.WindowStartupLocation = WindowStartupLocation.CenterScreen;
_startFormWpf.Closing += Wpf_HandleFormClosing;
_startFormWpf.ApplicationStateChange += ApplicationStateChange;
_startFormWpf.Show();
+ ElementHost.EnableModelessKeyboardInterop(_startFormWpf);
CurrentWpfForm = _startFormWpf;
break;
@@ -92,22 +94,25 @@ internal sealed class ImportApplication : ApplicationContext
_progressFormWpf = ActivatorUtilities.CreateInstance(
ServiceProvider
);
- _progressFormWpf.WindowStartupLocation = WindowStartupLocation.CenterOwner;
+ _progressFormWpf.WindowStartupLocation = WindowStartupLocation.CenterScreen;
_progressFormWpf.Closing += Wpf_HandleFormClosing;
_progressFormWpf.ApplicationStateChange += ApplicationStateChange;
-
- CurrentWpfForm = _progressFormWpf;
_progressFormWpf.Show();
+
+ ElementHost.EnableModelessKeyboardInterop(_progressFormWpf);
+ CurrentWpfForm = _progressFormWpf;
+
executor.Start();
break;
case ApplicationStage.Viewer:
_entityForm = ActivatorUtilities.CreateInstance(ServiceProvider);
- _entityForm.WindowStartupLocation = WindowStartupLocation.CenterOwner;
+ _entityForm.WindowStartupLocation = WindowStartupLocation.CenterScreen;
_entityForm.Closing += Wpf_HandleFormClosing;
//_entityForm.ApplicationStateChange += ApplicationStateChange;
_entityForm.Show();
+ ElementHost.EnableModelessKeyboardInterop(_entityForm);
CurrentWpfForm = _entityForm;
break;
diff --git a/Pf2eDataScrapper/Program.cs b/Pf2eDataScrapper/Program.cs
index 71d8b6c..6fc7b65 100644
--- a/Pf2eDataScrapper/Program.cs
+++ b/Pf2eDataScrapper/Program.cs
@@ -30,8 +30,6 @@ internal static class Program
ImportApplication app = ActivatorUtilities.CreateInstance(
serviceProvider
);
-
- Application.Run(app);
}
private static IServiceCollection RegisterServices()
diff --git a/Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml b/Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml
index e595937..cb9c466 100644
--- a/Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml
+++ b/Pf2eDataScrapper/WPF/Components/DbEntityListControl.xaml
@@ -6,6 +6,7 @@
xmlns:local="clr-namespace:Pf2eDataScrapper.WPF.Components"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Pf2eDataScrapper.WPF.ViewModels"
+ Padding="3,3,3,3"
d:DataContext="{d:DesignInstance Type=viewModels:DbEntityListViewModel}"
d:DesignHeight="450"
d:DesignWidth="400"
@@ -14,10 +15,11 @@
+
-
+
@@ -26,33 +28,48 @@
+ DockPanel.Dock="Left"
+ IsEnabled="{Binding HasPrevious}" />
+ DockPanel.Dock="Left"
+ IsEnabled="{Binding HasPrevious}" />
+ DockPanel.Dock="Right"
+ IsEnabled="{Binding HasNext}" />
+ DockPanel.Dock="Right"
+ IsEnabled="{Binding HasNext}" />
+ PreviewTextInput="TextBox_PreviewTextInput">
+
+
+
+
@@ -62,6 +79,7 @@
@@ -69,7 +87,8 @@
+ ItemsSource="{Binding Entities}"
+ SelectedItem="{Binding SelectedEntity}">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Pf2eDataScrapper/WPF/Components/EntityViewControl.xaml.cs b/Pf2eDataScrapper/WPF/Components/EntityViewControl.xaml.cs
new file mode 100644
index 0000000..8080899
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/Components/EntityViewControl.xaml.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using Pf2eDataScrapper.Forms.Controls.EntityTreeViewer;
+using Pf2eDataScrapper.Helpers;
+using Pf2eDataScrapper.WPF.ViewModels;
+using Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
+using Color = System.Windows.Media.Color;
+using SystemColors = System.Windows.SystemColors;
+
+namespace Pf2eDataScrapper.WPF.Components;
+
+internal partial class EntityViewControl : System.Windows.Controls.UserControl
+{
+ public EntityViewViewModel ViewModel
+ {
+ get => _viewModel;
+ set
+ {
+ _viewModel = value;
+ DataContext = ViewModel;
+ }
+ }
+ private EntityViewViewModel _viewModel;
+
+ #region "Поля элемента управления"
+
+ ///
+ /// Высота строки поля структуры.
+ ///
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
+ [Category("Entity Render")]
+ [Description("The height of each item in the tree view.")]
+ public int ItemHeight
+ {
+ get => _itemHeight;
+ set
+ {
+ _itemHeight = value;
+ Resources["ItemHeight"] = ItemHeight;
+ }
+ }
+ private int _itemHeight = 20;
+
+ ///
+ /// Ширина отступа глубины.
+ ///
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
+ [Category("Entity Render")]
+ [Description("The size of tab.")]
+ public int DepthSize { get; set; } = 20;
+
+ ///
+ /// Размер шрифта типа поля сущности.
+ ///
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
+ [Category("Entity Render")]
+ [Description("The size of tab.")]
+ public float TypeFontSize { get; set; } = 9.5f;
+
+ ///
+ /// Размер кнопки плюс/минус.
+ ///
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
+ [Category("Entity Render")]
+ [Description("The size of plus/minus button.")]
+ public int ButtonSize { get; set; } = 20;
+
+ ///
+ /// Ширина имени поля сущности.
+ ///
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int NameWidth { get; private set; } = 200;
+
+ #endregion
+
+
+ public EntityViewControl()
+ {
+ InitializeComponent();
+
+ Resources["ItemHeight"] = ItemHeight;
+ }
+}
diff --git a/Pf2eDataScrapper/WPF/Stores/SelectedEntityStore.cs b/Pf2eDataScrapper/WPF/Stores/SelectedEntityStore.cs
index e54ef6a..4e08c72 100644
--- a/Pf2eDataScrapper/WPF/Stores/SelectedEntityStore.cs
+++ b/Pf2eDataScrapper/WPF/Stores/SelectedEntityStore.cs
@@ -4,15 +4,15 @@ namespace Pf2eDataScrapper.WPF.Stores;
public class SelectedEntityStore
{
- private Entity? _selectedEntityType;
+ private Entity? _selectedEntity;
- public Entity? SelectedEntityType
+ public Entity? SelectedEntity
{
- get => _selectedEntityType;
+ get => _selectedEntity;
set
{
- _selectedEntityType = value;
- SelectedEntityTypeChanged?.Invoke(SelectedEntityType);
+ _selectedEntity = value;
+ SelectedEntityTypeChanged?.Invoke(SelectedEntity);
}
}
diff --git a/Pf2eDataScrapper/WPF/ViewModels/DbEntityListViewModel.cs b/Pf2eDataScrapper/WPF/ViewModels/DbEntityListViewModel.cs
index 9c4c465..a3241c2 100644
--- a/Pf2eDataScrapper/WPF/ViewModels/DbEntityListViewModel.cs
+++ b/Pf2eDataScrapper/WPF/ViewModels/DbEntityListViewModel.cs
@@ -1,5 +1,6 @@
-using System.Collections.Concurrent;
-using System.Collections.ObjectModel;
+using System.Collections.ObjectModel;
+using AngleSharp.Browser;
+using LinqToDB.DataProvider.Access;
using Pf2eDataScrapper.WPF.Stores;
using Pf2eModel;
using Pf2eModel.Model.Base;
@@ -11,9 +12,16 @@ internal class DbEntityListViewModel : ViewModelBase
{
public Pf2eDbContext Context { get; }
public SelectedEntityTypeStore SelectedEntityTypeStore { get; }
+ public SelectedEntityStore SelectedEntityStore { get; }
private EntityType? SelectedEntityType => SelectedEntityTypeStore.SelectedEntityType;
+ public Entity? SelectedEntity
+ {
+ get => SelectedEntityStore.SelectedEntity;
+ set => SelectedEntityStore.SelectedEntity = value;
+ }
+
public int Page
{
get => _page;
@@ -21,6 +29,9 @@ internal class DbEntityListViewModel : ViewModelBase
{
_page = Math.Clamp(value, 1, PageCount);
OnPropertyChanged();
+ OnPropertyChanged(nameof(HasPrevious));
+ OnPropertyChanged(nameof(HasNext));
+ Reload();
}
}
private int _page = 1;
@@ -33,6 +44,8 @@ internal class DbEntityListViewModel : ViewModelBase
_entitiesCount = value;
OnPropertyChanged();
OnPropertyChanged(nameof(PageCount));
+ OnPropertyChanged(nameof(HasPrevious));
+ OnPropertyChanged(nameof(HasNext));
}
}
private int _entitiesCount = 1;
@@ -45,6 +58,7 @@ internal class DbEntityListViewModel : ViewModelBase
_perPage = value;
OnPropertyChanged();
OnPropertyChanged(nameof(PageCount));
+ Reload();
}
}
private int _perPage = 30;
@@ -56,6 +70,7 @@ internal class DbEntityListViewModel : ViewModelBase
{
_search = value;
OnPropertyChanged();
+ Reload();
}
}
private string _search = "";
@@ -75,11 +90,13 @@ internal class DbEntityListViewModel : ViewModelBase
public DbEntityListViewModel(
Pf2eDbContext context,
- SelectedEntityTypeStore selectedEntityTypeStore
+ SelectedEntityTypeStore selectedEntityTypeStore,
+ SelectedEntityStore selectedEntityStore
)
{
Context = context;
SelectedEntityTypeStore = selectedEntityTypeStore;
+ SelectedEntityStore = selectedEntityStore;
SelectedEntityTypeStore.SelectedEntityTypeChanged +=
SelectedEntityTypeStore_SelectedEntityTypeChanged;
@@ -101,15 +118,34 @@ internal class DbEntityListViewModel : ViewModelBase
return;
}
- Query();
+ Page = 1;
+ Reload();
}
+ #region "Методы кнопок"
+
+ public void FirstPage() => Page = 1;
+
+ public void PreviousPage() => Page--;
+
+ public void NextPage() => Page++;
+
+ public void LastPage() => Page = PageCount;
+
+ public bool HasPrevious => Page != 1;
+
+ public bool HasNext => Page != PageCount;
+
+ #endregion
+
+ #region "Запросы"
+
///
/// Поток, в котором будет выполняться запрос к базе данных.
///
private Thread? _queryThread;
- private void Query()
+ public void Reload()
{
Abort();
_queryThread = new(PerformQuery);
@@ -143,13 +179,17 @@ internal class DbEntityListViewModel : ViewModelBase
e.Type == SelectedEntityType && Pf2eDbContext.LocaleSearch(e.Name, Search)
);
- Page = 1;
EntitiesCount = query.Count();
- Entities = query.OrderBy(e => e.Id).Skip((Page - 1) * PerPage).Take(PerPage).ToList();
+ Entities =
+ [
+ .. query.OrderBy(e => e.Id).Skip((Page - 1) * PerPage).Take(PerPage).ToList(),
+ ];
}
catch (ThreadInterruptedException)
{
// Ignore
}
}
+
+ #endregion
}
diff --git a/Pf2eDataScrapper/WPF/ViewModels/EntityFormViewModel.cs b/Pf2eDataScrapper/WPF/ViewModels/EntityFormViewModel.cs
index 9c6e3bf..5784442 100644
--- a/Pf2eDataScrapper/WPF/ViewModels/EntityFormViewModel.cs
+++ b/Pf2eDataScrapper/WPF/ViewModels/EntityFormViewModel.cs
@@ -45,5 +45,7 @@ internal class EntityFormViewModel : ViewModelBase
{
EntityTypes = Context.Entities.Select(e => e.Type).Distinct().ToList();
OnPropertyChanged(nameof(EntityTypes));
+
+ SelectedEntityTypeStore.SelectedEntityType = EntityTypes.Any() ? EntityTypes.First() : null;
}
}
diff --git a/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/LadderType.cs b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/LadderType.cs
new file mode 100644
index 0000000..df98ab0
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/LadderType.cs
@@ -0,0 +1,22 @@
+namespace Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
+
+///
+/// Тип лестницы глубины.
+///
+internal enum LadderType
+{
+ ///
+ /// Пустой элемент: [ ].
+ ///
+ Empty,
+
+ ///
+ /// Последний элемент: [└].
+ ///
+ Last,
+
+ ///
+ /// Проходной элемент: [├] или [│].
+ ///
+ Through,
+}
\ No newline at end of file
diff --git a/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureBuilder.cs b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureBuilder.cs
new file mode 100644
index 0000000..c0381f1
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureBuilder.cs
@@ -0,0 +1,141 @@
+namespace Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
+
+///
+/// Класс для построения структуры данных класса.
+///
+internal class StructureBuilder
+{
+ ///
+ /// Список обработанных объектов.
+ ///
+ private readonly List _processedObjects = [];
+
+ ///
+ /// Создание поля структуры.
+ ///
+ /// Название поля.
+ /// Значение поля.
+ /// Глубина, на котором поле отображается.
+ /// Поле структуры.
+ public StructureFieldComplex Create(string name, object value, int depth = 0)
+ {
+ _ = RegisterProcessed(value);
+
+ StructureFieldComplex field = new(this, value, name, depth);
+
+ CompleteProcessed(value, field);
+
+ return field;
+ }
+
+ ///
+ /// Регистрация обработанного объекта.
+ ///
+ /// Регистрируемый объект.
+ /// `true`, если объект был добавлен; `false`, если объект был добавлен ранее.
+ public bool RegisterProcessed(object value)
+ {
+ if (_processedObjects.Any(p => p.Value == value))
+ {
+ return false;
+ }
+
+ StructureProcessedObject processed = new(value);
+
+ _processedObjects.Add(processed);
+
+ return true;
+ }
+
+ ///
+ /// Дополнить обработанный объект.
+ ///
+ /// Значение.
+ /// Поле объекта.
+ public void CompleteProcessed(object value, StructureFieldComplex field)
+ {
+ StructureProcessedObject? q = _processedObjects.FirstOrDefault(p => p.Value == value);
+
+ if (q == null)
+ {
+ return;
+ }
+
+ q.Field = field;
+ }
+
+ ///
+ /// Выбор поля структуры по значению.
+ ///
+ /// Название поля.
+ /// Значение поля.
+ /// Глубина, на котором поле отображается.
+ ///
+ 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 StructureFieldReference(
+ this,
+ name,
+ null,
+ depth
+ );
+ processed.AddReference(reference);
+ return reference;
+ }
+
+ throw new("Impossible");
+ }
+
+ ///
+ /// Получить тип поля структуры по значению.
+ ///
+ /// Значение поля.
+ /// Тип поля структуры.
+ 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;
+ }
+}
diff --git a/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureField.cs b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureField.cs
new file mode 100644
index 0000000..c72f5e0
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureField.cs
@@ -0,0 +1,59 @@
+namespace Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
+
+///
+/// Базовый класс для полей структуры.
+///
+/// Класс построения структуры данных.
+/// Название поля.
+/// Глубина поля.
+internal abstract class StructureField(StructureBuilder builder, string name, int depth)
+{
+ ///
+ /// Название поля.
+ ///
+ public readonly string Name = name;
+
+ ///
+ /// Тип поля структуры.
+ ///
+ public abstract StructureFieldType FieldType { get; }
+
+ ///
+ /// Класс построения структуры данных.
+ ///
+ public readonly StructureBuilder Builder = builder;
+
+ ///
+ /// Флаг, указывающий, отображается ли поле.
+ ///
+ public virtual bool Visible { get; set; } = true;
+
+ ///
+ /// Глубина поля структуры.
+ ///
+ public readonly int Depth = depth;
+
+ ///
+ /// Получить строку, представляющую тип поля.
+ ///
+ /// Строка, представляющая тип поля.
+ public abstract string GetTypeString();
+
+ ///
+ /// Получить строку, представляющую значение поля.
+ ///
+ /// Строка, представляющая значение поля.
+ public abstract string GetValueString();
+
+ ///
+ /// Получить набор строк, используя результат функция.
+ ///
+ /// Тип результирующей строки.
+ /// Функция, оборажения поля в результирующую строку.
+ /// Коллекция, содержащая набор строк.
+ public virtual ICollection GetRows(Func func)
+ {
+ StructureField[] q = [this];
+ return q.Select(func).ToList();
+ }
+}
\ No newline at end of file
diff --git a/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureFieldComplex.cs b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureFieldComplex.cs
new file mode 100644
index 0000000..234e3bc
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureFieldComplex.cs
@@ -0,0 +1,211 @@
+using System.Collections;
+using System.Reflection;
+using LinqToDB.Extensions;
+using Pf2eModel.Model.Common.I18n;
+
+namespace Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
+
+///
+/// Поле стркутуры с объектом.
+///
+internal class StructureFieldComplex : StructureField
+{
+ ///
+ public override StructureFieldType FieldType => StructureFieldType.Complex;
+
+ ///
+ /// Набор полей объекта.
+ ///
+ public readonly List Fields = [];
+
+ ///
+ /// Тип значения поля.
+ ///
+ public readonly Type Type;
+
+ ///
+ /// Флаг, указывающий, отображается ли поля объекта.
+ ///
+ public bool Collapsed
+ {
+ get => _collapsed;
+ set
+ {
+ _collapsed = value;
+ UpdateFieldVisibility();
+ }
+ }
+ private bool _collapsed;
+
+ ///
+ public override bool Visible
+ {
+ get => _visible;
+ set
+ {
+ _visible = value;
+ UpdateFieldVisibility();
+ }
+ }
+ private bool _visible = true;
+
+ ///
+ /// Конструктор класса поля структуры с объектом.
+ ///
+ /// Класс построения структуры данных.
+ /// Значение поля.
+ /// Название поля.
+ /// Глубина поля.
+ public StructureFieldComplex(StructureBuilder builder, object value, string name, int depth)
+ : base(builder, name, depth)
+ {
+ Type = value.GetType();
+
+ SetFields((dynamic)value);
+ }
+
+ ///
+ /// Обновить видимость полей структуры.
+ ///
+ private void UpdateFieldVisibility()
+ {
+ foreach (StructureField field in Fields)
+ {
+ field.Visible = Visible && !Collapsed;
+ }
+ }
+
+ ///
+ /// Установить поля структуры `LocaleString`.
+ ///
+ /// Объект строки локализации.
+ private void SetFields(LocaleString localeString)
+ {
+ SetFieldsFromRecords(
+ localeString
+ .Select(field => new FieldRecord(field.Locale.ToString(), field.Value))
+ .ToList()
+ );
+ }
+
+ ///
+ /// Установить поля структуры `IEnumerable`.
+ ///
+ /// Объект-перечисление.
+ private void SetFields(IEnumerable enumerable)
+ {
+ int i = 0;
+
+ List records = [];
+
+ foreach (object? item in enumerable)
+ {
+ records.Add(new($"[{i++}]", item));
+ }
+
+ SetFieldsFromRecords(records);
+ }
+
+ ///
+ /// Установить поля структуры общего объекта.
+ ///
+ /// Объект.
+ private void SetFields(object value)
+ {
+ Type type = value.GetType();
+ PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
+
+ SetFieldsFromRecords(
+ properties
+ .Where(property =>
+ property.GetAttribute() is null
+ )
+ .Select(property => new FieldRecord(property.Name, property.GetValue(value, null)))
+ .ToList()
+ );
+ }
+
+ ///
+ /// Установить поля структуры из списка записей.
+ ///
+ ///
+ private void SetFieldsFromRecords(List 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)
+ );
+ }
+ }
+
+ ///
+ /// Запись поля структуры.
+ ///
+ /// Название поля.
+ /// Значение поля.
+ private class FieldRecord(string name, object? value)
+ {
+ ///
+ /// Название поля.
+ ///
+ public readonly string Name = name;
+
+ ///
+ /// Значение поля.
+ ///
+ public readonly object? Value = value;
+
+ ///
+ /// Тип поля структуры.
+ ///
+ public StructureFieldType FieldType => StructureBuilder.GetFieldType(Value);
+
+ ///
+ /// Флаг, указывающий, использовать ли поле.
+ ///
+ public bool Allowed = true;
+ }
+
+ ///
+ public override string GetTypeString()
+ {
+ if (Type.IsGenericEnumerableType())
+ {
+ Type elementType = Type.GetGenericArguments().Single();
+
+ return $"{elementType.Name}[]";
+ }
+ else
+ {
+ return Type.Name;
+ }
+ }
+
+ ///
+ public override string GetValueString()
+ {
+ if (Type.IsGenericEnumerableType())
+ {
+ return Fields.Count.ToString() + (Fields.Count == 1 ? " item" : " items");
+ }
+ return "";
+ }
+
+ ///
+ public override ICollection GetRows(Func func)
+ {
+ return [.. base.GetRows(func), .. Fields.SelectMany(f => f.GetRows(func)).ToList()];
+ }
+}
\ No newline at end of file
diff --git a/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureFieldEnum.cs b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureFieldEnum.cs
new file mode 100644
index 0000000..6622e44
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureFieldEnum.cs
@@ -0,0 +1,31 @@
+namespace Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
+
+///
+/// Поле структуры с перечислением.
+///
+/// Класс построения структуры данных.
+/// Значение поля.
+/// Название поля.
+/// Глубина поля.
+internal class StructureFieldEnum(StructureBuilder builder, object value, string name, int depth)
+ : StructureField(builder, name, depth)
+{
+ ///
+ public override StructureFieldType FieldType => StructureFieldType.Enum;
+
+ ///
+ /// Значение поля.
+ ///
+ public readonly object Value = value;
+
+ ///
+ /// Тип значения поля.
+ ///
+ public readonly Type Type = value.GetType();
+
+ ///
+ public override string GetTypeString() => "enum";
+
+ ///
+ public override string GetValueString() => $"{Type.Name}.{Value}";
+}
\ No newline at end of file
diff --git a/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureFieldNull.cs b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureFieldNull.cs
new file mode 100644
index 0000000..b808e4d
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureFieldNull.cs
@@ -0,0 +1,20 @@
+namespace Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
+
+///
+/// Пустое поле структуры.
+///
+/// Класс построения структуры данных.
+/// Название поля.
+/// Глубина поля.
+internal class StructureFieldNull(StructureBuilder builder, string name, int depth)
+ : StructureField(builder, name, depth)
+{
+ ///
+ public override StructureFieldType FieldType => StructureFieldType.Null;
+
+ ///
+ public override string GetTypeString() => "null";
+
+ ///
+ public override string GetValueString() => "";
+}
\ No newline at end of file
diff --git a/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureFieldPrimitive.cs b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureFieldPrimitive.cs
new file mode 100644
index 0000000..df64fe1
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureFieldPrimitive.cs
@@ -0,0 +1,53 @@
+namespace Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
+
+///
+/// Простое поле структуры.
+///
+/// Класс построения структуры данных.
+/// Значение поля.
+/// Название поля.
+/// Глубина поля.
+internal class StructureFieldPrimitive(
+ StructureBuilder builder,
+ object value,
+ string name,
+ int depth
+) : StructureField(builder, name, depth)
+{
+ ///
+ public override StructureFieldType FieldType => StructureFieldType.Primitive;
+
+ ///
+ /// Значение поля.
+ ///
+ public readonly object Value = value;
+
+ ///
+ /// Тип значения поля.
+ ///
+ public readonly Type Type = value.GetType();
+
+ ///
+ public override string GetTypeString()
+ {
+ return Type.GetTypeCode(Type) switch
+ {
+ TypeCode.Boolean => "bool",
+ TypeCode.Int32 => "int",
+ TypeCode.String => "string",
+ TypeCode.Double => "double",
+ _ => Type.Name,
+ };
+ }
+
+ ///
+ public override string GetValueString()
+ {
+ if (Type == typeof(string))
+ {
+ return "\"" + ((string)Value).Replace("\"", "\\\"") + "\"";
+ }
+
+ return Value.ToString() ?? string.Empty;
+ }
+}
\ No newline at end of file
diff --git a/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureFieldReference.cs b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureFieldReference.cs
new file mode 100644
index 0000000..1e3452c
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureFieldReference.cs
@@ -0,0 +1,30 @@
+namespace Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
+
+///
+/// Поле структуры с ссылкой на объект.
+///
+/// Класс построения структуры данных.
+/// Название поля.
+/// Ссылка на объект.
+/// Глубина поля.
+internal class StructureFieldReference(
+ StructureBuilder builder,
+ string name,
+ StructureFieldComplex? reference,
+ int depth
+) : StructureField(builder, name, depth)
+{
+ ///
+ /// Ссылка на объект.
+ ///
+ public StructureFieldComplex? Reference = reference;
+
+ ///
+ public override StructureFieldType FieldType => StructureFieldType.Reference;
+
+ ///
+ public override string GetTypeString() => Reference?.GetTypeString() ?? "???";
+
+ ///
+ public override string GetValueString() => "";
+}
diff --git a/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureFieldType.cs b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureFieldType.cs
new file mode 100644
index 0000000..e7de33a
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureFieldType.cs
@@ -0,0 +1,32 @@
+namespace Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
+
+///
+/// Тип поля структуры.
+///
+internal enum StructureFieldType
+{
+ ///
+ /// Пустое поле.
+ ///
+ Null,
+
+ ///
+ /// Простое поле.
+ ///
+ Primitive,
+
+ ///
+ /// Поле с перечислением.
+ ///
+ Enum,
+
+ ///
+ /// Поле с объектом.
+ ///
+ Complex,
+
+ ///
+ /// Поле с ссылкой на объект.
+ ///
+ Reference,
+}
\ No newline at end of file
diff --git a/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureProcessedObject.cs b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureProcessedObject.cs
new file mode 100644
index 0000000..c25cc36
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureProcessedObject.cs
@@ -0,0 +1,37 @@
+namespace Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
+
+///
+/// Класс для хранения обработанного объекта.
+///
+/// Объект.
+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 _references = [];
+
+ public void AddReference(StructureFieldReference reference)
+ {
+ _references.Add(reference);
+
+ if (_field is not null)
+ {
+ reference.Reference = Field;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureRow.cs b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureRow.cs
new file mode 100644
index 0000000..28991e7
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/ViewModels/EntityViewStructure/StructureRow.cs
@@ -0,0 +1,177 @@
+using System.CodeDom;
+using System.Globalization;
+using System.Security.Cryptography;
+using System.Windows;
+using System.Windows.Data;
+using System.Windows.Media;
+using static System.Windows.Forms.VisualStyles.VisualStyleElement.Header;
+using Brush = System.Windows.Media.Brush;
+using Brushes = System.Windows.Media.Brushes;
+using Color = System.Windows.Media.Color;
+
+namespace Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
+
+internal class StructureRow(StructureField item)
+{
+ ///
+ /// Отображаемая строка поля сущности.
+ ///
+ public StructureField Item { get; } = item;
+
+ ///
+ /// Список типов лестницы.
+ ///
+ public List Ladder { get; set; } = [];
+
+ ///
+ /// Флаг наличия кнопки.
+ ///
+ public bool HasButton => Item.FieldType == StructureFieldType.Complex;
+
+ ///
+ /// Флаг, указывающий, отображается ли строка.
+ ///
+ public bool Visible => Item.Visible;
+
+ ///
+ /// Глубина отображения поля.
+ ///
+ public int Depth => Item.Depth;
+
+ ///
+ /// Строковое представление названия поля.
+ ///
+ public string Name => Item.Name;
+
+ ///
+ /// Строковое представление типа поля.
+ ///
+ public string Type
+ {
+ get
+ {
+ string result = Item.GetTypeString();
+ if (result != "")
+ {
+ result = $@"({result})";
+ }
+ return result;
+ }
+ }
+
+ ///
+ /// Строковое представление значения поля.
+ ///
+ public string Value => Item.GetValueString();
+}
+
+// [ValueConversion(typeof(IEnumerable), typeof(Image), ParameterType = typeof(int))]
+public class LadderToImageConverter : IMultiValueConverter
+{
+ #region IValueConverter Members
+
+
+ public object? Convert(
+ object[] value,
+ Type targetType,
+ object? parameter,
+ System.Globalization.CultureInfo culture
+ )
+ {
+ if (value.Length != 2)
+ {
+ return null;
+ }
+
+ IEnumerable ladder = (IEnumerable)value[0];
+
+ if (!ladder.Any())
+ {
+ return null;
+ }
+
+ int n = ladder.Count();
+ if (!int.TryParse((string)value[1], out int itemHeight))
+ {
+ return null;
+ }
+
+ DrawingGroup result = new();
+
+ // Rectangle
+
+ result.Children.Add(
+ new GeometryDrawing()
+ {
+ Geometry = new RectangleGeometry(new(0, 0, n * 100, 100)),
+ Pen = new(Brushes.Transparent, 0),
+ }
+ );
+
+ result.Children.Add(
+ new GeometryDrawing()
+ {
+ Geometry = GetLadderGeometryGroup(ladder, itemHeight),
+ Pen = new(Brushes.Black, 1),
+ }
+ );
+
+ return result;
+ }
+
+ private GeometryGroup GetLadderGeometryGroup(IEnumerable ladder, int h)
+ {
+ float h2 = h * 0.5f;
+ GeometryGroup geometryGroup = new();
+
+ //geometryGroup.Children.Add(new RectangleGeometry(new(0, 0, ladder.Count() * 100, 100)));
+
+ int i = 0;
+
+ foreach (LadderType ladderType in ladder)
+ {
+ switch (ladderType)
+ {
+ case LadderType.Empty:
+ break;
+ case LadderType.Last:
+ geometryGroup.Children.Add(
+ new LineGeometry(new(i * h + h2, 0), new(i * h + h2, h2))
+ );
+ geometryGroup.Children.Add(
+ new LineGeometry(new(i * h + h2, 50), new(i * h + h, h2))
+ );
+ break;
+ case LadderType.Through:
+ geometryGroup.Children.Add(
+ new LineGeometry(new(i * h + h2, 0), new(i * h + h2, h))
+ );
+ if (i == ladder.Count() - 1)
+ {
+ geometryGroup.Children.Add(
+ new LineGeometry(new(i * h + h2, h2), new(i * h + h2, h2))
+ );
+ }
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(ladder));
+ }
+
+ i++;
+ }
+
+ return geometryGroup;
+ }
+
+ public object[] ConvertBack(
+ object value,
+ Type[] targetTypes,
+ object parameter,
+ CultureInfo culture
+ )
+ {
+ throw new NotSupportedException();
+ }
+
+ #endregion
+}
diff --git a/Pf2eDataScrapper/WPF/ViewModels/EntityViewViewModel.cs b/Pf2eDataScrapper/WPF/ViewModels/EntityViewViewModel.cs
new file mode 100644
index 0000000..a89abd2
--- /dev/null
+++ b/Pf2eDataScrapper/WPF/ViewModels/EntityViewViewModel.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+using Pf2eDataScrapper.Forms.Controls.EntityTreeViewer;
+using Pf2eDataScrapper.Helpers;
+using Pf2eDataScrapper.WPF.Stores;
+using Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
+using Pf2eModel;
+
+namespace Pf2eDataScrapper.WPF.ViewModels;
+
+internal class EntityViewViewModel : ViewModelBase
+{
+ public Pf2eDbContext Context { get; }
+ public SelectedEntityStore SelectedEntityStore { get; }
+
+ public EntityView? CurrentEntityView
+ {
+ get => _currentEntityView;
+ set
+ {
+ _currentEntityView = value;
+ OnPropertyChanged();
+ }
+ }
+ private EntityView? _currentEntityView;
+
+ public ObservableCollection Rows
+ {
+ get => _rows;
+ set
+ {
+ _rows = value;
+ OnPropertyChanged();
+ }
+ }
+ private ObservableCollection _rows = [];
+
+ private readonly List _fields = [];
+
+ private readonly List _allRows = [];
+
+ public EntityViewViewModel(Pf2eDbContext context, SelectedEntityStore selectedEntityStore)
+ {
+ Context = context;
+ SelectedEntityStore = selectedEntityStore;
+
+ SelectedEntityStore.SelectedEntityTypeChanged +=
+ SelectedEntityStore_SelectedEntityTypeChanged;
+ }
+
+ ~EntityViewViewModel()
+ {
+ SelectedEntityStore.SelectedEntityTypeChanged -=
+ SelectedEntityStore_SelectedEntityTypeChanged;
+ }
+
+ private void SelectedEntityStore_SelectedEntityTypeChanged(Pf2eModel.Model.Base.Entity? entity)
+ {
+ if (entity is null)
+ {
+ CurrentEntityView = null;
+ return;
+ }
+
+ CurrentEntityView = new(Context, entity.Type, entity.Id);
+
+ if (CurrentEntityView.Entity is null)
+ {
+ CurrentEntityView = null;
+ return;
+ }
+
+ StructureBuilder builder = new();
+
+ _fields.Clear();
+ _fields.Add(builder.Create("Entity", CurrentEntityView.Entity));
+
+ if (CurrentEntityView.SubEntity is not null)
+ {
+ string typeName = CurrentEntityView.SubEntity.GetType().Name;
+
+ _fields.Add(builder.Create(typeName, CurrentEntityView.SubEntity));
+ }
+
+ _allRows.Clear();
+ _allRows.AddRange(
+ _fields.SelectMany(field =>
+ field.GetRows(structureField => new StructureRow(structureField))
+ )
+ );
+
+ Rows.Clear();
+ foreach (StructureRow row in _allRows)
+ {
+ Rows.Add(row);
+ }
+
+ BuildLadder();
+
+ OnPropertyChanged(nameof(Rows));
+ }
+
+ ///
+ /// Построение лестницы для строк полей структуры.
+ ///
+ /// Набор строк полей структуры.
+ private void BuildLadder()
+ {
+ List lastLadder = [];
+
+ for (int i = Rows.Count - 1; i >= 0; i--)
+ {
+ StructureRow row = Rows[i];
+ List ladder;
+
+ if (row.Depth == 0)
+ {
+ ladder = [];
+ }
+ else if (row.Depth < lastLadder.Count)
+ {
+ ladder =
+ [
+ .. lastLadder[..(row.Depth - 1)],
+ lastLadder[row.Depth - 1] == LadderType.Empty
+ ? LadderType.Last
+ : LadderType.Through,
+ ];
+ }
+ else if (row.Depth == lastLadder.Count)
+ {
+ ladder = lastLadder;
+ }
+ else
+ {
+ ladder =
+ [
+ .. lastLadder,
+ .. Enumerable.Repeat(LadderType.Empty, row.Depth - lastLadder.Count - 1),
+ LadderType.Last,
+ ];
+ }
+
+ row.Ladder = ladder;
+
+ lastLadder = ladder
+ .Select(l => (l == LadderType.Last) ? LadderType.Through : l)
+ .ToList();
+ }
+ }
+}
diff --git a/Pf2eDataScrapper/WPF/Views/EntityForm.xaml b/Pf2eDataScrapper/WPF/Views/EntityForm.xaml
index 3179c64..09e9a5a 100644
--- a/Pf2eDataScrapper/WPF/Views/EntityForm.xaml
+++ b/Pf2eDataScrapper/WPF/Views/EntityForm.xaml
@@ -8,25 +8,27 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Pf2eDataScrapper.WPF.ViewModels"
Title="EntityForm"
- Width="1000"
- Height="600"
+ Width="1400"
+ Height="800"
d:DataContext="{d:DesignInstance Type=viewModels:EntityFormViewModel}"
x:ClassModifier="internal"
+ Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"
+ Loaded="Window_Loaded"
mc:Ignorable="d">
-
+
-
+
-
+
-
+
-
+
-
+
+
+
()
.AddTransient()
+ .AddTransient()
// Stores
.AddSingleton()
.AddSingleton()
diff --git a/Pf2eModel/Migrations/20250515080518_1.Designer.cs b/Pf2eModel/Migrations/20250516162158_1.Designer.cs
similarity index 99%
rename from Pf2eModel/Migrations/20250515080518_1.Designer.cs
rename to Pf2eModel/Migrations/20250516162158_1.Designer.cs
index 6c9b483..ecb8d11 100644
--- a/Pf2eModel/Migrations/20250515080518_1.Designer.cs
+++ b/Pf2eModel/Migrations/20250516162158_1.Designer.cs
@@ -13,7 +13,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Migrations
{
[DbContext(typeof(Pf2eDbContext))]
- [Migration("20250515080518_1")]
+ [Migration("20250516162158_1")]
partial class _1
{
///
diff --git a/Pf2eModel/Migrations/20250515080518_1.cs b/Pf2eModel/Migrations/20250516162158_1.cs
similarity index 100%
rename from Pf2eModel/Migrations/20250515080518_1.cs
rename to Pf2eModel/Migrations/20250516162158_1.cs