An Eventful Time
Been a while since Last Time but we’re diving back in this time, and we’re actually starting our ObjectForm
class. Today we’re going to layout our Event Handlers so lets get started
# Event Handler for a Textbox resizing when contained in a GroupBox in a SplitContainer hidden [void] TextBox_Resize([object] $sender, [EventArgs] $e) { # GroupBox is set to fill so we change the SplitContainer i.e. the TextBox's Parent's Parent's Parent ([SplitContainer]([TextBox]$sender).Parent.Parent.Parent).SplitterDistance = ([TextBox]$sender).Height * 2; } # Event Handler for a node being selected hidden [void] TreeView_AfterSelect([object] $sender, [TreeViewEventArgs] $e) { # Format the full path to our node and write the resulting Expression to ExpressionTextBox $this.ExpressionTextBox.Text = $this.FormatPath($sender.SelectedNode.FullPath) # Invoke that Expression $Result = Invoke-Expression ",$($this.ExpressionTextBox.Text)" # If we generated an object, write it and its members to our ValueTextBox $this.ValueTextBox.Text = $Result # If we have an object Parse its members if($Result -is [Object]) { $TheMembers = $Result | Get-Member $this.DisplayMembers($this.MethodsDataGridView,($TheMembers | Where-Object { $_.MemberType -eq "Method" })) $this.DisplayMembers($this.PropertiesDataGridView,($TheMembers | Where-Object { $_.MemberType -eq "Property" } )) } } # Event Handler for a node being expanded hidden [void] TreeView_BeforeExpand([object] $sender, [TreeViewCancelEventArgs] $e) { # Check if we have a dummy node, and clear it or return because we already generated our actual nodes with real data if($e.Node.FirstNode -and ($e.Node.FirstNode.Text -eq "...")) { $e.Node.Nodes.Clear() } else { return } # Call our PopulateNode method with the value created from our current path's expression $this.PopulateNode($e.Node, (Invoke-Expression ",$($this.FormatPath($e.Node.FullPath))")) } # Event Handler for when keyboard key is pressed in the Tree View hidden [void] TreeView_KeyUp([object] $sender, [KeyEventArgs] $e) { # We've got the Control Key Down if($e.Control) { # Check our Key Code switch ($e.KeyCode) { # a +? Add one to our FontSize {$_ -in [Keys]::Add,[Keys]::OemPlus} { $this.FontSize++ } # a -? Subtract one from our FontSize, make sure we don't go under 1 {$_ -in [Keys]::Subtract, [Keys]::OemMinus} { $this.FontSize-- } # Zero? reset FontSize to 12 {$_ -eq [Keys]::D0} { $this.FontSize = 12 } # C? Copy node path to clipboard {$_ -eq [Keys]::C} {[Clipboard]::SetText($this.FormatPath($sender.SelectedNode.FullPath)) } # Changed Font Size? Call UpdateFonts {$_ -in [Keys]::Add,[Keys]::Oemplus,[Keys]::Subtract,[Keys]::OemMinus,[Keys]::D0} { $this.UpdateFonts() } # Handled a Key? Say So {$_ -in [Keys]::Add,[Keys]::Oemplus,[Keys]::Subtract,[Keys]::OemMinus,[Keys]::D0,[Keys]::C} { $e.Handled = $true } } } } # Event Handler for rolling the mouse wheel on the form itself hidden [void] ObjectForm_MouseWheel([object] $sender, [MouseEventArgs] $e) { # Is the Control Key Down and our mouse wheel has moved if(([Control]::ModifierKeys -eq [Keys]::Control) -and ($e.Delta -ne 0)) { # Set our Font Size by 1 120th of the wheel delta $this.FontSize += ($e.Delta / 120) # Call UpdateFonts $this.UpdateFonts() # Handled that event $e.Handled = $true } }
Pretty much the same Event Handlers we started with but this time they are actually PART of our class as hidden methods. we’ll roll through them one at a time.
# Event Handler for a Textbox resizing when contained in a GroupBox in a SplitContainer hidden [void] TextBox_Resize([object] $sender, [EventArgs] $e) { # GroupBox is set to fill so we change the SplitContainer i.e. the TextBox's Parent's Parent's Parent ([SplitContainer]([TextBox]$sender).Parent.Parent.Parent).SplitterDistance = ([TextBox]$sender).Height * 2; }
To give a little better look to the layout I’m using a GroupBox control to hold my simple value text boxes and those are held in a horizontal panel of a SplitContainer control. So when the TextBox control’s size changes based on a Font size update we need to move the SplitterDistance of our SplitContainer (i.e. The Parent [SplitContainer] of The Parent [SplitContainer.Panel1 or Panel2] of The Parent [GroupBox] of our TextBox which is our sending object of the Event) to match. So as the font grows or shrinks our SplitContainers will adjust to always fit our items.
Next up:
# Event Handler for a node being selected hidden [void] TreeView_AfterSelect([object] $sender, [TreeViewEventArgs] $e) { # Format the full path to our node and write the resulting Expression to ExpressionTextBox $this.ExpressionTextBox.Text = $this.FormatPath($sender.SelectedNode.FullPath) # Invoke that Expression $Result = Invoke-Expression ",$($this.ExpressionTextBox.Text)" # If we generated an object, write it and its members to our ValueTextBox $this.ValueTextBox.Text = $Result # If we have an object Parse its members if($Result -is [Object]) { $TheMembers = $Result | Get-Member $this.DisplayMembers($this.MethodsDataGridView,($TheMembers | Where-Object { $_.MemberType -eq "Method" })) $this.DisplayMembers($this.PropertiesDataGridView,($TheMembers | Where-Object { $_.MemberType -eq "Property" } )) } }
This one has a little more in it and its the first one where we’ve updated the logic of the Event Handler from the original script in the First Post in this series
Our first difference comes in our first line. In our original source inspiration we have:
## Determine the selected node $nodeSelected = $Sender.SelectedNode ## Walk through its parents, creating the virtual ## PowerShell syntax to access this property. $nodePath = GetPathForNode $nodeSelected
In our new Class Method we start out like this we’re still using the $sender.SelectedNode
property but we only need it once so we pass it directly into our FormatPath
Class Method. We should notice though we’ve tacked on another Property after SelectedNode
FullPath
TreeNode controls have the ability to return a string representation of the current node’s path from the root node all the way up. Instead of stepping through each Node to build our path here we’ll format a different way (But more on that when we get to Part 5). We finally write that value to our ExpressionTextBox control giving the end user the ability to copy and paste right to a powershell prompt
# Format the full path to our node and write the resulting Expression to ExpressionTextBox $this.ExpressionTextBox.Text = $this.FormatPath($sender.SelectedNode.FullPath)
Next change in this Event Handler in our old script
## Now, invoke that PowerShell syntax to retrieve ## the value of the property. $resultObject = Invoke-Expression $nodePath $outputPane.Text = $nodePath ## If we got some output, put the object's member ## information in the text box. if($resultObject) { $members = Get-Member -InputObject $resultObject | Out-String $outputPane.Text += "`n" + $members }
Now in our New Class Event Handler we turn our text expression into an actual value just like in the old script. we’re this version diverges is we first write out our new value to its own TextBox. Where things really change is if our new Value is an actual Object instead of whole sale writing just the results of Get-Member
to a text box we separate the Methods and Properties and send them each to their own DisplayMembers
Class Method (more about that in Part 5)
# Invoke that Expression $Result = Invoke-Expression ",$($this.ExpressionTextBox.Text)" # If we generated an object, write it and its members to our ValueTextBox $this.ValueTextBox.Text = $Result # If we have an object Parse its members if($Result -is [Object]) { $TheMembers = $Result | Get-Member $this.DisplayMembers($this.MethodsDataGridView,($TheMembers | Where-Object { $_.MemberType -eq "Method" })) $this.DisplayMembers($this.PropertiesDataGridView,($TheMembers | Where-Object { $_.MemberType -eq "Property" } )) }
Our next Event Handler
# Event Handler for a node being expanded hidden [void] TreeView_BeforeExpand([object] $sender, [TreeViewCancelEventArgs] $e) { # Check if we have a dummy node, and clear it or return because we already generated our actual nodes with real data if($e.Node.FirstNode -and ($e.Node.FirstNode.Text -eq "...")) { $e.Node.Nodes.Clear() } else { return } # Call our PopulateNode method with the value created from our current path's expression $this.PopulateNode($e.Node, (Invoke-Expression ",$($this.FormatPath($e.Node.FullPath))")) }
Our versions is a little condensed compared to our source version.
We start out pretty much the same as in the old
## Determine the selected node $selectedNode = $TreeViewCancelEventArgs.Node ## If it has a child node that is the placeholder, clear ## the placehoder node. if($selectedNode.FirstNode -and ($selectedNode.FirstNode.Text -eq "...")) { $selectedNode.Nodes.Clear() } else { return }
We don’t need a specific object for our selected node so we just get it from our $e
holding our TreeViewCancelEventArgs
instance. The rest of the logic is identical.
# Check if we have a dummy node, and clear it or return because we already generated our actual nodes with real data if($e.Node.FirstNode -and ($e.Node.FirstNode.Text -eq "...")) { $e.Node.Nodes.Clear() } else { return }
Moving on to the rest of the Event Handler in our old we have
## Walk through its parents, creating the virtual ## PowerShell syntax to access this property. $nodePath = GetPathForNode $selectedNode ## Now, invoke that PowerShell syntax to retrieve ## the value of the property. Invoke-Expression "`$resultObject = $nodePath" ## And populate the node with the result object. PopulateNode $selectedNode $resultObject
There’s no reason to hold onto intermediate variables so we can condense everything into one line. Like we mentioned before we’ll get into that when we get to Part 5.
# Call our PopulateNode method with the value created from our current path's expression $this.PopulateNode($e.Node, (Invoke-Expression ",$($this.FormatPath($e.Node.FullPath))"))
We’re almost to the end of our Event Handlers. Next up we have
# Event Handler for when keyboard key is pressed in the Tree View hidden [void] TreeView_KeyUp([object] $sender, [KeyEventArgs] $e) { # We've got the Control Key Down if($e.Control) { # Check our Key Code switch ($e.KeyCode) { # a +? Add one to our FontSize {$_ -in [Keys]::Add,[Keys]::OemPlus} { $this.FontSize++ } # a -? Subtract one from our FontSize, make sure we don't go under 1 {$_ -in [Keys]::Subtract, [Keys]::OemMinus} { $this.FontSize-- } # Zero? reset FontSize to 12 {$_ -eq [Keys]::D0} { $this.FontSize = 12 } # C? Copy node path to clipboard {$_ -eq [Keys]::C} {[Clipboard]::SetText($this.FormatPath($sender.SelectedNode.FullPath)) } # Changed Font Size? Call UpdateFonts {$_ -in [Keys]::Add,[Keys]::Oemplus,[Keys]::Subtract,[Keys]::OemMinus,[Keys]::D0} { $this.UpdateFonts() } # Handled a Key? Say So {$_ -in [Keys]::Add,[Keys]::Oemplus,[Keys]::Subtract,[Keys]::OemMinus,[Keys]::D0,[Keys]::C} { $e.Handled = $true } } } }
This is a combination of two Event Handlers in our old version KeyPress and KeyUp
## A function to handle key presses on the tree view. ## In this case, we capture ^C to copy the path of ## the object property that we're currently viewing. function OnTreeViewKeyPress { param($Sender, $KeyPressEventArgs) ## [Char] 3 = Control-C if($KeyPressEventArgs.KeyChar -eq 3) { $KeyPressEventArgs.Handled = $true ## Get the object path, and set it on the clipboard $node = $Sender.SelectedNode $nodePath = GetPathForNode $node [System.Windows.Forms.Clipboard]::SetText($nodePath) $form.Close() } elseif([System.Windows.Forms.Control]::ModifierKeys -eq "Control") { if($KeyPressEventArgs.KeyChar -eq '+') { $SCRIPT:currentFontSize++ UpdateFonts $SCRIPT:currentFontSize $KeyPressEventArgs.Handled = $true } elseif($KeyPressEventArgs.KeyChar -eq '-') { $SCRIPT:currentFontSize-- if($SCRIPT:currentFontSize -lt 1) { $SCRIPT:currentFontSize = 1 } UpdateFonts $SCRIPT:currentFontSize $KeyPressEventArgs.Handled = $true } } } ## A function to handle key presses on the form. ## In this case, we handle Ctrl-Plus and Ctrl-Minus ## to adjust font size. function OnKeyUp { param($Sender, $KeyUpEventArgs) if([System.Windows.Forms.Control]::ModifierKeys -eq "Control") { if($KeyUpEventArgs.KeyCode -in 'Add','OemPlus') { $SCRIPT:currentFontSize++ UpdateFonts $SCRIPT:currentFontSize $KeyUpEventArgs.Handled = $true } elseif($KeyUpEventArgs.KeyCode -in 'Subtract','OemMinus') { $SCRIPT:currentFontSize-- if($SCRIPT:currentFontSize -lt 1) { $SCRIPT:currentFontSize = 1 } UpdateFonts $SCRIPT:currentFontSize $KeyUpEventArgs.Handled = $true } elseif($KeyUpEventArgs.KeyCode -eq 'D0') { $SCRIPT:currentFontSize = 12 UpdateFonts $SCRIPT:currentFontSize $KeyUpEventArgs.Handled = $true } } }
We are only concerned with Keys pressed while the Ctrl key is held down so we first check if
# We've got the Control Key Down if($e.Control) {
Which is just a combination of
## [Char] 3 = Control-C if($KeyPressEventArgs.KeyChar -eq 3) {
And
elseif([System.Windows.Forms.Control]::ModifierKeys -eq "Control") {
From our old version.
Next we just need to check the actual key that was just pressed. Since KeyCode is an enum called Keys we can use a switch statement to filter through the possible keys we care about.
# Check our Key Code switch ($e.KeyCode) {
Our first few cases in our switch handle our 2 + and 2 – keys and our 0 key to change our FontSize
class property
# a +? Add one to our FontSize {$_ -in [Keys]::Add,[Keys]::OemPlus} { $this.FontSize++ } # a -? Subtract one from our FontSize, make sure we don't go under 1 {$_ -in [Keys]::Subtract, [Keys]::OemMinus} { $this.FontSize-- } # Zero? reset FontSize to 12 {$_ -eq [Keys]::D0} { $this.FontSize = 12 }
These 3 lines partially replace these lines in the old version
if($KeyPressEventArgs.KeyChar -eq '+') { $SCRIPT:currentFontSize++ UpdateFonts $SCRIPT:currentFontSize $KeyPressEventArgs.Handled = $true } elseif($KeyPressEventArgs.KeyChar -eq '-') { $SCRIPT:currentFontSize-- if($SCRIPT:currentFontSize -lt 1) { $SCRIPT:currentFontSize = 1 } UpdateFonts $SCRIPT:currentFontSize $KeyPressEventArgs.Handled = $true }
And
if($KeyUpEventArgs.KeyCode -in 'Add','OemPlus') { $SCRIPT:currentFontSize++ UpdateFonts $SCRIPT:currentFontSize $KeyUpEventArgs.Handled = $true } elseif($KeyUpEventArgs.KeyCode -in 'Subtract','OemMinus') { $SCRIPT:currentFontSize-- if($SCRIPT:currentFontSize -lt 1) { $SCRIPT:currentFontSize = 1 } UpdateFonts $SCRIPT:currentFontSize $KeyUpEventArgs.Handled = $true } elseif($KeyUpEventArgs.KeyCode -eq 'D0') { $SCRIPT:currentFontSize = 12 UpdateFonts $SCRIPT:currentFontSize $KeyUpEventArgs.Handled = $true }
Next up we handle our Ctrl+C for copying the current nodes to the clipboard
# C? Copy node path to clipboard {$_ -eq [Keys]::C} {[Clipboard]::SetText($this.FormatPath($sender.SelectedNode.FullPath)) }
Which replaces these lines in our old version
## [Char] 3 = Control-C if($KeyPressEventArgs.KeyChar -eq 3) { $KeyPressEventArgs.Handled = $true ## Get the object path, and set it on the clipboard $node = $Sender.SelectedNode $nodePath = GetPathForNode $node [System.Windows.Forms.Clipboard]::SetText($nodePath) $form.Close() }
Our next two cases are special. First if we’d used any of the keys that change our font size we initiate the actual change. We should notice that these keys are a repeat of cases we’ve already processed this is because we are taking advantage of a trait of switch
where if you omit the break
after your logic the switch
continues to fall through to the next case until it reaches the end of the switch’s block or a break
.
# Changed Font Size? Call UpdateFonts {$_ -in [Keys]::Add,[Keys]::Oemplus,[Keys]::Subtract,[Keys]::OemMinus,[Keys]::D0} { $this.UpdateFonts() }
This replaces all the different calls to UpdateFonts $SCRIPT:currentFontSize
in our old version. The biggest difference is since our class is self contained we don’t need the use a of script level variable our Class already has the updated FontSize
so our Method needs no parameters.
Our final special case we fall into sets the Handled flag of our Event so the Event stops bubbling up
# Handled a Key? Say So {$_ -in [Keys]::Add,[Keys]::Oemplus,[Keys]::Subtract,[Keys]::OemMinus,[Keys]::D0,[Keys]::C} { $e.Handled = $true }
Well that Event handler took a LONG time to explain. We are at the final one though.
# Event Handler for rolling the mouse wheel on the form itself hidden [void] ObjectForm_MouseWheel([object] $sender, [MouseEventArgs] $e) { # Is the Control Key Down and our mouse wheel has moved if(([Control]::ModifierKeys -eq [Keys]::Control) -and ($e.Delta -ne 0)) { # Set our Font Size by 1 120th of the wheel delta $this.FontSize += ($e.Delta / 120) # Call UpdateFonts $this.UpdateFonts() # Handled that event $e.Handled = $true } }
Its a fairly equal replacement for
## A function to handle mouse wheel scrolling. ## In this case, we translate Ctrl-Wheel to zoom. function OnMouseWheel { param($Sender, $MouseEventArgs) if( ([System.Windows.Forms.Control]::ModifierKeys -eq "Control") -and ($MouseEventArgs.Delta -ne 0)) { $SCRIPT:currentFontSize += ($MouseEventArgs.Delta / 120) if($SCRIPT:currentFontSize -lt 1) { $SCRIPT:currentFontSize = 1 } UpdateFonts $SCRIPT:currentFontSize $MouseEventArgs.Handled = $true } }
And that’s it all our Event Handlers and the changes they went through to be part of our Class.
Next time we’ll explore the other large group of hidden Methods in our Class.