From 5e24cc95b025a39f12b714528ba7247375c4a9c6 Mon Sep 17 00:00:00 2001 From: Magomed Date: Sat, 18 Jul 2026 13:27:42 +0300 Subject: [PATCH] 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 --- .../HF_DownloadDataFromServer.Designer.cs | 46 ++++++- .../HelpForms/HF_DownloadDataFromServer.cs | 118 ++++++++++++++++-- 2 files changed, 153 insertions(+), 11 deletions(-) diff --git a/src/ElectronicPharmacy/HelpForms/HF_DownloadDataFromServer.Designer.cs b/src/ElectronicPharmacy/HelpForms/HF_DownloadDataFromServer.Designer.cs index e68f899..d47487a 100644 --- a/src/ElectronicPharmacy/HelpForms/HF_DownloadDataFromServer.Designer.cs +++ b/src/ElectronicPharmacy/HelpForms/HF_DownloadDataFromServer.Designer.cs @@ -32,17 +32,49 @@ this.dataGridView1 = new Elfisa.UI.Controls.ModernDataGridView(); this.DataSetPriceList = new System.Data.DataSet(); 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.DataSetPriceList)).BeginInit(); 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 // this.rtxtDebug.BackColor = System.Drawing.SystemColors.Control; 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.Size = new System.Drawing.Size(630, 319); + this.rtxtDebug.Size = new System.Drawing.Size(630, 215); this.rtxtDebug.TabIndex = 0; this.rtxtDebug.Text = ""; // @@ -79,6 +111,9 @@ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.BackColor = System.Drawing.SystemColors.Control; 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.dataGridView1); this.Controls.Add(this.rtxtDebug); @@ -93,7 +128,7 @@ ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.DataSetPriceList)).EndInit(); this.ResumeLayout(false); - + this.PerformLayout(); } #endregion @@ -102,5 +137,8 @@ private Elfisa.UI.Controls.ModernDataGridView dataGridView1; private System.Data.DataSet DataSetPriceList; private Elfisa.UI.Controls.ModernButton btnCloseThis; + private System.Windows.Forms.Label lblTitle; + private System.Windows.Forms.Label lblProgress; + private System.Windows.Forms.ProgressBar progressDownload; } -} \ No newline at end of file +} diff --git a/src/ElectronicPharmacy/HelpForms/HF_DownloadDataFromServer.cs b/src/ElectronicPharmacy/HelpForms/HF_DownloadDataFromServer.cs index 6f0a8e0..95b4dae 100644 --- a/src/ElectronicPharmacy/HelpForms/HF_DownloadDataFromServer.cs +++ b/src/ElectronicPharmacy/HelpForms/HF_DownloadDataFromServer.cs @@ -20,6 +20,7 @@ namespace Электронная_Фармация.HelpForms private readonly string _login = Settings.Default.stringLogin ?? string.Empty; private readonly string _password = Settings.Default.stringPassword ?? string.Empty; private readonly string _connectionStringToLocalDb = AppConfig.SqliteConnectionString; + private bool _isDownloading; private async void HF_DownloadDataFromServer_Load(object sender, EventArgs e) { @@ -28,6 +29,8 @@ namespace Электронная_Фармация.HelpForms dataGridView1.Visible = false; rtxtDebug.Visible = true; rtxtDebug.ReadOnly = true; + _isDownloading = true; + SetMarqueeProgress("Подключение к серверу..."); var client = new ApiClient(); AppendStatus($"Сервер: {client.BaseUrl}"); @@ -36,39 +39,52 @@ namespace Электронная_Фармация.HelpForms try { + SetMarqueeProgress("Авторизация..."); await EnsureAuthenticatedAsync(client); + SetMarqueeProgress("Загрузка прайса с сервера..."); AppendStatus("Загружаю сводный прайс (постранично, весь)..."); var allSuppliersSummary = await GetFullPriceSummaryPagedAsync(client); AppendStatus($"Получено {allSuppliersSummary.Summary?.Count ?? 0} позиций"); - var tablePrice = BuildPriceTable(allSuppliersSummary); - SavePriceTable(tablePrice); + SetMarqueeProgress("Подготовка таблицы..."); + var tablePrice = await Task.Run(() => BuildPriceTable(allSuppliersSummary)); + SetDeterminateProgress(0, Math.Max(tablePrice.Rows.Count, 1), "Сохранение в локальную базу..."); + var progress = new Progress(ReportSaveProgress); + await Task.Run(() => SavePriceTable(tablePrice, progress)); + + SetDeterminateProgress(100, 100, "Готово"); DialogResult = DialogResult.OK; AppendStatus($"Готово. Сохранено {tablePrice.Rows.Count} позиций в локальную базу."); ToastNotification.ShowSuccess($"Прайс обновлён: {tablePrice.Rows.Count} позиций"); } catch (UnauthorizedAccessException ex) { + SetMarqueeProgress("Ошибка авторизации"); AppendStatus($"Ошибка авторизации: {ex.Message}"); AppDebugLog.Error("Download", "Ошибка авторизации при загрузке прайса", ex); ToastNotification.ShowError($"Ошибка авторизации: {ex.Message}"); } catch (HttpRequestException ex) { + SetMarqueeProgress("Ошибка сети"); AppendStatus($"Ошибка сети: {ex.Message}"); AppDebugLog.Error("Download", "Ошибка сети при загрузке прайса", ex); ToastNotification.ShowError($"Ошибка сети: {ex.Message}"); } catch (Exception ex) { + SetMarqueeProgress("Ошибка загрузки"); AppendStatus($"Ошибка загрузки прайса: {ex.Message}"); AppDebugLog.Error("Download", "Неожиданная ошибка при загрузке прайса", ex); ToastNotification.ShowError($"Ошибка загрузки прайса: {ex.Message}"); } - - btnCloseThis.Enabled = true; + finally + { + _isDownloading = false; + btnCloseThis.Enabled = true; + } } private async Task EnsureAuthenticatedAsync(ApiClient client) @@ -118,6 +134,10 @@ namespace Электронная_Фармация.HelpForms for (int page = 0; page < maxPages; page++) { int offset = page * pageSize; + SetMarqueeProgress(page == 0 + ? "Ожидание ответа сервера..." + : $"Загрузка страницы {page + 1}..."); + PriceSummaryResponse chunk; try { @@ -126,10 +146,12 @@ namespace Электронная_Фармация.HelpForms catch (UnauthorizedAccessException) { AppendStatus("Токен недействителен, повторная авторизация..."); + SetMarqueeProgress("Повторная авторизация..."); Settings.Default.stringToken = string.Empty; Settings.Default.Save(); client.SetToken(null); await LoginAndSaveTokenAsync(client); + SetMarqueeProgress("Повторная загрузка прайса..."); chunk = await client.GetPriceSummaryAsync(supplierId: null, regionId: regionId, limit: pageSize, offset: offset); } @@ -138,6 +160,7 @@ namespace Электронная_Фармация.HelpForms { combined.Summary.AddRange(chunk.Summary); AppendStatus($"Загружено {combined.Summary.Count} позиций..."); + SetMarqueeProgress($"Получено {combined.Summary.Count} позиций..."); } if (got < pageSize) @@ -215,7 +238,7 @@ namespace Электронная_Фармация.HelpForms return tablePrice; } - private void SavePriceTable(DataTable tablePrice) + private void SavePriceTable(DataTable tablePrice, IProgress progress = null) { const string commandToDelete = "delete from [PriceList]"; const string commandToDeleteTempOrder = "delete from [TempOrderItems]"; @@ -253,6 +276,7 @@ VALUES @Dosage )"; + int total = tablePrice.Rows.Count; using (var sqliteCon = new SQLiteConnection(_connectionStringToLocalDb)) { sqliteCon.Open(); @@ -266,6 +290,7 @@ VALUES using (var transaction = sqliteCon.BeginTransaction()) { + int saved = 0; foreach (DataRow row in tablePrice.Rows) { 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.ExecuteNonQuery(); } + + saved++; + if (progress != null && (saved % 250 == 0 || saved == total)) + { + progress.Report(new SaveProgress(saved, total)); + } } 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(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(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) { if (InvokeRequired) @@ -310,13 +400,27 @@ VALUES private void HF_DownloadDataFromServer_FormClosing(object sender, FormClosingEventArgs e) { - Dispose(); - Close(); + if (_isDownloading) + { + e.Cancel = true; + } } private void btnCloseThis_Click(object sender, EventArgs e) { Close(); } + + private sealed class SaveProgress + { + public SaveProgress(int current, int total) + { + Current = current; + Total = total; + } + + public int Current { get; } + public int Total { get; } + } } }