//******************************************************************************************************
// AverageFrequencyViewModel.cs - Gbtc
//
// Copyright © 2010, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 08/20/2012 - Stephen C. Wills
// Generated original version of source code.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using GSF.Data;
using GSF.PhasorProtocols.UI.DataModels;
using GSF.PhasorProtocols.UI.UserControls;
using GSF.TimeSeries.UI;
using GSF.TimeSeries.UI.Commands;
using GSF.TimeSeries.UI.DataModels;
using Measurement = GSF.TimeSeries.UI.DataModels.Measurement;
namespace GSF.PhasorProtocols.UI.ViewModels
{
///
/// View model for the .
///
public class AverageFrequencyViewModel : DependencyObject, INotifyPropertyChanged
{
#region [ Members ]
// Nested Types
///
/// Represents a mapping between a frequency input definition
/// and an average frequency output definition.
///
public class IOMapping
{
private string m_signalReference;
private string m_inputKey;
private string m_outputKey;
///
/// Gets or sets the signal reference of the frequency input.
///
public string SignalReference
{
get
{
return m_signalReference;
}
set
{
m_signalReference = value;
}
}
///
/// Gets or sets the measurement key of the frequency input.
///
public string InputKey
{
get
{
return m_inputKey;
}
set
{
m_inputKey = value;
}
}
///
/// Gets or sets the measurement key of the frequency output.
///
public string OutputKey
{
get
{
return m_outputKey;
}
set
{
m_outputKey = value;
}
}
}
///
/// Represents a frequency measurement which can be selected
/// as an input to the average frequency calculator.
///
public class FrequencyMeasurement
{
private string m_signalReference;
private string m_key;
///
/// Gets or sets the signal reference of the frequency measurement.
///
public string SignalReference
{
get
{
return m_signalReference;
}
set
{
m_signalReference = value;
}
}
///
/// Gets or sets the measurement key of the frequency measurement.
///
public string Key
{
get
{
return m_key;
}
set
{
m_key = value;
}
}
}
// Data model for the average frequency user interface.
private class DataModel
{
public readonly List Adapters;
public readonly List Devices;
public readonly List Measurements;
public DataModel()
{
using (AdoDataConnection database = new AdoDataConnection(CommonFunctions.DefaultSettingsCategory))
{
Adapters = Adapter.Load(database, AdapterType.Action, Adapter.LoadIDs(database, AdapterType.Action, "", "")).ToList();
Devices = Device.Load(database, Device.LoadKeys(database)).ToList();
Measurements = Measurement.GetMeasurements(database, "WHERE SignalAcronym = 'FREQ'").ToList();
}
}
}
// Events
///
/// Defines an event for when a property has changed in the .
///
public event PropertyChangedEventHandler PropertyChanged;
// Fields
private ObservableCollection m_calculatorNames;
private ObservableCollection m_virtualDeviceNames;
private ObservableCollection m_ioMappings;
private ObservableCollection m_frequencyMeasurements;
private string m_selectedCalculatorName;
private string m_selectedVirtualDeviceName;
private string m_oldVirtualDeviceName;
private IOMapping m_selectedIOMapping;
private FrequencyMeasurement m_selectedFrequencyMeasurement;
private readonly RelayCommand m_addSelectedCommand;
private readonly RelayCommand m_removeSelectedCommand;
private DataModel m_dataModel;
private bool m_ignorePropertyNotify;
#endregion
#region [ Constructors ]
///
/// Creates a new instance of the class.
///
public AverageFrequencyViewModel()
{
m_addSelectedCommand = new RelayCommand(AddSelected);
m_removeSelectedCommand = new RelayCommand(RemoveSelected);
PropertyChanged += (sender, args) => HandlePropertyChanged(args.PropertyName);
}
#endregion
#region [ Properties ]
///
/// Gets or sets calculator names for the .
///
public ObservableCollection CalculatorNames
{
get
{
return m_calculatorNames;
}
set
{
m_calculatorNames = value;
OnPropertyChanged("CalculatorNames");
}
}
///
/// Gets or sets the virtual device names for the .
///
public ObservableCollection VirtualDeviceNames
{
get
{
return m_virtualDeviceNames;
}
set
{
m_virtualDeviceNames = value;
OnPropertyChanged("VirtualDeviceNames");
}
}
///
/// Gets or sets the Input/Output mappings for the .
///
public ObservableCollection IOMappings
{
get
{
return m_ioMappings;
}
set
{
m_ioMappings = value;
OnPropertyChanged("IOMappings");
}
}
///
/// Gets or sets the frequency measurements for the .
///
public ObservableCollection FrequencyMeasurements
{
get
{
return m_frequencyMeasurements;
}
set
{
m_frequencyMeasurements = value;
OnPropertyChanged("FrequencyMeasurements");
}
}
///
/// Gets or sets the selected calculator name for the .
///
public string SelectedCalculatorName
{
get
{
return m_selectedCalculatorName;
}
set
{
m_selectedCalculatorName = value;
OnPropertyChanged("SelectedCalculatorName");
}
}
///
/// Gets or sets the selected virtual device name for the .
///
public string SelectedVirtualDeviceName
{
get
{
return m_selectedVirtualDeviceName;
}
set
{
m_oldVirtualDeviceName = m_selectedVirtualDeviceName;
m_selectedVirtualDeviceName = value;
OnPropertyChanged("SelectedVirtualDeviceName");
}
}
///
/// Gets or sets the selected Input/Output mapping for the .
///
public IOMapping SelectedIOMapping
{
get
{
return m_selectedIOMapping;
}
set
{
m_selectedIOMapping = value;
OnPropertyChanged("SelectedIOMapping");
}
}
///
/// Gets or sets the selected frequency measurement for the .
///
public FrequencyMeasurement SelectedFrequencyMeasurement
{
get
{
return m_selectedFrequencyMeasurement;
}
set
{
m_selectedFrequencyMeasurement = value;
OnPropertyChanged("SelectedFrequencyMeasurement");
}
}
///
/// Gets the add selected relay command for the .
///
public RelayCommand AddSelectedCommand
{
get
{
return m_addSelectedCommand;
}
}
///
/// Gets the remove selected relay command for the .
///
public RelayCommand RemoveSelectedCommand
{
get
{
return m_removeSelectedCommand;
}
}
#endregion
#region [ Methods ]
///
/// Called when the view is loaded.
///
public virtual void Load()
{
IEnumerable calculatorNames;
IEnumerable virtualDeviceNames;
try
{
m_dataModel = new DataModel();
calculatorNames = m_dataModel.Adapters
.Where(adapter => adapter.AssemblyName == "OneSecondFrequencyAverager.dll")
.Where(adapter => adapter.TypeName == "OneSecondFrequencyAverager.OneSecondFrequencyAverager")
.Select(adapter => adapter.AdapterName);
virtualDeviceNames = m_dataModel.Devices
.Where(device => device.ProtocolName == "Virtual Device")
.Select(device => device.Acronym);
CalculatorNames = new ObservableCollection(calculatorNames);
VirtualDeviceNames = new ObservableCollection(virtualDeviceNames);
SelectCalculatorName();
}
catch (Exception ex)
{
MessageBox.Show("ERROR - Unable to load measurements from database due to exception: " + ex.Message, "Load", MessageBoxButton.OK);
}
}
///
/// Called when the view is unloaded.
///
public virtual void Unload()
{
const string calculatorNameKey = "AverageFrequency.CalculatorName";
string virtualDeviceNameKey = string.Format("AverageFrequency.{0}.VirtualDeviceName", m_selectedCalculatorName);
if (!string.IsNullOrEmpty(m_selectedCalculatorName))
IsolatedStorageManager.WriteToIsolatedStorage(calculatorNameKey, m_selectedCalculatorName);
if (!string.IsNullOrEmpty(m_selectedVirtualDeviceName))
IsolatedStorageManager.WriteToIsolatedStorage(virtualDeviceNameKey, m_selectedVirtualDeviceName);
}
///
/// Handles change in a property once per call stack.
///
/// The name of the property that was changed.
protected virtual void HandlePropertyChangedOnce(string propertyName)
{
switch (propertyName)
{
case "SelectedCalculatorName":
SelectVirtualDeviceName();
UpdateIOMappings();
UpdateFrequencyMeasurements();
break;
case "SelectedVirtualDeviceName":
// Check to see if a change was made to the value of the property
if (m_selectedVirtualDeviceName == m_oldVirtualDeviceName)
return;
// Ask the user to confirm their selection
if (ConfirmVirtualDeviceSelection())
{
UpdateVirtualDevice();
}
else
{
// User chose not to confirm; restore the old name and notify the view
Dispatcher.BeginInvoke(new Action(() =>
{
m_selectedVirtualDeviceName = m_oldVirtualDeviceName;
OnPropertyChanged("SelectedVirtualDeviceName");
}));
}
break;
}
}
// Calls HandlePropertyChangedOnce in such a way that prevents infinite recursion when modifying other properties.
private void HandlePropertyChanged(string propertyName)
{
if (m_ignorePropertyNotify)
return;
m_ignorePropertyNotify = true;
HandlePropertyChangedOnce(propertyName);
m_ignorePropertyNotify = false;
}
#region [ User adds a frequency ]
// Adds the selected measurement to the average calculator.
private void AddSelected()
{
if (string.IsNullOrEmpty(m_selectedCalculatorName))
return;
if ((object)m_selectedFrequencyMeasurement == null)
return;
CreateOutputMeasurement();
AddNewMapping();
Load();
}
// Creates a measurement to define the average frequency signal.
private void CreateOutputMeasurement()
{
Device virtualDevice = null;
Measurement inputMeasurement;
Measurement outputMeasurement;
if (!string.IsNullOrEmpty(m_selectedVirtualDeviceName))
{
virtualDevice = m_dataModel.Devices
.Single(dev => dev.Acronym == m_selectedVirtualDeviceName);
}
inputMeasurement = m_dataModel.Measurements
.Single(measurement => measurement.ID == m_selectedFrequencyMeasurement.Key);
outputMeasurement = new Measurement
{
HistorianID = inputMeasurement.HistorianID,
DeviceID = ((object)virtualDevice != null) ? virtualDevice.ID : (int?)null,
PointTag = string.Format("{0}_{1}", m_selectedCalculatorName, inputMeasurement.PointTag),
SignalTypeID = inputMeasurement.SignalTypeID,
SignalReference = string.Format("{0}-FQ", m_selectedVirtualDeviceName ?? m_selectedCalculatorName),
Description = string.Format("Average {0}", inputMeasurement.Description),
Internal = true,
Subscribed = false,
Enabled = true
};
Measurement.Save(null, outputMeasurement);
}
// Adds a new mapping between the input and output measurements to the connection string.
private void AddNewMapping()
{
Adapter calculator;
Dictionary settings;
string inputMeasurementKeys;
string outputMeasurements;
calculator = m_dataModel.Adapters
.Single(adapter => adapter.AdapterName == m_selectedCalculatorName);
settings = calculator.ConnectionString.ToNonNullString().ParseKeyValuePairs();
if (!settings.TryGetValue("inputMeasurementKeys", out inputMeasurementKeys))
inputMeasurementKeys = string.Empty;
if (!settings.TryGetValue("outputMeasurements", out outputMeasurements))
outputMeasurements = string.Empty;
inputMeasurementKeys += ";" + m_selectedFrequencyMeasurement.Key;
outputMeasurements += ";" + GetNewMeasurementKey();
settings["inputMeasurementKeys"] = inputMeasurementKeys;
settings["outputMeasurements"] = outputMeasurements;
calculator.ConnectionString = settings.JoinKeyValuePairs();
Adapter.Save(null, calculator);
}
// Gets the measurement key of the newly created measurement.
private string GetNewMeasurementKey()
{
DataModel tempDataModel = new DataModel();
IEnumerable oldKeys = m_dataModel.Measurements
.Select(measurement => measurement.ID);
return tempDataModel.Measurements
.Select(measurement => measurement.ID)
.Except(oldKeys)
.First();
}
#endregion
#region [ User removes a mapping ]
// Removes the selected item.
private void RemoveSelected()
{
IOMapping selected = m_selectedIOMapping;
if (string.IsNullOrEmpty(m_selectedCalculatorName))
return;
if ((object)selected == null)
return;
RemoveMapping();
DeleteOutputMeasurement(m_selectedIOMapping.OutputKey);
Load();
}
// Removes the selected mapping from the connection string.
private void RemoveMapping()
{
Adapter calculator;
Dictionary settings;
string inputMeasurementKeys;
string outputMeasurements;
calculator = m_dataModel.Adapters
.Single(adapter => adapter.AdapterName == m_selectedCalculatorName);
settings = calculator.ConnectionString.ToNonNullString().ParseKeyValuePairs();
if (!settings.TryGetValue("inputMeasurementKeys", out inputMeasurementKeys))
inputMeasurementKeys = string.Empty;
if (!settings.TryGetValue("outputMeasurements", out outputMeasurements))
outputMeasurements = string.Empty;
settings["inputMeasurementKeys"] = inputMeasurementKeys.Split(';')
.Where(key => key != m_selectedIOMapping.InputKey)
.Aggregate((keys, nextKey) => string.Format("{0};{1}", keys, nextKey));
settings["outputMeasurements"] = outputMeasurements.Split(';')
.Where(def => def.Split(',')[0] != m_selectedIOMapping.OutputKey)
.Aggregate((defs, nextDef) => string.Format("{0};{1}", defs, nextDef));
calculator.ConnectionString = settings.JoinKeyValuePairs();
Adapter.Save(null, calculator);
}
// Deletes the output measurement definition for the mapping that was removed.
private void DeleteOutputMeasurement(string outKey)
{
Measurement measurement = m_dataModel.Measurements
.SingleOrDefault(m => m.ID == outKey);
if ((object)measurement != null)
{
Measurement.Delete(null, measurement.SignalID);
}
}
#endregion
#region [ Operations to populate view-model properties ]
// Selects the calculator name that was defined in isolated storage.
private void SelectCalculatorName()
{
const string isolatedStorageKey = "AverageFrequency.CalculatorName";
string isolatedName = IsolatedStorageManager.ReadFromIsolatedStorage(isolatedStorageKey).ToNonNullString();
SelectedCalculatorName = m_calculatorNames.Contains(isolatedName) ? isolatedName : m_calculatorNames.FirstOrDefault();
}
// Selects the virtual device name that was defined in isolated storage.
private void SelectVirtualDeviceName()
{
if (!string.IsNullOrWhiteSpace(m_selectedCalculatorName))
{
string isolatedStorageKey = string.Format("AverageFrequency.{0}.VirtualDeviceName", m_selectedCalculatorName);
string isolatedName = IsolatedStorageManager.ReadFromIsolatedStorage(isolatedStorageKey).ToNonNullString();
SelectedVirtualDeviceName = m_virtualDeviceNames.Contains(isolatedName) ? isolatedName : m_virtualDeviceNames.FirstOrDefault();
}
}
// Updates the I/O mappings when changes are made to selections.
private void UpdateIOMappings()
{
Dictionary settings;
string setting;
IEnumerable inputKeys;
IEnumerable outputKeys;
IEnumerable ioMappings;
if ((object)m_selectedCalculatorName == null)
{
IOMappings = null;
return;
}
settings = m_dataModel.Adapters
.Single(adapter => adapter.AdapterName == m_selectedCalculatorName)
.ConnectionString.ToNonNullString().ParseKeyValuePairs();
if (settings.TryGetValue("inputMeasurementKeys", out setting))
{
inputKeys = setting.Split(';').Where(key => !string.IsNullOrWhiteSpace(key));
}
else
{
inputKeys = new List();
}
if (settings.TryGetValue("outputMeasurements", out setting))
{
outputKeys = setting.Split(';').Select(key => key.Split(',')[0])
.Where(key => !string.IsNullOrWhiteSpace(key));
}
else
{
outputKeys = new List();
}
ioMappings = inputKeys.Zip(outputKeys, MapKeys)
.Where(mapping => (object)mapping.SignalReference != null);
IOMappings = new ObservableCollection(ioMappings);
}
// Updates the frequency measurements when another virtual device is selected.
private void UpdateFrequencyMeasurements()
{
IEnumerable frequencyMeasurements;
frequencyMeasurements = m_dataModel.Measurements
.Select(measurement => new FrequencyMeasurement
{
SignalReference = measurement.SignalReference,
Key = measurement.ID
});
if ((object)m_ioMappings != null)
{
frequencyMeasurements = frequencyMeasurements
.Where(freq => m_ioMappings.All(mapping => freq.Key != mapping.InputKey && freq.Key != mapping.OutputKey));
}
FrequencyMeasurements = new ObservableCollection(frequencyMeasurements);
}
#endregion
#region [ Logic for HandlePropertyChangedOnce ]
// Creates an IOMapping object between the given input and output measurement keys.
private IOMapping MapKeys(string inKey, string outKey)
{
Measurement freq = m_dataModel.Measurements.SingleOrDefault(m => m.ID == inKey);
string signalReference = ((object)freq != null) ? freq.SignalReference : null;
return new IOMapping
{
SignalReference = signalReference,
InputKey = inKey,
OutputKey = outKey
};
}
// Confirms that the user wishes to modify the parent device of the
// output measurements defined for the currently selected calculator
// when the user changes their virtual device selection.
private bool ConfirmVirtualDeviceSelection()
{
const string caption = "Change virtual device";
const string text = "Output measurements will be updated. Continue?";
MessageBoxResult result = MessageBox.Show(text, caption, MessageBoxButton.YesNo);
return result == MessageBoxResult.Yes;
}
// Updates the parent device of the output measurements
// to the currently selected virtual device.
private void UpdateVirtualDevice()
{
Device virtualDevice = m_dataModel.Devices.Single(device => device.Acronym == m_selectedVirtualDeviceName);
IEnumerable outputMeasurements = m_dataModel.Measurements
.Where(measurement => m_ioMappings.Any(mapping => measurement.ID == mapping.OutputKey));
using (AdoDataConnection database = new AdoDataConnection(CommonFunctions.DefaultSettingsCategory))
{
foreach (Measurement outputMeasurement in outputMeasurements)
{
outputMeasurement.DeviceID = virtualDevice.ID;
Measurement.Save(database, outputMeasurement);
}
}
}
#endregion
#region [ Event triggers ]
// Triggers the PropertyChanged event.
private void OnPropertyChanged(string propertyName)
{
if ((object)PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
#endregion
}
}