//****************************************************************************************************** // MeasurementPagerUserControl.xaml.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: // ---------------------------------------------------------------------------------------------------- // 10/01/2012 - Stephen C. Wills // Generated original version of source code. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using GSF.TimeSeries.UI.ViewModels; namespace GSF.TimeSeries.UI.UserControls { /// /// Interaction logic for MeasurementPagerUserControl.xaml /// public partial class MeasurementPagerUserControl : UserControl { #region [ Members ] // Events /// /// Event triggered whenever the current page changes. /// public event EventHandler CurrentPageChanged; /// /// Event triggered whenever the currently highlighted item changes. /// public event EventHandler CurrentItemChanged; /// /// Event triggered whenever the set of selected measurements changes. /// public event EventHandler SelectedMeasurementsChanged; // Fields private Measurements m_dataContext; private readonly ISet m_selectedMeasurements; private readonly ObservableCollection m_dataGridColumns; private bool m_overrideDefaultColumns; private Func m_sortKeySelector; private DataGridColumn m_sortColumn; private string m_sortMemberPath; private ListSortDirection m_sortDirection; #endregion #region [ Constructors ] /// /// Creates a new instance of the . /// public MeasurementPagerUserControl() { InitializeComponent(); m_selectedMeasurements = new HashSet(); m_dataGridColumns = new ObservableCollection(); m_dataGridColumns.CollectionChanged += DataGridColumns_CollectionChanged; } #endregion #region [ Properties ] /// /// Gets or sets the number of measurements displayed on the data grid per page. /// public int ItemsPerPage { get { return (int)this.GetValue(ItemsPerPageProperty); } set { this.SetValue(ItemsPerPageProperty, value); } } /// /// Gets or sets the filter expression used when reading measurements from the database. /// public string FilterExpression { get { return (string)this.GetValue(FilterExpressionProperty); } set { this.SetValue(FilterExpressionProperty, value); } } /// /// Gets or sets the boolean flag which determines whether this control /// provides checkboxes that allow the user to select measurements to /// be placed in the set. /// public bool Selectable { get { return (bool)this.GetValue(SelectableProperty); } set { this.SetValue(SelectableProperty, value); } } /// /// Gets or sets the boolean flag which determines whether /// this control can be filtered by a search expression. /// public bool Searchable { get { return (bool)this.GetValue(SearchableProperty); } set { this.SetValue(SearchableProperty, value); } } /// /// Gets or sets the boolean flag which determines whether /// the page size should be shown on the control. /// public bool ShowPageSize { get { return (bool)this.GetValue(ShowPageSizeProperty); } set { this.SetValue(ShowPageSizeProperty, value); } } /// /// Gets or sets the boolean flag which determines whether the /// hotkey is enabled for displaying the advanced find popup. /// public bool AdvancedHotkeyIsEnabled { get { return (bool)this.GetValue(AdvancedHotkeyIsEnabledProperty); } set { this.SetValue(AdvancedHotkeyIsEnabledProperty, value); } } /// /// Gets the collection of data grid columns to be displayed. /// public ObservableCollection DataGridColumns { get { return m_dataGridColumns; } } /// /// Gets the current page of measurements in the measurement pager. /// public ObservableCollection CurrentPage { get { return m_dataContext.ItemsSource; } } /// /// Gets the currently highlighted item on the data grid. /// public DataModels.Measurement CurrentItem { get { return m_dataContext.CurrentItem; } } /// /// Gets the set of selected measurements. /// public ISet SelectedMeasurements { get { return m_selectedMeasurements; } } #endregion #region [ Methods ] /// /// Updates the collection of selected measurements based on which items are selected /// or unselected on the current page. This method also updates the selected measurements /// label to reflect the total number of selected measurements. /// public void UpdateSelections() { DataGridRow row; CheckBox itemCheckBox; DataModels.Measurement measurement; // Iterate over all the rows on the current page for (int i = 0; i < DataGridList.Items.Count; i++) { row = DataGridList.ItemContainerGenerator.ContainerFromIndex(i) as DataGridRow; if ((object)row != null) { // Get the measurement and its corresponding check box itemCheckBox = FindChildByName("ItemCheckBox", row) as CheckBox; measurement = row.Item as DataModels.Measurement; if ((object)itemCheckBox != null && (object)measurement != null) { // If it's checked, add it to the set of selections; // otherwise, remove it from the set of selection if (itemCheckBox.IsChecked ?? false) m_selectedMeasurements.Add(measurement.SignalID); else m_selectedMeasurements.Remove(measurement.SignalID); } } } SelectedMeasurementsLabel.Content = string.Format("Selected: {0}", m_selectedMeasurements.Count); } /// /// Causes all selections to be cleared from the data grid. /// public void ClearSelections() { SelectedMeasurements.Clear(); m_dataContext.Load(); UpdateSelections(); OnSelectedMeasurementsChanged(); } /// /// Causes the data grid to be reloaded with a new set of data. /// public void ReloadDataGrid() { bool search = ((object)m_dataContext.ItemsKeys != (object)m_dataContext.AllKeys); m_dataContext.ItemsKeys = null; if (search) m_dataContext.Search(); else m_dataContext.Load(); } /// /// Causes the data grid to be sorted by the values returned by the given key selector. /// /// The function to transform measurement IDs to a key to sort by. public void SortBy(Func keySelector) { if (m_sortKeySelector != keySelector) m_sortDirection = ListSortDirection.Ascending; else if (m_sortDirection == ListSortDirection.Descending) m_sortDirection = ListSortDirection.Ascending; else m_sortDirection = ListSortDirection.Descending; m_sortKeySelector = keySelector; m_sortColumn = null; m_sortMemberPath = null; DataGridList.Items.SortDescriptions.Clear(); DataGridList.Items.Refresh(); m_dataContext.SortDataBy(keySelector, m_sortDirection); } /// /// Invoked whenever the effective value of any dependency property on this /// has been updated. The specific dependency property that changed is reported in the arguments parameter. /// Overrides . /// /// The event data that describes the property that changed, as well as old and new values. protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) { base.OnPropertyChanged(e); if (e.Property == SelectableProperty) DataGridList.Columns[0].Visibility = (bool)e.NewValue ? Visibility.Visible : Visibility.Collapsed; else if (e.Property == FilterExpressionProperty && (object)m_dataContext != null) m_dataContext.FilterExpression = FilterExpression; else if (e.Property == ShowPageSizeProperty) DataPager.ShowPageSize = ShowPageSize; } // Creates and sets the data context once the user control has loaded. private void MeasurementPager_Loaded(object sender, RoutedEventArgs e) { if (!DesignerProperties.GetIsInDesignMode(this)) { m_dataContext = new Measurements(false); m_dataContext.PropertyChanged += ViewModel_PropertyChanged; m_dataContext.FilterExpression = FilterExpression; m_dataContext.ItemsPerPage = ItemsPerPage; m_dataContext.SearchCategories = AdvancedSearch.Categories; m_dataContext.LoadSettings(); m_dataContext.Load(); RootPanel.DataContext = m_dataContext; Application.Current.Exit += Application_Exit; } } // Saves advanced find settings when the pager is unloaded. private void MeasurementPager_Unloaded(object sender, RoutedEventArgs e) { if (!DesignerProperties.GetIsInDesignMode(this)) { Application.Current.Exit -= Application_Exit; m_dataContext.SaveSettings(); } } // Handles the hotkey for the advanced find popup. private void MeasurementPagerUserControl_KeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.F && Keyboard.Modifiers == ModifierKeys.Control && AdvancedHotkeyIsEnabled) { m_dataContext.AdvancedFindIsOpen = true; e.Handled = true; } } /// /// Handles the application exit event. /// /// Source of the event. /// Arguments of the event. private void Application_Exit(object sender, ExitEventArgs e) { m_dataContext.SaveSettings(); } // Edits the columns of the data grid so that they reflect any changes made to the DataGridColumns collection. private void DataGridColumns_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (!m_overrideDefaultColumns) { // If ever this collection changes, it will indicate // that the default columns should be overridden DataGridColumn selectColumn = DataGridList.Columns[0]; DataGridList.Columns.Clear(); DataGridList.Columns.Add(selectColumn); m_overrideDefaultColumns = true; } switch (e.Action) { case NotifyCollectionChangedAction.Add: for (int i = 0; i < e.NewItems.Count; i++) { DataGridList.Columns.Insert(e.NewStartingIndex + i + 1, (DataGridColumn)e.NewItems[i]); } break; case NotifyCollectionChangedAction.Move: DataGridList.Columns.Move(e.OldStartingIndex + 1, e.NewStartingIndex + 1); break; case NotifyCollectionChangedAction.Remove: foreach (object column in e.OldItems) { DataGridList.Columns.RemoveAt(e.OldStartingIndex + 1); } break; case NotifyCollectionChangedAction.Replace: for (int i = 0; i < e.NewItems.Count; i++) { DataGridList.Columns[e.OldStartingIndex + i + 1] = (DataGridColumn)e.NewItems[i]; } break; case NotifyCollectionChangedAction.Reset: DataGridColumn selectColumn = DataGridList.Columns[0]; DataGridList.Columns.Clear(); DataGridList.Columns.Add(selectColumn); break; } } // Executes search on the pager when the user presses Enter. private void SearchTextBox_KeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Enter) { m_dataContext.Search(); e.Handled = true; } } // Selects/unselects all the measurements on all pages. private void SelectAllHyperlink_Click(object sender, RoutedEventArgs e) { // Get the select-all check box CheckBox selectAllCheckBox = FindChildByName("SelectAllCheckBox", DataGridList) as CheckBox; if ((object)selectAllCheckBox != null) { if (selectAllCheckBox.IsChecked ?? false) { // Select measurements from all pages foreach (Guid signalID in m_dataContext.ItemsKeys) m_selectedMeasurements.Add(signalID); } else { // Deselect measurements from all pages foreach (Guid signalID in m_dataContext.ItemsKeys) m_selectedMeasurements.Remove(signalID); } } // Hide the hyperlink and update selections SelectAllHyperlink.Visibility = Visibility.Collapsed; UpdateSelections(); OnSelectedMeasurementsChanged(); } // Selects/unselects all the measurements on the current page. private void SelectAllCheckBox_Click(object sender, RoutedEventArgs e) { DependencyObject item; CheckBox itemCheckBox; CheckBox selectAllCheckBox; // Get select-all check box selectAllCheckBox = e.Source as CheckBox; if ((object)selectAllCheckBox != null) { for (int i = 0; i < DataGridList.Items.Count; i++) { // Get check box for current row item = DataGridList.ItemContainerGenerator.ContainerFromIndex(i); itemCheckBox = FindChildByName("ItemCheckBox", item) as CheckBox; // Update the IsChecked property for the check box if ((object)itemCheckBox != null) itemCheckBox.IsChecked = selectAllCheckBox.IsChecked; } // Update the hyperlink text to reflect whether items are being selected or unselected if (selectAllCheckBox.IsChecked ?? false) SelectAllHyperlink.Content = "Click here to select all measurements on all pages."; else SelectAllHyperlink.Content = "Click here to unselect all measurements on all pages."; // Make hyperlink visible to the user SelectAllHyperlink.Visibility = Visibility.Visible; } UpdateSelections(); OnSelectedMeasurementsChanged(); } // Selects the mesurement that the user clicked on. private void ItemCheckBox_Click(object sender, RoutedEventArgs e) { CheckBox itemCheckBox; CheckBox selectAllCheckBox; // Update selections first so that we can determine // whether all items on the page are selected UpdateSelections(); // Get check boxes itemCheckBox = e.Source as CheckBox; selectAllCheckBox = FindChildByName("SelectAllCheckBox", DataGridList) as CheckBox; if ((object)itemCheckBox != null && (object)selectAllCheckBox != null) { // Update select-all check box, if necessary if (!(itemCheckBox.IsChecked ?? false)) selectAllCheckBox.IsChecked = false; else if (m_dataContext.ItemsSource.All(measurement => m_selectedMeasurements.Contains(measurement.SignalID))) selectAllCheckBox.IsChecked = true; } // Hide hyperlink because user has deliberately ignored it SelectAllHyperlink.Visibility = Visibility.Collapsed; OnSelectedMeasurementsChanged(); } // Receives notification any time the data grid is loading a row. // We need to ensure that the selection check box is properly checked or unchecked. // This cannot be done until the row has finished loading, so we attach to that event here. private void DataGridList_LoadingRow(object sender, DataGridRowEventArgs e) { e.Row.Loaded += DataGridRow_Loaded; } // Ensures that the selection check box is properly // checked or unchecked based on the current selections. private void DataGridRow_Loaded(object sender, RoutedEventArgs e) { DataGridRow row; CheckBox itemCheckBox; DataModels.Measurement measurement; CheckBox selectAllCheckBox; // Get loaded row and select-all check box row = e.Source as DataGridRow; selectAllCheckBox = FindChildByName("SelectAllCheckBox", DataGridList) as CheckBox; if ((object)row != null) { // Get measurement and its corresponding check box row.Loaded -= DataGridRow_Loaded; itemCheckBox = FindChildByName("ItemCheckBox", row) as CheckBox; measurement = row.Item as DataModels.Measurement; if ((object)itemCheckBox != null && (object)measurement != null) { // Set the state of the check box based on whether the measurement is selected itemCheckBox.IsChecked = m_selectedMeasurements.Contains(measurement.SignalID); } } // Set the state of the select-all check box based on // the selected state of all measurements on the page if ((object)selectAllCheckBox != null) selectAllCheckBox.IsChecked = m_dataContext.ItemsSource.All(item => m_selectedMeasurements.Contains(item.SignalID)); // Hide the select all hyperlink as it's no longer relevant on the new page SelectAllHyperlink.Visibility = Visibility.Collapsed; } // Sorts the data grid by the selected sort member. private void DataGrid_Sorting(object sender, DataGridSortingEventArgs e) { if (e.Column.SortMemberPath != m_sortMemberPath) m_sortDirection = ListSortDirection.Ascending; else if (m_sortDirection == ListSortDirection.Ascending) m_sortDirection = ListSortDirection.Descending; else m_sortDirection = ListSortDirection.Ascending; m_sortKeySelector = null; m_sortColumn = e.Column; m_sortMemberPath = e.Column.SortMemberPath; m_dataContext.SortData(m_sortMemberPath, m_sortDirection); } // Sorts the current page of the data grid each time the view model's ItemsSource is modified. private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == "CurrentItem") { OnCurrentItemChanged(); if (m_dataContext.IsNewRecord) DataGridList.SelectedIndex = -1; } else if (e.PropertyName == "ItemsSource") { Dispatcher.BeginInvoke(new Action(ResizeDataGridContent)); Dispatcher.BeginInvoke(new Action(SortDataGrid)); OnCurrentPageChanged(); } else if (e.PropertyName == "AdvancedFindIsOpen") { Dispatcher.BeginInvoke(new Action(() => { TextBox searchTextBox = m_dataContext.AdvancedFindIsOpen ? AdvancedSearch.FindName("SearchTextBox") as TextBox : SearchTextBox; if ((object)searchTextBox != null) { searchTextBox.Focus(); searchTextBox.SelectAll(); } })); } } // Forces resize on the columns in the data grid // so that they can shrink as well as grow. private void ResizeDataGridContent() { List columnWidths = DataGridList.Columns.Select(column => column.Width).ToList(); foreach (DataGridColumn column in DataGridList.Columns) column.Width = 0; DataGridList.UpdateLayout(); for (int i = 0; i < DataGridList.Columns.Count; i++) DataGridList.Columns[i].Width = new DataGridLength(columnWidths[i].Value, columnWidths[i].UnitType); } // Sorts the current page of the data grid. private void SortDataGrid() { if ((object)m_sortColumn != null) { m_sortColumn.SortDirection = m_sortDirection; DataGridList.Items.SortDescriptions.Clear(); DataGridList.Items.SortDescriptions.Add(new SortDescription(m_sortMemberPath, m_sortDirection)); DataGridList.Items.Refresh(); } } // Traverses the visual tree, starting at the given parent object, toward // the leaf nodes in order to find a FrameworkElement with the given name. private FrameworkElement FindChildByName(string name, DependencyObject parent) { DependencyObject dependencyChild; FrameworkElement frameworkChild; int childrenCount; childrenCount = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < childrenCount; i++) { dependencyChild = VisualTreeHelper.GetChild(parent, i); frameworkChild = dependencyChild as FrameworkElement; if ((object)frameworkChild != null && frameworkChild.Name == name) return frameworkChild; frameworkChild = FindChildByName(name, dependencyChild); if ((object)frameworkChild != null) return frameworkChild; } return null; } // Triggers the CurrentPageChanged event. private void OnCurrentPageChanged() { if ((object)CurrentPageChanged != null) CurrentPageChanged(this, EventArgs.Empty); } // Triggers the CurrentItemChanged event. private void OnCurrentItemChanged() { if ((object)CurrentItemChanged != null) CurrentItemChanged(this, EventArgs.Empty); } // Triggers the SelectedMeasurementsChanged event. private void OnSelectedMeasurementsChanged() { if ((object)SelectedMeasurementsChanged != null) SelectedMeasurementsChanged(this, EventArgs.Empty); } #endregion #region [ Static ] // Static Fields /// /// for the property. /// public static DependencyProperty ItemsPerPageProperty = DependencyProperty.Register("ItemsPerPage", typeof(int), typeof(MeasurementPagerUserControl), new PropertyMetadata(10)); /// /// for the property. /// public static DependencyProperty FilterExpressionProperty = DependencyProperty.Register("FilterExpression", typeof(string), typeof(MeasurementPagerUserControl)); /// /// for the property. /// public static DependencyProperty SelectableProperty = DependencyProperty.Register("Selectable", typeof(bool), typeof(MeasurementPagerUserControl), new PropertyMetadata(false)); /// /// for the property. /// public static DependencyProperty SearchableProperty = DependencyProperty.Register("Searchable", typeof(bool), typeof(MeasurementPagerUserControl), new PropertyMetadata(false)); /// /// for the property. /// public static DependencyProperty ShowPageSizeProperty = DependencyProperty.Register("ShowPageSize", typeof(bool), typeof(MeasurementPagerUserControl), new PropertyMetadata(true)); /// /// for the property. /// public static DependencyProperty AdvancedHotkeyIsEnabledProperty = DependencyProperty.Register("AdvancedHotkeyIsEnabled", typeof(bool), typeof(MeasurementPagerUserControl), new PropertyMetadata(true)); #endregion } }