tbf/TBF/Rig/DataEntry/Uni/TestStartEndForm.cs

601 lines
24 KiB
C#

///
/// Copyright (c) 2023 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using log4net;
using Common;
using Results.Entities;
using TBF.Rig.GenericDevices;
using TBF.Resources;
namespace TBF.Rig.DataEntry.Uni
{
public partial class TestStartEndForm : Form, GenericDevices.IHasCompleted
{
private static readonly ILog log = LogManager.GetLogger(typeof(TestStartEndForm));
/// Arguments
public readonly IList<WaterMeter> waterMeters;
readonly IRegReader[] regReaders;
readonly int lineSize;
readonly Sz sz;
readonly bool isStartBoxAlwaysEn;
readonly bool isLrOrder; /// true = controls obtain focus after ENTER key in left-right order, false = top-down order
readonly IList<DEItem> colItems;
readonly string formCloseKeys;
public readonly string[] WMStartStateStr;
readonly double refVolume;
readonly double warningLimLo; /// limit to display exclamation mark, typically 2x errLimLo
readonly double warningLimHi; /// limit to display exclamation mark, typically 2x errLimHi
/// Derived from arguments in the constructor
readonly bool isEnd;
readonly WaterMeter firstValidWM;
readonly int wmsCount;
readonly int linesCount;
readonly bool fixedErrorLimits; /// false in case of calculated error limits
/// To be retrieved after the form closes
public double[] WMStartState;
public double[] WMEndState;
public Unit VolumeUnit;
int startStateColumn;
int endStateColumn;
/// Size and layout related values
readonly int meterHeight;
readonly int meterWidth;
readonly int margin;
readonly int spacing;
readonly int hdrHeight;
readonly int labelWid;
readonly int checkBoxWid;
/// UI elements
readonly Label[] labels; /// Labels for water meter numbers
readonly TextBox[,] textBoxes; /// Text boxes for values in columns
readonly Label[] exclamations;
readonly LocalSettings ls;
bool isHandlersEnabled; /// Initially false, set to true after ComboBoxes are filled with data
// Set to 'true' when the form closes
public bool Completed { get { return completed; } }
bool completed;
/// <summary>
/// Parameterless constructor for common functionality
/// </summary>
public TestStartEndForm()
{
InitializeComponent();
ControlBox = false;
completed = false;
StartForceCloseHandler();
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="waterMeters">Water meters</param>
/// <param name="regReaders">Array of register readers</param>
/// <param name="lineSize">Number of water meters in one (test bencch) line</param>
/// <param name="title">Form title</param>
/// <param name="sz">Font size</param>
/// <param name="isLrOrder">true = controls obtain focus in left-right order, forls = top-down order</param>
/// <param name="formCloseKeys">String containing keys to close this form</param>
/// <param name="colItems">Water meter items displayed in columns (in the matrix in the main part of the form)</param>
/// <param name="initialVolumeUnit">Volume unit preset initially</param>
/// <param name="wmStartStateStr">Information entered on test start</param>
/// <param name="refVolume">Reference volume, it is used to verify the end state, show/hide exclamation if necessary</param>
/// <param name="errLimLo">Error limit low, it is used to verify the end state, show/hide exclamation if necessary</param>
/// <param name="errLimHi">Error limit high, it is used to verify the end state, show/hide exclamation if necessary</param>
public TestStartEndForm(IList<WaterMeter> waterMeters, IRegReader[] regReaders, int lineSize, string title, Sz sz,
bool isStartBoxAlwaysEn, bool isLrOrder, string formCloseKeys, IList<DEItem> colItems,
Unit initialVolumeUnit, string[] wmStartStateStr = null,
double refVolume = 0, double errLimLo = 0, double errLimHi = 0)
: this()
{
this.waterMeters = waterMeters;
this.regReaders = regReaders;
this.lineSize = lineSize;
this.Text = !string.IsNullOrEmpty(title) ? title : string.Empty;
this.sz = sz;
this.isStartBoxAlwaysEn = isStartBoxAlwaysEn;
this.isLrOrder = isLrOrder;
this.formCloseKeys = !string.IsNullOrEmpty(formCloseKeys) ? formCloseKeys : string.Empty;
this.colItems = colItems;
this.VolumeUnit = initialVolumeUnit;
this.WMStartStateStr = wmStartStateStr;
this.refVolume = refVolume;
this.warningLimLo = 2 * errLimLo;
this.warningLimHi = 2 * errLimHi;
if (waterMeters == null || regReaders == null ||
(wmStartStateStr != null && wmStartStateStr.Length != waterMeters.Count))
{
throw new Exception("Invalid argument");
}
ls = Program.LocalSettings;
/// Readonly variables derived from arguments
isEnd = (wmStartStateStr != null);
wmsCount = waterMeters.Count;
if (WMStartStateStr == null) WMStartStateStr = new string[wmsCount];
fixedErrorLimits = (errLimHi > errLimLo);
linesCount = (wmsCount + Math.Max(1, lineSize) - 1) / Math.Max(1, lineSize);
firstValidWM = waterMeters.FirstOrDefault(x => (x != null && !x.Disabled));
WMStartState = new double[wmsCount];
WMEndState = new double[wmsCount];
///
unitComboBox.Text = VolumeUnit.ToDescription();
labels = new Label[wmsCount];
textBoxes = new TextBox[colItems.Count, wmsCount];
exclamations = new Label[wmsCount];
/// Layout related readonly variables derived from argument 'sz'
Font font;
Font exclamationFont;
switch (sz)
{
case Sz.S:
font = new Font("Verdana", 12, FontStyle.Regular);
//exclamationFont = new Font("Verdana", 24F, FontStyle.Bold, GraphicsUnit.Point, ((byte)(238)));
exclamationFont = new Font("Verdana", 18, FontStyle.Bold);
meterHeight = 26; /// Height of a ComboBox control
margin = 20;
spacing = 8;
hdrHeight = 160;
labelWid = 31;
checkBoxWid = 23;
break;
default:
case Sz.M:
font = new Font("Verdana", 14, FontStyle.Regular);
exclamationFont = new Font("Verdana", 20, FontStyle.Bold);
meterHeight = 31; /// Height of a ComboBox control
margin = 25;
spacing = 10;
hdrHeight = 160;
labelWid = 34;
checkBoxWid = 23;
break;
case Sz.L:
font = new Font("Verdana", 18, FontStyle.Regular);
exclamationFont = new Font("Verdana", 24, FontStyle.Bold);
meterHeight = 37; /// Height of a ComboBox control
margin = 30;
spacing = 12;
hdrHeight = 160;
labelWid = 40;
checkBoxWid = 23;
break;
}
meterWidth = labelWid + checkBoxWid + spacing;
foreach (var ci in colItems) { meterWidth += ci.Width + spacing; }
int tabIndex = 1;
///
/// Table
///
int columnsTop = hdrHeight;
for (int j = 0; j < linesCount; j++)
{
/// Captions
int capX = margin + j * (meterWidth + margin / 2) + labelWid + spacing;
int capY = columnsTop - meterHeight;
for (int k = 0; k < colItems.Count; k++)
{
int labelWidth = Convert.ToInt32(Graphics.FromImage(new Bitmap(1, 1)).MeasureString(colItems[k].Caption, font).Width);
var label = new Label
{
Text = colItems[k].Caption,
Location = new Point(capX, capY),
Size = new Size(labelWidth + spacing, meterHeight),
Font = font,
TabIndex = tabIndex++,
Parent = this,
};
capX += colItems[k].Width + spacing;
}
/// Water meters
for (int i = 0; i < lineSize; i++)
{
int wmPos0 = i + lineSize * j;
if (wmPos0 > wmsCount) continue;
int left = margin + j * (meterWidth + margin / 2);
int top = columnsTop + i * (meterHeight + spacing);
var label = new Label
{
Text = (wmPos0 + 1).ToString(),
Location = new Point(left, top),
Size = new Size(labelWid, meterHeight),
Font = font,
TextAlign = ContentAlignment.MiddleLeft,
TabIndex = tabIndex++,
Parent = this,
};
left += labelWid + spacing / 2;
labels[wmPos0] = label;
this.Controls.Add(label);
/// Columns
for (int k = 0; k < colItems.Count; k++)
{
DEItem ci = colItems[k];
var textBox = new TextBox
{
Name = string.Format("{0}~{1}", k, wmPos0),
Location = new Point(left, top),
Size = new Size(ci.Width, meterHeight),
Enabled = (ci.Action != Ac.LoadReadOnly && !waterMeters[wmPos0].Disabled),
Font = font,
TabIndex = tabIndex++,
Parent = this,
};
textBox.TextChanged += new System.EventHandler(textBox_TextChanged);
textBox.KeyPress += new System.Windows.Forms.KeyPressEventHandler(textBox_KeyPress);
textBox.MouseClick += new System.Windows.Forms.MouseEventHandler(textBox_MouseClick);
left += ci.Width + spacing;
textBoxes[k, wmPos0] = textBox;
this.Controls.Add(textBox);
}
var exclamation = new Label
{
AutoSize = true,
Font = exclamationFont,
ForeColor = System.Drawing.Color.Red,
Location = new Point(left, top),
Parent = this,
TabIndex = tabIndex++,
Text = "!",
Visible = false,
};
exclamations[wmPos0] = exclamation;
this.Controls.Add(exclamation);
}
}
///
/// Adjust window size
///
Rectangle screenRectangle = this.RectangleToScreen(this.ClientRectangle);
int titleBarHeight = screenRectangle.Top - this.Top;
Size = new Size(Math.Max(meterWidth * linesCount + margin * (linesCount + 1), 1300),
titleBarHeight + columnsTop + lineSize * (meterHeight + spacing) + margin);
Localize();
InitializeBoxes();
/// Set focus to the first enabled ComboBox
}
void Localize()
{
okButton.Text = Strings.OkBtnText;
}
/// <summary>
/// Initialize combo boxes state defined by related 'Action'.
/// Clear check boxes.
/// </summary>
void InitializeBoxes()
{
isHandlersEnabled = false;
/// Table
startStateColumn = -1;
endStateColumn = -1;
for (int k = 0; k < colItems.Count; k++)
{
if (colItems[k].Content == Ct.StartState)
{
startStateColumn = k;
for (int i = 0; i < wmsCount; i++)
{
textBoxes[k, i].Text = (!isEnd || waterMeters[i] == null
|| waterMeters[i].Disabled
|| i >= regReaders.Length
|| regReaders[i] == null
|| string.IsNullOrEmpty(WMStartStateStr[i])) ? string.Empty : WMStartStateStr[i];
textBoxes[k, i].Enabled = (isStartBoxAlwaysEn || !isEnd) && waterMeters[i] != null && !waterMeters[i].Disabled
&& i < regReaders.Length && regReaders[i] != null;
}
}
else if (colItems[k].Content == Ct.EndState)
{
endStateColumn = k;
for (int i = 0; i < wmsCount; i++)
{
textBoxes[k, i].Text = string.Empty;
textBoxes[k, i].Enabled = isEnd && waterMeters[i] != null && !waterMeters[i].Disabled
&& i < regReaders.Length && regReaders[i] != null;
}
}
else
{
switch (colItems[k].Action)
{
default:
case Ac.Clear:
case Ac.HistoryLast:
for (int i = 0; i < wmsCount; i++) textBoxes[k, i].Text = string.Empty;
break;
case Ac.Load:
case Ac.LoadReadOnly:
for (int i = 0; i < wmsCount; i++)
{
textBoxes[k, i].Text = DEUtils.GetContent(colItems[k].Content, waterMeters[i]);
}
break;
}
}
}
if (isEnd && isStartBoxAlwaysEn)
{
int k = 0;
int j = 0;
if (!FindNextEnabledTextBox(textBoxes, ref k, ref j, isLrOrder, true))
{
ActiveControl = textBoxes[k, j];
}
}
isHandlersEnabled = true;
}
/// <summary>
/// When one combo box is updated using drop-down menu, all combo boxes
/// are updated by this function.
/// </summary>
void UpdateWMsFromBoxes()
{
for (int k = 0; k < colItems.Count; k++)
{
for (int i = 0; i < wmsCount; i++)
{
if (colItems[k].Content == Ct.StartState)
{
/// Water meter start state
double startVol;
if (textBoxes[k, i].Enabled && Utils.TryParseUDouble(textBoxes[k, i].Text, out startVol))
{
WMStartStateStr[i] = textBoxes[k, i].Text;
WMStartState[i] = Units.ConvertFrom(VolumeUnit, startVol);
}
}
else if (colItems[k].Content == Ct.EndState)
{
/// Water meter end state
double endVol;
if (textBoxes[k, i].Enabled && Utils.TryParseUDouble(textBoxes[k, i].Text, out endVol))
{
WMEndState[i] = Units.ConvertFrom(VolumeUnit, endVol);
}
}
else
{
/// Other value - store values to a water meter structure
DEUtils.PutContent(colItems[k].Content, waterMeters[i], textBoxes[k, i].Text);
}
}
}
}
private void okButton_Click(object sender, EventArgs eArgs)
{
UpdateWMsFromBoxes();
completed = true;
Close();
}
private void unitComboBox_TextChanged(object sender, EventArgs e)
{
unitComboBox_SelectedIndexChanged(sender, e);
}
private void unitComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
var newUnit = Units.FromDescription(unitComboBox.Text);
if (Units.IsVolume(newUnit))
{
VolumeUnit = newUnit;
if (!isHandlersEnabled) return;
/// Update exclamation marks
if (isEnd && startStateColumn >= 0 && endStateColumn >= 0)
{
for (int i = 0; i < wmsCount; i++)
{
double startVol, endVol;
if (Utils.TryParseUDouble(textBoxes[startStateColumn, i].Text, out startVol) &&
Utils.TryParseUDouble(textBoxes[endStateColumn, i].Text, out endVol))
{
double startState = Units.ConvertFrom(VolumeUnit, startVol);
double endState = Units.ConvertFrom(VolumeUnit, endVol);
double err = (refVolume != 0) ? 100.0 * (endState - startState - refVolume) / refVolume : 1000.0;
exclamations[i].Visible = fixedErrorLimits ? ((err < warningLimLo) || (warningLimHi < err)) : ((err < -6) || (+6 < err));
}
else
{
exclamations[i].Visible = !string.IsNullOrEmpty(textBoxes[endStateColumn, i].Text);
}
}
largeTextBox.Text = string.Empty;
largeExclamationLabel.Visible = false;
}
}
}
private void textBox_KeyPress(object sndr, KeyPressEventArgs e)
{
if (!isHandlersEnabled) return;
string[] kj = (sndr as TextBox).Name.Split(new char[] { '~' });
int k = int.Parse(kj[0]);
int j = int.Parse(kj[1]);
/// Close the form if any 'form close key' was pressed
if (!string.IsNullOrEmpty(formCloseKeys))
{
/// Close the form if any 'close form key' was pressed
for (int ix = 0; ix < formCloseKeys.Length; ix++)
{
if (e.KeyChar == formCloseKeys[ix])
{
okButton_Click(sndr, e);
return;
}
}
}
if (e.KeyChar == '\r')
{
/// Change focus
if (FindNextEnabledTextBox(textBoxes, ref k, ref j, isLrOrder))
{
okButton.Focus();
}
else
{
textBoxes[k, j].Focus();
largeTextBox.Text = string.Format("{0}: {1}", j + 1, textBoxes[k, j].Text);
largeExclamationLabel.Visible = false;
}
}
}
/// Handle mouse clicks so that the large text box and the large exclamation mark are updated
private void textBox_MouseClick(object sndr, MouseEventArgs e)
{
textBox_TextChanged(sndr, e);
}
private void textBox_TextChanged(object sndr, EventArgs e)
{
if (!isHandlersEnabled) return;
string[] kj = (sndr as TextBox).Name.Split(new char[] { '~' });
int k = int.Parse(kj[0]);
int j = int.Parse(kj[1]);
largeTextBox.Text = string.Format("{0}: {1}", j + 1, textBoxes[k, j].Text);
if (isEnd && (k == startStateColumn || k == endStateColumn))
{
double startVol, endVol;
if (startStateColumn >= 0 && Utils.TryParseUDouble(textBoxes[startStateColumn, j].Text, out startVol) &&
endStateColumn >= 0 && Utils.TryParseUDouble(textBoxes[endStateColumn, j].Text, out endVol))
{
double startState = Units.ConvertFrom(VolumeUnit, startVol);
double endState = Units.ConvertFrom(VolumeUnit, endVol);
double err = (refVolume != 0) ? 100.0 * (endState - startState - refVolume) / refVolume : 1000.0;
largeExclamationLabel.Visible =
exclamations[j].Visible = fixedErrorLimits ? ((err < warningLimLo) || (warningLimHi < err)) : ((err < -6) || (+6 < err));
}
else
{
largeExclamationLabel.Visible =
exclamations[j].Visible = !string.IsNullOrEmpty(textBoxes[endStateColumn, j].Text);
}
}
else
{
largeExclamationLabel.Visible = false;
}
}
/// <summary>
/// Find the next enabled combo box, update k and i.
/// Return true (=pastTheEndOfArray) when there is no such next combo box.
/// </summary>
/// <param name="textBoxes">2-dimensional array of combo boxes</param>
/// <param name="k">0-nased column </param>
/// <param name="i">0-based row</param>
/// <param name="isLrOrder">Order of progress to the next combo: true = left-right, false = top-down</param>
/// <returns>true (=pastTheEndOfArray) when there is no such next combo box, otherwise false</returns>
bool FindNextEnabledTextBox(TextBox[,] textBoxes, ref int k, ref int i, bool isLrOrder, bool skipStartColumn = false)
{
bool pastTheEndOfArray = false;
do
{
if (isLrOrder)
{
if (++k == textBoxes.GetLength(0))
{
k = 0;
if (++i == textBoxes.GetLength(1))
{
i = 0;
pastTheEndOfArray = true;
break;
}
}
}
else
{
if (++i == textBoxes.GetLength(1))
{
i = 0;
if (++k == textBoxes.GetLength(0))
{
k = 0;
pastTheEndOfArray = true;
break;
}
}
}
}
while (!textBoxes[k, i].Enabled || (skipStartColumn && k == startStateColumn));
return pastTheEndOfArray;
}
#region Forced close handling
public void StartForceCloseHandler()
{
UiBridge.Bridge.CloseModelessFormHandler += delegate(object sender, EventArgs args)
{
if (InvokeRequired) { Invoke(new EventHandler<EventArgs>(OnForceClose), sender, args); }
else OnForceClose(sender, args);
};
}
void OnForceClose(object sender, EventArgs args)
{
DialogResult = DialogResult.Cancel;
Close();
}
#endregion
}
}