391 lines
12 KiB
C#
391 lines
12 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Класс элемента управления, который отображает сущности в виде таблицы.
|
|
/// </summary>
|
|
public partial class DatabaseViewerControl : UserControl
|
|
{
|
|
#region "Поля и свойства"
|
|
|
|
/// <summary>
|
|
/// Контекст базы данных, который используется для получения сущностей.
|
|
/// </summary>
|
|
public readonly Pf2eDbContext Context = new();
|
|
|
|
/// <summary>
|
|
/// Количество элементов на странице.
|
|
/// </summary>
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public int PerPage
|
|
{
|
|
get => _perPage;
|
|
set
|
|
{
|
|
_perPage = value;
|
|
UpdateView();
|
|
}
|
|
}
|
|
private int _perPage = 30;
|
|
|
|
/// <summary>
|
|
/// Номер страницы, которую необходимо отобразить.
|
|
/// </summary>
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public int Page
|
|
{
|
|
get => _page;
|
|
set
|
|
{
|
|
_page = value;
|
|
UpdateView();
|
|
}
|
|
}
|
|
private int _page = 1;
|
|
|
|
/// <summary>
|
|
/// Тип сущности, которую необходимо отобразить.
|
|
/// </summary>
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public EntityType Type
|
|
{
|
|
get => _type;
|
|
set
|
|
{
|
|
_page = 1;
|
|
_type = value;
|
|
UpdateView();
|
|
}
|
|
}
|
|
private EntityType _type;
|
|
|
|
/// <summary>
|
|
/// Количество элементов, которые были получены из базы данных.
|
|
/// </summary>
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public int Count { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Номер последней страницы, которая может быть отображена.
|
|
/// </summary>
|
|
public int MaxPage => Convert.ToInt32(Math.Ceiling(1.0 * Count / PerPage));
|
|
|
|
/// <summary>
|
|
/// Выбранная в элементе управления сущность.
|
|
/// </summary>
|
|
[AllowNull]
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public EntityView CurrentEntity { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Флаг, указывающий, что элемент управления занят выполнением запроса к базе данных.
|
|
/// </summary>
|
|
private bool _isBusy = true;
|
|
|
|
/// <summary>
|
|
/// Объект, который выполняет запрос к базе данных.
|
|
/// </summary>
|
|
private DatabaseQueryPerformer? _performer;
|
|
|
|
/// <summary>
|
|
/// Таймер, который используется для задержки обработки события изменения текста в поле поиска.
|
|
/// </summary>
|
|
private Timer? _typingTimer;
|
|
|
|
/// <summary>
|
|
/// Строка, которая хранит последнее значение, введенное в поле поиска.
|
|
/// </summary>
|
|
private string _lastSearch = "";
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Конструктор класса, который инициализирует элемент управления и связывает его с источником данных.
|
|
/// </summary>
|
|
public DatabaseViewerControl()
|
|
{
|
|
InitializeComponent();
|
|
|
|
BindingNavigator.BindingSource = DbBindingSource;
|
|
//DataGridView.AutoGenerateColumns = true;
|
|
DataGridView.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
|
|
|
_isBusy = false;
|
|
}
|
|
|
|
#region "События элемента управления"
|
|
|
|
/// <summary>
|
|
/// Обработчик события, который вызывается при изменении текущей страницы в элементе управления.
|
|
/// </summary>
|
|
/// <param name="sender">Источник события.</param>
|
|
/// <param name="e">Объект, содержащий данные события.</param>
|
|
private void BindingNavigator_RefreshItems(object sender, EventArgs e)
|
|
{
|
|
if (_isBusy)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Page = (int)(BindingNavigator?.BindingSource?.Current ?? 1);
|
|
UpdateView();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Обработчик события, который вызывается при изменении выделенной строки в элементе управления DataGridView.
|
|
/// </summary>
|
|
/// <param name="sender">Источник события.</param>
|
|
/// <param name="e">Объект, содержащий данные события.</param>
|
|
private void DataGridView_RowStateChanged(object sender, DataGridViewRowStateChangedEventArgs e)
|
|
{
|
|
if (_isBusy)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UpdateEntity();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Обработчик события, который вызывается когда поле поиска теряет фокус.
|
|
/// </summary>
|
|
/// <param name="sender">Источник события.</param>
|
|
/// <param name="e">Объект, содержащий данные события.</param>
|
|
private void SearchTextBox_Leave(object sender, EventArgs e)
|
|
{
|
|
if (_isBusy || SearchTextBox.Text == _lastSearch)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UpdateView();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Обработчик события, который вызывается при изменении текста в поле поиска.
|
|
/// </summary>
|
|
/// <param name="sender">Источник события.</param>
|
|
/// <param name="e">Объект, содержащий данные события.</param>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Обработчик события, который вызывается при истечении времени таймера ввода текста.
|
|
/// </summary>
|
|
/// <param name="sender">Источник события.</param>
|
|
/// <param name="e">Объект, содержащий данные события.</param>
|
|
private void HandleTypingTimerTimeout(object? sender, EventArgs e)
|
|
{
|
|
if (_isBusy || sender is not Timer timer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UpdateView();
|
|
timer.Stop();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region "Обновление данных"
|
|
|
|
/// <summary>
|
|
/// Устанавливает статус занятости элемента управления.
|
|
/// </summary>
|
|
/// <param name="busy">Новый статус занятости.</param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Обновляет данные в элементе управления.
|
|
/// </summary>
|
|
/// <param name="force">Проигнорировать флаг занятости.</param>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Обработчик события, который вызывается по завершении выполнения запроса к базе данных.
|
|
/// </summary>
|
|
/// <param name="sender">Источник события.</param>
|
|
/// <param name="result">Результат выполнения события</param>
|
|
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))
|
|
.OrderBy(r => r.Id)
|
|
.ToList();
|
|
|
|
SetBusyStatus(false);
|
|
|
|
if (view.Rows.Count > 0)
|
|
{
|
|
view.Rows[0].Selected = true;
|
|
UpdateEntity();
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Вызывает действие на указанном элементе управления в нужном потоке , если это необходимо.
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <param name="control"></param>
|
|
/// <param name="action"></param>
|
|
private void InvokeControl<T>(T control, Action<T> action)
|
|
where T : Control
|
|
{
|
|
if (control.InvokeRequired)
|
|
{
|
|
control.Invoke(action, control);
|
|
}
|
|
else
|
|
{
|
|
action(control);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Обновить сущность, которая была выбрана в элементе управления DataGridView.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Output events
|
|
|
|
/// <summary>
|
|
/// Делегат, который будет вызван при изменении выбранной сущности.
|
|
/// </summary>
|
|
/// <param name="newEntity">Новая выбранная сущность.</param>
|
|
public delegate void EntityChangeHandler(EntityView newEntity);
|
|
|
|
/// <summary>
|
|
/// Событие, которое вызывается при изменении выбранной сущности.
|
|
/// </summary>
|
|
public event EntityChangeHandler? EntityChange;
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Класс, который представляет строку в таблице сущностей.
|
|
/// </summary>
|
|
/// <param name="entity">Сущность, связанная со строкой</param>
|
|
internal class EntityGridRow(Entity entity)
|
|
{
|
|
/// <summary>
|
|
/// Идентификатор сущности.
|
|
/// </summary>
|
|
public int Id { get; set; } = entity.Id;
|
|
|
|
/// <summary>
|
|
/// Название сущности.
|
|
/// </summary>
|
|
public string Name { get; set; } = entity.Name[LocaleType.English];
|
|
|
|
/// <summary>
|
|
/// Редкость сущности.
|
|
/// </summary>
|
|
public EntityRarity Rarity { get; set; } = entity.Rarity;
|
|
}
|
|
}
|