PowerShell/src/Microsoft.PowerShell.GraphicalHost/commandHelpers/outgridview.cs
PowerShell Team c748652c34 Copy all mapped files from [SD:725290]
commit 8cec8f150da7583b7af5efbe2853efee0179750c
2016-07-28 23:23:03 -07:00

602 lines
24 KiB
C#

//-----------------------------------------------------------------------
// <copyright file="OutGridView.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <summary>
// PowerShell Graphical Out-GridView WPF support
//</summary>
//-----------------------------------------------------------------------
namespace Microsoft.Management.UI.Internal
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Management.Automation;
using System.Threading;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
/// <summary>
/// OutGridViewWindow definition for PowerShell command out-gridview.
/// </summary>
internal class OutGridViewWindow
{
#region private Fields
/// <summary>
/// Zoom Increments
/// </summary>
private const double ZOOM_INCREMENT = 0.2;
/// <summary>
/// Max ZoomLevel
/// </summary>
private const double ZOOM_MAX = 3.0;
/// <summary>
/// Min ZoomLevel
/// </summary>
private const double ZOOM_MIN = 0.5;
/// <summary>
/// Window for gridView.
/// </summary>
private Window gridViewWindow;
/// <summary>
/// Local ManagementList.
/// </summary>
private ManagementList managementList;
/// <summary>
/// A collection of PSObjects to be data bound to the local Management List.
/// </summary>
private ObservableCollection<PSObject> listItems;
/// <summary>
/// Event used for the thread gridViewWindows signaling main thread after Windows loaded
/// </summary>
private AutoResetEvent gridViewWindowLoaded;
/// <summary> Is used to store any Management list calls exceptions. </summary>
private Exception exception = null;
/// <summary>
/// Is used to block thread of the pipeline.
/// </summary>
private AutoResetEvent closedEvent;
/// <summary>
/// OK Button's content.
/// </summary>
private static readonly string OKButtonContent = Microsoft.Management.UI.Internal.XamlLocalizableResources.OutGridView_Button_OK;
/// <summary>
/// Cancel Button's content.
/// </summary>
private static readonly string CancelButtonContent = Microsoft.Management.UI.Internal.XamlLocalizableResources.OutGridView_Button_Cancel;
/// <summary>
/// Used to store selected items in the ok processing
/// </summary>
private List<PSObject> selectedItems;
/// <summary>
/// The GUI thread of Out-GridView
/// </summary>
private Thread guiThread;
/// <summary>
/// private constants for ZoomLevel
/// </summary>
private double zoomLevel = 1.0;
#endregion private Fields
#region internal Constructors
/// <summary>
/// Constructor for OutGridView.
/// </summary>
internal OutGridViewWindow()
{
// Initialize the data source collection.
this.listItems = new ObservableCollection<PSObject>();
}
#endregion internal Constructors
#region private delegates
/// <summary>
/// ThreadDelegate definition.
/// </summary>
/// <param name="arg">Start GridView Window delegate.</param>
private delegate void ThreadDelegate(object arg);
#endregion private delegates
#region Private method that are intended to be called by the Out-GridView cmdlet.
/// <summary>
/// Start a new thread as STA for gridView Window.
/// </summary>
/// <param name="invocation">commands of the PowerShell.</param>
/// <param name="outputModeOptions">selection mode of the list</param>
/// <param name="closedEvent">closedEvent</param>
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "The method is called using reflection.")]
private void StartWindow(string invocation, string outputModeOptions, AutoResetEvent closedEvent)
{
this.closedEvent = closedEvent;
this.gridViewWindowLoaded = new AutoResetEvent(false);
ParameterizedThreadStart threadStart = new ParameterizedThreadStart(
new ThreadDelegate(delegate
{
try
{
this.gridViewWindow = new Window();
this.managementList = CreateManagementList(outputModeOptions);
this.gridViewWindow.Loaded += new RoutedEventHandler(this.GridViewWindowLoaded);
this.gridViewWindow.Content = CreateMainGrid(outputModeOptions);
this.gridViewWindow.Title = invocation;
this.gridViewWindow.Closed += new EventHandler(this.GridViewWindowClosed);
RoutedCommand plusSettings = new RoutedCommand();
KeyGestureConverter keyGestureConverter = new KeyGestureConverter();
try
{
plusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomIn1Shortcut));
plusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomIn2Shortcut));
plusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomIn3Shortcut));
plusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomIn4Shortcut));
this.gridViewWindow.CommandBindings.Add(new CommandBinding(plusSettings, ZoomEventHandlerPlus));
}
catch (NotSupportedException)
{
//localized has a problematic string - going to default
plusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString("Ctrl+Add"));
plusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString("Ctrl+Plus"));
this.gridViewWindow.CommandBindings.Add(new CommandBinding(plusSettings, ZoomEventHandlerPlus));
};
RoutedCommand minusSettings = new RoutedCommand();
try
{
minusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomOut1Shortcut));
minusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomOut2Shortcut));
minusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomOut3Shortcut));
minusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomOut4Shortcut));
this.gridViewWindow.CommandBindings.Add(new CommandBinding(minusSettings, ZoomEventHandlerMinus));
}
catch (NotSupportedException)
{
//localized has a problematic string - going to default
minusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString("Ctrl+Subtract"));
minusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString("Ctrl+Minus"));
this.gridViewWindow.CommandBindings.Add(new CommandBinding(minusSettings, ZoomEventHandlerMinus));
}
this.gridViewWindow.ShowDialog();
}
catch (Exception e)
{
// Store the exception in a local variable that will be checked later.
if (e.InnerException != null)
{
this.exception = e.InnerException;
}
else
{
this.exception = e;
}
}
}));
guiThread = new Thread(threadStart);
guiThread.SetApartmentState(ApartmentState.STA);
guiThread.Start();
}
/// <summary>
/// Implements ZoomIn
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ZoomEventHandlerPlus(object sender, ExecutedRoutedEventArgs e)
{
if (this.zoomLevel == 0)
{
this.zoomLevel = 1;
}
if (this.zoomLevel < ZOOM_MAX)
{
this.zoomLevel = this.zoomLevel + ZOOM_INCREMENT;
Grid g = this.gridViewWindow.Content as Grid;
if (g != null)
{
g.LayoutTransform = new ScaleTransform(this.zoomLevel, this.zoomLevel, 0, 0);
}
}
}
/// <summary>
/// Implements ZoomOut
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ZoomEventHandlerMinus(object sender, ExecutedRoutedEventArgs e)
{
if (this.zoomLevel >= ZOOM_MIN)
{
this.zoomLevel = this.zoomLevel - ZOOM_INCREMENT;
Grid g = this.gridViewWindow.Content as Grid;
if (g != null)
{
g.LayoutTransform = new ScaleTransform(this.zoomLevel, this.zoomLevel, 0, 0);
}
}
}
/// <summary>
/// Creates a new ManagementList.
/// </summary>
/// <param name="outputMode">Output mode of the out-gridview</param>
/// <returns>A new ManagementList</returns>
private ManagementList CreateManagementList(string outputMode)
{
ManagementList newList = new ManagementList();
newList.ViewSaverUserActionState = UserActionState.Hidden;
newList.ViewManagerUserActionState = UserActionState.Hidden;
newList.List.VerticalAlignment = VerticalAlignment.Stretch;
newList.List.SetValue(Grid.RowProperty, 0);
newList.List.SelectionMode = (outputMode == "Single") ? SelectionMode.Single : SelectionMode.Extended;
return newList;
}
/// <summary>
/// Creates a new main grid for window.
/// </summary>
/// <param name="outputMode">Output mode of the out-gridview</param>
/// <returns>A new mainGrid</returns>
private Grid CreateMainGrid(string outputMode)
{
Grid mainGrid = new Grid();
mainGrid.RowDefinitions.Add(new RowDefinition());
mainGrid.RowDefinitions[0].Height = new GridLength(1, GridUnitType.Star);
mainGrid.Children.Add(managementList);
if (outputMode == "None")
{
return mainGrid;
}
// OK and Cancel button should only be displayed if OutputMode is not None.
mainGrid.RowDefinitions.Add(new RowDefinition());
mainGrid.RowDefinitions[1].Height = GridLength.Auto;
mainGrid.Children.Add(CreateButtonGrid());
return mainGrid;
}
/// <summary>
/// Creates a OK button.
/// </summary>
/// <returns>A new buttonGrid</returns>
private Grid CreateButtonGrid()
{
Grid buttonGrid = new Grid();
//// This will allow OK and Cancel to have the same width
buttonGrid.SetValue(Grid.IsSharedSizeScopeProperty, true);
buttonGrid.ColumnDefinitions.Add(new ColumnDefinition());
buttonGrid.ColumnDefinitions.Add(new ColumnDefinition());
buttonGrid.ColumnDefinitions[0].Width = GridLength.Auto;
buttonGrid.ColumnDefinitions[0].SharedSizeGroup = "okCancel";
buttonGrid.ColumnDefinitions[1].Width = GridLength.Auto;
buttonGrid.ColumnDefinitions[1].SharedSizeGroup = "okCancel";
buttonGrid.HorizontalAlignment = HorizontalAlignment.Right;
buttonGrid.SetValue(Grid.RowProperty, 1);
//// This will add OK and Cancel button to buttonGrid.
buttonGrid.Children.Add(CreateOKButton());
buttonGrid.Children.Add(CreateCancelButton());
return buttonGrid;
}
/// <summary>
/// Creates a OK button.
/// </summary>
/// <returns>A new OK button</returns>
private Button CreateOKButton()
{
Button ok = new Button();
ok.Content = OKButtonContent;
ok.Margin = new Thickness(5);
ok.Padding = new Thickness(2);
ok.SetValue(Grid.ColumnProperty, 0);
ok.IsDefault = true;
ok.SetValue(AutomationProperties.AutomationIdProperty, "OGVOK");
ok.Click += new RoutedEventHandler(OK_Click);
return ok;
}
/// <summary>
/// Creates a Cancel button.
/// </summary>
/// <returns>A new Cancel button</returns>
private Button CreateCancelButton()
{
Button cancel = new Button();
cancel.Content = CancelButtonContent;
cancel.Margin = new Thickness(5);
cancel.Padding = new Thickness(2);
cancel.SetValue(Grid.ColumnProperty, 1);
cancel.IsCancel = true;
cancel.SetValue(AutomationProperties.AutomationIdProperty, "OGVCancel");
cancel.Click += new RoutedEventHandler(Cancel_Click);
return cancel;
}
/// <summary>
/// Store the selected items for use in EndProcessing
/// </summary>
/// <param name="sender">event sender</param>
/// <param name="e">event arguments</param>
private void OK_Click(object sender, RoutedEventArgs e)
{
if (this.managementList.List.SelectedItems.Count != 0)
{
this.selectedItems = new List<PSObject>();
foreach(PSObject obj in this.managementList.List.SelectedItems)
{
this.selectedItems.Add(obj);
}
}
this.gridViewWindow.Close();
}
/// <summary>
/// Closes the window
/// </summary>
/// <param name="sender">event sender</param>
/// <param name="e">event arguments</param>
private void Cancel_Click(object sender, RoutedEventArgs e)
{
this.gridViewWindow.Close();
}
/// <summary>
/// Gets selected items from List.
/// </summary>
/// <returns>Selected items of the list</returns>
private List<PSObject> SelectedItems()
{
return this.selectedItems;
}
/// <summary>
/// Closes the window
/// </summary>
public void CloseWindow()
{
this.gridViewWindow.Dispatcher.Invoke(new ThreadStart(delegate { this.gridViewWindow.Close(); }));
}
/// <summary>
/// Add column defenitions to the underlying management list.
/// </summary>
/// <param name="propertyNames">An array of property names to add.</param>
/// <param name="displayNames">An array of display names to add.</param>
/// <param name="types">An array of types to add.</param>
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "The method is called using reflection.")]
private void AddColumns(string[] propertyNames, string[] displayNames, Type[] types)
{
// Wait for the gridViewWindow thread to signal that loading of Window is done
if (this.gridViewWindowLoaded != null)
{
this.gridViewWindowLoaded.WaitOne();
this.gridViewWindowLoaded = null;
}
this.managementList.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Normal,
new Action(
delegate()
{
// Pick the length of the shortest incoming arrays. Normally all incoming arrays should be of the same length.
int length = propertyNames.Length;
if (length > displayNames.Length)
{
length = displayNames.Length;
}
if (length > types.Length)
{
length = types.Length;
}
try
{
// Clear all columns in case the view is changed.
this.managementList.List.Columns.Clear();
// Clear column filter rules.
this.managementList.AddFilterRulePicker.ColumnFilterRules.Clear();
// Add columns with provided names and Types.
for (int i = 0; i < propertyNames.Length; ++i)
{
DataTemplate dataTemplate;
bool haveTemplate = this.managementList.FilterRulePanel.TryGetContentTemplate(types[i], out dataTemplate);
InnerListColumn column = null;
if(haveTemplate)
{
column = new InnerListColumn(new UIPropertyGroupDescription(propertyNames[i], displayNames[i], types[i]));
}
else
{
column = new InnerListColumn(new UIPropertyGroupDescription(propertyNames[i], displayNames[i], typeof(string)));
}
this.managementList.AddColumn(column);
}
this.managementList.List.SetColumnHeaderActions();
if(this.managementList.List.ItemsSource == null)
{
// Setting ItemsSource implicitly regenerates all columns.
this.managementList.List.ItemsSource = this.listItems;
}
// Set focus on ListView
this.managementList.List.SelectedIndex = 0;
this.managementList.List.Focus();
}
catch (Exception e)
{
// Store the exception in a local variable that will be checked later.
if(e.InnerException != null)
{
this.exception = e.InnerException;
}
else
{
this.exception = e;
}
}
}));
}
/// <summary>
/// Add an item to ObservableCollection.
/// </summary>
/// <param name="value">PSObject of comlet data.</param>
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "The method is called using reflection.")]
private void AddItem(PSObject value)
{
if (this.GetWindowClosedStatus())
{
return;
}
this.managementList.Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.Normal,
new Action(
delegate()
{
try
{
this.listItems.Add(value);
}
catch (Exception e)
{
// Store the exception in a local variable that will be checked later.
if(e.InnerException != null)
{
this.exception = e.InnerException;
}
else
{
this.exception = e;
}
}
}));
}
/// <summary>
/// Returns the state of GridView Window.
/// </summary>
/// <returns>The status of GridView Window close or not.</returns>
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "The method is called using reflection.")]
private bool GetWindowClosedStatus()
{
if (this.closedEvent == null)
{
return false;
}
return this.closedEvent.WaitOne(0);
}
/// <summary>
/// Returns any exception that has been thrown by previous method calls.
/// </summary>
/// <returns>The thrown and caught exception. It returns null if no exceptions were thrown by any previous method calls.</returns>
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "The method is called using reflection.")]
private Exception GetLastException()
{
Exception local = this.exception;
if(local != null)
{
// Clear the caught exception.
this.exception = null;
return local;
}
return this.exception;
}
#endregion Private method that are intended to be called by the Out-GridView cmdlet.
#region Private methods
/// <summary>
/// GridView Window is closing callback process.
/// </summary>
/// <param name="sender">The sender object.</param>
/// <param name="e">Event Args.</param>
private void GridViewWindowClosed(object sender, EventArgs e)
{
if (this.closedEvent != null && !this.closedEvent.SafeWaitHandle.IsClosed)
{
try
{
this.closedEvent.Set();
}
catch(ObjectDisposedException)
{
// we tried to avoid this exception with "&& !this.closedEvent.SafeWaitHandle.IsClosed"
// but since this runs in a different thread the if condition could be evaluated and after that
// the handle disposed
}
}
}
/// <summary>
/// Set loaded as true when this method invoked.
/// </summary>
/// <param name="sender">The sender object.</param>
/// <param name="e">RoutedEvent Args.</param>
private void GridViewWindowLoaded(object sender, RoutedEventArgs e)
{
// signal the main thread
this.gridViewWindowLoaded.Set();
// Make gridview window as active window
this.gridViewWindow.Activate();
// Set up AutomationId and Name
AutomationProperties.SetName(this.gridViewWindow, GraphicalHostResources.OutGridViewWindowObjectName);
AutomationProperties.SetAutomationId(this.gridViewWindow, "OutGridViewWindow");
}
#endregion Private methods
}
}