# 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();
}
PSEC is an extensive set of Powershell enhancements that, along with other acknowledged works, is deployed into the public domain subject to the limitations detailed below.
Ackowledgements
Pandoc, the system used to produce this documentation.
More detailed information is found by clicking on the link that triggered this popup