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

1846 lines
76 KiB
C#
Raw Normal View History

2022-04-01 10:31:58 +00:00
///
/// Copyright (c) 2023 Sensus Slovensko a.s.
2022-04-01 10:31:58 +00:00
///
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
2022-04-01 10:31:58 +00:00
using System.Windows.Forms;
using log4net;
using Common;
using Results.Entities;
2022-04-01 10:31:58 +00:00
using TBF.Rig.GenericDevices;
using TBF.Resources;
using System.Threading;
using System.IO;
2026-03-12 13:50:47 +00:00
using System.Threading.Tasks;
using NHibernate.Util;
using static System.Net.Mime.MediaTypeNames;
2022-04-01 10:31:58 +00:00
namespace TBF.Rig.DataEntry.Uni
2022-04-01 10:31:58 +00:00
{
public partial class TestStartEndForm : Form, GenericDevices.IHasCompleted
{
private static readonly ILog log = LogManager.GetLogger(typeof(TestStartEndForm));
const int PicSize = 480;
///
/// Static members and constructor
///
static int wmNr0b;
static Rotation imageRotation;
static bool[] isZoomed;
static Point[] zoomCenterLoc;
///
static TestStartEndForm()
{
wmNr0b = 0;
//imageRotation = new Rotation[40];
isZoomed = new bool[40];
zoomCenterLoc = new Point[40];
}
/// Arguments
public readonly IList<WaterMeter> waterMeters;
readonly IRegReader[] regReaders;
readonly int lineSize;
readonly FontSz 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 bool isCompound;
readonly string formCloseKeys;
public readonly string[] WMStartStateStr;
readonly bool isCameraPicture; /// true = camera picture displayed on the left side
readonly string[] startImages;
readonly string[] endImages;
readonly OcrVidi ocrVidi;
readonly string ocrMessage;
readonly string streamName;
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
2026-03-12 13:50:47 +00:00
private bool _readDataFromRegReaders = true;
/// <summary>
/// automaticly read serial number from register reader
/// </summary>
bool bAutoRead;
///<summary>
///autoclose disabled by default, if > 0 is enabled
/// - in seconds
/// </summary>
int iAutocloseGap;
const int DefaultDataEntryReadTimeoutSec = 5;
readonly int dataEntryReadTimeoutMs;
/// Derived from arguments in the constructor
readonly bool isEnd;
readonly Tst mode; /// Start, End or Deferred
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;
2022-04-01 10:31:58 +00:00
public double[] WMEndState;
public Unit VolumeUnit;
int startStateColumn;
int startStateColumnAux;
int endStateColumn;
int endStateColumnAux;
/// 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;
2022-04-01 10:31:58 +00:00
/// UI elements
readonly Label[] labels; /// Labels for water meter numbers
readonly TextBox[,] textBoxes; /// Text boxes for values in columns
readonly Label[] exclamations;
2022-04-01 10:31:58 +00:00
/// Picture box related
PictureBox pictureBox1;
Label imageProcessingProgressLabel;
ProgressBar imageProcessingProgressBar;
string lastImageName;
int lastWmNr1;
Bitmap lastImage; /// non-zoomed image
Bitmap zoomedImage;
Thread imageProcessingThread; /// Thread to process images
public delegate void OcrTextObtainedDelegate(int ix, bool isEnd, string text);
OcrTextObtainedDelegate ocrTextObtained;
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;
private volatile bool isClosing;
private System.Windows.Forms.Timer autoCloseTimer;
private int autoCloseSecondsRemaining;
private string okButtonTextBeforeAutoClose;
private bool showAutoCloseCountdown;
private const int AutoCloseCountdownDisplayThresholdSeconds = 3;
2022-04-01 10:31:58 +00:00
/// <summary>
/// Parameterless constructor for common functionality
2022-04-01 10:31:58 +00:00
/// </summary>
public TestStartEndForm()
{
InitializeComponent();
2026-03-12 13:50:47 +00:00
ControlBox = false;
imageProcessingThread = null;
completed = false;
2022-04-01 10:31:58 +00:00
StartForceCloseHandler();
}
/// <summary>
/// Constructor
2022-04-01 10:31:58 +00:00
/// </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="isStartBoxAlwaysEn">true = start box enabled at the end of a test</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="isCompound">true = Compound meters: StartStateAux and EndStateAux columns displayed</param>
/// <param name="initialVolumeUnit">Volume unit preset initially</param>
/// <param name="isCameraPicture">true = display camera pictures in this form</param>
/// <param name="startImages">Images before a standing start/stop test</param>
/// <param name="endImages">Images after a standing start/stop test</param>
/// <param name="ocrVidi">Reference to OCR objejct</param>
/// <param name="ocrMessage">Message to be displayed when OCR was not initialized properly</param>
/// <param name="streamName">OCR stream selection</param>
/// <param name="wmStartStateStr">Information entered on test start</param>
2022-04-01 10:31:58 +00:00
/// <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, FontSz sz,
bool isStartBoxAlwaysEn, bool isLrOrder, string formCloseKeys, IList<DEItem> colItems, bool isCompound,
Unit initialVolumeUnit, bool isCameraPicture, string[] startImages, string[] endImages,
OcrVidi ocrVidi, string ocrMessage, string streamName, bool bAutoRead, int iAutocloseGap,
int dataEntryReadTimeoutSec, string[] wmStartStateStr = null,
double refVolume = 0, double errLimLo = 0, double errLimHi = 0)
2022-04-01 10:31:58 +00:00
: this()
{
this.waterMeters = waterMeters;
2022-04-01 10:31:58 +00:00
this.regReaders = regReaders;
this.lineSize = Math.Min(waterMeters.Count, _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.isCompound = isCompound;
this.VolumeUnit = initialVolumeUnit;
this.isCameraPicture = isCameraPicture;
this.startImages = startImages;
this.endImages = endImages;
this.ocrVidi = ocrVidi;
this.ocrMessage = ocrMessage;
this.streamName = streamName;
this.WMStartStateStr = wmStartStateStr;
this.refVolume = refVolume;
2022-04-01 10:31:58 +00:00
this.warningLimLo = 2 * errLimLo;
this.warningLimHi = 2 * errLimHi;
2026-03-12 13:50:47 +00:00
this.bAutoRead = bAutoRead;
this.iAutocloseGap = iAutocloseGap;
int effectiveTimeoutSec = dataEntryReadTimeoutSec > 0
? dataEntryReadTimeoutSec
: DefaultDataEntryReadTimeoutSec;
this.dataEntryReadTimeoutMs = Math.Min(effectiveTimeoutSec, Int32.MaxValue / 1000) * 1000;
2022-04-01 10:31:58 +00:00
if (waterMeters == null || regReaders == null ||
(wmStartStateStr != null && wmStartStateStr.Length != (isCompound ? 2 : 1) * waterMeters.Count))
{
throw new Exception("Invalid argument");
}
ls = Program.LocalSettings;
/// Readonly variables derived from arguments
isEnd = (wmStartStateStr != null);
mode = isEnd ? Tst.End : Tst.Start;
wmsCount = waterMeters.Count;
if (WMStartStateStr == null) WMStartStateStr = new string[(isCompound ? 2 : 1) * 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[(isCompound ? 2 : 1) * wmsCount];
WMEndState = new double[(isCompound ? 2 : 1) * wmsCount];
///
unitComboBox.Text = VolumeUnit.ToDescription();
for (Unit u = 0; u < Unit.Count; u++)
{
if (Units.IsVolume(u)) unitComboBox.Items.Add(u.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 FontSz.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;
2022-04-01 10:31:58 +00:00
default:
case FontSz.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;
2022-04-01 10:31:58 +00:00
case FontSz.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; }
2022-04-01 10:31:58 +00:00
int tabIndex = 1;
2022-04-01 10:31:58 +00:00
///
/// Table
///
int columnsLeft = 0;
if (isCameraPicture)
{
columnsLeft = PicSize + 10;
largeTextBox.Left += columnsLeft;
largeExclamationLabel.Left += columnsLeft;
/// Picture box
pictureBox1 = new PictureBox
{
Location = new System.Drawing.Point(4, 5),
Name = "pictureBox1",
Size = new System.Drawing.Size(PicSize, PicSize),
SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage,
TabIndex = tabIndex++,
TabStop = false,
};
pictureBox1.Click += new System.EventHandler(pictureBox1_Click);
Controls.Add(pictureBox1);
/// Rotate image button
var rotateImageButton = new Button
{
Font = new System.Drawing.Font("Arial", 27.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(238))),
Location = new System.Drawing.Point(24, PicSize + 31),
Name = "rotateImageButton",
Size = new System.Drawing.Size(50, 50),
TabIndex = tabIndex++,
Text = "↺",
UseVisualStyleBackColor = true,
};
rotateImageButton.Click += new System.EventHandler(rotateImageButton_Click);
Controls.Add(rotateImageButton);
/// Image processing progress bar
imageProcessingProgressBar = new ProgressBar
{
Location = new System.Drawing.Point(100, PicSize + 56),
Name = "imageProcessingProgressBar",
Size = new System.Drawing.Size(246, 23),
TabIndex = tabIndex++,
Visible = false,
};
Controls.Add(imageProcessingProgressBar);
/// Image processing progress label
imageProcessingProgressLabel = new Label
{
AutoSize = true,
Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))),
Location = new System.Drawing.Point(100, PicSize + 31),
Name = "imageProcessingProgressLabel",
Size = new System.Drawing.Size(249, 18),
TabIndex = tabIndex++,
Text = "Progress of image processing",
Visible = false,
};
Controls.Add(imageProcessingProgressLabel);
}
int columnsTop = hdrHeight;
for (int j = 0; j < linesCount; j++)
2022-04-01 10:31:58 +00:00
{
/// Captions
int capX = columnsLeft + 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;
Controls.Add(label);
}
2022-04-01 10:31:58 +00:00
/// Water meters
for (int i = 0; i < lineSize; i++)
{
int wmPos0 = i + lineSize * j;
if (wmPos0 > wmsCount) continue;
2022-04-01 10:31:58 +00:00
int left = columnsLeft + 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;
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.AcceptsTabChanged += new System.EventHandler(textBox_AcceptsTabChanged);
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);
textBox.MouseDown += new System.Windows.Forms.MouseEventHandler(textBox_MouseDown);
left += ci.Width + spacing;
textBoxes[k, wmPos0] = textBox;
Controls.Add(textBox);
}
2022-04-01 10:31:58 +00:00
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;
Controls.Add(exclamation);
}
}
2022-04-01 10:31:58 +00:00
///
/// Adjust window size
///
Rectangle screenRectangle = this.RectangleToScreen(this.ClientRectangle);
int titleBarHeight = screenRectangle.Top - this.Top;
Size = new Size(columnsLeft + Math.Max(meterWidth * linesCount + margin * (linesCount + 1), 1040),
titleBarHeight + Math.Max(columnsTop + lineSize * (meterHeight + spacing) + margin, isCameraPicture ? 590 : 0));
2022-04-01 10:31:58 +00:00
Localize();
InitializeBoxes();
2022-04-01 10:31:58 +00:00
/// Set focus to the first enabled ComboBox
}
2022-04-01 10:31:58 +00:00
private void TestStartEndForm_Load(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(ocrMessage))
{
MessageBox.Show(ocrMessage, Strings.Warning, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
int imagesCount = ImagesToProcessCount();
log.WarnFormat("Images to process count = {0}", imagesCount);
///
if (imagesCount > 0 && ocrVidi != null)
{
log.WarnFormat("Starting image processing thread with OcrVidi={0} and OcrStream={1}", ocrVidi, streamName);
/// Delegate for a thread->form communication
ocrTextObtained = new OcrTextObtainedDelegate(OnOcrTextObtained);
/// Show and initalize the progress bar
imageProcessingProgressLabel.Visible = true;
imageProcessingProgressBar.Visible = true;
imageProcessingProgressBar.Minimum = 0;
imageProcessingProgressBar.Value = 0;
imageProcessingProgressBar.Maximum = imagesCount;
/// Start the image processing thread
imageProcessingThread = new Thread(new ThreadStart(ImageProcessingThread));
imageProcessingThread.Start();
}
}
private void TestStartEndForm_FormClosed(object sender, FormClosedEventArgs e)
{
if (imageProcessingThread != null && imageProcessingThread.IsAlive)
{
imageProcessingThread.Join(2000);
}
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
isClosing = true;
StopAutoCloseCountdown();
VolumeStartReadbyRegReader = null;
DoneUpdateByRegReader = null;
base.OnFormClosing(e);
}
void Localize()
{
okButton.Text = Strings.OkBtnText;
}
2022-04-01 10:31:58 +00:00
2026-03-12 13:50:47 +00:00
2022-04-01 10:31:58 +00:00
/// <summary>
/// Initialize combo boxes state defined by related 'Action'.
/// Clear check boxes.
2022-04-01 10:31:58 +00:00
/// </summary>
void InitializeBoxes()
2022-04-01 10:31:58 +00:00
{
isHandlersEnabled = false;
/// Table
startStateColumn = -1;
startStateColumnAux = -1;
endStateColumn = -1;
endStateColumnAux = -1;
for (int k = 0; k < colItems.Count; k++)
2022-04-01 10:31:58 +00:00
{
if (colItems[k].Content == Ct.StartState)
2022-04-01 10:31:58 +00:00
{
startStateColumn = k;
for (int i = 0; i < wmsCount; i++)
{
int rrIx = (isCompound ? 2 : 1) * 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[rrIx];
textBoxes[k, i].Enabled = (isStartBoxAlwaysEn || !isEnd) && waterMeters[i] != null && !waterMeters[i].Disabled
&& rrIx < regReaders.Length && regReaders[rrIx] != null;
}
2026-03-12 13:50:47 +00:00
}
else if (colItems[k].Content == Ct.StartStateAux)
{
startStateColumnAux = k;
for (int i = 0; i < wmsCount; i++)
{
int rrIx = 2 * i + 1;
textBoxes[k, i].Text = (!isEnd || !isCompound
|| waterMeters[i] == null
|| waterMeters[i].Disabled
|| i >= regReaders.Length
|| regReaders[i] == null
|| string.IsNullOrEmpty(WMStartStateStr[i])) ? string.Empty : WMStartStateStr[rrIx];
textBoxes[k, i].Enabled = isCompound && (isStartBoxAlwaysEn || !isEnd) && waterMeters[i] != null && !waterMeters[i].Disabled
&& rrIx < regReaders.Length && regReaders[rrIx] != null;
}
2022-04-01 10:31:58 +00:00
}
else if (colItems[k].Content == Ct.EndState)
{
endStateColumn = k;
for (int i = 0; i < wmsCount; i++)
{
int rrIx = (isCompound ? 2 : 1) * i;
textBoxes[k, i].Text = string.Empty;
textBoxes[k, i].Enabled = isEnd && waterMeters[i] != null && !waterMeters[i].Disabled
&& rrIx < regReaders.Length && regReaders[rrIx] != null;
}
}
else if (colItems[k].Content == Ct.EndStateAux)
{
endStateColumn = k;
for (int i = 0; i < wmsCount; i++)
{
int rrIx = 2 * i + 1;
textBoxes[k, i].Text = string.Empty;
textBoxes[k, i].Enabled = isEnd && isCompound && waterMeters[i] != null && !waterMeters[i].Disabled
&& rrIx < regReaders.Length && regReaders[rrIx] != 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;
}
}
}
2022-04-01 10:31:58 +00:00
if (isEnd && isStartBoxAlwaysEn)
2022-04-01 10:31:58 +00:00
{
int k = 0;
int j = 0;
if (!FindNextEnabledTextBox(textBoxes, ref k, ref j, isLrOrder, true))
2022-04-01 10:31:58 +00:00
{
ActiveControl = textBoxes[k, j];
2022-04-01 10:31:58 +00:00
}
2022-04-01 10:31:58 +00:00
}
2026-03-12 13:50:47 +00:00
if (bAutoRead)
{
IRegReader[] selectedRegReaders = RegisterReaderSelection.GetSelectedForDataEntry(
regReaders, waterMeters, log, isEnd ? "end volume" : "begin volume");
2026-03-12 13:50:47 +00:00
log.Debug($"Reading serial numbers from register readers... IsEnd: {isEnd}");
this.VolumeStartReadbyRegReader += (s, eArgs) =>
{
log.Debug("Volume updated: " + eArgs.Volume);
UpdateVolume(eArgs.Reader, eArgs.Volume);
};
BeforeUpdate(selectedRegReaders);
2026-03-12 13:50:47 +00:00
this.DoneUpdateByRegReader += (s, eArgs) =>
{
log.Debug("Volume updated DONE!");
DoneUpdate();
};
ReadVolumeAsync(selectedRegReaders);
2026-03-12 13:50:47 +00:00
}
2022-04-01 10:31:58 +00:00
isHandlersEnabled = true;
}
2026-03-12 13:50:47 +00:00
LinkedHashMap<string, bool> storeUIForUpdate = new LinkedHashMap<string, bool>();
private Cursor _previousCursor;
private bool GetStoredOrDefault(string key)
{
bool value;
if (storeUIForUpdate.TryGetValue(key, out value))
return value;
return true; // default if nothing stored
}
private void BeforeUpdate(IEnumerable<IRegReader> selectedRegReaders)
2026-03-12 13:50:47 +00:00
{
log.Debug("BeforeUpdate");
storeUIForUpdate["okButton"] = this.okButton.Enabled;
storeUIForUpdate["largeTextBox"] = this.largeTextBox.Enabled;
storeUIForUpdate["largeTextBoxReadOnly"] = this.largeTextBox.ReadOnly;
2026-03-12 13:50:47 +00:00
storeUIForUpdate["largeExclamationLabel"] = this.largeExclamationLabel.Enabled;
storeUIForUpdate["unitComboBox"] = this.unitComboBox.Enabled;
this.okButton.Enabled = false;
this.largeTextBox.Enabled = false;
this.largeExclamationLabel.Enabled = false;
this.unitComboBox.Enabled = false;
largeTextBox.ReadOnly = true;
largeTextBox.Text = Strings.Loading;
2026-03-12 13:50:47 +00:00
_previousCursor = Cursor.Current;
Cursor.Current = Cursors.WaitCursor;
var selectedPositions = new HashSet<int>(
selectedRegReaders.Select(reader => reader.Position));
log.DebugFormat(
"DATA_ENTRY_VOLUME_UI_SELECTION IsEnd={0}, SelectedCount={1}, Positions=[{2}], Watermark=[{3}]",
isEnd, selectedPositions.Count, string.Join(",", selectedPositions), Strings.Loading);
List<Ct> stateColumns = isEnd
? new List<Ct> { Ct.EndState, Ct.EndStateAux }
: new List<Ct> { Ct.StartState, Ct.StartStateAux };
for (int k = 0; k < colItems.Count; k++)
{
if (!stateColumns.Contains(colItems[k].Content))
continue;
for (int ix = 0; ix < wmsCount; ix++)
{
DataEntryWatermark.Set(
textBoxes[k, ix],
selectedPositions.Contains(ix + 1)
? Strings.Loading
: string.Empty);
}
}
2026-03-12 13:50:47 +00:00
}
private void DoneUpdate()
{
if (!CanUpdateDataEntryUi())
{
log.Debug("DATA_ENTRY_VOLUME_DONE_SKIPPED Form is closing or disposed.");
return;
}
2026-03-12 13:50:47 +00:00
log.Debug("DoneUpdate");
this.okButton.Enabled = GetStoredOrDefault("okButton");
this.largeTextBox.Enabled = GetStoredOrDefault("largeTextBox");
this.largeExclamationLabel.Enabled = GetStoredOrDefault("largeExclamationLabel");
this.unitComboBox.Enabled = GetStoredOrDefault("unitComboBox");
if (largeTextBox.Text == Strings.Loading)
largeTextBox.Text = string.Empty;
largeTextBox.ReadOnly = GetStoredOrDefault("largeTextBoxReadOnly");
2026-03-12 13:50:47 +00:00
Cursor.Current = _previousCursor;
if (iAutocloseGap > 0)
{
AutoClickOkAfterDelay(iAutocloseGap * 1000);
2026-03-12 13:50:47 +00:00
}
}
public void UpdateVolume(IRegReader eArgsReader, double eArgsVolume)
{
if (!CanUpdateDataEntryUi())
{
log.Debug("DATA_ENTRY_VOLUME_UPDATE_SKIPPED Form is closing or disposed.");
return;
}
log.DebugFormat(
"DATA_ENTRY_VOLUME_UI_HANDLER Name={0}, Position={1}, DebugLevel={2}, Volume={3}, IsNaN={4}",
eArgsReader.Name, eArgsReader.Position, eArgsReader.DebugLevel,
eArgsVolume, Double.IsNaN(eArgsVolume));
2026-03-12 13:50:47 +00:00
PopulateVolume(eArgsReader, eArgsVolume);
}
/// 24-bit counter wrap in liters: 2^24 ticks * 0.00025 L/tick
private const double VOL_RANGE_LITERS = 16777216.0 * 0.00025; // 4194.304
/// <summary>
/// Populate Volume - also include roll over VOL_RANGE_LITERS
/// Unit test Concept: TBFTests/Rig/DataEntry/Uni/PopulateVolumeRolloverTests_Concept.cs
/// </summary>
/// <param name="eArgsReader"></param>
/// <param name="eArgsVolume"></param>
public void PopulateVolume(IRegReader eArgsReader, double eArgsVolume)
{
if (eArgsReader != null && Double.IsNaN(eArgsVolume))
{
SetVolumeWatermark(
eArgsReader,
eArgsReader.DebugLevel == DebugMode.Simulate
? Strings.Simulated
: Strings.Not_loaded);
}
if (regReaders == null || Double.IsNaN(eArgsVolume) || textBoxes == null)
{
log.WarnFormat(
"DATA_ENTRY_VOLUME_UI_SKIP Name={0}, Position={1}, DebugLevel={2}, " +
"Volume={3}, ReadersNull={4}, ControlsNull={5}",
eArgsReader == null ? "<null>" : eArgsReader.Name,
eArgsReader == null ? -1 : eArgsReader.Position,
eArgsReader == null ? DebugMode.Normal : eArgsReader.DebugLevel,
eArgsVolume, regReaders == null, textBoxes == null);
2026-03-12 13:50:47 +00:00
return;
}
2026-03-12 13:50:47 +00:00
int regReadersLength = regReaders.Length;
int comboRows = textBoxes.GetLength(0);
int comboCols = textBoxes.GetLength(1);
//just combine Start and End - switch column to write
List<Ct> ctValues = isEnd
? new List<Ct> { Ct.EndState, Ct.EndStateAux }
: new List<Ct> { Ct.StartState, Ct.StartStateAux };
double VolumeRaw = eArgsVolume;
// Test and compensate roll over
if (isEnd)
{
if (eArgsReader != null && (!Double.IsNaN(eArgsReader.BeginWMState)))
{
if (eArgsReader.BeginWMState > VolumeRaw) //Do RollOver
{
VolumeRaw = eArgsVolume + VOL_RANGE_LITERS;
log.Debug(
$"Roll over detected: {eArgsReader.Name} - {eArgsReader.BeginWMState} -> {VolumeRaw}");
}
}
else
{
//volume from form
Double beginVolume = findBeginStateFromForm();
if (beginVolume > VolumeRaw) //Do RollOver
{
VolumeRaw = eArgsVolume + VOL_RANGE_LITERS;
log.Debug(
$"Begin from Form! Roll over detected: {eArgsReader.Name} - {beginVolume} -> {VolumeRaw}");
}
}
}
for (int k = 0; k < colItems.Count && k < comboRows; k++)
{
Ct current = (Ct)colItems[k].Content;
if (ctValues.Contains(current)) // it define if is start or end
{
for (int ix = 0; ix < wmsCount && ix < comboCols; ix++)
{
int regIndex = ix;
if (regIndex >= 0 && regIndex < regReadersLength)
{
var textBox = textBoxes[k, ix];
if (textBox != null && regReaders[regIndex] == eArgsReader)
{
try
{
DataEntryWatermark.Set(textBox, string.Empty);
2026-03-12 13:50:47 +00:00
if (VolumeUnit == Unit.None)
VolumeUnit = Unit.l;
Double convertTo = Units.ConvertTo(VolumeUnit, VolumeRaw);
textBox.Text = convertTo.ToString();
}
catch (Exception ex)
{
log.Error($"Error converting volume to {VolumeUnit}: {ex.Message}");
}
}
}
}
}
}
}
private void SetVolumeWatermark(IRegReader reader, string watermark)
{
if (reader == null || regReaders == null || textBoxes == null)
return;
if (IsHandleCreated && InvokeRequired)
{
BeginInvoke(new Action(() => SetVolumeWatermark(reader, watermark)));
return;
}
int readerIndex = Array.IndexOf(regReaders, reader);
if (readerIndex < 0 || readerIndex >= wmsCount)
return;
List<Ct> stateColumns = isEnd
? new List<Ct> { Ct.EndState, Ct.EndStateAux }
: new List<Ct> { Ct.StartState, Ct.StartStateAux };
for (int k = 0; k < colItems.Count; k++)
{
if (stateColumns.Contains(colItems[k].Content))
DataEntryWatermark.Set(textBoxes[k, readerIndex], watermark);
}
log.DebugFormat(
"DATA_ENTRY_VOLUME_WATERMARK Name={0}, Position={1}, MeterIndex={2}, Text=[{3}]",
reader.Name, reader.Position, readerIndex, watermark);
}
2026-03-12 13:50:47 +00:00
private Double findBeginStateFromForm()
{
List<Ct> ctValues = new List<Ct> { Ct.StartState, Ct.StartStateAux };
int regReadersLength = regReaders.Length;
int comboRows = textBoxes.GetLength(0);
int comboCols = textBoxes.GetLength(1);
for (int k = 0; k < colItems.Count && k < comboRows; k++)
{
Ct current = (Ct)colItems[k].Content;
if (ctValues.Contains(current)) // it define if is start or end
{
for (int ix = 0; ix < wmsCount && ix < comboCols; ix++)
{
int regIndex = ix;
if (regIndex >= 0 && regIndex < regReadersLength)
{
var textBox = textBoxes[k, ix];
if (textBox != null && textBox.Text != null && textBox.Text.Length > 0)
{
try
{
if (VolumeUnit == Unit.None)
VolumeUnit = Unit.l;
double VolumeRaw = Double.Parse(textBox.Text);
Double convertBeginVolumeINLiters = Units.ConvertFrom(VolumeUnit, VolumeRaw);
return convertBeginVolumeINLiters;
}
catch (Exception ex)
{
log.Error($"Error converting volume to {VolumeUnit}: {ex.Message}");
}
}
}
}
}
}
return 0.0D;
}
/// <summary>
/// When one combo box is updated using drop-down menu, all combo boxes
/// are updated by this function.
/// </summary>
void UpdateWMsFromBoxes()
2022-04-01 10:31:58 +00:00
{
for (int k = 0; k < colItems.Count; k++)
2022-04-01 10:31:58 +00:00
{
for (int i = 0; i < wmsCount; i++)
2022-04-01 10:31:58 +00:00
{
int rrIx = (isCompound ? 2 : 1) * i;
double volume;
if (colItems[k].Content == Ct.StartState)
{
if (textBoxes[k, i].Enabled && Utils.TryParseUDouble(textBoxes[k, i].Text, out volume))
{
WMStartStateStr[rrIx] = textBoxes[k, i].Text;
WMStartState[rrIx] = Units.ConvertFrom(VolumeUnit, volume);
}
}
else if (colItems[k].Content == Ct.StartStateAux)
{
if (isCompound && textBoxes[k, i].Enabled && Utils.TryParseUDouble(textBoxes[k, i].Text, out volume))
{
WMStartStateStr[rrIx + 1] = textBoxes[k, i].Text;
WMStartState[rrIx + 1] = Units.ConvertFrom(VolumeUnit, volume);
}
}
else if (colItems[k].Content == Ct.EndState)
{
if (textBoxes[k, i].Enabled && Utils.TryParseUDouble(textBoxes[k, i].Text, out volume))
{
WMEndState[rrIx] = Units.ConvertFrom(VolumeUnit, volume);
}
}
else if (colItems[k].Content == Ct.EndStateAux)
{
if (isCompound && textBoxes[k, i].Enabled && Utils.TryParseUDouble(textBoxes[k, i].Text, out volume))
{
WMEndState[rrIx + 1] = Units.ConvertFrom(VolumeUnit, volume);
}
}
else
{
/// Other value - store values to a water meter structure
DEUtils.PutContent(colItems[k].Content, waterMeters[i], textBoxes[k, i].Text);
}
2022-04-01 10:31:58 +00:00
}
}
}
int ImagesToProcessCount()
{
int count = 0;
/// Count images
for (int i = 0; i < wmsCount; i++)
{
int rrIx = (isCompound ? 2 : 1) * i;
if (mode == Tst.Start || mode == Tst.Both)
{
if (startStateColumn >= 0 && textBoxes[startStateColumn, i].Enabled &&
startImages != null && startImages.Length > rrIx && !string.IsNullOrEmpty(startImages[rrIx]))
{
count++;
}
if (startStateColumnAux >= 0 && textBoxes[startStateColumnAux, i].Enabled &&
startImages != null && startImages.Length > rrIx + 1 && !string.IsNullOrEmpty(startImages[rrIx + 1]))
{
count++;
}
}
if (mode == Tst.End || mode == Tst.Both)
{
if (endStateColumn >= 0 && textBoxes[endStateColumn, i].Enabled &&
endImages != null && endImages.Length > rrIx && !string.IsNullOrEmpty(endImages[rrIx]))
{
count++;
}
if (endStateColumnAux >= 0 && textBoxes[endStateColumnAux, i].Enabled &&
endImages != null && endImages.Length > rrIx + 1 && !string.IsNullOrEmpty(endImages[rrIx + 1]))
{
count++;
}
}
}
return count;
}
void ImageProcessingThread()
{
double denominator; /// derived from PulsesPerLtr, passed to OcrVidi
string format; /// derived from PulsesPerLtr, passed to OcrVidi
Thread.Sleep(2000);
bool[] startImgProcessed = new bool[(isCompound ? 2 : 1) * wmsCount];
bool[] endImgProcessed = new bool[(isCompound ? 2 : 1) * wmsCount];
for (int attempt = 1; attempt <= 3; attempt++)
{
for (int i = 0; i < wmsCount; i++)
{
int rrIx = (isCompound ? 2 : 1) * i;
double pulsesPerLtr = (regReaders != null && regReaders.Length > rrIx && regReaders[rrIx] != null) ? regReaders[rrIx].PulsesPerLtr : 1;
double pulsesPerLtrAux = (regReaders != null && regReaders.Length > rrIx + 1 && regReaders[rrIx + 1] != null) ? regReaders[rrIx + 1].PulsesPerLtr : 1;
if (mode == Tst.Start || mode == Tst.Both)
{
if (startStateColumn >= 0 && textBoxes[startStateColumn, i].Enabled &&
startImages != null && startImages.Length > rrIx && !string.IsNullOrEmpty(startImages[rrIx]))
{
if (!startImgProcessed[rrIx])
{
try
{
/// Process a start image
GetOcrParameters(pulsesPerLtr, out denominator, out format);
var text = ocrVidi.Recognize(streamName, startImages[rrIx], denominator, format); /// OCR
this.Invoke(ocrTextObtained, new object[] { rrIx, false, text });
startImgProcessed[rrIx] = true;
}
catch (Exception) { }
}
}
if (startStateColumnAux >= 0 && textBoxes[startStateColumnAux, i].Enabled &&
startImages != null && startImages.Length > rrIx + 1 && !string.IsNullOrEmpty(startImages[rrIx + 1]))
{
if (!startImgProcessed[rrIx + 1])
{
try
{
/// Process a start image
GetOcrParameters(pulsesPerLtrAux, out denominator, out format);
var text = ocrVidi.Recognize(streamName, startImages[rrIx + 1], denominator, format); /// OCR
this.Invoke(ocrTextObtained, new object[] { rrIx + 1, false, text });
startImgProcessed[rrIx + 1] = true;
}
catch (Exception) { }
}
}
}
if (mode == Tst.End || mode == Tst.Both)
{
if (endStateColumn >= 0 && textBoxes[endStateColumn, i].Enabled &&
endImages != null && endImages.Length > rrIx && !string.IsNullOrEmpty(endImages[rrIx]))
{
if (!endImgProcessed[rrIx])
{
try
{
/// Process an end image
GetOcrParameters(pulsesPerLtr, out denominator, out format);
var text = ocrVidi.Recognize(streamName, endImages[rrIx], denominator, format); /// OCR
this.Invoke(ocrTextObtained, new object[] { rrIx, true, text });
endImgProcessed[rrIx] = true;
}
catch (Exception) { }
}
}
if (endStateColumnAux >= 0 && textBoxes[endStateColumnAux, i].Enabled &&
endImages != null && endImages.Length > rrIx + 1 && !string.IsNullOrEmpty(endImages[rrIx + 1]))
{
if (!endImgProcessed[rrIx + 1])
{
try
{
/// Process an end image
GetOcrParameters(pulsesPerLtrAux, out denominator, out format);
var text = ocrVidi.Recognize(streamName, endImages[rrIx + 1], denominator, format); /// OCR
this.Invoke(ocrTextObtained, new object[] { rrIx + 1, true, text });
endImgProcessed[rrIx + 1] = true;
}
catch (Exception) { }
}
}
}
}
}
}
void GetOcrParameters(double pulsesPerLtr, out double denominator, out string format)
{
if (pulsesPerLtr == 0.1)
{
denominator = 100;
format = "F2";
}
else if (pulsesPerLtr == 0.01)
{
denominator = 10;
format = "F1";
}
else if (pulsesPerLtr == 10)
{
denominator = 10000;
format = "F4";
}
else // if (pulsesPerLtr == 1)
{
denominator = 1000;
format = "F3";
}
}
public void OnOcrTextObtained(int rrIx, bool _isEnd, string text)
{
2023-08-08 12:59:32 +00:00
if (!isCameraPicture) return;
if (_isEnd)
{
if (endStateColumn>= 0 && textBoxes[endStateColumn, rrIx].Enabled)
{
textBoxes[endStateColumn, rrIx].Text = text;
LoadImage(null, Tst.End, rrIx);
}
}
else
{
if (startStateColumn >= 0 && textBoxes[startStateColumn, rrIx].Enabled)
{
textBoxes[endStateColumn, rrIx].Text = text;
LoadImage(null, Tst.Start, rrIx);
}
}
imageProcessingProgressBar.Value++;
}
private void okButton_Click(object sender, EventArgs eArgs)
{
StopAutoCloseCountdown();
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, startVolAux, endVolAux;
if (isCompound && startStateColumnAux >= 0 && endStateColumnAux >= 0)
{
/// Compound
if (Utils.TryParseUDouble(textBoxes[startStateColumn, i].Text, out startVol) &&
Utils.TryParseUDouble(textBoxes[startStateColumnAux, i].Text, out startVolAux) &&
Utils.TryParseUDouble(textBoxes[endStateColumn, i].Text, out endVol) &&
Utils.TryParseUDouble(textBoxes[endStateColumnAux, i].Text, out endVolAux))
{
double startState = Units.ConvertFrom(VolumeUnit, startVol + startVolAux);
double endState = Units.ConvertFrom(VolumeUnit, endVol + endVolAux);
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);
}
}
else
{
/// Single
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 sender, KeyPressEventArgs e)
{
if (!isHandlersEnabled) return;
string[] kj = (sender 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(sender, e);
return;
}
}
}
if (e.KeyChar == '\r')
{
/// Change focus
if (FindNextEnabledTextBox(textBoxes, ref k, ref j, isLrOrder))
{
okButton.Focus();
}
else
{
largeTextBox.Text = string.Format("{0}: {1}", j + 1, textBoxes[k, j].Text);
largeExclamationLabel.Visible = false;
if (isCameraPicture && (k == startStateColumn || k == startStateColumnAux ||
k == endStateColumn || k == endStateColumnAux))
{
int rrIx = (isCompound ? 2 : 1) * j + ((k == startStateColumnAux || k == endStateColumnAux) ? 1 : 0);
LoadImage(sender, (k == startStateColumn || k == startStateColumnAux) ? Tst.Start : Tst.End, rrIx);
}
textBoxes[k, j].Focus();
}
}
}
/// Handle mouse clicks so that the large text box and the large exclamation mark are updated
private void textBox_MouseClick(object sender, MouseEventArgs e)
{
textBox_TextChanged(sender, e);
}
private void textBox_TextChanged(object sender, EventArgs e)
{
if (!isHandlersEnabled) return;
string[] kj = (sender 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 == startStateColumnAux || k == endStateColumn || k == endStateColumnAux))
{
UpdateExclamation(j);
}
else
{
largeExclamationLabel.Visible = false;
}
}
void UpdateExclamation(int j)
{
double startVol, startVolAux, endVol, endVolAux;
if (isCompound && startStateColumn >= 0 && Utils.TryParseUDouble(textBoxes[startStateColumn, j].Text, out startVol) &&
startStateColumnAux >= 0 && Utils.TryParseUDouble(textBoxes[startStateColumnAux, j].Text, out startVolAux) &&
endStateColumn >= 0 && Utils.TryParseUDouble(textBoxes[endStateColumn, j].Text, out endVol) &&
endStateColumnAux >= 0 && Utils.TryParseUDouble(textBoxes[endStateColumnAux, j].Text, out endVolAux))
{
double startState = Units.ConvertFrom(VolumeUnit, startVol + startVolAux);
double endState = Units.ConvertFrom(VolumeUnit, endVol + endVolAux);
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 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 = false;
}
}
private void textBox_MouseDown(object sender, MouseEventArgs e)
{
textBox_AcceptsTabChanged(sender, e);
}
private void textBox_AcceptsTabChanged(object sender, EventArgs e)
{
2023-08-08 12:59:32 +00:00
if (!isHandlersEnabled || !isCameraPicture) return;
string[] kj = (sender as TextBox).Name.Split(new char[] { '~' });
int k = int.Parse(kj[0]);
int j = int.Parse(kj[1]);
2023-08-08 12:59:32 +00:00
if (k == startStateColumn || k == endStateColumn)
{
LoadImage(sender, (k == startStateColumn) ? Tst.Start : Tst.End, j);
}
}
/// <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;
}
/// <summary>
/// Loads appropriate image into pictureBox1.Image
/// Handles mouse clicks and Tab-changes inside text boxes.
/// </summary>
/// <param name="startOrEnd">Tst.Start or Tst.End</param>
/// <param name="rrIx">0-based WM position</param>
void LoadImage(object sender, Tst startOrEnd, int rrIx)
{
string imageFileName;
if (startOrEnd == Tst.Start && startImages[rrIx] != null)
imageFileName = startImages[rrIx];
else if (startOrEnd == Tst.End && endImages[rrIx] != null)
imageFileName = endImages[rrIx];
else
imageFileName = string.Empty;
if (imageFileName == lastImageName)
{
return;
}
lastImageName = imageFileName;
lastWmNr1 = rrIx + 1;
if (string.IsNullOrEmpty(imageFileName) || !File.Exists(imageFileName))
{
lastImage = null;
}
else
{
using (Bitmap tempBmp = System.Drawing.Image.FromFile(imageFileName) as Bitmap)
{
Rectangle rect;
if (tempBmp.Height < tempBmp.Width)
{
int hght = tempBmp.Height;
int leftMargin = (tempBmp.Width - hght) / 2;
rect = new Rectangle(leftMargin, 0, hght, hght);
}
else
{
int hght = tempBmp.Height;
int wdth = tempBmp.Width;
int margin = (hght - wdth) / 2;
rect = new Rectangle(0, margin, wdth, wdth);
}
//int hght = tempBmp.Height;
//int leftMargin = (tempBmp.Width - hght) / 2;
//lastImage = new Bitmap(tempBmp.Clone(new Rectangle(leftMargin, 0, hght, hght), tempBmp.PixelFormat));
lastImage = new Bitmap(tempBmp.Clone(rect, tempBmp.PixelFormat));
if (isZoomed[wmNr0b])
{
zoomedImage = MakeZoomedImage(lastImage, zoomCenterLoc[wmNr0b]);
}
}
}
ShowImage();
}
/// <summary>
/// Left click = Zoom, Right click = Unzoom
/// </summary>
private void pictureBox1_Click(object sender, EventArgs e)
{
if (lastImage == null) return;
MouseEventArgs me = (MouseEventArgs)e;
if (me.Button == MouseButtons.Left && !isZoomed[wmNr0b])
{
switch (imageRotation)
{
case Rotation.R0:
zoomCenterLoc[wmNr0b] = me.Location;
break;
case Rotation.R90:
zoomCenterLoc[wmNr0b].X = pictureBox1.Size.Height - me.Location.Y - 1;
zoomCenterLoc[wmNr0b].Y = me.Location.X;
break;
case Rotation.R180:
zoomCenterLoc[wmNr0b].X = pictureBox1.Size.Width - me.Location.X - 1;
zoomCenterLoc[wmNr0b].Y = pictureBox1.Size.Height - me.Location.Y - 1;
break;
case Rotation.R270:
zoomCenterLoc[wmNr0b].X = me.Location.Y;
zoomCenterLoc[wmNr0b].Y = pictureBox1.Size.Width - me.Location.X - 1;
break;
}
zoomedImage = MakeZoomedImage(lastImage, zoomCenterLoc[wmNr0b]);
isZoomed[wmNr0b] = true;
ShowImage();
}
else if (me.Button == MouseButtons.Right)
{
isZoomed[wmNr0b] = false;
ShowImage();
}
}
private void rotateImageButton_Click(object sender, EventArgs e)
{
if (lastImage == null) return;
if (++imageRotation == Rotation.Count)
{
imageRotation = Rotation.R0;
}
ShowImage();
}
Bitmap MakeZoomedImage(Bitmap oriImage, Point centerLoc)
{
float picBoxW = (float)pictureBox1.Size.Width;
float picBoxH = (float)pictureBox1.Size.Height;
float newRelX = Math.Max(0, Math.Min(centerLoc.X / picBoxW - 0.25f, 0.5f));
float newRelY = Math.Max(0, Math.Min(centerLoc.Y / picBoxH - 0.25f, 0.5f));
Rectangle newRect = new Rectangle((int)(newRelX * oriImage.Width),
(int)(newRelY * oriImage.Height),
oriImage.Width / 2,
oriImage.Height / 2); /// image resolution, 1280 x 960
return lastImage.Clone(newRect, oriImage.PixelFormat);
}
///
/// Used by ShowImage()
///
Brush lightBrush = new SolidBrush(Color.LightGreen);
Brush darkBrush = new SolidBrush(Color.Green);
Font largeFont = new Font("arial", 24.0F, FontStyle.Bold); /// normal image
2026-03-12 13:50:47 +00:00
Font smallFont = new Font("arial", 12.0F, FontStyle.Bold);
/// zoomed image
/// <summary>
/// Show selected image in the picture box.
/// lastWmNr1, lastImage, zoomedImage, isZoomed and imageRotation are used.
/// </summary>
void ShowImage()
{
2023-08-08 12:59:32 +00:00
if (pictureBox1 == null) return;
if (lastImage == null)
{
pictureBox1.Image = new Bitmap(TBF.Properties.Resources.Empty);
return;
}
Bitmap tempBmp = isZoomed[wmNr0b]
? new Bitmap(zoomedImage.Clone(new Rectangle(0, 0, zoomedImage.Width, zoomedImage.Height), zoomedImage.PixelFormat))
: new Bitmap(lastImage.Clone(new Rectangle(0, 0, lastImage.Width, lastImage.Height), lastImage.PixelFormat));
switch (imageRotation)
{
case Rotation.R90: tempBmp.RotateFlip(RotateFlipType.Rotate270FlipNone); break;
case Rotation.R180: tempBmp.RotateFlip(RotateFlipType.Rotate180FlipNone); break;
case Rotation.R270: tempBmp.RotateFlip(RotateFlipType.Rotate90FlipNone); break;
}
/// Write water meter number to the bitmap
using (var grph = Graphics.FromImage(tempBmp))
{
Font font = isZoomed[wmNr0b] ? smallFont : largeFont;
int margin = isZoomed[wmNr0b] ? 1 : 2;
int pixelsCount = 0;
int brightness = 0;
for (int x = margin; x <= 8 * margin; x += margin)
{
for (int y = margin; y <= 8 * margin; y += margin)
{
brightness += (int)tempBmp.GetPixel(x, y).G;
pixelsCount++;
}
}
brightness /= pixelsCount;
Brush brush = (brightness > 160) ? darkBrush : lightBrush;
grph.DrawString(lastWmNr1.ToString(), font, brush, margin, margin);
}
pictureBox1.Image = tempBmp;
}
#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)
{
StopAutoCloseCountdown();
DialogResult = DialogResult.Cancel;
Close();
}
#endregion
2026-03-12 13:50:47 +00:00
public event EventHandler<VolumeReadEventArgs> VolumeStartReadbyRegReader;
public event EventHandler<VolumeReadDoneEventArgs> DoneUpdateByRegReader;
public sealed class VolumeReadEventArgs : EventArgs
{
public IRegReader Reader { get; private set; }
public double Volume { get; private set; }
public VolumeReadEventArgs(IRegReader reader, double volume)
{
Reader = reader;
Volume = volume;
}
}
public sealed class VolumeReadDoneEventArgs : EventArgs
{
public VolumeReadDoneEventArgs()
{
}
}
protected virtual void OnReadVolume(IRegReader reader, double volume)
{
var handler = VolumeStartReadbyRegReader; // copy for thread-safety
if (handler == null || !CanUpdateDataEntryUi())
2026-03-12 13:50:47 +00:00
{
log.Debug("DATA_ENTRY_VOLUME_EVENT_SKIPPED Form is closing or disposed.");
return;
2026-03-12 13:50:47 +00:00
}
Action dispatch = () =>
2026-03-12 13:50:47 +00:00
{
if (!CanUpdateDataEntryUi())
{
log.Debug("DATA_ENTRY_VOLUME_EVENT_DISPATCH_SKIPPED Form closed before dispatch.");
return;
}
2026-03-12 13:50:47 +00:00
handler(this, new VolumeReadEventArgs(reader, volume));
};
if (InvokeRequired)
{
TryBeginDataEntryInvoke(dispatch, "volume result");
return;
2026-03-12 13:50:47 +00:00
}
dispatch();
2026-03-12 13:50:47 +00:00
}
protected virtual void OnReadDone()
{
var handler = DoneUpdateByRegReader; // copy for thread-safety
if (handler == null || !CanUpdateDataEntryUi())
2026-03-12 13:50:47 +00:00
{
log.Debug("DATA_ENTRY_VOLUME_DONE_EVENT_SKIPPED Form is closing or disposed.");
return;
2026-03-12 13:50:47 +00:00
}
Action dispatch = () =>
2026-03-12 13:50:47 +00:00
{
if (!CanUpdateDataEntryUi())
{
log.Debug("DATA_ENTRY_VOLUME_DONE_DISPATCH_SKIPPED Form closed before dispatch.");
return;
}
2026-03-12 13:50:47 +00:00
handler(this, new VolumeReadDoneEventArgs());
};
if (InvokeRequired)
{
TryBeginDataEntryInvoke(dispatch, "volume completion");
return;
}
dispatch();
}
private bool CanUpdateDataEntryUi()
{
return !isClosing && !IsDisposed && !Disposing;
}
private void TryBeginDataEntryInvoke(Action action, string operation)
{
if (!CanUpdateDataEntryUi() || !IsHandleCreated)
return;
try
{
BeginInvoke(action);
}
catch (ObjectDisposedException)
{
log.DebugFormat(
"DATA_ENTRY_VOLUME_INVOKE_SKIPPED Operation={0}, Reason=disposed",
operation);
}
catch (InvalidOperationException ex)
{
log.DebugFormat(
"DATA_ENTRY_VOLUME_INVOKE_SKIPPED Operation={0}, Reason={1}",
operation,
ex.Message);
2026-03-12 13:50:47 +00:00
}
}
public async void ReadVolumeAsync(IEnumerable<IRegReader> regReaders)
{
log.Debug("Reading serial numbers from register readers...");
try
{
var groups = regReaders
.OfType<IRegReaderSmart>()
.GroupBy(r => r.Group)
.OrderBy(g => g.Key);
foreach (var group in groups)
{
var subGroups = group
.GroupBy(r => r.MuxBoardNrOrGroup14)
.OrderBy(sg => sg.Key);
foreach (var subGroup in subGroups)
{
log.Debug(string.Format("Processing Group {0}, SubGroup {1}", group.Key, subGroup.Key));
// Start tasks in parallel inside subgroup
var tasks = subGroup.Select(async r =>
{
IRegReader reader = (IRegReader)r;
log.DebugFormat(
"DATA_ENTRY_VOLUME_READ_START Name={0}, Position={1}, DebugLevel={2}, " +
"Mode={3}, Group={4}, SubGroup={5}, Thread={6}",
reader.Name, reader.Position, reader.DebugLevel,
isEnd ? "end" : "begin", r.Group, r.MuxBoardNrOrGroup14,
System.Threading.Thread.CurrentThread.ManagedThreadId);
2026-03-12 13:50:47 +00:00
double volume = Double.NaN;
if (isEnd)
{
volume = await r.DataEntry_ReadEndVolume(dataEntryReadTimeoutMs).ConfigureAwait(false);
2026-03-12 13:50:47 +00:00
}
else
{
volume = await r.DataEntry_ReadBeginVolume(dataEntryReadTimeoutMs).ConfigureAwait(false);
2026-03-12 13:50:47 +00:00
}
log.DebugFormat(
"DATA_ENTRY_VOLUME_READ_RESULT Name={0}, Position={1}, DebugLevel={2}, " +
"Mode={3}, Volume={4}, IsNaN={5}, Thread={6}",
reader.Name, reader.Position, reader.DebugLevel,
isEnd ? "end" : "begin", volume, Double.IsNaN(volume),
System.Threading.Thread.CurrentThread.ManagedThreadId);
return new KeyValuePair<IRegReader, double>(reader, volume);
2026-03-12 13:50:47 +00:00
}).ToList();
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
if (isClosing)
{
log.Debug("DATA_ENTRY_VOLUME_RESULTS_SKIPPED Form was closed while reading.");
return;
}
2026-03-12 13:50:47 +00:00
foreach (var kv in results)
{
var reader = kv.Key;
var volume = kv.Value;
// Notify for each successful read
OnReadVolume(reader, volume);
}
}
}
}
catch (Exception ex)
{
log.Error("Error reading Strat Volume from register readers", ex);
}
finally
{
OnReadDone();
}
log.Debug("Reading Start Volume DONE!");
}
public void AutoClickOkAfterDelay(int delayMs = 5000)
2026-03-12 13:50:47 +00:00
{
if (delayMs <= 0 || IsDisposed || Disposing)
return;
if (InvokeRequired)
{
BeginInvoke(new Action<int>(AutoClickOkAfterDelay), delayMs);
return;
}
StopAutoCloseCountdown();
okButtonTextBeforeAutoClose = okButton.Text;
autoCloseSecondsRemaining = Math.Max(1, (int)Math.Ceiling(delayMs / 1000D));
showAutoCloseCountdown = autoCloseSecondsRemaining >= AutoCloseCountdownDisplayThresholdSeconds;
if (showAutoCloseCountdown)
UpdateAutoCloseButtonText();
autoCloseTimer = new System.Windows.Forms.Timer { Interval = 1000 };
autoCloseTimer.Tick += AutoCloseTimer_Tick;
autoCloseTimer.Start();
2026-03-12 13:50:47 +00:00
}
private void AutoCloseTimer_Tick(object sender, EventArgs e)
2026-03-12 13:50:47 +00:00
{
autoCloseSecondsRemaining--;
if (autoCloseSecondsRemaining > 0)
{
UpdateAutoCloseButtonText();
return;
}
2026-03-12 13:50:47 +00:00
bool canClick = okButton.IsHandleCreated && okButton.Enabled && okButton.Visible;
StopAutoCloseCountdown();
if (canClick)
okButton.PerformClick();
}
private void UpdateAutoCloseButtonText()
{
if (!showAutoCloseCountdown)
return;
okButton.Text = string.Format("{0} ({1})", okButtonTextBeforeAutoClose, autoCloseSecondsRemaining);
}
private void StopAutoCloseCountdown()
{
if (autoCloseTimer != null)
2026-03-12 13:50:47 +00:00
{
autoCloseTimer.Stop();
autoCloseTimer.Tick -= AutoCloseTimer_Tick;
autoCloseTimer.Dispose();
autoCloseTimer = null;
2026-03-12 13:50:47 +00:00
}
if (okButtonTextBeforeAutoClose != null && !okButton.IsDisposed)
okButton.Text = okButtonTextBeforeAutoClose;
okButtonTextBeforeAutoClose = null;
showAutoCloseCountdown = false;
autoCloseSecondsRemaining = 0;
2026-03-12 13:50:47 +00:00
}
2022-04-01 10:31:58 +00:00
}
}