|
|
November 19 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 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 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.
- The first will give a simple example of the use XAML.
- The second will be an example of using custom types with XAML.
- The third will be an example of using a compiled window type.
- 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)
}
|