Завершение (надеюсь) работы с WPF.

This commit is contained in:
2025-05-21 15:04:30 +04:00
parent 0274d9cbef
commit bb50ebe986
12 changed files with 267 additions and 92 deletions
+2 -2
View File
@@ -25,8 +25,8 @@ internal sealed class ImportApplication : Application
Settings = settings;
Processors = processors;
//CurrentStage = ApplicationStage.InitialSettings;
CurrentStage = ApplicationStage.Viewer;
CurrentStage = ApplicationStage.InitialSettings;
//CurrentStage = ApplicationStage.Viewer;
}
#region "Поля"
@@ -6,6 +6,7 @@
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:Pf2eDataScrapper.WPF.EntityViewer.EntityViewStructure"
xmlns:viewmodel="clr-namespace:Pf2eDataScrapper.WPF.EntityViewer.ViewModel"
x:Name="_this"
d:DataContext="{d:DesignInstance Type=viewmodel:ViewViewModel}"
@@ -17,15 +18,15 @@
<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="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
<Setter Property="Width" Value="{Binding ButtonSize, ElementName=_this}" />
<Setter Property="Height" Value="{Binding ButtonSize, ElementName=_this}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderThickness" Value="2,2,1,1" />
<Setter Property="Background">
<Setter.Value>
@@ -41,7 +42,17 @@
<Setter Property="Foreground" Value="Black" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="DarkGray" />
<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>
@@ -52,6 +63,14 @@
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>
@@ -63,7 +82,7 @@
</UserControl.Background>
<ScrollViewer>
<ItemsControl ItemsSource="{Binding Rows}">
<ItemsControl AlternationCount="2" ItemsSource="{Binding Rows}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Grid.IsSharedSizeScope="True" />
@@ -72,7 +91,11 @@
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Height="{Binding ItemHeight, ElementName=_this}" Visibility="{Binding ItemVisibility}">
<Grid
x:Name="FooBar"
Height="{Binding ItemHeight, ElementName=_this}"
Visibility="{Binding ItemVisibility}">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="ColumnOne" />
<ColumnDefinition Width="*" />
@@ -86,13 +109,15 @@
Ladder="{Binding Ladder}" />
<Button
Margin="-22,0,0,0"
Margin="-20,0,0,0"
Padding="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Command="{Binding CollapseCommand}"
Content="{Binding ButtonIcon}"
Style="{StaticResource FaButtonStyle}"
Visibility="{Binding ButtonVisibility}" />
Visibility="{Binding ButtonVisibility}">
<TextBlock Margin="-3,-3,0,0" Text="{Binding ButtonIcon}" />
</Button>
<TextBlock
Margin="2,0,10,0"
@@ -108,14 +133,35 @@
VerticalAlignment="Center"
FontSize="{Binding TypeFontSize, ElementName=_this}"
FontStyle="Italic"
Text="{Binding Type}" />
Text="{Binding Type}">
<TextBlock.Foreground>
<MultiBinding Converter="{StaticResource ColorAdapterConverter}">
<Binding Path="AccentColor" />
</MultiBinding>
</TextBlock.Foreground>
</TextBlock>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Value}" />
Text="{Binding Value}"
TextTrimming="CharacterEllipsis">
<TextBlock.Foreground>
<MultiBinding Converter="{StaticResource ColorAdapterConverter}">
<Binding Path="AccentColor" />
</MultiBinding>
</TextBlock.Foreground>
</TextBlock>
</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>
@@ -5,16 +5,18 @@
xmlns:component="clr-namespace:Pf2eDataScrapper.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:Pf2eDataScrapper.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="20" />
<RowDefinition Height="25" />
<RowDefinition Height="150" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
@@ -47,6 +49,11 @@
Cursor="SizeNS"
ResizeDirection="Rows" />
<Button
Margin="3,4,0,0"
Click="Button_Click"
Content="&lt;&lt;&lt; Вернуться в начало" />
<ListBox
x:Name="EntityTypeListBox"
Grid.Row="1"
@@ -75,7 +82,7 @@
Grid.RowSpan="4"
Grid.Column="4"
IsReadOnly="True"
Text="{Binding TestText, Mode=OneWay}"
Text="{Binding SelectedEntityDescription, Mode=OneWay}"
TextWrapping="Wrap" />
</Grid>
</Window>
@@ -12,11 +12,7 @@ internal partial class EntityForm : Window
public Pf2eDbContext Context { get; }
public EntityFormViewModel ViewModel { get; }
public EntityForm(
Pf2eDbContext context,
EntityFormViewModel viewModel,
ViewViewModel entityViewViewModel
)
public EntityForm(Pf2eDbContext context, EntityFormViewModel viewModel)
{
Context = context;
ViewModel = viewModel;
@@ -31,4 +27,9 @@ internal partial class EntityForm : Window
}
private void Window_Loaded(object sender, RoutedEventArgs e) { }
private void Button_Click(object sender, RoutedEventArgs e)
{
(Application.Current as ImportApplication)!.CurrentStage = ApplicationStage.InitialSettings;
}
}
@@ -103,12 +103,7 @@ internal class StructureBuilder
return Create(name, value!, depth);
}
StructureFieldReference reference = new StructureFieldReference(
this,
name,
null,
depth
);
StructureFieldReference reference = new(this, name, null, depth);
processed.AddReference(reference);
return reference;
}
@@ -12,7 +12,8 @@ namespace Pf2eDataScrapper.WPF.EntityViewer.EntityViewStructure;
/// <param name="builder">Класс построения структуры данных.</param>
/// <param name="name">Название поля.</param>
/// <param name="depth">Глубина поля.</param>
internal abstract class StructureField(StructureBuilder builder, string name, int depth) : INotifyPropertyChanged
internal abstract class StructureField(StructureBuilder builder, string name, int depth)
: INotifyPropertyChanged
{
/// <summary>
/// Название поля.
@@ -216,3 +217,32 @@ internal class StructureFieldReference(
/// <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>";
}
@@ -41,6 +41,19 @@ internal class StructureFieldComplex : StructureField
}
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
{
@@ -118,14 +131,20 @@ internal class StructureFieldComplex : StructureField
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()
);
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>
@@ -203,6 +222,12 @@ internal class StructureFieldComplex : StructureField
{
return Fields.Count.ToString() + (Fields.Count == 1 ? " item" : " items");
}
if (IdValue is not null)
{
return $"#{IdValue}";
}
return "";
}
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
@@ -9,6 +10,9 @@ using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using FontAwesome.Sharp;
using static System.Runtime.InteropServices.JavaScript.JSType;
using static LinqToDB.Sql;
using Color = System.Windows.Media.Color;
namespace Pf2eDataScrapper.WPF.EntityViewer.EntityViewStructure;
@@ -27,7 +31,8 @@ internal class StructureRow : INotifyPropertyChanged
/// <summary>
/// Флаг наличия кнопки.
/// </summary>
public bool HasButton => Field.FieldType == StructureFieldType.Complex;
public bool HasButton =>
Field.FieldType == StructureFieldType.Complex && ((StructureFieldComplex)Field).Collapsable;
/// <summary>
/// Флаг, указывающий, отображается ли строка.
@@ -47,7 +52,10 @@ internal class StructureRow : INotifyPropertyChanged
/// <summary>
/// Иконка кнопки.
/// </summary>
public string ButtonIcon => HasButton && !((StructureFieldComplex)Field).Collapsed ? IconChar.PlusSquare.ToChar() : IconChar.MinusSquare.ToChar();
public string ButtonIcon =>
HasButton && !((StructureFieldComplex)Field).Collapsed
? IconChar.PlusSquare.ToChar()
: IconChar.MinusSquare.ToChar();
/// <summary>
/// Глубина отображения поля.
@@ -78,7 +86,26 @@ internal class StructureRow : INotifyPropertyChanged
/// <summary>
/// Строковое представление значения поля.
/// </summary>
public string Value => Field.GetValueString();
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>
/// Команда переключения.
@@ -91,14 +118,14 @@ internal class StructureRow : INotifyPropertyChanged
/// <param name="field">Поле.</param>
public StructureRow(StructureField field)
{
Field = field;
Field = field;
CollapseCommand = new CollapseCommand(this);
Field.PropertyChanged += Field_PropertyChanged;
}
/// <summary>
///
///
/// </summary>
/// <param name="obj"></param>
private void Field_PropertyChanged(object? sender, PropertyChangedEventArgs e)
@@ -147,3 +174,59 @@ internal class CollapseCommand(StructureRow structureRow) : ICommand
field.Collapsed = !field.Collapsed;
}
}
public class ColorAdapterConverter : IMultiValueConverter
{
private byte Mix(byte a, byte b, double rate) =>
System.Convert.ToByte(Math.Round(a * rate + b * (1 - rate)));
public object? Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
{
Color textColor = System.Windows.SystemColors.WindowTextColor;
double a = textColor.A;
double r = textColor.R;
double g = textColor.G;
double b = textColor.B;
int n = 1 + value.Length;
foreach (var item in value)
{
if (item == null || item is not Color)
{
n--;
continue;
}
Color color = (Color)item;
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))
);
var @switch = new Dictionary<Type, Func<object>>
{
{ typeof(System.Windows.Media.Brush), () => new SolidColorBrush(resultColor) },
};
return @switch.TryGetValue(targetType, out var action) ? action() : null;
}
public object[] ConvertBack(
object value,
Type[] targetType,
object parameter,
CultureInfo culture
)
{
throw new NotImplementedException();
}
}
@@ -13,9 +13,9 @@ public class SelectedEntityStore
set
{
_selectedEntity = value;
SelectedEntityTypeChanged?.Invoke(SelectedEntity);
SelectedEntityChanged?.Invoke(SelectedEntity);
}
}
public event Action<Entity?>? SelectedEntityTypeChanged;
public event Action<Entity?>? SelectedEntityChanged;
}
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Pf2eDataScrapper.WPF.EntityViewer.Stores;
using Pf2eModel;
using Pf2eModel.Model.Base;
using Pf2eModel.Model.Enum;
namespace Pf2eDataScrapper.WPF.EntityViewer.ViewModel;
@@ -16,13 +17,15 @@ internal class EntityFormViewModel : ViewModelBase
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
SelectedEntityTypeStore selectedEntityTypeStore,
SelectedEntityStore selectedEntityStore
)
{
ServiceProvider = serviceProvider;
@@ -30,7 +33,11 @@ internal class EntityFormViewModel : ViewModelBase
DbEntityListViewModel = entityListViewModel;
ViewViewModel = viewViewModel;
SelectedEntityTypeStore = selectedEntityTypeStore;
SelectedEntityStore = selectedEntityStore;
Update();
SelectedEntityStore.SelectedEntityChanged += SelectedEntityStore_SelectedEntityChanged;
}
public void Update()
@@ -64,5 +71,19 @@ internal class EntityFormViewModel : ViewModelBase
#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";
}
@@ -87,6 +87,7 @@ internal class EntityListViewModel : ViewModelBase
set
{
_entities = [.. value];
SelectedEntity = Entities.FirstOrDefault();
OnPropertyChanged();
}
}
@@ -144,55 +145,24 @@ internal class EntityListViewModel : ViewModelBase
#region "Запросы"
/// <summary>
/// Поток, в котором будет выполняться запрос к базе данных.
/// </summary>
private Thread? _queryThread;
public void Reload()
{
Abort();
_queryThread = new(PerformQuery);
_queryThread.Start();
}
/// <summary>
/// Прерывает выполнение потока, если он ещё не завершён.
/// </summary>
public void Abort()
{
_queryThread?.Interrupt();
_queryThread = null;
}
/// <summary>
/// Выполняет запрос к базе данных и обрабатывает результаты.
/// </summary>
private void PerformQuery()
public void Reload()
{
try
{
Pf2eDbContext dbContext = new();
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)
);
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(),
];
}
catch (ThreadInterruptedException)
{
// Ignore
}
EntitiesCount = query.Count();
Entities = [.. query.OrderBy(e => e.Id).Skip((Page - 1) * PerPage).Take(PerPage).ToList()];
}
#endregion
@@ -31,7 +31,6 @@ internal class ViewViewModel : ViewModelBase
{
_rows = value;
OnPropertyChanged();
}
}
private ObservableCollection<StructureRow> _rows = [];
@@ -45,14 +44,12 @@ internal class ViewViewModel : ViewModelBase
Context = context;
SelectedEntityStore = selectedEntityStore;
SelectedEntityStore.SelectedEntityTypeChanged +=
SelectedEntityStore_SelectedEntityTypeChanged;
SelectedEntityStore.SelectedEntityChanged += SelectedEntityStore_SelectedEntityTypeChanged;
}
~ViewViewModel()
{
SelectedEntityStore.SelectedEntityTypeChanged -=
SelectedEntityStore_SelectedEntityTypeChanged;
SelectedEntityStore.SelectedEntityChanged -= SelectedEntityStore_SelectedEntityTypeChanged;
}
private void SelectedEntityStore_SelectedEntityTypeChanged(Pf2eModel.Model.Base.Entity? entity)