Laying The Foundation:
Following up from previously. We begin by using, well . using
using namespace Microsoft.PowerShell.Commands using namespace System.Windows.Forms using namespace System.Management.Automation using namespace System.Drawing using namespace System.Collections using namespace System.Collections.Generic
This takes advantage of PowerShell’s ability to pull in entire namespaces of types making most of the uses of Add-Type obsolete. I also use the direct type format whenever possible
$something.EnumProperty = [SomeEnum]::SomeEnumValue
Over
$something.EnumProperty = "SomeEnumValue"
One because its clearer what’s going on, and two because intellisense. That being said my OCD hates seeing a bunch of fully qualified class types in my code so this lets me turn
$Form = [System.Windows.Forms.Form]::new()
into
$Form = [Form]::new()
MUCH better for my neurosis.
Next up we’ll taking advantage of Inheritance just like a C# WinForms project would. We don’t create a base object. Form
# Old way Add-Type -Assembly System.Windows.Forms $form = New-Object Windows.Forms.Form
using namespace System.Windows.Forms
Class ObjectForm : Form {
Form() {}
}
$form - [ObjectForm]::new()
Yes its more lines of code but in the old way is a VARIABLE that is ONLY available in the scope its created in, $formObjectForm on the other hand is a TYPE that can be instantiated ANYWHERE we need it.
Also it is worth noting here that after 100,000 object creations each measured by Measure-Command, in a quick and dirty set of tests including with property assignment, is almost an order of magnitude slower than $Obj = New-Object <SomeType>$Obj = [<SomeType>]::new()
Even is still way faster though this one only works if there is a parameterless constructor, but lets us do fun things like $Obj = [<SomeType>]@{} giving us a $Obj = [<SomeType>=@{Property1="something";Property2=42} of type $Obj with SomeType, and Property1 already set. It does comes at a performance cost the more properties we try to set so Property2 is pretty much the undisputed champ of object creation.::new()
Attending Some Events
We’re going to take a trip to the future to explain this next part. PowerShell’s handling of WinForms Events is much more disjointed than in a pure C# WinForms.
In C# WinForms you assign an Event Handler directly to the event,
Button.Click += new System.EventHandler(this.Button_Click)
and your Event Handler method always has the same basic signature
private void Button_Click(object sender,EventArgs e){}
Which in the end is just syntax sugar to call the underlying method of that control. Since PowerShell does not have access directly to the Events it uses the add_click(<Event Handler Method>)add_ methods to accomplish the same thing.
So assuming we have
Class FormTest : Form {
hidden [Button] $Button
hidden [TextBox] $TextBox
hidden [void] Button_Click([Object] $sender, [EventArgs] $e) {
$this.TextBox.Text = "Clicked"
}
Then we would call
$this.Button.add_click($this.Button_Click)
and promptly error out.
Turns out the FIRST caveat is PowerShell only lets you pass a type into the [ScriptBlock] methods which is basically what a add_<event> is. Function is a Button_Click type.[PSMethod]
The SECOND caveat is inside that ScriptBlock PowerShell automatically assigns to the $thissender object, and to the $_EventArgs object. so even if we had
$this.Button.add_click({$this.TextBox.Text = "Clicked"})
It would again error out as is NOT our form object in this scope.$this
We could work around this like this:
hidden [ScriptBlock] $ButtonClickHandler = { Button_Click($this,$_) }
hidden [void] Button_Click([Object] $sender, [EventArgs] $e) {
$this.TextBox.Text = "Clicked"
}
Then we would call it like:
$this.Button.add_click($this.ButtonClickHandler)
Works great but we’d need one of those script blocks in between EVERY Event Handler method and that seems like a lot of work. Fortunately there is an easier way. Since the signatures of our ScriptBlock and Event Handler methods NEVER CHANGE ( types change but they are all still inherited from EventArgs so to the type system nothing has changed) we just need a way to convert ANY Event Handler Method into the EventArgs PowerShell expects.ScriptBlock
Something like:
hidden [scriptblock] GetHandler([PSMethod] $method) {
return {
Param([object] $sender, [EventArgs] $e)
$method.Invoke($sender,$e)
}.GetNewClosure()
}
or in its nice one-liner
hidden [scriptblock] GetHandler([PSMethod] $method) { return { Param([object] $sender, [EventArgs] $e) $method.Invoke($sender,$e) }.GetNewClosure() }
Now we can call
$Button.add_click($this.GetHandler($this.Button_Click))
EVEN BETTER!!
But we’re still not done. GetHandler is something that even though its just one line would be redundant to include in every Form Class we create so instead we’re back to Inheritance.
Instead of what we have now
using namespace System.Windows.Forms
Class ObjectForm : Form {
hidden [scriptblock] GetHandler([PSMethod] $method) {
return {
Param([object] $sender, [EventArgs] $e)
$method.Invoke($sender,$e)
}.GetNewClosure()
}
[Button] $Button
[TextBox] $TextBox
ObjectForm() {
$this.TextBox = [TextBox]::new()
$this.Button = [Button]::new()
$Button.add_click($this.GetHandler($this.Button_Click))
$this.Controls.Add($this.Button)
$this.Controls.Add($this.TextBox)
}
hidden [void] Button_Click([Object] $sender, [EventArgs] $e) {
$this.TextBox.Text = "Clicked"
}
}
We switch it up like this
using namespace System.Windows.Forms
Class PSForm : Form {
hidden [scriptblock] GetHandler([PSMethod] $method) {
return {
Param([object] $sender, [EventArgs] $e)
$method.Invoke($sender,$e)
}.GetNewClosure()
}
}
Class ObjectForm : PSForm {
[Button] $Button
[TextBox] $TextBox
ObjectForm() {
$this.TextBox = [TextBox]::new()
$this.Button = [Button]::new()
$Button.add_click($this.GetHandler($this.Button_Click))
$this.Controls.Add($this.Button)
$this.Controls.Add($this.TextBox)
}
hidden [void] Button_Click([Object] $sender, [EventArgs] $e) {
$this.TextBox.Text = "Clicked"
}
}
So now we have TWO level of inheritance going on but that change means not ONLY does our type inherit ALL that ObjectForm type has but also ALL that Form has. Meaning it ALSO comes with our PSFormGetHandler method built in.
From here you would probably separate type definition into its own file to dot source or even better into a module that a little PSForm pulls in for us for in ANY script we want to create an inherited Form Class from, and BOOM we’re ready to start rocking.using module PSForm
Next time we’ll work on abstracting away the concept of creating and initializing Form Controls.
