- Introduce `bAutoRead` and `iAutocloseGap` fields for serial number auto-read and delay-based form auto-close. - Update constructors and methods in `CycleBgEnForm`, `TestStartEndForm`, and related classes to handle new functionality. - Refactor serial number reading logic to improve maintainability. - Extend configuration (`EntryFormCfg`) for new auto-read and auto-close options. - Update `AssemblyVersion` and `AssemblyFileVersion` to `3.9.2218.1`.
1236 lines
48 KiB
C#
1236 lines
48 KiB
C#
///
|
|
/// Copyright (c) 2023 Sensus Slovensko a.s.
|
|
///
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
using log4net;
|
|
using NHibernate.Util;
|
|
using Results.Entities;
|
|
using TBF.Resources;
|
|
using TBF.Rig.GenericDevices;
|
|
|
|
namespace TBF.Rig.DataEntry.Uni
|
|
{
|
|
public partial class CycleBgEnForm : Form, GenericDevices.IHasCompleted
|
|
{
|
|
private static readonly ILog log = LogManager.GetLogger(typeof(CycleBgEnForm));
|
|
|
|
/// Arguments
|
|
public readonly IList<WaterMeter> WaterMeters;
|
|
readonly int lineSize;
|
|
readonly FontSz sz;
|
|
readonly bool isLrOrder; /// true = controls obtain focus after ENTER key in left-right order, false = top-down order
|
|
readonly IList<DEItem> commonItems;
|
|
readonly IList<DEItem> colItems;
|
|
readonly bool isEnd;
|
|
readonly string formCloseKeys;
|
|
|
|
/// Derived from arguments in the constructor
|
|
readonly WaterMeter firstValidWM;
|
|
readonly int wmsCount;
|
|
readonly int linesCount;
|
|
readonly int commonItemsCount;
|
|
|
|
/// Size and layout related values
|
|
readonly Font font;
|
|
readonly int meterHeight;
|
|
readonly int meterWidth;
|
|
readonly int margin;
|
|
readonly int spacing;
|
|
readonly int buttonsWidth;
|
|
readonly int hdrHeight;
|
|
readonly int labelWid;
|
|
readonly int checkBoxWid;
|
|
|
|
/// UI elements
|
|
readonly Label[] commonLabels;
|
|
readonly ComboBox[] commonComboBoxes;
|
|
readonly Label[] labels; /// Labels for water meter numbers
|
|
readonly ComboBox[,] comboBoxes; /// Combo boxes for values in columns
|
|
readonly CheckBox[] checkBoxes; /// Check boxes for water meter enable/disable
|
|
///
|
|
bool readSerialNoByRegisterReader = 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;
|
|
|
|
|
|
readonly MultiPurposeBtnFunction multiPurposeButtonFn;
|
|
bool multiPurposeButtonFlag;
|
|
|
|
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 readonly IRegReader[] regReaders;
|
|
|
|
|
|
/// <summary>
|
|
/// Parameterless constructor for common functionality
|
|
/// </summary>
|
|
public CycleBgEnForm()
|
|
{
|
|
InitializeComponent();
|
|
ControlBox = false;
|
|
completed = false;
|
|
StartForceCloseHandler();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="waterMeters">Water meters</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="commonItems">Common items displayed in the upper part of the form</param>
|
|
/// <param name="colItems">Water meter items displayed in columns (in the matrix in the main part of the form)</param>
|
|
/// <param name="isEnd">true = This form is displayed at the end of cycle</param>
|
|
public CycleBgEnForm(IList<WaterMeter> waterMeters, int _lineSize, string title, FontSz sz, bool isLrOrder, bool isCameraPicture,
|
|
string formCloseKeys, IList<DEItem> commonItems, IList<DEItem> colItems, bool AutoRead, int AutoCloseGap, bool isEnd = false)
|
|
: this()
|
|
{
|
|
/// Arguments
|
|
this.WaterMeters = waterMeters;
|
|
this.lineSize = Math.Min(waterMeters.Count, _lineSize);
|
|
this.Text = !string.IsNullOrEmpty(title) ? title : string.Empty;
|
|
this.sz = sz;
|
|
this.isLrOrder = isLrOrder;
|
|
this.formCloseKeys = !string.IsNullOrEmpty(formCloseKeys) ? formCloseKeys : string.Empty;
|
|
this.commonItems = commonItems;
|
|
this.colItems = colItems;
|
|
this.isEnd = isEnd;
|
|
|
|
this.bAutoRead = AutoRead;
|
|
this.iAutocloseGap = AutoCloseGap; //autoclose disabled by default, if > 0 is enabled
|
|
|
|
/// Preserve column items for use in SummaryResults
|
|
if (!isEnd)
|
|
{
|
|
/// cycle beginning
|
|
DEItem.ClearSummaryColumns();
|
|
foreach (var it in colItems) if (it.Content != Ct.SerialNr) DEItem.AddSummaryColumn(it);
|
|
}
|
|
else
|
|
{
|
|
/// cycle end
|
|
foreach (var it in colItems) if (it.Content != Ct.SerialNr) DEItem.AddSummaryColumn(it);
|
|
}
|
|
|
|
ls = Program.LocalSettings;
|
|
|
|
/// Readonly variables derived from arguments
|
|
commonItemsCount = (commonItems == null) ? 0 : commonItems.Count;
|
|
wmsCount = (waterMeters == null) ? 0 : waterMeters.Count;
|
|
linesCount = (wmsCount + Math.Max(1, lineSize) - 1) / Math.Max(1, lineSize);
|
|
firstValidWM = (waterMeters != null) ? waterMeters.FirstOrDefault(x => (x != null && !x.Disabled)) : null;
|
|
///
|
|
commonLabels = new Label[commonItemsCount];
|
|
commonComboBoxes = new ComboBox[commonItemsCount];
|
|
labels = new Label[wmsCount];
|
|
comboBoxes = new ComboBox[colItems.Count, wmsCount];
|
|
checkBoxes = new CheckBox[wmsCount];
|
|
|
|
///
|
|
/// Determine the multi purpose button function
|
|
///
|
|
multiPurposeButton.Visible = false;
|
|
multiPurposeButtonFn = MultiPurposeBtnFunction.None;
|
|
int buttonsWidth = 350;
|
|
for (int k = 0; k < colItems.Count; k++)
|
|
{
|
|
if ((colItems[k].Content == Ct.SerialNr || colItems[k].Content == Ct.SerialNrAux) && colItems[k].Action != Ac.LoadReadOnly)
|
|
{
|
|
multiPurposeButton.Visible = true;
|
|
multiPurposeButton.Text = "Auto s/n";
|
|
multiPurposeButtonFn = MultiPurposeBtnFunction.AutoSN;
|
|
buttonsWidth += 147;
|
|
break;
|
|
}
|
|
|
|
if (colItems[k].Content == Ct.PrintLabel)
|
|
{
|
|
multiPurposeButton.Visible = true;
|
|
multiPurposeButton.Text = string.Format("{0} ({1}/{2})", Strings.Print, Strings.yes, Strings.no);
|
|
multiPurposeButtonFn = MultiPurposeBtnFunction.PrintLabelsOnOff;
|
|
multiPurposeButtonFlag = false;
|
|
buttonsWidth += 147;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// Layout related readonly variables derived from argument 'sz'
|
|
switch (sz)
|
|
{
|
|
case FontSz.S:
|
|
font = new Font("Verdana", 12, FontStyle.Regular);
|
|
meterHeight = 26; /// Height of a ComboBox control
|
|
margin = 20;
|
|
spacing = 8;
|
|
this.buttonsWidth = buttonsWidth;
|
|
hdrHeight = 120;
|
|
labelWid = 31;
|
|
checkBoxWid = 23;
|
|
break;
|
|
|
|
default:
|
|
case FontSz.M:
|
|
font = new Font("Verdana", 14, FontStyle.Regular);
|
|
meterHeight = 31; /// Height of a ComboBox control
|
|
margin = 25;
|
|
spacing = 10;
|
|
this.buttonsWidth = buttonsWidth;
|
|
hdrHeight = 140;
|
|
labelWid = 34;
|
|
checkBoxWid = 23;
|
|
break;
|
|
|
|
case FontSz.L:
|
|
font = new Font("Verdana", 18, FontStyle.Regular);
|
|
meterHeight = 37; /// Height of a ComboBox control
|
|
margin = 30;
|
|
spacing = 12;
|
|
this.buttonsWidth = buttonsWidth;
|
|
hdrHeight = 160;
|
|
labelWid = 40;
|
|
checkBoxWid = 23;
|
|
break;
|
|
}
|
|
meterWidth = labelWid + checkBoxWid + spacing;
|
|
foreach (var ci in colItems) { meterWidth += ci.Width + spacing; }
|
|
|
|
int tabIndex = 1;
|
|
|
|
///
|
|
/// Group box with common items
|
|
///
|
|
int groupBoxWidth = 0;
|
|
int groupBoxHeight = 0;
|
|
if (commonItemsCount > 0)
|
|
{
|
|
var groupBox = new GroupBox();
|
|
|
|
int labelsWidth = 0;
|
|
for (int ix = 0; ix < commonItemsCount; ix++)
|
|
{
|
|
int oneLabelWidth = Convert.ToInt32(Graphics.FromImage(new Bitmap(1, 1)).MeasureString(commonItems[ix].Caption, font).Width);
|
|
var oneLabel = new Label
|
|
{
|
|
Text = commonItems[ix].Caption,
|
|
Font = font,
|
|
Location = new Point(margin, (ix + 2) * spacing + ix * meterHeight),
|
|
Size = new Size(oneLabelWidth + spacing, meterHeight),
|
|
TextAlign = ContentAlignment.MiddleLeft,
|
|
TabIndex = tabIndex++,
|
|
Parent = groupBox,
|
|
};
|
|
groupBox.Controls.Add(oneLabel);
|
|
labelsWidth = Math.Max(labelsWidth, oneLabelWidth);
|
|
}
|
|
|
|
int combosWidth = 0;
|
|
for (int ix = 0; ix < commonItemsCount; ix++)
|
|
{
|
|
var cb = new ComboBox
|
|
{
|
|
Name = string.Format("-1~{0}", ix + 1),
|
|
Location = new Point(margin + spacing + labelsWidth, (ix + 2) * spacing + ix * meterHeight),
|
|
Size = new Size(commonItems[ix].Width, meterHeight),
|
|
Enabled = (commonItems[ix].Action != Ac.LoadReadOnly),
|
|
Font = font,
|
|
TabIndex = tabIndex++,
|
|
Parent = groupBox,
|
|
};
|
|
|
|
cb.SelectedIndexChanged += new System.EventHandler(comboBox_SelectedIndexChanged);
|
|
cb.TextChanged += new System.EventHandler(comboBox_TextChanged);
|
|
cb.KeyPress += new System.Windows.Forms.KeyPressEventHandler(comboBox_KeyPress);
|
|
|
|
switch (ix)
|
|
{
|
|
case 0: AddHistoryToCombo(cb, isEnd ? ls.LastEnComm1 : ls.LastBgComm1); break;
|
|
case 1: AddHistoryToCombo(cb, isEnd ? ls.LastEnComm2 : ls.LastBgComm2); break;
|
|
case 2: AddHistoryToCombo(cb, isEnd ? ls.LastEnComm3 : ls.LastBgComm3); break;
|
|
}
|
|
|
|
commonComboBoxes[ix] = cb;
|
|
groupBox.Controls.Add(cb);
|
|
combosWidth = Math.Max(combosWidth, commonItems[ix].Width);
|
|
}
|
|
///
|
|
groupBox.Location = new Point(margin, margin - spacing);
|
|
groupBoxWidth = labelsWidth + combosWidth + 2 * margin + spacing;
|
|
groupBoxHeight = commonItemsCount * meterHeight + (commonItemsCount + 2) * spacing;
|
|
groupBox.Size = new Size(groupBoxWidth, groupBoxHeight);
|
|
groupBox.Font = font;
|
|
groupBox.TabIndex = tabIndex++;
|
|
groupBox.Parent = this;
|
|
this.Controls.Add(groupBox);
|
|
}
|
|
|
|
///
|
|
/// Table
|
|
///
|
|
int commonCheckBoxLeft = 0;
|
|
int commonCheckBoxTop = 0;
|
|
int columnsTop = Math.Max(hdrHeight, groupBoxHeight + 2 * margin + spacing);
|
|
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 comboBox = new ComboBox
|
|
{
|
|
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,
|
|
};
|
|
|
|
comboBox.SelectedIndexChanged += new System.EventHandler(this.comboBox_SelectedIndexChanged);
|
|
comboBox.TextChanged += new System.EventHandler(this.comboBox_TextChanged);
|
|
comboBox.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.comboBox_KeyPress);
|
|
|
|
if (ci.Content == Ct.PrintLabel)
|
|
{
|
|
comboBox.Items.Add(Strings.yes);
|
|
comboBox.Items.Add(Strings.no);
|
|
}
|
|
else
|
|
{
|
|
var lastVals = GetHistoryFromLS(isEnd, k);
|
|
comboBox.Items.Add(lastVals.Length > wmPos0 ? lastVals[wmPos0] : string.Empty); /// History in drop-down menu
|
|
}
|
|
|
|
left += ci.Width + spacing;
|
|
comboBoxes[k, wmPos0] = comboBox;
|
|
this.Controls.Add(comboBox);
|
|
}
|
|
|
|
var checkBox = new CheckBox
|
|
{
|
|
Location = new Point(left, top + 6),
|
|
Size = new Size(checkBoxWid, 23),
|
|
TabIndex = tabIndex++,
|
|
Parent = this,
|
|
};
|
|
checkBoxes[wmPos0] = checkBox;
|
|
this.Controls.Add(checkBox);
|
|
|
|
if (commonCheckBoxLeft == 0)
|
|
{
|
|
commonCheckBoxLeft = left;
|
|
commonCheckBoxTop = columnsTop - meterHeight;
|
|
}
|
|
}
|
|
|
|
okButton.TabIndex = tabIndex++;
|
|
clearButton.TabIndex = tabIndex++;
|
|
|
|
var commonCheckBox = new CheckBox
|
|
{
|
|
Location = new Point(commonCheckBoxLeft, commonCheckBoxTop + 6),
|
|
Size = new Size(checkBoxWid, 23),
|
|
TabIndex = tabIndex++,
|
|
Parent = this,
|
|
};
|
|
commonCheckBox.CheckedChanged += new System.EventHandler(commonCheckBox_CheckedChanged);
|
|
this.Controls.Add(commonCheckBox);
|
|
}
|
|
|
|
///
|
|
/// 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), margin + groupBoxWidth + this.buttonsWidth),
|
|
titleBarHeight + columnsTop + lineSize * (meterHeight + spacing) + margin);
|
|
|
|
Localize();
|
|
InitializeBoxes();
|
|
|
|
/// Set focus to the first enabled ComboBox
|
|
bool activeControlFound = false;
|
|
for (int ix = 0; ix < commonItemsCount; ix++)
|
|
{
|
|
if (commonComboBoxes[ix].Enabled)
|
|
{
|
|
ActiveControl = commonComboBoxes[ix];
|
|
activeControlFound = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!activeControlFound)
|
|
{
|
|
/// Initialize k,j so that after the 1st increment k = 0 and j = 0
|
|
int k, j;
|
|
if (isLrOrder) { k = -1; j = 0; }
|
|
else { k = 0; j = -1; }
|
|
|
|
/// Change focus
|
|
if (FindNextEnabledCombo(comboBoxes, ref k, ref j, isLrOrder))
|
|
{
|
|
ActiveControl = okButton;
|
|
}
|
|
else
|
|
{
|
|
ActiveControl = comboBoxes[k, j];
|
|
}
|
|
}
|
|
}
|
|
|
|
public CycleBgEnForm(IList<WaterMeter> waterMeters, IRegReader[] regReaders, int title, string myCfgBgTitle, FontSz myCfgBgSize, bool myCfgBgIsLrOrder, bool myCfgBgIsCameraPicture, string myCfgBgFormCloseKeys, IList<DEItem> getItems, IList<DEItem> getColumns, bool b, IRegReader[] iRegReaders, bool AutoRead, int AutoCloseGap)
|
|
: this(waterMeters, title, myCfgBgTitle, myCfgBgSize, myCfgBgIsLrOrder, myCfgBgIsCameraPicture, myCfgBgFormCloseKeys, getItems, getColumns,AutoRead,AutoCloseGap, b)
|
|
{
|
|
this.regReaders = regReaders;
|
|
if (bAutoRead)
|
|
{
|
|
ReadAndProcessSerialNumbersByRegReader();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add combo box items from a history stored in a string array (obtained usually from LocalSettings)
|
|
/// </summary>
|
|
/// <param name="comboBox">ComboBox to prepare</param>
|
|
/// <param name="history">String array with a history</param>
|
|
void AddHistoryToCombo(ComboBox comboBox, string[] history)
|
|
{
|
|
if (history != null)
|
|
{
|
|
for (int i = 0; i < history.Length; i++) comboBox.Items.Add(history[i]);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an array of strings from ls.
|
|
/// Never returns null, null is converted to new string[0].
|
|
/// </summary>
|
|
/// <param name="isEnd">false = Beginning of cycle, true = End of cycle</param>
|
|
/// <param name="k">Column number (0-based)</param>
|
|
/// <returns>Array of strings</returns>
|
|
string[] GetHistoryFromLS(bool isEnd, int k)
|
|
{
|
|
if (isEnd)
|
|
{
|
|
switch (k)
|
|
{
|
|
case 0: if (ls.LastEnColA == null) ls.LastEnColA = new string[0]; return ls.LastEnColA;
|
|
case 1: if (ls.LastEnColB == null) ls.LastEnColB = new string[0]; return ls.LastEnColB;
|
|
case 2: if (ls.LastEnColC == null) ls.LastEnColC = new string[0]; return ls.LastEnColC;
|
|
case 3: if (ls.LastEnColD == null) ls.LastEnColD = new string[0]; return ls.LastEnColD;
|
|
case 4: if (ls.LastEnColE == null) ls.LastEnColE = new string[0]; return ls.LastEnColE;
|
|
default: return new string[0];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (k)
|
|
{
|
|
case 0: if (ls.LastBgColA == null) ls.LastBgColA = new string[0]; return ls.LastBgColA;
|
|
case 1: if (ls.LastBgColB == null) ls.LastBgColB = new string[0]; return ls.LastBgColB;
|
|
case 2: if (ls.LastBgColC == null) ls.LastBgColC = new string[0]; return ls.LastBgColC;
|
|
case 3: if (ls.LastBgColD == null) ls.LastBgColD = new string[0]; return ls.LastBgColD;
|
|
case 4: if (ls.LastBgColE == null) ls.LastBgColE = new string[0]; return ls.LastBgColE;
|
|
default: return new string[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
void Localize()
|
|
{
|
|
okButton.Text = Strings.OkBtnText;
|
|
clearButton.Text = Strings.ClearBtnText;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize combo boxes state defined by related 'Action'.
|
|
/// Clear check boxes.
|
|
/// </summary>
|
|
void InitializeBoxes()
|
|
{
|
|
isHandlersEnabled = false;
|
|
|
|
/// Common combo boxes
|
|
for (int ix = 0; ix < commonItemsCount; ix++)
|
|
{
|
|
switch (commonItems[ix].Action)
|
|
{
|
|
default:
|
|
case Ac.Clear:
|
|
commonComboBoxes[ix].Text = string.Empty;
|
|
break;
|
|
|
|
case Ac.HistoryLast:
|
|
commonComboBoxes[ix].Text = (commonComboBoxes[ix].Items.Count > 0)
|
|
? commonComboBoxes[ix].Items[0].ToString()
|
|
: string.Empty;
|
|
break;
|
|
|
|
case Ac.Load:
|
|
case Ac.LoadReadOnly:
|
|
commonComboBoxes[ix].Text = DEUtils.GetContent(commonItems[ix].Content, firstValidWM);
|
|
break;
|
|
|
|
case Ac.Set:
|
|
commonComboBoxes[ix].Text = Strings.yes;
|
|
break;
|
|
case Ac.RegReader:
|
|
commonComboBoxes[ix].Text = "--";
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// Table
|
|
for (int k = 0; k < colItems.Count; k++)
|
|
{
|
|
switch (colItems[k].Action)
|
|
{
|
|
default:
|
|
case Ac.Clear:
|
|
for (int i = 0; i < wmsCount; i++) comboBoxes[k, i].Text = string.Empty;
|
|
break;
|
|
|
|
case Ac.HistoryLast:
|
|
for (int i = 0; i < wmsCount; i++)
|
|
{
|
|
comboBoxes[k, i].Text = comboBoxes[k, i].Items.Count > 0 ? comboBoxes[k, i].Items[0].ToString() : string.Empty;
|
|
}
|
|
break;
|
|
|
|
case Ac.Load:
|
|
case Ac.LoadReadOnly:
|
|
for (int i = 0; i < wmsCount; i++)
|
|
{
|
|
comboBoxes[k, i].Text = DEUtils.GetContent(colItems[k].Content, WaterMeters[i]);
|
|
}
|
|
break;
|
|
|
|
case Ac.Set:
|
|
for (int i = 0; i < wmsCount; i++)
|
|
{
|
|
comboBoxes[k, i].Text = (WaterMeters[i] != null && !WaterMeters[i].Disabled) ? Strings.yes : Strings.no;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < wmsCount; i++)
|
|
{
|
|
checkBoxes[i].Checked = isEnd ? (WaterMeters[i] != null && !WaterMeters[i].Disabled) : false;
|
|
}
|
|
|
|
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 ix = 0; ix < commonItemsCount; ix++ )
|
|
{
|
|
DEUtils.PutContent(commonItems[ix].Content, firstValidWM, commonComboBoxes[ix].Text);
|
|
if (commonItems[ix].Action != Ac.LoadReadOnly)
|
|
{
|
|
if (isEnd)
|
|
{
|
|
switch (ix)
|
|
{
|
|
case 0: ls.UpdateHistory(commonComboBoxes[ix].Text, ref ls.LastEnComm1); break;
|
|
case 1: ls.UpdateHistory(commonComboBoxes[ix].Text, ref ls.LastEnComm2); break;
|
|
case 2: ls.UpdateHistory(commonComboBoxes[ix].Text, ref ls.LastEnComm3); break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (ix)
|
|
{
|
|
case 0: ls.UpdateHistory(commonComboBoxes[ix].Text, ref ls.LastBgComm1); break;
|
|
case 1: ls.UpdateHistory(commonComboBoxes[ix].Text, ref ls.LastBgComm2); break;
|
|
case 2: ls.UpdateHistory(commonComboBoxes[ix].Text, ref ls.LastBgComm3); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
/// Propagate common (batch) values to water meters in case of
|
|
/// Content.BatchAndWmOrder, Content.BatchAndWmRemark, Content.YearOfCalibration and Content.ArchivePath
|
|
///
|
|
foreach (var wm in WaterMeters)
|
|
{
|
|
if (wm != null && !wm.Disabled)
|
|
{
|
|
switch (commonItems[ix].Content)
|
|
{
|
|
case Ct.BatchAndWmOrder: wm.PurchaseOrder = firstValidWM.Batch.PurchaseOrder; break;
|
|
case Ct.BatchAndWmRemark: wm.Remark = firstValidWM.Batch.Remark; break;
|
|
case Ct.YearOfCalibration: wm.YearOfProduction = firstValidWM.YearOfProduction; break;
|
|
case Ct.ArchivePath: wm.ArchivePath = firstValidWM.ArchivePath; break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int k = 0; k < colItems.Count; k++)
|
|
{
|
|
/// Store values to water meters and prepare an array for a history in the LocalSettings
|
|
var currentVals = new string[wmsCount];
|
|
for (int i = 0; i < wmsCount; i++)
|
|
{
|
|
DEUtils.PutContent(colItems[k].Content, WaterMeters[i], comboBoxes[k, i].Text);
|
|
currentVals[i] = comboBoxes[k, i].Text;
|
|
}
|
|
|
|
/// Update history
|
|
if (colItems[k].Action != Ac.LoadReadOnly)
|
|
{
|
|
if (isEnd)
|
|
{
|
|
switch (k)
|
|
{
|
|
case 0: ls.LastEnColA = currentVals; break;
|
|
case 1: ls.LastEnColB = currentVals; break;
|
|
case 2: ls.LastEnColC = currentVals; break;
|
|
case 3: ls.LastEnColD = currentVals; break;
|
|
case 4: ls.LastEnColE = currentVals; break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (k)
|
|
{
|
|
case 0: ls.LastBgColA = currentVals; break;
|
|
case 1: ls.LastBgColB = currentVals; break;
|
|
case 2: ls.LastBgColC = currentVals; break;
|
|
case 3: ls.LastBgColD = currentVals; break;
|
|
case 4: ls.LastBgColE = currentVals; break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < wmsCount; i++)
|
|
{
|
|
if (WaterMeters[i] != null) WaterMeters[i].Disabled = !checkBoxes[i].Checked;
|
|
}
|
|
}
|
|
|
|
private void commonCheckBox_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
bool state = (sender as CheckBox).Checked;
|
|
for (int i = 0; i < wmsCount; i++)
|
|
{
|
|
checkBoxes[i].Checked = state;
|
|
}
|
|
}
|
|
|
|
private void okButton_Click(object sender, EventArgs e)
|
|
{
|
|
UpdateWMsFromBoxes();
|
|
|
|
completed = true;
|
|
Close();
|
|
}
|
|
|
|
private void clearButton_Click(object sender, EventArgs e)
|
|
{
|
|
InitializeBoxes();
|
|
}
|
|
|
|
private void multiPurposeButton_Click(object sender, EventArgs e)
|
|
{
|
|
if (multiPurposeButtonFn == MultiPurposeBtnFunction.None) return;
|
|
|
|
if (multiPurposeButtonFn == MultiPurposeBtnFunction.AutoSN)
|
|
{
|
|
///
|
|
/// Find the 1st enabled water meter
|
|
///
|
|
int firstIx;
|
|
for (firstIx = 0; firstIx < wmsCount; firstIx++)
|
|
{
|
|
if (checkBoxes[firstIx].Checked) break;
|
|
}
|
|
if (firstIx == wmsCount)
|
|
{
|
|
return; /// There is not any enabled water meter
|
|
}
|
|
|
|
char[] digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
|
|
|
|
// if (readSerialNoByRegisterReader)
|
|
// {
|
|
// Task.Run(() => ReadAndProcessSerialNumbersByRegReader());
|
|
// }
|
|
// else
|
|
{
|
|
///
|
|
/// Find a column with serial numbers
|
|
///
|
|
for (int k = 0; k < colItems.Count; k++)
|
|
{
|
|
if ((colItems[k].Content == Ct.SerialNr || colItems[k].Content == Ct.SerialNrAux) &&
|
|
colItems[k].Action != Ac.LoadReadOnly)
|
|
{
|
|
///
|
|
/// Column with serial numbers found => perform an auto s/n assignment
|
|
///
|
|
string firstSN = comboBoxes[k, firstIx].Text;
|
|
|
|
|
|
|
|
|
|
|
|
int firstSnNr;
|
|
int startIx = firstSN.IndexOfAny(digits);
|
|
if (startIx >= 0)
|
|
{
|
|
int lastDigitPosPlus1 = startIx + 1;
|
|
var listOfDigits = new List<char>(digits);
|
|
while (lastDigitPosPlus1 < firstSN.Length &&
|
|
listOfDigits.Contains(firstSN[lastDigitPosPlus1]))
|
|
{
|
|
lastDigitPosPlus1++;
|
|
}
|
|
|
|
int digitsCount = lastDigitPosPlus1 - startIx;
|
|
|
|
if (int.TryParse(firstSN.Substring(startIx, digitsCount), out firstSnNr) &&
|
|
firstSnNr >= 0)
|
|
{
|
|
for (int ix = firstIx + 1; ix < wmsCount; ix++)
|
|
{
|
|
if (checkBoxes[ix].Checked)
|
|
{
|
|
firstSnNr++;
|
|
string newSN = firstSnNr.ToString();
|
|
int len = newSN.Length;
|
|
if (len <= digitsCount)
|
|
{
|
|
comboBoxes[k, ix].Text =
|
|
firstSN.Substring(0, startIx + digitsCount - len) + newSN +
|
|
firstSN.Substring(startIx + digitsCount);
|
|
}
|
|
else
|
|
{
|
|
comboBoxes[k, ix].Text = firstSN.Substring(0, startIx) + newSN +
|
|
firstSN.Substring(startIx + digitsCount);
|
|
;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (multiPurposeButtonFn == MultiPurposeBtnFunction.PrintLabelsOnOff)
|
|
{
|
|
///
|
|
/// Find a column with Ct.PrintLabel
|
|
///
|
|
for (int k = 0; k < colItems.Count; k++)
|
|
{
|
|
if (colItems[k].Content == Ct.PrintLabel)
|
|
{
|
|
for (int ix = 0; ix < wmsCount; ix++)
|
|
{
|
|
if (WaterMeters[ix] != null && !WaterMeters[ix].Disabled)
|
|
{
|
|
comboBoxes[k, ix].Text = multiPurposeButtonFlag ? Strings.yes : Strings.no;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
multiPurposeButtonFlag = !multiPurposeButtonFlag;
|
|
}
|
|
}
|
|
|
|
private void ReadAndProcessSerialNumbersByRegReader()
|
|
{
|
|
log.Debug("Reading serial numbers from register readers...");
|
|
this.SerialNumberRead += (s, eArgs) =>
|
|
{
|
|
log.Debug("Serial updated: " + eArgs.SerialNumber);
|
|
UpdateSomethingBySerial(eArgs.Reader, eArgs.SerialNumber);
|
|
};
|
|
|
|
BeforeUpdate();
|
|
this.DoneUpdateBySerial += (s, eArgs) =>
|
|
{
|
|
log.Debug("Serial updated DONE!");
|
|
DoneUpdate();
|
|
};
|
|
|
|
ReadSerialNumbersAsync(regReaders);
|
|
}
|
|
|
|
private void UpdateSomethingBySerial(IRegReader eReader, string eSerialNumber)
|
|
{
|
|
log.Debug($"RegReader name: {eReader.Name}, Serial No updated: " + eSerialNumber);
|
|
PopulateComboBoxWithSerialNumbers(eReader, eSerialNumber);
|
|
}
|
|
|
|
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()
|
|
{
|
|
log.Debug("BeforeUpdate");
|
|
|
|
storeUIForUpdate["okButton"] = this.okButton.Enabled;
|
|
storeUIForUpdate["clearButton"] = this.clearButton.Enabled;
|
|
storeUIForUpdate["multiPurposeButton"] = this.multiPurposeButton.Enabled;
|
|
|
|
this.okButton.Enabled = false;
|
|
this.clearButton.Enabled = false;
|
|
this.multiPurposeButton.Enabled = false;
|
|
|
|
_previousCursor = Cursor.Current;
|
|
Cursor.Current = Cursors.WaitCursor;
|
|
|
|
|
|
//Enable all checkboxes
|
|
|
|
for (int firstIx = 0; firstIx < wmsCount; firstIx++)
|
|
{
|
|
checkBoxes[firstIx].Checked = true;
|
|
}
|
|
|
|
|
|
|
|
}
|
|
private void DoneUpdate()
|
|
{
|
|
log.Debug("DoneUpdate");
|
|
|
|
//enbale only found checkboxes
|
|
int comboRows = comboBoxes.GetLength(0);
|
|
int comboCols = comboBoxes.GetLength(1);
|
|
|
|
for (int k = 0; k < colItems.Count && k < comboRows; k++)
|
|
{
|
|
for (int ix = 0; ix < wmsCount && ix < comboCols; ix++)
|
|
{
|
|
bool letEnable = false;
|
|
var combo = comboBoxes[k, ix];
|
|
if (combo != null)
|
|
{
|
|
letEnable = !string.IsNullOrEmpty(combo.Text);
|
|
}
|
|
|
|
if (letEnable && checkBoxes.Length > ix)
|
|
{
|
|
|
|
checkBoxes[ix].Enabled = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
this.okButton.Enabled = GetStoredOrDefault("okButton");
|
|
this.clearButton.Enabled = GetStoredOrDefault("clearButton");
|
|
this.multiPurposeButton.Enabled = GetStoredOrDefault("multiPurposeButton");
|
|
Cursor.Current = _previousCursor;
|
|
|
|
if (iAutocloseGap > 0)
|
|
{
|
|
AutoClickOkAfterDelay(iAutocloseGap * 1000);
|
|
}
|
|
}
|
|
|
|
private void PopulateComboBoxWithSerialNumbers( IRegReader eReader, string eSerialNumber)
|
|
{
|
|
if (regReaders == null || comboBoxes == null)
|
|
return;
|
|
|
|
int regReadersLength = regReaders.Length;
|
|
int comboRows = comboBoxes.GetLength(0);
|
|
int comboCols = comboBoxes.GetLength(1);
|
|
|
|
for (int k = 0; k < colItems.Count && k < comboRows; k++)
|
|
{
|
|
for (int ix = 0; ix < wmsCount && ix < comboCols; ix++)
|
|
{
|
|
int regIndex = ix + wmsCount*k;
|
|
|
|
if (regIndex >= 0 && regIndex < regReadersLength)
|
|
{
|
|
var combo = comboBoxes[k, ix];
|
|
if (combo != null && regReaders[regIndex] == eReader)
|
|
{
|
|
combo.Text = eSerialNumber;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void comboBox_SelectedIndexChanged(object sndr, EventArgs e)
|
|
{
|
|
if (!isHandlersEnabled) return;
|
|
|
|
string[] kj = (sndr as ComboBox).Name.Split(new char[] { '~' });
|
|
int k = int.Parse(kj[0]);
|
|
int j = int.Parse(kj[1]);
|
|
|
|
isHandlersEnabled = false;
|
|
|
|
if (k >= 0 && colItems[k].Content != Ct.PrintLabel && comboBoxes[k, j].Text == comboBoxes[k, j].Items[0].ToString())
|
|
{
|
|
for (int i = 0; i < wmsCount; i++)
|
|
{
|
|
comboBoxes[k, i].Text = comboBoxes[k, i].Items[0].ToString();
|
|
if (!string.IsNullOrEmpty(comboBoxes[k, i].Text)) checkBoxes[i].Checked = true;
|
|
}
|
|
}
|
|
|
|
isHandlersEnabled = true;
|
|
}
|
|
|
|
private void comboBox_KeyPress(object sndr, KeyPressEventArgs e)
|
|
{
|
|
if (!isHandlersEnabled) return;
|
|
|
|
string[] kj = (sndr as ComboBox).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
|
|
for (int ix = 0; ix < formCloseKeys.Length; ix++)
|
|
{
|
|
if (e.KeyChar == formCloseKeys[ix])
|
|
{
|
|
okButton_Click(sndr, e);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (e.KeyChar == '\r')
|
|
{
|
|
if (k < 0)
|
|
{
|
|
for (int ix = j; ix < commonItemsCount; ix++)
|
|
{
|
|
if (commonComboBoxes[ix].Enabled)
|
|
{
|
|
commonComboBoxes[ix].Focus();
|
|
return;
|
|
}
|
|
}
|
|
|
|
/// No next enabled common item found:
|
|
/// Initialize k,j so that after the 1st increment k = 0 and j = 0
|
|
if (isLrOrder) { k = -1; j = 0; }
|
|
else { k = 0; j = -1; }
|
|
}
|
|
|
|
/// Change focus
|
|
if (FindNextEnabledCombo(comboBoxes, ref k, ref j, isLrOrder))
|
|
{
|
|
okButton.Focus();
|
|
}
|
|
else
|
|
{
|
|
comboBoxes[k, j].Focus();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void comboBox_TextChanged(object sndr, EventArgs e)
|
|
{
|
|
if (!isHandlersEnabled) return;
|
|
|
|
string[] kj = (sndr as ComboBox).Name.Split(new char[] { '~' });
|
|
int k = int.Parse(kj[0]);
|
|
int j = int.Parse(kj[1]);
|
|
|
|
if (k >= 0) checkBoxes[j].Checked = true;
|
|
}
|
|
|
|
/// <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="comboBoxes">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 FindNextEnabledCombo(ComboBox[,] comboBoxes, ref int k, ref int i, bool isLrOrder)
|
|
{
|
|
bool pastTheEndOfArray = false;
|
|
do
|
|
{
|
|
if (isLrOrder)
|
|
{
|
|
if (++k == comboBoxes.GetLength(0))
|
|
{
|
|
k = 0;
|
|
if (++i == comboBoxes.GetLength(1))
|
|
{
|
|
i = 0;
|
|
pastTheEndOfArray = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (++i == comboBoxes.GetLength(1))
|
|
{
|
|
i = 0;
|
|
if (++k == comboBoxes.GetLength(0))
|
|
{
|
|
k = 0;
|
|
pastTheEndOfArray = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
while (!comboBoxes[k, i].Enabled);
|
|
|
|
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);
|
|
};
|
|
}
|
|
|
|
private void OnForceClose(object sender, EventArgs args)
|
|
{
|
|
DialogResult = DialogResult.Cancel;
|
|
Close();
|
|
}
|
|
|
|
#endregion
|
|
|
|
public event EventHandler<SerialNumberReadEventArgs> SerialNumberRead;
|
|
public event EventHandler<SerialNumberReadDoneEventArgs> DoneUpdateBySerial;
|
|
|
|
public sealed class SerialNumberReadEventArgs : EventArgs
|
|
{
|
|
public IRegReader Reader { get; private set; }
|
|
public string SerialNumber { get; private set; }
|
|
|
|
public SerialNumberReadEventArgs(IRegReader reader, string serialNumber)
|
|
{
|
|
Reader = reader;
|
|
SerialNumber = serialNumber;
|
|
}
|
|
}
|
|
|
|
public sealed class SerialNumberReadDoneEventArgs : EventArgs
|
|
{
|
|
public SerialNumberReadDoneEventArgs()
|
|
{
|
|
}
|
|
}
|
|
|
|
protected virtual void OnSerialNumberRead(IRegReader reader, string serial)
|
|
{
|
|
var handler = SerialNumberRead; // copy for thread-safety
|
|
if (handler == null) return;
|
|
if (IsHandleCreated && InvokeRequired)
|
|
{
|
|
BeginInvoke(new Action(() =>
|
|
handler(this, new SerialNumberReadEventArgs(reader, serial))));
|
|
}
|
|
else
|
|
{
|
|
handler(this, new SerialNumberReadEventArgs(reader, serial));
|
|
}
|
|
}
|
|
|
|
protected virtual void OnReadDone()
|
|
{
|
|
var handler = DoneUpdateBySerial; // copy for thread-safety
|
|
if (handler == null) return;
|
|
|
|
if (IsHandleCreated && InvokeRequired)
|
|
{
|
|
BeginInvoke(new Action(() =>
|
|
handler(this, new SerialNumberReadDoneEventArgs())));
|
|
}
|
|
else
|
|
{
|
|
handler(this, new SerialNumberReadDoneEventArgs());
|
|
}
|
|
}
|
|
|
|
public async void ReadSerialNumbersAsync(IEnumerable<IRegReader> regReaders)
|
|
{
|
|
log.Debug($"Reading-> regReaders.length({(regReaders != null ? regReaders.Count() : 0)})");
|
|
if (regReaders == null)
|
|
{
|
|
log.Debug("Reading-> regReaders is null");
|
|
return;
|
|
}
|
|
|
|
|
|
foreach (IRegReader reader in regReaders)
|
|
{
|
|
if (reader == null) continue;
|
|
log.Debug($"Reading-> regReader: {reader.GetType().Name}");
|
|
}
|
|
|
|
|
|
try
|
|
{
|
|
var groups = regReaders
|
|
.OfType<IRegReaderSmart>()
|
|
.GroupBy(r => r.Group)
|
|
.OrderBy(g => g.Key);
|
|
|
|
if (groups != null)
|
|
log.Debug($"Reading-> groups.length({groups.Count()})");
|
|
|
|
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 =>
|
|
{
|
|
var serial = await r.DataEntry_ReadSerialNumber().ConfigureAwait(false);
|
|
return new KeyValuePair<IRegReader, string>((IRegReader)r, serial);
|
|
}).ToList();
|
|
|
|
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
|
|
|
foreach (var kv in results)
|
|
{
|
|
var reader = kv.Key;
|
|
var serial = kv.Value;
|
|
|
|
if (string.IsNullOrWhiteSpace(serial))
|
|
continue;
|
|
|
|
// Notify for each successful read
|
|
OnSerialNumberRead(reader, serial);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error("Error reading serial numbers from register readers", ex);
|
|
}
|
|
finally
|
|
{
|
|
OnReadDone();
|
|
}
|
|
|
|
|
|
log.Debug("Reading serial numbers DONE!");
|
|
}
|
|
|
|
public void AutoClickOkAfterDelay(int delayMs = 10000)
|
|
{
|
|
_ = AutoClickInternal(okButton, delayMs);
|
|
}
|
|
|
|
private async Task AutoClickInternal(Button clickButton, int delayMs)
|
|
{
|
|
await Task.Delay(delayMs);
|
|
|
|
if (clickButton.IsHandleCreated && clickButton.Enabled && clickButton.Visible)
|
|
{
|
|
// Invoke on UI thread
|
|
if (clickButton.InvokeRequired)
|
|
clickButton.BeginInvoke(new Action(() => clickButton.PerformClick()));
|
|
else
|
|
clickButton.PerformClick();
|
|
}
|
|
}
|
|
}
|
|
}
|