2611 lines
113 KiB
C#
2611 lines
113 KiB
C#
/*********************************************************************************************************************/
|
|
/*! @file FM2014TestBench.xaml.cs
|
|
* @brief <i>Test bench implementation for manual tests of FM2014 driver</i>
|
|
*
|
|
* @author Thomas Wiedebusch
|
|
* @date 2026-Feb-02
|
|
*
|
|
* @details <i>Multiple FM2014s can be tested using an FM2014 user control.</i>
|
|
*
|
|
* @copyright © SENSUS GmbH 2026. All rights reserved.
|
|
*********************************************************************************************************************/
|
|
using Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Config;
|
|
using Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core;
|
|
using Sensus.Ui.Fm2014TestBench.UserControls;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.IO.Ports;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Documents;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using System.Windows.Threading;
|
|
using Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core.Consts;
|
|
using Xylem.Common.CommonCore.Consts;
|
|
using Xylem.Common.Utils.ProcessExec.EventArguments;
|
|
using CheckBox = System.Windows.Controls.CheckBox;
|
|
using KeyEventArgs = System.Windows.Input.KeyEventArgs;
|
|
using Label = System.Windows.Controls.Label;
|
|
using TextBox = System.Windows.Controls.TextBox;
|
|
using UserControl = System.Windows.Controls.UserControl;
|
|
using static Sensus.Ui.Fm2014TestBench.Properties.Resources;
|
|
|
|
namespace Sensus.Ui.FM2014TestBench
|
|
{
|
|
/// <summary>
|
|
/// Interaction logic for MainWindow.xaml
|
|
/// </summary>
|
|
public partial class FM2014TestBenchWindow
|
|
{
|
|
#region ------------------------------------------- Properties ------------------------------------------------
|
|
// cancellation token
|
|
private CancellationTokenSource _cancellationTokenSource;
|
|
private CancellationToken _cancellationToken;
|
|
|
|
// Maximal FM2014 which can be served
|
|
private const Int32 MaxFm2014s = 10;
|
|
|
|
/// <summary>
|
|
/// The FM2014s served by this program
|
|
/// </summary>
|
|
private readonly List<FM2014> Fm2014s = new List<FM2014>();
|
|
|
|
/// <summary>
|
|
/// Stack panels for UcFM2014Device
|
|
/// </summary>
|
|
private readonly List<StackPanel> PanelsForUcFM2014 = new List<StackPanel>();
|
|
|
|
/// <summary>
|
|
/// Slot selections for FM2014s
|
|
/// </summary>
|
|
private readonly List<CheckBox> SlotSelections = new List<CheckBox>();
|
|
|
|
/// <summary>
|
|
/// User controls of FM2014Device
|
|
/// </summary>
|
|
private readonly List<UserControl> UcFM2014s = new List<UserControl>();
|
|
|
|
/// <summary>
|
|
/// Variable measurement labels depending on ongoing measurement shared for all
|
|
/// FM2014 devices.
|
|
/// </summary>
|
|
private readonly List<Label> MeasurementLabels = new List<Label>();
|
|
|
|
/// <summary>
|
|
/// The start time is going to be used for time measurements of processes.
|
|
/// It has to be set to the actual time if the measurement should be (re)started.
|
|
/// </summary>
|
|
private DateTimeOffset _startTime;
|
|
|
|
/// <summary>
|
|
/// Program internal timer
|
|
/// </summary>
|
|
private readonly DispatcherTimer _tmrProgressUpdate = new DispatcherTimer();
|
|
|
|
/// <summary>
|
|
/// The auto progress bar enabled is for infinite processes or if the process doesn't feed
|
|
/// the progress bar with information (e.g. Connect()).
|
|
/// </summary>
|
|
private Boolean _autoProgressBar;
|
|
private Version _version;
|
|
|
|
|
|
// status information
|
|
private static readonly Brush ColorDefault = Brushes.Black;
|
|
private static readonly Brush ColorSuccess = Brushes.Green;
|
|
private static readonly Brush ColorProcessFailed = Brushes.Red;
|
|
//private static readonly Color ColorOngoingProcess = Color.Blue;
|
|
//private static readonly Color ColorUnknownStatus = Color.Gray;
|
|
private static Brush ColorStandardInputField = Brushes.White;
|
|
private static Brush ColorStandardDisplayField;
|
|
//private static Brush ColorStandardTextColor;
|
|
|
|
//private const String SuccessSign = @"✔";
|
|
//private const String FailedSign = @"✘";
|
|
|
|
private const String StrSeparator = "--------------------------------------------------" +
|
|
"--------------------------------------------------" +
|
|
"--------------------------------------------------";
|
|
|
|
//private readonly ILogger _logger = NLogHelper.CreateOrGetLogger("FM2014");
|
|
private readonly FM2014Config _fm2014Config = new FM2014Config();
|
|
|
|
// Common FM2014 to take care of all settings and limit chacks on input without real usage as
|
|
// connected device.
|
|
private readonly FM2014 _fm2014Common = new FM2014();
|
|
|
|
// internal reminders of changed items to avoid write access on startup if items are preloaded
|
|
private Int32 _comPortIdx;
|
|
|
|
// Reminder of settings in standalone measurement to avoid repeated storage af already stored values
|
|
private UInt32 _initialRefPulsesPerCm;
|
|
private UInt16 _initialDutPulsesPerCm;
|
|
private Byte _initialCurrentOutputAttenuation;
|
|
|
|
/// <summary>
|
|
/// The regulation setup will be stored directly to the FM2014 being able to use it at startup
|
|
/// </summary>
|
|
private Boolean _fm2014RegulationSetupHasChanged;
|
|
|
|
/// <summary>
|
|
/// If the program configuration for the calibration measurement has changed, this shall be stored to the
|
|
/// configuration file
|
|
/// </summary>
|
|
private Boolean _programConfigurationSetupHasChanged;
|
|
|
|
private String _lastLoggingTextToAvoidRepetition;
|
|
|
|
// DUT to REF regulation label index published to UcFM2014s measurement positions 2
|
|
private const Int32 REG_LBL_IDX_REF_FREQU = 0;
|
|
private const Int32 REG_LBL_IDX_FLOW_RATE = 1;
|
|
private const Int32 REG_LBL_IDX_DUT_TO_REF_TOL = 2;
|
|
|
|
private const Int32 REG_MEASURE_COUNT = 3;
|
|
|
|
// DUT to REF calibration label index published to UcFM2014s measurement positions 0..6
|
|
private const Int32 CAL_LBL_IDX_REF_REQ_PLS = 0;
|
|
private const Int32 CAL_LBL_IDX_REF_RMN_PLS = 1;
|
|
private const Int32 CAL_LBL_IDX_DUT_REQ_PLS = 2;
|
|
private const Int32 CAL_LBL_IDX_DUT_RMN_PLS = 3;
|
|
private const Int32 CAL_LBL_IDX_REF_MEAS_TMR = 4;
|
|
private const Int32 CAL_LBL_IDX_DUT_MEAS_TMR = 5;
|
|
private const Int32 CAL_LBL_IDX_DUT_TO_REF_TOL = 6;
|
|
|
|
private const Int32 CAL_MEASURE_COUNT = 7;
|
|
|
|
// Pulse counter measurements label index published to UcFM2014s measurement positions 2..3
|
|
private const Int32 CTR_LBL_IDX_REF_PLS = 0;
|
|
private const Int32 CTR_LBL_IDX_DUT_PLS = 1;
|
|
|
|
private const Int32 CTR_MEASURE_COUNT = 2;
|
|
|
|
// Hide button to avoid stress to EEPROM of FM2014
|
|
private readonly Boolean _displayBtnSaveSetupToFm2014 = false;
|
|
|
|
// Reminder for the last address of received message for separator mark between two devices
|
|
private Int32 _lastAddressReceived;
|
|
|
|
#endregion ---------------------------------------- Properties ------------------------------------------------
|
|
#region ------------------------------------------- FormControls ----------------------------------------------
|
|
/// <summary>
|
|
/// Ctor
|
|
/// </summary>
|
|
/// <remarks date="2026-Feb-02" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
public FM2014TestBenchWindow()
|
|
{
|
|
InitializeComponent();
|
|
//var cultureInfo = new CultureInfo("en");
|
|
//Thread.CurrentThread.CurrentUICulture = cultureInfo;
|
|
//Thread.CurrentThread.CurrentCulture = cultureInfo;
|
|
|
|
ColorStandardDisplayField = lblRefPulsesPerCm.Background;
|
|
ColorStandardInputField = chkSlot1.Background;
|
|
//ColorStandardTextColor = lblRefPulsesPerCm.Foreground;
|
|
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
|
_cancellationToken = _cancellationTokenSource.Token;
|
|
|
|
// Set timer to 500 ms interval
|
|
_tmrProgressUpdate.Interval = new TimeSpan(0, 0, 0, 0, 500);
|
|
_tmrProgressUpdate.Tick += tmrProgressUpdate_Tick;
|
|
}
|
|
|
|
private void Window_Loaded(Object sender, RoutedEventArgs e)
|
|
{
|
|
//if (!Software.IsAutenticated)
|
|
// OnLogin_Click(this, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize the main window after it is completely loaded and assign all objects:
|
|
/// - 10 x Reserve panels for 10 x FM2014.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
/// <remarks date="2026-Feb-03..04" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void Window_Initialized(Object sender, EventArgs e)
|
|
{
|
|
_version = Assembly.GetExecutingAssembly().GetName().Version;
|
|
|
|
// ---- SORTED! ---- list of the stack panels
|
|
PanelsForUcFM2014.Add(spDevice1);
|
|
PanelsForUcFM2014.Add(spDevice2);
|
|
PanelsForUcFM2014.Add(spDevice3);
|
|
PanelsForUcFM2014.Add(spDevice4);
|
|
PanelsForUcFM2014.Add(spDevice5);
|
|
PanelsForUcFM2014.Add(spDevice6);
|
|
PanelsForUcFM2014.Add(spDevice7);
|
|
PanelsForUcFM2014.Add(spDevice8);
|
|
PanelsForUcFM2014.Add(spDevice9);
|
|
PanelsForUcFM2014.Add(spDevice10);
|
|
|
|
// ---- SORTED! ---- list of check boxes for slot selection
|
|
SlotSelections.Add(chkSlot1);
|
|
SlotSelections.Add(chkSlot2);
|
|
SlotSelections.Add(chkSlot3);
|
|
SlotSelections.Add(chkSlot4);
|
|
SlotSelections.Add(chkSlot5);
|
|
SlotSelections.Add(chkSlot6);
|
|
SlotSelections.Add(chkSlot7);
|
|
SlotSelections.Add(chkSlot8);
|
|
SlotSelections.Add(chkSlot9);
|
|
SlotSelections.Add(chkSlot10);
|
|
|
|
// ---- SORTED! ---- list of measurement labels for all devices
|
|
MeasurementLabels.Add(lblMeasurement1Text);
|
|
MeasurementLabels.Add(lblMeasurement2Text);
|
|
MeasurementLabels.Add(lblMeasurement3Text);
|
|
MeasurementLabels.Add(lblMeasurement4Text);
|
|
MeasurementLabels.Add(lblMeasurement5Text);
|
|
MeasurementLabels.Add(lblMeasurement6Text);
|
|
MeasurementLabels.Add(lblMeasurement7Text);
|
|
|
|
// Preset attenuation
|
|
var attenuation = FM2014.CURRENT_OUTPUT_ATTENUATION_MIN;
|
|
for (var idx = 0; idx <= FM2014.CURRENT_OUTPUT_ATTENUATION_MAX - FM2014.CURRENT_OUTPUT_ATTENUATION_MIN; idx++)
|
|
{
|
|
cbxToleranceDisplayAttenuation.Items.Add(attenuation.ToString());
|
|
attenuation++;
|
|
}
|
|
|
|
// Preset tolerance setup for feedback calculation of DUT to REF tolerance
|
|
cbxToleranceDisplayRange.Items.Add(FM2014.CURRENT_OUTPUT_DISPLAY_RANGE_STANDARD_NOMINAL_percent.ToString());
|
|
cbxToleranceDisplayRange.Items.Add(FM2014.CURRENT_OUTPUT_DISPLAY_RANGE_EXTENDED_NOMINAL_percent.ToString());
|
|
|
|
Init();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dispatch new user control to main screen or remove it with uc == null
|
|
/// </summary>
|
|
/// <param name="sp"></param>
|
|
/// <param name="uc">new UserControl or null to remove it</param>
|
|
private void SetNewUserControl(StackPanel sp, UserControl uc)
|
|
{
|
|
sp.Dispatcher.Invoke(DispatcherPriority.Normal,
|
|
new Action(() =>
|
|
{
|
|
// remove last userControl
|
|
sp.Children.Clear();
|
|
if (uc != null)
|
|
{
|
|
sp.Children.Add(uc);
|
|
DockPanel.SetDock(uc, Dock.Top); // & Dock.Left);
|
|
}
|
|
}
|
|
));
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Exit with disposure of objects.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void Window_Closing(Object sender, System.ComponentModel.CancelEventArgs e)
|
|
{
|
|
_cancellationTokenSource.Cancel();
|
|
Thread.Sleep(500);
|
|
foreach (var fm2014 in Fm2014s)
|
|
{
|
|
fm2014.OnRawRecordReceived -= DataReceived_Handler;
|
|
}
|
|
// Dispose all registered FM2014
|
|
FM2014.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update UI elements text with content and color
|
|
/// </summary>
|
|
/// <param name="c"></param>
|
|
/// <param name="content"></param>
|
|
/// <param name="color"></param>
|
|
private void UpdateTextBox(TextBox c, String content, Brush color = null)
|
|
{
|
|
color = color ?? Brushes.Black;
|
|
|
|
c.Dispatcher.Invoke(DispatcherPriority.Normal,
|
|
new Action(() =>
|
|
{
|
|
c.Text = content;
|
|
c.Foreground = color;
|
|
}));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update progress bar
|
|
/// </summary>
|
|
/// <param name="pb"></param>
|
|
/// <param name="value"></param>
|
|
/// <param name="color"></param>
|
|
private void UpdateProgressBar(ProgressBar pb, Double value, Brush color = null)
|
|
{
|
|
color = color ?? Brushes.Green;
|
|
|
|
pb.Dispatcher.Invoke(DispatcherPriority.Normal,
|
|
new Action(() =>
|
|
{
|
|
if (value < 0.0)
|
|
value = 0.0;
|
|
if (value > 100.0)
|
|
value = 100.0;
|
|
|
|
pb.Value = value;
|
|
pb.Foreground = color;
|
|
}));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enable UI elements
|
|
/// </summary>
|
|
/// <param name="elm"></param>
|
|
/// <param name="isEnabled"></param>
|
|
/// <param name="isVisible"></param>
|
|
private void UiElmEnable(UIElement elm, Boolean isEnabled, Boolean isVisible = true)
|
|
{
|
|
elm.Dispatcher.Invoke(DispatcherPriority.Normal,
|
|
new Action(() =>
|
|
{
|
|
elm.Visibility = isVisible ? Visibility.Visible : Visibility.Hidden;
|
|
elm.IsEnabled = isEnabled;
|
|
}));
|
|
}
|
|
|
|
///// <summary>
|
|
///// Dispatch GUI messages
|
|
///// </summary>
|
|
///// <param name="sender"></param>
|
|
///// <param name="e"></param>
|
|
///// <remarks date="2025-Sep-15" author="Thomas Wiedebusch">
|
|
///// - Initial
|
|
///// </remarks>
|
|
//private void GuiMessageDispatcher_Handler(Object sender, GuiMessageArgs e)
|
|
//{
|
|
// if (e?.Obj is Int32 obj)
|
|
// {
|
|
// switch (e.GuiItem)
|
|
// {
|
|
// case GuiItem.OrderAmount:
|
|
// OrderAmount = e.Obj != null ? obj : 0;
|
|
// break;
|
|
// case GuiItem.OrderCounter:
|
|
// OrderCounter = e.Obj != null ? obj : 0;
|
|
// break;
|
|
// }
|
|
// }
|
|
//}
|
|
|
|
/// <summary>
|
|
/// Output message to control without access violation.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
/// <remarks date="????" author="Roland Drabesch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <remarks date="2023-07-11" author="Thomas Wiedebusch">
|
|
/// - Introduced multi line text with center alignment.
|
|
/// </remarks>
|
|
private static void UpdateContentControl(ContentControl ctl, String text)
|
|
{
|
|
ctl.Dispatcher.Invoke(DispatcherPriority.Normal,
|
|
new Action(() =>
|
|
{
|
|
ctl.Content = new TextBlock()
|
|
{
|
|
Text = text,
|
|
TextWrapping = TextWrapping.Wrap,
|
|
TextAlignment = TextAlignment.Center
|
|
};
|
|
}));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update the elapsed time as information
|
|
/// </summary>
|
|
/// <returns>information attached to process depending on region</returns>
|
|
/// <remarks date="2026-Feb-03" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
private void SetTimeDisplay()
|
|
{
|
|
var time = DateTimeOffset.UtcNow;
|
|
var timeSpan = time - _startTime;
|
|
UpdateContentControl(lblTimeValue, $"{(UInt32)timeSpan.TotalHours}:" +
|
|
$"{(UInt32)timeSpan.TotalMinutes:00}:" +
|
|
$"{timeSpan.Seconds:00}");
|
|
}
|
|
|
|
#endregion FormControls
|
|
#region ------------------------------------------- TimerControls ---------------------------------------------
|
|
private void tmrProgressUpdate_Tick(Object sender, EventArgs e)
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
if (_autoProgressBar && pbActualProgress.IsVisible)
|
|
{
|
|
pbActualProgress.Value = pbActualProgress.Value + 4 > 100 ? 0 : pbActualProgress.Value + 4;
|
|
}
|
|
});
|
|
|
|
SetTimeDisplay();
|
|
}
|
|
|
|
#endregion ---------------------------------------- TimerControls ---------------------------------------------
|
|
#region ------------------------------------------- BoardControls ---------------------------------------------
|
|
/// <summary>
|
|
/// Clear object lists, initialize and assign all objects:
|
|
/// - 10 x FM2014 objects including address assignment based on the panel number,
|
|
/// - 10 x FM2014 user controls,
|
|
/// - Assign a common receive handler,
|
|
/// - Load configuration:
|
|
/// - ComPort,
|
|
/// - Slot selections,
|
|
/// - Individual hardware tolerance selection.
|
|
/// </summary>
|
|
/// <remarks date="2025-Nov-13..2026-Feb-18" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void Init()
|
|
{
|
|
ActionControl(false);
|
|
ClearInfoWindow();
|
|
if (_version != null)
|
|
{
|
|
// Logging to history window
|
|
var timeUtc = DateTime.UtcNow;
|
|
var timeUtcStr = $"{timeUtc:yyyy-MM-dd HH:mm:ss:fff} UTC";
|
|
var version = $"Version: {_version}";
|
|
Title = $@"FM2014 Test Bench - {version}";
|
|
LogText($"{timeUtcStr} - FM2014 Test Bench: {version}");
|
|
LogText(StrSeparator);
|
|
}
|
|
|
|
_fm2014RegulationSetupHasChanged = false;
|
|
_programConfigurationSetupHasChanged = false;
|
|
|
|
// Load the configuration from the local file stored in AppData\FM2014
|
|
_fm2014Config.ReadFM2014Config();
|
|
|
|
// Clear and dispose all FM2014 objects
|
|
if (Fm2014s.Count != 0)
|
|
{
|
|
for (var ctr = 0; ctr < Fm2014s.Count; ctr++)
|
|
{
|
|
// Remove objects from user control panel
|
|
SetNewUserControl(PanelsForUcFM2014[ctr], null);
|
|
|
|
// Kill FM2014
|
|
Fm2014s[ctr].OnRawRecordReceived -= DataReceived_Handler;
|
|
}
|
|
|
|
Fm2014s.Clear();
|
|
UcFM2014s.Clear();
|
|
FM2014.Dispose();
|
|
}
|
|
|
|
// Assign and build a list of FM2014s and dispatch these to the according user control
|
|
// Disable manual input
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
for (var ctr = 0; ctr < MaxFm2014s; ctr++)
|
|
{
|
|
var fm2014 = new FM2014
|
|
{
|
|
Address = ctr + 1
|
|
};
|
|
|
|
fm2014.OnRawRecordReceived += DataReceived_Handler;
|
|
Fm2014s.Add(fm2014);
|
|
var userControl = new UcFM2014Device(fm2014, StoreProgramConfiguration);
|
|
UcFM2014s.Add(userControl);
|
|
SetNewUserControl(PanelsForUcFM2014[ctr], UcFM2014s[ctr]);
|
|
// Restore previous slot selection
|
|
if (_fm2014Config?.SlotIsSelected?[ctr] != null &&
|
|
_fm2014Config.SlotIsSelected.Count == MaxFm2014s &&
|
|
SlotSelections.Count == MaxFm2014s)
|
|
SlotSelections[ctr].IsChecked = _fm2014Config?.SlotIsSelected[ctr] ?? false;
|
|
// Restore individual tolerance selection
|
|
if (_fm2014Config?.IndividualCurrentOutputDisplayRangeNominal_percent?[ctr] != null &&
|
|
_fm2014Config.IndividualCurrentOutputDisplayRangeNominal_percent.Count == MaxFm2014s)
|
|
{
|
|
fm2014.CurrentOutputDisplayRangeNominal_percent =
|
|
_fm2014Config?.IndividualCurrentOutputDisplayRangeNominal_percent[ctr] ??
|
|
FM2014.CURRENT_OUTPUT_DISPLAY_RANGE_STANDARD_NOMINAL_percent;
|
|
}
|
|
}
|
|
if (_fm2014Config != null)
|
|
{
|
|
// Restore baudrate
|
|
FM2014.Baudrate = _fm2014Config.Baudrate;
|
|
|
|
// Restore calibration settings for user convenience and use the FM2014 properties to limit the settings.
|
|
_fm2014Common.RefPulsesRequired = _fm2014Config.RefPulsesRequired;
|
|
_fm2014Common.DutPulsesRequired = _fm2014Config.DutPulsesRequired;
|
|
_fm2014Common.DoublePulseDeadtime_ms = _fm2014Config.DoublePulseDeadtime_ms;
|
|
_fm2014Common.DutToRefCalibrationToleranceMax_percent = _fm2014Config.DutToRefCalibrationToleranceMax_percent;
|
|
_fm2014Common.DutToRefCalibrationToleranceMin_percent = _fm2014Config.DutToRefCalibrationToleranceMin_percent;
|
|
|
|
tbxRefPulsesRequired.Text = $"{_fm2014Common.RefPulsesRequired:D}";
|
|
tbxDutPulsesRequired.Text = $"{_fm2014Common.DutPulsesRequired:D}";
|
|
tbxDoublePulseDeadtime.Text = $"{_fm2014Common.DoublePulseDeadtime_ms:D}";
|
|
tbxDutToRefTolMax.Text = $"{_fm2014Common.DutToRefCalibrationToleranceMax_percent:F2}";
|
|
tbxDutToRefTolMin.Text = $"{_fm2014Common.DutToRefCalibrationToleranceMin_percent:F2}";
|
|
|
|
// Restore pulse to cubic-meter ratios as those will not be taken from the FM2014 standalone measurement but
|
|
// the configuration file to avoid stress to the FM2014 EEPROM if a change should remain permanently.
|
|
// Using one FM2014 with its properties to build the RefToDutScale and to limit the inputs.
|
|
_fm2014Common.RefPulses_per_cm = _fm2014Config.RefPulsesPerCm;
|
|
_fm2014Common.DutPulses_per_cm = _fm2014Config.DutPulsesPerCm;
|
|
_fm2014Common.CurrentOutputAttenuation = _fm2014Config.CurrentOutputAttenuation;
|
|
// Publish the limited and recalculated inputs to the GUI
|
|
tbxRefPulsesPerCm.Text = $"{_fm2014Common.RefPulses_per_cm:D}";
|
|
tbxDutPulsesPerCm.Text = $"{_fm2014Common.DutPulses_per_cm:D}";
|
|
tbxScaleRefToDut.Text = $"{_fm2014Common.RefToDutScale_norm:F4}";
|
|
cbxToleranceDisplayAttenuation.SelectedItem = $"{_fm2014Common.CurrentOutputAttenuation}";
|
|
|
|
// Display on regulation page as common setup for ALL
|
|
cbxToleranceDisplayRange.SelectedItem = _fm2014Config.CommonCurrentOutputDisplayRangeNominal_percent.ToString();
|
|
}
|
|
|
|
// Disable and hide dynamic measurement labels
|
|
foreach (var measLbl in MeasurementLabels)
|
|
UiElmEnable(measLbl, false, false);
|
|
|
|
// Display actual com-port, if not assigned use the '?'. This will be placed on Items[0]
|
|
if (_fm2014Config?.SharedSerialPort != null && !cbxProgramComPort.Items.Contains(_fm2014Config.SharedSerialPort))
|
|
{
|
|
cbxProgramComPort.Items.Add(_fm2014Config.SharedSerialPort);
|
|
}
|
|
else if (_fm2014Config?.SharedSerialPort == null && !cbxProgramComPort.Items.Contains("?"))
|
|
{
|
|
cbxProgramComPort.Items.Add("?");
|
|
}
|
|
// Collect and add all com-ports from device manager to items
|
|
var comPorts = SerialPort.GetPortNames().ToList();
|
|
foreach (var comPort in comPorts.Where(comPort => !cbxProgramComPort.Items.Contains(comPort)))
|
|
{
|
|
cbxProgramComPort.Items.Add(comPort);
|
|
}
|
|
|
|
cbxProgramComPort.Text = cbxProgramComPort.Items[_comPortIdx]?.ToString();
|
|
|
|
// Dispatch multilingual denotation to GUI elements
|
|
btnFM2014Connect.Content = StrBtnConnect;
|
|
chkDecodeEntireStatus.Content = StrChkDecodeEntireStatus;
|
|
|
|
// Measurement selection tab setup
|
|
tabDutToRefRegulation.Header = StrGbxDutToRefRegulation;
|
|
tabDutToRefCalibration.Header = StrGbxDutToRefCalibration;
|
|
tabRefToScaleCalibration.Header = StrGbxManualRefCalibration;
|
|
|
|
// Menu setup
|
|
//hlpMenu.Header = Properties.Resources.StrLblHelp;
|
|
//optMenu.Header = Properties.Resources.StrLblOptions;
|
|
//optMenuLogin.Header = Properties.Resources.StrLblOptionsLogin;
|
|
//optMenuLogout.Header = Properties.Resources.StrLblOptionsLogout;
|
|
//optMenuChangePassword.Header = Properties.Resources.StrLblOptionsChangePassword;
|
|
portMenu.Content = StrLblComPort;
|
|
|
|
// Static labels for all devices
|
|
lblSerialNumberText.Content = StrLblFM2014SerialNumber;
|
|
lblLifeTimeText.Content = StrLblLifetime;
|
|
lblSlotSelection.Content = StrLblSlotSelection;
|
|
|
|
// DUT to REF Regulation
|
|
lblRefPulsesPerCm.Content = StrLblRefPulsesPerCmRatio;
|
|
lblDutPulsesPerCm.Content = StrLblDutPulsesPerCmRatio;
|
|
lblScaleRefToDut.Content = StrLblRefToDutScale;
|
|
tbxScaleRefToDut.IsReadOnly = true;
|
|
lblToleranceDisplayAttenuation.Content = StrLblDisplayAttenuation;
|
|
btnSaveSetupToFm2014.Content = StrBtnSaveRegulationSetup;
|
|
UiElmEnable(btnSaveSetupToFm2014, false, _displayBtnSaveSetupToFm2014);
|
|
|
|
lblToleranceDisplayRange.Content = StrLblFM2014Tolerance;
|
|
chkUseDampedTolerance.Content = StrChkUseDampedTolerance;
|
|
btnDutToRefRegulation.Content = StrBtnStartRegulation;
|
|
|
|
// Time measurement / DUT to REF Calibration
|
|
rbtRefTimeMeasurement.Content = StrSelectRefTimeMeasurement;
|
|
rbtDutTimeMeasurement.Content = StrSelectDutTimeMeasurement;
|
|
rbtDutToRefCalibration.Content = StrSelectDutToRefCalibration;
|
|
rbtDutToRefCalibration.IsChecked = true;
|
|
lblRefPulsesRequired.Content = StrLblRefPulsesRequired;
|
|
lblDutPulsesRequired.Content = StrLblDutPulsesRequired;
|
|
lblDoublePulseDeadtime.Content = StrLblDoublePulseDeadTime;
|
|
lblDutToRefTolMax.Content = StrLblDutToRefToleranceMax;
|
|
lblDutToRefTolMin.Content = StrLblDutToRefToleranceMin;
|
|
|
|
// Pulse counter measurement / Manual REF to Volume Calibration
|
|
chkRefPulsesMeasured.Content = StrLblRefPulsesMeasured;
|
|
chkRefPulsesMeasured.IsChecked = true;
|
|
tbxRefPulsesMeasured.Text = "";
|
|
tbxRefPulsesMeasured.IsReadOnly = true;
|
|
chkDutPulsesMeasured.Content = StrLblDutPulsesMeasured;
|
|
chkDutPulsesMeasured.IsChecked = false;
|
|
tbxDutPulsesMeasured.Text = "";
|
|
tbxDutPulsesMeasured.IsReadOnly = true;
|
|
lblVolumeMeasuredLiters.Content = StrLblVolumeMeasured;
|
|
tbxVolumeMeasuredLiters.Text = "";
|
|
lblRefPulsesPerCmScale.Content = StrLblRefPulsesPerCmRatio;
|
|
tbxRefPulsesPerCmCalib.Text = "";
|
|
tbxRefPulsesPerCmCalib.IsReadOnly = true;
|
|
btnRefToVolumeCalibration.Content = StrBtnStartCalibration;
|
|
|
|
//// Process status
|
|
//lblActualProcessLabel.Content = Properties.Resources.StrLblActualProcessStatus;
|
|
//lblTotalProcessLabel.Content = Properties.Resources.StrLblOverallProcessStatus;
|
|
lblTimeText.Text = StrLblTime;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Store the settings adjusted by the UI or the UcFM2014Device to a configuration file!
|
|
/// </summary>
|
|
/// <remarks date="2025-Nov-13..2026-Feb-16" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void StoreProgramConfiguration()
|
|
{
|
|
if (_fm2014Config == null)
|
|
return;
|
|
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
_programConfigurationSetupHasChanged = false;
|
|
_fm2014Config.IndividualCurrentOutputDisplayRangeNominal_percent?.Clear();
|
|
_fm2014Config.IndividualCurrentOutputDisplayRangeNominal_percent = null;
|
|
_fm2014Config.IndividualCurrentOutputDisplayRangeNominal_percent = new List<Int32?>();
|
|
|
|
_fm2014Config.SlotIsSelected?.Clear();
|
|
_fm2014Config.SlotIsSelected = null;
|
|
_fm2014Config.SlotIsSelected = new List<Boolean?>();
|
|
|
|
_fm2014Config.SharedSerialPort = cbxProgramComPort.Text;
|
|
|
|
for (var ctr = 0; ctr < MaxFm2014s; ctr++)
|
|
{
|
|
var fm2014 = Fm2014s[ctr];
|
|
if (fm2014 == null)
|
|
return;
|
|
_fm2014Config.IndividualCurrentOutputDisplayRangeNominal_percent.Add(
|
|
fm2014.CurrentOutputDisplayRangeNominal_percent);
|
|
_fm2014Config.SlotIsSelected.Add(SlotSelections[ctr].IsChecked);
|
|
}
|
|
|
|
_fm2014Config.CommonCurrentOutputDisplayRangeNominal_percent =
|
|
_fm2014Common.CurrentOutputDisplayRangeNominal_percent;
|
|
_fm2014Config.RefPulsesRequired = _fm2014Common.RefPulsesRequired;
|
|
_fm2014Config.DutPulsesRequired = (UInt16)_fm2014Common.DutPulsesRequired;
|
|
_fm2014Config.DoublePulseDeadtime_ms = _fm2014Common.DoublePulseDeadtime_ms;
|
|
_fm2014Config.DutToRefCalibrationToleranceMax_percent = _fm2014Common.DutToRefCalibrationToleranceMax_percent;
|
|
_fm2014Config.DutToRefCalibrationToleranceMin_percent = _fm2014Common.DutToRefCalibrationToleranceMin_percent;
|
|
_fm2014Config.RefPulsesPerCm = _fm2014Common.RefPulses_per_cm;
|
|
_fm2014Config.DutPulsesPerCm = _fm2014Common.DutPulses_per_cm;
|
|
_fm2014Config.CurrentOutputAttenuation = _fm2014Common.CurrentOutputAttenuation;
|
|
});
|
|
|
|
_fm2014Config.Update();
|
|
}
|
|
/// <summary>
|
|
/// Establish connection to FM2014
|
|
/// </summary>
|
|
/// <remarks date="2025-Oct-02" author="Thomas Wiedebusch">
|
|
/// - Catch error message on unknown data type and kill meter.
|
|
/// </remarks>
|
|
private void Connect()
|
|
{
|
|
try
|
|
{
|
|
if (_programConfigurationSetupHasChanged)
|
|
StoreProgramConfiguration();
|
|
|
|
Init();
|
|
if (string.IsNullOrEmpty(cbxProgramComPort?.SelectedItem?.ToString()) ||
|
|
cbxProgramComPort.SelectedItem.ToString().Equals("?"))
|
|
{
|
|
var text = StrErrorMsgComPortNotAssigned;
|
|
//MessageBox.Show(text, Properties.Resources.StrError, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
LogText(text);
|
|
return;
|
|
}
|
|
|
|
_autoProgressBar = true;
|
|
ActionControl(true);
|
|
|
|
ResetCancellationToken();
|
|
FM2014.SharedCancellationToken = _cancellationToken;
|
|
|
|
UpdateContentControl(lblActualProcessText, StrMsgConnecting);
|
|
|
|
var comPort = cbxProgramComPort.SelectedItem.ToString();
|
|
|
|
Task.Factory.StartNew(() =>
|
|
{
|
|
for (var ctr = 0; ctr < MaxFm2014s; ctr++)
|
|
{
|
|
var ucFm2014Device = (UcFM2014Device)UcFM2014s[ctr];
|
|
var fm2014 = Fm2014s[ctr];
|
|
|
|
// Connect only to selected slots
|
|
var slotShallBeUsed = false;
|
|
|
|
var ctr1 = ctr;
|
|
SlotSelections[ctr].Dispatcher.Invoke(() =>
|
|
{
|
|
var isChecked = SlotSelections[ctr1].IsChecked;
|
|
if (isChecked != null)
|
|
slotShallBeUsed = (Boolean)isChecked;
|
|
});
|
|
|
|
if (!slotShallBeUsed || !fm2014.Connect(comPort))
|
|
{
|
|
ucFm2014Device.SetConnectionStatus(ConnectStatusName.offline);
|
|
continue;
|
|
}
|
|
ucFm2014Device.SetConnectionStatus(ConnectStatusName.online);
|
|
|
|
if (!fm2014.IsLoggedOn)
|
|
{
|
|
LogErrorText(StrErrorMsgFM2014AccessDenied);
|
|
continue;
|
|
}
|
|
|
|
if (fm2014.IsLoggedOn)
|
|
{
|
|
// Logging to history window
|
|
var timeUtc = DateTime.UtcNow;
|
|
var timeUtcStr = $"{timeUtc:yyyy-MM-dd HH:mm:ss:fff} UTC";
|
|
|
|
LogText(StrSeparator);
|
|
LogText($"{timeUtcStr} - Ad: {fm2014.Address} - FM2014 ID: {fm2014.ConnectResponse}");
|
|
LogText($"{timeUtcStr} - Ad: {fm2014.Address} - FM2014 Firmware Version: {fm2014.FwVersion}");
|
|
LogText($"{timeUtcStr} - Ad: {fm2014.Address} - FM2014 {StrLblFM2014SerialNumber} {fm2014.SerialNumber}");
|
|
LogText(StrSeparator);
|
|
}
|
|
}
|
|
}, _cancellationToken).ContinueWith(delegate
|
|
{
|
|
ActionControl(false);
|
|
}, _cancellationToken);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ActionControl(false);
|
|
LogErrorText(ex.Message);
|
|
}
|
|
}
|
|
|
|
#endregion ---------------------------------------- BoardControls ---------------------------------------------
|
|
#region ------------------------------------------- ProcessControls -------------------------------------------
|
|
/// <summary>
|
|
/// Common method to (de-)activate controls and timer.
|
|
/// </summary>
|
|
/// <remarks date="2026-Jan-14..Feb-14" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void ActionControl(Boolean isActive)
|
|
{
|
|
if (isActive)
|
|
{
|
|
UiElmEnable(lblStatusProcessInfo, true);
|
|
UiElmEnable(lblActualProcessText, true);
|
|
//UiElmEnable(lblTotalProcessText, true);
|
|
//UiElmEnable(lblActualProcessLabel, true);
|
|
//UiElmEnable(lblTotalProcessLabel, true);
|
|
UiElmEnable(lblActualProgressPercent, !_autoProgressBar, !_autoProgressBar);
|
|
//UiElmEnable(lblTotalProgressPercent, true);
|
|
UiElmEnable(pbActualProgress, true);
|
|
//UiElmEnable(pbTotalProgress, true);
|
|
SetFM2014AccessLocked();
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
pbActualProgress.Value = 0.0;
|
|
_tmrProgressUpdate.IsEnabled = true;
|
|
});
|
|
}
|
|
else
|
|
{
|
|
_autoProgressBar = false;
|
|
Dispatcher.Invoke(() => _tmrProgressUpdate.IsEnabled = false);
|
|
UiElmEnable(lblStatusProcessInfo, false, false);
|
|
UiElmEnable(lblActualProcessText, false, false);
|
|
//UiElmEnable(lblTotalProcessText, false, false);
|
|
//UiElmEnable(lblActualProcessLabel, false, false);
|
|
//UiElmEnable(lblTotalProcessLabel, false, false);
|
|
UiElmEnable(lblActualProgressPercent, false, false);
|
|
//UiElmEnable(lblTotalProgressPercent, false, false);
|
|
UiElmEnable(pbActualProgress, false, false);
|
|
//UiElmEnable(pbTotalProgress, false, false);
|
|
|
|
// Check if any FM2014 is connected and logged in to enable access buttons
|
|
if (FM2014.RegisteredFm2014s == null || FM2014.RegisteredFm2014s.Count == 0 ||
|
|
FM2014.RegisteredFm2014s.All(fm2014 => !fm2014.IsLoggedOn))
|
|
{
|
|
SetFM2014AccessLocked();
|
|
UiElmEnable(btnFM2014Connect, true);
|
|
UiElmEnable(cbxProgramComPort, true);
|
|
foreach (var slotSelector in SlotSelections)
|
|
{
|
|
UiElmEnable(slotSelector, true);
|
|
}
|
|
return;
|
|
}
|
|
SetFM2014AccessEnabled();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Common method to prepare for changed regulation setup.
|
|
/// </summary>
|
|
/// <remarks date="2026-Jan-14..20" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void DutToRefRegulationSetupHasChanged()
|
|
{
|
|
_fm2014RegulationSetupHasChanged = true;
|
|
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
btnSaveSetupToFm2014.IsEnabled = true;
|
|
btnDutToRefRegulation.IsEnabled = true;
|
|
|
|
tbxRefPulsesPerCm.Background = ColorStandardInputField;
|
|
tbxDutPulsesPerCm.Background = ColorStandardInputField;
|
|
tbxScaleRefToDut.Background = ColorStandardDisplayField;
|
|
|
|
tbxVolumeMeasuredLiters.Background = ColorStandardInputField;
|
|
tbxRefPulsesPerCmCalib.Background = ColorStandardDisplayField;
|
|
});
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method checks if any setting applied to the FM2014 memory has been changed.
|
|
/// Initially, those values will be read and preset during the connection procedure.
|
|
/// </summary>
|
|
/// <remarks date="2026-Feb-17..18" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void CheckForFM2014SetupChanged()
|
|
{
|
|
// Avoid stress to the FM2014 EEPROM as the identical values mustn't be stored again
|
|
if (_initialCurrentOutputAttenuation == _fm2014Common.CurrentOutputAttenuation &&
|
|
_initialRefPulsesPerCm == _fm2014Common.RefPulses_per_cm &&
|
|
_initialDutPulsesPerCm == _fm2014Common.DutPulses_per_cm)
|
|
{
|
|
_fm2014RegulationSetupHasChanged = false;
|
|
UiElmEnable(btnSaveSetupToFm2014, false, _displayBtnSaveSetupToFm2014);
|
|
return;
|
|
}
|
|
|
|
// Mark that the setup has to be stored to the configuration file
|
|
_programConfigurationSetupHasChanged = true;
|
|
DutToRefRegulationSetupHasChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Common routine for REF to DUT scale check and update
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
/// <remarks date="2026-Jan-14..Feb-18" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private Boolean UpdateRefToDutScale()
|
|
{
|
|
UpdateTextBox(tbxScaleRefToDut, $@"{_fm2014Common.RefToDutScale_norm:F4}");
|
|
|
|
// Display error if scale doesn't fit
|
|
var tempRefToDutScale_norm = (Double)_fm2014Common.RefPulses_per_cm / _fm2014Common.DutPulses_per_cm;
|
|
if (tempRefToDutScale_norm < FM2014.REF_TO_DUT_SCALE_MIN ||
|
|
tempRefToDutScale_norm > FM2014.REF_TO_DUT_SCALE_MAX ||
|
|
_fm2014Common.RefToDutScale_norm < FM2014.REF_TO_DUT_SCALE_MIN ||
|
|
_fm2014Common.RefToDutScale_norm > FM2014.REF_TO_DUT_SCALE_MAX)
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
tbxScaleRefToDut.Background = ColorProcessFailed;
|
|
tbxScaleRefToDut.Text = StrError;
|
|
btnSaveSetupToFm2014.IsEnabled = false;
|
|
btnDutToRefRegulation.IsEnabled = false;
|
|
});
|
|
return false;
|
|
}
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
tbxScaleRefToDut.Background = ColorStandardDisplayField;
|
|
});
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Common routine to reset the cancellation token on restart or new meter.
|
|
/// </summary>
|
|
/// <remarks date="2026-Jan-26" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
private void ResetCancellationToken()
|
|
{
|
|
// This state needs an external state change like user input to go ahead
|
|
// Remove cancellation token if idle reached for clean start
|
|
if (_cancellationToken.IsCancellationRequested)
|
|
{
|
|
// Reset the cancellation request
|
|
_cancellationTokenSource.Dispose();
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
|
_cancellationToken = _cancellationTokenSource.Token;
|
|
}
|
|
}
|
|
#endregion ---------------------------------------- ProcessControls -------------------------------------------
|
|
#region ------------------------------------------- InfoWindow ------------------------------------------------
|
|
/// <summary>
|
|
/// Clear info window.
|
|
/// </summary>
|
|
/// <remarks date="2026-Feb-02" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
private void ClearInfoWindow()
|
|
{
|
|
rtbLog.Dispatcher.Invoke(() => rtbLog.Document.Blocks.Clear());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Output exclusively to user update remarks text window.
|
|
/// </summary>
|
|
/// <remarks date="2021-Apr-26" author="Thomas Wiedebusch">
|
|
/// - Color added.
|
|
/// </remarks>
|
|
/// <remarks date="2023-Aug-15" author="Thomas Wiedebusch">
|
|
/// - File output added.
|
|
/// </remarks>
|
|
private void LogText(String txtHistory, String filename = "")
|
|
{
|
|
InfoWindowColoredText(txtHistory, ColorDefault);
|
|
if (!string.IsNullOrEmpty(filename))
|
|
{
|
|
File.AppendAllLines(filename, new[] { txtHistory });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Output exclusively to user update remarks text window.
|
|
/// </summary>
|
|
private void LogErrorText(String txtHistory)
|
|
{
|
|
InfoWindowColoredText(txtHistory, ColorProcessFailed);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Output exclusively to user update remarks text window.
|
|
/// </summary>
|
|
private void LogSuccessText(String message)
|
|
{
|
|
InfoWindowColoredText(message, ColorSuccess);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Output exclusively to user update remarks text window.
|
|
/// </summary>
|
|
private void InfoWindowColoredText(String message, Brush color)
|
|
{
|
|
rtbLog.Dispatcher.Invoke(DispatcherPriority.Normal,
|
|
new Action(() =>
|
|
{
|
|
try
|
|
{
|
|
// remove unacceptable spacing between the lines
|
|
rtbLog.Document.LineHeight = 1;
|
|
|
|
color = color ?? Brushes.Black;
|
|
|
|
if (!string.IsNullOrEmpty(message) && !message.Equals(_lastLoggingTextToAvoidRepetition))
|
|
{
|
|
var tr = new TextRange(rtbLog.Document.ContentEnd, rtbLog.Document.ContentEnd)
|
|
{
|
|
Text = message
|
|
};
|
|
tr.ApplyPropertyValue(TextElement.ForegroundProperty, color);
|
|
rtbLog.ScrollToEnd();
|
|
rtbLog.AppendText(Environment.NewLine);
|
|
}
|
|
|
|
_lastLoggingTextToAvoidRepetition = message;
|
|
}
|
|
catch (FormatException)
|
|
{
|
|
// forget it
|
|
}
|
|
}
|
|
));
|
|
}
|
|
#endregion ---------------------------------------- InfoWindow ------------------------------------------------
|
|
#region ------------------------------------------- ActivationControls ----------------------------------------
|
|
/// <summary>
|
|
/// Lock all buttons
|
|
/// </summary>
|
|
private void SetFM2014AccessLocked()
|
|
{
|
|
// Disable all buttons
|
|
UiElmEnable(btnDutToRefCalibration, false);
|
|
UiElmEnable(btnSaveSetupToFm2014, false, _displayBtnSaveSetupToFm2014);
|
|
UiElmEnable(btnDutToRefRegulation, false);
|
|
UiElmEnable(btnFM2014Connect, false);
|
|
UiElmEnable(btnRefToVolumeCalibration, false);
|
|
|
|
// Disable manual input
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
tbxVolumeMeasuredLiters.IsReadOnly = true;
|
|
tbxRefPulsesPerCm.IsReadOnly = true;
|
|
tbxDutPulsesPerCm.IsReadOnly = true;
|
|
});
|
|
|
|
// Program settings
|
|
UiElmEnable(cbxToleranceDisplayRange, false);
|
|
UiElmEnable(cbxProgramComPort, false);
|
|
foreach (var slotSelector in SlotSelections)
|
|
{
|
|
UiElmEnable(slotSelector, false);
|
|
}
|
|
|
|
// Time measurement / DUT to REF calibration
|
|
UiElmEnable(tbxRefPulsesRequired, false);
|
|
UiElmEnable(tbxDutPulsesRequired, false);
|
|
UiElmEnable(tbxDoublePulseDeadtime, false);
|
|
UiElmEnable(tbxDutToRefTolMax, false);
|
|
UiElmEnable(tbxDutToRefTolMin, false);
|
|
UiElmEnable(rbtDutToRefCalibration, false);
|
|
UiElmEnable(rbtRefTimeMeasurement, false);
|
|
UiElmEnable(rbtDutTimeMeasurement, false);
|
|
|
|
// Regulation measurement
|
|
UiElmEnable(tbxRefPulsesPerCm, false);
|
|
UiElmEnable(tbxDutPulsesPerCm, false);
|
|
UiElmEnable(tbxScaleRefToDut, false); // Is permanent read only
|
|
UiElmEnable(cbxToleranceDisplayAttenuation, false);
|
|
|
|
// Pulse counter measurement / manual REF to Volume calibration
|
|
UiElmEnable(tbxRefPulsesMeasured, false); // Is permanent read only
|
|
UiElmEnable(tbxDutPulsesMeasured, false); // Is permanent read only
|
|
UiElmEnable(chkRefPulsesMeasured, false);
|
|
UiElmEnable(chkDutPulsesMeasured, false);
|
|
UiElmEnable(tbxVolumeMeasuredLiters, false);
|
|
UiElmEnable(tbxRefPulsesPerCmCalib, false); // Is permanent read only
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enable all buttons, FM2014 has to be connected
|
|
/// </summary>
|
|
private void SetFM2014AccessEnabled()
|
|
{
|
|
// Enable buttons and display correct information
|
|
UiElmEnable(btnDutToRefCalibration, true);
|
|
UiElmEnable(btnSaveSetupToFm2014, _fm2014RegulationSetupHasChanged, _displayBtnSaveSetupToFm2014);
|
|
UiElmEnable(btnDutToRefRegulation, true);
|
|
UiElmEnable(btnFM2014Connect, true);
|
|
UiElmEnable(btnRefToVolumeCalibration, true);
|
|
|
|
// Prepare test output to start as it is used for strat and stop
|
|
UpdateContentControl(btnRefToVolumeCalibration, StrBtnStartCalibration);
|
|
UpdateContentControl(btnDutToRefCalibration, StrBtnStartCalibration);
|
|
UpdateContentControl(btnDutToRefRegulation, StrBtnStartRegulation);
|
|
|
|
var dutPulseMeasurementIsActive = false;
|
|
var refPulseMeasurementIsActive = true;
|
|
// Enable manual input
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
if (chkRefPulsesMeasured.IsChecked != null)
|
|
refPulseMeasurementIsActive = (Boolean)chkRefPulsesMeasured.IsChecked;
|
|
if (chkDutPulsesMeasured.IsChecked != null)
|
|
dutPulseMeasurementIsActive = (Boolean)chkDutPulsesMeasured.IsChecked;
|
|
tbxVolumeMeasuredLiters.IsReadOnly = false;
|
|
tbxRefPulsesPerCm.IsReadOnly = false;
|
|
tbxDutPulsesPerCm.IsReadOnly = false;
|
|
});
|
|
|
|
// Program settings
|
|
UiElmEnable(cbxToleranceDisplayRange, true);
|
|
UiElmEnable(cbxProgramComPort, true);
|
|
foreach (var slotSelector in SlotSelections)
|
|
{
|
|
UiElmEnable(slotSelector, true);
|
|
}
|
|
|
|
// Time measurement / DUT to REF calibration
|
|
UiElmEnable(tbxRefPulsesRequired, true);
|
|
UiElmEnable(tbxDutPulsesRequired, true);
|
|
UiElmEnable(tbxDoublePulseDeadtime, true);
|
|
UiElmEnable(tbxDutToRefTolMax, true);
|
|
UiElmEnable(tbxDutToRefTolMin, true);
|
|
UiElmEnable(rbtDutToRefCalibration, true);
|
|
UiElmEnable(rbtRefTimeMeasurement, true);
|
|
UiElmEnable(rbtDutTimeMeasurement, true);
|
|
|
|
// Regulation measurement
|
|
UiElmEnable(tbxRefPulsesPerCm, true);
|
|
UiElmEnable(tbxDutPulsesPerCm, true);
|
|
UiElmEnable(tbxScaleRefToDut, true); // Is permanent read only
|
|
UiElmEnable(cbxToleranceDisplayAttenuation, true);
|
|
|
|
// Pulse counter measurement / manual REF to Volume calibration
|
|
UiElmEnable(chkRefPulsesMeasured, true);
|
|
UiElmEnable(chkDutPulsesMeasured, true);
|
|
UiElmEnable(tbxRefPulsesMeasured, refPulseMeasurementIsActive); // Is permanent read only
|
|
UiElmEnable(tbxDutPulsesMeasured, dutPulseMeasurementIsActive); // Is permanent read only
|
|
UiElmEnable(tbxVolumeMeasuredLiters, true);
|
|
UiElmEnable(tbxRefPulsesPerCmCalib, true); // Is permanent read only
|
|
}
|
|
|
|
#endregion ---------------------------------------- ActivationControls ----------------------------------------
|
|
#region ------------------------------------------- Event handler ---------------------------------------------
|
|
/// <summary>
|
|
/// Feedback from FM2014being parsed to GUI
|
|
/// </summary>
|
|
/// <remarks date="2026-Feb-02..20" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void DataReceived_Handler(Object sender, ProcessExecEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
// Remind the FM2014 user control based on the address of the FM2014 to dispatch received message to
|
|
// the correct device
|
|
UcFM2014Device ucFm2014;
|
|
if (sender is FM2014 fm2014)
|
|
{
|
|
var idx = fm2014.Address - 1;
|
|
// Broadcast address is 0 !!!!
|
|
if (idx < 0)
|
|
{
|
|
ucFm2014 = null;
|
|
}
|
|
else
|
|
{
|
|
ucFm2014 = (UcFM2014Device)UcFM2014s[idx];
|
|
}
|
|
}
|
|
else return;
|
|
|
|
var firstFm2014 = FM2014.GetFirstConnectedAndLoggedInFm2014();
|
|
|
|
if (e.ActualProcessMessage != null)
|
|
{
|
|
UpdateContentControl(lblActualProcessText, e.ActualProcessMessage);
|
|
}
|
|
|
|
if (e.ActualProcessPercent != null && !_autoProgressBar)
|
|
{
|
|
UpdateProgressBar(pbActualProgress, (Double)e.ActualProcessPercent);
|
|
}
|
|
|
|
// A circulating process bar will be used to observe an activity, but as it restarts at the beginning
|
|
// after reaching the end, a value isn't of importance
|
|
if (!_autoProgressBar)
|
|
Dispatcher.Invoke(() => lblActualProgressPercent.Content = $@"{pbActualProgress.Value:##0.0} %");
|
|
|
|
SetTimeDisplay();
|
|
|
|
// Data dispatcher
|
|
var resp = (FM2014CmdResponse)e.SpecificInfoObj;
|
|
String valueStr;
|
|
|
|
// Used as marker for error, null means measurement is in range or status or health is good
|
|
Brush statusColor = null;
|
|
|
|
// Put a separator line to the output for better visibility
|
|
if (_lastAddressReceived != fm2014.Address)
|
|
LogText(StrSeparator);
|
|
_lastAddressReceived = fm2014.Address;
|
|
|
|
// Color the output for the user display for quick check capability
|
|
if (e.StatusReturn == StatusReturn.MeasurementOutOfRange)
|
|
statusColor = ColorProcessFailed;
|
|
else if (e.StatusReturn == StatusReturn.MeasurementInRange)
|
|
statusColor = ColorSuccess;
|
|
|
|
var timeUtcStr = $"{resp.TimestampUtc:yyyy-MM-dd HH:mm:ss:fff} UTC";
|
|
if (e.StatusReturn == StatusReturn.Failed)
|
|
{
|
|
LogErrorText($"{timeUtcStr} - Ad: {fm2014.Address} - {resp.AnswerStr}");
|
|
//ErrorHandler(resp.CmdName);
|
|
}
|
|
else if (resp.IntValue == null && resp.DoubleValue == null)
|
|
{
|
|
LogText($"{timeUtcStr} - Ad: {fm2014.Address} - {resp.AnswerStr}");
|
|
}
|
|
else if (resp.IntValue != null)
|
|
{
|
|
LogText($"{timeUtcStr} - Ad: {fm2014.Address} - {resp.AnswerStr}: {resp.IntValue:D} {resp.Unit}");
|
|
switch (resp.CmdName)
|
|
{
|
|
case CmdName.CmdGetRefPulsesRemaining:
|
|
ucFm2014?.SetValue(CAL_LBL_IDX_REF_RMN_PLS, $"{resp.IntValue:D}", statusColor);
|
|
break;
|
|
case CmdName.CmdGetDutPulsesRemaining:
|
|
ucFm2014?.SetValue(CAL_LBL_IDX_DUT_RMN_PLS, $"{resp.IntValue:D}", statusColor);
|
|
break;
|
|
case CmdName.CmdGetRefPulsesCounted:
|
|
case CmdName.CmdGetRefPulsesCountedBackup:
|
|
if (firstFm2014 != null && firstFm2014.Address == fm2014.Address)
|
|
UpdateTextBox(tbxRefPulsesMeasured, $"{resp.IntValue:D}", statusColor);
|
|
ucFm2014?.SetValue(CTR_LBL_IDX_REF_PLS, $"{resp.IntValue:D}", statusColor);
|
|
break;
|
|
case CmdName.CmdGetDutPulsesCounted:
|
|
case CmdName.CmdGetDutPulsesCountedBackup:
|
|
if (firstFm2014 != null && firstFm2014.Address == fm2014.Address)
|
|
UpdateTextBox(tbxDutPulsesMeasured, $"{resp.IntValue:D}", statusColor);
|
|
ucFm2014?.SetValue(CTR_LBL_IDX_DUT_PLS, $"{resp.IntValue:D}", statusColor);
|
|
break;
|
|
case CmdName.CmdSetRefPulsesPerCm:
|
|
if (firstFm2014 != null && firstFm2014.Address == fm2014.Address)
|
|
{
|
|
UpdateTextBox(tbxRefPulsesPerCm, $"{resp.IntValue:D}", statusColor);
|
|
_initialRefPulsesPerCm = (UInt32)resp.IntValue;
|
|
}
|
|
break;
|
|
case CmdName.CmdSetDutPulsesPerCm:
|
|
if (firstFm2014 != null && firstFm2014.Address == fm2014.Address)
|
|
{
|
|
UpdateTextBox(tbxDutPulsesPerCm, $"{resp.IntValue:D}", statusColor);
|
|
_initialDutPulsesPerCm = (UInt16)resp.IntValue;
|
|
}
|
|
break;
|
|
case CmdName.CmdSetCurrentOutputAttenuation:
|
|
if (firstFm2014 != null && firstFm2014.Address == fm2014.Address)
|
|
{
|
|
Dispatcher.Invoke(() => cbxToleranceDisplayAttenuation.SelectedItem = $"{resp.IntValue}");
|
|
_initialCurrentOutputAttenuation = (Byte)resp.IntValue;
|
|
}
|
|
break;
|
|
case CmdName.CmdGetRefFrequency:
|
|
valueStr = $"{resp.IntValue:D}";
|
|
ucFm2014?.SetValue(REG_LBL_IDX_REF_FREQU, valueStr, statusColor);
|
|
break;
|
|
case CmdName.CmdResetMeasurement:
|
|
// Immediately lock all buttons and input fields until reset is finished
|
|
SetFM2014AccessLocked();
|
|
UiElmEnable(lblActualProgressPercent, true);
|
|
// Check countdown of reset method
|
|
if (resp.IntValue == 0)
|
|
{
|
|
ActionControl(false);
|
|
LogText(StrSeparator);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
else if (resp.DoubleValue != null)
|
|
{
|
|
LogText($"{timeUtcStr} - Ad: {fm2014.Address} - {resp.AnswerStr}: {resp.DoubleValue:F6} {resp.Unit}");
|
|
switch (resp.CmdName)
|
|
{
|
|
case CmdName.CmdGetRefTimerTicks:
|
|
valueStr = $"{resp.DoubleValue:F3}";
|
|
ucFm2014?.SetValue(CAL_LBL_IDX_REF_MEAS_TMR, valueStr, statusColor);
|
|
break;
|
|
case CmdName.CmdGetDutTimerTicks:
|
|
valueStr = $"{resp.DoubleValue:F3}";
|
|
ucFm2014?.SetValue(CAL_LBL_IDX_DUT_MEAS_TMR, valueStr, statusColor);
|
|
break;
|
|
case CmdName.CmdCalculatedDutToRefTolerance:
|
|
valueStr = $"{resp.DoubleValue:F3}";
|
|
ucFm2014?.SetValue(CAL_LBL_IDX_DUT_TO_REF_TOL, valueStr, statusColor);
|
|
break;
|
|
case CmdName.CmdGetDutToRefToleranceUndamped:
|
|
case CmdName.CmdGetDutToRefToleranceDamped:
|
|
valueStr = $"{resp.DoubleValue:F3}";
|
|
ucFm2014?.SetValue(REG_LBL_IDX_DUT_TO_REF_TOL, valueStr, statusColor);
|
|
break;
|
|
case CmdName.CmdRefToDutScale:
|
|
if (firstFm2014 != null && firstFm2014.Address == fm2014.Address)
|
|
UpdateTextBox(tbxScaleRefToDut, $"{resp.DoubleValue:F4}", statusColor);
|
|
break;
|
|
// // DEBUG
|
|
// case FM2014CmdDef.CmdName.CMD_GET_REF_PERIOD:
|
|
// tbxRefPeriodMs.Text = $"{resp.DoubleValue:F3}";
|
|
// break;
|
|
// case FM2014CmdDef.CmdName.CMD_GET_DUT_PERIOD:
|
|
// tbxDutPeriodMs.Text = $"{resp.DoubleValue:F3}";
|
|
// break;
|
|
// case FM2014CmdDef.CmdName.CMD_CAL_FREQU_REF_PERIOD:
|
|
// tbxRefFrequencyFromRefPeriodHz.Text = $"{resp.DoubleValue:F3}";
|
|
// break;
|
|
// case FM2014CmdDef.CmdName.CMD_CAL_FREQU_DUT_PERIOD:
|
|
// tbxDutFrequencyFromDutPeriodHz.Text = $"{resp.DoubleValue:F3}";
|
|
// break;
|
|
case CmdName.CmdCalculatedFlowRefFrequ:
|
|
valueStr = $"{resp.DoubleValue:F3}";
|
|
ucFm2014?.SetValue(REG_LBL_IDX_FLOW_RATE, valueStr, statusColor);
|
|
//UpdateTextBox(tbxRefFlowRateFromRefFrequencyCmPerH, valueStr);
|
|
// TODO THW Check if the actual flow shall be taken based on 'REF Frequency'
|
|
//UpdateTextBox(tbxActualFlowRateCmPerHour, valueStr);
|
|
break;
|
|
// case FM2014CmdDef.CmdName.CMD_CAL_FLOW_REF_PERIOD:
|
|
// tbxRefFlowRateFromRefPeriodCmPerH.Text = $"{resp.DoubleValue:F3}";
|
|
// break;
|
|
// case FM2014CmdDef.CmdName.CMD_CAL_FLOW_DUT_PERIOD:
|
|
// tbxDutFlowRateFromDutPeriodCmPerH.Text = $"{resp.DoubleValue:F3}";
|
|
// break;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
//
|
|
}
|
|
|
|
}
|
|
|
|
#endregion ---------------------------------------- Event handler ---------------------------------------------
|
|
#region ------------------------------------------- Buttons and Controls --------------------------------------
|
|
/// <summary>
|
|
/// Establish connection to FM2014 with individual address and read out FM2014 info.
|
|
/// </summary>
|
|
/// <remarks date="2026-Jan-04" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void btnConnect_Click(Object sender, EventArgs e)
|
|
{
|
|
_startTime = DateTimeOffset.UtcNow;
|
|
Connect();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start and stop the DUT to REF calibration measurement.
|
|
/// </summary>
|
|
/// <remarks date="2026-Feb-07..18" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void btnDutToRefCalibration_Click(Object sender, EventArgs e)
|
|
{
|
|
_startTime = DateTimeOffset.UtcNow;
|
|
if (FM2014.SharedCyclicMeasSequ == CyclicMeasSequ.Idle)
|
|
{
|
|
// Disable and hide dynamic measurement labels
|
|
foreach (var measLbl in MeasurementLabels)
|
|
UiElmEnable(measLbl, false, false);
|
|
|
|
UInt16? refPulsesRequired = null;
|
|
UInt16? dutPulsesRequired = null;
|
|
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
if ((rbtDutToRefCalibration.IsChecked != null && (Boolean)rbtDutToRefCalibration.IsChecked) ||
|
|
(rbtRefTimeMeasurement.IsChecked != null && (Boolean)rbtRefTimeMeasurement.IsChecked))
|
|
{
|
|
refPulsesRequired = (UInt16)_fm2014Common.RefPulsesRequired;
|
|
}
|
|
|
|
if ((rbtDutToRefCalibration.IsChecked != null && (Boolean)rbtDutToRefCalibration.IsChecked) ||
|
|
(rbtDutTimeMeasurement.IsChecked != null && (Boolean)rbtDutTimeMeasurement.IsChecked))
|
|
{
|
|
dutPulsesRequired = (UInt16)_fm2014Common.DutPulsesRequired;
|
|
}
|
|
});
|
|
|
|
// Set the measurement labels
|
|
if (refPulsesRequired != null)
|
|
{
|
|
UpdateContentControl(MeasurementLabels[CAL_LBL_IDX_REF_REQ_PLS],
|
|
StrLblRefPulsesRequired);
|
|
UpdateContentControl(MeasurementLabels[CAL_LBL_IDX_REF_RMN_PLS],
|
|
StrLblRefPulsesRemaining);
|
|
UpdateContentControl(MeasurementLabels[CAL_LBL_IDX_REF_MEAS_TMR], StrLblRefTimeMeasured);
|
|
}
|
|
|
|
if (dutPulsesRequired != null)
|
|
{
|
|
UpdateContentControl(MeasurementLabels[CAL_LBL_IDX_DUT_REQ_PLS],
|
|
StrLblDutPulsesRequired);
|
|
UpdateContentControl(MeasurementLabels[CAL_LBL_IDX_DUT_RMN_PLS],
|
|
StrLblDutPulsesRemaining);
|
|
UpdateContentControl(MeasurementLabels[CAL_LBL_IDX_DUT_MEAS_TMR], StrLblDutTimeMeasured);
|
|
}
|
|
|
|
if (refPulsesRequired != null && dutPulsesRequired != null)
|
|
{
|
|
UpdateContentControl(MeasurementLabels[CAL_LBL_IDX_DUT_TO_REF_TOL],
|
|
StrLblDutToRefToleranceMeasured);
|
|
}
|
|
|
|
UiElmEnable(MeasurementLabels[CAL_LBL_IDX_REF_REQ_PLS], true);
|
|
UiElmEnable(MeasurementLabels[CAL_LBL_IDX_REF_RMN_PLS], true);
|
|
UiElmEnable(MeasurementLabels[CAL_LBL_IDX_DUT_REQ_PLS], true);
|
|
UiElmEnable(MeasurementLabels[CAL_LBL_IDX_DUT_RMN_PLS], true);
|
|
UiElmEnable(MeasurementLabels[CAL_LBL_IDX_REF_MEAS_TMR], true);
|
|
UiElmEnable(MeasurementLabels[CAL_LBL_IDX_DUT_MEAS_TMR], true);
|
|
UiElmEnable(MeasurementLabels[CAL_LBL_IDX_DUT_TO_REF_TOL], true);
|
|
|
|
if (FM2014.RegisteredFm2014s == null || FM2014.RegisteredFm2014s.Count == 0)
|
|
return;
|
|
|
|
// Adjust the user control
|
|
foreach (var fm2014 in FM2014.RegisteredFm2014s.Where(x => x.IsLoggedOn))
|
|
{
|
|
var idx = fm2014.Address - 1;
|
|
var ucFM2014 = (UcFM2014Device)UcFM2014s[idx];
|
|
ucFM2014.EnableMeasurements(CAL_MEASURE_COUNT);
|
|
|
|
// Set initial required pulses as those are static upon the entire measurement
|
|
if (refPulsesRequired != null)
|
|
{
|
|
ucFM2014.SetValue(CAL_LBL_IDX_REF_REQ_PLS, $"{_fm2014Common.RefPulsesRequired:D}");
|
|
ucFM2014.SetValue(CAL_LBL_IDX_REF_MEAS_TMR, "---");
|
|
}
|
|
|
|
if (dutPulsesRequired != null)
|
|
{
|
|
ucFM2014.SetValue(CAL_LBL_IDX_DUT_REQ_PLS, $"{_fm2014Common.DutPulsesRequired:D}");
|
|
ucFM2014.SetValue(CAL_LBL_IDX_DUT_MEAS_TMR, "---");
|
|
}
|
|
|
|
if (refPulsesRequired != null && dutPulsesRequired != null)
|
|
{
|
|
ucFM2014.SetValue(CAL_LBL_IDX_DUT_TO_REF_TOL, "---");
|
|
}
|
|
|
|
// Set thresholds for tolerance verification
|
|
fm2014.DutToRefCalibrationToleranceMax_percent = _fm2014Common.DutToRefCalibrationToleranceMax_percent;
|
|
fm2014.DutToRefCalibrationToleranceMin_percent = _fm2014Common.DutToRefCalibrationToleranceMin_percent;
|
|
}
|
|
|
|
// Backup if meanwhile anything has changed
|
|
if (_programConfigurationSetupHasChanged)
|
|
StoreProgramConfiguration();
|
|
|
|
if (FM2014.PulseTimeMeasurement(refPulsesRequired, dutPulsesRequired, _fm2014Common.DoublePulseDeadtime_ms))
|
|
{
|
|
_autoProgressBar = false;
|
|
ActionControl(true);
|
|
UpdateContentControl(btnDutToRefCalibration, StrBtnStopCalibration);
|
|
UiElmEnable(btnDutToRefCalibration, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Deactivate the button temporary to avoid repeated execution as the reset takes a certain time
|
|
UiElmEnable(btnDutToRefCalibration, false);
|
|
FM2014.ResetHardwareAllDevices();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start and stop the DUT to REF regulation measurement.
|
|
/// </summary>
|
|
/// <remarks date="2026-Jan-14..Feb-18" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void btnDutToRefRegulation_Click(Object sender, EventArgs e)
|
|
{
|
|
_startTime = DateTimeOffset.UtcNow;
|
|
if (FM2014.SharedCyclicMeasSequ == CyclicMeasSequ.Idle)
|
|
{
|
|
// Disable and hide dynamic measurement labels
|
|
foreach (var measLbl in MeasurementLabels)
|
|
UiElmEnable(measLbl, false, false);
|
|
|
|
// Set the measurement labels
|
|
UpdateContentControl(MeasurementLabels[REG_LBL_IDX_REF_FREQU], StrLblRefFrequencyHz);
|
|
UpdateContentControl(MeasurementLabels[REG_LBL_IDX_FLOW_RATE], StrLblFlowRateMeasured);
|
|
UpdateContentControl(MeasurementLabels[REG_LBL_IDX_DUT_TO_REF_TOL], StrLblDutToRefToleranceMeasured);
|
|
UiElmEnable(MeasurementLabels[REG_LBL_IDX_REF_FREQU], true);
|
|
UiElmEnable(MeasurementLabels[REG_LBL_IDX_FLOW_RATE], true);
|
|
UiElmEnable(MeasurementLabels[REG_LBL_IDX_DUT_TO_REF_TOL], true);
|
|
|
|
if (FM2014.RegisteredFm2014s == null || FM2014.RegisteredFm2014s.Count == 0)
|
|
return;
|
|
|
|
// Adjust the user control
|
|
foreach (var fm2014 in FM2014.RegisteredFm2014s.Where(x => x.IsLoggedOn))
|
|
{
|
|
var idx = fm2014.Address - 1;
|
|
var ucFM2014 = (UcFM2014Device)UcFM2014s[idx];
|
|
ucFM2014.EnableMeasurements(REG_MEASURE_COUNT);
|
|
fm2014.CurrentOutputDisplayRangeNominal_percent = _fm2014Common.CurrentOutputDisplayRangeNominal_percent;
|
|
ucFM2014.SetValue(REG_LBL_IDX_REF_FREQU, "---");
|
|
ucFM2014.SetValue(REG_LBL_IDX_FLOW_RATE, "---");
|
|
ucFM2014.SetValue(REG_LBL_IDX_DUT_TO_REF_TOL, "---");
|
|
}
|
|
|
|
// Backup if meanwhile anything has changed
|
|
if (_programConfigurationSetupHasChanged || _programConfigurationSetupHasChanged)
|
|
StoreProgramConfiguration();
|
|
|
|
if (FM2014.RegulationMeasurement(_fm2014Common.RefPulses_per_cm, _fm2014Common.DutPulses_per_cm,
|
|
_fm2014Common.DoublePulseDeadtime_ms, _fm2014Common.CurrentOutputAttenuation))
|
|
{
|
|
_autoProgressBar = true;
|
|
ActionControl(true);
|
|
UpdateContentControl(btnDutToRefRegulation, StrBtnStopRegulation);
|
|
UiElmEnable(btnDutToRefRegulation, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Deactivate the button temporary to avoid repeated execution as the reset takes a certain time
|
|
UiElmEnable(btnDutToRefRegulation, false);
|
|
_autoProgressBar = false;
|
|
FM2014.ResetHardwareAllDevices();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start and stop the manual REF calibration measurement.
|
|
/// </summary>
|
|
/// <remarks date="2026-Jan-04..Feb-19" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void btnRefToVolumeCalibration_Click(Object sender, EventArgs e)
|
|
{
|
|
_startTime = DateTimeOffset.UtcNow;
|
|
if (FM2014.SharedCyclicMeasSequ == CyclicMeasSequ.Idle)
|
|
{
|
|
// Disable and hide dynamic measurement labels
|
|
foreach (var measLbl in MeasurementLabels)
|
|
UiElmEnable(measLbl, false, false);
|
|
|
|
// Set the measurement labels
|
|
UpdateContentControl(MeasurementLabels[CTR_LBL_IDX_REF_PLS], StrLblRefPulsesMeasured);
|
|
UpdateContentControl(MeasurementLabels[CTR_LBL_IDX_DUT_PLS], StrLblDutPulsesMeasured);
|
|
UiElmEnable(MeasurementLabels[CTR_LBL_IDX_REF_PLS], true);
|
|
UiElmEnable(MeasurementLabels[CTR_LBL_IDX_DUT_PLS], true);
|
|
|
|
UpdateTextBox(tbxRefPulsesMeasured, "");
|
|
UpdateTextBox(tbxDutPulsesMeasured, "");
|
|
UpdateTextBox(tbxVolumeMeasuredLiters, "");
|
|
UpdateTextBox(tbxRefPulsesPerCmCalib, "");
|
|
|
|
if (FM2014.RegisteredFm2014s == null || FM2014.RegisteredFm2014s.Count == 0)
|
|
return;
|
|
|
|
var measureDutPulses = false;
|
|
var measureRefPulses = false;
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
measureDutPulses = chkDutPulsesMeasured.IsChecked ?? false;
|
|
measureRefPulses = chkRefPulsesMeasured.IsChecked ?? false;
|
|
});
|
|
|
|
// Adjust the user control
|
|
foreach (var fm2014 in FM2014.RegisteredFm2014s.Where(x => x.IsLoggedOn))
|
|
{
|
|
var idx = fm2014.Address - 1;
|
|
var ucFM2014 = (UcFM2014Device)UcFM2014s[idx];
|
|
ucFM2014.EnableMeasurements(CTR_MEASURE_COUNT);
|
|
}
|
|
|
|
// Backup if meanwhile anything has changed
|
|
if (_programConfigurationSetupHasChanged || _programConfigurationSetupHasChanged)
|
|
StoreProgramConfiguration();
|
|
|
|
if (FM2014.PulseCounterMeasurement(measureRefPulses, measureDutPulses))
|
|
{
|
|
_autoProgressBar = true;
|
|
ActionControl(true);
|
|
UpdateContentControl(btnRefToVolumeCalibration, StrBtnStopCalibration);
|
|
UiElmEnable(btnRefToVolumeCalibration, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Deactivate the button temporary to avoid repeated execution as the reset takes a certain time
|
|
UiElmEnable(btnRefToVolumeCalibration, false);
|
|
_autoProgressBar = false;
|
|
FM2014.ResetHardwareAllDevices();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Select next control on enter key pressed
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-09" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void cbxToleranceDisplayRange_KeyDown(Object sender, KeyEventArgs e)
|
|
{
|
|
// Catch the enter key, recalculate all settings
|
|
if (e.Key == Key.Enter || e.Key == Key.Tab)
|
|
{
|
|
Keyboard.Focus(btnSaveSetupToFm2014);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// FM2014 tolerance setup for calculation of it item has changed:
|
|
/// - This will dispatch the new value to all FM2014s.
|
|
/// </summary>
|
|
/// <remarks date="2026-Feb-09..Feb-18" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void cbxToleranceDisplayRange_SelectedIndexChanged(Object sender, EventArgs e)
|
|
{
|
|
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
if (byte.TryParse(cbxToleranceDisplayRange.SelectedItem.ToString(), out var tolerance) &&
|
|
(tolerance == FM2014.CURRENT_OUTPUT_DISPLAY_RANGE_STANDARD_NOMINAL_percent ||
|
|
tolerance == FM2014.CURRENT_OUTPUT_DISPLAY_RANGE_EXTENDED_NOMINAL_percent) &&
|
|
_fm2014Common.CurrentOutputDisplayRangeNominal_percent != tolerance)
|
|
{
|
|
_programConfigurationSetupHasChanged = true;
|
|
_fm2014Common.CurrentOutputDisplayRangeNominal_percent = tolerance;
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Select next control on enter key pressed
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Jan-24" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void cbxToleranceDisplayAttenuation_KeyDown(Object sender, KeyEventArgs e)
|
|
{
|
|
// Catch the enter key, recalculate all settings
|
|
if (e.Key == Key.Enter || e.Key == Key.Tab)
|
|
{
|
|
Keyboard.Focus(cbxToleranceDisplayRange);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// FM2014 current attenuation item has changed:
|
|
/// - This will dispatch the new value to all FM2014s.
|
|
/// </summary>
|
|
/// <remarks date="2026-Jan-18..Feb-18" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void cbxToleranceDisplayAttenuation_SelectedIndexChanged(Object sender, EventArgs e)
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
if (byte.TryParse(cbxToleranceDisplayAttenuation.SelectedItem.ToString(), out var attenuation) &&
|
|
attenuation >= FM2014.CURRENT_OUTPUT_ATTENUATION_MIN &&
|
|
attenuation <= FM2014.CURRENT_OUTPUT_ATTENUATION_MAX &&
|
|
_fm2014Common.CurrentOutputAttenuation != attenuation)
|
|
{
|
|
_fm2014Common.CurrentOutputAttenuation = attenuation;
|
|
CheckForFM2014SetupChanged();
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Selected COM port, tolerance display settings or address item has changed.
|
|
/// These values can be setup before any connection to the FM2014 has been established. Therefore, those
|
|
/// settings have to be remembered within this object.
|
|
/// </summary>
|
|
/// <remarks date="2026-Jan-04..Feb-16" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void cbxProgramComPort_SelectedValueChanged(Object sender, EventArgs e)
|
|
{
|
|
if (_comPortIdx != cbxProgramComPort.SelectedIndex)
|
|
{
|
|
_comPortIdx = cbxProgramComPort.SelectedIndex;
|
|
_programConfigurationSetupHasChanged = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Safe standalone measurement setup:
|
|
/// - This will save the setup in all FM2014s.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Jan-18" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void btnSaveSetupToFm2014_Click(Object sender, EventArgs e)
|
|
{
|
|
if (FM2014.StoreAllConfigurations())
|
|
{
|
|
_fm2014RegulationSetupHasChanged = false;
|
|
UiElmEnable(btnSaveSetupToFm2014, false, _displayBtnSaveSetupToFm2014);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Switch between damped or undamped tolerance display. This can be changed during the
|
|
/// 'Regulation Measurement' on the fly as it will just collect a different dataset and
|
|
/// not change anything on the measurement setup:
|
|
/// - This will dispatch the new value to all FM2014s.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Jan-14" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void chkUseDampedTolerance_CheckedChanged(Object sender, EventArgs e)
|
|
{
|
|
Dispatcher.Invoke(DispatcherPriority.Normal,
|
|
new Action(() =>
|
|
{
|
|
if (FM2014.RegisteredFm2014s == null || FM2014.RegisteredFm2014s.Count <= 0)
|
|
return;
|
|
|
|
foreach (var fm2014 in FM2014.RegisteredFm2014s)
|
|
{
|
|
if (fm2014 != null && chkUseDampedTolerance.IsChecked != null)
|
|
fm2014.UseDampedTolerance = (Boolean)chkUseDampedTolerance.IsChecked;
|
|
}
|
|
}));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enable/Disable input
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void chkDecodeEntireStatus_CheckChanged(Object sender, EventArgs e)
|
|
{
|
|
Dispatcher.Invoke(DispatcherPriority.Normal,
|
|
new Action(() =>
|
|
{
|
|
if (FM2014.RegisteredFm2014s == null || FM2014.RegisteredFm2014s.Count <= 0)
|
|
return;
|
|
|
|
if (chkDecodeEntireStatus.IsChecked != null)
|
|
FM2014.DecodeEntireStatus = (Boolean)chkDecodeEntireStatus.IsChecked;
|
|
}));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enable/Disable input
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void chkRefPulseMeasurement_CheckChanged(Object sender, EventArgs e)
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
var isChecked = false;
|
|
if (chkRefPulsesMeasured.IsChecked != null)
|
|
isChecked = (Boolean)chkRefPulsesMeasured.IsChecked;
|
|
UiElmEnable(tbxRefPulsesMeasured, isChecked);
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enable/Disable input
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void chkDutPulseMeasurement_CheckChanged(Object sender, EventArgs e)
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
var isChecked = false;
|
|
if (chkDutPulsesMeasured.IsChecked != null)
|
|
isChecked = (Boolean)chkDutPulsesMeasured.IsChecked;
|
|
UiElmEnable(tbxDutPulsesMeasured, isChecked);
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Common check for key input to edit an integer field:
|
|
/// - Allows Left, Right, Back, Delete and 0-9 keys . and ,
|
|
/// </summary>
|
|
/// <param name="keyCode"></param>
|
|
/// <returns>true an allowed key-code</returns>
|
|
/// <remarks date="2026-Feb-09" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private static Boolean MaskEditDoubleInput(Key keyCode)
|
|
{
|
|
return keyCode == Key.Back ||
|
|
keyCode == Key.Left ||
|
|
keyCode == Key.Right ||
|
|
keyCode == Key.Delete ||
|
|
keyCode == Key.Decimal ||
|
|
keyCode == Key.OemComma ||
|
|
keyCode == Key.OemPeriod ||
|
|
keyCode == Key.OemMinus ||
|
|
keyCode == Key.OemPlus ||
|
|
Regex.IsMatch($"{keyCode}", @"[0-9]");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Common check for key input to edit an integer field:
|
|
/// - Allows Left, Right, Back, Delete and 0-9 keys.
|
|
/// </summary>
|
|
/// <param name="keyCode"></param>
|
|
/// <returns>true an allowed key-code</returns>
|
|
/// <remarks date="2026-Feb-06" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private static Boolean MaskEditIntegerInput(Key keyCode)
|
|
{
|
|
return keyCode == Key.Back ||
|
|
keyCode == Key.Left ||
|
|
keyCode == Key.Right ||
|
|
keyCode == Key.Delete ||
|
|
Regex.IsMatch($"{keyCode}", @"[0-9]");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Redirect keystroke 'Enter' to leaving the cell event
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-09" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxRefPulsesRequired_KeyDown(Object sender, KeyEventArgs e)
|
|
{
|
|
// Catch the enter key, recalculate all settings
|
|
if (e.Key == Key.Enter || e.Key == Key.Tab)
|
|
{
|
|
tbxRefPulsesRequired_LostFocus(this, null);
|
|
Keyboard.Focus(tbxDutPulsesRequired);
|
|
TextBoxSelectAll(tbxDutPulsesRequired);
|
|
}
|
|
else if (!MaskEditIntegerInput(e.Key))
|
|
{
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changed test to cover the delete key which is not in the key-down event!?
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-09" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxRefPulsesRequired_TextChanged(Object sender, TextChangedEventArgs e)
|
|
{
|
|
// Take the new input and calculate the results after the input has been taken after this event!
|
|
Task.Factory.StartNew(() =>
|
|
{
|
|
Thread.Sleep(1);
|
|
}, _cancellationToken).ContinueWith(delegate
|
|
{
|
|
Dispatcher.Invoke(() => tbxRefPulsesRequired_LostFocus(this, null));
|
|
}, _cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// After manual input of the required REF pulses for the calibration measurement:
|
|
/// - This will dispatch the new value to all FM2014s.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-09..Feb-18" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxRefPulsesRequired_LostFocus(Object sender, EventArgs e)
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
if (string.IsNullOrEmpty(tbxRefPulsesRequired.Text))
|
|
{
|
|
tbxRefPulsesRequired.Background = ColorStandardInputField;
|
|
return;
|
|
}
|
|
// Check for change
|
|
var backupPulsesRequired = _fm2014Common.RefPulsesRequired;
|
|
|
|
// Convert the input and check limits
|
|
if (int.TryParse(tbxRefPulsesRequired.Text, out var pulses) &&
|
|
FM2014.PULSES_TIME_MEASUREMENT_INPUT_MIN <= pulses &&
|
|
FM2014.PULSES_TIME_MEASUREMENT_INPUT_MAX >= pulses)
|
|
{
|
|
if (backupPulsesRequired != pulses)
|
|
{
|
|
_programConfigurationSetupHasChanged = true;
|
|
// Preset to fill the configuration file storage ability
|
|
_fm2014Common.RefPulsesRequired = (UInt32)pulses;
|
|
}
|
|
|
|
tbxRefPulsesRequired.Background = ColorStandardInputField;
|
|
}
|
|
else
|
|
{
|
|
tbxRefPulsesRequired.Background = ColorProcessFailed;
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Redirect keystroke 'Enter' to leaving the cell event
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-09" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxDutPulsesRequired_KeyDown(Object sender, KeyEventArgs e)
|
|
{
|
|
// Catch the enter key, recalculate all settings
|
|
if (e.Key == Key.Enter || e.Key == Key.Tab)
|
|
{
|
|
tbxDutPulsesRequired_LostFocus(this, null);
|
|
Keyboard.Focus(tbxDoublePulseDeadtime);
|
|
TextBoxSelectAll(tbxDoublePulseDeadtime);
|
|
}
|
|
else if (!MaskEditIntegerInput(e.Key))
|
|
{
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Select all text in text box
|
|
/// </summary>
|
|
/// <param name="textBox"></param>
|
|
private void TextBoxSelectAll(TextBox textBox)
|
|
{
|
|
Dispatcher.Invoke(textBox.SelectAll);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changed test to cover the delete key which is not in the key-down event!?
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-09" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxDutPulsesRequired_TextChanged(Object sender, TextChangedEventArgs e)
|
|
{
|
|
// Take the new input and calculate the results after the input has been taken after this event!
|
|
Task.Factory.StartNew(() =>
|
|
{
|
|
Thread.Sleep(1);
|
|
}, _cancellationToken).ContinueWith(delegate
|
|
{
|
|
Dispatcher.Invoke(() => tbxDutPulsesRequired_LostFocus(this, null));
|
|
}, _cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// After manual input of the required DUT pulses for the calibration measurement:
|
|
/// - This will dispatch the new value to all FM2014s.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-09..Feb-16" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxDutPulsesRequired_LostFocus(Object sender, EventArgs e)
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
if (string.IsNullOrEmpty(tbxDutPulsesRequired.Text))
|
|
{
|
|
tbxDutPulsesRequired.Background = ColorProcessFailed;
|
|
return;
|
|
}
|
|
|
|
// Check for change
|
|
var backupPulsesRequired = _fm2014Common.DutPulsesRequired;
|
|
|
|
// Convert the input and check limits
|
|
if (int.TryParse(tbxDutPulsesRequired.Text, out var pulses) &&
|
|
FM2014.PULSES_TIME_MEASUREMENT_INPUT_MIN <= pulses &&
|
|
FM2014.PULSES_TIME_MEASUREMENT_INPUT_MAX >= pulses)
|
|
{
|
|
if (backupPulsesRequired != pulses)
|
|
{
|
|
_programConfigurationSetupHasChanged = true;
|
|
// Preset to fill the configuration file storage ability
|
|
_fm2014Common.DutPulsesRequired = (UInt32)pulses;
|
|
}
|
|
|
|
tbxDutPulsesRequired.Background = ColorStandardInputField;
|
|
}
|
|
else
|
|
{
|
|
tbxDutPulsesRequired.Background = ColorProcessFailed;
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Redirect keystroke 'Enter' to leaving the cell event
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-09" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxDoublePulseDeadtime_KeyDown(Object sender, KeyEventArgs e)
|
|
{
|
|
// Catch the enter key, recalculate all settings
|
|
if (e.Key == Key.Enter || e.Key == Key.Tab)
|
|
{
|
|
tbxDoublePulseDeadtime_LostFocus(this, null);
|
|
Keyboard.Focus(tbxDutToRefTolMax);
|
|
TextBoxSelectAll(tbxDutToRefTolMax);
|
|
}
|
|
else if (!MaskEditIntegerInput(e.Key))
|
|
{
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changed test to cover the delete key which is not in the key-down event!?
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-09" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxDoublePulseDeadtime_TextChanged(Object sender, TextChangedEventArgs e)
|
|
{
|
|
// Take the new input and calculate the results after the input has been taken after this event!
|
|
Task.Factory.StartNew(() =>
|
|
{
|
|
Thread.Sleep(1);
|
|
}, _cancellationToken).ContinueWith(delegate
|
|
{
|
|
Dispatcher.Invoke(() => tbxDoublePulseDeadtime_LostFocus(this, null));
|
|
}, _cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// After manual input of the double pulse detection deadtime:
|
|
/// - This will dispatch the new value to all FM2014s.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-09..Feb-18" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxDoublePulseDeadtime_LostFocus(Object sender, EventArgs e)
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
if (string.IsNullOrEmpty(tbxDoublePulseDeadtime.Text))
|
|
{
|
|
tbxDoublePulseDeadtime.Background = ColorProcessFailed;
|
|
return;
|
|
}
|
|
|
|
// Check for change
|
|
var backupDoublePulseDeadtime = _fm2014Common.DoublePulseDeadtime_ms;
|
|
|
|
// Convert the input and check limits
|
|
if (int.TryParse(tbxDoublePulseDeadtime.Text, out var deadtime) &&
|
|
FM2014.DOUBLE_PULSE_DEADTIME_MIN_ms <= deadtime &&
|
|
FM2014.DOUBLE_PULSE_DEADTIME_MAX_ms >= deadtime)
|
|
{
|
|
if (backupDoublePulseDeadtime != deadtime)
|
|
{
|
|
_programConfigurationSetupHasChanged = true;
|
|
// Preset to fill the configuration file storage ability
|
|
_fm2014Common.DoublePulseDeadtime_ms = (UInt16)deadtime;
|
|
}
|
|
|
|
tbxDoublePulseDeadtime.Background = ColorStandardInputField;
|
|
}
|
|
else
|
|
{
|
|
tbxDoublePulseDeadtime.Background = ColorProcessFailed;
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Redirect keystroke 'Enter' to leaving the cell event
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-09" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxDutToRefToleranceMax_KeyDown(Object sender, KeyEventArgs e)
|
|
{
|
|
// Catch the enter key and tab key as indicator for leaving the cell
|
|
if (e.Key == Key.Enter || e.Key == Key.Tab)
|
|
{
|
|
tbxDutToRefToleranceMax_LostFocus(this, null);
|
|
Keyboard.Focus(tbxDutToRefTolMin);
|
|
TextBoxSelectAll(tbxDutToRefTolMin);
|
|
}
|
|
else if (!MaskEditDoubleInput(e.Key))
|
|
{
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changed test to cover the delete key which is not in the key-down event!?
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-06" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxDutToRefToleranceMax_TextChanged(Object sender, TextChangedEventArgs e)
|
|
{
|
|
// Take the new input and calculate the results after the input has been taken after this event!
|
|
Task.Factory.StartNew(() =>
|
|
{
|
|
Thread.Sleep(1);
|
|
}, _cancellationToken).ContinueWith(delegate
|
|
{
|
|
Dispatcher.Invoke(() => tbxDutToRefToleranceMax_LostFocus(this, null));
|
|
}, _cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// After manual input of measured weight scale or reservoir in liters this will calculate the
|
|
/// REF pulses per cubic meter and copy from REF pulses per cubic meter in 'Manual REF Calibration'
|
|
/// to 'Regulation Setup':
|
|
/// - This will dispatch the new value to all FM2014s.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-09..Feb-24" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxDutToRefToleranceMax_LostFocus(Object sender, EventArgs e)
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
|
|
if (string.IsNullOrEmpty(tbxDutToRefTolMax.Text))
|
|
{
|
|
tbxDutToRefTolMax.Background = ColorProcessFailed;
|
|
return;
|
|
}
|
|
|
|
// Check for change
|
|
var backupDutToRefTolerance = _fm2014Common.DutToRefCalibrationToleranceMax_percent;
|
|
|
|
var englishNumberFormat = tbxDutToRefTolMax.Text.Replace(',', '.');
|
|
if (double.TryParse(englishNumberFormat, NumberStyles.Any, new CultureInfo("en"), out var tolerance))
|
|
{
|
|
if (Math.Abs(backupDutToRefTolerance - tolerance) > 0.01)
|
|
{
|
|
_programConfigurationSetupHasChanged = true;
|
|
// Preset to fill the configuration file storage ability
|
|
_fm2014Common.DutToRefCalibrationToleranceMax_percent = tolerance;
|
|
}
|
|
|
|
tbxDutToRefTolMax.Background = ColorStandardInputField;
|
|
}
|
|
else
|
|
{
|
|
tbxDutToRefTolMax.Background = ColorProcessFailed;
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Redirect keystroke 'Enter' to leaving the cell event
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-09" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxDutToRefToleranceMin_KeyDown(Object sender, KeyEventArgs e)
|
|
{
|
|
// Catch the enter key, recalculate all settings
|
|
if (e.Key == Key.Enter || e.Key == Key.Tab)
|
|
{
|
|
tbxDutToRefToleranceMin_LostFocus(this, null);
|
|
Keyboard.Focus(btnDutToRefCalibration);
|
|
}
|
|
else if (!MaskEditDoubleInput(e.Key))
|
|
{
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changed test to cover the delete key which is not in the key-down event!?
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-06" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxDutToRefToleranceMin_TextChanged(Object sender, TextChangedEventArgs e)
|
|
{
|
|
// Take the new input and calculate the results after the input has been taken after this event!
|
|
Task.Factory.StartNew(() =>
|
|
{
|
|
Thread.Sleep(1);
|
|
}, _cancellationToken).ContinueWith(delegate
|
|
{
|
|
Dispatcher.Invoke(() => tbxDutToRefToleranceMin_LostFocus(this, null));
|
|
}, _cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// After manual input of measured weight scale or reservoir in liters this will calculate the
|
|
/// REF pulses per cubic meter and copy from REF pulses per cubic meter in 'Manual REF Calibration'
|
|
/// to 'Regulation Setup':
|
|
/// - This will dispatch the new value to all FM2014s.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-09..Feb-24" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxDutToRefToleranceMin_LostFocus(Object sender, EventArgs e)
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
|
|
if (string.IsNullOrEmpty(tbxDutToRefTolMin.Text))
|
|
{
|
|
tbxDutToRefTolMin.Background = ColorProcessFailed;
|
|
return;
|
|
}
|
|
|
|
// Check for change
|
|
var backupDutToRefTolerance = _fm2014Common.DutToRefCalibrationToleranceMin_percent;
|
|
|
|
var englishNumberFormat = tbxDutToRefTolMin.Text.Replace(',', '.');
|
|
if (double.TryParse(englishNumberFormat, NumberStyles.Any, new CultureInfo("en"), out var tolerance))
|
|
{
|
|
if (Math.Abs(backupDutToRefTolerance - tolerance) > 0.01)
|
|
{
|
|
_programConfigurationSetupHasChanged = true;
|
|
// Preset to fill the configuration file storage ability
|
|
_fm2014Common.DutToRefCalibrationToleranceMin_percent = tolerance;
|
|
}
|
|
|
|
tbxDutToRefTolMin.Background = ColorStandardInputField;
|
|
}
|
|
else
|
|
{
|
|
tbxDutToRefTolMin.Background = ColorProcessFailed;
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Redirect keystroke 'Enter' to leaving the cell event
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Jan-14..24" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxRefPulsesPerCm_KeyDown(Object sender, KeyEventArgs e)
|
|
{
|
|
// Catch the enter key, recalculate all settings
|
|
if (e.Key == Key.Enter || e.Key == Key.Tab)
|
|
{
|
|
tbxRefPulsesPerCm_LostFocus(this, null);
|
|
Keyboard.Focus(tbxDutPulsesPerCm);
|
|
TextBoxSelectAll(tbxDutPulsesPerCm);
|
|
}
|
|
else if (!MaskEditIntegerInput(e.Key))
|
|
{
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changed test to cover the delete key which is not in the key-down event!?
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-06" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxRefPulsesPerCm_TextChanged(Object sender, TextChangedEventArgs e)
|
|
{
|
|
// Take the new input and calculate the results after the input has been taken after this event!
|
|
Task.Factory.StartNew(() =>
|
|
{
|
|
Thread.Sleep(1);
|
|
}, _cancellationToken).ContinueWith(delegate
|
|
{
|
|
Dispatcher.Invoke(() => tbxRefPulsesPerCm_LostFocus(this, null));
|
|
}, _cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// After manual input of the REF pulses per cubic meter the scale REF to DUT has to be recalculated:
|
|
/// - This will dispatch the new value to all FM2014s.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Jan-14..Feb-18" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxRefPulsesPerCm_LostFocus(Object sender, EventArgs e)
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
if (string.IsNullOrEmpty(tbxRefPulsesPerCm.Text))
|
|
{
|
|
tbxRefPulsesPerCm.Background = ColorProcessFailed;
|
|
btnSaveSetupToFm2014.IsEnabled = false;
|
|
btnDutToRefRegulation.IsEnabled = false;
|
|
return;
|
|
}
|
|
|
|
// Parse and check limits
|
|
if (int.TryParse(tbxRefPulsesPerCm.Text, out var pulses_per_cm) &&
|
|
FM2014.PULSES_PER_CM_REGULATION_SETUP_MIN <= pulses_per_cm &&
|
|
FM2014.REF_PULSES_PER_CM_REGULATION_INPUT_MAX >= pulses_per_cm)
|
|
{
|
|
// During setup of the 'Ref_pulse_per_cm' the 'RefToDutScaleStr' will be generated
|
|
_fm2014Common.RefPulses_per_cm = (UInt32)pulses_per_cm;
|
|
|
|
if (!UpdateRefToDutScale())
|
|
{
|
|
tbxRefPulsesPerCm.Background = ColorProcessFailed;
|
|
return;
|
|
}
|
|
|
|
_programConfigurationSetupHasChanged = true;
|
|
tbxRefPulsesPerCm.Background = ColorStandardInputField;
|
|
CheckForFM2014SetupChanged();
|
|
}
|
|
else
|
|
{
|
|
tbxRefPulsesPerCm.Background = ColorProcessFailed;
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Redirect keystroke 'Enter' to leaving the cell event
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Jan-14..24" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxDutPulsesPerCm_KeyDown(Object sender, KeyEventArgs e)
|
|
{
|
|
// Catch the enter key, recalculate all settings
|
|
if (e.Key == Key.Enter || e.Key == Key.Tab)
|
|
{
|
|
tbxDutPulsesPerCm_LostFocus(this, null);
|
|
Keyboard.Focus(cbxToleranceDisplayAttenuation);
|
|
}
|
|
else if (!MaskEditIntegerInput(e.Key))
|
|
{
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changed test to cover the delete key which is not in the key-down event!?
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-06" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxDutPulsesPerCm_TextChanged(Object sender, TextChangedEventArgs e)
|
|
{
|
|
// Take the new input and calculate the results after the input has been taken after this event!
|
|
Task.Factory.StartNew(() =>
|
|
{
|
|
Thread.Sleep(1);
|
|
}, _cancellationToken).ContinueWith(delegate
|
|
{
|
|
Dispatcher.Invoke(() => tbxDutPulsesPerCm_LostFocus(this, null));
|
|
}, _cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// After manual input of the DUT pulses per cubic meter the scale REF to DUT has to be recalculated:
|
|
/// - This will dispatch the new value to all FM2014s.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Jan-14..Feb-18" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxDutPulsesPerCm_LostFocus(Object sender, EventArgs e)
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
if (string.IsNullOrEmpty(tbxDutPulsesPerCm.Text))
|
|
{
|
|
tbxDutPulsesPerCm.Background = ColorProcessFailed;
|
|
btnSaveSetupToFm2014.IsEnabled = false;
|
|
btnDutToRefRegulation.IsEnabled = false;
|
|
return;
|
|
}
|
|
|
|
// Parse and check limits
|
|
if (int.TryParse(tbxDutPulsesPerCm.Text, out var pulses_per_cm) &&
|
|
FM2014.PULSES_PER_CM_REGULATION_SETUP_MIN <= pulses_per_cm &&
|
|
FM2014.PULSES_PER_CM_REGULATION_SETUP_MAX >= pulses_per_cm)
|
|
{
|
|
// During setup of the 'Dut_pulse_per_cm' the 'RefToDutScaleStr' will be generated
|
|
_fm2014Common.DutPulses_per_cm = (UInt16)pulses_per_cm;
|
|
|
|
if (!UpdateRefToDutScale())
|
|
{
|
|
tbxDutPulsesPerCm.Background = ColorProcessFailed;
|
|
return;
|
|
}
|
|
|
|
_programConfigurationSetupHasChanged = true;
|
|
tbxDutPulsesPerCm.Background = ColorStandardInputField;
|
|
CheckForFM2014SetupChanged();
|
|
}
|
|
else
|
|
{
|
|
tbxDutPulsesPerCm.Background = ColorProcessFailed;
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Redirect keystroke 'Enter' to leaving the cell event
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-09" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxVolumeMeasuredLiters_KeyDown(Object sender, KeyEventArgs e)
|
|
{
|
|
// Catch the enter key, recalculate all settings
|
|
if (e.Key == Key.Enter || e.Key == Key.Tab)
|
|
{
|
|
tbxVolumeMeasuredLiters_LostFocus(this, null);
|
|
Keyboard.Focus(tbxDutToRefTolMax);
|
|
TextBoxSelectAll(tbxDutToRefTolMax);
|
|
}
|
|
else if (!MaskEditIntegerInput(e.Key))
|
|
{
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changed test to cover the delete key which is not in the key-down event!?
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-06" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxVolumeMeasuredLiters_TextChanged(Object sender, TextChangedEventArgs e)
|
|
{
|
|
// Take the new input and calculate the results after the input has been taken after this event!
|
|
Task.Factory.StartNew(() =>
|
|
{
|
|
Thread.Sleep(1);
|
|
}, _cancellationToken).ContinueWith(delegate
|
|
{
|
|
Dispatcher.Invoke(() => tbxVolumeMeasuredLiters_LostFocus(this, null));
|
|
}, _cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// After manual input of measured weight scale or reservoir in liters this will calculate the
|
|
/// REF pulses per cubic meter and copy from REF pulses per cubic meter in 'Manual REF Calibration'
|
|
/// to 'Regulation Setup':
|
|
/// - This will dispatch the new value to all FM2014s.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-09..24" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void tbxVolumeMeasuredLiters_LostFocus(Object sender, EventArgs e)
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
|
|
// If REF pulses have been acquired the volume field has to contain a value
|
|
if (!string.IsNullOrEmpty(tbxRefPulsesMeasured.Text) &&
|
|
string.IsNullOrEmpty(tbxVolumeMeasuredLiters.Text))
|
|
{
|
|
tbxVolumeMeasuredLiters.Background = ColorProcessFailed;
|
|
return;
|
|
}
|
|
|
|
// Calculate REF pulses per cubic meter
|
|
if (int.TryParse(tbxRefPulsesMeasured.Text, out var pulses) && pulses > 0 &&
|
|
int.TryParse(tbxVolumeMeasuredLiters.Text, out var liters) && liters > 0)
|
|
{
|
|
// Calculate the new pulse ratio based on the manual calibration
|
|
var pulses_per_cm = FM2014.CalculatePulsesPerCm(pulses, liters);
|
|
|
|
// Try to set the new calculated REF pulses limited by the property setter
|
|
_fm2014Common.RefPulses_per_cm = pulses_per_cm;
|
|
|
|
// If this succeeded, then update the new manual calibrated value
|
|
if (pulses_per_cm == _fm2014Common.RefPulses_per_cm)
|
|
{
|
|
tbxRefPulsesPerCm.Text = $@"{_fm2014Common.RefPulses_per_cm:D}";
|
|
tbxRefPulsesPerCmCalib.Text = $@"{_fm2014Common.RefPulses_per_cm:D}";
|
|
|
|
if (!UpdateRefToDutScale())
|
|
{
|
|
tbxVolumeMeasuredLiters.Background = ColorProcessFailed;
|
|
tbxRefPulsesPerCmCalib.Background = ColorProcessFailed;
|
|
tbxRefPulsesPerCm.Background = ColorProcessFailed;
|
|
return;
|
|
}
|
|
_programConfigurationSetupHasChanged = true;
|
|
tbxRefPulsesPerCmCalib.Background = ColorStandardDisplayField;
|
|
tbxRefPulsesPerCm.Background = ColorStandardInputField;
|
|
tbxVolumeMeasuredLiters.Background = ColorStandardInputField;
|
|
CheckForFM2014SetupChanged();
|
|
}
|
|
else
|
|
{
|
|
tbxVolumeMeasuredLiters.Background = ColorProcessFailed;
|
|
tbxRefPulsesPerCmCalib.Background = ColorProcessFailed;
|
|
tbxRefPulsesPerCm.Text = StrError;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private void chkAnySlot_Click(Object sender, RoutedEventArgs e)
|
|
{
|
|
_programConfigurationSetupHasChanged = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Access to help menu
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void HlpMenu_Click(Object sender, RoutedEventArgs e)
|
|
{
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Change the font size on mouse wheel and [Ctrl] button.
|
|
/// Needs to be registered for:
|
|
/// 1. MouseWheel="rtbLog_MouseWheel" and
|
|
/// 2. PreviewMouseWheel="rtbLog_MouseWheel",
|
|
/// otherwise the window will first scroll up/down and only reaching the limits (fully up/down) firing the
|
|
/// MouseWheel event!
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <remarks date="2026-Feb-25" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
private void rtbLog_MouseWheel(Object sender, MouseWheelEventArgs e)
|
|
{
|
|
if (Keyboard.IsKeyDown(Key.RightCtrl) || Keyboard.IsKeyDown(Key.LeftCtrl))
|
|
{
|
|
e.Handled = true;
|
|
if (e.Delta > 0)
|
|
{
|
|
Dispatcher.Invoke(() => rtbLog.FontSize += 1);
|
|
}
|
|
else
|
|
{
|
|
Dispatcher.Invoke(() => rtbLog.FontSize -= 1);
|
|
}
|
|
}
|
|
}
|
|
#endregion ---------------------------------------- Buttons and Controls --------------------------------------
|
|
}
|
|
} |