Steven's profileLunatic ExperimentsPhotosBlogLists Tools Help

Blog


    November 19

    WPF and Powershell: Using a compiled type in XAML(Part 3)

    Here is another example of how to use WPF with Powershell. This example shows how to use a custom control in WPF. This example is somewhat esoteric and does not pertain directly to Powershell, but custom types can make it easier to create different interfaces. Knowing how to do this is important because many of the base WPF types do not expose much useful functionality(e.g. a Button doesn't do anything on it's own and needs to have code added to it's events).

    The first half of the example script compiles a new WPF class derived from the Button class. This new class does not necessarily need to be defined in the script. XAML can use any WPF class from any loaded assembly. This class could be part of a library of custom controls.

    The new class, CountButton, is derived from the Button class in the System.Windows.Controls namespace. The button functionality is extended by making the button count the number of times it is clicked by overriding the OnClick method. The number of times the button is clicked is then exposed by the read only dependancy property ClickCount.

    The second half of the example script contains XAML that will use the new WPF class. In the XAML the "t" xml namespace corresponds to the Test namespace in the embedded C# code. The element name for our CountButton class is then t:CountButton. The example XAML defines a CountButton with the name "countbutton" and the display text as "Click this!" The example XAML also defines a label with it's content bounded to the ClickCount property of the named button.

    When this example script is invoked, the button will keep track of the number of times it is clicked and the label will automatically update it's content.

    Combining example one and this example, a person could display some properties of a collection of objects, and have buttons available to invoke methods on those objects.

    . Library-PresentationInterface
    
    [void](new-cassembly @'
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    
    [assembly:System.Windows.Markup.XmlnsDefinition("http://testschema/test","Test")]
    
    namespace Test {
    	public class CountButton : Button {
    		int _ClickCount = 0;
    		public int ClickCount {get {return _ClickCount;}}
    		internal static readonly DependencyPropertyKey ClickCountKey = DependencyProperty.RegisterReadOnly(
    			"ClickCount", typeof(int), typeof(CountButton),new PropertyMetadata(0));
    		public static readonly DependencyProperty ClickCountProperty = ClickCountKey.DependencyProperty;
    		
    		protected override void OnClick() {
    			this._ClickCount++;
    			base.OnClick();
    			this.SetValue(ClickCountKey, this._ClickCount);
    		}
    	}
    }
    '@)
    
    $result = start-PresentationInterface -xaml ([xml]@'
    <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    	xmlns:t="http://testschema/test"
    	Title="Example 2" SizeToContent="WidthAndHeight">
    	<StackPanel>
    		<t:CountButton Name="countbutton" Width="150" HorizontalAlignment="Left">Click this!</t:CountButton>
    		<StackPanel Orientation="Horizontal">
    			<Label>Button click event count:</Label>
    			<Label Content="{Binding ElementName=countbutton, Path=ClickCount}" />
    		</StackPanel>
    	</StackPanel>
    </Window>
    '@)
    [void](stop-PresentationInterface $result)
    
    November 14

    WPF and Powershell: XAML(Part 2)

    This is the first real example of how WPF could be used by Powershell. It shows how to display a few properties of a collection of objects using XAML and no compiled parts. The XAML in the example script tells WPF how to display the data type we are using(WPF will actually only see the PSObjects that are passed, not the content), and the collection is then handed over to the window.

    This is not the only way to do this. It would also be possible to construct the XAML for each object in the collection individually and not need to use a template.

    . Library-PresentationInterface

    function Get-ProcessDialog {
    $Xaml = [Xml](
    @"
    <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ps="clr-namespace:System.Management.Automation;assembly=System.Management.Automation"
    Name="ThisWindow"
    Title="Formated Process List">
    <Window.Resources>
    <DataTemplate DataType="{x:Type ps:PSObject}">
    <Border BorderThickness='0,0,0,1' BorderBrush='Gray' Padding='3'>
    <StackPanel>
    <TextBlock FontSize='14' FontWeight='Bold' Text="{Binding Properties[ProcessName].Value}" />
    <StackPanel Orientation="Horizontal">
    <StackPanel.Style>
    <Style TargetType="StackPanel">
    <Style.Triggers>
    <DataTrigger Binding="{Binding Properties[CPU].Value}" Value="{x:Null}">
    <Setter Property="Visibility" Value="Collapsed" />
    </DataTrigger>
    </Style.Triggers>
    </Style>
    </StackPanel.Style>
    <TextBlock FontSize='12' Text="CPU: " />
    <TextBlock FontSize='12' Text="{Binding Properties[CPU].Value}" />
    </StackPanel>
    <StackPanel Orientation="Horizontal">
    <StackPanel.Style>
    <Style TargetType="StackPanel">
    <Style.Triggers>
    <DataTrigger Binding="{Binding Properties[WorkingSet].Value}" Value="{x:Null}">
    <Setter Property="Visibility" Value="Collapsed" />
    </DataTrigger>
    </Style.Triggers>
    </Style>
    </StackPanel.Style>
    <TextBlock FontSize='12' Text="Working Set: " />
    <TextBlock FontSize='12' Text="{Binding Properties[WorkingSet].Value}" />
    </StackPanel>
    <StackPanel Orientation="Horizontal">
    <StackPanel.Style>
    <Style TargetType="StackPanel">
    <Style.Triggers>
    <DataTrigger Binding="{Binding Properties[Description].Value}" Value="{x:Null}">
    <Setter Property="Visibility" Value="Collapsed" />
    </DataTrigger>
    </Style.Triggers>
    </Style>
    </StackPanel.Style>
    <TextBlock FontSize='12' Text="Description: " />
    <TextBlock FontSize='12' Text="{Binding Properties[Description].Value}" />
    </StackPanel>
    <StackPanel Orientation="Horizontal">
    <StackPanel.Style>
    <Style TargetType="StackPanel">
    <Style.Triggers>
    <DataTrigger Binding="{Binding Properties[Path].Value}" Value="{x:Null}">
    <Setter Property="Visibility" Value="Collapsed" />
    </DataTrigger>
    </Style.Triggers>
    </Style>
    </StackPanel.Style>
    <TextBlock FontSize='12' Text="Path: " />
    <TextBlock FontSize='12' Text="{Binding Properties[Path].Value}" />
    </StackPanel>
    </StackPanel>
    </Border>
    </DataTemplate>
    </Window.Resources>
    <ScrollViewer>
    <ItemsControl ItemsSource="{Binding ElementName=ThisWindow, Path=Tag}" />
    </ScrollViewer>
    </Window>
    "@
    )

    $result = Start-PresentationInterface -xaml ($Xaml) -WindowTag (
    @($input | select ProcessName, CPU, WorkingSet, Description, Path))
    [void](stop-PresentationInterface $result)
    }

    Get-Process | Get-ProcessDialog

    November 07

    WPF and Powershell: Library-PresentationInterface(Part 1)

    In the past, when it came to generating a GUI using Powershell, the only option was to use System.Windows.Forms.dll to generate the GUI. This method required lots of verbose script, and the result was not always pretty. The PresentationFramework.dll library provided the XAML loader, but the Presentation classes required an STA thread to initialize while the threads that interpreted Powershell scripts are always MTA.

    I really wanted to be able to create WPF windows in Powershell so I created a library script that will define several classes that will handle the initialization of an STA thread that can then handle the initialization of a WPF window.

    Here are a list of a few of the major features of the library script.

    • Windows can be initialized from XAML.
    • Windows can be initialized using the default constructor of a class that derives from the window type.
    • The Tag property can be set prior to initialization. Arguments for the window can be passed here.
    • The value of the Tag property is returned when the window is closed. This can be useful in the case that a bool dialog result is not enough.
    • The window threads operate asynchronously to Powershell. Synchronization is done useing an IAsyncResult object.
    • The Dispatch object that is associated with the window's UI thread is attached to the IAsyncResult object. External threads cannot interact with user interface elements, but can use the Dispatch object to give instructions to the UI thread.

    Later I will be giving four examples of how to use this library.

    1. The first will give a simple example of the use XAML.
    2. The second will be an example of using custom types with XAML.
    3. The third will be an example of using a compiled window type.
    4. The forth example will show how to use the Dispatch object.

    To use this library script you will first need to save a copy of the New-CAssembly script either as a function or as a script file in a path location.

    Use of the library is fairly simple. Simply dot source the library. Then, call the Start-PresentationInterface function passing either the XAML as an XML node to the Xaml parameter, or the type object of any compiled window type to the WindowType parameter. You may also initialize the window's Tag property by passing it to the WindowTag parameter. The result of that function will give an IAsyncResult object. It behaves just like any other IAsyncResult object, and has a IExposeDispatch interface giving it the Dispatch property. That object may be disposed of safely or you can pass it to the Result parameter of the Stop-PresentationInterface function. That function will block the calling thread untill the window has closed. The return value will give the DialogResult of the window and the final value of the Tag property of the window.

    The script for the library is below, but first here is a really simple example to get you started.

    . Library-PresentationInterface
    # Initialize a window
    $result = Start-PresentationInterface -xaml (
    	[xml]'<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"><Viewbox><TextBlock>Welcome to the Presentation Framework!</TextBlock></Viewbox></Window>'
    )
    # Look at the result object
    $result
    # Wait for the window to close
    Stop-PresentationInterface $result
    

    Now the script.

    # Library-PresentationInterface.ps1
    # Version: 1.0
    # Author: LunaticExperimentalist
    # End User License: Public Domain/Unrestricted Use
    # Provided "as is," with out warranty, and without any assertions of suitability
    # for any purpose either express or implied. 
    
    if ($args -contains '-?') { @'
    Library-PresentationInterface
    VERSION: 1.0
    AUTHOR: LunaticExperimentalist
    
    USAGE: Library-PresentationInterface
    
    DESCRIPTION: This library contains two functions and four types. The types are
      only used by the functions and do not need to used directly. The two
      functions, Start-PresentationInterface and Stop-PresentationInterface, provide
      access to the Windows Presentation Framework. 
    
    CONTAINS: Start-PresentationInterface
              Stop-PresentationInterface
    '@
    return
    }
    
    
    [Void]([Reflection.Assembly]::LoadWithPartialName('PresentationFramework'))
    [Void](New-CAssembly @"
    using System;
    using System.Threading;
    using System.Windows;
    
    namespace LibraryPresentationInterface {
    	// structure returned by the stop method
    	public struct PresentationResult {
    		// Contains the value of the DialogResult property of the window
    		public bool DialogResult;
    		// Contains the value of the Tag property of the window
    		// may be used if a bool is insufficient
    		public object WindowTag;
    		public PresentationResult(bool dialogResult, object tag) {
    			DialogResult = dialogResult; WindowTag = tag;
    		}
    	}
    
    	// public interface can give a reference to the dispatcher assosiated with a window
    	public interface IExposeDispatcher {
    		System.Windows.Threading.Dispatcher Dispatcher {get;}
    	}
    	
    	// this is an IAsynResult object returned by the start method
    	// may be passed to the stop method to retrieve the dialog result or
    	// may be discarded safely
    	internal class PresentationAsyncResultImpl : IAsyncResult, IExposeDispatcher {
    		object UserObject;
    		EventWaitHandle Handle;
    		bool Completed;
    		PresentationResult DialogResult;
    		bool IsError;
    		System.Exception Error;
    		System.Windows.Threading.Dispatcher _Dispatcher;
    		
    		public PresentationAsyncResultImpl(object userObject, EventWaitHandle handle) {
    			UserObject = userObject; this.Handle = handle; Completed = false;
    		}
    		// returns the user defined object that was given at the start method
    		public object AsyncState {get{return this.UserObject;}}
    		// returns a wait handle that will be signaled when the window is closed
    		public WaitHandle AsyncWaitHandle {get{return this.Handle;}}
    		// returns false, this is an asyncronous method
    		public bool CompletedSynchronously {get{return false;}}
    		// returns true if the window is closed, or if initialization has failed
    		public bool IsCompleted {get{return this.Completed;}}
    		// returns the dispatcher of the UI thread of the window
    		public System.Windows.Threading.Dispatcher Dispatcher {get {return _Dispatcher;}}
    		// used by the stop mehtod to get the result
    		internal PresentationResult Result {get{
    			if (IsError)
    				throw Error;
    			return DialogResult;
    		}}
    		// used to set the result when the window is closed
    		internal void SetComplete(PresentationResult result) {
    			this.DialogResult = result; this.Completed = true; this.Handle.Set();
    		}
    		// used to set the dispatcher once the window is initialized
    		internal void SetDispatcher(System.Windows.Threading.Dispatcher dispatcher) {
    			_Dispatcher = dispatcher;
    		}
    		// used to set the result to throw an exception
    		internal void SetException(System.Exception ex) {
    			this.IsError = true; this.Error = ex; this.Completed = true;
    			this.Handle.Set();
    		}
    	}
    	
    	// Args passed to the window thread
    	internal class WindowArgs {
    		// [xml]containing the xaml for the window or a [type] for a self describing window type
    		public object WindowInfo;
    		// initial value for the window's tag
    		public object WindowTag;
    		// an internal reference to the IAsyncResult object so the the result can be set
    		public PresentationAsyncResultImpl AsyncResult;
    		// initialization wait handle, signaled once initialization is completed
    		public EventWaitHandle InitHandle;
    		// an initialization exception that, if set, will be thown by the start method
    		public Exception InitException; 
    	}
    	
    	public class Presentation {
    		// internal method to start the window thread
    		private static IAsyncResult InternalStart (object windowInfo, object tag, object userObject) {
    			WindowArgs Args = new WindowArgs();
    			Args.WindowInfo = windowInfo; Args.WindowTag = tag;
    			Args.AsyncResult = new PresentationAsyncResultImpl(userObject, new EventWaitHandle(false, EventResetMode.ManualReset));
    			Args.InitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
    			Args.InitException = null;
    			Thread WindowThread = new Thread(new ParameterizedThreadStart(WindowProc));
    			WindowThread.Name = "Presentation.Thread";
    			WindowThread.SetApartmentState(ApartmentState.STA);
    			WindowThread.Start(Args);
    			Args.InitHandle.WaitOne();
    			if (Args.InitException != null)
    				throw Args.InitException;
    			return Args.AsyncResult;
    		}
    		// public start method to accept xaml
    		public static IAsyncResult Start(System.Xml.XmlNode xaml, object tag, object userObject) {
    			if (xaml == null)
    				throw new ArgumentNullException("xaml");
    			return InternalStart(xaml, tag, userObject);
    		}
    		// public start method to accept a window type
    		public static IAsyncResult Start(Type windowClass, object tag, object userObject) {
    			if (windowClass == null)
    				throw new ArgumentNullException("windowClass");
    			return InternalStart(windowClass, tag, userObject);
    		}
    		// stop method to retrieve the dialog result 
    		public static PresentationResult Stop(IAsyncResult asyncResult) {
    			if (asyncResult == null)
    				throw new ArgumentNullException("asyncResult");
    			if (!(asyncResult is PresentationAsyncResultImpl))
    				throw new ArgumentException("Unrecognised result type.");
    			PresentationAsyncResultImpl Result = (PresentationAsyncResultImpl)asyncResult;
    			Result.AsyncWaitHandle.WaitOne();
    			return Result.Result;
    		}
    		// procedure for the window thread
    		private static void WindowProc(object obj) {
    			WindowArgs Args = (WindowArgs)obj;
    			Window LocalWindow;
    			PresentationResult FinalResult = new PresentationResult();
    			// trap initialization errors
    			try {
    				if (Args.WindowInfo is System.Xml.XmlNode) {
    					// initialize from xaml
    					object XamlObject = System.Windows.Markup.XamlReader.Load(
    						new System.Xml.XmlNodeReader((System.Xml.XmlNode)Args.WindowInfo));
    					if (!(XamlObject is Window))
    						throw new InvalidCastException("The given XAML did not return a Window object.");
    					LocalWindow = (Window)XamlObject;
    				}
    				else {
    					// initialize from type
    					Type WindowType = (Type)Args.WindowInfo;
    					System.Reflection.ConstructorInfo Constructor = WindowType.GetConstructor(Type.EmptyTypes);
    					if (Constructor != null) {
    						LocalWindow = (Window)Constructor.Invoke(new Object[0]);
    					}
    					else {
    						throw new ArgumentException(String.Format("The type\"{0}\" does not have an accessable default constructor.",WindowType.FullName));
    					}
    				}
    				LocalWindow.Tag = Args.WindowTag;
    			}
    			catch (Exception ex) {
    				// set initialization exception
    				Args.InitException = ex;
    				Args.InitHandle.Set();
    				return;
    			}
    			// initialization complete
    			// set the dispatcher
    			Args.AsyncResult.SetDispatcher(LocalWindow.Dispatcher);
    			// set the initialization wait handle
    			Args.InitHandle.Set();
    			// trap runtime exceptions
    			try {
    				FinalResult.DialogResult = (bool)LocalWindow.ShowDialog();
    				FinalResult.WindowTag = LocalWindow.Tag;
    			}
    			catch (Exception ex) {
    				// set the runtime exception to be rethrown by the stop method
    				Args.AsyncResult.SetException(ex);
    				return;
    			}
    			// set the dialog result
    			Args.AsyncResult.SetComplete(FinalResult);
    		}
    	}
    }
    "@)
    
    function Start-PresentationInterface([System.Xml.XmlNode]$Xaml, [Type]$WindowType, $WindowTag, $UserObject) {
    if (($args -contains '-?') -or ((!$Xaml) -and (!$WindowType)) -or (($Xaml) -and ($WindowType))) { @'
    Library-PresentationInterface
    VERSION: 1.0
    AUTHOR: LunaticExperimentalist
    
    USAGE:
      Start-PresentationInterface -Xaml [System.Xml.XmlNode]
    	  [-WindowTag [Object]] [-UserObject [Object]]
      Start-PresentationInterface -WindowType [Type] [-WindowTag [Object]]
    	  [-UserObject [Object]]
    
    PARAMETERS:
      Xaml: An XML node, such as an XML document, in the format of XAML that
        describes the content of a Presentation Framework window. This argument is
        mandatory and mutually exclusive to WindowType.
    
      WindowType: May be any type derived from and including the type
        System.Windows.Window. The type must have a public default constructor.
        The default constuctor of this type is called and the resulting window
        object is displayed. This argument is mandatory and mutually exclusive to
        Xaml.
        
      WindowTag: The value of this argument is applied to the Tag property of the
        window after initialization.
      
      UserObject: This object is not used internally. It is only made available
        though the resulting IAsyncResult object.
    
    RETURNS: An IAsyncResult object that can be used to get the dialog result of
      the window.
    
    DESCRIPTION: Initializes a new window using the information provided by the
      WindowType or Xaml arguments.
    
    SEE ALSO: Stop-PresentationInterface
    '@
    return
    }
    	if ($Xaml) {
    		[LibraryPresentationInterface.Presentation]::Start($Xaml,$WindowTag,$UserObject)
    	}
    	else {
    		[LibraryPresentationInterface.Presentation]::Start($WindowType,$WindowTag,$UserObject)
    	}
    }
    
    function Stop-PresentationInterface([IAsyncResult]$Result) {
    if (($args -contains '-?') -or (!$Result)) { @'
    Stop-PresentationInterface
    VERSION: 1.0
    AUTHOR: LunaticExperimentalist
    
    USAGE:
      Stop-PresentationInterface -Result [IAsyncResult]
    
    PARAMETERS:
      Result: An IAsyncResult object obtained from the Start-PresentationInterface
        function.
    	
    RETURNS: An object of type LibraryPresentationInterface.PresentationResult
      containing dialog result information.
    
    DESCRIPTION: Causes the current thread to wait for the window corresponding the
      given IAsyncResult object to close.
    
    SEE ALSO: Start-PresentationInterface
    '@
    return
    }
    	[LibraryPresentationInterface.Presentation]::Stop($Result)
    }