<Fix>: Prevent component configuration crashes, add a copyable error dialog, increase revision to 3.9.3145.109

Cause:

* A configuration control can reject an incompatible configuration and return null.
* ComponentsManagerDlg accessed the rejected configuration without validation, causing a NullReferenceException and closing TBF.
* The standard MessageBox was difficult to read and did not provide convenient access to technical details.

Solution:

1. Added configuration validation

   * Validates the configuration returned by the component control.
   * Logs the rejected control and configuration types.
   * Cancels the add operation without terminating TBF.

2. Added a reusable error details dialog

   * Displays a clear error summary and structured technical details.
   * Allows the diagnostic text to be selected and copied.
   * Added Copy and Close actions and registered the new form in TBF.csproj.

3. Increased revision

   * Updated revision to 3.9.3145.109.
This commit is contained in:
2026-09-09 10:09:49 +02:00
parent 8677e49680
commit eb4f7ad971
5 changed files with 372 additions and 3 deletions
+10
View File
@@ -3387,6 +3387,12 @@
<Compile Include="UI\Shared\ErrorDlg.designer.cs">
<DependentUpon>ErrorDlg.cs</DependentUpon>
</Compile>
<Compile Include="UI\Shared\ErrorDetailsDlg.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="UI\Shared\ErrorDetailsDlg.Designer.cs">
<DependentUpon>ErrorDetailsDlg.cs</DependentUpon>
</Compile>
<Compile Include="UI\Shared\IParentOfListViewEx.cs" />
<Compile Include="UI\Shared\ITabWithListViewEx.cs" />
<Compile Include="UI\Shared\LoginDlgWithBenchSelection.cs">
@@ -4391,6 +4397,10 @@
<EmbeddedResource Include="UI\Shared\ClosingTbfForm.resx">
<DependentUpon>ClosingTbfForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="UI\Shared\ErrorDetailsDlg.resx">
<DependentUpon>ErrorDetailsDlg.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="UI\Shared\ErrorDlg.resx">
<DependentUpon>ErrorDlg.cs</DependentUpon>
</EmbeddedResource>
@@ -424,10 +424,35 @@ namespace TBF.UI.Bench.Components
/// Create a single component of the selected type
///
IComponentCfgCtrl cfgControl = cfg.GetControl(cmpntEntities);
cfgControl.Config = cfg;
cfgControl.Config.ItemNr = (cmpntEntities.Count > 0) ? (cmpntEntities[cmpntEntities.Count - 1].ItemNr + 1) : 1;
cfgControl.Config = cfg;
ComponentParametersDlg cfgForm = new ComponentParametersDlg(this);
if (cfgControl.Config == null)
{
string controlType = cfgControl.GetType().FullName;
string configType = cfg.GetType().FullName;
string details = string.Join(Environment.NewLine, new[]
{
"CONFIGURATION CONTROL",
controlType,
"",
"RECEIVED CONFIGURATION",
configType,
"",
"PROBABLE CAUSE",
"The configuration control expects a different configuration type.",
"Check the type cast in the Config property setter of the configuration control."
});
log.Error(details);
ErrorDetailsDlg.ShowError(this, "Invalid component configuration", "The component could not be added.", details);
return;
}
int lastItemNr = (cmpntEntities.Count > 0) ? cmpntEntities[cmpntEntities.Count - 1].ItemNr : 0;
cfgControl.Config.ItemNr = lastItemNr + 1;
ComponentParametersDlg cfgForm = new ComponentParametersDlg(this);
cfgForm.CmpntEntities = cmpntEntities;
cfgForm.ComponentCfgCtrl = cfgControl;
cfgForm.UnlockAfterStart = true;
+221
View File
@@ -0,0 +1,221 @@
namespace TBF.UI.Shared
{
partial class ErrorDetailsDlg
{
private System.ComponentModel.IContainer components = null;
private System.Windows.Forms.TableLayoutPanel mainLayoutPanel;
private System.Windows.Forms.TableLayoutPanel headerLayoutPanel;
private System.Windows.Forms.PictureBox errorIconPictureBox;
private System.Windows.Forms.Label summaryLabel;
private System.Windows.Forms.GroupBox technicalDetailsGroupBox;
private System.Windows.Forms.TextBox detailsTextBox;
private System.Windows.Forms.TableLayoutPanel footerLayoutPanel;
private System.Windows.Forms.Label copyStatusLabel;
private System.Windows.Forms.FlowLayoutPanel buttonsFlowLayoutPanel;
private System.Windows.Forms.Button copyButton;
private System.Windows.Forms.Button closeButton;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.mainLayoutPanel = new System.Windows.Forms.TableLayoutPanel();
this.headerLayoutPanel = new System.Windows.Forms.TableLayoutPanel();
this.errorIconPictureBox = new System.Windows.Forms.PictureBox();
this.summaryLabel = new System.Windows.Forms.Label();
this.technicalDetailsGroupBox = new System.Windows.Forms.GroupBox();
this.detailsTextBox = new System.Windows.Forms.TextBox();
this.footerLayoutPanel = new System.Windows.Forms.TableLayoutPanel();
this.copyStatusLabel = new System.Windows.Forms.Label();
this.buttonsFlowLayoutPanel = new System.Windows.Forms.FlowLayoutPanel();
this.closeButton = new System.Windows.Forms.Button();
this.copyButton = new System.Windows.Forms.Button();
this.mainLayoutPanel.SuspendLayout();
this.headerLayoutPanel.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.errorIconPictureBox)).BeginInit();
this.technicalDetailsGroupBox.SuspendLayout();
this.footerLayoutPanel.SuspendLayout();
this.buttonsFlowLayoutPanel.SuspendLayout();
this.SuspendLayout();
//
// mainLayoutPanel
//
this.mainLayoutPanel.ColumnCount = 1;
this.mainLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.mainLayoutPanel.Controls.Add(this.headerLayoutPanel, 0, 0);
this.mainLayoutPanel.Controls.Add(this.technicalDetailsGroupBox, 0, 1);
this.mainLayoutPanel.Controls.Add(this.footerLayoutPanel, 0, 2);
this.mainLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill;
this.mainLayoutPanel.Location = new System.Drawing.Point(0, 0);
this.mainLayoutPanel.Name = "mainLayoutPanel";
this.mainLayoutPanel.Padding = new System.Windows.Forms.Padding(12);
this.mainLayoutPanel.RowCount = 3;
this.mainLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.AutoSize));
this.mainLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.mainLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.AutoSize));
this.mainLayoutPanel.Size = new System.Drawing.Size(744, 381);
this.mainLayoutPanel.TabIndex = 0;
//
// headerLayoutPanel
//
this.headerLayoutPanel.AutoSize = true;
this.headerLayoutPanel.ColumnCount = 2;
this.headerLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.AutoSize));
this.headerLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.headerLayoutPanel.Controls.Add(this.errorIconPictureBox, 0, 0);
this.headerLayoutPanel.Controls.Add(this.summaryLabel, 1, 0);
this.headerLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill;
this.headerLayoutPanel.Location = new System.Drawing.Point(15, 15);
this.headerLayoutPanel.Margin = new System.Windows.Forms.Padding(3, 3, 3, 12);
this.headerLayoutPanel.Name = "headerLayoutPanel";
this.headerLayoutPanel.RowCount = 1;
this.headerLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.headerLayoutPanel.Size = new System.Drawing.Size(714, 42);
this.headerLayoutPanel.TabIndex = 0;
//
// errorIconPictureBox
//
this.errorIconPictureBox.Location = new System.Drawing.Point(3, 3);
this.errorIconPictureBox.Margin = new System.Windows.Forms.Padding(3, 3, 12, 3);
this.errorIconPictureBox.Name = "errorIconPictureBox";
this.errorIconPictureBox.Size = new System.Drawing.Size(32, 32);
this.errorIconPictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize;
this.errorIconPictureBox.TabIndex = 0;
this.errorIconPictureBox.TabStop = false;
//
// summaryLabel
//
this.summaryLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.summaryLabel.AutoSize = true;
this.summaryLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.summaryLabel.Location = new System.Drawing.Point(50, 14);
this.summaryLabel.Name = "summaryLabel";
this.summaryLabel.Size = new System.Drawing.Size(222, 13);
this.summaryLabel.TabIndex = 1;
this.summaryLabel.Text = "The requested operation could not be completed.";
//
// technicalDetailsGroupBox
//
this.technicalDetailsGroupBox.Controls.Add(this.detailsTextBox);
this.technicalDetailsGroupBox.Dock = System.Windows.Forms.DockStyle.Fill;
this.technicalDetailsGroupBox.Location = new System.Drawing.Point(15, 72);
this.technicalDetailsGroupBox.Name = "technicalDetailsGroupBox";
this.technicalDetailsGroupBox.Padding = new System.Windows.Forms.Padding(8);
this.technicalDetailsGroupBox.Size = new System.Drawing.Size(714, 249);
this.technicalDetailsGroupBox.TabIndex = 1;
this.technicalDetailsGroupBox.TabStop = false;
this.technicalDetailsGroupBox.Text = "Technical details";
//
// detailsTextBox
//
this.detailsTextBox.BackColor = System.Drawing.SystemColors.Window;
this.detailsTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
this.detailsTextBox.Font = new System.Drawing.Font("Consolas", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.detailsTextBox.Location = new System.Drawing.Point(8, 21);
this.detailsTextBox.Multiline = true;
this.detailsTextBox.Name = "detailsTextBox";
this.detailsTextBox.ReadOnly = true;
this.detailsTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.detailsTextBox.Size = new System.Drawing.Size(698, 220);
this.detailsTextBox.TabIndex = 0;
this.detailsTextBox.WordWrap = false;
//
// footerLayoutPanel
//
this.footerLayoutPanel.AutoSize = true;
this.footerLayoutPanel.ColumnCount = 2;
this.footerLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.footerLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.AutoSize));
this.footerLayoutPanel.Controls.Add(this.copyStatusLabel, 0, 0);
this.footerLayoutPanel.Controls.Add(this.buttonsFlowLayoutPanel, 1, 0);
this.footerLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill;
this.footerLayoutPanel.Location = new System.Drawing.Point(15, 327);
this.footerLayoutPanel.Name = "footerLayoutPanel";
this.footerLayoutPanel.RowCount = 1;
this.footerLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.footerLayoutPanel.Size = new System.Drawing.Size(714, 39);
this.footerLayoutPanel.TabIndex = 2;
//
// copyStatusLabel
//
this.copyStatusLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.copyStatusLabel.AutoSize = true;
this.copyStatusLabel.ForeColor = System.Drawing.SystemColors.GrayText;
this.copyStatusLabel.Location = new System.Drawing.Point(3, 13);
this.copyStatusLabel.Name = "copyStatusLabel";
this.copyStatusLabel.Size = new System.Drawing.Size(0, 13);
this.copyStatusLabel.TabIndex = 0;
//
// buttonsFlowLayoutPanel
//
this.buttonsFlowLayoutPanel.AutoSize = true;
this.buttonsFlowLayoutPanel.Controls.Add(this.closeButton);
this.buttonsFlowLayoutPanel.Controls.Add(this.copyButton);
this.buttonsFlowLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill;
this.buttonsFlowLayoutPanel.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft;
this.buttonsFlowLayoutPanel.Location = new System.Drawing.Point(544, 3);
this.buttonsFlowLayoutPanel.Name = "buttonsFlowLayoutPanel";
this.buttonsFlowLayoutPanel.Size = new System.Drawing.Size(167, 33);
this.buttonsFlowLayoutPanel.TabIndex = 1;
//
// closeButton
//
this.closeButton.AutoSize = true;
this.closeButton.DialogResult = System.Windows.Forms.DialogResult.OK;
this.closeButton.Location = new System.Drawing.Point(89, 3);
this.closeButton.Name = "closeButton";
this.closeButton.Size = new System.Drawing.Size(75, 23);
this.closeButton.TabIndex = 0;
this.closeButton.Text = "Close";
this.closeButton.UseVisualStyleBackColor = true;
//
// copyButton
//
this.copyButton.AutoSize = true;
this.copyButton.Location = new System.Drawing.Point(8, 3);
this.copyButton.Name = "copyButton";
this.copyButton.Size = new System.Drawing.Size(75, 23);
this.copyButton.TabIndex = 1;
this.copyButton.Text = "Copy";
this.copyButton.UseVisualStyleBackColor = true;
this.copyButton.Click += new System.EventHandler(this.copyButton_Click);
//
// ErrorDetailsDlg
//
this.AcceptButton = this.closeButton;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.closeButton;
this.ClientSize = new System.Drawing.Size(744, 381);
this.Controls.Add(this.mainLayoutPanel);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.MinimumSize = new System.Drawing.Size(600, 320);
this.Name = "ErrorDetailsDlg";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Error";
this.mainLayoutPanel.ResumeLayout(false);
this.mainLayoutPanel.PerformLayout();
this.headerLayoutPanel.ResumeLayout(false);
this.headerLayoutPanel.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.errorIconPictureBox)).EndInit();
this.technicalDetailsGroupBox.ResumeLayout(false);
this.technicalDetailsGroupBox.PerformLayout();
this.footerLayoutPanel.ResumeLayout(false);
this.footerLayoutPanel.PerformLayout();
this.buttonsFlowLayoutPanel.ResumeLayout(false);
this.buttonsFlowLayoutPanel.PerformLayout();
this.ResumeLayout(false);
}
}
}
+52
View File
@@ -0,0 +1,52 @@
using System;
using System.Drawing;
using System.Windows.Forms;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window;
namespace TBF.UI.Shared
{
public partial class ErrorDetailsDlg : Form
{
public ErrorDetailsDlg()
{
InitializeComponent();
errorIconPictureBox.Image = SystemIcons.Error.ToBitmap();
}
public ErrorDetailsDlg(string title, string summary, string details) : this()
{
Text = title;
summaryLabel.Text = summary;
detailsTextBox.Text = details;
}
public static DialogResult ShowError(IWin32Window owner, string title, string summary, string details)
{
using (ErrorDetailsDlg dialog = new ErrorDetailsDlg(title, summary, details))
{
return dialog.ShowDialog(owner);
}
}
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
detailsTextBox.Select(0, 0);
closeButton.Focus();
}
private void copyButton_Click(object sender, EventArgs e)
{
try
{
Clipboard.SetText(detailsTextBox.Text);
copyButton.Text = "Copied";
copyStatusLabel.Text = "Technical details copied to clipboard.";
}
catch (Exception ex)
{
copyStatusLabel.Text = "Copy failed: " + ex.Message;
}
}
}
}
+61
View File
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<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" />
</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>
</root>