Cause: - ResultsWriter required a generic way to generate customer-specific XML result data from TBF measurement results. - The customer reference XML contains example runtime values and repeated result structures, so it cannot be used directly as the generated output. - TBF result variables must be explicitly mapped to destinations in the customer XML structure. - The generated XML result data must support two output targets: - direct creation of an XML file, - delivery of the XML payload to a Microsoft SQL stored procedure. - Preview generation must allow the XML structure and configured mappings to be verified without executing the production database write. - Increase revision to 3.9.3145.100. Solution: 1. Added XML reference analysis - Creates a clean base XML structure. - Extracts the repeating result prototype. - Prevents sample runtime values from the customer reference XML from leaking into generated results. 2. Added configurable TBF-to-XML result mapping - Allows explicit mapping of TBF result variables to customer XML destinations. - Supports one-time and repeating XML destinations. - Keeps the mapping independent of the semantic meaning of customer XML attribute names. 3. Added XML destination viewer and configurator - Shows the current mapping. - Highlights repeating destinations. - Identifies already used one-time destinations. 4. Added runtime XML result generation - Uses the configured TBF-to-XML mappings. - Uses measurement procedure result data. - Builds the output from the clean base XML and repeating XML prototype. - Generates repeated result records according to the executed measurement procedure. 5. Added simulation-based Preview request - Generates a complete XML payload using simulation values. - Allows XML structure and mapping verification before production execution. - Does not execute the production stored procedure. 6. Added direct XML file output support to UniDataStorageWriter - Supports File / .xml as a physical output target. - Creates the generated XML result file in the configured output directory. 7. Added Microsoft SQL stored-procedure XML output - Supports Microsoft SQL / StoredProcedure as a physical output target. - Passes the generated XML payload through the configured stored procedure parameter. 8. Added optional XML payload archiving - Allows generated XML payloads to be stored in the configured Payload archive. - Can be used together with the Microsoft SQL stored-procedure output. 9. Kept ResultsWriter independent of the physical output target - ResultsWriter generates the result payload. - UniDataStorageWriter decides how and where the payload is physically written. - The same ResultsWriter XML generation mechanism is therefore used for both XML file and MSSQL outputs. 10. Increased revision - Updated revision to 3.9.3145.100.
597 lines
19 KiB
C#
597 lines
19 KiB
C#
///
|
|
/// Copyright (c) 2026 Sensus Slovensko a.s.
|
|
///
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Windows.Forms;
|
|
using Common;
|
|
using Common.Forms;
|
|
using Results.Resources;
|
|
|
|
namespace Results.Forms
|
|
{
|
|
public partial class ResultsConfigCtrl : UserControl
|
|
{
|
|
/// <summary>
|
|
/// ListViewEx columns.
|
|
/// </summary>
|
|
private enum Column
|
|
{
|
|
Item,
|
|
Caption,
|
|
Units,
|
|
Format,
|
|
Precision,
|
|
Width,
|
|
Alignment,
|
|
Merge,
|
|
TestID,
|
|
Count,
|
|
}
|
|
|
|
private Control[] editors;
|
|
private ComboBox unitsCB;
|
|
private bool unlocked;
|
|
private string captionColumnText;
|
|
|
|
public MetersKind MetersKind;
|
|
public IList<WMeterRsltItemSpec> SelectedItems;
|
|
public bool SupressTestIDColumn;
|
|
|
|
/// <summary>
|
|
/// Gets or sets an optional callback used to select a Caption value.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// When null, Caption keeps the original TextBox editor. When assigned,
|
|
/// clicking Caption invokes the callback. Returning null means Cancel.
|
|
/// </remarks>
|
|
public Func<WMeterRsltItemSpec, string> CaptionPicker
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Caption column header text.
|
|
/// </summary>
|
|
public string CaptionColumnText
|
|
{
|
|
get { return captionColumnText; }
|
|
set
|
|
{
|
|
captionColumnText = value;
|
|
|
|
if (selectedResultsListViewEx.Columns.Count > (int)Column.Caption)
|
|
{
|
|
selectedResultsListViewEx.Columns[(int)Column.Caption].Text =
|
|
string.IsNullOrWhiteSpace(value)
|
|
? Strings.Caption
|
|
: value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool Unlocked
|
|
{
|
|
set
|
|
{
|
|
availableTabControl.Enabled = value;
|
|
selectedResultsListViewEx.Enabled = value;
|
|
addButton.Enabled = value;
|
|
removeButton.Enabled = value;
|
|
removeAllButton.Enabled = value;
|
|
upButton.Enabled = value;
|
|
downButton.Enabled = value;
|
|
unlocked = value;
|
|
}
|
|
get { return unlocked; }
|
|
}
|
|
|
|
public ResultsConfigCtrl(bool supressTestIDColumn)
|
|
: this()
|
|
{
|
|
SupressTestIDColumn = supressTestIDColumn;
|
|
}
|
|
|
|
public ResultsConfigCtrl()
|
|
{
|
|
InitializeComponent();
|
|
}
|
|
|
|
void Localize()
|
|
{
|
|
Text = Strings.Configuration;
|
|
availableResultsLabel.Text = Strings.Available_results;
|
|
availableTabControl.TabPages[0].Text = Strings.Quantity;
|
|
availableTabControl.TabPages[1].Text = Strings.Category;
|
|
availableTabControl.TabPages[2].Text = "A...Z";
|
|
|
|
selectedResultsLabel.Text = Strings.Selected_results;
|
|
|
|
addButton.Text = Strings.Add;
|
|
removeButton.Text = Strings.Remove;
|
|
removeAllButton.Text = Strings.Remove_all;
|
|
upButton.Text = Strings.UpBtnText;
|
|
downButton.Text = Strings.DownBtnText;
|
|
}
|
|
|
|
private void ResultsConfigCtrl_Load(object sender, EventArgs e)
|
|
{
|
|
Localize();
|
|
|
|
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Item, Width = 120 });
|
|
selectedResultsListViewEx.Columns.Add(
|
|
new ColumnHeader
|
|
{
|
|
Text = string.IsNullOrWhiteSpace(captionColumnText)
|
|
? Strings.Caption
|
|
: captionColumnText
|
|
});
|
|
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Units });
|
|
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Format });
|
|
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Precision });
|
|
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Width });
|
|
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Alignment });
|
|
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Merge });
|
|
|
|
if (!SupressTestIDColumn)
|
|
{
|
|
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Test_ID });
|
|
}
|
|
|
|
unitsCB = new ComboBox();
|
|
|
|
var alignmentCB = new ComboBox();
|
|
for (Alignment a = 0; a < Alignment.Count; a++)
|
|
{
|
|
alignmentCB.Items.Add(a.ToDescription());
|
|
}
|
|
|
|
var mergeCB = new ComboBox();
|
|
mergeCB.Items.Add(Strings.No);
|
|
mergeCB.Items.Add(Strings.Yes);
|
|
|
|
editors = new Control[]
|
|
{
|
|
null,
|
|
new TextBox(),
|
|
unitsCB,
|
|
new TextBox(),
|
|
new TextBox(),
|
|
new TextBox(),
|
|
alignmentCB,
|
|
mergeCB,
|
|
new TextBox(),
|
|
};
|
|
|
|
foreach (var edi in editors)
|
|
{
|
|
if (edi != null)
|
|
{
|
|
edi.Visible = false;
|
|
Controls.Add(edi);
|
|
}
|
|
}
|
|
|
|
selectedResultsListViewEx.SubItemClicked +=
|
|
new SubItemEventHandler(selectedResultsListViewEx_SubItemClicked);
|
|
|
|
selectedResultsListViewEx.SubItemEndEditing +=
|
|
new SubItemEndEditingEventHandler(selectedResultsListViewEx_SubItemEndEditing);
|
|
|
|
availableByQuantityTreeView.ShowNodeToolTips = true;
|
|
availableByCategoryTreeView.ShowNodeToolTips = true;
|
|
availableAlphabeticTreeView.ShowNodeToolTips = true;
|
|
|
|
RedrawAvailable();
|
|
RedrawSelected();
|
|
}
|
|
|
|
void selectedResultsListViewEx_SubItemClicked(object sender, SubItemEventArgs e)
|
|
{
|
|
if (e.SubItem == (int)Column.Caption && CaptionPicker != null)
|
|
{
|
|
WMeterRsltItemSpec item = e.Item.Tag as WMeterRsltItemSpec;
|
|
if (item == null) return;
|
|
|
|
string selectedCaption = CaptionPicker(item);
|
|
|
|
if (selectedCaption != null)
|
|
{
|
|
item.Caption = selectedCaption;
|
|
e.Item.SubItems[e.SubItem].Text = selectedCaption;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (e.SubItem == (int)Column.Units)
|
|
{
|
|
WMeterRsltItemSpec item = e.Item.Tag as WMeterRsltItemSpec;
|
|
if (item == null) return;
|
|
|
|
Quantity quantity = item.Quantity;
|
|
unitsCB.Items.Clear();
|
|
unitsCB.Items.Add(Unit.None.ToDescription());
|
|
|
|
for (Unit u = (Unit)1; u < Unit.Count; u++)
|
|
{
|
|
if (Units.IsQuantity(u, quantity))
|
|
{
|
|
unitsCB.Items.Add(u.ToDescription());
|
|
}
|
|
}
|
|
|
|
selectedResultsListViewEx.StartEditing(unitsCB, e.Item, e.SubItem);
|
|
}
|
|
else if ((e.SubItem > 0) &&
|
|
(e.SubItem < (int)(SupressTestIDColumn ? Column.TestID : Column.Count)))
|
|
{
|
|
selectedResultsListViewEx.StartEditing(editors[e.SubItem], e.Item, e.SubItem);
|
|
}
|
|
}
|
|
|
|
void selectedResultsListViewEx_SubItemEndEditing(object sender, SubItemEndEditingEventArgs e)
|
|
{
|
|
ListViewItem lvi = e.Item;
|
|
WMeterRsltItemSpec item = lvi.Tag as WMeterRsltItemSpec;
|
|
|
|
switch ((Column)e.SubItem)
|
|
{
|
|
case Column.Caption:
|
|
item.Caption = e.DisplayText;
|
|
return;
|
|
|
|
case Column.Units:
|
|
for (Unit u = 0; u < Unit.Count; u++)
|
|
{
|
|
if (u.ToDescription().Equals(unitsCB.Text))
|
|
{
|
|
item.Units = u;
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Column.Format:
|
|
item.Format = e.DisplayText;
|
|
return;
|
|
|
|
case Column.Precision:
|
|
item.Precision = e.DisplayText;
|
|
return;
|
|
|
|
case Column.Width:
|
|
{
|
|
int width;
|
|
if (Int32.TryParse(editors[e.SubItem].Text, out width) && width >= 0)
|
|
{
|
|
item.Width = width;
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Column.Alignment:
|
|
for (Alignment a = 0; a < Alignment.Count; a++)
|
|
{
|
|
if (a.ToDescription().Equals(editors[e.SubItem].Text))
|
|
{
|
|
item.Alignment = a;
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Column.Merge:
|
|
if (editors[e.SubItem].Text == Strings.Yes)
|
|
{
|
|
item.Merge = true;
|
|
return;
|
|
}
|
|
else if (editors[e.SubItem].Text == Strings.No)
|
|
{
|
|
item.Merge = false;
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case Column.TestID:
|
|
item.TestID = e.DisplayText;
|
|
return;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
e.DisplayText = e.Item.SubItems[e.SubItem].Text;
|
|
e.Cancel = true;
|
|
}
|
|
|
|
void RedrawAvailable()
|
|
{
|
|
RedrawByQuantity(availableByQuantityTreeView);
|
|
RedrawByCategory(availableByCategoryTreeView);
|
|
RedrawInAlphabeticOrder(availableAlphabeticTreeView);
|
|
}
|
|
|
|
void RedrawInAlphabeticOrder(TreeView treeView)
|
|
{
|
|
treeView.Nodes.Clear();
|
|
|
|
IList<WMeterRsltItemSpec> alphabeticList =
|
|
WMeterRsltItemSpec.AllItems.OrderBy(x => x.Name).ToList();
|
|
|
|
foreach (var item in alphabeticList)
|
|
{
|
|
TreeNode node = new TreeNode(item.Name);
|
|
node.Tag = item;
|
|
node.ToolTipText = item.ToolTipText;
|
|
treeView.Nodes.Add(node);
|
|
}
|
|
}
|
|
|
|
void RedrawByQuantity(TreeView treeView)
|
|
{
|
|
treeView.Nodes.Clear();
|
|
|
|
IList<Quantity> quantities = new List<Quantity>();
|
|
for (Quantity q = 0; q < Quantity.Count; q++) quantities.Add(q);
|
|
|
|
IList<Quantity> sortedQuantities =
|
|
quantities.OrderBy(x => x.ToDescription()).ToList();
|
|
|
|
foreach (var q in sortedQuantities)
|
|
{
|
|
int n = 0;
|
|
|
|
foreach (var ri in WMeterRsltItemSpec.AllItems)
|
|
{
|
|
if (ri.Quantity == q) n++;
|
|
}
|
|
|
|
if (n > 0)
|
|
{
|
|
TreeNode[] array = new TreeNode[n];
|
|
int i = 0;
|
|
|
|
foreach (var ri in WMeterRsltItemSpec.AllItems)
|
|
{
|
|
if (ri.Quantity == q)
|
|
{
|
|
TreeNode node = new TreeNode(ri.Name);
|
|
node.Tag = ri;
|
|
node.ToolTipText = ri.ToolTipText;
|
|
array[i++] = node;
|
|
}
|
|
}
|
|
|
|
treeView.Nodes.Add(new TreeNode(q.ToDescription(), array));
|
|
}
|
|
}
|
|
}
|
|
|
|
void RedrawByCategory(TreeView treeView)
|
|
{
|
|
treeView.Nodes.Clear();
|
|
|
|
IList<ItemCategory> categories = new List<ItemCategory>();
|
|
for (ItemCategory c = 0; c < ItemCategory.Count; c++) categories.Add(c);
|
|
|
|
IList<ItemCategory> sortedCategories =
|
|
categories.OrderBy(x => x.ToDescription()).ToList();
|
|
|
|
foreach (var c in sortedCategories)
|
|
{
|
|
int n = 0;
|
|
|
|
foreach (var ri in WMeterRsltItemSpec.AllItems)
|
|
{
|
|
if (ri.Category == c) n++;
|
|
}
|
|
|
|
if (n > 0)
|
|
{
|
|
TreeNode[] array = new TreeNode[n];
|
|
int i = 0;
|
|
|
|
foreach (var ri in WMeterRsltItemSpec.AllItems)
|
|
{
|
|
if (ri.Category == c)
|
|
{
|
|
TreeNode node = new TreeNode(ri.Name);
|
|
node.Tag = ri;
|
|
node.ToolTipText = ri.ToolTipText;
|
|
array[i++] = node;
|
|
}
|
|
}
|
|
|
|
treeView.Nodes.Add(new TreeNode(c.ToDescription(), array));
|
|
}
|
|
}
|
|
}
|
|
|
|
void RedrawSelected()
|
|
{
|
|
selectedResultsListViewEx.Items.Clear();
|
|
|
|
if (SelectedItems == null) return;
|
|
|
|
foreach (var item in SelectedItems)
|
|
{
|
|
ListViewItem lvi = new ListViewItem(item.Name);
|
|
lvi.Tag = item;
|
|
lvi.SubItems.Add(item.Caption);
|
|
lvi.SubItems.Add(item.Units.ToDescription());
|
|
lvi.SubItems.Add(item.Format);
|
|
lvi.SubItems.Add(item.Precision);
|
|
lvi.SubItems.Add(item.Width.ToString());
|
|
lvi.SubItems.Add(item.Alignment.ToDescription());
|
|
lvi.SubItems.Add(item.Merge ? Strings.Yes : Strings.No);
|
|
|
|
if (!SupressTestIDColumn)
|
|
{
|
|
lvi.SubItems.Add(item.TestID);
|
|
}
|
|
|
|
selectedResultsListViewEx.Items.Add(lvi);
|
|
}
|
|
}
|
|
|
|
void UpdateSelectedFromView()
|
|
{
|
|
}
|
|
|
|
void addButton_Click(object sender, EventArgs e)
|
|
{
|
|
switch (availableTabControl.SelectedIndex)
|
|
{
|
|
case 0:
|
|
availableByQuantityTreeView_DoubleClick(this, null);
|
|
break;
|
|
case 1:
|
|
availableByCategoryTreeView_DoubleClick(this, null);
|
|
break;
|
|
case 2:
|
|
availableAlphabeticTreeView_DoubleClick(this, null);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void availableByQuantityTreeView_DoubleClick(object sender, EventArgs e)
|
|
{
|
|
if (availableByQuantityTreeView.SelectedNode != null &&
|
|
availableByQuantityTreeView.SelectedNode.Tag is WMeterRsltItemSpec)
|
|
{
|
|
AddItem(availableByQuantityTreeView.SelectedNode.Tag as WMeterRsltItemSpec);
|
|
}
|
|
}
|
|
|
|
private void availableByCategoryTreeView_DoubleClick(object sender, EventArgs e)
|
|
{
|
|
if (availableByCategoryTreeView.SelectedNode != null &&
|
|
availableByCategoryTreeView.SelectedNode.Tag is WMeterRsltItemSpec)
|
|
{
|
|
AddItem(availableByCategoryTreeView.SelectedNode.Tag as WMeterRsltItemSpec);
|
|
}
|
|
}
|
|
|
|
private void availableAlphabeticTreeView_DoubleClick(object sender, EventArgs e)
|
|
{
|
|
if (availableAlphabeticTreeView.SelectedNode != null &&
|
|
availableAlphabeticTreeView.SelectedNode.Tag is WMeterRsltItemSpec)
|
|
{
|
|
AddItem(availableAlphabeticTreeView.SelectedNode.Tag as WMeterRsltItemSpec);
|
|
}
|
|
}
|
|
|
|
void AddItem(WMeterRsltItemSpec item)
|
|
{
|
|
if (SelectedItems == null)
|
|
{
|
|
SelectedItems = new List<WMeterRsltItemSpec>();
|
|
}
|
|
|
|
WMeterRsltItemSpec newItem = item.Clone();
|
|
newItem.Caption = newItem.Name;
|
|
SelectedItems.Add(newItem);
|
|
|
|
RedrawSelected();
|
|
|
|
selectedResultsListViewEx.Focus();
|
|
selectedResultsListViewEx.Items[SelectedItems.Count - 1].Selected = true;
|
|
selectedResultsListViewEx.Items[SelectedItems.Count - 1].EnsureVisible();
|
|
}
|
|
|
|
private void selectedResultsListViewEx_DoubleClick(object sender, EventArgs e)
|
|
{
|
|
if (selectedResultsListViewEx.SelectedIndices.Count == 1)
|
|
{
|
|
SelectedItems.RemoveAt(selectedResultsListViewEx.SelectedIndices[0]);
|
|
RedrawAvailable();
|
|
RedrawSelected();
|
|
}
|
|
}
|
|
|
|
void removeButton_Click(object sender, EventArgs e)
|
|
{
|
|
for (int i = selectedResultsListViewEx.SelectedIndices.Count - 1; i >= 0; i--)
|
|
{
|
|
SelectedItems.RemoveAt(selectedResultsListViewEx.SelectedIndices[i]);
|
|
}
|
|
|
|
RedrawAvailable();
|
|
RedrawSelected();
|
|
}
|
|
|
|
void removeAllButton_Click(object sender, EventArgs e)
|
|
{
|
|
SelectedItems.Clear();
|
|
RedrawAvailable();
|
|
RedrawSelected();
|
|
}
|
|
|
|
private void ResultsConfigCtrl_KeyPress(object sender, KeyPressEventArgs e)
|
|
{
|
|
e.Handled = true;
|
|
}
|
|
|
|
private void upButton_Click(object sender, EventArgs e)
|
|
{
|
|
if (selectedResultsListViewEx.SelectedIndices.Count != 1) return;
|
|
|
|
int selIdx = selectedResultsListViewEx.SelectedIndices[0];
|
|
|
|
if (selIdx == 0)
|
|
{
|
|
selectedResultsListViewEx.Focus();
|
|
selectedResultsListViewEx.Items[0].Selected = true;
|
|
return;
|
|
}
|
|
|
|
WMeterRsltItemSpec tmp = SelectedItems[selIdx - 1];
|
|
SelectedItems[selIdx - 1] = SelectedItems[selIdx];
|
|
SelectedItems[selIdx] = tmp;
|
|
|
|
RedrawSelected();
|
|
|
|
selectedResultsListViewEx.Focus();
|
|
selectedResultsListViewEx.Items[selIdx - 1].Selected = true;
|
|
selectedResultsListViewEx.Items[selIdx - 1].EnsureVisible();
|
|
}
|
|
|
|
private void downButton_Click(object sender, EventArgs e)
|
|
{
|
|
if (selectedResultsListViewEx.SelectedIndices.Count != 1) return;
|
|
|
|
int selIdx = selectedResultsListViewEx.SelectedIndices[0];
|
|
|
|
if (selIdx == SelectedItems.Count - 1)
|
|
{
|
|
selectedResultsListViewEx.Focus();
|
|
selectedResultsListViewEx.Items[SelectedItems.Count - 1].Selected = true;
|
|
return;
|
|
}
|
|
|
|
WMeterRsltItemSpec tmp = SelectedItems[selIdx + 1];
|
|
SelectedItems[selIdx + 1] = SelectedItems[selIdx];
|
|
SelectedItems[selIdx] = tmp;
|
|
|
|
RedrawSelected();
|
|
|
|
selectedResultsListViewEx.Focus();
|
|
selectedResultsListViewEx.Items[selIdx + 1].Selected = true;
|
|
selectedResultsListViewEx.Items[selIdx + 1].EnsureVisible();
|
|
}
|
|
|
|
private void availableByCategoryTreeView_NodeMouseHover2(
|
|
object sender,
|
|
TreeNodeMouseHoverEventArgs e)
|
|
{
|
|
ToolTip toolTip = new ToolTip();
|
|
toolTip.SetToolTip(this, e.Node.ToolTipText);
|
|
}
|
|
}
|
|
} |