Продолжение рефакторинга (вынесение WPF в отдельный проект)
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
<UserControl
|
||||
x:Class="Pf2e.WPF.EntityViewer.Component.EntityListControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:viewmodel="clr-namespace:Pf2e.WPF.EntityViewer.ViewModel"
|
||||
Padding="3,3,3,3"
|
||||
d:DataContext="{d:DesignInstance Type=viewmodel:EntityListViewModel}"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="400"
|
||||
x:ClassModifier="internal"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Background>
|
||||
<SolidColorBrush Color="{DynamicResource {x:Static SystemColors.ControlColorKey}}" />
|
||||
</UserControl.Background>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<DockPanel Grid.Row="0" Grid.Column="0">
|
||||
|
||||
<Button
|
||||
Width="25"
|
||||
Margin="0,0,3,0"
|
||||
Command="{Binding FirstPageCommand}"
|
||||
Content="<<"
|
||||
DockPanel.Dock="Left"
|
||||
IsEnabled="{Binding HasPrevious}" />
|
||||
|
||||
<Button
|
||||
Width="25"
|
||||
Margin="0,0,3,0"
|
||||
Command="{Binding PreviousPageCommand}"
|
||||
Content="<"
|
||||
DockPanel.Dock="Left"
|
||||
IsEnabled="{Binding HasPrevious}" />
|
||||
|
||||
<Button
|
||||
Width="25"
|
||||
Margin="3,0,0,0"
|
||||
Command="{Binding LastPageCommand}"
|
||||
Content=">>"
|
||||
DockPanel.Dock="Right"
|
||||
IsEnabled="{Binding HasNext}" />
|
||||
|
||||
<Button
|
||||
Width="25"
|
||||
Margin="3,0,0,0"
|
||||
Command="{Binding NextPageCommand}"
|
||||
Content=">"
|
||||
DockPanel.Dock="Right"
|
||||
IsEnabled="{Binding HasNext}" />
|
||||
|
||||
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
|
||||
<Label VerticalContentAlignment="Center" Content="Страница" />
|
||||
<TextBox
|
||||
Width="30"
|
||||
VerticalContentAlignment="Center"
|
||||
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}" />
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</DockPanel>
|
||||
|
||||
<TextBox
|
||||
Grid.Row="1"
|
||||
Margin="0,3,0,3"
|
||||
VerticalAlignment="Center"
|
||||
Text="TextBox"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<ListView
|
||||
Grid.Row="2"
|
||||
d:ItemsSource="{d:SampleData ItemCount=50}"
|
||||
ItemsSource="{Binding Entities}"
|
||||
SelectedItem="{Binding SelectedEntity}">
|
||||
<ListView.View>
|
||||
<GridView>
|
||||
<GridViewColumn
|
||||
Width="Auto"
|
||||
DisplayMemberBinding="{Binding Id}"
|
||||
Header="ID" />
|
||||
<GridViewColumn DisplayMemberBinding="{Binding Name}" Header="Название" />
|
||||
<GridViewColumn
|
||||
Width="Auto"
|
||||
DisplayMemberBinding="{Binding Rarity}"
|
||||
Header="Редк." />
|
||||
</GridView>
|
||||
</ListView.View>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace Pf2e.WPF.EntityViewer.Component;
|
||||
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для DbEntityListControl.xaml
|
||||
/// </summary>
|
||||
internal partial class EntityListControl : System.Windows.Controls.UserControl
|
||||
{
|
||||
public EntityListControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void TextBox_PreviewTextInput(
|
||||
object sender,
|
||||
System.Windows.Input.TextCompositionEventArgs e
|
||||
)
|
||||
{
|
||||
e.Handled = !int.TryParse(e.Text, out int result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<UserControl
|
||||
x:Class="Pf2e.WPF.EntityViewer.Component.ViewControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:component="clr-namespace:Pf2e.WPF.EntityViewer.Component"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:fa="http://schemas.awesome.incremented/wpf/xaml/fontawesome.sharp"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:struct="clr-namespace:Pf2e.WPF.EntityViewer.EntityViewStructure"
|
||||
xmlns:viewmodel="clr-namespace:Pf2e.WPF.EntityViewer.ViewModel"
|
||||
x:Name="_this"
|
||||
d:DataContext="{d:DesignInstance Type=viewmodel:ViewViewModel}"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="300"
|
||||
x:ClassModifier="internal"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<FontFamily x:Key="FontAwesome">/FontAwesome.Sharp;component/fonts/#Font Awesome 6 Free Solid</FontFamily>
|
||||
|
||||
<struct:ColorAdapterConverter x:Key="ColorAdapterConverter" />
|
||||
|
||||
<Style x:Key="ButtonStyle" TargetType="Button">
|
||||
<Setter Property="FontSize" Value="{Binding ButtonSize, ElementName=_this}" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
<Setter Property="Width" Value="{Binding ButtonSize, ElementName=_this}" />
|
||||
<Setter Property="Height" Value="{Binding ButtonSize, ElementName=_this}" />
|
||||
<Setter Property="BorderThickness" Value="2,2,1,1" />
|
||||
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="{DynamicResource {x:Static SystemColors.ControlColorKey}}" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="BorderBrush">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="{DynamicResource {x:Static SystemColors.ControlColorKey}}" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
<Setter Property="Foreground" Value="Black" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Foreground" Value="DarkSlateGray" />
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="Transparent" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="BorderBrush">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="Transparent" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="FaButtonStyle"
|
||||
BasedOn="{StaticResource ButtonStyle}"
|
||||
TargetType="Button">
|
||||
<Setter Property="FontFamily" Value="{StaticResource FontAwesome}" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="HoverRow" TargetType="Grid">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlDarkBrush}}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<UserControl.BorderBrush>
|
||||
<SolidColorBrush Color="{DynamicResource {x:Static SystemColors.ActiveBorderColorKey}}" />
|
||||
</UserControl.BorderBrush>
|
||||
|
||||
<UserControl.Background>
|
||||
<SolidColorBrush Color="{DynamicResource {x:Static SystemColors.ControlColorKey}}" />
|
||||
</UserControl.Background>
|
||||
|
||||
<ScrollViewer>
|
||||
<ItemsControl AlternationCount="2" ItemsSource="{Binding Rows}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Grid.IsSharedSizeScope="True" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid
|
||||
x:Name="FooBar"
|
||||
Height="{Binding ItemHeight, ElementName=_this}"
|
||||
Visibility="{Binding ItemVisibility}">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition SharedSizeGroup="ColumnOne" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||
<component:ViewLadder
|
||||
Width="65"
|
||||
Height="{Binding ItemHeight, ElementName=_this}"
|
||||
DepthSize="{Binding DepthSize, ElementName=_this}"
|
||||
Ladder="{Binding Ladder}" />
|
||||
|
||||
<Button
|
||||
Margin="-20,0,0,0"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding CollapseCommand}"
|
||||
Style="{StaticResource FaButtonStyle}"
|
||||
Visibility="{Binding ButtonVisibility}">
|
||||
<TextBlock Margin="-3,-3,0,0" Text="{Binding ButtonIcon}" />
|
||||
</Button>
|
||||
|
||||
<TextBlock
|
||||
Margin="2,0,10,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Name}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Margin="0,0,5,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="{Binding TypeFontSize, ElementName=_this}"
|
||||
FontStyle="Italic"
|
||||
Text="{Binding Type}">
|
||||
<TextBlock.Foreground>
|
||||
<MultiBinding Converter="{StaticResource ColorAdapterConverter}">
|
||||
<Binding Path="AccentColor" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Foreground>
|
||||
</TextBlock>
|
||||
<TextBox
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding Value, Mode=OneWay}">
|
||||
<TextBox.Foreground>
|
||||
<MultiBinding Converter="{StaticResource ColorAdapterConverter}">
|
||||
<Binding Path="AccentColor" />
|
||||
</MultiBinding>
|
||||
</TextBox.Foreground>
|
||||
</TextBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<DataTemplate.Triggers>
|
||||
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
|
||||
<Setter TargetName="FooBar" Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlDarkBrush}}" />
|
||||
</Trigger>
|
||||
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
|
||||
<Setter TargetName="FooBar" Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrush}}" />
|
||||
</Trigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,102 @@
|
||||
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 Pf2e.Scrapper.Helpers;
|
||||
|
||||
namespace Pf2e.WPF.EntityViewer.Component;
|
||||
|
||||
internal partial class ViewControl : UserControl
|
||||
{
|
||||
#region "Поля элемента управления"
|
||||
|
||||
/// <summary>
|
||||
/// Высота строки поля структуры.
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
|
||||
[Category("Entity Render")]
|
||||
[Description("The height of each item in the tree view.")]
|
||||
public double ItemHeight
|
||||
{
|
||||
get { return (double)GetValue(ItemHeightProperty); }
|
||||
set { SetValue(ItemHeightProperty, value); }
|
||||
}
|
||||
public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register(
|
||||
"ItemHeight",
|
||||
typeof(double),
|
||||
typeof(ViewControl),
|
||||
new PropertyMetadata(20.0)
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Ширина отступа глубины.
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
|
||||
[Category("Entity Render")]
|
||||
[Description("The size of tab.")]
|
||||
public double DepthSize
|
||||
{
|
||||
get { return (double)GetValue(DepthSizeProperty); }
|
||||
set { SetValue(DepthSizeProperty, value); }
|
||||
}
|
||||
public static readonly DependencyProperty DepthSizeProperty = DependencyProperty.Register(
|
||||
"DepthSize",
|
||||
typeof(double),
|
||||
typeof(ViewControl),
|
||||
new PropertyMetadata(20.0)
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Размер шрифта типа поля сущности.
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
|
||||
[Category("Entity Render")]
|
||||
[Description("The size of tab.")]
|
||||
public double TypeFontSize
|
||||
{
|
||||
get { return (double)GetValue(TypeFontSizeProperty); }
|
||||
set { SetValue(TypeFontSizeProperty, value); }
|
||||
}
|
||||
public static readonly DependencyProperty TypeFontSizeProperty = DependencyProperty.Register(
|
||||
"TypeFontSize",
|
||||
typeof(double),
|
||||
typeof(ViewControl),
|
||||
new PropertyMetadata(9.5)
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Размер кнопки плюс/минус.
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
|
||||
[Category("Entity Render")]
|
||||
[Description("The size of plus/minus button.")]
|
||||
public double ButtonSize
|
||||
{
|
||||
get { return (double)GetValue(ButtonSizeProperty); }
|
||||
set { SetValue(ButtonSizeProperty, value); }
|
||||
}
|
||||
public static readonly DependencyProperty ButtonSizeProperty = DependencyProperty.Register(
|
||||
"ButtonSize",
|
||||
typeof(double),
|
||||
typeof(ViewControl),
|
||||
new PropertyMetadata(12.0)
|
||||
);
|
||||
|
||||
#endregion
|
||||
|
||||
public ViewControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<UserControl
|
||||
x:Class="Pf2e.WPF.EntityViewer.Component.ViewLadder"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Pf2e.WPF.EntityViewer.Component"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
d:DesignHeight="20"
|
||||
d:DesignWidth="60"
|
||||
x:ClassModifier="internal"
|
||||
mc:Ignorable="d">
|
||||
<Canvas x:Name="TargetCanvas" />
|
||||
</UserControl>
|
||||
@@ -0,0 +1,143 @@
|
||||
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 Pf2e.WPF.EntityViewer;
|
||||
using Pf2e.WPF.EntityViewer.EntityViewStructure;
|
||||
|
||||
namespace Pf2e.WPF.EntityViewer.Component;
|
||||
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для ViewLadder.xaml
|
||||
/// </summary>
|
||||
internal partial class ViewLadder : UserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Ширина отступа глубины.
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
|
||||
[Category("Entity Render")]
|
||||
[Description("The size of tab.")]
|
||||
public double DepthSize
|
||||
{
|
||||
get { return (double)GetValue(DepthSizeProperty); }
|
||||
set { SetValue(DepthSizeProperty, value); }
|
||||
}
|
||||
public static readonly DependencyProperty DepthSizeProperty = DependencyProperty.Register(
|
||||
"DepthSize",
|
||||
typeof(double),
|
||||
typeof(ViewLadder),
|
||||
new FrameworkPropertyMetadata(20.0, new PropertyChangedCallback(OnValueChanged))
|
||||
);
|
||||
|
||||
public IEnumerable<LadderType> Ladder
|
||||
{
|
||||
get { return (IEnumerable<LadderType>)GetValue(LadderProperty); }
|
||||
set { SetValue(LadderProperty, value); }
|
||||
}
|
||||
public static readonly DependencyProperty LadderProperty = DependencyProperty.Register(
|
||||
"Ladder",
|
||||
typeof(IEnumerable<LadderType>),
|
||||
typeof(ViewLadder),
|
||||
new FrameworkPropertyMetadata(
|
||||
Array.Empty<LadderType>(),
|
||||
new PropertyChangedCallback(OnValueChanged)
|
||||
)
|
||||
);
|
||||
|
||||
private static void OnValueChanged(
|
||||
DependencyObject obj,
|
||||
DependencyPropertyChangedEventArgs args
|
||||
)
|
||||
{
|
||||
ViewLadder control = (ViewLadder)obj;
|
||||
control.Rebuild();
|
||||
}
|
||||
|
||||
public ViewLadder()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void Rebuild()
|
||||
{
|
||||
int n = Ladder.Count();
|
||||
Width = n * DepthSize;
|
||||
|
||||
DrawingGroup result = new();
|
||||
|
||||
result.Children.Add(
|
||||
new GeometryDrawing()
|
||||
{
|
||||
Geometry = new RectangleGeometry(new(0, 0, n * DepthSize, Height)),
|
||||
Pen = new(Brushes.Transparent, 0),
|
||||
}
|
||||
);
|
||||
|
||||
result.Children.Add(
|
||||
new GeometryDrawing()
|
||||
{
|
||||
Geometry = GetLadderGeometryGroup(Ladder, DepthSize, Height),
|
||||
Pen = new(Brushes.Black, 1),
|
||||
}
|
||||
);
|
||||
|
||||
TargetCanvas.Background = new DrawingBrush(result);
|
||||
}
|
||||
|
||||
private GeometryGroup GetLadderGeometryGroup(IEnumerable<LadderType> ladder, double w, double h)
|
||||
{
|
||||
GeometryGroup geometryGroup = new();
|
||||
double w2 = w / 2;
|
||||
double h2 = h / 2;
|
||||
int i = 0;
|
||||
|
||||
foreach (LadderType ladderType in ladder)
|
||||
{
|
||||
switch (ladderType)
|
||||
{
|
||||
case LadderType.Empty:
|
||||
break;
|
||||
case LadderType.Last:
|
||||
geometryGroup.Children.Add(
|
||||
new LineGeometry(new(i * w + w2, 0), new(i * w + w2, h2))
|
||||
);
|
||||
geometryGroup.Children.Add(
|
||||
new LineGeometry(new(i * w + w2, h2), new(i * w + w, h2))
|
||||
);
|
||||
break;
|
||||
|
||||
case LadderType.Through:
|
||||
geometryGroup.Children.Add(
|
||||
new LineGeometry(new(i * w + w2, 0), new(i * w + w2, h))
|
||||
);
|
||||
|
||||
if (i == ladder.Count() - 1)
|
||||
{
|
||||
geometryGroup.Children.Add(
|
||||
new LineGeometry(new(i * w + w2, h2), new(i * w + w, h2))
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(ladder));
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return geometryGroup;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<Window
|
||||
x:Class="Pf2e.WPF.EntityViewer.EntityForm"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:component="clr-namespace:Pf2e.WPF.EntityViewer.Component"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:viewmodel="clr-namespace:Pf2e.WPF.EntityViewer.ViewModel"
|
||||
Title="EntityForm"
|
||||
Width="1400"
|
||||
Height="800"
|
||||
d:DataContext="{d:DesignInstance Type=viewmodel:EntityFormViewModel}"
|
||||
x:ClassModifier="internal"
|
||||
Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"
|
||||
Loaded="Window_Loaded"
|
||||
mc:Ignorable="d">
|
||||
<Grid ClipToBounds="True">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="150" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="215*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="537*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="322*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<GridSplitter
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="4"
|
||||
Grid.Column="1"
|
||||
Width="5"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<GridSplitter
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="4"
|
||||
Grid.Column="3"
|
||||
Width="5"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<GridSplitter
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Height="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
Cursor="SizeNS"
|
||||
ResizeDirection="Rows" />
|
||||
|
||||
<Button
|
||||
Margin="3,4,3,0"
|
||||
Click="Button_Click"
|
||||
Content="<<< Вернуться в начало" />
|
||||
|
||||
<ListBox
|
||||
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}" />
|
||||
|
||||
<component:EntityListControl
|
||||
x:Name="EntityListControl"
|
||||
Grid.Row="2"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="0"
|
||||
ClipToBounds="True" />
|
||||
|
||||
<component:ViewControl
|
||||
x:Name="ViewControl"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="4"
|
||||
Grid.Column="2"
|
||||
FontFamily="Consolas" />
|
||||
|
||||
<TextBox
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="4"
|
||||
Grid.Column="4"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding SelectedEntityDescription, Mode=OneWay}"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Scrapper.WPF.EntityViewer.ViewModel;
|
||||
|
||||
namespace Pf2e.WPF.EntityViewer;
|
||||
|
||||
internal partial class EntityForm : Window
|
||||
{
|
||||
public Pf2eDbContext Context { get; }
|
||||
public EntityFormViewModel ViewModel { get; }
|
||||
|
||||
public EntityForm(Pf2eDbContext context, EntityFormViewModel viewModel)
|
||||
{
|
||||
Context = context;
|
||||
ViewModel = viewModel;
|
||||
InitializeComponent();
|
||||
|
||||
EntityListControl.DataContext = ViewModel.DbEntityListViewModel;
|
||||
ViewControl.DataContext = ViewModel.ViewViewModel;
|
||||
|
||||
DataContext = ViewModel;
|
||||
|
||||
ViewModel.DbEntityListViewModel.Reload();
|
||||
}
|
||||
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e) { }
|
||||
|
||||
private void Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ApplicationStateChange?.Invoke(this, ApplicationStage.InitialSettings);
|
||||
}
|
||||
|
||||
public event ImportApplication.ApplicationStateChangeDelegate? ApplicationStateChange;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Pf2e.WPF.EntityViewer.EntityViewStructure;
|
||||
|
||||
/// <summary>
|
||||
/// Тип лестницы глубины.
|
||||
/// </summary>
|
||||
internal enum LadderType
|
||||
{
|
||||
/// <summary>
|
||||
/// Пустой элемент: [ ].
|
||||
/// </summary>
|
||||
Empty,
|
||||
|
||||
/// <summary>
|
||||
/// Последний элемент: [└].
|
||||
/// </summary>
|
||||
Last,
|
||||
|
||||
/// <summary>
|
||||
/// Проходной элемент: [├] или [│].
|
||||
/// </summary>
|
||||
Through,
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Pf2e.WPF.EntityViewer.EntityViewStructure;
|
||||
|
||||
/// <summary>
|
||||
/// Класс для построения структуры данных класса.
|
||||
/// </summary>
|
||||
internal class StructureBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Список обработанных объектов.
|
||||
/// </summary>
|
||||
private readonly List<StructureProcessedObject> _processedObjects = [];
|
||||
|
||||
/// <summary>
|
||||
/// Создание поля структуры.
|
||||
/// </summary>
|
||||
/// <param name="name">Название поля.</param>
|
||||
/// <param name="value">Значение поля.</param>
|
||||
/// <param name="depth">Глубина, на котором поле отображается.</param>
|
||||
/// <returns>Поле структуры.</returns>
|
||||
public StructureFieldComplex Create(string name, object value, int depth = 0)
|
||||
{
|
||||
_ = RegisterProcessed(value);
|
||||
|
||||
StructureFieldComplex field = new(this, value, name, depth);
|
||||
|
||||
CompleteProcessed(value, field);
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Регистрация обработанного объекта.
|
||||
/// </summary>
|
||||
/// <param name="value">Регистрируемый объект.</param>
|
||||
/// <returns>`true`, если объект был добавлен; `false`, если объект был добавлен ранее.</returns>
|
||||
public bool RegisterProcessed(object value)
|
||||
{
|
||||
if (_processedObjects.Any(p => p.Value == value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
StructureProcessedObject processed = new(value);
|
||||
|
||||
_processedObjects.Add(processed);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Дополнить обработанный объект.
|
||||
/// </summary>
|
||||
/// <param name="value">Значение.</param>
|
||||
/// <param name="field">Поле объекта.</param>
|
||||
public void CompleteProcessed(object value, StructureFieldComplex field)
|
||||
{
|
||||
StructureProcessedObject? q = _processedObjects.FirstOrDefault(p => p.Value == value);
|
||||
|
||||
if (q == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
q.Field = field;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выбор поля структуры по значению.
|
||||
/// </summary>
|
||||
/// <param name="name">Название поля.</param>
|
||||
/// <param name="value">Значение поля.</param>
|
||||
/// <param name="depth">Глубина, на котором поле отображается.</param>
|
||||
/// <returns></returns>
|
||||
public StructureField Resolve(string name, object? value, int depth)
|
||||
{
|
||||
switch (GetFieldType(value))
|
||||
{
|
||||
case StructureFieldType.Null:
|
||||
return new StructureFieldNull(this, name, depth);
|
||||
|
||||
case StructureFieldType.Primitive:
|
||||
return new StructureFieldPrimitive(this, value!, name, depth);
|
||||
|
||||
case StructureFieldType.Enum:
|
||||
return new StructureFieldEnum(this, value!, name, depth);
|
||||
|
||||
case StructureFieldType.Reference:
|
||||
break;
|
||||
|
||||
case StructureFieldType.Complex:
|
||||
default:
|
||||
|
||||
StructureProcessedObject? processed = _processedObjects.FirstOrDefault(p =>
|
||||
p.Value == value
|
||||
);
|
||||
|
||||
if (processed is null)
|
||||
{
|
||||
return Create(name, value!, depth);
|
||||
}
|
||||
|
||||
StructureFieldReference reference = new(this, name, null, depth);
|
||||
processed.AddReference(reference);
|
||||
return reference;
|
||||
}
|
||||
|
||||
throw new("Impossible");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить тип поля структуры по значению.
|
||||
/// </summary>
|
||||
/// <param name="value">Значение поля.</param>
|
||||
/// <returns>Тип поля структуры.</returns>
|
||||
public static StructureFieldType GetFieldType(object? value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return StructureFieldType.Null;
|
||||
}
|
||||
|
||||
Type type = value.GetType();
|
||||
|
||||
if (type.IsEnum)
|
||||
{
|
||||
return StructureFieldType.Enum;
|
||||
}
|
||||
|
||||
if (type.IsPrimitive || type == typeof(string))
|
||||
{
|
||||
return StructureFieldType.Primitive;
|
||||
}
|
||||
|
||||
return StructureFieldType.Complex;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Pf2e.WPF.EntityViewer.EntityViewStructure;
|
||||
|
||||
/// <summary>
|
||||
/// Базовый класс для полей структуры.
|
||||
/// </summary>
|
||||
/// <param name="builder">Класс построения структуры данных.</param>
|
||||
/// <param name="name">Название поля.</param>
|
||||
/// <param name="depth">Глубина поля.</param>
|
||||
internal abstract class StructureField(StructureBuilder builder, string name, int depth)
|
||||
: INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// Название поля.
|
||||
/// </summary>
|
||||
public string Name { get; } = name;
|
||||
|
||||
/// <summary>
|
||||
/// Тип поля структуры.
|
||||
/// </summary>
|
||||
public abstract StructureFieldType FieldType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Класс построения структуры данных.
|
||||
/// </summary>
|
||||
public StructureBuilder Builder { get; } = builder;
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, отображается ли поле.
|
||||
/// </summary>
|
||||
public virtual bool Visible
|
||||
{
|
||||
get => _visible;
|
||||
set
|
||||
{
|
||||
_visible = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
private bool _visible = true;
|
||||
|
||||
/// <summary>
|
||||
/// Глубина поля структуры.
|
||||
/// </summary>
|
||||
public int Depth { get; } = depth;
|
||||
|
||||
/// <summary>
|
||||
/// Получить строку, представляющую тип поля.
|
||||
/// </summary>
|
||||
/// <returns>Строка, представляющая тип поля.</returns>
|
||||
public abstract string GetTypeString();
|
||||
|
||||
/// <summary>
|
||||
/// Получить строку, представляющую значение поля.
|
||||
/// </summary>
|
||||
/// <returns>Строка, представляющая значение поля.</returns>
|
||||
public abstract string GetValueString();
|
||||
|
||||
/// <summary>
|
||||
/// Получить набор строк, используя результат функция.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Тип результирующей строки.</typeparam>
|
||||
/// <param name="func">Функция, оборажения поля в результирующую строку.</param>
|
||||
/// <returns>Коллекция, содержащая набор строк.</returns>
|
||||
public virtual ICollection<T> GetRows<T>(Func<StructureField, T> func)
|
||||
{
|
||||
StructureField[] q = [this];
|
||||
return q.Select(func).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Событие изменения поля.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Запустить событие изменения поля.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Название поля.</param>
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new(propertyName));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Поле структуры с перечислением.
|
||||
/// </summary>
|
||||
/// <param name="builder">Класс построения структуры данных.</param>
|
||||
/// <param name="value">Значение поля.</param>
|
||||
/// <param name="name">Название поля.</param>
|
||||
/// <param name="depth">Глубина поля.</param>
|
||||
internal class StructureFieldEnum(StructureBuilder builder, object value, string name, int depth)
|
||||
: StructureField(builder, name, depth)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override StructureFieldType FieldType => StructureFieldType.Enum;
|
||||
|
||||
/// <summary>
|
||||
/// Значение поля.
|
||||
/// </summary>
|
||||
public readonly object Value = value;
|
||||
|
||||
/// <summary>
|
||||
/// Тип значения поля.
|
||||
/// </summary>
|
||||
public readonly Type Type = value.GetType();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetTypeString() => "enum";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetValueString() => $"{Type.Name}.{Value}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Пустое поле структуры.
|
||||
/// </summary>
|
||||
/// <param name="builder">Класс построения структуры данных.</param>
|
||||
/// <param name="name">Название поля.</param>
|
||||
/// <param name="depth">Глубина поля.</param>
|
||||
internal class StructureFieldNull(StructureBuilder builder, string name, int depth)
|
||||
: StructureField(builder, name, depth)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override StructureFieldType FieldType => StructureFieldType.Null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetTypeString() => "null";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetValueString() => "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Простое поле структуры.
|
||||
/// </summary>
|
||||
/// <param name="builder">Класс построения структуры данных.</param>
|
||||
/// <param name="value">Значение поля.</param>
|
||||
/// <param name="name">Название поля.</param>
|
||||
/// <param name="depth">Глубина поля.</param>
|
||||
internal class StructureFieldPrimitive(
|
||||
StructureBuilder builder,
|
||||
object value,
|
||||
string name,
|
||||
int depth
|
||||
) : StructureField(builder, name, depth)
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override StructureFieldType FieldType => StructureFieldType.Primitive;
|
||||
|
||||
/// <summary>
|
||||
/// Значение поля.
|
||||
/// </summary>
|
||||
public readonly object Value = value;
|
||||
|
||||
/// <summary>
|
||||
/// Тип значения поля.
|
||||
/// </summary>
|
||||
public readonly Type Type = value.GetType();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetTypeString()
|
||||
{
|
||||
return Type.GetTypeCode(Type) switch
|
||||
{
|
||||
TypeCode.Boolean => "bool",
|
||||
TypeCode.Int32 => "int",
|
||||
TypeCode.String => "string",
|
||||
TypeCode.Double => "double",
|
||||
_ => Type.Name,
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetValueString()
|
||||
{
|
||||
if (Type == typeof(string))
|
||||
{
|
||||
return "\"" + ((string)Value).Replace("\"", "\\\"") + "\"";
|
||||
}
|
||||
|
||||
return Value.ToString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Поле структуры с ссылкой на объект.
|
||||
/// </summary>
|
||||
/// <param name="builder">Класс построения структуры данных.</param>
|
||||
/// <param name="name">Название поля.</param>
|
||||
/// <param name="reference">Ссылка на объект.</param>
|
||||
/// <param name="depth">Глубина поля.</param>
|
||||
internal class StructureFieldReference(
|
||||
StructureBuilder builder,
|
||||
string name,
|
||||
StructureFieldComplex? reference,
|
||||
int depth
|
||||
) : StructureField(builder, name, depth)
|
||||
{
|
||||
/// <summary>
|
||||
/// Ссылка на объект.
|
||||
/// </summary>
|
||||
public StructureFieldComplex? Reference = reference;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override StructureFieldType FieldType => StructureFieldType.Reference;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetTypeString() => Reference?.GetTypeString() ?? "???";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetValueString() => "<Reference>";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Поле структуры пропускаемого объекта.
|
||||
/// </summary>
|
||||
/// <param name="builder">Класс построения структуры данных.</param>
|
||||
/// <param name="name">Название поля.</param>
|
||||
/// <param name="reference">Ссылка на объект.</param>
|
||||
/// <param name="depth">Глубина поля.</param>
|
||||
internal class StructureFieldIgnored(
|
||||
StructureBuilder builder,
|
||||
string name,
|
||||
StructureFieldComplex? reference,
|
||||
int depth
|
||||
) : StructureField(builder, name, depth)
|
||||
{
|
||||
/// <summary>
|
||||
/// Ссылка на объект.
|
||||
/// </summary>
|
||||
public StructureFieldComplex? Reference = reference;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override StructureFieldType FieldType => StructureFieldType.Reference;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetTypeString() => Reference?.GetTypeString() ?? "???";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetValueString() => "<Ignored>";
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using LinqToDB.Extensions;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
|
||||
namespace Pf2e.WPF.EntityViewer.EntityViewStructure;
|
||||
|
||||
/// <summary>
|
||||
/// Поле стркутуры с объектом.
|
||||
/// </summary>
|
||||
internal class StructureFieldComplex : StructureField
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override StructureFieldType FieldType => StructureFieldType.Complex;
|
||||
|
||||
/// <summary>
|
||||
/// Набор полей объекта.
|
||||
/// </summary>
|
||||
public readonly List<StructureField> Fields = [];
|
||||
|
||||
/// <summary>
|
||||
/// Тип значения поля.
|
||||
/// </summary>
|
||||
public readonly Type Type;
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, отображается ли поля объекта.
|
||||
/// </summary>
|
||||
public bool Collapsed
|
||||
{
|
||||
get => _collapsed;
|
||||
set
|
||||
{
|
||||
_collapsed = value;
|
||||
UpdateFieldVisibility();
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
private bool _collapsed;
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, что у этот элемент можно свернуть.
|
||||
/// </summary>
|
||||
public bool Collapsable
|
||||
{
|
||||
get => Fields.Count > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Значение поля идентификатора элемента.
|
||||
/// </summary>
|
||||
public string? IdValue { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Visible
|
||||
{
|
||||
get => base.Visible;
|
||||
set
|
||||
{
|
||||
base.Visible = value;
|
||||
UpdateFieldVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор класса поля структуры с объектом.
|
||||
/// </summary>
|
||||
/// <param name="builder">Класс построения структуры данных.</param>
|
||||
/// <param name="value">Значение поля.</param>
|
||||
/// <param name="name">Название поля.</param>
|
||||
/// <param name="depth">Глубина поля.</param>
|
||||
public StructureFieldComplex(StructureBuilder builder, object value, string name, int depth)
|
||||
: base(builder, name, depth)
|
||||
{
|
||||
Type = value.GetType();
|
||||
|
||||
SetFields((dynamic)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновить видимость полей структуры.
|
||||
/// </summary>
|
||||
private void UpdateFieldVisibility()
|
||||
{
|
||||
foreach (StructureField field in Fields)
|
||||
{
|
||||
field.Visible = Visible && !Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Установить поля структуры `LocaleString`.
|
||||
/// </summary>
|
||||
/// <param name="localeString">Объект строки локализации.</param>
|
||||
private void SetFields(LocaleString localeString)
|
||||
{
|
||||
SetFieldsFromRecords(
|
||||
localeString
|
||||
.Select(field => new FieldRecord(field.Locale.ToString(), field.Value))
|
||||
.ToList()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Установить поля структуры `IEnumerable`.
|
||||
/// </summary>
|
||||
/// <param name="enumerable">Объект-перечисление.</param>
|
||||
private void SetFields(IEnumerable enumerable)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
List<FieldRecord> records = [];
|
||||
|
||||
foreach (object? item in enumerable)
|
||||
{
|
||||
records.Add(new($"[{i++}]", item));
|
||||
}
|
||||
|
||||
SetFieldsFromRecords(records);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Установить поля структуры общего объекта.
|
||||
/// </summary>
|
||||
/// <param name="value">Объект.</param>
|
||||
private void SetFields(object value)
|
||||
{
|
||||
Type type = value.GetType();
|
||||
PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
var records = properties
|
||||
.Where(property => property.GetAttribute<Newtonsoft.Json.JsonIgnoreAttribute>() is null)
|
||||
.Select(property => new FieldRecord(property.Name, property.GetValue(value, null)))
|
||||
.ToList();
|
||||
|
||||
var idRecord = records.FirstOrDefault(record => record.Name == "Id");
|
||||
|
||||
if (idRecord is not null && idRecord.Value is not null)
|
||||
{
|
||||
records.Remove(idRecord);
|
||||
IdValue = idRecord.Value.ToString();
|
||||
}
|
||||
|
||||
SetFieldsFromRecords(records);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Установить поля структуры из списка записей.
|
||||
/// </summary>
|
||||
/// <param name="records"></param>
|
||||
private void SetFieldsFromRecords(List<FieldRecord> records)
|
||||
{
|
||||
foreach (
|
||||
FieldRecord record in records.Where(record =>
|
||||
record.FieldType == StructureFieldType.Complex
|
||||
)
|
||||
)
|
||||
{
|
||||
record.Allowed = Builder.RegisterProcessed(record.Value!);
|
||||
}
|
||||
|
||||
foreach (FieldRecord record in records)
|
||||
{
|
||||
Fields.Add(
|
||||
record is { FieldType: StructureFieldType.Complex, Allowed: true }
|
||||
? Builder.Create(record.Name, record.Value!, Depth + 1)
|
||||
: Builder.Resolve(record.Name, record.Value!, Depth + 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Запись поля структуры.
|
||||
/// </summary>
|
||||
/// <param name="name">Название поля.</param>
|
||||
/// <param name="value">Значение поля.</param>
|
||||
private class FieldRecord(string name, object? value)
|
||||
{
|
||||
/// <summary>
|
||||
/// Название поля.
|
||||
/// </summary>
|
||||
public readonly string Name = name;
|
||||
|
||||
/// <summary>
|
||||
/// Значение поля.
|
||||
/// </summary>
|
||||
public readonly object? Value = value;
|
||||
|
||||
/// <summary>
|
||||
/// Тип поля структуры.
|
||||
/// </summary>
|
||||
public StructureFieldType FieldType => StructureBuilder.GetFieldType(Value);
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, использовать ли поле.
|
||||
/// </summary>
|
||||
public bool Allowed = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetTypeString()
|
||||
{
|
||||
if (Type.IsGenericEnumerableType())
|
||||
{
|
||||
Type elementType = Type.GetGenericArguments().Single();
|
||||
|
||||
return $"{elementType.Name}[]";
|
||||
}
|
||||
else
|
||||
{
|
||||
return Type.Name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetValueString()
|
||||
{
|
||||
if (Type.IsGenericEnumerableType())
|
||||
{
|
||||
return Fields.Count.ToString() + (Fields.Count == 1 ? " item" : " items");
|
||||
}
|
||||
|
||||
if (IdValue is not null)
|
||||
{
|
||||
return $"#{IdValue}";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ICollection<T> GetRows<T>(Func<StructureField, T> func)
|
||||
{
|
||||
return [.. base.GetRows(func), .. Fields.SelectMany(f => f.GetRows(func)).ToList()];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace Pf2e.WPF.EntityViewer.EntityViewStructure;
|
||||
|
||||
/// <summary>
|
||||
/// Тип поля структуры.
|
||||
/// </summary>
|
||||
internal enum StructureFieldType
|
||||
{
|
||||
/// <summary>
|
||||
/// Пустое поле.
|
||||
/// </summary>
|
||||
Null,
|
||||
|
||||
/// <summary>
|
||||
/// Простое поле.
|
||||
/// </summary>
|
||||
Primitive,
|
||||
|
||||
/// <summary>
|
||||
/// Поле с перечислением.
|
||||
/// </summary>
|
||||
Enum,
|
||||
|
||||
/// <summary>
|
||||
/// Поле с объектом.
|
||||
/// </summary>
|
||||
Complex,
|
||||
|
||||
/// <summary>
|
||||
/// Поле с ссылкой на объект.
|
||||
/// </summary>
|
||||
Reference,
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Pf2e.WPF.EntityViewer.EntityViewStructure;
|
||||
|
||||
/// <summary>
|
||||
/// Класс для хранения обработанного объекта.
|
||||
/// </summary>
|
||||
/// <param name="value">Объект.</param>
|
||||
internal class StructureProcessedObject(object value)
|
||||
{
|
||||
public readonly object Value = value;
|
||||
|
||||
private StructureFieldComplex? _field;
|
||||
|
||||
public StructureFieldComplex? Field
|
||||
{
|
||||
get => _field;
|
||||
set
|
||||
{
|
||||
_field = value;
|
||||
foreach (StructureFieldReference reference in _references)
|
||||
{
|
||||
reference.Reference = Field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<StructureFieldReference> _references = [];
|
||||
|
||||
public void AddReference(StructureFieldReference reference)
|
||||
{
|
||||
_references.Add(reference);
|
||||
|
||||
if (_field is not null)
|
||||
{
|
||||
reference.Reference = Field;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using FontAwesome.Sharp;
|
||||
using Color = System.Windows.Media.Color;
|
||||
|
||||
namespace Pf2e.WPF.EntityViewer.EntityViewStructure;
|
||||
|
||||
/// <summary>
|
||||
/// Строка структуры, представляющая поле сущности.
|
||||
/// </summary>
|
||||
internal class StructureRow : INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// Отображаемая строка поля сущности.
|
||||
/// </summary>
|
||||
public StructureField Field { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Список типов лестницы.
|
||||
/// </summary>
|
||||
public List<LadderType> Ladder { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Флаг наличия кнопки.
|
||||
/// </summary>
|
||||
public bool HasButton =>
|
||||
Field.FieldType == StructureFieldType.Complex && ((StructureFieldComplex)Field).Collapsable;
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, отображается ли строка.
|
||||
/// </summary>
|
||||
public bool Visible => Field.Visible;
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, отображается ли строка.
|
||||
/// </summary>
|
||||
public Visibility ItemVisibility => Visible ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, отображается ли строка.
|
||||
/// </summary>
|
||||
public Visibility ButtonVisibility => HasButton ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
/// <summary>
|
||||
/// Иконка кнопки.
|
||||
/// </summary>
|
||||
public string ButtonIcon =>
|
||||
HasButton && !((StructureFieldComplex)Field).Collapsed
|
||||
? IconChar.MinusSquare.ToChar()
|
||||
: IconChar.PlusSquare.ToChar();
|
||||
|
||||
/// <summary>
|
||||
/// Глубина отображения поля.
|
||||
/// </summary>
|
||||
public int Depth => Field.Depth;
|
||||
|
||||
/// <summary>
|
||||
/// Строковое представление названия поля.
|
||||
/// </summary>
|
||||
public string Name => Field.Name;
|
||||
|
||||
/// <summary>
|
||||
/// Строковое представление типа поля.
|
||||
/// </summary>
|
||||
public string Type
|
||||
{
|
||||
get
|
||||
{
|
||||
string result = Field.GetTypeString();
|
||||
if (result != "")
|
||||
{
|
||||
result = $@"({result})";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Строковое представление значения поля.
|
||||
/// </summary>
|
||||
public string Value => Field.GetValueString().Replace("\r", "").Replace("\n", "");
|
||||
|
||||
/// <summary>
|
||||
/// Цвет значения
|
||||
/// </summary>
|
||||
public Color? AccentColor =>
|
||||
Field switch
|
||||
{
|
||||
StructureFieldPrimitive primitive => System.Type.GetTypeCode(primitive.Type) switch
|
||||
{
|
||||
TypeCode.Boolean => Colors.DarkGoldenrod,
|
||||
TypeCode.Int32 => Colors.Blue,
|
||||
TypeCode.Double => Colors.Aqua,
|
||||
TypeCode.String => Colors.Green,
|
||||
_ => null,
|
||||
},
|
||||
StructureFieldEnum => Colors.Red,
|
||||
StructureFieldNull => Colors.Transparent,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Команда переключения.
|
||||
/// </summary>
|
||||
public ICommand CollapseCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Кониструктор класса <see cref="StructureRow"/>.
|
||||
/// </summary>
|
||||
/// <param name="field">Поле.</param>
|
||||
public StructureRow(StructureField field)
|
||||
{
|
||||
Field = field;
|
||||
CollapseCommand = new CollapseCommand(this);
|
||||
|
||||
Field.PropertyChanged += Field_PropertyChanged;
|
||||
}
|
||||
|
||||
private void Field_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
OnPropertyChanged(nameof(Field));
|
||||
OnPropertyChanged(nameof(Visible));
|
||||
OnPropertyChanged(nameof(ItemVisibility));
|
||||
OnPropertyChanged(nameof(ButtonVisibility));
|
||||
OnPropertyChanged(nameof(ButtonIcon));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Событие изменения поля.
|
||||
/// </summary>
|
||||
public event Action<StructureRow, StructureField>? OnChange;
|
||||
|
||||
/// <summary>
|
||||
/// Изменение поля.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// При изменении поля.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Название поля.</param>
|
||||
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new(propertyName));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Команда для сворачивания/разворачивания поля структуры.
|
||||
/// </summary>
|
||||
/// <param name="structureRow">Строка структуры, содержащая поле.</param>
|
||||
internal class CollapseCommand(StructureRow structureRow) : ICommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Строка структуры, содержащая поле, для которого выполняется команда.
|
||||
/// </summary>
|
||||
private StructureRow StructureRow { get; } = structureRow;
|
||||
|
||||
/// <summary>
|
||||
/// Событие, вызываемое при изменении возможности выполнения команды.
|
||||
/// </summary>
|
||||
public event EventHandler? CanExecuteChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, может ли команда быть выполнена с указанным параметром.
|
||||
/// </summary>
|
||||
/// <param name="parameter">Параметр, передаваемый команде.</param>
|
||||
/// <returns><c>true</c>, если команда может быть выполнена; в противном случае <c>false</c>.</returns>
|
||||
public bool CanExecute(object? parameter)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выполняет команду с указанным параметром.
|
||||
/// </summary>
|
||||
/// <param name="parameter">Параметр, передаваемый команде.</param>
|
||||
public void Execute(object? parameter)
|
||||
{
|
||||
StructureFieldComplex field = (StructureFieldComplex)StructureRow.Field;
|
||||
field.Collapsed = !field.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Преобразователь, который адаптирует цвет в зависимости от типа целевого свойства.
|
||||
/// </summary>
|
||||
public class ColorAdapterConverter : IMultiValueConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Смешивает два компнента цвета с заданным коэффициентом смешивания.
|
||||
/// </summary>
|
||||
/// <param name="a">Первый компонент цвета.</param>
|
||||
/// <param name="b">Второй компонент цвета.</param>
|
||||
/// <param name="rate">Коэффициент смешивания, где 0.0 означает полный компонент <paramref name="a"/>, а 1.0 означает полный компонент <paramref name="b"/>.</param>
|
||||
/// <returns>Смешанный компонент цвета.</returns>
|
||||
private byte Mix(byte a, byte b, double rate) =>
|
||||
System.Convert.ToByte(Math.Round(a * rate + b * (1 - rate)));
|
||||
|
||||
/// <summary>
|
||||
/// Преобразует массив значений в целевой тип, усредняя цвета.
|
||||
/// </summary>
|
||||
/// <param name="value">Массив значений, содержащий цвета.</param>
|
||||
/// <param name="targetType">Целевой тип, в который нужно преобразовать значения.</param>
|
||||
/// <param name="parameter">Параметр преобразования, не используется.</param>
|
||||
/// <param name="culture">Культура, используемая для преобразования, не используется.</param>
|
||||
/// <returns>Результат преобразования в целевой тип, который может быть <c>null</c>, если преобразование невозможно.</returns>
|
||||
public object? Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
Color textColor = SystemColors.WindowTextColor;
|
||||
double a = textColor.A;
|
||||
double r = textColor.R;
|
||||
double g = textColor.G;
|
||||
double b = textColor.B;
|
||||
int n = 1 + value.Length;
|
||||
|
||||
foreach (object item in value)
|
||||
{
|
||||
if (item is not Color color)
|
||||
{
|
||||
n--;
|
||||
continue;
|
||||
}
|
||||
|
||||
a += color.A;
|
||||
r += color.R;
|
||||
g += color.G;
|
||||
b += color.B;
|
||||
}
|
||||
|
||||
Color resultColor = Color.FromArgb(
|
||||
System.Convert.ToByte(Math.Round(a / n)),
|
||||
System.Convert.ToByte(Math.Round(r / n)),
|
||||
System.Convert.ToByte(Math.Round(g / n)),
|
||||
System.Convert.ToByte(Math.Round(b / n))
|
||||
);
|
||||
|
||||
Dictionary<Type, Func<object>> @switch = new()
|
||||
{
|
||||
{ typeof(Brush), () => new SolidColorBrush(resultColor) },
|
||||
};
|
||||
|
||||
return @switch.TryGetValue(targetType, out Func<object>? action) ? action() : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Преобразует значение обратно в массив типов, но не реализован.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <param name="parameter"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public object[] ConvertBack(
|
||||
object value,
|
||||
Type[] targetType,
|
||||
object parameter,
|
||||
CultureInfo culture
|
||||
)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using Pf2e.Database.Model.Base;
|
||||
|
||||
namespace Pf2e.WPF.EntityViewer.Stores;
|
||||
|
||||
/// <summary>
|
||||
/// Хранилище выбранной сущности.
|
||||
/// </summary>
|
||||
public class SelectedEntityStore
|
||||
{
|
||||
/// <exclude />
|
||||
private Entity? _selectedEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Сущность, выбранная в интерфейсе.
|
||||
/// </summary>
|
||||
public Entity? SelectedEntity
|
||||
{
|
||||
get => _selectedEntity;
|
||||
set
|
||||
{
|
||||
_selectedEntity = value;
|
||||
SelectedEntityChanged?.Invoke(SelectedEntity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Событие, вызываемое при изменении выбранной сущности.
|
||||
/// </summary>
|
||||
public event Action<Entity?>? SelectedEntityChanged;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
|
||||
namespace Pf2e.WPF.EntityViewer.Stores;
|
||||
|
||||
internal class SelectedEntityTypeStore
|
||||
{
|
||||
private EntityType? _selectedEntityType;
|
||||
|
||||
public EntityType? SelectedEntityType
|
||||
{
|
||||
get => _selectedEntityType;
|
||||
set
|
||||
{
|
||||
_selectedEntityType = value;
|
||||
SelectedEntityTypeChanged?.Invoke(SelectedEntityType);
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<EntityType?>? SelectedEntityTypeChanged;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Base;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Scrapper.WPF.EntityViewer.Stores;
|
||||
|
||||
namespace Pf2e.WPF.EntityViewer.ViewModel;
|
||||
|
||||
internal class EntityFormViewModel : ViewModelBase
|
||||
{
|
||||
#region "Конструктор"
|
||||
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
public Pf2eDbContext Context { get; }
|
||||
public EntityListViewModel DbEntityListViewModel { get; }
|
||||
public ViewViewModel ViewViewModel { get; }
|
||||
public SelectedEntityTypeStore SelectedEntityTypeStore { get; }
|
||||
public SelectedEntityStore SelectedEntityStore { get; }
|
||||
|
||||
public EntityFormViewModel(
|
||||
IServiceProvider serviceProvider,
|
||||
Pf2eDbContext context,
|
||||
EntityListViewModel entityListViewModel,
|
||||
ViewViewModel viewViewModel,
|
||||
SelectedEntityTypeStore selectedEntityTypeStore,
|
||||
SelectedEntityStore selectedEntityStore
|
||||
)
|
||||
{
|
||||
ServiceProvider = serviceProvider;
|
||||
Context = context;
|
||||
DbEntityListViewModel = entityListViewModel;
|
||||
ViewViewModel = viewViewModel;
|
||||
SelectedEntityTypeStore = selectedEntityTypeStore;
|
||||
SelectedEntityStore = selectedEntityStore;
|
||||
|
||||
Update();
|
||||
|
||||
SelectedEntityStore.SelectedEntityChanged += SelectedEntityStore_SelectedEntityChanged;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
LoadEntityTypes();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Выбор типа"
|
||||
|
||||
public IEnumerable<EntityType> EntityTypes { get; set; } = [];
|
||||
|
||||
public EntityType? SelectedEntityType
|
||||
{
|
||||
get => SelectedEntityTypeStore.SelectedEntityType;
|
||||
set
|
||||
{
|
||||
SelectedEntityTypeStore.SelectedEntityType = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadEntityTypes()
|
||||
{
|
||||
EntityTypes = Context.Entities.Select(e => e.Type).Distinct().Order().ToList();
|
||||
OnPropertyChanged(nameof(EntityTypes));
|
||||
|
||||
SelectedEntityTypeStore.SelectedEntityType = EntityTypes.Any() ? EntityTypes.First() : null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Текущий элемент"
|
||||
|
||||
public string SelectedEntityDescription
|
||||
{
|
||||
get => SelectedEntityStore.SelectedEntity?.Description ?? "";
|
||||
}
|
||||
|
||||
private void SelectedEntityStore_SelectedEntityChanged(Entity? obj)
|
||||
{
|
||||
OnPropertyChanged(nameof(SelectedEntityDescription));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public string TestText => SelectedEntityType?.ToString() ?? "null";
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Database.Model.Base;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
using Pf2e.Scrapper.WPF.EntityViewer.Stores;
|
||||
|
||||
namespace Pf2e.WPF.EntityViewer.ViewModel;
|
||||
|
||||
internal class EntityListViewModel : 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;
|
||||
set
|
||||
{
|
||||
_page = Math.Clamp(value, 1, PageCount);
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(HasPrevious));
|
||||
OnPropertyChanged(nameof(HasNext));
|
||||
Reload();
|
||||
}
|
||||
}
|
||||
private int _page = 1;
|
||||
|
||||
public int EntitiesCount
|
||||
{
|
||||
get => _entitiesCount;
|
||||
set
|
||||
{
|
||||
_entitiesCount = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(PageCount));
|
||||
OnPropertyChanged(nameof(HasPrevious));
|
||||
OnPropertyChanged(nameof(HasNext));
|
||||
}
|
||||
}
|
||||
private int _entitiesCount = 1;
|
||||
|
||||
public int PerPage
|
||||
{
|
||||
get => _perPage;
|
||||
set
|
||||
{
|
||||
_perPage = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(PageCount));
|
||||
Reload();
|
||||
}
|
||||
}
|
||||
private int _perPage = 30;
|
||||
|
||||
public string Search
|
||||
{
|
||||
get => _search;
|
||||
set
|
||||
{
|
||||
_search = value;
|
||||
OnPropertyChanged();
|
||||
Reload();
|
||||
}
|
||||
}
|
||||
private string _search = "";
|
||||
|
||||
public int PageCount => Convert.ToInt32(Math.Ceiling(1.0 * EntitiesCount / PerPage));
|
||||
|
||||
public IEnumerable<Entity> Entities
|
||||
{
|
||||
get => _entities;
|
||||
set
|
||||
{
|
||||
_entities = [.. value];
|
||||
SelectedEntity = Entities.FirstOrDefault();
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
private ObservableCollection<Entity> _entities = [];
|
||||
|
||||
public EntityListViewModel(
|
||||
Pf2eDbContext context,
|
||||
SelectedEntityTypeStore selectedEntityTypeStore,
|
||||
SelectedEntityStore selectedEntityStore
|
||||
)
|
||||
{
|
||||
Context = context;
|
||||
SelectedEntityTypeStore = selectedEntityTypeStore;
|
||||
SelectedEntityStore = selectedEntityStore;
|
||||
|
||||
SelectedEntityTypeStore.SelectedEntityTypeChanged +=
|
||||
SelectedEntityTypeStore_SelectedEntityTypeChanged;
|
||||
}
|
||||
|
||||
~EntityListViewModel()
|
||||
{
|
||||
SelectedEntityTypeStore.SelectedEntityTypeChanged -=
|
||||
SelectedEntityTypeStore_SelectedEntityTypeChanged;
|
||||
}
|
||||
|
||||
private void SelectedEntityTypeStore_SelectedEntityTypeChanged(EntityType? entityType)
|
||||
{
|
||||
if (entityType is null)
|
||||
{
|
||||
Page = 1;
|
||||
EntitiesCount = 1;
|
||||
Entities = [];
|
||||
return;
|
||||
}
|
||||
|
||||
Page = 1;
|
||||
Reload();
|
||||
}
|
||||
|
||||
#region "Методы кнопок"
|
||||
|
||||
private PageNavigationCommand? _firstPageCommand;
|
||||
public PageNavigationCommand FirstPageCommand
|
||||
{
|
||||
get
|
||||
{
|
||||
return _firstPageCommand ??= new PageNavigationCommand(() =>
|
||||
{
|
||||
Page = 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private PageNavigationCommand? _previousPageCommand;
|
||||
public PageNavigationCommand PreviousPageCommand
|
||||
{
|
||||
get
|
||||
{
|
||||
return _previousPageCommand ??= new PageNavigationCommand(() =>
|
||||
{
|
||||
Page--;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private PageNavigationCommand? _nextPageCommand;
|
||||
public PageNavigationCommand NextPageCommand
|
||||
{
|
||||
get
|
||||
{
|
||||
return _nextPageCommand ??= new PageNavigationCommand(() =>
|
||||
{
|
||||
Page++;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private PageNavigationCommand? _lastPageCommand;
|
||||
public PageNavigationCommand LastPageCommand
|
||||
{
|
||||
get
|
||||
{
|
||||
return _lastPageCommand ??= new PageNavigationCommand(() =>
|
||||
{
|
||||
Page = PageCount;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasPrevious => Page != 1;
|
||||
|
||||
public bool HasNext => Page != PageCount;
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Запросы"
|
||||
|
||||
/// <summary>
|
||||
/// Выполняет запрос к базе данных и обрабатывает результаты.
|
||||
/// </summary>
|
||||
public void Reload()
|
||||
{
|
||||
Pf2eDbContext dbContext = new();
|
||||
|
||||
IQueryable<Entity> query =
|
||||
Search == ""
|
||||
? dbContext.Entities.Where(e => e.Type == SelectedEntityType)
|
||||
:
|
||||
// ReSharper disable once EntityFramework.UnsupportedServerSideFunctionCall
|
||||
dbContext.Entities.Where(e =>
|
||||
e.Type == SelectedEntityType && Pf2eDbContext.LocaleSearch(e.Name, Search)
|
||||
);
|
||||
|
||||
EntitiesCount = query.Count();
|
||||
Entities = [.. query.OrderBy(e => e.Id).Skip((Page - 1) * PerPage).Take(PerPage).ToList()];
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Команда для навигации по страницам.
|
||||
/// </summary>
|
||||
/// <param name="action">Действие, выполняемое при выполнении команды.</param>
|
||||
public class PageNavigationCommand(Action action) : System.Windows.Input.ICommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Действие, выполняемое при выполнении команды.
|
||||
/// </summary>
|
||||
public Action Action { get; } = action;
|
||||
|
||||
/// <summary>
|
||||
/// Событие, вызываемое при изменении возможности выполнения команды.
|
||||
/// </summary>
|
||||
public event EventHandler? CanExecuteChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, может ли команда быть выполнена с указанным параметром.
|
||||
/// </summary>
|
||||
/// <param name="parameter">Параметр команды.</param>
|
||||
/// <returns>Флаг, указывающий, может ли команда быть выполнена.</returns>
|
||||
public bool CanExecute(object? parameter)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выполняет команду с указанным параметром.
|
||||
/// </summary>
|
||||
/// <param name="parameter">Параметр команды.</param>
|
||||
public void Execute(object? parameter)
|
||||
{
|
||||
Action.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Pf2e.Database;
|
||||
using Pf2e.Scrapper.Helpers;
|
||||
using Pf2e.Scrapper.WPF.EntityViewer.EntityViewStructure;
|
||||
using Pf2e.Scrapper.WPF.EntityViewer.Stores;
|
||||
|
||||
namespace Pf2e.WPF.EntityViewer.ViewModel;
|
||||
|
||||
internal class ViewViewModel : ViewModelBase
|
||||
{
|
||||
public Pf2eDbContext Context { get; }
|
||||
public SelectedEntityStore SelectedEntityStore { get; }
|
||||
|
||||
public FullEntity? CurrentEntityView
|
||||
{
|
||||
get => _currentEntityView;
|
||||
set
|
||||
{
|
||||
_currentEntityView = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
private FullEntity? _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 ViewViewModel(Pf2eDbContext context, SelectedEntityStore selectedEntityStore)
|
||||
{
|
||||
Context = context;
|
||||
SelectedEntityStore = selectedEntityStore;
|
||||
|
||||
SelectedEntityStore.SelectedEntityChanged += SelectedEntityStore_SelectedEntityTypeChanged;
|
||||
}
|
||||
|
||||
~ViewViewModel()
|
||||
{
|
||||
SelectedEntityStore.SelectedEntityChanged -= SelectedEntityStore_SelectedEntityTypeChanged;
|
||||
}
|
||||
|
||||
private void SelectedEntityStore_SelectedEntityTypeChanged(Database.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);
|
||||
|
||||
row.OnChange += Row_OnChange;
|
||||
}
|
||||
|
||||
BuildLadder();
|
||||
|
||||
OnPropertyChanged(nameof(Rows));
|
||||
}
|
||||
|
||||
private void Row_OnChange(StructureRow row, StructureField field)
|
||||
{
|
||||
OnPropertyChanged(nameof(Rows));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Построение лестницы для строк полей структуры.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user