//****************************************************************************************************** // 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 } }