Author SHA1 Message Date
redlineoandClaude Sonnet 5 f04de700d9 Add Settings button to the bulk sender window
The ribbon button only opened SettingsForm while the archive folder
was unconfigured - once saved once, there was no way back into
settings (domain, templates, sender account, signature path) short of
breaking the saved folder path. Added a "Настройки..." button to
PreviewForm that reopens SettingsForm and rescans with the reloaded
settings afterward.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 10:35:56 +03:00
redlineoandClaude Sonnet 5 e40d971de0 Fix crash opening bulk sender window (rows null during InitializeComponent)
Real stack trace from the operator's machine pinned it down: WinForms
fires DataGridView.CellValueChanged as a side effect of
DataGridViewColumn.HeaderText assignments inside InitializeComponent(),
i.e. before PreviewForm's constructor has had a chance to run RunScan()
and populate the `rows` field. UpdateSummaryLabel() then hit
rows.Count(...) on a null source. Nothing to do with Outlook/COM at
all - a pure WinForms initialization-order gotcha.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 10:26:29 +03:00
redlineoandClaude Sonnet 5 f50624023a Show full exception detail in error dialogs instead of just Message
An operator hit "Значение не может быть неопределенным. Имя параметра:
source" (ArgumentNullException, ParamName=source) opening the bulk
sender window on a real Outlook install. A full read-through of every
LINQ call reachable from that path found nothing that could receive a
null source, and this dev machine has no Outlook to reproduce against
- so the next occurrence needs a real stack trace to diagnose instead
of guessing from ex.Message alone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 10:19:09 +03:00
4 changed files with 65 additions and 13 deletions

No files matched your search

@@ -60,7 +60,7 @@ namespace BulkArchiveMailer.AddIn
catch (Exception ex) catch (Exception ex)
{ {
MessageBox.Show( MessageBox.Show(
"Не удалось открыть окно массовой рассылки: " + ex.Message, "Не удалось открыть окно массовой рассылки: " + ex.ToString(),
"BulkArchiveMailer", "BulkArchiveMailer",
MessageBoxButtons.OK, MessageBoxButtons.OK,
MessageBoxIcon.Error); MessageBoxIcon.Error);
@@ -30,6 +30,7 @@ namespace BulkArchiveMailer.AddIn.UI
private System.Windows.Forms.Button btnCheckAllReady; private System.Windows.Forms.Button btnCheckAllReady;
private System.Windows.Forms.Button btnUncheckAll; private System.Windows.Forms.Button btnUncheckAll;
private System.Windows.Forms.Button btnRescan; private System.Windows.Forms.Button btnRescan;
private System.Windows.Forms.Button btnSettings;
private System.Windows.Forms.ProgressBar progressSend; private System.Windows.Forms.ProgressBar progressSend;
private System.Windows.Forms.Label lblProgressStatus; private System.Windows.Forms.Label lblProgressStatus;
@@ -56,6 +57,7 @@ namespace BulkArchiveMailer.AddIn.UI
this.btnCheckAllReady = new System.Windows.Forms.Button(); this.btnCheckAllReady = new System.Windows.Forms.Button();
this.btnUncheckAll = new System.Windows.Forms.Button(); this.btnUncheckAll = new System.Windows.Forms.Button();
this.btnRescan = new System.Windows.Forms.Button(); this.btnRescan = new System.Windows.Forms.Button();
this.btnSettings = new System.Windows.Forms.Button();
this.progressSend = new System.Windows.Forms.ProgressBar(); this.progressSend = new System.Windows.Forms.ProgressBar();
this.lblProgressStatus = new System.Windows.Forms.Label(); this.lblProgressStatus = new System.Windows.Forms.Label();
@@ -70,7 +72,7 @@ namespace BulkArchiveMailer.AddIn.UI
// lblPasswordReminder // lblPasswordReminder
// //
this.lblPasswordReminder.Location = new System.Drawing.Point(12, 10); this.lblPasswordReminder.Location = new System.Drawing.Point(12, 10);
this.lblPasswordReminder.Size = new System.Drawing.Size(900, 20); this.lblPasswordReminder.Size = new System.Drawing.Size(1018, 20);
this.lblPasswordReminder.Text = "Напоминание: пароль от архивов сообщается получателям отдельно (вручную, не через это письмо и не через этот инструмент)."; this.lblPasswordReminder.Text = "Напоминание: пароль от архивов сообщается получателям отдельно (вручную, не через это письмо и не через этот инструмент).";
this.lblPasswordReminder.ForeColor = System.Drawing.Color.DarkRed; this.lblPasswordReminder.ForeColor = System.Drawing.Color.DarkRed;
this.lblPasswordReminder.Font = new System.Drawing.Font(this.lblPasswordReminder.Font, System.Drawing.FontStyle.Bold); this.lblPasswordReminder.Font = new System.Drawing.Font(this.lblPasswordReminder.Font, System.Drawing.FontStyle.Bold);
@@ -80,7 +82,7 @@ namespace BulkArchiveMailer.AddIn.UI
// lblSignatureWarning // lblSignatureWarning
// //
this.lblSignatureWarning.Location = new System.Drawing.Point(12, 32); this.lblSignatureWarning.Location = new System.Drawing.Point(12, 32);
this.lblSignatureWarning.Size = new System.Drawing.Size(900, 20); this.lblSignatureWarning.Size = new System.Drawing.Size(1018, 20);
this.lblSignatureWarning.Text = "Подпись не найдена — письма будут отправлены без подписи. Настройте .htm-файл вручную в Настройках."; this.lblSignatureWarning.Text = "Подпись не найдена — письма будут отправлены без подписи. Настройте .htm-файл вручную в Настройках.";
this.lblSignatureWarning.ForeColor = System.Drawing.Color.DarkOrange; this.lblSignatureWarning.ForeColor = System.Drawing.Color.DarkOrange;
this.lblSignatureWarning.Visible = false; this.lblSignatureWarning.Visible = false;
@@ -90,7 +92,7 @@ namespace BulkArchiveMailer.AddIn.UI
// grid // grid
// //
this.grid.Location = new System.Drawing.Point(12, 58); this.grid.Location = new System.Drawing.Point(12, 58);
this.grid.Size = new System.Drawing.Size(900, 420); this.grid.Size = new System.Drawing.Size(1018, 420);
this.grid.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) this.grid.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
@@ -215,24 +217,34 @@ namespace BulkArchiveMailer.AddIn.UI
this.btnUncheckAll.UseVisualStyleBackColor = true; this.btnUncheckAll.UseVisualStyleBackColor = true;
this.btnUncheckAll.Click += new System.EventHandler(this.btnUncheckAll_Click); this.btnUncheckAll.Click += new System.EventHandler(this.btnUncheckAll_Click);
//
// btnSettings
//
this.btnSettings.Location = new System.Drawing.Point(416, 540);
this.btnSettings.Size = new System.Drawing.Size(110, 27);
this.btnSettings.Text = "Настройки...";
this.btnSettings.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnSettings.UseVisualStyleBackColor = true;
this.btnSettings.Click += new System.EventHandler(this.btnSettings_Click);
// //
// progressSend // progressSend
// //
this.progressSend.Location = new System.Drawing.Point(430, 543); this.progressSend.Location = new System.Drawing.Point(548, 543);
this.progressSend.Size = new System.Drawing.Size(300, 20); this.progressSend.Size = new System.Drawing.Size(300, 20);
this.progressSend.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.progressSend.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
// //
// lblProgressStatus // lblProgressStatus
// //
this.lblProgressStatus.Location = new System.Drawing.Point(430, 565); this.lblProgressStatus.Location = new System.Drawing.Point(548, 565);
this.lblProgressStatus.Size = new System.Drawing.Size(482, 20); this.lblProgressStatus.Size = new System.Drawing.Size(482, 20);
this.lblProgressStatus.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.lblProgressStatus.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
// //
// btnClose // btnClose
// //
this.btnClose.Location = new System.Drawing.Point(720, 540); this.btnClose.Location = new System.Drawing.Point(838, 540);
this.btnClose.Size = new System.Drawing.Size(90, 30); this.btnClose.Size = new System.Drawing.Size(90, 30);
this.btnClose.Text = "Закрыть"; this.btnClose.Text = "Закрыть";
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
@@ -243,7 +255,7 @@ namespace BulkArchiveMailer.AddIn.UI
// //
// btnSend // btnSend
// //
this.btnSend.Location = new System.Drawing.Point(816, 540); this.btnSend.Location = new System.Drawing.Point(934, 540);
this.btnSend.Size = new System.Drawing.Size(96, 30); this.btnSend.Size = new System.Drawing.Size(96, 30);
this.btnSend.Text = "Отправить"; this.btnSend.Text = "Отправить";
this.btnSend.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.btnSend.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
@@ -256,8 +268,8 @@ namespace BulkArchiveMailer.AddIn.UI
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(924, 582); this.ClientSize = new System.Drawing.Size(1042, 582);
this.MinimumSize = new System.Drawing.Size(760, 420); this.MinimumSize = new System.Drawing.Size(878, 420);
this.Controls.Add(this.lblPasswordReminder); this.Controls.Add(this.lblPasswordReminder);
this.Controls.Add(this.lblSignatureWarning); this.Controls.Add(this.lblSignatureWarning);
this.Controls.Add(this.grid); this.Controls.Add(this.grid);
@@ -266,6 +278,7 @@ namespace BulkArchiveMailer.AddIn.UI
this.Controls.Add(this.btnRescan); this.Controls.Add(this.btnRescan);
this.Controls.Add(this.btnCheckAllReady); this.Controls.Add(this.btnCheckAllReady);
this.Controls.Add(this.btnUncheckAll); this.Controls.Add(this.btnUncheckAll);
this.Controls.Add(this.btnSettings);
this.Controls.Add(this.progressSend); this.Controls.Add(this.progressSend);
this.Controls.Add(this.lblProgressStatus); this.Controls.Add(this.lblProgressStatus);
this.Controls.Add(this.btnClose); this.Controls.Add(this.btnClose);
@@ -39,7 +39,7 @@ namespace BulkArchiveMailer.AddIn.UI
} }
catch (Exception ex) catch (Exception ex)
{ {
MessageBox.Show(this, "Не удалось прочитать настройки: " + ex.Message, "BulkArchiveMailer", MessageBoxButtons.OK, MessageBoxIcon.Error); MessageBox.Show(this, "Не удалось прочитать настройки: " + ex.ToString(), "BulkArchiveMailer", MessageBoxButtons.OK, MessageBoxIcon.Error);
Close(); Close();
return; return;
} }
@@ -128,6 +128,17 @@ namespace BulkArchiveMailer.AddIn.UI
private void UpdateSummaryLabel() private void UpdateSummaryLabel()
{ {
if (rows == null)
{
// Can be invoked before RunScan() ever ran - most notably,
// WinForms fires DataGridView.CellValueChanged as a side
// effect of DataGridViewColumn.HeaderText assignments in
// InitializeComponent(), i.e. before the constructor (and
// therefore RunScan()) has had a chance to populate `rows`.
// Nothing to summarize yet in that case.
return;
}
var ready = rows.Count(r => r.Job.Status == SendStatus.Ready); var ready = rows.Count(r => r.Job.Status == SendStatus.Ready);
var already = rows.Count(r => r.Job.Status == SendStatus.AlreadySent); var already = rows.Count(r => r.Job.Status == SendStatus.AlreadySent);
var errors = rows.Count(r => r.Job.Status == SendStatus.Error); var errors = rows.Count(r => r.Job.Status == SendStatus.Error);
@@ -161,6 +172,34 @@ namespace BulkArchiveMailer.AddIn.UI
RunScan(); RunScan();
} }
private void btnSettings_Click(object sender, EventArgs e)
{
using (var settingsForm = new SettingsForm(outlookApplication))
{
settingsForm.ShowDialog(this);
}
// Regardless of DialogResult (OK or Cancel), reload settings and
// rescan so a changed archive folder, domain, or templates are
// reflected immediately without a separate "Пересканировать"
// click. A closed-without-saving dialog just reloads the same
// settings again, which is harmless.
try
{
settings = AppSettings.Load();
}
catch (Exception ex)
{
MessageBox.Show(this, "Не удалось прочитать настройки: " + ex.ToString(), "BulkArchiveMailer", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
signatureHtml = ResolveSignatureHtml();
lblSignatureWarning.Visible = string.IsNullOrEmpty(signatureHtml);
RunScan();
}
private void btnCheckAllReady_Click(object sender, EventArgs e) private void btnCheckAllReady_Click(object sender, EventArgs e)
{ {
foreach (var row in rows) foreach (var row in rows)
@@ -31,7 +31,7 @@ namespace BulkArchiveMailer.AddIn.UI
catch (Exception ex) catch (Exception ex)
{ {
MessageBox.Show( MessageBox.Show(
"Не удалось прочитать config.json (" + AppSettings.ConfigFilePath + "):\n" + ex.Message + "Не удалось прочитать config.json (" + AppSettings.ConfigFilePath + "):\n" + ex.ToString() +
"\n\nБудут показаны пустые настройки — сохранение перезапишет файл заново.", "\n\nБудут показаны пустые настройки — сохранение перезапишет файл заново.",
"BulkArchiveMailer", "BulkArchiveMailer",
MessageBoxButtons.OK, MessageBoxButtons.OK,
@@ -231,7 +231,7 @@ namespace BulkArchiveMailer.AddIn.UI
} }
catch (Exception ex) catch (Exception ex)
{ {
MessageBox.Show(this, "Не удалось сохранить настройки: " + ex.Message, "BulkArchiveMailer", MessageBoxButtons.OK, MessageBoxIcon.Error); MessageBox.Show(this, "Не удалось сохранить настройки: " + ex.ToString(), "BulkArchiveMailer", MessageBoxButtons.OK, MessageBoxIcon.Error);
return; return;
} }