MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections;6using System.Collections.Generic;7using System.Collections.ObjectModel;8using System.Collections.Specialized;9using System.ComponentModel;10using System.Diagnostics;11using System.Globalization;12using System.Text;13using System.Windows;14using System.Windows.Automation;15using System.Windows.Controls;16using System.Windows.Controls.Primitives;17using System.Windows.Data;18using System.Windows.Documents;19using System.Windows.Input;20using System.Windows.Markup;21using System.Windows.Media;22using System.Windows.Media.Imaging;23using System.Windows.Shapes;24using System.Windows.Threading;25 26namespace Microsoft.Management.UI.Internal27{28 /// <content>29 /// Partial class implementation for InnerList control.30 /// </content>31 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]32 public partial class InnerList : System.Windows.Controls.ListView33 {34 #region internal fields35 #region StyleCop Suppression - generated code36 /// <summary>37 /// The current ICollectionView being displayed.38 /// </summary>39 internal ICollectionView CollectionView;40 41 #endregion StyleCop Suppression - generated code42 #endregion internal fields43 44 #region private fields45 /// <summary>46 /// The current GridView.47 /// </summary>48 private InnerListGridView innerGrid;49 50 private InnerListColumn sortedColumn;51 52 /// <summary>53 /// ContextMenu for InnerList columns.54 /// </summary>55 private ContextMenu contextMenu;56 57 /// <summary>58 /// Private setter for <see cref="Columns"/>.59 /// </summary>60 private ObservableCollection<InnerListColumn> columns = new ObservableCollection<InnerListColumn>();61 62 /// <summary>63 /// Gets or sets whether the current items source is non-null and has items.64 /// </summary>65 private bool itemsSourceIsEmpty = false;66 67 #endregion private fields68 69 #region constructors70 71 /// <summary>72 /// Initializes a new instance of this control.73 /// </summary>74 public InnerList()75 : base()76 {77 // This flag is needed to dramatically increase performance of scrolling \\78 VirtualizingStackPanel.SetVirtualizationMode(this, VirtualizationMode.Recycling);79 80 AutomationProperties.SetAutomationId(this, "InnerList"); // No localization needed81 }82 #endregion constructors83 84 #region Events85 86 /// <summary>87 /// Register PropertyChangedEventHandler ItemSourcesPropertyChanged .88 /// </summary>89 public event PropertyChangedEventHandler ItemSourcesPropertyChanged;90 #endregion Events91 92 #region public properties93 94 /// <summary>95 /// Gets ItemsSource instead.96 /// <see cref="InnerList"/> Does not support adding to Items.97 /// </summary>98 [Browsable(false)]99 public new ItemCollection Items100 {101 get102 {103 return base.Items;104 }105 }106 107 /// <summary>108 /// Gets the column that is sorted, or <c>null</c> if no column is sorted.109 /// </summary>110 public InnerListColumn SortedColumn111 {112 get113 {114 return this.sortedColumn;115 }116 }117 118 /// <summary>119 /// Gets InnerListGridView.120 /// </summary>121 public InnerListGridView InnerGrid122 {123 get { return this.innerGrid; }124 protected set { this.innerGrid = value; }125 }126 127 /// <summary>128 /// Gets the collection of columns that this list should display.129 /// </summary>130 public ObservableCollection<InnerListColumn> Columns131 {132 get { return this.columns; }133 }134 135 #endregion public properties136 137 #region public methods138 139 /// <summary>140 /// Causes the object to scroll into view.141 /// </summary>142 /// <param name="item">Object to scroll.</param>143 /// <remarks>This method overrides ListBox.ScrollIntoView(), which throws NullReferenceException when VirtualizationMode is set to Recycling.144 /// This implementation uses a workaround recommended by the WPF team.</remarks>145 public new void ScrollIntoView(object item)146 {147 Dispatcher.BeginInvoke(148 DispatcherPriority.Background,149 (DispatcherOperationCallback)((arg) =>150 {151 if (this.IsLoaded)152 {153 base.ScrollIntoView(arg);154 }155 156 return null;157 }),158 item);159 }160 161 /// <summary>162 /// Causes the object to scroll into view from the top, so that it tends to appear at the bottom of the scroll area.163 /// </summary>164 /// <param name="item">Object to scroll.</param>165 public void ScrollIntoViewFromTop(object item)166 {167 if (this.Items.Count > 0)168 {169 this.ScrollIntoView(this.Items[0]);170 this.ScrollIntoView(item);171 }172 }173 174 /// <summary>175 /// Updates the InnerGrid based upon the columns collection.176 /// </summary>177 public void RefreshColumns()178 {179 this.UpdateView(this.ItemsSource);180 }181 182 /// <summary>183 /// Sorts the list by the specified column. This has no effect if the list does not have a data source.184 /// </summary>185 /// <param name="column">186 /// The column to sort187 /// </param>188 /// <param name="shouldScrollIntoView">189 /// Indicates whether the SelectedItem should be scrolled into view.190 /// </param>191 /// <exception cref="ArgumentNullException">The specified value is a null reference.</exception>192 public void ApplySort(InnerListColumn column, bool shouldScrollIntoView)193 {194 ArgumentNullException.ThrowIfNull(column);195 196 // NOTE : By setting the column here, it will be used197 // later to set the sorted column when the UI state198 // is ready.199 this.sortedColumn = column;200 201 // If the list hasn't been populated, don't do anything \\202 if (this.CollectionView == null)203 {204 return;205 }206 207 this.UpdatePrimarySortColumn();208 209 using (this.CollectionView.DeferRefresh())210 {211 ListCollectionView lcv = (ListCollectionView)this.CollectionView;212 lcv.CustomSort = new PropertyValueComparer(this.GetDescriptionsForSorting(), true, FilterRuleCustomizationFactory.FactoryInstance.PropertyValueGetter);213 }214 215 if (shouldScrollIntoView && this.SelectedIndex > 0)216 {217 this.ScrollIntoView(this.SelectedItem);218 }219 }220 221 private void UpdatePrimarySortColumn()222 {223 foreach (InnerListColumn column in this.InnerGrid.AvailableColumns)224 {225 bool isPrimarySortColumn = object.ReferenceEquals(this.sortedColumn, column);226 227 InnerList.SetIsPrimarySortColumn(column, isPrimarySortColumn);228 InnerList.SetIsPrimarySortColumn((GridViewColumnHeader)column.Header, isPrimarySortColumn);229 }230 }231 232 /// <summary>233 /// Gets a list of data descriptions for the columns that are not the primary sort column.234 /// </summary>235 /// <returns>A list of data descriptions for the columns that are not the primary sort column.</returns>236 private List<UIPropertyGroupDescription> GetDescriptionsForSorting()237 {238 List<UIPropertyGroupDescription> dataDescriptions = new List<UIPropertyGroupDescription>();239 240 dataDescriptions.Add(this.SortedColumn.DataDescription);241 242 foreach (InnerListColumn column in this.InnerGrid.Columns)243 {244 if (!object.ReferenceEquals(this.SortedColumn, column))245 {246 dataDescriptions.Add(column.DataDescription);247 }248 }249 250 return dataDescriptions;251 }252 253 /// <summary>254 /// Clears the sort order from the list.255 /// </summary>256 public void ClearSort()257 {258 if (this.CollectionView == null)259 {260 return;261 }262 263 using (this.CollectionView.DeferRefresh())264 {265 this.sortedColumn = null;266 ListCollectionView lcv = (ListCollectionView)this.CollectionView;267 lcv.CustomSort = null;268 }269 270 // If columns are shown, update them to show none are sorted \\271 if (this.InnerGrid != null)272 {273 this.UpdatePrimarySortColumn();274 }275 }276 277 #endregion public methods278 279 #region internal methods280 281 #endregion internal methods282 283 #region protected methods284 /// <summary>285 /// Called when the ItemsSource changes to set internal fields, subscribe to the view change286 /// and possibly autopopulate columns.287 /// </summary>288 /// <param name="oldValue">Previous ItemsSource.</param>289 /// <param name="newValue">Current ItemsSource.</param>290 protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue)291 {292 base.OnItemsSourceChanged(oldValue, newValue);293 294 this.itemsSourceIsEmpty = this.ItemsSource != null && this.ItemsSource.GetEnumerator().MoveNext() == false;295 296 // A view can be created if there is data to auto-generate columns, or columns are added programmatically \\297 bool canCreateView = (this.ItemsSource != null) &&298 (this.itemsSourceIsEmpty == false || this.AutoGenerateColumns == false);299 300 if (canCreateView)301 {302 this.UpdateViewAndCollectionView(this.ItemsSource);303 304 // If there are items, select the first item now \\305 this.SelectedIndex = this.itemsSourceIsEmpty ? -1 : 0;306 }307 else308 {309 // Release the current inner grid \\310 this.ReleaseInnerGridReferences();311 312 // clean up old state if can not set the state.313 this.SetCollectionView(null);314 this.innerGrid = null;315 this.View = null;316 }317 }318 319 /// <summary>320 /// Called when ItemsChange to throw an exception indicating we don't support321 /// changing Items directly.322 /// </summary>323 /// <param name="e">Event parameters.</param>324 protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)325 {326 base.OnItemsChanged(e);327 328 if (e.NewItems != null)329 {330 // If the items source now has items, select the first item \\331 if (this.itemsSourceIsEmpty && this.Items.Count > 0)332 {333 this.SelectedIndex = 0;334 this.itemsSourceIsEmpty = false;335 }336 337 if (e.Action == NotifyCollectionChangedAction.Add)338 {339 if (this.InnerGrid == null)340 {341 this.UpdateViewAndCollectionView(this.ItemsSource);342 }343 }344 }345 }346 347 /// <summary>348 /// Called when a key is pressed while within the InnerList scope.349 /// </summary>350 /// <param name="e">The event args.</param>351 protected override void OnKeyDown(KeyEventArgs e)352 {353 base.OnKeyDown(e);354 355 if ((e.Key == Key.Left || e.Key == Key.Right) &&356 Keyboard.Modifiers == ModifierKeys.None)357 {358 // If pressing Left or Right on a column header, move the focus \\359 GridViewColumnHeader header = e.OriginalSource as GridViewColumnHeader;360 361 if (header != null)362 {363 header.MoveFocus(new TraversalRequest(KeyboardHelp.GetNavigationDirection(this, e.Key)));364 e.Handled = true;365 }366 }367 }368 369 #endregion protected methods370 371 #region static private methods372 373 /// <summary>374 /// Called when the View property is changed.375 /// </summary>376 /// <param name="obj">InnerList whose property is being changed.</param>377 /// <param name="e">Event arguments.</param>378 private static void InnerList_OnViewChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)379 {380 InnerList thisList = (InnerList)obj;381 GridView newGrid = e.NewValue as GridView;382 InnerListGridView innerGrid = e.NewValue as InnerListGridView;383 if (newGrid != null && innerGrid == null)384 {385 throw new NotSupportedException(string.Format(386 CultureInfo.InvariantCulture,387 InvariantResources.ViewSetWithType,388 nameof(GridView),389 nameof(InnerListGridView)));390 }391 392 ((InnerList)obj).innerGrid = innerGrid;393 }394 395 /// <summary>396 /// Gets the exception to be thrown when using Items.397 /// </summary>398 /// <returns>The exception to be thrown when using Items.</returns>399 private static NotSupportedException GetItemsException()400 {401 return new NotSupportedException(402 string.Format(403 CultureInfo.InvariantCulture,404 InvariantResources.NotSupportAddingToItems,405 nameof(InnerList),406 ItemsControl.ItemsSourceProperty.Name));407 }408 #endregion static private methods409 410 #region instance private methods411 /// <summary>412 /// Called from OnItemsSourceChanged to set the collectionView field and413 /// subscribe to the collectionView changed event.414 /// </summary>415 /// <param name="newValue">ITemsSource passed to OnItemsSourceChanged.</param>416 private void SetCollectionView(System.Collections.IEnumerable newValue)417 {418 if (newValue == null)419 {420 this.CollectionView = null;421 }422 else423 {424 CollectionViewSource newValueViewSource = newValue as CollectionViewSource;425 if (newValueViewSource != null && newValueViewSource.View != null)426 {427 this.CollectionView = newValueViewSource.View;428 }429 else430 {431 this.CollectionView = CollectionViewSource.GetDefaultView(newValue);432 }433 }434 }435 436 /// <summary>437 /// Update View And CollectionView.438 /// </summary>439 /// <param name="value">InnerList object.</param>440 private void UpdateViewAndCollectionView(IEnumerable value)441 {442 Debug.Assert(value != null, "value should be non-null");443 444 // SetCollectionView deals with a null newEnumerable445 this.SetCollectionView(value);446 447 this.UpdateView(value);448 449 // Generate property changed event.450 if (this.ItemSourcesPropertyChanged != null)451 {452 this.ItemSourcesPropertyChanged(this, new PropertyChangedEventArgs("ItemsSource"));453 }454 }455 456 private void UpdateView(IEnumerable value)457 {458 // NOTE : We need to clear the SortDescription before459 // clearing the InnerGrid.Columns so that the Adorners460 // are appropriately cleared.461 InnerListColumn sortedColumn = this.SortedColumn;462 this.ClearSort();463 464 // Release the current inner grid \\465 this.ReleaseInnerGridReferences();466 467 if (this.AutoGenerateColumns)468 {469 this.innerGrid = new InnerListGridView();470 471 // PopulateColumns deals with a null newEnumerable472 this.innerGrid.PopulateColumns(value);473 }474 else475 {476 this.innerGrid = new InnerListGridView(this.Columns);477 }478 479 this.View = this.innerGrid;480 this.SetColumnHeaderActions();481 482 if (sortedColumn != null && this.Columns.Contains(sortedColumn))483 {484 this.ApplySort(sortedColumn, false);485 }486 }487 488 /// <summary>489 /// Releases all references to the current inner grid, if one exists.490 /// </summary>491 private void ReleaseInnerGridReferences()492 {493 if (this.innerGrid != null)494 {495 // Tell the inner grid to release its references \\496 this.innerGrid.ReleaseReferences();497 498 // Release the column headers \\499 foreach (InnerListColumn column in this.innerGrid.AvailableColumns)500 {501 GridViewColumnHeader header = column.Header as GridViewColumnHeader;502 503 if (header != null)504 {505 header.Click -= this.Header_Click;506 header.PreviewKeyDown -= this.Header_KeyDown;507 }508 }509 }510 }511 512 /// <summary>513 /// Called when the ItemsSource changes, after SetGridview to add event handlers514 /// to the column header.515 /// </summary>516 internal void SetColumnHeaderActions()517 {518 if (this.innerGrid == null)519 {520 return;521 }522 523 // set context menu524 this.innerGrid.ColumnHeaderContextMenu = this.GetListColumnsContextMenu();525 526 foreach (GridViewColumn column in this.innerGrid.AvailableColumns)527 {528 // A string header needs an explicit GridViewColumnHeader529 // so we can hook up our events530 string headerString = column.Header as string;531 if (headerString != null)532 {533 GridViewColumnHeader columnHeader = new GridViewColumnHeader();534 columnHeader.Content = headerString;535 column.Header = columnHeader;536 }537 538 GridViewColumnHeader header = column.Header as GridViewColumnHeader;539 540 if (header != null)541 {542 // header Click543 header.Click += this.Header_Click;544 header.PreviewKeyDown += this.Header_KeyDown;545 }546 547 // If it is a GridViewColumnHeader we will not have the same nice sorting and grouping548 // capabilities549 }550 }551 552 #region ApplicationCommands.Copy553 554 partial void OnCopyCanExecuteImplementation(CanExecuteRoutedEventArgs e)555 {556 e.CanExecute = this.SelectedItems.Count > 0;557 }558 559 partial void OnCopyExecutedImplementation(ExecutedRoutedEventArgs e)560 {561 string text = this.GetClipboardTextForSelectedItems();562 this.SetClipboardWithSelectedItemsText(text);563 }564 565 #region Copy Helpers566 567 /// <summary>568 /// Gets a tab-delimited string representing the data of the selected rows.569 /// </summary>570 /// <returns>A tab-delimited string representing the data of the selected rows.</returns>571 protected internal string GetClipboardTextForSelectedItems()572 {573 StringBuilder text = new StringBuilder();574 575 foreach (object value in this.Items)576 {577 if (this.SelectedItems.Contains(value))578 {579 string entry = this.GetClipboardTextLineForSelectedItem(value);580 text.AppendLine(entry);581 }582 }583 584 return text.ToString();585 }586 587 private string GetClipboardTextLineForSelectedItem(object value)588 {589 StringBuilder entryText = new StringBuilder();590 591 foreach (InnerListColumn column in this.InnerGrid.Columns)592 {593 object propertyValue;594 if (!FilterRuleCustomizationFactory.FactoryInstance.PropertyValueGetter.TryGetPropertyValue(column.DataDescription.PropertyName, value, out propertyValue))595 {596 propertyValue = string.Empty;597 }598 599 entryText.Append(CultureInfo.CurrentCulture, $"{propertyValue}\t");600 }601 602 return entryText.ToString();603 }604 605 private void SetClipboardWithSelectedItemsText(string text)606 {607 if (string.IsNullOrEmpty(text))608 {609 return;610 }611 612 DataObject data = new DataObject(DataFormats.UnicodeText, text);613 Clipboard.SetDataObject(data);614 }615 616 #endregion Copy Helpers617 618 #endregion ApplicationCommands.Copy619 620 /// <summary>621 /// Called to implement sorting functionality on column header pressed by space or enter key.622 /// </summary>623 /// <param name="sender">Typically a GridViewColumnHeader.</param>624 /// <param name="e">The event information.</param>625 private void Header_KeyDown(object sender, KeyEventArgs e)626 {627 if (e.Key != Key.Space && e.Key != Key.Enter)628 {629 return;630 }631 632 // Call HeaderActionProcess when space or enter key pressed633 this.HeaderActionProcess(sender);634 e.Handled = true;635 }636 637 /// <summary>638 /// Called to implement sorting functionality on column header click.639 /// </summary>640 /// <param name="sender">Typically a GridViewColumnHeader.</param>641 /// <param name="e">The event information.</param>642 private void Header_Click(object sender, RoutedEventArgs e)643 {644 // Call HeaderActionProcess when mouse clicked on the header645 this.HeaderActionProcess(sender);646 }647 648 /// <summary>649 /// Called to implement sorting functionality.650 /// </summary>651 /// <param name="sender">Typically a GridViewColumnHeader.</param>652 private void HeaderActionProcess(object sender)653 {654 GridViewColumnHeader header = (GridViewColumnHeader)sender;655 InnerListColumn column = (InnerListColumn)header.Column;656 UIPropertyGroupDescription dataDescription = column.DataDescription;657 658 if (dataDescription == null)659 {660 return;661 }662 663 // If the sorted column is sorted again, reverse the sort \\664 if (object.ReferenceEquals(column, this.sortedColumn))665 {666 dataDescription.ReverseSortDirection();667 }668 669 this.ApplySort(column, true);670 }671 672 /// <summary>673 /// Create default Context Menu.674 /// </summary>675 /// <returns>ContextMenu of List Columns.</returns>676 private ContextMenu GetListColumnsContextMenu()677 {678 this.contextMenu = new ContextMenu();679 680 // Add Context Menu item.681 this.SetColumnPickerContextMenuItem();682 683 return this.contextMenu;684 }685 686 /// <summary>687 /// Set up context menu item for Column Picker feature.688 /// </summary>689 /// <returns>True if it is successfully set up.</returns>690 private bool SetColumnPickerContextMenuItem()691 {692 MenuItem columnPicker = new MenuItem();693 AutomationProperties.SetAutomationId(columnPicker, "ChooseColumns");694 columnPicker.Header = UICultureResources.ColumnPicker;695 columnPicker.Click += this.innerGrid.OnColumnPicker;696 697 this.contextMenu.Items.Add(columnPicker);698 699 return true;700 }701 702 static partial void StaticConstructorImplementation()703 {704 // Adds notification for the View changing705 ListView.ViewProperty.OverrideMetadata(706 typeof(InnerList),707 new PropertyMetadata(new PropertyChangedCallback(InnerList_OnViewChanged)));708 }709 710 #endregion instance private methods711 }712}713 