# gui-classes.psm1 - Gui class definitions and factories
#

# This set of objects provide a set of classes that can drive a GUI type application.
#
# Note that they are contained in one file.  It was attempted but became an onorous task
# to split the individual classes into separate files. Caching, loading, referencing were
# all problematic due in most part to the nature of PowerShell's overlay on top of .NET.
#
# History:
# EC8A01 - initial

using namespace System.Collections;
using namespace System.Management.Automation;

set-strictmode -version 2.0

<#.std-parms
.parm $peer Windows.Forms component displaying @@c.Elem contents
.parm $dgv subject DataGridView
.parm $name name to lookup
.parm $pts Points size of font
.parm $family Font family to use
.parm $em contextual mate
.parm $col color code
.parm $h height in pixels
.parm $w width in pixels
.parm $lft left margin width in pixels
.parm $rgt right margin width in pixels
.parm $top top margin width in pixels
.parm $bot bottom in pixels
#>

<#.md
.desc
**Elem** is the base object for both the @@link.area-obj and the @@link.gizmo-obj.
.details
**Elem** is the base object for both the @@link.area-obj and the @@link.gizmo-obj. It is the wrapper for
the @@link.peer which is the @@wpf.forms/WPF:Form:Object the same name.  For this reason the @@wpf.forms/WPF:Forms:namespace cannot be
`used` in this and other modules.

The `peer.tag` field points back at the **Elem** object.

Many standard methods are bound to this object. A few inspect the object they are called on behalf of
and modify their behavoir accordingly.  Alternately, the standard method\'s behavoir can be changed by overriding the method in the subclass.



#### Render Modifiers ####

 * **Font**

 
Set with the @@m.Elem-setFont. Must be registered with
* **Color**
Set with the @@m.Elem-backColor, @@m.Elem-foreColor. See @@rgb to generate and register colors. Set with the @x@Elem.peer.setBackColor and @x@Elem.peer.setForeColor to directly change the value Must be registered with
#### Note on Methods #### Many methods are are 'Chainable' in that they return the *Elem* instance. This means several methods can be used using the return value of the previous method call. If not used or not assigned to a $vbl cast to [void] to avoid PowerShell issues of "Cannot convert the "System.Object[]" #>
class Elem { #---- properties ---- [string] $type; [string] $name; [boolean] $bGizmo = $false; #: Gizmo not Area if true. set $true by specific constructor. [boolean] $bLayDirty = $false; #: rebuild layout, it is dirty. set $true when events require layout change. [boolean] $bHorz = $false; #: Use horizontal layout. set $true by @@m.Elem-setHorz [boolean] $bFill = $false; #: fill Gizmo to container. set $true by @@m.Elem-setFill [boolean] $bHidden = $false; #: area not displayed. set by @@m.Elem-setHidden [boolean] $bUseVScr = $false; #: True if to manage Vert Scroll, Set externally. [Form] $form = $null; #: owning form. null for form itself [Elem] $par = $null; #: immediate parent. set by constructor [int32] $wid = 0; #: calculated width [int32] $hgt = 0; #: calculated height [int32] $dynX = 0; [int32] $dynY = 0; [int32] $biasX = 0; [int32] $biasY = 0; [int32] $posX = 0; [int32] $posY = 0; [int32] $marTop = 0; # inner margins of kids on first/last elem [int32] $marBot = 0; [int32] $marLft = 0; [int32] $marRgt = 0; [int32] $gapX = 0 # inner spacing of kids when not first last; [int32] $gapY = 0; [int32] $minW = 0; [int32] $minH = 0; [int32] $maxW = 0; #EC9B28 - scroll support [int32] $maxH = 0; [int32] $reqW = 0; #EC9B28 - actual required [int32] $reqH = 0; [int32] $dynH = 0; #EC9B28 - dynamic extra for scroll [int32] $scrW = 0; [int32] $absW = 0; [int32] $absH = 0; [int32] $adjW = 0; [int32] $adjH = 0; [int32] $peerW = 0; # Original peer size [int32] $peerH = 0; [System.Object] $peer; #: The @@link.peer we manage with this class [Elem] $mate; # To connect elems (eg clear link for logger) [System.Object] $tied; #: Related object (such as @@link.Task-cls [ElemMate] $clickMate; #: onClick with context. V2 handler superceeds onClick [ElemMate] $focusMate; #: onFocusLost with context. [boolean] $bHaveCH; # have installed onClick handler [boolean] $bHaveFH; # have installed onFocusLost handler [boolean] $elemDisabled; # future #---------- evnt handlers ------ [scriptblock] $onClick; # for clickable gizmos (Obsolete) #---- constructors ---- #---- methods ---- <#.md .desc Returns a summary of the *Elem* instance #>
[String]ToString() { if ($this.name) { return "$($this.type).$($this.name)[$($this.detailStr())]"; } else { return "$($this.type)[$($this.detailStr())]"; } } <#.md .desc Returns a detailed summary of the *Elem* instance #>
[String]detailStr() { $g = ""; if ($this.bGizmo) {$g = "g,";} if ($this.bHidden) {$g += "H,";} $s = "$g"+ "wh=$($this.wid),h=$($this.hgt)"+ " xy=$($this.posX),$($this.posY)"+ " dyn=$($this.dynX),$($this.dynY)"+ " bz=$($this.biasX),$($this.biasY)"+ " gap=$($this.gapX),$($this.gapY)"+ " mar=$($this.marLft),$($this.marRgt),$($this.marTop),$($this.marBot)"+ #X Y seq " min=$($this.minW),$($this.minH)"+ " abs=$($this.absW),$($this.absH)"+ " adj=$($this.adjW),$($this.adjH)"+ " peer=$($this.peerW),$($this.peerH)/$($this.peer.width),$($this.peer.height)"; if ($this.bGizmo) { $s += " giz=$($this.peer.width),$($this.peer.height)" } return $s; } <#.md .parm $peer The @@wpf.forms/Form:Object .desc Used by a constructor to set the @@link.peer. #>
[void]setPeer([System.Object]$peer) { $this.peer = $peer; $peer.tag = $this; if (!$this.bGizmo) { $peer.width = $this.wid; $peer.height= $this.hgt; } } # Note: These are chainable. If not used or not assigned to a $vbl cast to [void] to avoid PowerShell issues of # "Cannot convert the "System.Object[]" <#.md .desc sets the $bHorz flag to $true. See @@link.layout-alg. Chainable. #>
[Elem]setHorz () {$this.bHorz = $true; return $this; } <#.md .desc sets the $bFill flag to $true. See @@link.layout-alg. Chainable. #>
[Elem]setFill () {$this.bFill = $true; return $this; } <#.md .desc sets the left and right margins to $x. sets top and bottom mat=rigins to $y. See @@link.layout-alg. Chainable. #>
[Elem]setMar ([int32]$x,[int32]$y) {$this.marLft = $x; $this.marRgt = $x; $this.marTop = $y; $this.marBot = $y; return $this; } <#.md .parm $x width in pixels. .parm $y height in pixels. .desc sets the left, right, top and bottom margins. See @@link.layout-alg. Chainable. #>
[Elem]setMar ([int32]$lft,[int32]$rgt,[int32]$top,[int32]$bot) {$this.marLft = $lft; $this.marRgt = $rgt; $this.marTop = $top; $this.marBot = $bot; return $this;} <#.md .desc sets the left and right gap (padding). See @@link.layout-alg. Chainable. #>
[Elem]setGap ([int32]$w,[int32]$h) {$this.gapX = $w; $this.gapY = $h; return $this;} <#.md .desc sets the absolute width and height of the *Elem*. See @@link.layout-alg. Chainable. #>
[Elem]setAbs ([int32]$w,[int32]$h) {$this.absW = $w; $this.absH = $h; return $this;} <#.md .desc sets the background color of the *Elem*. Chainable. #>
[Elem]setBackColor ([string]$col) {$this.peer.BackColor = $col; return $this;} <#.md .desc sets the foreground (text) color of the *Elem*. Chainable. #>
[Elem]setForeColor ([string]$col) {$this.peer.ForeColor = $col; return $this;} <#.md .desc sets the name of the *Elem*. Typically used to find element using @@m.Form-findElem. Chainable. #>
[Elem]setName ([string]$name) {$this.name = $name; return $this;} <#.md .desc sets the text of the @@link.peer for the the *Elem*. Chainable. #>
[Elem]setText ([string]$text) {$this.peer.text = $text; return $this;} <#.md .desc sets the background (text) color of the *Elem* using the registered color $col. Chainable. #>
[Elem]backColor ([string]$col) {$this.setBackColor($this.color($col)); return $this;} ## color chart lookup <#.md .desc sets the foreground (text) color of the *Elem* using the registered color $col. Chainable. #>
[Elem]foreColor ([string]$col) {$this.setForeColor($this.color($col)); return $this;} <#.md .desc sets the docking flag of the *Elem*. See @@link.layout-alg. Chainable. #>
[Elem]dock ([string]$dock) {$this.peer.Dock = $dock; return $this;} <#.md .parm $fontName registered font name. See .desc sets the font of the *Elem* using a registered font style name. See @@m.Form-regFont. Chainable. #>
[Elem]setFont ([string]$fontName) {$this.peer.Font = $this.font($fontName); return $this;} <#.md .parm $hide $true to hide, $false to reveal .desc Hides or shows a *Elem* according to the $hide parameter. Chainable. The *Elem* still occupies space. See @@m.Elem-setHidden. #>
[Elem]hidePeer ([boolean]$hide) {$this.peer.visible = !($hide); return $this;} <#.md .desc sets the maximum height of the *Elem*. Required for scroll support. See @@link.layout-alg. Chainable. #>
[Elem]setMaxHgt ([int32]$h) { #EC9B28 - scroll support, EC2707 - Added screen size support $this.maxH = $h; if ($h -lt 0) { $this.maxH = [System.Windows.Forms.SystemInformation]::PrimaryMonitorSize.Height + $h; } return $this; } <#.md .desc sets the maximum width of the *Elem*. Required for scroll support. See @@link.layout-alg. Chainable. #>
[Elem]setMaxWid ([int32]$w) { #EC9B28 - scroll support, EC2707 - Added screen size support $this.maxW = $w; if ($w -lt 0) { $this.maxW = [System.Windows.Forms.SystemInformation]::PrimaryMonitorSize.Width + $w; } return $this; } <#.md .parm $hide $true to hide, $false to reveal .desc Hides or shows a *Elem* and its @@link.peer according to the $hide parameter. Chainable. The *Elem* will not occupy space if hidden. See @@m.Elem-hidePeer. #>
[Elem]setHidden ([boolean]$hide) { $this.peer.visible = !$hide; $this.bHidden = $hide; return $this; } <#.md .parm $disable $true to disable, $false to enable .desc Enables or Disables an *Elem*. Only meaningful for @@c.Gizmo. Chainable. .details This is a virtual disabling and requires the GUI logic to ignore a 'disabled' element. #>
[Elem]setDisabled ([boolean]$disable) { $this.elemDisabled = $disable; return $this; } <#.md .desc Creates an onClick handler. If $em is null it removes the handler. .details The onclick handler will be passed the @@Elem and the ctx object value from the @@ElemMate If the link is diabled the handler is not called even if this interface is called. #>
[Elem]clickHandler([ElemMate] $em) { if ($em -eq $null) { $this.clickMate = $null; $this.bHaveCH = $false; } else { $this.clickMate = $em; if (!$this.bHaveCH) { # avoid double entries $this.bHaveCH = $true; $this.peer.add_Click({ $peer = $this; #hlog("clicked $peer"); $elem = $peer.tag; if ($elem.clickMate -ne $null) { if ($elem.elemDisabled) {return;} [void](& $elem.clickMate.func $elem $elem.clickMate.ctx) } }); } } return $this; } <#.md .desc Creates an onLostFocus handler. If $em is null it removes the handler. .details The onLostFocus handler will be passed the @@Elem and the ctx object value from the @@ElemMate This can be used to validate the input #>
[Elem]focusHandler([ElemMate] $em) { if ($em -eq $null) { $this.focusMate = $null; $this.bHaveFH = $false; } else { $this.focusMate = $em; if (!$this.bHaveFH) { # avoid double entries $this.bHaveFH = $true; $this.peer.add_LostFocus({ $peer = $this; #hlog("clicked $peer"); $elem = $peer.tag; if ($elem.focusMate -ne $null) { [void](& $elem.focusMate.func $elem $elem.focusMate.ctx) } }); } } return $this; } <#.md .desc Register the `elem.name` using @@m.Form-regElem to be used by @@m.Form-findElem .details Chainable #>
[Elem]regName() { $this.form.regElem($this,$this.name); return $this; } <#.md .desc returns color registered with @@m.Form-regColor .details #> # configurable color chart by name. Set up hashTable when creating form
[string]color([string]$name) { if ($this.form -eq $null) {return "#FF0080"} if ($this.form.colors[$name] -eq $null) {return "#C00000"} return $this.form.colors[$name]; } # configurable font pool by name. Set up hashTable when creating form <#.md .desc returns font registered with @@m.Form-regFont .details #>
[System.Drawing.Font]font([string]$name) { if ($this.form -eq $null) {throw 'expected form ptr'} if ($this.form.fonts[$name] -eq $null) {throw "lost font $name"} return $this.form.fonts[$name]; } <#.md .desc Insert *Elem* address into given object *hook* field. Chainable. .details This legacy method allows an event handler to access an *Elem* #>
[Elem]hook([Object]$obj) { $obj.hook($this); return $this; } } <#.md .desc Provide handling context for a click in a Elem .details Stored in the Elem to act as router and context for a click Supercedes the @@Elem.onClick handler mechanism as it contains both the handler and a context object. #>
class ElemMate { #---- properties ---- [string] $type; [scriptBlock] $func; [object] $ctx; #---- constructors ---- <#.md .desc Create ElemMate to be used in @@Elem.clickHandler .details #> ElemMate([string]$type,[scriptBlock]$func,[object]$ctx) { $this.type = $type $this.func = $func; $this.ctx = $ctx; if ($type -eq 'click') { #allow future uses if ($func -eq $null) {throw "type click ElemMate requires click function"} } elseif ($type -eq 'focus') { if ($func -eq $null) {throw "type focus ElemMate requires validate function"} } else { throw "type $type not supported as ElemMate" } } } <#.md .desc Areas are containers that have certain layout characteristics and contain other areas or Gizmos .details The Area class implements the Windows.Forms.Panel object. It acts as a container for other Area objects or Gizmos and contains the *layout algorithm* . ### Layout Algorithm {layout-alg} ### The layout algorithm sets the size of outermost window @@c.Form and the size and position of all components contained within the @@c.Form. It is implemented by the @@m.Area-layout. The Workflow application is a heavy user of the various GUI facilities and can be referenced for working examples. ##### Layout Goal ##### It layout algorithm assumes the @@c.Form is resizable. The layout algorithm\'s goal is to determine the minimum size of the outermost window @@c.Form. If the existing size is less than the minimum required, the height and width are set to the minimum required values. ##### Layout Calculations ##### An Area has a notion of horizontal or vertical (default) layout. The layout calculations are controlled by the following settings. * **Horizontal mode**
Set with the @@m.Elem-setHorz. The width is the sum of the contained @@c.Elem widths plus margins plus gaps. The height is the highest of the contained @@c.Elem/Elem:objects.
* **Vertical mode**
The default when the @@m.Elem-setHorz is not used. The width of the widest of the contained @@c.Elem/Elem:objects. The height is the sum of the contained @@c.Elem heights plus margins plus gaps.
* **Gap**
Set with the @@m.Elem-setGap. Adds a gap (padding) between @@c.Elem/Elem:objects of the specified x and y value.
* **Margin**
Set with the @@m.Elem-setMar. Adds a margin around @@c.Elem/Elem:objects of the specified x and y value.
* **Absolute Position**
Set with the @@m.Elem-setAbs. It prevents the spacing of elements. Sets the absolute position of the @@c.Elem within the immediate @@c.Area. An X or Y value of 0 means the @@c.Elem floats in the horizontal or vertical direction respectively. An X or Y value of 1 will cause the layout algorithm to calculate the closest value without overlapping in the horizontal or vertical direction respectively. Otherwise additional space, if any, is inserted between each element in the container. An X and Y value of 0 is the default value.
* **bFill**
Set with the @@m.Elem-setFill. A value of $true will cause the components to expand themselves to fill the container in which they reside. If there are multiple *Elems* with the *bFill* option the additional space is divided by their count so that each *bFill* component expands by an equal amount in each direction.
* **Dock**
Set with the @@m.Elem-dock. A value of \"right\" will cause right adjustment of the contained @@c.Elem/Elem:objects. Other values such as \"centre\" are untested. This is handled by the @@link.peer positioning of the underlying .NET support code.
#>
class Area : Elem { #---- properties ---- [ArrayList] $kids #---- constructors ---- Area() { $this.kids = New-Object ArrayList #hlog("created area"); } #---- methods ---- <#.md .desc TODO - find out what thi does .details #>
[String] detailStr() { return "$(([Elem]$this).detailStr()),k=$($this.kids.count)"; } <#.md .desc TODO - find out what thi does .details #>
[void]dump([string]$mar) { if ($mar -eq "") { hlog("--- dump ---- $this"); } else { hlog("$mar area $this"); } forEach($kid in $this.kids) { if ($kid.bGizmo) { hlog("$mar gizmo $kid"); } else { $kid.dump($mar+" "); } } } # find somewhere else to focus <#.md .desc TODO - find out what thi does .details #>
[void] addChild([Elem]$kid) { #$p = $this.peer.size $kid.form = $this.form; # propogate $form $kid.par = $this; $this.kids.add($kid); $this.peer.controls.add($kid.peer); #hlog("addChild $kid But[$($kid.peer.location),$($kid.peer.size),$($kid.peer.size)] added to $this.par") } # find somewhere else to focus <#.md .desc This removes the children from an @@Elem and the @@peer .details This is used when a rebuild is required. All down-tree objects need to be repopulated. #>
[void] resetChildren() { $this.kids = New-Object ArrayList $this.peer.controls.clear(); } <#.md .desc Implements the layout algorithm. See @@link.layout-alg. .details #>
[void]layout() { #if ($this.form.bDebLayout){hlog("layout $this $this.form kids=$($this.kids.count)")} if ($this.par -eq $null) { # test outermost if ($this.bLayDirty) { # very first time or if dirty calc minimums $this.minW = $this.calcMinWidth(); $this.minH = $this.calcMinHeight(); $maxWinW = [System.Windows.Forms.SystemInformation]::PrimaryMonitorSize.Width $maxWinH = [System.Windows.Forms.SystemInformation]::PrimaryMonitorSize.Height hlog("layout.form {0} {1} minWH={2},{3} scrnWH={4},{5} maxWH={6},{7}" -f $this,$this.bLayDirty,$this.minW,$this.minH,$maxWinW,$maxWinH,$this.maxW,$this.maxH); #if ($this.form.bDebLayout){hlog("layout.mins {0},{1}" -f $this.minW,$this.minH)} if (($maxWinH -gt $this.maxH) -and ($this.maxH -ne 0) -and ($this.minH -ge $this.maxH)) { hlogBoth("jam dialogue size $maxWinH $($this.peer.height)"); $this.peer.height = $this.maxH; #$this.bUseVscr = $true; } else { $this.peer.height = $this.minH; if (($this.hgt -ne 0) -and ($this.hgt -gt $this.minH)) { $this.peer.height = $this.hgt; } } $this.calcElemPositions(); $size = $this.peer.clientSize; hlog("layout.form2 clientWH={0},{1} sz={2}" -f $size.width,$size.height,$size); $this.adjustFormAreas($this,$size.width,$size.height,$this.bNoJam); $size = $this.peer.clientSize; hlog("layout.form3 clientWH={0},{1} sz={2}" -f $size.width,$size.height,$size); $this.bLayDirty = $false; ##$this.dump(""); } #if ($this.form.bDebLayout){hlog("layout.done {0} k={1}" -f "xxx" , $this.kids.count)} } else { # allow recalc of subsection #TODO - readjust panel sizes so background works $this.minW = $this.calcMinWidth(); $this.minH = $this.calcMinHeight(); $this.calcElemPositions(); $size = $this.peer.clientSize; #$this.adjustFormAreas($this,$size.width,$size.height); } $this.setElemPositions(); if (($this.form.layoutExit -ne $null) -and (!($this.form.layoutExitBusy))) { $this.form.layoutExitBusy = $true; [void](& $this.form.layoutExit $this); $this.form.layoutExitBusy = $false; } } <#.md .desc TODO - find out what thi does .details #>
[void]adjustFormAreas([Form]$form,[int32]$maxW,[int32]$maxH,[boolean]$bNoJam) { $totW = $this.marLft + $this.marRgt; $totH = $this.marTop + $this.marBot; <#if ($this.form.bDebLayout)#>{hlog("adjustFormAreas $($this.name) $($this.type) max=$maxW,$maxH tots=$totW,$totH peer=$($this.peer.width),$($this.peer.height)")}; $absCntW = 0; $absCntH = 0; $formW = 0 $formH = 0 $ix = -1; forEach($kid in $this.kids) { if ($Kid.bGizmo -and !$kid.bFill) {continue;} if ($Kid.bHidden) {continue;} if ($Kid.type -eq 'pane') {continue;} $ix += 1; if ($this.bHorz) { $totW += $kid.minW; if ($kid.absW -eq 0) {$absCntW += 1;} if ($ix -eq 0) { $totH += $kid.minH; } else { $totW += $this.gapX; } if (($absCntH -eq 0) -and ($kid.absH -eq 0)) {$absCntH += 1;} } else { $totH += $kid.minH; if ($kid.absH -eq 0) {$absCntH += 1;} if ($ix -eq 0) { $totW += $kid.minW; if ($kid.absW -eq 0) {$absCntW += 1;} } else { $totH += $this.gapY; } if (($absCntW -eq 0) -and ($kid.absW -eq 0)) {$absCntW += 1;} } } if ($form -ne $null) { # Root form $formW = $form.peer.width; $formH = $form.peer.height; $origW = $formW $origH = $formH $size = $this.peer.clientSize; if (($totW -gt $size.width) -and ($this.maxW -eq 0)) { # determine minimums and re-adjust $formW = ($formW - $size.width) + $totW; } if (($totH -gt $size.height) -and ($this.maxH -eq 0)) { $formH = ($formH - $size.height) + $totH; } if (($totH -gt $size.height) -and ($this.maxH -ne 0)) { #$this.peer.scrollBars = "Vertical"; $this.peer.AutoScroll = $true; } if (($formW -ne $origW) -or ($formH -ne $origH)) { if ($bNoJam) { hlog("bNoJam set. skipped jam minimum $($this.peer) $($this.peer.size) orig=$origW $origH t=$totW $totH"); } else { $this.peer.size = "$($formW),$($formH)"; $size = $this.peer.clientSize; hlog("jam minimum $($this.peer) $($this.peer.size) orig=$origW $origH t=$totW $totH"); } } $maxW = $formW; $maxH = $formH; } else { $size = @{width=$maxW - 5;height=$maxH - 5} # EC9209 - -5 needed to balance appearance if ($size.width -lt 0) {$size.width = 0} if ($size.height -lt 0) {$size.height = 0} if ($this.type -eq "panel") {$this.peer.size = "$($size.width),$($size.height)"} #$this.peer.size = "$($size.width),$($size.height)"; #EC2519 } $adjW = 0; $adjH = 0; if ($absCntW -gt 0) { $adjW = ([float]($size.width - $totW)) / $absCntW; if ($adjW -lt 0) {$adjW = 0} forEach($kid in $this.kids) { if ($Kid.bHidden) {continue;} if ($kid.absW -eq 0) { $kid.adjW = $adjW; } } } if (($absCntH -gt 0) -and $false) { ## EC0103 TODO - there is a glitch here $adjH = ([float]($size.height - $totH)) / $absCntH; if ($adjH -lt 0) {$adjH = 0} forEach($kid in $this.kids) { if ($Kid.bHidden) {continue;} if ($kid.absH -eq 0) { $kid.adjH = $adjH; } } } $biasW = 0 $biasH = 0 if (!$this.bUseVScr) { forEach($kid in $this.kids) { if ($Kid.bGizmo -and !$kid.bFill) {continue;} if ($Kid.bHidden) {continue;} $kid.biasX = 0; $kid.biasY = 0; if ($this.bHorz) { $kid.biasX += $biasW; $biasW += $kid.adjW; } else { $kid.biasY += $biasH; $biasH += $kid.adjH; } } } #if ($this.form.bDebLayout){hlog("adjustFormAreas $totW $absCntW $totH $absCntH $formW,$formH $size $adjW $adjH")} if (!$this.bUseVScr) { forEach($kid in $this.kids) { if (!$kid.bGizmo -and !$kid.bHidden) { #if ($this.form.bDebLayout){hlog("adjustKid $($kid.name) $($kid.minW + $kid.adjW + $kid.biasX) $($kid.minH + $kid.adjH + $kid.biasY)")} $kid.adjustFormAreas($null,$kid.minW + $kid.adjW + $kid.biasX,$kid.minH + $kid.adjH + $kid.biasY,$true); } if ($kid.bGizmo -and $kid.bFill -and ($kid.type -eq "richarea")) { #EC2629 hlog("fixGixmoFill sz=$($kid.peer.size) before") ##tre{$kid.peer.size = "$($kid.minW + $kid.adjW + $kid.biasX),$($kid.minH + $kid.adjH + $kid.biasY )"} $kid.peer.RTF = $kid.peer.RTF; hlog("fixGixmoFill sz=$($kid.peer.size) after") }} } } <#.md .desc TODO - find out what thi does .details #>
[int32]calcMinWidth() { $minW = 10; $ix = -1; $firstIX = -1; $lastIX = -1; if ($this.form.bLayDirty) {$this.minW = 0; $this.minH = 0;} forEach($kid in $this.kids) { $ix += 1; if ($kid.form.bLayDirty) {$kid.minW = 0; $kid.minH = 0;} if ($kid.bHidden) {continue} if ($firstIX -eq -1) {$firstIX = $ix}; $lastIX = $ix; } $ix = -1; forEach($kid in $this.kids) { $ix += 1; if ($kid.bHidden) {continue} $first = $false; $last = $false; if ($ix -eq $firstIX) {$first = $true;} if ($ix -eq $lastIX) {$last = $true;} if ($kid.bGizmo) { $min = $kid.minW = $kid.getGizmoWidth() } else { $min = $kid.minW = $kid.calcMinWidth(); } if (!$this.bHorz) { $min += $this.marLft + $this.marRgt if ($min -gt $minW) {$minW = $min}; } else { if ($first) {$minW = $this.marLft;} if ($last) {$minW += $this.marRgt;} if (!$first) {$minW += $this.gapX;} $minW += $min; } #if ($this.form.bDebLayout){hlog("calcMinWidth.loop hz=$($this.bHorz) $ix $first $last $minW $min $this")} } if (!$this.bHorz) { forEach($kid in $this.kids) { if ($Kid.bHidden) {continue;} if (!$kid.bGizmo -and ($kid.minW -lt $minW)) {$kid.minW = $minW} } } if ($this.form.bDebLayout){hlog("calcMinWidth $minW $this")} return $minW; } <#.md .desc TODO - find out what thi does .details #>
[int32]calcMinHeight() { $minH = 10; $ix = -1; $firstIX = -1; $lastIX = -1; forEach($kid in $this.kids) { $ix += 1; if ($kid.bHidden) {continue} if ($firstIX -eq -1) {$firstIX = $ix}; $lastIX = $ix; } $ix = -1; forEach($kid in $this.kids) { $ix += 1; if ($Kid.bHidden) {continue;} $first = $false; $last = $false; if ($ix -eq $firstIX) {$first = $true;} if ($ix -eq $lastIX) {$last = $true;} if ($kid.bGizmo) { #if ($kid.peerH -eq 0) {$kid.peerH = $kid.peer.height} #$min = $kid.minH = $kid.peerH; $min = $kid.minH = $kid.reqH = $kid.getGizmoHeight() } else { $min = $kid.minH = $kid.reqH = $kid.calcMinHeight(); } if ($kid.bUseVScr) { hlog("testVertScroll min=$min maxH=$($kid.maxH) reqH=$($kid.reqH)"); if (($kid.maxH -gt 0)-and ($min -gt $kid.maxH)) { $kid.dynH = $min - $kid.maxH; hlog("activateScroll min=$min maxH=$($kid.maxH) reqH=$($kid.reqH) dynH=$($kid.dynH)"); $min = $kid.minH = $kid.maxH; $kid.peer.AutoScroll = $false; $kid.peer.AutoScrollMinSize = "0,$($kid.reqH + 1000)" $kid.peer.HorizontalScroll.Enabled = $false; $kid.peer.HorizontalScroll.Visible = $false; $kid.peer.HorizontalScroll.Maximum = 0; $kid.scrW = 0; $kid.peer.AutoScroll = $true; } else { $kid.dynH = 0; hlog("de-activateScroll min=$min maxH=$($kid.maxH) reqH=$($kid.reqH)"); $kid.scrW = 0; $kid.peer.AutoScrollMinSize = "0,0" $kid.peer.AutoScroll = $false; $kid.scrW = 0; } } if ($this.bHorz) { $min += $this.marTop + $this.marBot if ($min -gt $minH) {$minH = $min}; } else { if ($first) {$minH = $this.marTop;} if ($last) {$minH += $this.marBot + 5;} if (!$first) {$minH += $this.gapY;} $minH += $min; } if ($this.form.bDebLayout){hlog("calcMinHeight.loop hz=$($this.bHorz) ix=$ix F=$first L=$last m=$min mH=$minH $this")} } if ($this.absH -gt $minH) {$minH = $this.absH}; if ($this.bHorz) { forEach($kid in $this.kids) { if ($Kid.bHidden) {continue;} if (!$kid.bGizmo -and ($kid.minH -lt $minH)) {$kid.minH = $minH - $this.marTop - $this.marBot - 5} } } if ($this.form.bDebLayout){hlog("calcMinHeight hz=$($this.bHorz) $($this.getType()) gz=$($this.bGizmo) minh=$minH $this")} return $minH; } <#.md .desc TODO - find out what thi does .details #>
[void]calcElemPositions() { ##if ($this.name -eq "top3r") {$this.minH = 38;} if ($this.form.bDebLayout){hlog("calcElemPositions $this")} $posX = $this.marLft; $posY = $this.marTop; forEach($kid in $this.kids) { if ($Kid.bHidden) {continue;} if (!$kid.bGizmo) {$kid.calcElemPositions();} $kid.dynX = $posX $kid.dynY = $posY if ($this.bHorz) { $posX += $this.gapX + $kid.minW } else { $posY += $this.gapY + $kid.minH } } if ($this.form.bDebLayout){hlog("positioned $this")} } <#.md .desc TODO - find out what thi does .details #>
[void]setElemPositions() { forEach($kid in $this.kids) { if ($Kid.bHidden) {continue;} if ($this.bUseVScr) { hlog("VscrPanel: name=$($kid.name) dyn=$($kid.dynX),$($kid.dynY) bias=$($kid.biasX),$($kid.biasY) sz=$($kid.minW),$($kid.minH) adj=$($kid.adjW),$($kid.adjH)") } else { hlog("HscrPanel: name=$($kid.name) dyn=$($kid.dynX),$($kid.dynY) bias=$($kid.biasX),$($kid.biasY) sz=$($kid.minW),$($kid.minH) adj=$($kid.adjW),$($kid.adjH)") } $kid.peer.location = "$($kid.dynX + $kid.biasX),$($kid.dynY + $kid.biasY)" $kid.peer.size = "$($kid.minW + $kid.adjW),$($kid.minH + $kid.adjH)" if (!$kid.bGizmo) {$kid.setElemPositions();} if ($this.form.bDebLayout){hlog("setElemPositions $($this.getType()) $($this.bGizmo) $($this.name) peer=$($this.peer.width),$($this.peer.height) $this")} } } } # --- <#.md .desc Gizmos represent the lowest level control such as a Button .details Gizmos represent the lowest level control such as a Button. They contain no other Elem. They can be hidden or disabled. #>
class Gizmo : Elem { #---- properties ---- #---- constructors ---- Gizmo() { $this.bGizmo = $true; } #---- methods ---- <#.md .desc This calculates the width of the Gizmo. It can be overidden by particular gizmos (such as @@TextBox) when adjustments have to be made based on the @@peer implementation characteristics. #>
[int32]getGizmoWidth() { $gizmo = $this; $peer = $gizmo.peer; if ($gizmo.peerW -eq 0) { $gizmo.peerW = $peer.width; return $gizmo.peerW; } else { return $gizmo.peerW; } } <#.md .desc This calculates the height of the Gizmo. It can be overidden by particular gizmos (such as @@TextBox) when adjustments have to be made based on the @@peer implementation characteristics. #>
[int32]getGizmoHeight() { $gizmo = $this; $peer = $gizmo.peer; <# hlog("gizmo.hgt.what /$($gizmo.getType())/ $($gizmo.peerH)"); if ("$($gizmo.getType())" -eq "Label") { hlog("gizmo.hgt.label /$($gizmo.getType())/ $($gizmo.peerH)"); if ($gizmo.peerH -eq 0) { $gizmo.peerH = $peer.height + 30; } } #> if ($gizmo.peerH -eq 0) { $gizmo.peerH = $peer.height; hlog("gizmo.hgt.def $($gizmo.getType()) $($gizmo.peerH)"); return $gizmo.peerH; } else { hlog("gizmo.hgt.set $($gizmo.getType()) $($gizmo.peerH)"); return $gizmo.peerH; } } } # --- Form - the outer window #> <#.md .desc Implementation of Windows.Forms.Form. .details The form is the global object used to hang things off of since every Elem has a pointer to the form. It is extended to add custom objects. See x. #>
class Form : Area { #---- properties ---- [object] $appJob; # Owning Object (such as JobDesc) or $null [object] $custObj; # Custon owning object [string] $title; # Value shown in outer window caption [hashtable] $cfg; # Related config [scriptBlock] $onClose; # Script to execute on closing [ScriptBlock] $confirmClose; # Script to execute before closing. return true to close, false to cancel close. [scriptBlock] $layoutExit; # execute after layout performed [scriptBlock] $resizeExit; # EC9B29 - resize exit [scriptBlock] $openExit; # EC2701 - used to perform processing after form is visible [string] $exitCmdSeq; # Actual exit str (copy of $exitCharStr, timing issue means above is not reliable) [string] $exitCharSeq; # if we used k/b to exit, set flags. as below [string] $exitRestartSeq; # What we look for. Default // Restart GUI and classes with exit code 901 [string] $exitExitSeq; # What we look for. Default .. Exit GUI. [boolean] $layoutExitBusy; # prevents recursion if exit dinks with sizes and causes new [boolean] $bNoJam; # EC9B30 - resize exit. Do not ajust form size [object] $ctx; # context for confirmClose [hashtable] $colors; # Registered RBG colors [hashtable] $fonts; # Registered named fonts [hashtable] $icons; # Registered named Icons [hashtable] $dialogs; # Dialogs attached to Form [hashtable] $elems; # Elems contained in form [hashtable] $appGbl; # application wide globals such as base folder [Windows.Forms.ToolTip] $toolTip; # Tool tips attached to Form [object] $gridList; # Registered Grids [object] $appRoot; # app specific root object [boolean] $bDebLayout; # on if layout debug [boolean] $bRestartGUI; # EC2520 - on if to restart GUI after closing #---- constructors ---- <#.md .desc Constructs standard Form object .details The width and height are the starting dimensions. The default window can be resized. The title string is shown as the title on the Windows window representing the form. .parm $title string shown in title section of external window .parm $wid starting width .parm $wid starting height #> Form([System.Object]$peer,[string]$title,[int32]$wid,[int32]$hgt) { $this.type = 'form' $this.wid = $wid; $this.hgt = $hgt; $this.setMar(5,5,16,12); $this.title = $title; $this.setPeer($peer); $this.bLayDirty = $true; $this.colors = @{} $this.fonts = @{} $this.icons = @{} $this.elems = @{} $this.appgbl = @{} $this.toolTip = new-object Windows.Forms.ToolTip $this.exitRestartSeq = "//"; $this.exitExitSeq = ".."; } #---- methods ---- <#.md .ffunc Form .details The @@Form created is the topmost window of the GUI interface The optional $cfg parm provides a hashtable where configration information such as window sizes is stored. This is used in rebuilding the window. See @@formCfg. The optional $closeCallBack parm provides a script or function that is called when the window is about to close. See @@formClose. The optional $form parm provides a means whereby a Form can be extendeds with additional fields or behavoirs. see @@formExtend. .optp $cfg Configuration file .optp $closeCallBack Function/script to call when @@Form closes .optp $Form Existing extended @@Form to use as parent #>
static [Form] createForm([object]$appJob,[hashtable]$cfg,[scriptBlock] $closeCallBack,[Form]$form) { # $form -ne $null for extended Form class if ($null -ne $form) { #extending form $elem = $form; } else { $peer = New-Object Windows.Forms.Form $elem = [Form]::new($peer, 'main', $cfg.winsize.formW, $cfg.winsize.formH); if (exists $cfg.winsize title) { $peer.text = $cfg.winsize.title } } $elem.appJob = $appJob; if ($appJob -ne $null) { $appJob.form = $elem; } $elem.cfg = $cfg; $elem.onClose = $closeCallBack; $elem.peer.StartPosition = "CenterScreen"; $elem.peer.AutoScaleMode = "Font"; $elem.form = $elem; [void]$elem.setGap(15, 15); $elem.peer.add_ResizeEnd({ $form = $this.tag; if ($form.resizeExit -ne $null) { & $form.resizeExit $form $form.ctx } $this.tag.resize(); }) # this has the desired effect of flushing hlog functions. If we are looking at the dlog output # in an editor then the editor will see the most current lines when we switch to it. $elem.peer.add_Deactivate({ hlogBoth("form $this is deactivated") }) $elem.peer.add_Shown({ hlogBoth("form $this is opened") $form = $this.tag; if ($form.openExit -ne $null) { & $form.openExit $form $form.ctx } }) $elem.peer.add_FormClosing({ $evt = $_ $form = $this.tag; if ($form.confirmClose -ne $null) { if (!(& $form.confirmClose $form $form.ctx)) { $evt.cancel = $true; return; } } if ((exists $form appJob) -and (exists $form.appJob guiAppExitCmd)) { $str = "$($form.appJob.guiAppExitCmd).$($form.appJob.guiAppExitCmd)" } else { $str = "form $($form.peer.text) is closing" } hlog("form $form is closing termStr=$str") if ($form.onClose -ne $null) { hlog("form issue onClose"); & $form.onClose $form.exitCharSeq } if ($form.dialogs -ne $null) { forEach($k in $form.dialogs.keys) { [Dialog]$dlg = $form.dialogs[$k] hlog("forceClose of $k $dlg"); $dlg.forceClose = $true; $dlg.peer.close(); } } }); # we do this here to t/off the grid selection. Seems like it has to be done after the form is shown. $elem.peer.add_shown({ hlog("form shown $($this.tag)"); $form = $this.tag; if ($form.gridList -ne $null) { forEach($dgv in $form.gridList) { ##hlog("clear $dgv"); $dgv.CurrentCell = $null; $dgv.clearSelection(); } } }); hlog("created $($elem) w=$($cfg.winsize.formW),h=$($cfg.winsize.formH) $(easyType($closeCallBack))"); return $elem; } <#.md .desc shows the form .details This shows the form and freezes the Powershell console window. @@hlog messages and its varients are still displayed. A keyboard listener is installed so that the keyboard can be used to close the window. The following keyboard sequences have meaning. All others are ignored but forwarded to controls that accept keyboard input. * \.\. Terminate and restart GUI window. Used if script is changed * // Terminate and restart GUI driver and then GUI window. Used if underlying GUI is changed. Implies things were started with the @@New * \./ Just terminate with no restart Refer to @@GuiRestart for more details. #>
[void] showForm() { if ($this.appJob -ne $null) { $this.appJob.guiAppExitCmd = $null; # reset } hlog("--- show form $this"); $this.layout(); $this.peer.add_KeyPress({ # Fast Exit. TODO - make config driven $form = $this.tag; $e = $_; hlog("keyPress.form $e $($e.KeyChar) $($form.exitCharSeq)"); # $($e.getType())"); if (($form.exitCharSeq -eq $form.exitExitSeq.substring(0,1)) -and ($e.KeyChar -eq $form.exitExitSeq.substring(1,1))) { $form.exitCharSeq += $e.KeyChar; $form.exitCmdSeq = $form.exitCharSeq; $form.peer.close(); # fast exit via k/b, stop GUI } if (($form.exitCharSeq -eq $form.exitRestartSeq.substring(0,1)) -and ($e.KeyChar -eq $form.exitRestartSeq.substring(1,1))) { $form.exitCharSeq += $e.KeyChar; $form.exitCmdSeq = $form.exitCharSeq; $form.peer.close(); # fast exit via k/b, flag for GUI restart } $form.exitCharSeq = $e.KeyChar; }); $this.peer.KeyPreview = $true; $this.peer.ShowDialog(); } <#.md .desc Register Elem using the Elem.name property #>
[void] regElem([Elem]$elem) { regElem($elem,$elem.name); } <#.md .desc Register Elem using the $name parameter as the name #>
[void] regElem([Elem]$elem,[string]$name) { hlog("--- register elem $elem as $name"); if ($this.elems[$name] -ne $null) {throw "duplicate elem name $name for $elem"} $this.elems[$name] = $elem } <#.md .desc Find the registered Elem using the $name parameter as the search argument .details Throws an error if not found #>
[Elem]findElem([string]$name) { if ($this.elems[$name] -eq $null) {throw "lost elem $name"} return $this.elems[$name]; } <#.md .desc Find the registered Elem using the $name parameter as the search argument .details Returns $null if not found #>
[Elem]findElemOpt([string]$name) { if ($this.elems[$name] -eq $null) {return $null} return $this.elems[$name]; } <#.md .desc Register a Dialog as being open .details This is used so that open dialogs are automatically closed when the parent window is closed. Failure to do this causes orphan windows that tangles up the Windows OS. This is automatically called by the Dialog create factory function. #>
[void] regDialog([Dialog]$dlg) { hlog("--- register dialog $($dlg.hash) $dlg"); if ($this.dialogs -eq $null) {$this.dialogs = @{};} $this.dialogs[$dlg.hash] = $dlg } <#.md .desc Remove a Dialog for the registry .details Automatically called by the standard Dialog close function #>
[void] remDialog([Dialog]$dlg) { hlog("--- remove dialog $($dlg.hash) $dlg"); if ($this.dialogs -eq $null) {return;} if ($this.dialogs[$dlg.hash] -ne $null) {$this.dialogs.remove($dlg.hash)} } <#.md .desc Resizes the current window .details This is called internally when the user resizes the Form window (typically with the mouse). This triggers the Form layout method to be called #>
[void] resize() { $p = $this.peer.size $this.wid = $p.width; $this.hgt = $p.height; if ($this.cfg -ne $null) { if (notExists $this.cfg winsize) {$this.cfg.winsize = @{};} #hlog("resize cfg $($this.cfg.formW) $($this.cfg.formH)") $this.cfg.winsize.formW = $this.wid; $this.cfg.winsize.formH = $this.hgt; } hlog("resize $($p.width) $($p.height)") $size = $this.peer.ClientSize; $this.adjustFormAreas($this,$size.width,$size.height,$this.bNoJam); $this.bNoJam = $false; #$this.dump(""); $this.layout(); } <#.md .desc Log a message to the Logger window of the form, if it exists .parm $msg the message .parm $col the color (default black) .notes The specified color sometimes has difficulty \'taking\', especially in the early stages of the windows creation or after clearing the logging window using the \"Clear Log\" link #>
[void]log([string]$msg,[string]$col) { $logger = $this.findElemOpt("LOGGER"); if ($logger -eq $null) {return}; $logger.peer.SelectionColor = $col; $logger.peer.appendText(""+$msg) $logger.peer.SelectionColor = "black"; $logger.peer.appendText("`r`n") } <#.md .desc Log a black message to the Logger window of the form, if it exists #>
[void]log([string]$msg) {$this.log($msg,"Black");} <#.md .desc Log a green message to the Logger window of the form, if it exists #>
[void]logGood([string]$msg) {$this.log($msg,"Green");} <#.md .desc Log a red message to the Logger window of the form, if it exists #>
[void]logFlag([string]$msg) {$this.log($msg,"Red");} <#.md .desc Log an orange message to the Logger window of the form, if it exists #>
[void]logWarn([string]$msg) {$this.log($msg,"Orange");} <#.md .desc Register a color by name .parm $name the name to register under .parm $r red component of RGB value .parm $g green component of RGB value .parm $b blue component of RGB value #>
[void]regColor([string]$name,[int]$r,[int]$g,[int]$b) { $rgb = $this.rgb($r,$g,$b); if ($this.colors[$name] -ne $null) {$this.logWarn("Color $name being replaced by $rgb");} $this.colors[$name] = $rgb; } <#.md .desc Find a color by name. Used internally by backColor and foreColor methods. #>
[string]findColor([string]$name) { return $this.colors[$name] } <#.md .desc Register a font by name #>
[void]regFont([string]$name,[Drawing.Font] $font) { if ($this.fonts[$name] -ne $null) {$this.logWarn("Font $name being replaced by $font");} $this.fonts[$name] = $font; } <#.md .desc Register an Icon (image) by name #>
[void]regIcon([string]$name,[Drawing.BitMap] $icon) { hlog("regIcon $name $($icon.getType())"); if ($this.icons[$name] -ne $null) {$this.logWarn("Icon $name being replaced");} $this.icons[$name] = $icon; } <#.md .desc Calculate the color value using RGB values .parm $red red component of RGB value .parm $green green component of RGB value .parm $blue blue component of RGB value #>
[string] rgb([int]$red,[int]$green,[int]$blue) { $rgb = [long] ($blue + ($green * 256) + ($red * 65536)) $hex = "{0:x6}" -f $rgb return "#$hex" } <#.md .desc Create a font of the specified Points using the font family \"Microsoft Sans Serif\" #>
[System.Drawing.Font] getFont([int]$pts) { return $this.getFont($pts,"Microsoft Sans Serif"); } <#.md .desc Create a font of the specfied Points using the specified font family #>
[System.Drawing.Font] getFont([int]$pts,[string]$family) { return New-Object System.Drawing.Font($family,$pts,[System.Drawing.FontStyle]::Regular); } <#.md .desc Create a bolded font of the specified Points using the font family \"Microsoft Sans Serif\" #>
[System.Drawing.Font] getBoldFont([int]$pts) { return $this.getBoldFont($pts,"Microsoft Sans Serif"); } <#.md .desc Create a bolded font of the specfied Points using the specified font family #>
[System.Drawing.Font] getBoldFont([int]$pts,[string]$family) { return New-Object System.Drawing.Font($family,$pts,[System.Drawing.FontStyle]::Bold); } <#.md .desc Create a fixed-font of the specified Points using the font family \"Lucida Console\" #>
[System.Drawing.Font] getFixedFont([int]$pts) { return $this.getFont($pts,"Lucida Console"); } <#.md .desc Register a Grid on the Form .details Timing issues exist with Grids and focus. Registering the Grid allows for race conditions to be circumvented and the \'non-focus\' of a grid cell to be more reliable. #>
[void] addGridList([Windows.Forms.DataGridView]$dgv) { if ($this.gridList -eq $null) {$this.gridList = @();} $this.gridList += $dgv; } #---- methods ---- <#.md .desc Sets or removes confirmClose property .details #>
[Form]setConfirmClose([object]$ctx,[ScriptBlock]$confirmClose) { $this.confirmClose = $confirmClose; $this.ctx = $ctx; return $this; } } # --- Dialog - the outer window <#.md .desc Dialog (popup Window) implemented with Windows.Form.Form .details powershell (popup Window) implemented with Windows.Form.Form and shown with the showForm() method. They are hung of the primary form so that when the Primary form is closed they also are closed. This prevents orphans which causes Windows OS extreme grief. #>
class Dialog : Form { #---- properties ---- [Form]$parForm # parent form for proper cleanup [string]$hash # unique id [boolean]$forceClose = $false; # true if force close by parent form [string]$exitStr # set to allow treatment as result code #---- constructors ---- <#.md .desc TODO - find out what thi does .details #> Dialog([Form]$form,[System.Object]$peer,[string]$title,[int32]$wid,[int32]$hgt) : base($peer,$title,$wid,$hgt) { $this.type = 'dialog' $this.parForm = $form $this.peer.text = $title; $now = [DateTime]::now.toBinary(); $this.hash = "hash$now" $this.parForm.regDialog($this); } #---- methods ----
static [Dialog] createDialog([Form]$form,[int]$w,[int]$h,[string]$iconName,[string]$title,[boolean]$noCanSave) { $peer = New-Object Windows.Forms.Form $elem = [Dialog]::new($form,$peer,$title, $W, $H); $elem.appRoot = $form.appRoot; $elem.appJob = $form.appJob; $elem.peer.StartPosition = "CenterScreen"; $elem.peer.AutoScaleMode = "Font"; $elem.form = $elem; [void]$elem.setGap(15,15); $elem.peer.add_ResizeEnd({ $this.tag.resize(); }) ##$CVT = $GLOBAL:CVT; if ($iconName -eq $null) { ##$icon = new-object System.drawing.icon "$($CVT.psecBase)\scr\imgs\psec-err.ico"; $icon = $null; } else { $icon = getGuiIcon($iconName); } $peer.icon = $icon; $elem.peer.add_FormClosing({ $evt = $_ $dlg = $this.tag; hlog("dialog close fc=$($dlg.forceClose) closing of form ($evt)") if (!$dlg.forceClose) { if ($dlg.confirmClose -ne $null) { if (!(& $dlg.confirmClose $dlg $dlg.ctx)) { $evt.cancel = $true; return; } } $dlg.parForm.remDialog($dlg); if ($dlg.onClose -ne $null) { (& $dlg.onClose $dlg.form.exitStr); } } }); hlog("created Dialog $($elem) w=$w,h=$h"); return $elem; }
static [Dialog] makeCommonDialog([Form]$form,[int]$w,[int]$h,[string]$icon,[string]$title,[boolean]$noCanSave) { [dialog]$dlg = [Dialog]::createDialog($form,$w,$h,$icon,$title,$noCanSave) $dlg.peer.MaximizeBox = $false $dlg.peer.FormBorderStyle = 'FixedDialog' return $dlg } } # --- Panel - a visible container <#.md .desc A Panel implementation with visible borders. .details The Panel object is used to paint areas of the screen. It has borders and represents something that delineates partitions of the screen. #>
class Panel : Area { #---- properties ---- [string] $style = 'Fixed3D' #---- constructors ---- Panel() {} Panel([System.Object]$peer,[Elem]$par) { $this.type = 'panel' $this.setPeer($peer); $par.addChild($this); } #---- methods ----
static [Panel] createPanel([Area]$par,[string]$name) { $peer = New-Object Windows.Forms.Panel $peer.Size = "40,50" $peer.BorderStyle = 'Fixed3D' $peer.BackColor = "white" $elem = [Panel]::new($peer,$par); $elem.peer.AutoSizeMode = "GrowAndShrink"; $elem.name = $name [void]$elem.setGap(10,10).setMar(10,10); return $elem }
static [Panel] createLogger([Form]$form,[string] $name,[int32]$hgt) { $log = ([Panel]::createPanel($form,$name)).setMar(0,0).setGap(0,0); $menu = ([Div]::createDiv($log,'menu')).setBackColor("lightgray").setAbs(0,1).setHorz(); $lnk = ([Link]::createLink($menu,'clear log')) $lab = ([Label]::createLabel($menu," ")); #margin $chk = ([CheckBox]::createCheckBox($menu,$null,$null,"Keep Log Windows")); #borrow the space $lines = ([Div]::createDiv($log,'lines')).setAbs(0,$hgt) #$lines.peer.height = 140; #Dock = "Fill" $lb = ([LogBox]::createLogBox($lines,$null)).setBackColor("white") $lb.peer.size = "400,$($hgt)" $lb.peer.Dock = "Fill" $lb.peer.ScrollBars = "Vertical"; $lnk.mate = $lb; $lnk.peer.add_Click({ $elem = $this.tag; $elem.mate.peer.text = ""; }); $form.regElem($lb,"LOGGER"); $form.regElem($chk,"log-view-keep"); return $log; } } # --- TabPanel - a visible container with multiple Panel contents <#.md .desc Tabbed Panel implementation .detail Multiple named panels are added via the AddTabPanel method. All are hidden except the first. The switchPanel method is used to switch between the named panels. An exi5t is optionally called when the new Panel is displayed. #>
class TabPanel : Panel { #---- properties ---- [Panel] $curPanel = $null; [hashtable] $panels = $null; [scriptblock] $switchExit = $null; #---- constructors ---- <#.md .desc TODO - find out what thi does .details #> TabPanel([System.Object]$peer,[Elem]$par) { $this.type = 'tabpanel' $this.setPeer($peer); $par.addChild($this) } #---- methods ----
static [TabPanel] createTabPanel([Area]$par,[string]$name) { $peer = New-Object Windows.Forms.Panel $peer.Size = "40,50" $peer.BorderStyle = 'Fixed3D' $peer.BackColor = "white" $elem = [TabPanel]::new($peer,$par); $elem.name = $name $elem.peer.AutoSizeMode = "GrowAndShrink"; [void]$elem.setGap(10,10).setMar(10,10); return $elem } <#.md .desc Remove TabPanel panels (but not children) .details #>
[void]resetTabPanel() { $this.panels = @{}; if ($this.curPanel -ne $null) { $this.curPanel.bHidden = $true; $this.curPanel.peer.visible = $false; } $this.curPanel = $null; } <#.md .desc TODO - find out what thi does .details #>
[void]addTabPanel([Panel]$panel) { $this.addTabPanel($panel,$panel.name); } <#.md .desc TODO - find out what thi does .details #>
[void]addTabPanel([Panel]$panel,[string]$tabName) { if ($this.panels -eq $null) {$this.panels = @{};} if ($this.panels[$tabName] -ne $null) {throw "duplicate TabPanel child-panel name=$tabName)"} $this.panels[$tabName] = $panel if ($this.curPanel -eq $null) { $this.curPanel = $panel; $panel.bHidden = $false; $panel.peer.visible = $true; } else { $panel.bHidden = $true; $panel.peer.visible = $false; } } <#.md .desc TODO - find out what thi does .details #>
[void] switchPanel([string]$name) { if ($this.panels[$name] -eq $null) {throw "missing TabPanel child-panel name=$name"} if ($this.curPanel -ne $null) { [void]$this.curPanel.setHidden($true); $task = $this.curPanel.tied if ($task -ne $null) { $task.menuDiv.backColor("taskBG") } } $this.curPanel = $this.panels[$name]; [void]$this.curPanel.setHidden($false); $this.form.bLayDirty = $true; $this.form.layout(); $this.form.logGood("switched to panel $name"); if ($this.switchExit -ne $null) { & $this.switchExit $this $this.curPanel } }
[Panel]findPanelByName([string]$panelName) { return $this.panels[$panelName]; } <#.md .desc Replace the named panel with a new panel and make it the current panel. #>
[void]replacePanelByName([Panel]$panel,[string]$tabName) { $this.panels[$tabName] = $panel; if ($this.curPanel -ne $null) { $this.curPanel.bHidden = $true; $this.curPanel.peer.visible = $false; } $this.curPanel = $panel; $panel.bHidden = $false; $panel.peer.visible = $true; } } #> # --- Div - a visible container, no border <#.md .desc Borderless panel .details Used to organize Areas and Gizmos with no visible partitions. .Note DIVs have a defualt of 5 pixcels for the margin and gap. In nested situations setting these values to 0 might clear up alignment problems. #>
class Div : Area { #---- properties ---- [string] $style = '$null' #---- constructors ---- Div([System.Object]$peer,[Elem]$par) { $this.type = 'div' $this.setPeer($peer); $par.addChild($this) } #---- methods ----
static [Div] createDiv([Area]$par,[string]$name) { $peer = New-Object Windows.Forms.Panel $peer.Size = "40,50" $peer.BackColor = "white" $elem = [Div]::new($peer,$par); $elem.name = $name $elem.peer.AutoSizeMode = "GrowAndShrink"; [void]$elem.setGap(5,5).setMar(5,5); return $elem } } # --- Pane - a visible container, no border <#.md .desc A visible container that does not take part in the layout calculations. .details Used to create a Floating area that can be positioned independantly of its container. The `posPane` hook is called at the end of layout processing. The can be used to position the area (and its children relative to its parent). #>
class Pane : Area { #---- properties ---- [string] $style = '$null' #---- constructors ---- Pane([System.Object]$peer,[Elem]$par) { $this.type = 'pane' $this.setPeer($peer); $par.addChild($this) } #---- methods ----
static [Pane] createPane([Area]$par,[string]$name) { $peer = New-Object Windows.Forms.Panel $peer.Size = "40,50" $peer.BackColor = "white" $elem = [Pane]::new($peer,$par); $elem.peer.AutoSizeMode = "GrowAndShrink"; $elem.name = $name return $elem }
[void] posPane() { hlog("position pane"); } } # ------------------------ Gizmos ------------------ <#.md .desc Button Gizmo .details #>
class Button : Gizmo { #---- properties ---- #[string] $style = 'Fixed3D' #---- constructors ---- Button([System.Object]$peer,[Elem]$par) { #hlog("creating button $($peer.text)"); $peer.BackColor = "Lightgray" $this.type = 'button' $this.setPeer($peer); $par.addChild($this) } #---- methods ----
static [Button] createButton([Area]$par,[string]$text,[string]$size,[string]$locn) { $peer = New-Object Windows.Forms.Button $peer.text = $text; if ($size -eq $null) { $peer.AutoSize = $true; } else { $peer.size = $size } if ($locn -ne "") { $peer.Location = $locn } $elem = [Button]::new($peer,$par); #EC1B07 $elem.peer.AutoScaleMode = "Inherit"; $peer.add_Click({ $peer = $this; #hlog("clicked $peer"); $elem = $peer.tag; if ($elem.onClick -ne $null) { if ($elem.linkDisabled) {return;} [void](& $elem.onClick $elem) } }); $elem.name = $text; return $elem } } <#.md .desc Clickable link Gizmo .details #>
class Link : Gizmo { #---- properties ---- [object] $ctx = $null; # context object for click [PSMethod] $action; # action handler (has precedence over onClick) [string] $origBackColor = "Lightgray"; # Original background color (for restore on mouseLeave) [string] $origForeColor = "Blue"; # Original foreground color (for restore on mouseLeave) [boolean] $linkDisabled = $false; # true if link disabled (peer does not honour) #---- constructors ---- <#.md .desc TODO - find out what thi does .details #> Link([System.Object]$peer,[Elem]$par) { $peer.BackColor = $this.origBackColor; $this.type = 'link' $this.setPeer($peer); if ($par -ne $null) { $par.addChild($this) } } #---- methods ----
static [Link] createLink([Area]$par,[string]$text) { return [Link]::createLink($par,$text,"","") } #implementation note: $size and $locn with values not tested.
static [Link] createLink([Area]$par,[string]$text,[string]$size,[string]$locn) { $peer = New-Object Windows.Forms.Label $peer.text = $text; if ($size -ne "") { $peer.Size = $size; } else { $peer.AutoSize = $true; } if ($locn -ne "") { $peer.Location = $locn; } $elem = [Link]::new($peer,$par); ##$elem.peer.AutoScaleMode = "Inherit"; $elem = $elem.setName($text).setForeColor("blue"); $elem.peer.add_MouseEnter({$this.tag.eventsEnter()}) $elem.peer.add_MouseLeave({$this.tag.eventsLeave()}) $peer.add_Click({ $peer = $this; [Link]$elem = $peer.tag; if (($elem.onClick -ne $null) -and ($elem.action -eq $null)) { if ($elem.linkDisabled) {return;} [void](& $elem.onClick $elem) } if ($elem.action -ne $null) { if ($elem.linkDisabled) {return;} hlogBoth("link.action $($elem.action.name)"); $elem.action.invoke(@($elem,$elem.ctx)); } }); return $elem } <#.md .desc TODO - find out what thi does .details #>
[void] eventsEnter() { if ($this.linkDisabled) {return;} $this.peer.BackColor = "Cyan"; $this.peer.ForeColor = "Black"; } <#.md .desc TODO - find out what thi does .details #>
[void] eventsLeave() { if ($this.linkDisabled) {return;} $this.peer.BackColor = $this.origBackColor; $this.peer.ForeColor = $this.origForeColor; } <#.md .desc TODO - find out what thi does .details #>
[Elem]disableLink() { if ($this.linkDisabled) {return $this;} $this.linkDisabled = $true; $this.peer.BackColor = $this.origBackColor; $this.peer.ForeColor = "gray"; return $this; } <#.md .desc TODO - find out what thi does .details #>
[Elem]enableLink() { $this.linkDisabled = $false; $this.peer.BackColor = $this.origBackColor; $this.peer.ForeColor = $this.origForeColor; return $this; } <#.md .desc TODO - find out what thi does .details #>
[Elem]setLinkText([string]$text) { $this.peer.text = $text; return $this; } # intercept color changes for proper disable/enable/enter/leave <#.md .desc TODO - find out what thi does .details #>
[Elem]setBackColor ([string]$col) { $this.peer.backColor = $col; $this.origBackColor = $col; return $this; } <#.md .desc TODO - find out what thi does .details #>
[Elem]setForeColor ([string]$col) { $this.peer.foreColor = $col; $this.origForeColor = $col; return $this; } <#.md .desc TODO - find out what thi does .details #>
[Elem]setContext([object]$ctx) { $this.ctx = $ctx; return $this; } } <#.md .desc Visible Icon .details #>
class Icon : Gizmo { #---- properties ---- #---- constructors ---- Icon([System.Object]$peer,[Elem]$par) { $this.type = 'label' $this.setPeer($peer); $par.addChild($this) } #---- methods ----
static [Icon] createIcon([Area]$par,[object]$icon) { $peer = new-object Windows.Forms.PictureBox $peer.width = 32; $peer.height = 32; $peer.AutoSize = $true $peer.image = $icon $elem = [Icon]::new($peer,$par); #$elem.peer.AutoScaleMode = "Inherit"; return $elem } } <#.md .desc Label implementation .details #>
class Label : Gizmo { #---- properties ---- #---- constructors ---- Label([System.Object]$peer,[Elem]$par) { $this.type = 'label' $this.setPeer($peer); $par.addChild($this) } #---- methods ----
static [Label] createLabel([Area]$par,[string]$text) { return [Label]::createLabel($par,$text,""); }
static [Label] createLabel([Area]$par,[string]$text,[string]$size) { $peer = New-Object Windows.Forms.Label $peer.text = $text; if ($size -eq "") { $peer.AutoSize = $true; } else { $peer.Size = $size; } $elem = [Label]::new($peer,$par); #$elem.peer.AutoScaleMode = "Inherit"; $elem = $elem.setName($text); return $elem } <#.md .desc TODO - find out what thi does .details #>
[Label]maxWidth([int]$maxW) { if (!$this.peer.autoSize) {throw "maxWidth requires autoSize"}; $this.peer.MaximumSize = "$maxW,0"; return $this; } <#.md .desc Aligns text on the right margin .details #>
[Label]alignCenter() { $this.peer.textAlign = "MiddleCenter"; return $this; } <#.md .desc Aligns text on the right margin .details #>
[Label]alignRight() { $this.peer.textAlign = "MiddleRight"; return $this; } } <# #> <#.md .desc Text Input widget implementation .details #>
class InputBox : Gizmo { #---- properties ---- [string]$place #---- constructors ---- InputBox() {} InputBox([System.Object]$peer,[Elem]$par) { $this.type = 'inputbox' $this.setPeer($peer); $par.addChild($this) } #---- methods ----
static [InputBox] createInputBox([Area]$par,[string]$size,[string]$locn,[string]$place) { $peer = New-Object Windows.Forms.TextBox $peer.text = ''; if ($size -eq "") { $peer.AutoSize = $true; } else { $peer.size = $size } if ($locn -ne "") { $peer.Location = $locn } $elem = [InputBox]::new($peer,$par); #$elem.peer.AutoScaleMode = "Inherit"; if ($place -ne "") { $elem.place = $place; $peer.Add_GotFocus({ $peer = $this; if (($peer.foreColor -match "gray") -and (!($peer.readonly))) { $peer.text = ""; $peer.foreColor = "black"; } }); $peer.Add_LostFocus({ hlog("lost focus $this"); $peer = $this; if ($peer.Text -eq '') { $peer.Text = $peer.tag.place $peer.ForeColor = 'gray' } }); $peer.Text = $peer.tag.place $peer.ForeColor = 'gray' $peer.TabStop = $false; } return $elem }
[InputBox]setText ([string]$text) { ([Gizmo]$this).setText($text); $this.setTextColor(); return $this; }
[void]setTextColor() { $peer = $this.peer if ($peer.text -ne '') { $peer.ForeColor = 'black' } else { $peer.ForeColor = 'gray' } } <#.md .desc TODO - find out what thi does .details #>
[boolean]hasText() { if (("$($this.peer.text)" -eq "$($this.place)") -or ("$($this.peer.text)" -eq "")) {return $false} return $true; } <#.md .desc Aligns text on the right margin .details #>
[InputBox]setReadonly() { $this.peer.ReadOnly = $true; return $this; } } <#.md .desc Checkbox Input widget implementation .details #>
class CheckBox : Gizmo { #---- properties ---- #---- constructors ---- CheckBox() {} CheckBox([System.Object]$peer,[Elem]$par) { $this.type = 'checkbox' $this.setPeer($peer); $par.addChild($this) } #---- methods ----
static [CheckBox] createCheckBox([Area]$par,[string]$size,[string]$locn,[string]$place) { $peer = New-Object Windows.Forms.CheckBox $peer.text = $place if ($size -eq "") { $peer.AutoSize = $true; } else { $peer.size = $size } if ($locn -ne "") { $peer.Location = $locn } $elem = [CheckBox]::new($peer,$par); #$elem.peer.AutoScaleMode = "Inherit"; return $elem }
[boolean]isChecked() { return $this.peer.checked; } } <#.md .desc Multiple Radio Input widget implementation .details Multiple Radio Input widget implementation with grouping to allow only one to ever be on at one time. #>
class RadioBoxes : Gizmo { #---- properties ---- #---- constructors ---- <#.md .desc TODO - find out what thi does .details #> RadioBoxes() {} <#.md .desc TODO - find out what thi does .details EC9C01 - size has third parm, number of lines #> RadioBoxes([System.Object]$peer,[Elem]$par) { $this.type = 'radio' $this.setPeer($peer); $par.addChild($this) } #---- methods ----
static [RadioBoxes] createRadioBoxes([Area]$par,[string]$size,[string]$locn,[Object[]]$values,[Object[]]$labels,[int]$selected,[string]$group) { $peer = New-Object Windows.Forms.Panel # Use GroupBox if want border $ix = -1; $widPos = 0; $hgt = 0; $hgtPos = 0; if ($size -eq "") { $peer.AutoSize = $true; } else { if ($size -match "^(?[0-9]+),(?[0-9]+),(?[0-9]+)$") { $peer.size = "$($matches.wid),$(($matches.hgt - 0) * ($matches.lines - 0))" hlog("multiple line match $size $($peer.size)"); $hgt = $($matches.hgt); } else { $peer.size = $size } } if ($locn -ne "") { $peer.Location = $locn } $elem = [RadioBoxes]::new($peer,$par); $elem.peer.AutoSizeMode = "GrowAndShrink"; forEach($value in $values) { $ix += 1; $radio = New-Object Windows.Forms.RadioButton $radio.AutoSize = $true; $radio.Location = "$widPos,$hgtPos"; if ($ix -eq $selected) {$radio.checked = $true;} if ($hgt -gt 0) { $hgtPos += $radio.height; } else { $widPos += $radio.width; } $radio.tag = $value; $radio.text = $labels[$ix] [void]$peer.Controls.add($radio) } return $elem } } <#.md .desc Select widget implementation .details Select widget implementation with a current value set by changing the value from among the choice of values. #>
class ComboBox : Gizmo { #---- properties ---- [Object[]]$values; #Values assigned to comboBox #---- constructors ---- <#.md .desc TODO - find out what thi does .details #> ComboBox() {} <#.md .desc TODO - find out what thi does .details #> ComboBox([System.Object]$peer,[Elem]$par) { $this.type = 'combo' $this.setPeer($peer); $par.addChild($this) } #---- methods ---- <#.md .desc Create a combo box .parm $values returned values or uses $labels if null .notes The efficient way to return a null $value for a default (normally 0 index) is to change the $combo.values[0] to $null after passing in only the $labels #>
static [ComboBox] createComboBox([Area]$par,[string]$size,[string]$locn,[Object[]]$values,[Object[]]$labels,[int]$selected) { $peer = New-Object Windows.Forms.ComboBox if ($size -eq "") { $peer.AutoSize = $true; } else { $peer.size = $size } if ($locn -ne "") { $peer.Location = $locn } $elem = [ComboBox]::new($peer,$par); #$elem.peer.AutoScaleMode = "Inherit"; $ix = -1; $elem.values = $values forEach($value in $values) { $ix += 1; [void]$peer.Items.add($labels[$ix]); } if ($selected -ne -1) {$peer.SelectedIndex = $selected} return $elem }
[object]getSelectedValue() { if ($this.peer.selectedIndex -lt 0) {return $null;} return $this.values[$this.peer.selectedIndex]; } } <#.md .desc Multi-Select widget implementation .details Multi-Select widget implementation with a current value set by checking the values from among the choice of values. #>
class CheckedListBox : Gizmo { #---- properties ---- [boolean] $checkLock; #avoid check recursion [Object[]] $values; #Values assigned to CheckedListBox #---- constructors ---- <#.md .desc TODO - find out what thi does .details #> CheckedListBox() {} <#.md .desc TODO - find out what thi does .details #> CheckedListBox([System.Object]$peer,[Elem]$par) { $this.type = 'multi-combo' $this.setPeer($peer); $par.addChild($this) } #---- methods ----
static [CheckedListBox] createCheckedListBox([Area]$par,[string]$size,[string]$locn,[Object[]]$values,[Object[]]$labels,[int[]]$selected) { $peer = New-Object Windows.Forms.CheckedListBox $peer.checkOnClick = $true; if ($size -eq "") { $peer.AutoSize = $true; } else { $peer.size = $size } if ($locn -ne "") { $peer.Location = $locn } $elem = [CheckedListBox]::new($peer,$par); ##EC1B07$ elem.peer.AutoScaleMode = "Inherit"; $ix = -1; $elem.values = $values forEach($value in $values) { $ix += 1; [void]$peer.Items.add($labels[$ix]); } return $elem }
[object[]]getSelectedValues() { $checked = @(); $nIX = -1; forEach($item in $this.peer.items) { $nIX += 1; if ($this.peer.getItemCheckState($nIX) -eq 1) { # checked, not indeterminate $checked += $item; } } return $checked; }
[string]getSelectedValues([string]$sep) { $sepStr = ""; $out = ""; forEach($val in $this.getSelectedValues()) { $out += "$($sepStr)$val" $sepStr = $sep; } return $out; } } <#.md .desc Textbox widget implementation .details Larger, resizable are for input. If a RichTextBox is choses as the peer other formatting is allowed such as RTF formats. #>
class TextBox : Gizmo { #---- properties ---- #---- constructors ---- <#.md .desc Default constructor .details #> TextBox() {} <#.md .desc Constructor that supplies the @@Peer object, typically a RichText object. .details #> TextBox([System.Object]$peer,[Elem]$par) { $this.type = 'textarea' $this.setPeer($peer); $par.addChild($this) } #---- methods ----
static [TextBox] createTextBox([Area]$par,[string]$size,[string]$locn,[boolean]$rich) { if ($rich) { $peer = New-Object Windows.Forms.RichTextBox } else { $peer = New-Object Windows.Forms.TextBox } $peer.text = 'text box'; if ($size -eq "") { $peer.AutoSize = $true; } else { $peer.size = $size } if ($locn -ne "") { $peer.Location = $locn } $elem = [TextBox]::new($peer,$par); #$elem.peer.AutoScaleMode = "Inherit"; if ($rich) { $elem.type = 'richarea' #$elem.peer.ZoomFactor = 0.5; } return $elem } <#.md .desc Add fudge factor for rich text boxes. #>
[int32]getGizmoWidth() { $gizmo = $this; $peer = $gizmo.peer; $wid = ([Gizmo]$gizmo).getGizmoWidth(); if ($gizmo.type -eq 'richarea') { hlog("richarea wid $wid"); return $wid - 50; } return $wid; } <#.md .desc Add fudge factor for rich text boxes. #>
[int32]getGizmoHeight() { $gizmo = $this; $peer = $gizmo.peer; $hgt = ([Gizmo]$gizmo).getGizmoHeight(); if ($gizmo.type -eq 'richarea') { hlog("richarea hgt $hgt"); return $hgt + 50; } return $hgt; } } <#.md .desc A combination of widgets to produce a logging window. .details A logging window that can be cleared and have minimal formatting (such as colors). Typically used to report major major events (such as Task completion) in a GUI application. #>
class LogBox : TextBox { #---- properties ---- #---- constructors ---- <#.md .desc TODO - find out what thi does .details #> LogBox([System.Object]$peer,[Elem]$par) { $this.type = 'logbox' $this.setPeer($peer); $par.addChild($this) } #---- methods ----
static [LogBox]createLogBox([Area]$par,[string]$size) { $peer = New-Object Windows.Forms.RichTextBox $peer.text = 'log box'; $peer.size = "10,10" $elem = [LogBox]::new($peer,$par); #$elem.peer.AutoScaleMode = "Inherit"; return $elem } } <#.md .desc Provide handling context for a click in a Grid .details Stored in a link to act as router and context for a click #>
class CellMate { #---- properties ---- [string] $type; [scriptBlock] $func; [PSMethod] $meth; [object] $ctx; #---- constructors ---- <#.md .desc TODO - find out what thi does .details #> CellMate([string]$type,[scriptBlock]$func,[object]$ctx) { $this.type = $type $this.func = $func; $this.ctx = $ctx; if (($type -eq 'link') -or($type -eq 'rowlink')) { if ($func -eq $null) {throw "type link/rowlink CellMate requires click function"} } else { throw "type $type not supported as CellMate" } } <#.md .desc TODO - find out what thi does .details #> CellMate([string]$type,[PSMethod]$meth,[object]$ctx) { $this.type = $type $this.meth = $meth; $this.ctx = $ctx; if (($type -eq 'link') -or($type -eq 'rowlink')) { if ($meth -eq $null) {throw "type link/rowlink CellMate requires click function"} } else { throw "type $type not supported as CellMate" } } } <#.md .desc Implementation of the Windows.Forms.DataGridView .details This implementation extends the Windows.Forms.DataGridView implemtation with many formatting and handling functions (methods). #>
class Grid : Gizmo { #---- properties ---- [Object] $ctx; # click context [boolean] $hasClickHandler; [string] $selRowColor; [ScriptBlock] $selRowFunc; [int] $curRowIX = -1; #---- constructors ---- <#.md .desc TODO - find out what thi does .details #> Grid([Windows.Forms.DataGridView]$peer,[Elem]$par) { $this.type = 'grid' $this.setPeer($peer); $par.addChild($this) $par.form.addGridList($peer); } #--static methods-
static [Grid] createGrid([Area]$par,[string]$size,[string]$locn,[scriptBlock]$formatter,[System.Object]$ctx) { $peer = New-Object Windows.Forms.DataGridView if ($size -eq "") { $peer.AutoSize = $true; } else { $peer.size = $size } if ($locn -ne "") { $peer.Location = $locn } if ($formatter -ne $null) { $ok = (& $formatter $ctx $peer $par) if ($ok -eq $null) {return $null}; # formatter wants to discontinue } $elem = [Grid]::new($peer,$par); #$elem.peer.AutoScaleMode = "Inherit"; if ($ctx -ne $null) { $elem.ctx = $ctx; #if ($ctx -as [task] -ne $null) {$elem.task = $ctx;} } return $elem } <#.md .desc Calculates the optimal Grid size .details Calculates the optimal Grid size by inspecting the row and column counts. .notes These static methods are used before the dgv is hooked to the Grid class wrapper #>
static [string]calcGridSize([Windows.Forms.DataGridView]$dgv,[int]$maxRows) { $rowH = $dgv.rows[0].height; $rowNum = $dgv.Rows.Count; $extraW = 0; if (($rowNum -gt $maxRows) -and ($maxRows -gt 0)) { #EC9B28 $rowNum = $maxRows; $extraW = 30; } $w = 0; forEach($col in $dgv.Columns) { #hlog("col $($col.width) $col"); $w += $col.width; } $h = (($RowH * $rowNum) + 25); #hlog("dgv size $($dgv.size) new $w $h $dgv"); $size = "$($w + 3 + $extraW),$($h)" $dgv.MaximumSize = $size; return $size; #hlog("dgv.2 size $($dgv.size) new $w $h $dgv"); }
static [string]calcGridSize([Windows.Forms.DataGridView]$dgv) { return [Grid]::calcGridSize($dgv,0); } <#.md .desc Add a set of column headers .details Calculates the optimal Grid size by inspecting the row and column counts. .notes These static methods are used before the dgv is hooked to the Grid class wrapper #>
static [void] addCols([Windows.Forms.DataGridView]$dgv,[string]$colStr) { $cols = $colStr -split ","; $ix = -1 $dgv.ColumnCount = $cols.count; forEach($col in $cols) { $ix += 1; $parts = $col -split "/"; [int]$w = $parts[1] $dgv.Columns[$ix].Name = $parts[0]; $dgv.Columns[$ix].Width = $w; } } <#.md .desc The grid width is more dynamic so we always use the peer size ubnless it ever become $peerW non-zero. #>
[int32]getGizmoWidth() { $gizmo = $this; $peer = $gizmo.peer; if ($gizmo.peerW -eq 0) { return $peer.width } else { return $gizmo.peerW; } } # These are convenience methods for a familiar pattern, 2 cols with ToolHelp on the 2nd col. <#.md .desc TODO - find out what thi does .details #>
static [void] addRow([Windows.Forms.DataGridView]$dgv,[System.Object]$col1,[System.Object]$col2) { [Grid]::addRow($dgv,$col1,$col2,$null); } <#.md .desc TODO - find out what thi does .details #>
static [void] addRow([Windows.Forms.DataGridView]$dgv,[System.Object]$col1,[System.Object]$col2,[string]$hint) { $dgv.Rows.add($col1,$col2) if ($hint -ne $null) { $row = $dgv.Rows[$dgv.Rows.count - 1]; $row.Cells[1].toolTipText = $hint; } } <#.md .desc TODO - find out what thi does .details #>
static [Windows.Forms.DataGridViewRow] getLastRow([Windows.Forms.DataGridView]$dgv) { if ($dgv.rows.count -eq 0) {return $null}; return $dgv.Rows[$dgv.Rows.count - 1]; } # General purpose populator. Returns row so more dinking can be done. <#.md .desc TODO - find out what thi does .details #>
static [Windows.Forms.DataGridViewRow] addRow([Windows.Forms.DataGridView]$dgv,[System.Object[]]$cols) { $dgv.Rows.add($cols[0]) $row = $dgv.Rows[$dgv.Rows.count - 1]; $ix = -1; forEach($v in $cols) { $ix += 1 if (($ix -gt 0) -and ($ix -lt $dgv.Columns.count)) { $cell = $row.Cells[$ix]; $cell.value = $cols[$ix]; } } return $row; } # add hooks to support clickable cells. Those marked with a CellMate type link # call the registered handler. <#.md .desc TODO - find out what thi does .details #>
static [void] addMouseHooks([Windows.Forms.DataGridView]$dgv) { $dgv.add_CellMouseEnter({ $evt = $_; $dgv = $this; [Grid]$grid = $dgv.tag; $grid.cellEnter($evt.rowIndex,$evt.columnIndex); }); $dgv.add_CellMouseLeave({ $evt = $_; $dgv = $this; [Grid]$grid = $dgv.tag; $grid.cellLeave($evt.rowIndex,$evt.columnIndex); }); $dgv.add_CellMouseClick({ $evt = $_; $dgv = $this; [Grid]$grid = $dgv.tag; $grid.cellClick($evt.rowIndex,$evt.columnIndex); }); } #---- methods ---- <#.md .desc TODO - find out what thi does .details #>
[void]cellEnter($ri,$ci) { #hlog("cellEnter ($ri,$ci)"); $dgv = $this.peer; $cell = $dgv.rows[$ri].cells[$ci]; if ($cell.tag -as [CellMate] -ne $null) { [CellMate]$cm = $cell.tag; if ($cm.type -eq 'link') { $cell.style.foreColor = 'black'; $cell.style.backColor = 'cyan'; } } } <#.md .desc TODO - find out what thi does .details #>
[void]cellLeave($ri,$ci) { #hlog("cellLeave ($ri,$ci)"); $dgv = $this.peer; $cell = $dgv.rows[$ri].cells[$ci]; if ($cell.tag -as [CellMate] -ne $null) { [CellMate]$cm = $cell.tag; if ($cm.type -eq 'link') { $cell.style.foreColor = 'blue'; $cell.style.backColor = 'white'; } } } <#.md .desc TODO - find out what thi does .details #>
[void]cellClick($ri,$ci) { $dgv = $this.peer; $grid = $dgv.tag; $cell = $dgv.rows[$ri].cells[$ci]; $row = $dgv.rows[$ri]; hlog("cellClick ($ri,$ci) $($grid.curRowIX) $($grid.selRowColor)"); if (("$($grid.selRowColor)" -ne "") -and ($($grid.curRowIX) -ne -1)) { $rowPrev = $dgv.rows[$grid.curRowIX]; $rowPrev.defaultcellstyle.backcolor = "white"; } $grid.curRowIX = $ri; if ("$($grid.selRowColor)" -ne "") { $row.defaultcellstyle.backcolor = $grid.selRowColor; if ($grid.selRowFunc -ne $null) { [void](& $grid.selRowFunc $grid $ri); } } if ($cell.tag -as [CellMate] -ne $null) { [CellMate]$cm = $cell.tag; if (($cm.type -eq 'link') -and ($cm.func -ne $null)) { [void](& $cm.func $cell $cm); } if (($cm.type -eq 'link') -and ($cm.meth -ne $null)) { #[void](& $cm.func $cell $cm); hlog("invoke cellMate method"); [void]($cm.meth.invoke(@($cell,$cm))); } } if ($row.tag -as [CellMate] -ne $null) { [CellMate]$cm = $row.tag; if ($cm.type -eq 'rowlink') { [void](& $cm.func $row $cm); } } } <#.md .desc Sets a mode where rows are focused, not cells using the supplied background color .details Requires that we set the clearSelection just prior to row painting. #>
[Grid]focusRow([string]$bg,[ScriptBlock]$func) { $dgv = $this.peer; $this.selRowColor = $bg; $this.selRowFunc = $func; $dgv.add_RowPrePaint({ $dgv = $this; #hlog("RowPrePaint $dgv"); if ($dgv.CurrentCell -ne $null) { $dgv.ClearSelection(); } }); return $this; } <#.md .desc Programatically sets a row as focused .details Assumed focusRow is enabled .notes using $rownum = -1 will turn of focused rows #>
[void]setRowAsFocused([int]$rownum) { $grid = $this; $dgv = $grid.peer; if (("$($grid.selRowColor)" -ne "") -and ($($grid.curRowIX) -ne -1)) { $rowPrev = $dgv.rows[$grid.curRowIX]; $rowPrev.defaultcellstyle.backcolor = "white"; } $grid.curRowIX = $rownum; if (("$($grid.selRowColor)" -ne "") -and ($($grid.curRowIX) -ne -1)) { $row = $dgv.rows[$rownum]; $row.defaultcellstyle.backcolor = $this.selRowColor; } } } # ------------------------ Standard Contructs ------------------
function emitTrouble([Form]$form,[string]$msg,[string]$title) { if ($title -eq "") {$title = "Trouble: Unable to continue with request"} $dlg = [Dialog]::createDialog($form,800,300,"bng",$title,$true) #$dlg.BackColor( = rgb 250 243 251 $dlg.peer.MaximizeBox = $false $dlg.peer.FormBorderStyle = 'FixedDialog' $lab = ([Label]::createLabel($dlg,"$msg")).foreColor('flag') $dlg.showForm(); }
X
PSEC - Powershell Enhanced Capability
1.2.1
  src: gui-classes.psm1

Copyright © 2018-2021, 2022, Rexcel System Inc.