Глобальное обновление #1

Merged
kozhilya merged 33 commits from dev into master 2025-06-01 22:28:15 +04:00
29 changed files with 1297 additions and 665 deletions
Showing only changes of commit 204238de4d - Show all commits
@@ -1,5 +1,6 @@
using System.ComponentModel;
using Pf2eDataScrapper.Helpers;
using Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
namespace Pf2eDataScrapper.Forms.Controls.EntityTreeViewer;
@@ -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;
@@ -1,627 +0,0 @@
using System.Collections;
using System.Reflection;
using LinqToDB.Extensions;
using Pf2eModel.Model.Common.I18n;
namespace Pf2eDataScrapper.Forms.Controls.EntityTreeViewer;
/// <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 StructureFieldReference(
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;
}
}
/// <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;
}
}
}
/// <summary>
/// Тип поля структуры.
/// </summary>
internal enum StructureFieldType
{
/// <summary>
/// Пустое поле.
/// </summary>
Null,
/// <summary>
/// Простое поле.
/// </summary>
Primitive,
/// <summary>
/// Поле с перечислением.
/// </summary>
Enum,
/// <summary>
/// Поле с объектом.
/// </summary>
Complex,
/// <summary>
/// Поле с ссылкой на объект.
/// </summary>
Reference,
}
/// <summary>
/// Тип лестницы глубины.
/// </summary>
internal enum LadderType
{
/// <summary>
/// Пустой элемент: [ ].
/// </summary>
Empty,
/// <summary>
/// Последний элемент: [└].
/// </summary>
Last,
/// <summary>
/// Проходной элемент: [├] или [│].
/// </summary>
Through,
}
/// <summary>
/// Базовый класс для полей структуры.
/// </summary>
/// <param name="builder">Класс построения структуры данных.</param>
/// <param name="name">Название поля.</param>
/// <param name="depth">Глубина поля.</param>
internal abstract class StructureField(StructureBuilder builder, string name, int depth)
{
/// <summary>
/// Название поля.
/// </summary>
public readonly string Name = name;
/// <summary>
/// Тип поля структуры.
/// </summary>
public abstract StructureFieldType FieldType { get; }
/// <summary>
/// Класс построения структуры данных.
/// </summary>
public readonly StructureBuilder Builder = builder;
/// <summary>
/// Флаг, указывающий, отображается ли поле.
/// </summary>
public virtual bool Visible { get; set; } = true;
/// <summary>
/// Глубина поля структуры.
/// </summary>
public readonly int Depth = 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>
/// <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="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>
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();
}
}
private bool _collapsed;
/// <inheritdoc/>
public override bool Visible
{
get => _visible;
set
{
_visible = value;
UpdateFieldVisibility();
}
}
private bool _visible = true;
/// <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);
SetFieldsFromRecords(
properties
.Where(property =>
property.GetAttribute<Newtonsoft.Json.JsonIgnoreAttribute>() is null
)
.Select(property => new FieldRecord(property.Name, property.GetValue(value, null)))
.ToList()
);
}
/// <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");
}
return "";
}
/// <inheritdoc/>
public override ICollection<T> GetRows<T>(Func<StructureField, T> func)
{
return [.. base.GetRows(func), .. Fields.SelectMany(f => f.GetRows(func)).ToList()];
}
}
/// <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>";
}
+10 -5
View File
@@ -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<StartForm>(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<ProgressFormWpf>(
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<EntityForm>(ServiceProvider);
_entityForm.WindowStartupLocation = WindowStartupLocation.CenterOwner;
_entityForm.WindowStartupLocation = WindowStartupLocation.CenterScreen;
_entityForm.Closing += Wpf_HandleFormClosing;
//_entityForm.ApplicationStateChange += ApplicationStateChange;
_entityForm.Show();
ElementHost.EnableModelessKeyboardInterop(_entityForm);
CurrentWpfForm = _entityForm;
break;
-2
View File
@@ -30,8 +30,6 @@ internal static class Program
ImportApplication app = ActivatorUtilities.CreateInstance<ImportApplication>(
serviceProvider
);
Application.Run(app);
}
private static IServiceCollection RegisterServices()
@@ -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 @@
<UserControl.Background>
<SolidColorBrush Color="{DynamicResource {x:Static SystemColors.ControlColorKey}}" />
</UserControl.Background>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="30" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
@@ -26,33 +28,48 @@
<Button
Width="25"
Margin="0,0,3,0"
Click="FirstButton_OnClick"
Content="&lt;&lt;"
DockPanel.Dock="Left" />
DockPanel.Dock="Left"
IsEnabled="{Binding HasPrevious}" />
<Button
Width="25"
Margin="0,0,3,0"
Click="PreviousButton_OnClick"
Content="&lt;"
DockPanel.Dock="Left" />
DockPanel.Dock="Left"
IsEnabled="{Binding HasPrevious}" />
<Button
Width="25"
Margin="3,0,0,0"
Click="LastButton_OnClick"
Content="&gt;&gt;"
DockPanel.Dock="Right" />
DockPanel.Dock="Right"
IsEnabled="{Binding HasNext}" />
<Button
Width="25"
Margin="3,0,0,0"
Click="NextButton_OnClick"
Content="&gt;"
DockPanel.Dock="Right" />
DockPanel.Dock="Right"
IsEnabled="{Binding HasNext}" />
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<Label VerticalContentAlignment="Center" Content="Страница" />
<TextBox
Width="30"
VerticalContentAlignment="Center"
Text="{Binding Page, StringFormat=\{0:D\}}" />
PreviewTextInput="TextBox_PreviewTextInput">
<TextBox.Text>
<Binding
Mode="TwoWay"
Path="Page"
UpdateSourceTrigger="PropertyChanged" />
</TextBox.Text>
</TextBox>
<Label VerticalContentAlignment="Center" Content="из" />
<Label VerticalContentAlignment="Center" Content="{Binding PageCount, Mode=OneWay}" />
@@ -62,6 +79,7 @@
<TextBox
Grid.Row="1"
Margin="0,3,0,3"
VerticalAlignment="Center"
Text="TextBox"
TextWrapping="Wrap" />
@@ -69,7 +87,8 @@
<ListView
Grid.Row="2"
d:ItemsSource="{d:SampleData ItemCount=50}"
ItemsSource="{Binding Entities}">
ItemsSource="{Binding Entities}"
SelectedItem="{Binding SelectedEntity}">
<ListView.View>
<GridView>
<GridViewColumn
@@ -1,4 +1,5 @@
using Pf2eDataScrapper.WPF.ViewModels;
using System.Windows;
using Pf2eDataScrapper.WPF.ViewModels;
namespace Pf2eDataScrapper.WPF.Components;
@@ -25,4 +26,32 @@ internal partial class DbEntityListControl : System.Windows.Controls.UserControl
}
//https://www.youtube.com/watch?v=54ZmhbpjBmg
private void FirstButton_OnClick(object sender, RoutedEventArgs e)
{
ViewModel?.FirstPage();
}
private void PreviousButton_OnClick(object sender, RoutedEventArgs e)
{
ViewModel?.PreviousPage();
}
private void NextButton_OnClick(object sender, RoutedEventArgs e)
{
ViewModel?.NextPage();
}
private void LastButton_OnClick(object sender, RoutedEventArgs e)
{
ViewModel?.LastPage();
}
private void TextBox_PreviewTextInput(
object sender,
System.Windows.Input.TextCompositionEventArgs e
)
{
e.Handled = !int.TryParse(e.Text, out int result);
}
}
@@ -0,0 +1,71 @@
<UserControl
x:Class="Pf2eDataScrapper.WPF.Components.EntityViewControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="clr-namespace:Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Pf2eDataScrapper.WPF.Components"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Pf2eDataScrapper.WPF.ViewModels"
d:DataContext="{d:DesignInstance Type=viewModels:EntityViewViewModel}"
d:DesignHeight="450"
d:DesignWidth="300"
x:ClassModifier="internal"
mc:Ignorable="d">
<UserControl.BorderBrush>
<SolidColorBrush Color="{DynamicResource {x:Static SystemColors.ActiveBorderColorKey}}" />
</UserControl.BorderBrush>
<UserControl.Background>
<SolidColorBrush Color="{DynamicResource {x:Static SystemColors.ControlColorKey}}" />
</UserControl.Background>
<UserControl.Resources>
<conv:LadderToImageConverter x:Key="LadderToImageConverter" />
<Style TargetType="{x:Type DockPanel}">
<Setter Property="Background" Value="{x:Static SystemColors.ControlBrush}" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{x:Static SystemColors.ControlLightBrush}" />
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<ScrollViewer>
<ItemsControl ItemsSource="{Binding Rows}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DockPanel Style="{StaticResource {x:Type DockPanel}}">
<DockPanel.Height>
<DynamicResource ResourceKey="ItemHeight" />
</DockPanel.Height>
<StackPanel Width="200" Orientation="Horizontal">
<Image>
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<MultiBinding Converter="{StaticResource LadderToImageConverter}" ConverterParameter="{Binding ItemHeight, RelativeSource={RelativeSource AncestorType=local:EntityViewControl}}">
<Binding Path="Depth" />
<Binding Path="ItemHeight" RelativeSource="{RelativeSource AncestorType=local:EntityViewControl}"></Binding>
<DynamicResource ResourceKey="ItemHeight" />
</MultiBinding>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
<TextBlock Text="{Binding Name}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Center" Text="{Binding Type}" />
<TextBlock HorizontalAlignment="Center" Text="{Binding Value}" />
</StackPanel>
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</UserControl>
@@ -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 "Поля элемента управления"
/// <summary>
/// Высота строки поля структуры.
/// </summary>
[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;
/// <summary>
/// Ширина отступа глубины.
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Category("Entity Render")]
[Description("The size of tab.")]
public int DepthSize { get; set; } = 20;
/// <summary>
/// Размер шрифта типа поля сущности.
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Category("Entity Render")]
[Description("The size of tab.")]
public float TypeFontSize { get; set; } = 9.5f;
/// <summary>
/// Размер кнопки плюс/минус.
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Category("Entity Render")]
[Description("The size of plus/minus button.")]
public int ButtonSize { get; set; } = 20;
/// <summary>
/// Ширина имени поля сущности.
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public int NameWidth { get; private set; } = 200;
#endregion
public EntityViewControl()
{
InitializeComponent();
Resources["ItemHeight"] = ItemHeight;
}
}
@@ -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);
}
}
@@ -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 "Запросы"
/// <summary>
/// Поток, в котором будет выполняться запрос к базе данных.
/// </summary>
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
}
@@ -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;
}
}
@@ -0,0 +1,22 @@
namespace Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
/// <summary>
/// Тип лестницы глубины.
/// </summary>
internal enum LadderType
{
/// <summary>
/// Пустой элемент: [ ].
/// </summary>
Empty,
/// <summary>
/// Последний элемент: [└].
/// </summary>
Last,
/// <summary>
/// Проходной элемент: [├] или [│].
/// </summary>
Through,
}
@@ -0,0 +1,141 @@
namespace Pf2eDataScrapper.WPF.ViewModels.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 StructureFieldReference(
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,59 @@
namespace Pf2eDataScrapper.WPF.ViewModels.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)
{
/// <summary>
/// Название поля.
/// </summary>
public readonly string Name = name;
/// <summary>
/// Тип поля структуры.
/// </summary>
public abstract StructureFieldType FieldType { get; }
/// <summary>
/// Класс построения структуры данных.
/// </summary>
public readonly StructureBuilder Builder = builder;
/// <summary>
/// Флаг, указывающий, отображается ли поле.
/// </summary>
public virtual bool Visible { get; set; } = true;
/// <summary>
/// Глубина поля структуры.
/// </summary>
public readonly int Depth = 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();
}
}
@@ -0,0 +1,211 @@
using System.Collections;
using System.Reflection;
using LinqToDB.Extensions;
using Pf2eModel.Model.Common.I18n;
namespace Pf2eDataScrapper.WPF.ViewModels.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();
}
}
private bool _collapsed;
/// <inheritdoc/>
public override bool Visible
{
get => _visible;
set
{
_visible = value;
UpdateFieldVisibility();
}
}
private bool _visible = true;
/// <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);
SetFieldsFromRecords(
properties
.Where(property =>
property.GetAttribute<Newtonsoft.Json.JsonIgnoreAttribute>() is null
)
.Select(property => new FieldRecord(property.Name, property.GetValue(value, null)))
.ToList()
);
}
/// <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");
}
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,31 @@
namespace Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
/// <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}";
}
@@ -0,0 +1,20 @@
namespace Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
/// <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() => "";
}
@@ -0,0 +1,53 @@
namespace Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
/// <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;
}
}
@@ -0,0 +1,30 @@
namespace Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
/// <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>";
}
@@ -0,0 +1,32 @@
namespace Pf2eDataScrapper.WPF.ViewModels.EntityViewStructure;
/// <summary>
/// Тип поля структуры.
/// </summary>
internal enum StructureFieldType
{
/// <summary>
/// Пустое поле.
/// </summary>
Null,
/// <summary>
/// Простое поле.
/// </summary>
Primitive,
/// <summary>
/// Поле с перечислением.
/// </summary>
Enum,
/// <summary>
/// Поле с объектом.
/// </summary>
Complex,
/// <summary>
/// Поле с ссылкой на объект.
/// </summary>
Reference,
}
@@ -0,0 +1,37 @@
namespace Pf2eDataScrapper.WPF.ViewModels.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,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)
{
/// <summary>
/// Отображаемая строка поля сущности.
/// </summary>
public StructureField Item { get; } = item;
/// <summary>
/// Список типов лестницы.
/// </summary>
public List<LadderType> Ladder { get; set; } = [];
/// <summary>
/// Флаг наличия кнопки.
/// </summary>
public bool HasButton => Item.FieldType == StructureFieldType.Complex;
/// <summary>
/// Флаг, указывающий, отображается ли строка.
/// </summary>
public bool Visible => Item.Visible;
/// <summary>
/// Глубина отображения поля.
/// </summary>
public int Depth => Item.Depth;
/// <summary>
/// Строковое представление названия поля.
/// </summary>
public string Name => Item.Name;
/// <summary>
/// Строковое представление типа поля.
/// </summary>
public string Type
{
get
{
string result = Item.GetTypeString();
if (result != "")
{
result = $@"({result})";
}
return result;
}
}
/// <summary>
/// Строковое представление значения поля.
/// </summary>
public string Value => Item.GetValueString();
}
// [ValueConversion(typeof(IEnumerable<LadderType>), 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<LadderType> ladder = (IEnumerable<LadderType>)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<LadderType> 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
}
@@ -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<StructureRow> Rows
{
get => _rows;
set
{
_rows = value;
OnPropertyChanged();
}
}
private ObservableCollection<StructureRow> _rows = [];
private readonly List<StructureField> _fields = [];
private readonly List<StructureRow> _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));
}
/// <summary>
/// Построение лестницы для строк полей структуры.
/// </summary>
/// <param name="rows">Набор строк полей структуры.</param>
private void BuildLadder()
{
List<LadderType> lastLadder = [];
for (int i = Rows.Count - 1; i >= 0; i--)
{
StructureRow row = Rows[i];
List<LadderType> 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();
}
}
}
+23 -8
View File
@@ -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">
<Grid>
<Grid ClipToBounds="True">
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="150" />
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="215*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="537*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="322*" />
</Grid.ColumnDefinitions>
<GridSplitter
Grid.Row="0"
@@ -52,11 +54,24 @@
x:Name="EntityTypeListBox"
Grid.Row="1"
Grid.Column="0"
Margin="3,3,3,0"
d:ItemsSource="{d:SampleData ItemCount=10}"
ItemsSource="{Binding EntityTypes}"
SelectedItem="{Binding SelectedEntityType}" />
<components:DbEntityListControl x:Name="DbEntityListControl" Grid.Row="2" Grid.Column="0" />
<components:DbEntityListControl
x:Name="DbEntityListControl"
Grid.Row="2"
Grid.RowSpan="2"
Grid.Column="0"
ClipToBounds="True" />
<components:EntityViewControl
x:Name="EntityViewControl"
Grid.Row="0"
Grid.RowSpan="4"
Grid.Column="2"
FontFamily="Consolas" />
<TextBox
Grid.Row="0"
+11 -2
View File
@@ -1,5 +1,6 @@
using System.Collections.ObjectModel;
using System.Windows;
using Pf2eDataScrapper.WPF.Components;
using Pf2eDataScrapper.WPF.ViewModels;
using Pf2eModel;
using Pf2eModel.Model.Enum;
@@ -17,13 +18,21 @@ internal partial class EntityForm : Window
public EntityForm(
Pf2eDbContext context,
EntityFormViewModel viewModel,
DbEntityListViewModel dbEntityListViewModel
DbEntityListViewModel dbEntityListViewModel,
EntityViewViewModel entityViewViewModel
)
{
Context = context;
ViewModel = viewModel;
DataContext = ViewModel;
InitializeComponent();
DbEntityListControl.ViewModel = dbEntityListViewModel;
EntityViewControl.ViewModel = entityViewViewModel;
DataContext = ViewModel;
dbEntityListViewModel.Reload();
}
private void Window_Loaded(object sender, RoutedEventArgs e) { }
}
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Pf2eDataScrapper.WPF.Components;
using Pf2eDataScrapper.WPF.Stores;
using Pf2eDataScrapper.WPF.ViewModels;
@@ -12,6 +13,7 @@ public static class WpfServiceProviderExtension
// ViewModels
.AddTransient<EntityFormViewModel>()
.AddTransient<DbEntityListViewModel>()
.AddTransient<EntityViewViewModel>()
// Stores
.AddSingleton<SelectedEntityTypeStore>()
.AddSingleton<SelectedEntityStore>()
@@ -13,7 +13,7 @@ using Pf2eModel.Model.Enum;
namespace Pf2eModel.Migrations
{
[DbContext(typeof(Pf2eDbContext))]
[Migration("20250515080518_1")]
[Migration("20250516162158_1")]
partial class _1
{
/// <inheritdoc />