Add animated progress bar during price list download.
Show marquee while waiting for the API and determinate progress while saving rows so the UI no longer looks frozen. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
bcd57db153
commit
5e24cc95b0
@ -32,17 +32,49 @@
|
|||||||
this.dataGridView1 = new Elfisa.UI.Controls.ModernDataGridView();
|
this.dataGridView1 = new Elfisa.UI.Controls.ModernDataGridView();
|
||||||
this.DataSetPriceList = new System.Data.DataSet();
|
this.DataSetPriceList = new System.Data.DataSet();
|
||||||
this.btnCloseThis = new Elfisa.UI.Controls.ModernButton();
|
this.btnCloseThis = new Elfisa.UI.Controls.ModernButton();
|
||||||
|
this.lblTitle = new System.Windows.Forms.Label();
|
||||||
|
this.lblProgress = new System.Windows.Forms.Label();
|
||||||
|
this.progressDownload = new System.Windows.Forms.ProgressBar();
|
||||||
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();
|
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();
|
||||||
((System.ComponentModel.ISupportInitialize)(this.DataSetPriceList)).BeginInit();
|
((System.ComponentModel.ISupportInitialize)(this.DataSetPriceList)).BeginInit();
|
||||||
this.SuspendLayout();
|
this.SuspendLayout();
|
||||||
//
|
//
|
||||||
|
// lblTitle
|
||||||
|
//
|
||||||
|
this.lblTitle.AutoSize = true;
|
||||||
|
this.lblTitle.Font = new System.Drawing.Font("Segoe UI Semibold", 12F, System.Drawing.FontStyle.Bold);
|
||||||
|
this.lblTitle.Location = new System.Drawing.Point(22, 14);
|
||||||
|
this.lblTitle.Name = "lblTitle";
|
||||||
|
this.lblTitle.Size = new System.Drawing.Size(220, 28);
|
||||||
|
this.lblTitle.TabIndex = 5;
|
||||||
|
this.lblTitle.Text = "Загрузка прайс-листа";
|
||||||
|
//
|
||||||
|
// progressDownload
|
||||||
|
//
|
||||||
|
this.progressDownload.Location = new System.Drawing.Point(25, 52);
|
||||||
|
this.progressDownload.MarqueeAnimationSpeed = 30;
|
||||||
|
this.progressDownload.Name = "progressDownload";
|
||||||
|
this.progressDownload.Size = new System.Drawing.Size(630, 22);
|
||||||
|
this.progressDownload.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
|
||||||
|
this.progressDownload.TabIndex = 6;
|
||||||
|
//
|
||||||
|
// lblProgress
|
||||||
|
//
|
||||||
|
this.lblProgress.AutoEllipsis = true;
|
||||||
|
this.lblProgress.Font = new System.Drawing.Font("Segoe UI", 10F);
|
||||||
|
this.lblProgress.Location = new System.Drawing.Point(25, 82);
|
||||||
|
this.lblProgress.Name = "lblProgress";
|
||||||
|
this.lblProgress.Size = new System.Drawing.Size(630, 24);
|
||||||
|
this.lblProgress.TabIndex = 7;
|
||||||
|
this.lblProgress.Text = "Подготовка...";
|
||||||
|
//
|
||||||
// rtxtDebug
|
// rtxtDebug
|
||||||
//
|
//
|
||||||
this.rtxtDebug.BackColor = System.Drawing.SystemColors.Control;
|
this.rtxtDebug.BackColor = System.Drawing.SystemColors.Control;
|
||||||
this.rtxtDebug.BorderStyle = System.Windows.Forms.BorderStyle.None;
|
this.rtxtDebug.BorderStyle = System.Windows.Forms.BorderStyle.None;
|
||||||
this.rtxtDebug.Location = new System.Drawing.Point(25, 12);
|
this.rtxtDebug.Location = new System.Drawing.Point(25, 116);
|
||||||
this.rtxtDebug.Name = "rtxtDebug";
|
this.rtxtDebug.Name = "rtxtDebug";
|
||||||
this.rtxtDebug.Size = new System.Drawing.Size(630, 319);
|
this.rtxtDebug.Size = new System.Drawing.Size(630, 215);
|
||||||
this.rtxtDebug.TabIndex = 0;
|
this.rtxtDebug.TabIndex = 0;
|
||||||
this.rtxtDebug.Text = "";
|
this.rtxtDebug.Text = "";
|
||||||
//
|
//
|
||||||
@ -79,6 +111,9 @@
|
|||||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
this.BackColor = System.Drawing.SystemColors.Control;
|
this.BackColor = System.Drawing.SystemColors.Control;
|
||||||
this.ClientSize = new System.Drawing.Size(684, 396);
|
this.ClientSize = new System.Drawing.Size(684, 396);
|
||||||
|
this.Controls.Add(this.lblProgress);
|
||||||
|
this.Controls.Add(this.progressDownload);
|
||||||
|
this.Controls.Add(this.lblTitle);
|
||||||
this.Controls.Add(this.btnCloseThis);
|
this.Controls.Add(this.btnCloseThis);
|
||||||
this.Controls.Add(this.dataGridView1);
|
this.Controls.Add(this.dataGridView1);
|
||||||
this.Controls.Add(this.rtxtDebug);
|
this.Controls.Add(this.rtxtDebug);
|
||||||
@ -93,7 +128,7 @@
|
|||||||
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
|
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
|
||||||
((System.ComponentModel.ISupportInitialize)(this.DataSetPriceList)).EndInit();
|
((System.ComponentModel.ISupportInitialize)(this.DataSetPriceList)).EndInit();
|
||||||
this.ResumeLayout(false);
|
this.ResumeLayout(false);
|
||||||
|
this.PerformLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -102,5 +137,8 @@
|
|||||||
private Elfisa.UI.Controls.ModernDataGridView dataGridView1;
|
private Elfisa.UI.Controls.ModernDataGridView dataGridView1;
|
||||||
private System.Data.DataSet DataSetPriceList;
|
private System.Data.DataSet DataSetPriceList;
|
||||||
private Elfisa.UI.Controls.ModernButton btnCloseThis;
|
private Elfisa.UI.Controls.ModernButton btnCloseThis;
|
||||||
|
private System.Windows.Forms.Label lblTitle;
|
||||||
|
private System.Windows.Forms.Label lblProgress;
|
||||||
|
private System.Windows.Forms.ProgressBar progressDownload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -20,6 +20,7 @@ namespace Электронная_Фармация.HelpForms
|
|||||||
private readonly string _login = Settings.Default.stringLogin ?? string.Empty;
|
private readonly string _login = Settings.Default.stringLogin ?? string.Empty;
|
||||||
private readonly string _password = Settings.Default.stringPassword ?? string.Empty;
|
private readonly string _password = Settings.Default.stringPassword ?? string.Empty;
|
||||||
private readonly string _connectionStringToLocalDb = AppConfig.SqliteConnectionString;
|
private readonly string _connectionStringToLocalDb = AppConfig.SqliteConnectionString;
|
||||||
|
private bool _isDownloading;
|
||||||
|
|
||||||
private async void HF_DownloadDataFromServer_Load(object sender, EventArgs e)
|
private async void HF_DownloadDataFromServer_Load(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
@ -28,6 +29,8 @@ namespace Электронная_Фармация.HelpForms
|
|||||||
dataGridView1.Visible = false;
|
dataGridView1.Visible = false;
|
||||||
rtxtDebug.Visible = true;
|
rtxtDebug.Visible = true;
|
||||||
rtxtDebug.ReadOnly = true;
|
rtxtDebug.ReadOnly = true;
|
||||||
|
_isDownloading = true;
|
||||||
|
SetMarqueeProgress("Подключение к серверу...");
|
||||||
|
|
||||||
var client = new ApiClient();
|
var client = new ApiClient();
|
||||||
AppendStatus($"Сервер: {client.BaseUrl}");
|
AppendStatus($"Сервер: {client.BaseUrl}");
|
||||||
@ -36,40 +39,53 @@ namespace Электронная_Фармация.HelpForms
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
SetMarqueeProgress("Авторизация...");
|
||||||
await EnsureAuthenticatedAsync(client);
|
await EnsureAuthenticatedAsync(client);
|
||||||
|
|
||||||
|
SetMarqueeProgress("Загрузка прайса с сервера...");
|
||||||
AppendStatus("Загружаю сводный прайс (постранично, весь)...");
|
AppendStatus("Загружаю сводный прайс (постранично, весь)...");
|
||||||
var allSuppliersSummary = await GetFullPriceSummaryPagedAsync(client);
|
var allSuppliersSummary = await GetFullPriceSummaryPagedAsync(client);
|
||||||
AppendStatus($"Получено {allSuppliersSummary.Summary?.Count ?? 0} позиций");
|
AppendStatus($"Получено {allSuppliersSummary.Summary?.Count ?? 0} позиций");
|
||||||
|
|
||||||
var tablePrice = BuildPriceTable(allSuppliersSummary);
|
SetMarqueeProgress("Подготовка таблицы...");
|
||||||
SavePriceTable(tablePrice);
|
var tablePrice = await Task.Run(() => BuildPriceTable(allSuppliersSummary));
|
||||||
|
|
||||||
|
SetDeterminateProgress(0, Math.Max(tablePrice.Rows.Count, 1), "Сохранение в локальную базу...");
|
||||||
|
var progress = new Progress<SaveProgress>(ReportSaveProgress);
|
||||||
|
await Task.Run(() => SavePriceTable(tablePrice, progress));
|
||||||
|
|
||||||
|
SetDeterminateProgress(100, 100, "Готово");
|
||||||
DialogResult = DialogResult.OK;
|
DialogResult = DialogResult.OK;
|
||||||
AppendStatus($"Готово. Сохранено {tablePrice.Rows.Count} позиций в локальную базу.");
|
AppendStatus($"Готово. Сохранено {tablePrice.Rows.Count} позиций в локальную базу.");
|
||||||
ToastNotification.ShowSuccess($"Прайс обновлён: {tablePrice.Rows.Count} позиций");
|
ToastNotification.ShowSuccess($"Прайс обновлён: {tablePrice.Rows.Count} позиций");
|
||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException ex)
|
catch (UnauthorizedAccessException ex)
|
||||||
{
|
{
|
||||||
|
SetMarqueeProgress("Ошибка авторизации");
|
||||||
AppendStatus($"Ошибка авторизации: {ex.Message}");
|
AppendStatus($"Ошибка авторизации: {ex.Message}");
|
||||||
AppDebugLog.Error("Download", "Ошибка авторизации при загрузке прайса", ex);
|
AppDebugLog.Error("Download", "Ошибка авторизации при загрузке прайса", ex);
|
||||||
ToastNotification.ShowError($"Ошибка авторизации: {ex.Message}");
|
ToastNotification.ShowError($"Ошибка авторизации: {ex.Message}");
|
||||||
}
|
}
|
||||||
catch (HttpRequestException ex)
|
catch (HttpRequestException ex)
|
||||||
{
|
{
|
||||||
|
SetMarqueeProgress("Ошибка сети");
|
||||||
AppendStatus($"Ошибка сети: {ex.Message}");
|
AppendStatus($"Ошибка сети: {ex.Message}");
|
||||||
AppDebugLog.Error("Download", "Ошибка сети при загрузке прайса", ex);
|
AppDebugLog.Error("Download", "Ошибка сети при загрузке прайса", ex);
|
||||||
ToastNotification.ShowError($"Ошибка сети: {ex.Message}");
|
ToastNotification.ShowError($"Ошибка сети: {ex.Message}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
SetMarqueeProgress("Ошибка загрузки");
|
||||||
AppendStatus($"Ошибка загрузки прайса: {ex.Message}");
|
AppendStatus($"Ошибка загрузки прайса: {ex.Message}");
|
||||||
AppDebugLog.Error("Download", "Неожиданная ошибка при загрузке прайса", ex);
|
AppDebugLog.Error("Download", "Неожиданная ошибка при загрузке прайса", ex);
|
||||||
ToastNotification.ShowError($"Ошибка загрузки прайса: {ex.Message}");
|
ToastNotification.ShowError($"Ошибка загрузки прайса: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isDownloading = false;
|
||||||
btnCloseThis.Enabled = true;
|
btnCloseThis.Enabled = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task EnsureAuthenticatedAsync(ApiClient client)
|
private async Task EnsureAuthenticatedAsync(ApiClient client)
|
||||||
{
|
{
|
||||||
@ -118,6 +134,10 @@ namespace Электронная_Фармация.HelpForms
|
|||||||
for (int page = 0; page < maxPages; page++)
|
for (int page = 0; page < maxPages; page++)
|
||||||
{
|
{
|
||||||
int offset = page * pageSize;
|
int offset = page * pageSize;
|
||||||
|
SetMarqueeProgress(page == 0
|
||||||
|
? "Ожидание ответа сервера..."
|
||||||
|
: $"Загрузка страницы {page + 1}...");
|
||||||
|
|
||||||
PriceSummaryResponse chunk;
|
PriceSummaryResponse chunk;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -126,10 +146,12 @@ namespace Электронная_Фармация.HelpForms
|
|||||||
catch (UnauthorizedAccessException)
|
catch (UnauthorizedAccessException)
|
||||||
{
|
{
|
||||||
AppendStatus("Токен недействителен, повторная авторизация...");
|
AppendStatus("Токен недействителен, повторная авторизация...");
|
||||||
|
SetMarqueeProgress("Повторная авторизация...");
|
||||||
Settings.Default.stringToken = string.Empty;
|
Settings.Default.stringToken = string.Empty;
|
||||||
Settings.Default.Save();
|
Settings.Default.Save();
|
||||||
client.SetToken(null);
|
client.SetToken(null);
|
||||||
await LoginAndSaveTokenAsync(client);
|
await LoginAndSaveTokenAsync(client);
|
||||||
|
SetMarqueeProgress("Повторная загрузка прайса...");
|
||||||
chunk = await client.GetPriceSummaryAsync(supplierId: null, regionId: regionId, limit: pageSize, offset: offset);
|
chunk = await client.GetPriceSummaryAsync(supplierId: null, regionId: regionId, limit: pageSize, offset: offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,6 +160,7 @@ namespace Электронная_Фармация.HelpForms
|
|||||||
{
|
{
|
||||||
combined.Summary.AddRange(chunk.Summary);
|
combined.Summary.AddRange(chunk.Summary);
|
||||||
AppendStatus($"Загружено {combined.Summary.Count} позиций...");
|
AppendStatus($"Загружено {combined.Summary.Count} позиций...");
|
||||||
|
SetMarqueeProgress($"Получено {combined.Summary.Count} позиций...");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (got < pageSize)
|
if (got < pageSize)
|
||||||
@ -215,7 +238,7 @@ namespace Электронная_Фармация.HelpForms
|
|||||||
return tablePrice;
|
return tablePrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SavePriceTable(DataTable tablePrice)
|
private void SavePriceTable(DataTable tablePrice, IProgress<SaveProgress> progress = null)
|
||||||
{
|
{
|
||||||
const string commandToDelete = "delete from [PriceList]";
|
const string commandToDelete = "delete from [PriceList]";
|
||||||
const string commandToDeleteTempOrder = "delete from [TempOrderItems]";
|
const string commandToDeleteTempOrder = "delete from [TempOrderItems]";
|
||||||
@ -253,6 +276,7 @@ VALUES
|
|||||||
@Dosage
|
@Dosage
|
||||||
)";
|
)";
|
||||||
|
|
||||||
|
int total = tablePrice.Rows.Count;
|
||||||
using (var sqliteCon = new SQLiteConnection(_connectionStringToLocalDb))
|
using (var sqliteCon = new SQLiteConnection(_connectionStringToLocalDb))
|
||||||
{
|
{
|
||||||
sqliteCon.Open();
|
sqliteCon.Open();
|
||||||
@ -266,6 +290,7 @@ VALUES
|
|||||||
|
|
||||||
using (var transaction = sqliteCon.BeginTransaction())
|
using (var transaction = sqliteCon.BeginTransaction())
|
||||||
{
|
{
|
||||||
|
int saved = 0;
|
||||||
foreach (DataRow row in tablePrice.Rows)
|
foreach (DataRow row in tablePrice.Rows)
|
||||||
{
|
{
|
||||||
using (var cmd = new SQLiteCommand(commandToInsert, sqliteCon, transaction))
|
using (var cmd = new SQLiteCommand(commandToInsert, sqliteCon, transaction))
|
||||||
@ -286,6 +311,12 @@ VALUES
|
|||||||
cmd.Parameters.AddWithValue("@Dosage", row.Table.Columns.Contains("Dosage") ? row["Dosage"] : DBNull.Value);
|
cmd.Parameters.AddWithValue("@Dosage", row.Table.Columns.Contains("Dosage") ? row["Dosage"] : DBNull.Value);
|
||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saved++;
|
||||||
|
if (progress != null && (saved % 250 == 0 || saved == total))
|
||||||
|
{
|
||||||
|
progress.Report(new SaveProgress(saved, total));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
@ -293,6 +324,65 @@ VALUES
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ReportSaveProgress(SaveProgress state)
|
||||||
|
{
|
||||||
|
if (IsDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int percent = state.Total <= 0 ? 0 : (int)(100.0 * state.Current / state.Total);
|
||||||
|
SetDeterminateProgress(
|
||||||
|
percent,
|
||||||
|
100,
|
||||||
|
$"Сохранение в базу: {state.Current} / {state.Total} ({percent}%)");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetMarqueeProgress(string text)
|
||||||
|
{
|
||||||
|
if (IsDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (InvokeRequired)
|
||||||
|
{
|
||||||
|
BeginInvoke(new Action<string>(SetMarqueeProgress), text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
progressDownload.Style = ProgressBarStyle.Marquee;
|
||||||
|
progressDownload.MarqueeAnimationSpeed = 30;
|
||||||
|
lblProgress.Text = text ?? string.Empty;
|
||||||
|
lblProgress.Refresh();
|
||||||
|
progressDownload.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetDeterminateProgress(int value, int maximum, string text)
|
||||||
|
{
|
||||||
|
if (IsDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (InvokeRequired)
|
||||||
|
{
|
||||||
|
BeginInvoke(new Action<int, int, string>(SetDeterminateProgress), value, maximum, text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progressDownload.Style != ProgressBarStyle.Continuous)
|
||||||
|
{
|
||||||
|
progressDownload.Style = ProgressBarStyle.Continuous;
|
||||||
|
}
|
||||||
|
|
||||||
|
progressDownload.Maximum = Math.Max(maximum, 1);
|
||||||
|
progressDownload.Value = Math.Max(0, Math.Min(value, progressDownload.Maximum));
|
||||||
|
lblProgress.Text = text ?? string.Empty;
|
||||||
|
lblProgress.Refresh();
|
||||||
|
progressDownload.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
private void AppendStatus(string message)
|
private void AppendStatus(string message)
|
||||||
{
|
{
|
||||||
if (InvokeRequired)
|
if (InvokeRequired)
|
||||||
@ -310,13 +400,27 @@ VALUES
|
|||||||
|
|
||||||
private void HF_DownloadDataFromServer_FormClosing(object sender, FormClosingEventArgs e)
|
private void HF_DownloadDataFromServer_FormClosing(object sender, FormClosingEventArgs e)
|
||||||
{
|
{
|
||||||
Dispose();
|
if (_isDownloading)
|
||||||
Close();
|
{
|
||||||
|
e.Cancel = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnCloseThis_Click(object sender, EventArgs e)
|
private void btnCloseThis_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class SaveProgress
|
||||||
|
{
|
||||||
|
public SaveProgress(int current, int total)
|
||||||
|
{
|
||||||
|
Current = current;
|
||||||
|
Total = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Current { get; }
|
||||||
|
public int Total { get; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user