using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.Composition; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using System.Windows.Data; using DTS.Common.Events; using DTS.Common.Interface.TestSetups.Imports.TTS; using DTS.Common.Interface.TestSetups.Imports.TTS.ReadFile; using Prism.Events; using Unity; using DTS.Common.Interactivity; using Prism.Regions; using Prism.Commands; using TTSImport.Model; using TTSImport.Resources; using Application = System.Windows.Application; namespace TTSImport { /// /// this class handles Level Trigger edit/create functionality /// [PartCreationPolicy(CreationPolicy.Shared)] public class EditFileViewModel : IEditFileViewModel { /// /// The Hardware Scan view /// public IEditFileView View { get; set; } private const string DOUBLEUPTO15 = "0.###############"; //Write up to 15 decimal places to .csv private const string STRINGWRITEFORMAT_CHANNELRANGE = DOUBLEUPTO15; private const string STRINGWRITEFORMAT_SENSITIVITY = DOUBLEUPTO15; private const string STRINGWRITEFORMAT_CAPACITY = DOUBLEUPTO15; private IEventAggregator _eventAggregator { get; } private IRegionManager _regionManager; private IUnityContainer UnityContainer { get; } public InteractionRequest NotificationRequest { get; } public InteractionRequest ConfirmationRequest { get; } /// ///Occurs when a property value changes. /// public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); switch (propertyName) { case "TestName": ChangeValidationIsNeeded = true; break; } } #region constructors and initializers /// /// Creates a new instance of the TechnologyDomainEditViewModel. /// /// /// The logical placeholder defined within the application's UI (in the shell or within views) into which views are displayed. /// The EventAggregator which allows different components to publish/subscribe to events without being coupled to each other. /// The unityContainer. public EditFileViewModel(EditFileView view, IRegionManager regionManager, IEventAggregator eventAggregator, IUnityContainer unityContainer) { View = view; View.DataContext = this; NotificationRequest = new InteractionRequest(); ConfirmationRequest = new InteractionRequest(); _eventAggregator = eventAggregator; UnityContainer = unityContainer; _regionManager = regionManager; _eventAggregator.GetEvent().Subscribe(OnRaiseNotification); _eventAggregator.GetEvent().Subscribe(OnBusyIndicatorNotification, ThreadOption.PublisherThread, true); _eventAggregator.GetEvent().Subscribe(OnReadFileFinished, ThreadOption.PublisherThread, true); } #endregion #region Methods /// /// filters the available sensors from the db by the given text /// we keep two lists, allchannels and SystemSensors /// SystemSensors only holds those that aren't in use and are /// available /// /// public void Search(string text) { SystemSensors.Clear(); //build a list of sensors already used to exclude those sensors var hash = new HashSet(); foreach (var ch in RequiredChannels) { hash.Add(ch.SensorSerialNumber); } if (string.IsNullOrEmpty(text)) { //nothing to sort foreach (var s in _allChannels) { if (!hash.Contains(s.SensorSerialNumber)) { SystemSensors.Add(s); } } } else { text = text.ToLower(); foreach (var s in _allChannels) { if (s.SensorSerialNumber.ToLower().Contains(text)) { if (!hash.Contains(s.SensorSerialNumber)) { SystemSensors.Add(s); } } } } } private void OnReadFileFinished(ReadFileStatusArg statusArg) { if (!Application.Current.Dispatcher.CheckAccess()) { Application.Current.Dispatcher.BeginInvoke(new Action(() => { OnReadFileFinished(statusArg); })); return; } if (!statusArg.Status) return; _setup = statusArg.TTSSetup; } public void InitializeView() { if (_setup == null) return; //turn off Change Validation and remove old channels/sensors, if any ChangeValidationIsNeeded = false; RequiredChannels.Clear(); SystemSensors.Clear(); //will turn Change Validation back on TestName = _setup.TestId; SaveFileEnabled = false; var requiredChannels = new ObservableCollection(); _allChannels.Clear(); foreach (var channelRecord in _setup.Channels) { if (channelRecord.IsDigitalOutput) { continue; } if (channelRecord.ChannelCode != TTSChannelRecord.NONE) { //isn't a "reserved sensor" var requiredChannel = channelRecord.Copy(); requiredChannel.Parent = this; requiredChannels.Add(requiredChannel); } } foreach (var sensor in DTS.SensorDB.SensorsCollection.SensorsList.GetAllSensors(false)) { if (sensor.IsDigitalOutput()) { continue; } if (sensor.IsTestSpecificSquib) { continue; } if (sensor.IsTestSpecificDigitalIn) { continue; } _allChannels.Add(new TTSChannelRecord(sensor) { Parent = this }); } RequiredChannels = requiredChannels; Search(_searchText); ValidateChannelCodes(); ValidateJCodes(); _originalHash = GenerateHash(); NumChannelsAndSensors = string.Format(StringResources.NumChannelsAndSensors, RequiredChannels.Count, SystemSensors.Count); SaveFileEnabled = false; } private string GenerateHash() { var bytes = new List(); foreach (var rc in RequiredChannels) { bytes.AddRange(rc.GetBytes()); } foreach (var rs in SystemSensors) { bytes.AddRange(rs.GetBytes()); } var sha = new SHA256Managed(); var hash = sha.ComputeHash(bytes.ToArray()); return BitConverter.ToString(hash).Replace("-", string.Empty); } public void Cleanup() { } public Task CleanupAsync() { return Task.CompletedTask; } public void Initialize() { } public void Initialize(object parameter) { } public void Initialize(object parameter, object model) { } public Task InitializeAsync() { return Task.CompletedTask; } public Task InitializeAsync(object parameter) { return Task.CompletedTask; } public void Activated() { } /// /// Private Event handler for RaiseNotification event. /// private void OnBusyIndicatorNotification(bool eventArg) { IsBusy = eventArg; } /// /// Private Event handler for RaiseNotification event. /// private void OnRaiseNotification(NotificationContentEventArgs eventArgsWithTitle) { // The NotificationRequest.Raise triggers the Invoke() method of the PopupWindowAction object to show the NotificationWindow window // Notification object expects a NotificationContentEventArgsWithoutTitle object and a Title string. var eventArgsWithoutTitle = new NotificationContentEventArgs(eventArgsWithTitle.Message, "", eventArgsWithTitle.Image, string.Empty); NotificationRequest.Raise(new Notification { Content = eventArgsWithoutTitle, Title = eventArgsWithTitle.Title }); } #endregion #region Properties /// /// this is the list of all channels, this is a superset while SystemSensors is the subset /// this contains some records we don't want /// private readonly List _allChannels = new List(); /// /// this is the current text the user is search for in available channels/sensors /// private readonly string _searchText = ""; private ITTSSetup _setup; public bool ChangeValidationIsNeeded { get; set; } public bool IsDirty { get; private set; } private bool _isBusy; public bool IsBusy { get => _isBusy; set { _isBusy = value; OnPropertyChanged("IsBusy"); } } private bool _isMenuIncluded; public bool IsMenuIncluded { get => _isMenuIncluded; set { _isMenuIncluded = value; OnPropertyChanged("IsMenuIncluded"); } } private bool _isNavigationIncluded; public bool IsNavigationIncluded { get => _isNavigationIncluded; set { _isNavigationIncluded = value; OnPropertyChanged("IsNavigationIncluded"); } } private bool _isTestNameValid; public bool IsTestNameValid { get => _isTestNameValid; set { _isTestNameValid = value; OnPropertyChanged("IsTestNameValid"); } } private string _testName = string.Empty; public string TestName { get => _testName; set { _testName = value; IsTestNameValid = !string.IsNullOrWhiteSpace(value); OnPropertyChanged("TestName"); } } private DelegateCommand _testNameLostFocus; public DelegateCommand TestNameLostFocus => _testNameLostFocus ?? (_testNameLostFocus = new DelegateCommand(TestNameLostFocusMethod)); public void TestNameLostFocusMethod(string code) { if (!ChangeValidationIsNeeded) return; ValidateChange(); } private bool _saveFileEnabled; public bool SaveFileEnabled { get => _saveFileEnabled; set { _saveFileEnabled = value; OnPropertyChanged("SaveFileEnabled"); EnableOrDisableButtons(); } } private string _numChannelsAndSensors = string.Empty; public string NumChannelsAndSensors { get => _numChannelsAndSensors; set { _numChannelsAndSensors = value; OnPropertyChanged("NumChannelsAndSensors"); } } private bool _replaceEnabled; public bool ReplaceEnabled { get => _replaceEnabled; set { _replaceEnabled = value; OnPropertyChanged("ReplaceEnabled"); } } private bool _addCodeEnabled; public bool AddCodeEnabled { get => _addCodeEnabled; set { _addCodeEnabled = value; OnPropertyChanged("AddCodeEnabled"); } } private bool _deleteCodeEnabled; public bool DeleteCodeEnabled { get => _deleteCodeEnabled; set { _deleteCodeEnabled = value; OnPropertyChanged("DeleteCodeEnabled"); } } /// /// stores a hash representing the state of channels at the time they were read in /// this is used to determine if any changes of significance were made in edit file /// private string _originalHash; private ObservableCollection _requiredChannels = new ObservableCollection(); public ObservableCollection RequiredChannels { get => _requiredChannels; set { _requiredChannels = value; OnPropertyChanged("RequiredChannels"); } } private ObservableCollection _systemSensors = new ObservableCollection(); public ObservableCollection SystemSensors { get => _systemSensors; set { _systemSensors = value; OnPropertyChanged("SystemSensors"); } } private ITTSChannelRecord _selectedRequiredChannel; public ITTSChannelRecord SelectedRequiredChannel { get => _selectedRequiredChannel; set { _selectedRequiredChannel = value; OnPropertyChanged("SelectedRequiredChannel"); EnableOrDisableButtons(); } } private ITTSChannelRecord _selectedSystemSensor; public ITTSChannelRecord SelectedSystemSensor { get => _selectedSystemSensor; set { _selectedSystemSensor = value; OnPropertyChanged("SelectedSystemSensor"); EnableOrDisableButtons(); } } private void EnableOrDisableButtons() { if (null == _selectedRequiredChannel || null == _selectedSystemSensor) { ReplaceEnabled = false; } else if (_selectedRequiredChannel.IsDigitalInput) { ReplaceEnabled = _selectedSystemSensor.IsDigitalInput; } else if (_selectedRequiredChannel.IsDigitalOutput) { ReplaceEnabled = _selectedSystemSensor.IsDigitalOutput; } else if (_selectedRequiredChannel.IsSquib) { ReplaceEnabled = _selectedSystemSensor.IsSquib; } else { ReplaceEnabled = !(_selectedSystemSensor.IsSquib || _selectedSystemSensor.IsDigitalInput || _selectedSystemSensor.IsDigitalOutput); } AddCodeEnabled = _selectedSystemSensor != null; DeleteCodeEnabled = _selectedRequiredChannel != null; } #endregion Properties #region Commands /// /// Browse to import file /// private DelegateCommand _saveFileClicked; public DelegateCommand SaveFileClicked => _saveFileClicked ?? (_saveFileClicked = new DelegateCommand(SaveFileMethod)); public void SaveFileMethod() { using (var sfd = new System.Windows.Forms.SaveFileDialog()) { var fullFilePath = Path.GetFullPath(_setup.OriginalImportFile); sfd.Filter = @"TTS import (*.csv)|*.csv"; sfd.RestoreDirectory = true; sfd.FilterIndex = 0; var fi = new FileInfo(fullFilePath); sfd.FileName = fi.Name; if (fi.DirectoryName != null) sfd.InitialDirectory = Path.GetFullPath(fi.DirectoryName); var result = sfd.ShowDialog(); if (result != System.Windows.Forms.DialogResult.OK) return; fullFilePath = sfd.FileName; var csv = new StringBuilder(); var newRow = _setup.Line1; csv.Append(newRow + Environment.NewLine); newRow = _setup.Line2; _setup.TestId = TestName; if (_setup.Line2 != null) { var fields = _setup.Line2.Split(','); var newLine2 = fields.Where((t, index) => index > 0).Aggregate(_setup.TestId, (current, t) => current + "," + t); newRow = newLine2; } csv.Append(newRow + Environment.NewLine); newRow = _setup.Line3; csv.Append(newRow + Environment.NewLine); newRow = _setup.Line4; csv.Append(newRow + Environment.NewLine); //Order the required channels first var channelList = RequiredChannels.ToList(); _setup.Channels = channelList.ToArray(); foreach (var channel in _setup.Channels) { var channelNumber = channel.ChannelNumber.ToString(); var channelCode = channel.ChannelCode; var channelRange = channel.IsSquib ? channel.SquibFireDelayMs.ToString(STRINGWRITEFORMAT_CHANNELRANGE) : channel.ChannelRange.ToString(STRINGWRITEFORMAT_CHANNELRANGE); //Write up to 15 decimal places to .csv var channelFilter = channel.IsSquib ? channel.SquibFireDurationMs.ToString(STRINGWRITEFORMAT_CHANNELRANGE) : channel.ChannelFilterHz.ToString(System.Globalization.CultureInfo.InvariantCulture); if (channel.ChannelCode == TTSChannelRecord.NONE) { //Blank the following fields from reserved sensors channelNumber = ""; channelCode = ""; channelRange = ""; channelFilter = ""; } var sensorSensitivity = channel.SensorSensitivity.ToString(STRINGWRITEFORMAT_SENSITIVITY); //Write up to 15 decimal places to .csv var sensorExcitationVolts = channel.SensorExcitationVolts.ToString("0.#"); var sensorCapacity = channel.SensorCapacity.ToString(STRINGWRITEFORMAT_CAPACITY); //Write up to 15 decimal places to .csv var sensorPolarity = channel.SensorPolarity ? "+" : "-"; //Only FullBridge and HalfBridge contain a space string channelType; switch (channel.ChannelType) { case DTS.Common.Enums.TTS.ToyotaBridgeType.FullBridge: channelType = "Full Bridge"; break; case DTS.Common.Enums.TTS.ToyotaBridgeType.HalfBridge: channelType = "Half Bridge"; break; default: channelType = channel.ChannelType.ToString(); break; } var proportionalToExcitation = channel.ProportionalToExcitation ? "mv/V/EU" : ""; var bridgeResistance = channel.BridgeResistance.ToString("0.#"); var initialOffsetVoltage = channel.InitialOffsetVoltage.ToString("0.#"); var initialOffsetVoltageTolerance = channel.InitialOffsetVoltageTolerance.ToString("0.#"); var removeOffset = channel.RemoveOffset ? "1" : "0"; var zeroMethod = ((int)channel.ZeroMethod).ToString(); var initialEUInMv = double.IsNaN(channel.InitialEUInMV) ? "" : channel.InitialEUInMV.ToString("0.##"); var initialEUInEU = double.IsNaN(channel.InitialEUInEU) ? "" : channel.InitialEUInEU.ToString("0.##"); var iRTraccExponent = double.IsNaN(channel.IRTraccExponent) ? "" : channel.IRTraccExponent.ToString("0.##"); var polynomialConstant = double.IsNaN(channel.PolynomialConstant) ? "" : channel.PolynomialConstant.ToString("0.##"); var polynomialCoefficientC = double.IsNaN(channel.PolynomialCoefficientC) ? "" : channel.PolynomialCoefficientC.ToString("0.##"); var polynomialCoefficentB = double.IsNaN(channel.PolynomialCoefficentB) ? "" : channel.PolynomialCoefficentB.ToString("0.##"); var polynomialCoefficientA = double.IsNaN(channel.PolynomialCoefficientA) ? "" : channel.PolynomialCoefficientA.ToString("0.##"); var polynomialCoefficientAlpha = double.IsNaN(channel.PolynomialCoefficientAlpha) ? "" : channel.PolynomialCoefficientAlpha.ToString("0.##"); var diagnosticsMode = channel.DiagnosticsMode ? TTSChannelRecord.DIAGNOSTICSMODE : ""; if (channel.IsDigitalInput) { channelRange = string.Empty; channelFilter = string.Empty; } if (channel.IsDigitalOutput) { //14708 Squib appears to be unassigned when using edit file during TTS import //I changed this to be consistent with the reader, which only reads the first 3 //fields for digital outputs newRow = $"{channelNumber},{channelCode},{channel.JCodeOrDescription}{Environment.NewLine}"; } if (channel.IsDigitalInput || channel.IsSquib) { //14708 Squib appears to be unassigned when using edit file during TTS import //don't wipe out EID or serial number, these are valid fields for these sensors //isocode and description should be valid too? as well as channel description? //note that the import reads up to the serial number, so no point it writing more newRow = $"{channelNumber},{channelCode},{channel.JCodeOrDescription},{channelRange},{channelFilter},{channel.SensorEID},{channel.SensorSerialNumber}{Environment.NewLine}"; } else { newRow = $"{channelNumber},{channelCode},{channel.JCodeOrDescription},{channelRange},{channelFilter},{channel.SensorEID},{channel.SensorSerialNumber},{sensorSensitivity},{sensorExcitationVolts},{sensorCapacity},{channel.SensorEU}," + $"{sensorPolarity},{channelType},{channel.Description},{proportionalToExcitation},{bridgeResistance},{initialOffsetVoltage},{initialOffsetVoltageTolerance},{removeOffset},{zeroMethod},{channel.CableMultiplier.ToString(System.Globalization.CultureInfo.InvariantCulture)}," + $"{initialEUInMv},{initialEUInEU},{iRTraccExponent},{polynomialConstant},{polynomialCoefficientC},{polynomialCoefficentB},{polynomialCoefficientA},{polynomialCoefficientAlpha},{channel.ISOCode},{channel.ISODescription},{channel.ISOPolarity},{diagnosticsMode}{Environment.NewLine}"; } csv.Append(newRow); } File.WriteAllText(fullFilePath, csv.ToString(), Encoding.GetEncoding("Shift-JIS")); _setup.OriginalImportFile = fullFilePath; } SaveFileEnabled = false; //Enable future nav steps since changes have been saved _eventAggregator.GetEvent().Publish(true); _originalHash = GenerateHash(); } #region Replace /// /// Swap the assignments of a Required Channel and a Reserved Sensor /// private DelegateCommand _replaceCommand; public DelegateCommand ReplaceCommand => _replaceCommand ?? (_replaceCommand = new DelegateCommand(Replace)); private void Replace() { //11574 replace button removed JCode and range and filter from an "add code" channel in edit file //we preserve the original channel settings when doing replace //Initialize a Reserved Sensor that's about to become a Required Channel SelectedSystemSensor.ChannelNumber = SelectedRequiredChannel.ChannelNumber; SelectedSystemSensor.ChannelCode = SelectedRequiredChannel.ChannelCode; SelectedRequiredChannel.ChannelCode = TTSChannelRecord.NONE; SelectedSystemSensor.JCodeOrDescription = SelectedRequiredChannel.JCodeOrDescription; SelectedSystemSensor.ChannelRange = SelectedRequiredChannel.ChannelRange; SelectedSystemSensor.ChannelFilterHz = SelectedRequiredChannel.ChannelFilterHz; // Save off the channels that are about to be removed from their old lists var savedSelectedSystemSensor = SelectedSystemSensor; var savedSelectedRequiredChannel = SelectedRequiredChannel; //Save the indices of the channels that are about to be removed from their old lists var selectedRequiredChannelIndex = RequiredChannels.IndexOf(SelectedRequiredChannel); var selectedSystemSensorIndex = SystemSensors.IndexOf(SelectedSystemSensor); //Remove the channels from their old lists RequiredChannels.Remove(SelectedRequiredChannel); SystemSensors.Remove(SelectedSystemSensor); //Insert the saved channels into their new lists RequiredChannels.Insert(selectedRequiredChannelIndex, savedSelectedSystemSensor); SystemSensors.Insert(selectedSystemSensorIndex, savedSelectedRequiredChannel); //Re-select the same rows in the tables SelectedRequiredChannel = RequiredChannels[selectedRequiredChannelIndex]; SelectedSystemSensor = SystemSensors[selectedSystemSensorIndex]; UpdateAndValidate(); } #endregion Replace #region Add Code private DelegateCommand _addCodeCommand; public DelegateCommand AddCodeCommand => _addCodeCommand ?? (_addCodeCommand = new DelegateCommand(AddCode)); /// /// remove a hardware channel assignment(does not remove the channel from the test setup though?) /// private void AddCode() { AddSystemSensor(); UpdateAndValidate(); } private void AddSystemSensor() { var maxChannelNumber = RequiredChannels.Select(requiredChannel => requiredChannel.ChannelNumber).Concat(new[] { 0 }).Max(); SelectedSystemSensor.ChannelNumber = maxChannelNumber + 1; SelectedSystemSensor.ChannelCode = string.Empty; SelectedSystemSensor.JCodeOrDescription = string.Empty; SelectedSystemSensor.ChannelRange = 0; SelectedSystemSensor.ChannelFilterHz = -1; RequiredChannels.Add(SelectedSystemSensor); var selectedSystemSensorIndex = SystemSensors.IndexOf(SelectedSystemSensor); SystemSensors.Remove(SelectedSystemSensor); if (SystemSensors.Count > 0) { //If removing from the end of the list, set the previous record to Selected, otherwise set next record to Selected SelectedSystemSensor = selectedSystemSensorIndex == SystemSensors.Count ? SystemSensors[selectedSystemSensorIndex - 1] : SystemSensors[selectedSystemSensorIndex]; } } #endregion Add Code private DelegateCommand _addSquibCommand; public DelegateCommand AddSquibCommand => _addSquibCommand ?? (_addSquibCommand = new DelegateCommand(AddSquib)); private void AddSquib() { var maxChannelNumber = RequiredChannels.Select(requiredChannel => requiredChannel.ChannelNumber).Concat(new[] { 0 }).Max(); var channelRecord = new TTSChannelRecord(); channelRecord.ChannelNumber = ++maxChannelNumber; channelRecord.IsSquib = true; int maxSquibNumber = 0; foreach (var rc in RequiredChannels) { if (!rc.IsSquib) continue; var channelCode = rc.ChannelCode.Replace("TF", "").Replace("SQ", ""); if (int.TryParse(channelCode, out var temp)) { maxSquibNumber = Math.Max(temp, maxSquibNumber); } } maxSquibNumber++; channelRecord.ChannelCode = $"SQ{maxSquibNumber}"; channelRecord.JCodeOrDescription = string.Empty; channelRecord.LimitDuration = true; channelRecord.ChannelRange = (int)channelRecord.SquibFireDelayMs; channelRecord.SquibFireDurationMs = _setup.DefaultSquibFireDurationMs; channelRecord.ChannelFilterHz = (int)channelRecord.SquibFireDurationMs; //-1; channelRecord.Parent = this; RequiredChannels.Add(channelRecord); Validate(); } private DelegateCommand _addDICommand; public DelegateCommand ADDDICommand => _addDICommand ?? (_addDICommand = new DelegateCommand(AddDI)); private void AddDI() { var maxChannelNumber = RequiredChannels.Select(requiredChannel => requiredChannel.ChannelNumber).Concat(new[] { 0 }).Max(); var channelRecord = new TTSChannelRecord(); channelRecord.ChannelNumber = ++maxChannelNumber; channelRecord.IsDigitalInput = true; int maxDINumber = 0; foreach (var rc in RequiredChannels) { if (!rc.IsDigitalInput) continue; var channelCode = rc.ChannelCode.Replace("DI", ""); if (int.TryParse(channelCode, out var temp)) { maxDINumber = Math.Max(temp, maxDINumber); } } maxDINumber++; channelRecord.ChannelCode = $"DI{maxDINumber}"; channelRecord.JCodeOrDescription = string.Empty; channelRecord.ChannelFilterHz = -1; channelRecord.Parent = this; channelRecord.IsChannelCodeValid = true; RequiredChannels.Add(channelRecord); } #region Delete Code private DelegateCommand _deleteCodeCommand; public DelegateCommand DeleteCodeCommand => _deleteCodeCommand ?? (_deleteCodeCommand = new DelegateCommand(DeleteCode)); /// /// enables or disables a channel in the test. /// private void DeleteCode() { DeleteRequiredChannel(); UpdateAndValidate(); } private void DeleteRequiredChannel() { if (null == SelectedRequiredChannel) { return; } SelectedRequiredChannel.ChannelCode = TTSChannelRecord.NONE; //So that when it's saved, these fields will be set to empty strings, but if it's re-Added, force user to enter a value. SelectedRequiredChannel.JCodeOrDescription = string.Empty; //In case it's re-Added, force user to enter a value. SelectedRequiredChannel.ChannelRange = 0; //In case it's re-Added, force user to enter a value. SelectedRequiredChannel.ChannelFilterHz = -1; //In case it's re-Added, force user to choose a filter or "None". SelectedRequiredChannel.HardwareChannel = null; SystemSensors.Add(SelectedRequiredChannel); var selectedRequiredChannelIndex = RequiredChannels.IndexOf(SelectedRequiredChannel); RequiredChannels.Remove(SelectedRequiredChannel); if (RequiredChannels.Count > 0) { //Ensure the channels don't have any gaps foreach (var channel in RequiredChannels) { channel.ChannelNumber = RequiredChannels.IndexOf(channel) + 1; } //If removing from the end of the list, set the previous record to Selected, otherwise set next record to Selected SelectedRequiredChannel = selectedRequiredChannelIndex == RequiredChannels.Count ? RequiredChannels[selectedRequiredChannelIndex - 1] : RequiredChannels[selectedRequiredChannelIndex]; } } #endregion Delete Code private void UpdateAndValidate() { NumChannelsAndSensors = string.Format(StringResources.NumChannelsAndSensors, RequiredChannels.Count, SystemSensors.Count); EnableOrDisableButtons(); ValidateChange(); CollectionViewSource.GetDefaultView(RequiredChannels)?.Refresh(); } #endregion Commands /// /// Returns True if all fields are valid, False if not /// /// public bool Validate() { var duplicates = RequiredChannels.GroupBy(x => x.ChannelCode).Any(g => g.Count() > 1); return !duplicates && IsTestNameValid && RequiredChannels.All(x => x.IsChannelCodeValid /*&& x.IsJCodeValid*/ && (x.IsRangeValid || x.RangeVisible != System.Windows.Visibility.Visible) && (x.IsFilterValid || x.FilterVisible != System.Windows.Visibility.Visible)); } /// /// returns true if there are any changes that have not been saved yet /// /// private bool HasUnsavedChanges() { var hash = GenerateHash(); return hash != _originalHash; } /// /// If all fields are valid after a change, enables the Save File button and returns True. /// If not all fields are valid after a change, disables the Save File button and returns False. /// Disables all future nav steps (they will be enabled when the Save File button is clicked). /// /// public bool ValidateChange(ITTSChannelRecord record = null) { //Enable the Save File button if everything is valid ValidateChannelCodes(); ValidateJCodes(); bool bValid = Validate(); SaveFileEnabled = bValid && HasUnsavedChanges(); //Prevent calls to Validate if losing focus on an unchanged control, for example ChangeValidationIsNeeded = false; //Disable future nav steps since changes have been made but not saved, or are just or are just invalid _eventAggregator.GetEvent().Publish(!SaveFileEnabled); return SaveFileEnabled; } private void ValidateChannelCodes() { var channelCodeToChannel = new Dictionary(); foreach (var channel in RequiredChannels) { if (string.IsNullOrWhiteSpace(channel.ChannelCode)) { channel.IsChannelCodeValid = false; } else if (channel.ChannelCode == TTSChannelRecord.NONE) { channel.IsChannelCodeValid = false; } else if (channelCodeToChannel.ContainsKey(channel.ChannelCode)) { channelCodeToChannel[channel.ChannelCode].IsChannelCodeValid = false; channel.IsChannelCodeValid = false; } else { channelCodeToChannel[channel.ChannelCode] = channel; channel.IsChannelCodeValid = true; } } } private void ValidateJCodes() { var channelJCodeToChannel = new Dictionary(); foreach (var channel in RequiredChannels) { if (string.IsNullOrWhiteSpace(channel.JCodeOrDescription)) { channel.IsJCodeValid = false; } //else if (channelJCodeToChannel.ContainsKey(channel.JCodeOrDescription)) //{ // channelJCodeToChannel[channel.JCodeOrDescription].IsJCodeValid = false; // channel.IsJCodeValid = false; //} else { channelJCodeToChannel[channel.JCodeOrDescription] = channel; channel.IsJCodeValid = true; } } } } }