//******************************************************************************************************
// Measurements.cs - Gbtc
//
// Copyright © 2012, 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:
// ----------------------------------------------------------------------------------------------------
// 05/13/2011 - Magdiel Lorenzo
// Generated original version of source code.
// 05/13/2011 - Mehulbhai P Thakkar
// Added constructor overload and other changes to handle device specific data.
// 12/20/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using GSF.Searching;
using GSF.TimeSeries.UI.Commands;
using GSF.TimeSeries.UI.DataModels;
using GSF.TimeSeries.UI.UserControls;
namespace GSF.TimeSeries.UI.ViewModels
{
///
/// Class to hold bindable collection.
///
public class Measurements : PagedViewModelBase
{
#region [ Members ]
private readonly Dictionary m_historianLookupList;
private readonly Dictionary m_signalTypeLookupList;
private readonly int m_deviceID;
private IList m_allKeys;
private RelayCommand m_searchCommand;
private RelayCommand m_advancedFindCommand;
private RelayCommand m_closeAdvancedFindCommand;
private string m_filterExpression;
private string m_searchText;
private bool m_ignoreCase;
private bool m_useWildcards;
private bool m_useRegex;
private ICollection m_searchCategories;
private bool m_advancedFindIsOpen;
private readonly AsyncSearcher m_searcher;
private string m_pendingSearch;
private bool m_searching;
private bool m_cancelSearch;
#endregion
#region [ Constructors ]
///
/// Creates a new instance of the class.
///
/// The ID of the device that the current measurement is associated with..
/// The number of measurements to display on each page of the data grid.
/// Determines whether the current item is saved automatically when a new item is selected.
public Measurements(int deviceID, int itemsPerPage, bool autosave = true)
: base(0, autosave) // Set ItemsPerPage to zero to avoid load() in the base class.
{
m_deviceID = deviceID;
ItemsPerPage = itemsPerPage;
m_historianLookupList = Historian.GetLookupList(null, isOptional: true);
m_signalTypeLookupList = SignalType.GetLookupList(null);
m_searcher = new AsyncSearcher();
m_searcher.MatchesFound += Searcher_MatchesFound;
m_searcher.SearchComplete += Searcher_SearchComplete;
Load();
}
///
/// Creates a new instance of the class.
///
/// Determines whether the current item is saved automatically when a new item is selected.
public Measurements(bool autosave = true)
: base(0, autosave)
{
m_historianLookupList = Historian.GetLookupList(null, isOptional: true);
m_signalTypeLookupList = SignalType.GetLookupList(null);
m_searcher = new AsyncSearcher();
m_searcher.MatchesFound += Searcher_MatchesFound;
m_searcher.SearchComplete += Searcher_SearchComplete;
}
#endregion
#region [ Properties ]
///
/// Gets or sets the filter expression used when querying records from the database.
///
public string FilterExpression
{
get
{
string filterExpression = null;
if (m_deviceID > 0 && !string.IsNullOrEmpty(m_filterExpression))
filterExpression = string.Format("DeviceID = {0} AND ({1})", m_deviceID, m_filterExpression);
else if (m_deviceID > 0)
filterExpression = string.Format("DeviceID = {0}", m_deviceID);
else if (!string.IsNullOrEmpty(m_filterExpression))
filterExpression = m_filterExpression;
return filterExpression;
}
set
{
m_filterExpression = value;
}
}
///
/// Gets flag that determines if is a new record.
///
public override bool IsNewRecord
{
get
{
return (CurrentItem.SignalID == Guid.Empty);
}
}
///
/// Gets type collection of historians defined in the database.
///
public virtual Dictionary HistorianLookupList
{
get
{
return m_historianLookupList;
}
}
///
/// Gets type collection of signal types defined in the database.
///
public virtual Dictionary SignalTypeLookupList
{
get
{
return m_signalTypeLookupList;
}
}
///
/// Gets to search within measurements.
///
public ICommand SearchCommand
{
get
{
if (m_searchCommand == null)
m_searchCommand = new RelayCommand(Search);
return m_searchCommand;
}
}
///
/// Gets to open the advanced find popup.
///
public ICommand AdvancedFindCommand
{
get
{
if (m_advancedFindCommand == null)
m_advancedFindCommand = new RelayCommand(() => AdvancedFindIsOpen = true);
return m_advancedFindCommand;
}
}
///
/// Gets to close the advanced find popup.
///
public ICommand CloseAdvancedFindCommand
{
get
{
if ((object)m_closeAdvancedFindCommand == null)
m_closeAdvancedFindCommand = new RelayCommand(() => AdvancedFindIsOpen = false);
return m_closeAdvancedFindCommand;
}
}
///
/// Gets or sets the list of all keys without the search text applied.
///
public IList AllKeys
{
get
{
if ((object)m_allKeys == null)
m_allKeys = DataModels.Measurement.LoadSignalIDs(null, FilterExpression, SortMember, SortDirection);
return m_allKeys;
}
set
{
m_allKeys = value;
}
}
///
/// Gets or sets the text to search for in the measurements.
///
public string SearchText
{
get
{
return m_searchText;
}
set
{
m_searchText = value;
OnPropertyChanged("SearchText");
}
}
///
/// Gets or sets the flag that determines whether to ignore casing in searches.
///
public bool IgnoreCase
{
get
{
return m_ignoreCase;
}
set
{
m_ignoreCase = value;
OnPropertyChanged("IgnoreCase");
}
}
///
/// Gets or sets the flag that determines whether to use wildcards in searches.
///
public bool UseWildcards
{
get
{
return m_useWildcards;
}
set
{
m_useWildcards = value;
OnPropertyChanged("UseWildcards");
}
}
///
/// Gets or sets the flag that determines whether search tokens are regular expressions.
///
public bool UseRegex
{
get
{
return m_useRegex;
}
set
{
m_useRegex = value;
OnPropertyChanged("UseRegex");
}
}
///
/// Gets or sets the collection of categories on which to search for the .
///
public ICollection SearchCategories
{
get
{
return m_searchCategories;
}
set
{
m_searchCategories = value;
OnPropertyChanged("SearchCategories");
}
}
///
/// Gets or sets the flag that determines whether the advanced find dialog is open.
///
public bool AdvancedFindIsOpen
{
get
{
return m_advancedFindIsOpen;
}
set
{
if (m_advancedFindIsOpen != value)
{
m_advancedFindIsOpen = value;
OnPropertyChanged("AdvancedFindIsOpen");
}
}
}
#endregion
#region [ Methods ]
///
/// Gets the primary key value of the .
///
/// The primary key value of the .
public override Guid GetCurrentItemKey()
{
return CurrentItem.SignalID;
}
///
/// Gets the string based named identifier of the .
///
/// The string based named identifier of the .
public override string GetCurrentItemName()
{
return CurrentItem.PointTag;
}
///
/// Creates a new instance of and assigns it to CurrentItem.
///
public override void Clear()
{
base.Clear();
if (m_historianLookupList.Count > 1)
CurrentItem.HistorianID = m_historianLookupList.Skip(1).First().Key;
else if (m_historianLookupList.Count > 0)
CurrentItem.HistorianID = m_historianLookupList.First().Key;
if (m_signalTypeLookupList.Count > 0)
CurrentItem.SignalTypeID = m_signalTypeLookupList.First().Key;
}
///
/// Initialization to be done before the initial call to .
///
public override void Initialize()
{
base.Initialize();
SortMember = "PointID";
SortDirection = "ASC";
}
///
/// Loads collection of defined in the database.
///
public override void Load()
{
List pageKeys;
try
{
if (!m_searching)
Mouse.OverrideCursor = Cursors.Wait;
if ((object)ItemsKeys == null)
{
ItemsKeys = DataModels.Measurement.LoadSignalIDs(null, FilterExpression, SortMember, SortDirection);
if ((object)SortSelector != null)
{
if (SortDirection == "ASC")
ItemsKeys = ItemsKeys.OrderBy(SortSelector).ToList();
else
ItemsKeys = ItemsKeys.OrderByDescending(SortSelector).ToList();
}
AllKeys = ItemsKeys;
}
pageKeys = ItemsKeys.Skip((CurrentPageNumber - 1) * ItemsPerPage).Take(ItemsPerPage).ToList();
ItemsSource = DataModels.Measurement.LoadFromKeys(null, pageKeys);
}
catch (Exception ex)
{
if (ex.InnerException != null)
{
Popup(ex.Message + Environment.NewLine + "Inner Exception: " + ex.InnerException.Message, "Load " + DataModelName + " Exception:", MessageBoxImage.Error);
CommonFunctions.LogException(null, "Load " + DataModelName, ex.InnerException);
}
else
{
Popup(ex.Message, "Load " + DataModelName + " Exception:", MessageBoxImage.Error);
CommonFunctions.LogException(null, "Load " + DataModelName, ex);
}
}
finally
{
if (!m_searching)
Mouse.OverrideCursor = null;
}
}
///
/// Handles .
///
public virtual void Search()
{
AdvancedFindIsOpen = false;
if (m_searching || m_searcher.Searching)
{
// We are in the middle of searching so we
// need to set up a pending search and
// cancel the current search operation
m_pendingSearch = m_searchText;
m_cancelSearch = true;
m_searcher.Cancel();
}
else if (!string.IsNullOrEmpty(m_pendingSearch) || !string.IsNullOrEmpty(m_searchText))
{
// Begin the search
SetCurrentPageNumber(1);
ItemsKeys = new List();
Load();
BeginSearching();
}
else
{
// Search text is empty so show all
m_pendingSearch = null;
SetCurrentPageNumber(1);
ItemsKeys = AllKeys;
Load();
}
}
///
/// Sorts the keys by the given sort member in the given direction.
///
/// The member by which to sort the data.
/// The direction in which to sort the data.
public override void SortData(string sortMember, ListSortDirection sortDirection)
{
bool search = ((object)ItemsKeys != (object)AllKeys);
SortSelector = null;
SortMember = sortMember;
SortDirection = (sortDirection == ListSortDirection.Descending) ? "DESC" : "ASC";
AllKeys = null;
ItemsKeys = null;
if (search)
Search();
else
Load();
}
///
/// Sorts model data.
///
/// Transform function for sorting.
/// Ascending or descending.
public override void SortDataBy(Func sortSelector, ListSortDirection sortDirection)
{
bool search = ((object)ItemsKeys != (object)AllKeys);
SortSelector = sortSelector;
SortMember = string.Empty;
SortDirection = (sortDirection == ListSortDirection.Descending) ? "DESC" : "ASC";
AllKeys = null;
ItemsKeys = null;
if (search)
Search();
else
Load();
}
///
/// Loads advanced find settings from isolated storage.
///
public void LoadSettings()
{
string searchText;
string ignoreCase;
string useWildcards;
string useRegex;
string searchCategories;
HashSet searchCategoriesSet;
searchText = (string)IsolatedStorageManager.ReadFromIsolatedStorage("MeasurementSearchText") ?? string.Empty;
ignoreCase = (string)IsolatedStorageManager.ReadFromIsolatedStorage("MeasurementSearchIgnoreCase") ?? string.Empty;
useWildcards = (string)IsolatedStorageManager.ReadFromIsolatedStorage("MeasurementSearchUseWildcards") ?? string.Empty;
useRegex = (string)IsolatedStorageManager.ReadFromIsolatedStorage("MeasurementSearchUseRegex") ?? string.Empty;
searchCategories = (string)IsolatedStorageManager.ReadFromIsolatedStorage("MeasurementSearchCategories") ?? string.Empty;
SearchText = searchText;
IgnoreCase = string.IsNullOrEmpty(ignoreCase) || ignoreCase.ParseBoolean();
UseWildcards = string.IsNullOrEmpty(useWildcards) || useWildcards.ParseBoolean();
UseRegex = !string.IsNullOrEmpty(useRegex) && useRegex.ParseBoolean();
searchCategoriesSet = new HashSet(searchCategories.Split(','));
foreach (AdvancedSearchCategory category in SearchCategories)
{
if (string.IsNullOrEmpty(searchCategories) || searchCategoriesSet.Contains(category.Name))
category.Selected = true;
}
}
///
/// Saves advanced find settings to isolated storage.
///
public void SaveSettings()
{
string searchCategories = string.Empty;
if (SearchCategories.Any(category => category.Selected))
{
searchCategories = SearchCategories
.Where(category => category.Selected)
.Select(category => category.Name)
.Aggregate((str1, str2) => str1 + "," + str2);
}
IsolatedStorageManager.WriteToIsolatedStorage("MeasurementSearchText", SearchText);
IsolatedStorageManager.WriteToIsolatedStorage("MeasurementSearchIgnoreCase", IgnoreCase.ToString());
IsolatedStorageManager.WriteToIsolatedStorage("MeasurementSearchUseWildcards", UseWildcards.ToString());
IsolatedStorageManager.WriteToIsolatedStorage("MeasurementSearchUseRegex", UseRegex.ToString());
IsolatedStorageManager.WriteToIsolatedStorage("MeasurementSearchCategories", searchCategories);
}
private void BeginSearching()
{
Thread searchThread;
try
{
m_searching = true;
m_searcher.SearchText = m_pendingSearch ?? m_searchText;
m_searcher.IgnoreCase = m_ignoreCase;
m_searcher.UseWildcards = m_useWildcards;
m_searcher.UseRegex = m_useRegex;
m_searcher.SearchCategories.Clear();
foreach (AdvancedSearchCategory category in m_searchCategories)
{
if (category.Selected)
m_searcher.SearchCategories.Add(category.Name);
}
m_pendingSearch = null;
Mouse.OverrideCursor = Cursors.AppStarting;
searchThread = new Thread(SearchInBackground);
searchThread.IsBackground = true;
searchThread.Start();
}
catch (Exception ex)
{
Mouse.OverrideCursor = null;
m_searching = false;
CommonFunctions.LogException(null, "Search " + DataModelName, ex);
}
}
private void SearchInBackground()
{
const int SearchGroupSize = 1000;
IList allKeys = AllKeys;
IList measurements;
List searchGroup;
int searchIndex = 0;
string sortMember = SortMember;
string sortDirection = SortDirection;
PropertyInfo sortProperty = typeof(DataModels.Measurement).GetProperty(sortMember);
try
{
while (searchIndex < allKeys.Count && !m_cancelSearch)
{
searchGroup = allKeys.Skip(searchIndex).Take(SearchGroupSize).ToList();
measurements = DataModels.Measurement.LoadFromKeys(null, searchGroup);
if ((object)sortProperty == null)
m_searcher.Add(measurements);
else if (sortDirection == "ASC")
m_searcher.Add(measurements.OrderBy(sortProperty.GetValue));
else
m_searcher.Add(measurements.OrderByDescending(sortProperty.GetValue));
searchIndex += SearchGroupSize;
}
if (m_cancelSearch)
m_searcher.Clear();
}
catch (Exception ex)
{
CommonFunctions.LogException(null, "Search " + DataModelName, ex);
}
finally
{
m_searching = false;
m_cancelSearch = false;
}
}
private void Searcher_MatchesFound(object sender, EventArgs> e)
{
try
{
foreach (DataModels.Measurement measurement in e.Argument)
ItemsKeys.Add(measurement.SignalID);
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
if (ItemsSource.Count == 0)
{
Load();
}
else
{
if (ItemsSource.Count < ItemsPerPage)
{
e.Argument.Take(ItemsPerPage - ItemsSource.Count).ToList()
.ForEach(measurement => ItemsSource.Add(measurement));
}
GeneratePages();
}
}));
}
catch (Exception ex)
{
CommonFunctions.LogException(null, "Search " + DataModelName, ex);
}
}
private void Searcher_SearchComplete(object sender, EventArgs e)
{
if (!m_searching)
{
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
if ((object)m_pendingSearch == null)
Mouse.OverrideCursor = null;
else
Search();
}));
}
}
#endregion
}
}