Завершение обновления формы отображения

This commit is contained in:
2025-05-05 14:34:56 +04:00
parent c704bc551b
commit 2c1756cef6
14 changed files with 450 additions and 430 deletions
@@ -0,0 +1,76 @@
using System.Collections.Concurrent;
using Pf2eModel;
using Pf2eModel.Model.Base;
using Pf2eModel.Model.Enum;
namespace Pf2eViewer.DatabaseViewer;
internal class DatabaseQueryPerformer
{
public required EntityType Type;
public required int Page;
public required int PerPage;
public required string Search = "";
private readonly Thread _queryThread;
public DatabaseQueryPerformer()
{
_queryThread = new(PerformQuery);
}
public void Query()
{
_queryThread.Start();
}
public void Abort()
{
_queryThread.Interrupt();
}
private void PerformQuery()
{
try
{
Pf2eDbContext dbContext = new();
IQueryable<Entity> query =
Search == ""
? dbContext.Entities.Where(e => e.Type == Type)
:
// ReSharper disable once EntityFramework.UnsupportedServerSideFunctionCall
dbContext.Entities.Where(e =>
e.Type == Type && Pf2eDbContext.LocaleSearch(e.Name, Search)
);
int count = query.Count();
List<Entity> entities = query.Skip((Page - 1) * PerPage).Take(PerPage).ToList();
DatabaseQueryResult result = new(count, entities);
OnQueryCompleted?.Invoke(this, result);
}
catch (ThreadInterruptedException)
{
// Ignore
}
}
public delegate void DatabaseQueryPerformerDelegate(
DatabaseQueryPerformer sender,
DatabaseQueryResult result
);
public event DatabaseQueryPerformerDelegate? OnQueryCompleted;
}
internal class DatabaseQueryResult(int count, ICollection<Entity> entities)
{
public int Count { get; } = count;
public ConcurrentBag<Entity> Entities { get; } = new(entities);
}
@@ -0,0 +1,148 @@
namespace Pf2eViewer.DatabaseViewer;
partial class DatabaseViewerControl
{
/// <summary>
/// Обязательная переменная конструктора.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Освободить все используемые ресурсы.
/// </summary>
/// <param name="disposing">истинно, если управляемый ресурс должен быть удален; иначе ложно.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Код, автоматически созданный конструктором компонентов
/// <summary>
/// Требуемый метод для поддержки конструктора — не изменяйте
/// содержимое этого метода с помощью редактора кода.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
BindingNavigator = new CoreBindingNavigator();
SearchTextBox = new TextBox();
DataGridView = new DataGridView();
DbBindingSource = new BindingSource(components);
PagesBindingSource = new BindingSource(components);
Id = new DataGridViewTextBoxColumn();
EntityName = new DataGridViewTextBoxColumn();
Rarity = new DataGridViewTextBoxColumn();
(BindingNavigator).BeginInit();
((System.ComponentModel.ISupportInitialize)DataGridView).BeginInit();
((System.ComponentModel.ISupportInitialize)DbBindingSource).BeginInit();
((System.ComponentModel.ISupportInitialize)PagesBindingSource).BeginInit();
SuspendLayout();
//
// BindingNavigator
//
BindingNavigator.AddNewItem = null;
BindingNavigator.DeleteItem = null;
BindingNavigator.ImeMode = ImeMode.NoControl;
BindingNavigator.LayoutStyle = ToolStripLayoutStyle.HorizontalStackWithOverflow;
BindingNavigator.Location = new Point(0, 0);
BindingNavigator.Name = "BindingNavigator";
BindingNavigator.Size = new Size(345, 25);
BindingNavigator.Stretch = true;
BindingNavigator.TabIndex = 1;
BindingNavigator.Text = "coreBindingNavigator1";
BindingNavigator.RefreshItems += BindingNavigator_RefreshItems;
//
// SearchTextBox
//
SearchTextBox.Dock = DockStyle.Top;
SearchTextBox.Location = new Point(0, 25);
SearchTextBox.Name = "SearchTextBox";
SearchTextBox.PlaceholderText = "Поиск";
SearchTextBox.Size = new Size(345, 23);
SearchTextBox.TabIndex = 3;
SearchTextBox.TextChanged += SearchTextBox_TextChanged;
SearchTextBox.Leave += SearchTextBox_Leave;
//
// DataGridView
//
DataGridView.AllowUserToAddRows = false;
DataGridView.AllowUserToDeleteRows = false;
DataGridView.AutoGenerateColumns = false;
DataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCellsExceptHeader;
DataGridView.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCellsExceptHeaders;
DataGridView.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
DataGridView.Columns.AddRange(new DataGridViewColumn[] { Id, EntityName, Rarity });
DataGridView.DataSource = DbBindingSource;
DataGridView.Dock = DockStyle.Fill;
DataGridView.Location = new Point(0, 48);
DataGridView.MultiSelect = false;
DataGridView.Name = "DataGridView";
DataGridView.ReadOnly = true;
DataGridView.RowHeadersVisible = false;
DataGridView.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
DataGridView.Size = new Size(345, 345);
DataGridView.TabIndex = 4;
DataGridView.RowStateChanged += DataGridView_RowStateChanged;
//
// Id
//
Id.DataPropertyName = "Id";
Id.HeaderText = "Id";
Id.Name = "Id";
Id.ReadOnly = true;
Id.Resizable = DataGridViewTriState.False;
Id.SortMode = DataGridViewColumnSortMode.NotSortable;
Id.Width = 5;
//
// EntityName
//
EntityName.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
EntityName.DataPropertyName = "Name";
EntityName.HeaderText = "Name";
EntityName.Name = "EntityName";
EntityName.ReadOnly = true;
EntityName.Resizable = DataGridViewTriState.False;
EntityName.SortMode = DataGridViewColumnSortMode.NotSortable;
//
// Rarity
//
Rarity.DataPropertyName = "Rarity";
Rarity.HeaderText = "Rarity";
Rarity.Name = "Rarity";
Rarity.ReadOnly = true;
Rarity.Resizable = DataGridViewTriState.False;
Rarity.SortMode = DataGridViewColumnSortMode.NotSortable;
Rarity.Width = 5;
//
// DatabaseViewerControl
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
Controls.Add(DataGridView);
Controls.Add(SearchTextBox);
Controls.Add(BindingNavigator);
Name = "DatabaseViewerControl";
Size = new Size(345, 393);
(BindingNavigator).EndInit();
((System.ComponentModel.ISupportInitialize)DataGridView).EndInit();
((System.ComponentModel.ISupportInitialize)DbBindingSource).EndInit();
((System.ComponentModel.ISupportInitialize)PagesBindingSource).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
private CoreBindingNavigator BindingNavigator;
private TextBox SearchTextBox;
private DataGridView DataGridView;
private BindingSource DbBindingSource;
private BindingSource PagesBindingSource;
private DataGridViewTextBoxColumn Id;
private DataGridViewTextBoxColumn EntityName;
private DataGridViewTextBoxColumn Rarity;
}
@@ -0,0 +1,271 @@
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Pf2eModel;
using Pf2eModel.Model.Base;
using Pf2eModel.Model.Common.I18n;
using Pf2eModel.Model.Enum;
using Timer = System.Windows.Forms.Timer;
namespace Pf2eViewer.DatabaseViewer;
public partial class DatabaseViewerControl : UserControl
{
public readonly Pf2eDbContext Context = new();
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public int PerPage
{
get => _perPage;
set
{
_perPage = value;
UpdateView();
}
}
private int _perPage = 30;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public int Page
{
get => _page;
set
{
_page = value;
UpdateView();
}
}
private int _page = 1;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public EntityType Type
{
get => _type;
set
{
_page = 1;
_type = value;
UpdateView();
}
}
private EntityType _type;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public int Count { get; private set; }
public int MaxPage => Convert.ToInt32(Math.Ceiling(1.0 * Count / PerPage));
[AllowNull]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public EntityView CurrentEntity { get; private set; }
private bool _isBusy = true;
private DatabaseQueryPerformer? _performer;
private Timer? _typingTimer;
private string _lastSearch = "";
public DatabaseViewerControl()
{
InitializeComponent();
BindingNavigator.BindingSource = DbBindingSource;
//DataGridView.AutoGenerateColumns = true;
DataGridView.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
_isBusy = false;
}
// Control events
private void BindingNavigator_RefreshItems(object sender, EventArgs e)
{
if (_isBusy)
{
return;
}
Page = (int)(BindingNavigator?.BindingSource?.Current ?? 1);
UpdateView();
}
private void DataGridView_RowStateChanged(object sender, DataGridViewRowStateChangedEventArgs e)
{
if (_isBusy)
{
return;
}
UpdateEntity();
}
private void SearchTextBox_Leave(object sender, EventArgs e)
{
if (_isBusy || SearchTextBox.Text == _lastSearch)
{
return;
}
UpdateView();
}
private void SearchTextBox_TextChanged(object sender, EventArgs e)
{
if (_isBusy)
{
return;
}
if (_typingTimer == null)
{
_typingTimer = new();
_typingTimer.Interval = 3000;
_typingTimer.Tick += HandleTypingTimerTimeout;
}
_typingTimer.Stop(); // Resets the timer
_typingTimer.Tag = (sender as TextBox)!.Text; // This should be done with EventArgs
_typingTimer.Start();
}
private void HandleTypingTimerTimeout(object? sender, EventArgs e)
{
if (_isBusy || sender is not Timer timer)
{
return;
}
UpdateView();
timer.Stop();
}
// Updater
private void SetBusyStatus(bool busy)
{
if (InvokeRequired)
{
Invoke(SetBusyStatus, busy);
return;
}
_isBusy = busy;
BindingNavigator.Enabled = !busy;
DataGridView.Enabled = !busy;
SearchTextBox.Enabled = !busy;
DataGridView.ColumnHeadersHeightSizeMode = !busy
? DataGridViewColumnHeadersHeightSizeMode.AutoSize
: DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
}
public void UpdateView(bool force = false)
{
if (_isBusy && !force)
{
return;
}
SetBusyStatus(true);
_performer?.Abort();
_performer = new()
{
Type = Type,
Page = Page,
PerPage = PerPage,
Search = SearchTextBox.Text,
};
_performer.OnQueryCompleted += HandleQueryCompleted;
_performer.Query();
}
private void HandleQueryCompleted(DatabaseQueryPerformer sender, DatabaseQueryResult result)
{
if (sender != _performer)
{
return;
}
Count = result.Count;
_lastSearch = SearchTextBox.Text;
InvokeControl(
BindingNavigator,
b =>
{
PagesBindingSource.DataSource = Enumerable.Range(1, MaxPage).ToList();
b.BindingSource = PagesBindingSource;
}
);
InvokeControl(
DataGridView,
view =>
{
view.DataSource = result.Entities.Select(e => new EntityGridRow(e)).ToList();
SetBusyStatus(false);
if (view.Rows.Count > 0)
{
view.Rows[0].Selected = true;
UpdateEntity();
}
}
);
}
private void InvokeControl<T>(T control, Action<T> action)
where T : Control
{
if (control.InvokeRequired)
{
control.Invoke(action, control);
}
else
{
action(control);
}
}
private void UpdateEntity()
{
if (DataGridView.SelectedRows.Count == 0)
{
return;
}
EntityGridRow? data = (EntityGridRow?)DataGridView.SelectedRows[0].DataBoundItem;
try
{
CurrentEntity = data is not null ? new(Context, Type, data.Id) : null;
}
catch (Exception)
{
// ignored
}
EntityChange?.Invoke(CurrentEntity);
}
// Output events
public delegate void EntityChangeHandler(EntityView newEntity);
public event EntityChangeHandler? EntityChange;
internal class EntityGridRow(Entity e)
{
public int Id { get; set; } = e.Id;
public string Name { get; set; } = e.Name[LocaleType.English];
public EntityRarity Rarity { get; set; } = e.Rarity;
}
}
@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="Id.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Rarity.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="DbBindingSource.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="PagesBindingSource.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>163, 17</value>
</metadata>
</root>