GuiMyPS.psm1

<#
.SYNOPSIS
    Adds a Click event handler to every button within a given element.
 
.DESCRIPTION
    The Add-ClickToEveryButton function traverses the visual tree of a given element and adds a Click event handler to every button found. Specific buttons can be ignored based on their names.
 
.PARAMETER Element
    The root element to start the search.
 
.PARAMETER ClickHandler
    The Click event handler to be added to each button.
 
.EXAMPLE
    Add-ClickToEveryButton -Element $window -ClickHandler $ClickHandler
 
    Traverses the visual tree of the $window element and adds the $ClickHandler Click event handler to every button found.
 
.NOTES
    Author: Brooks Vaughn
    Date: 2025-02-18 14:22:52
#>

Function Add-ClickToEveryButton {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true)]
        $Element,
        [Parameter(Mandatory = $false)]
        [System.Windows.RoutedEventHandler]$ClickHandler
    )

    # Dump Parameter Values
    # $PSBoundParameters.GetEnumerator() | ForEach-Object {
    # Write-Verbose "$($_.Key) = $($_.Value)"
    # }

    If ($Element -is 'System.Windows.Controls.Button') {
        Write-Verbose ("Add-ClickToEveryButton() Button Object: [{0}] - {1}" -f $Element.GetType().ToString(), $Element.Name)
        If (-not [String]::IsNullOrEmpty($ClickHandler)) {
            Write-Verbose (" Adding Click Event For: {0}" -f $Element.Content)
            $Element.Add_Click($ClickHandler)
        }
    } Else {
        Write-Verbose ("Add-ClickToEveryButton() Object: [{0}] - {1}" -f $Element.GetType().ToString(), $Element.Name)
    }

    If ($Element.HasItems -or $Element.HasContent -or $Element.Child.Count -gt 0 -or $Element.Children.Count -gt 0 -or $Element.Items.Count -gt 0) {
        # The logical tree can contain any type of object, not just
        # instances of DependencyObject subclasses. LogicalTreeHelper
        # only works with DependencyObject subclasses, so we must be
        # sure that we do not pass it an object of the wrong type.
        $depObj = $Element
        If ($null -ne $depObj) {
            ForEach ($logicalChild in ([System.Windows.LogicalTreeHelper]::GetChildren($depObj))) {
                Add-ClickToEveryButton -Element $logicalChild -ClickHandler:$ClickHandler
            }
        }
    }
}


<#
.SYNOPSIS
    Adds a Click event handler to every menu item within a given menu object.
 
.DESCRIPTION
    The Add-ClickToEveryMenuItem function traverses the visual tree of a given menu object and adds a Click event handler to every menu item found. Specific menu items can be ignored based on their properties.
 
.PARAMETER MenuObj
    The root menu object to start the search.
 
.PARAMETER Handler
    The Click event handler to be added to each menu item.
 
.EXAMPLE
    Add-ClickToEveryMenuItem -MenuObj $menu -Handler $handler
 
    Traverses the visual tree of the $menu object and adds the $handler Click event handler to every menu item found.
 
.NOTES
    Author: Brooks Vaughn
    Date: 2025-02-18 14:22:52
#>

Function Add-ClickToEveryMenuItem {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)]
        $MenuObj,
        [Parameter(Mandatory = $true)]
        [ScriptBlock]$Handler
    )

    # Dump Parameter Values
    $PSBoundParameters.GetEnumerator() | ForEach-Object {
        Write-Verbose "$($_.Key) = $($_.Value)"
    }

    If ([String]::IsNullOrEmpty($Handler)) {
        Write-Warning "Parameter -Handler `$Handler cannot be blank"
        Return
    }
    If ([String]::IsNullOrEmpty($MenuObj)) {
        Write-Warning "Parameter -MenuObj `$MenuObj cannot be blank"
        Return
    }

    Write-Verbose ("Menu Object: [{0}] - {1}" -f $MenuObj.Name, $MenuObj.GetType().ToString())
    ForEach ($child in $MenuObj.Items) {
        Write-Verbose (" {0} - [{1}]" -f $child.Header, $child.GetType().ToString())
        If ($child.HasItems) {
            Add-ClickToEveryMenuItem -MenuObj $child -Handler:$Handler
        } Else {
            If ($child -is 'System.Windows.Controls.MenuItem') {
                If (-not [String]::IsNullOrEmpty($Handler)) {
                    Write-Verbose (" Adding Click Event For: {0}" -f $child.Content)
                    $child.Add_Click($Handler)
                } Else {
                    $child.Add_Click($null)
                }
            }
        }
    }
}

<#
.SYNOPSIS
    Adds event handlers to every checkbox within a given element.
 
.DESCRIPTION
    The Add-EventsToEveryCheckBox function traverses the visual tree of a given element and adds event handlers to every checkbox found. Specific event handlers can be added based on the properties of the checkboxes.
 
.PARAMETER Element
    The root element to start the search.
 
.PARAMETER ClickHandler
    The Click event handler to be added to each checkbox.
 
.PARAMETER CheckedHandler
    The Checked event handler to be added to each checkbox.
 
.PARAMETER UncheckedHandler
    The Unchecked event handler to be added to each checkbox.
 
.PARAMETER PreviewMouseUpHandler
    The PreviewMouseUp event handler to be added to each TreeViewItem.
 
.PARAMETER PreventSelectionScrolling
    A switch to prevent horizontal content scrolling when an item is clicked.
 
.EXAMPLE
    Add-EventsToEveryCheckBox -Element $window -ClickHandler $clickHandler
 
    Traverses the visual tree of the $window element and adds the $clickHandler Click event handler to every checkbox found.
 
.NOTES
    Author: Brooks Vaughn
    Date: 2025-02-18 14:22:52
#>

Function Add-EventsToEveryCheckBox {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true)]
        $Element,
        [Parameter(Mandatory = $false)]
        [System.Windows.RoutedEventHandler]$ClickHandler = $null,
        [Parameter(Mandatory = $false)]
        [System.Windows.RoutedEventHandler]$CheckedHandler = $null,
        [Parameter(Mandatory = $false)]
        [System.Windows.RoutedEventHandler]$UncheckedHandler = $null,
        [Parameter(Mandatory = $false)]
        [System.Windows.Input.MouseButtonEventHandler]$PreviewMouseUpHandler = $null,
        [Switch]$PreventSelectionScrolling
    )

    # Dump Parameter Values
    # $PSBoundParameters.GetEnumerator() | ForEach-Object {
    # Write-Verbose "$($_.Key) = $($_.Value)"
    # }

    Write-Verbose ("Add-EventsToEveryCheckBox(): [{0}] : ({1})" -f $($Element.GetType().ToString()), $($Element.Name))

    If ($Element -is 'System.Windows.Controls.TreeViewItem') {
        $Element.IsExpanded = $true # "True"
        If ($null -ne $PreviewMouseUpHandler) {
            $Element.Add_PreviewMouseUp($PreviewMouseUpHandler)
        }
        If ($PreventSelectionScrolling) {
            # This prevents horizontal content scrolling when an item is clicked
            $Element.Add_RequestBringIntoView({
                Param(
                    [object]$theSender, 
                    [System.Windows.RequestBringIntoViewEventArgs]$e
                )
                Write-Verbose ("`$Element.Add_RequestBringIntoView {0}: {1}({2})" -f $theSender.Name, $e.Source.Name, $e.ToString())
                # Mark the event as handled
                $e.Handled = $true
            })
        }
        <#
        $Count = [System.Windows.Media.VisualTreeHelper]::GetChildrenCount($Element)
        For ($i=0; $i -lt $Count; $I++) {
            $current = [System.Windows.Media.VisualTreeHelper]::GetChild($Element, $i);
            Add-EventsToEveryCheckBox -Element $current -ClickHandler:$ClickHandler -CheckedHandler:$CheckedHandler -UncheckedHandler:$UncheckedHandler
        }
        #>

        Write-Verbose (" Header Name: {0}, Type: {1}" -f $Element.Header.Type, $Element.Header.Name)
        <#
        If ($Element.Header.Type -eq 'Server') {
            $current = [System.Windows.Media.VisualTreeHelper]::GetChild($Element, 0);
            Add-EventsToEveryCheckBox -Element $current -ClickHandler:$ClickHandler -CheckedHandler:$CheckedHandler -UncheckedHandler:$UncheckedHandler -PreviewMouseUpHandler:$PreviewMouseUpHandler
        }
        #>

    } ElseIf ($Element -is 'System.Windows.Controls.TextBlock') {
        Write-Verbose (" TextBlock: {0}" -f $($Element.Inlines.Text | Out-String))
    } ElseIf ($Element -is 'System.Windows.Controls.CheckBox') {
        Write-Verbose (" Adding CheckBox Event Handlers For: " + $Element.Content.Inlines.text -join "")
        If (-not [Sting]::IsNullOrEmpty($ClickHandler)) {
            $Element.Add_Click($ClickHandler)
        }
        If (-not [Sting]::IsNullOrEmpty($CheckedHandler)) {
            $Element.Add_Checked($CheckedHandler)
        }
        If (-not [Sting]::IsNullOrEmpty($UncheckedHandler)) {
            $Element.Add_Unchecked($UncheckedHandler)
        }
    }

    $Count = [System.Windows.Media.VisualTreeHelper]::GetChildrenCount($Element)
    For ($i=0; $i -lt $Count; $i++) {
        $current = [System.Windows.Media.VisualTreeHelper]::GetChild($Element, $i)
        Add-EventsToEveryCheckBox -Element $current -ClickHandler:$ClickHandler -CheckedHandler:$CheckedHandler -UncheckedHandler:$UncheckedHandler -PreviewMouseUpHandler:$PreviewMouseUpHandler
    }
}

<#
.SYNOPSIS
    Generates Event Handler Code Snippet for the desired controlType.
 
.DESCRIPTION
    The Build-HandlerCode function generates an event handler code snippet for the specified control type.
    $Elements is an array of [PSCustomObject] created by the Find-EveryControl function.
    $ControlType is the type of elements in $Elements and is used mostly for naming the handler.
 
.PARAMETER Elements
    An array of [PSCustomObject] representing the elements created by Find-EveryControl() function.
    [PSCustomObject]@{
        Name = $Element.Name
        Type = $Element.GetType().ToString()
        Text = (Get-ControlContent -Element $Element)
        Element = $Element
    }
 
.PARAMETER ControlType
    Is the type of elements in $Elements and is used mostly for naming of the handler
 
.EXAMPLE
    $elements = @()
    $elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem'
    $elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Primitives.ToggleButton'
    Build-HandlerCode -Elements $elements -ControlType System.Windows.Controls.MenuItem
 
.NOTES
    Author: Brooks Vaughn
    Date: 2025-02-19 18:12:04
#>

Function Build-HandlerCode {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)]
        [System.Object[]]$Elements,
        [Parameter(Mandatory = $true)]
        [String]$ControlType
    )

    If ($null -eq $Elements) {
        Write-Warning "Parameter -Elements is empty or missing"
        Return
    }

    If ([String]::IsNullOrEmpty($ControlType)) {
        Write-Host "Parameter -ControlType is empty or missing" -ForegroundColor Red
        Return
    }

    $eol = [System.Environment]::NewLine
    $sb = [System.Text.StringBuilder]::new()
    $handlerName = "`$handler_$($ControlType.Split('.')[-1])_Click"

    # Starting Code
    [void]$sb.AppendLine(@"
$handlerName = {
    Param ([object]`$theSender, [System.EventArgs]`$e)
    Write-Host ("``$handlerName() Item clicked: {0}" -f `$theSender.Name)
    Switch -Regex (`$theSender.Name) {
"@
 + $eol)

    # Body Code consists of Regex patterns of the Item Element Name it finds of matching ControlTypes
    ForEach ($element in $Elements) {
        If (-not [String]::IsNullOrEmpty($element.Name)) {
            [void]$sb.AppendLine(@"
            '^$($element.Name)$' {
                Break
            }
"@
)
        }
    }

    # Ending Code
    [void]$sb.AppendLine(@"
        default {
            Write-Host ("{0}: {1}({2})" -f `$theSender.Name, `$e.OriginalSource.Name, `$e.OriginalSource.ToString())
        }
    }
}
"@
)

    $sb.ToString()
}

<# Usage Example # >
$elements = @()
$elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem'
$elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Primitives.ToggleButton'
Build-HandlerCode -Elements $elements -ControlType 'System.Windows.Controls.MenuItem'
# How to use the same $elements list to Add_Click() Events
$elements.ForEach({$_.Element.Add_Click($handler_MenuItem_Click)})
#>


<#
.SYNOPSIS
    Finds every control of a specified type within a given element.
 
.DESCRIPTION
    The Find-EveryControl function traverses the visual or logical tree of a given element and finds every control of a specified type. Specific controls can be excluded based on their properties.
 
.PARAMETER ControlType
    The type of control to Gather
 
.PARAMETER Element
    The root element to start searching from
 
.PARAMETER ExcludeElement
    A switch to exclude attaching the element to the results.
 
.PARAMETER UseVisualTreeHelper
    A switch to use VisualTreeHelper for the search.
 
.PARAMETER IncludeAll
    A switch to include all controls in the search results.
 
.EXAMPLE
    Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Primitives.ToggleButton'
 
    Finds every ToggleButton control within the $form element.
 
.EXAMPLE
    Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' -ExcludeElement -UseVisualTreeHelper -IncludeAll
     
    Finds all controls (-IncludeAll) using VisualTreeHelper. Best use after exiting ShowDialog(). Excludes attaching Element (-ExcludeElement) to result
     
.EXAMPLE
    $elements = @()
    $elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Primitives.ToggleButton'
    $elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem'
    $elements.ForEach({$_.Element.Add_Click($handler_MenuItem_Click)})
 
    Find all MenuItem and ToggleButton elements and Add Click Event Handler
 
.EXAMPLE
    $elements = @()
    $elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Primitives.ToggleButton' -ExcludeElement
    $elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' -ExcludeElement
    Build-HandlerCode -Elements $elements -ControlType System.Windows.Controls.MenuItem
     
    Find all MenuItem and ToggleButton elements and Generate the code for the Click Event Handler
 
.NOTES
    Author: Brooks Vaughn
    Date: 2025-02-19 14:29:36
#>

Function Find-EveryControl {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true)]
        $ControlType,
        [Parameter(Mandatory = $true)]
        $Element,
        [Parameter(Mandatory = $false)]
        [Switch]$ExcludeElement,
        [Parameter(Mandatory = $false)]
        [Switch]$UseVisualTreeHelper,
        [Parameter(Mandatory = $false)]
        [Switch]$IncludeAll
    )

    If (-not [String]::IsNullOrEmpty($IncludeAll) -and $IncludeAll) {
        $includeElement = $null
        If (-not $ExcludeElement -or [String]::IsNullOrEmpty($ExcludeElement)) {
            $includeElement = $Element
        }
        [PSCustomObject]@{
            Name = $Element.Name
            Type = $Element.GetType().ToString()
            Text = (Get-ControlContent -Element $Element)
            Element = $includeElement
        }        
    } Else {
        If ($Element -is $ControlType) {
            $includeElement = $null
            If (-not $ExcludeElement -or [String]::IsNullOrEmpty($ExcludeElement)) {
                $includeElement = $Element
            }
            [PSCustomObject]@{
                Name = $Element.Name
                Type = $Element.GetType().ToString()
                Text = (Get-ControlContent -Element $Element)
                Element = $includeElement
            }
        }
    }

    If ($UseVisualTreeHelper) {
        $Count = [System.Windows.Media.VisualTreeHelper]::GetChildrenCount($Element)
        If ($Count -gt 0) {
            For ($i=0; $i -lt $Count; $i++) {
                $current = [System.Windows.Media.VisualTreeHelper]::GetChild($Element, $i)
                Find-EveryControl -Element $current -ControlType:$ControlType -IncludeAll:$IncludeAll -ExcludeElement:$ExcludeElement -UseVisualTreeHelper:$UseVisualTreeHelper
            }
        }
    } Else {
        If ($Element.HasContent) {
            ForEach ($item in $Element.Content) {
                Find-EveryControl -Element $item -ControlType:$ControlType -IncludeAll:$IncludeAll -ExcludeElement:$ExcludeElement
            }
        }
        If ($Element.Children) {
            ForEach ($child in $Element.Children) {
                Find-EveryControl -Element $child -ControlType:$ControlType -IncludeAll:$IncludeAll -ExcludeElement:$ExcludeElement
            }
        }
        If ($Element.HasItems) {
            ForEach ($item in $Element.Items) {
                Find-EveryControl -Element $item -ControlType:$ControlType -IncludeAll:$IncludeAll -ExcludeElement:$ExcludeElement
            }
        }
    }
}

# Example usage:
# Find-EveryControl -Element $WPF_menuMasterDataGrid -ControlType 'System.Windows.Controls.MenuItem'
# Find-EveryControl -Element $WPF_menuDetailDataGrid -ControlType 'System.Windows.Controls.MenuItem'
# Find-EveryControl -Element $WPF_tabControl -ControlType 'System.Windows.Controls.Button'
# Find-EveryControl -Element $WPF_gridSqlQueryEditor -ControlType 'System.Windows.Controls.Primitives.ToggleButton'
# Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem'
# Find-EveryControl -Element $WPF_tabControl -ControlType 'System.Windows.Controls.Primitives.ToggleButton'
# Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Primitives.ToggleButton'
# Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem'
# Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.RadioButton'
# Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Combobox'
# Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Button'
# Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Primitives.ToggleButton'
# Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' -IncludeAll -ExcludeElement -UseVisualTreeHelper
# Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' -IncludeAll -ExcludeElement

# How to identify all controls (-IncludeAll) from VisualTreeHelper. Use after exiting ShowDialog()
# Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' -ExcludeElement -UseVisualTreeHelper -IncludeAll
# Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' -ExcludeElement -UseVisualTreeHelper
# Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem' -ExcludeElement


<#
.SYNOPSIS
Locates the root element (main form) of a control.
 
.DESCRIPTION
This function traverses the visual tree to find the root element of a given control.
 
.PARAMETER Element
The control for which to find the root element.
 
.EXAMPLE
    $form = Find-RootElement -Element $Button
 
.NOTES
    Author: Brooks Vaughn
    Date: 2025-02-19 14:29:36
#>


Function Find-RootElement {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true)]
        [System.Windows.DependencyObject]
        $Element
    )
    While ($Parent = [System.Windows.Media.VisualTreeHelper]::GetParent($Element)) {
        $Element = $Parent
    }
    Return $Element
}

<#
.SYNOPSIS
    Formats an XML string with indentation and optional attribute formatting and XML declaration.
 
.DESCRIPTION
    The Format-XML function formats an XML string with a specified indentation level. It provides options to format attributes with new lines and to include or omit the XML declaration.
 
.PARAMETER xml
    The XML string to be formatted.
 
.PARAMETER indent
    The number of spaces to use for indentation. Default is 4.
 
.PARAMETER FormatAttributes
    A switch to format attributes with new lines.
 
.PARAMETER IncludeXmlDeclaration
    A switch to include the XML declaration.
 
.EXAMPLE
    Format-XML -xml $inputXML -indent 4
 
    Formats the XML string $inputXML with an indentation level of 4 spaces.
 
.EXAMPLE
    Format-XML -xml $inputXML -indent 4 -FormatAttributes
 
    Formats the XML string $inputXML with an indentation level of 4 spaces and formats attributes with new lines.
 
.EXAMPLE
    Format-XML -xml $inputXML -indent 4 -IncludeXmlDeclaration
 
    Formats the XML string $inputXML with an indentation level of 4 spaces and includes the XML declaration.
 
.NOTES
    Author: Brooks Vaughn
    Date: 2025-02-18 14:22:52
#>

function Format-XML {
    [CmdletBinding()]
    Param (
        [parameter(Mandatory=$true,ValueFromPipeLine=$true)]
        [xml]$xml,
        [parameter(Mandatory=$false,ValueFromPipeLine=$false)]
        $indent = 4,
        [switch]$FormatAttributes,
        [switch]$IncludeXmlDeclaration
    )
    Process {
        $StringWriter = New-Object System.IO.StringWriter
        $XmlWriterSettings = New-Object System.Xml.XmlWriterSettings
        $XmlWriterSettings.Indent = $true
        $XmlWriterSettings.IndentChars = " " * $indent
        If (-not [Sting]::IsNullOrEmpty($IncludeXmlDeclaration)) {
            $XmlWriterSettings.OmitXmlDeclaration = $true
        }
        If (-not [Sting]::IsNullOrEmpty($FormatAttributes)) {
            $XmlWriterSettings.NewLineOnAttributes = $true
        }
        $XmlWriter = [System.Xml.XmlWriter]::Create($StringWriter, $XmlWriterSettings)
        $xml.WriteTo($XmlWriter)
        $XmlWriter.Flush()
        $StringWriter.Flush()
        $XmlWriter.Close()
        $Result = $StringWriter.ToString()
        $StringWriter.Dispose()
        $Result
    }
}

<#
.SYNOPSIS
    Retrieves the content of a given control element.
 
.DESCRIPTION
    The Get-ControlContent function retrieves the content of a given control element based on its properties, such as Content, Header, Text, or SelectedItem.
 
.PARAMETER Element
    The control element from which to retrieve the content.
 
.EXAMPLE
    Get-ControlContent -Element $button
 
    Retrieves the content of the $button element.
 
.NOTES
    Author: Brooks Vaughn
    Date: 2025-02-19 18:16:11
#>

Function Get-ControlContent {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)]
        $Element 
    )

    Switch ($Element) {
        { $_.Content }        { Return $_.Content }
        { $_.Header }         { Return $_.Header }
        { $_.Text }           { Return $_.Text }
        { $_.SelectedItem }   { Return $_.SelectedItem }
        Default               { Return $_.Name }
    }
}

<#
.SYNOPSIS
    Displays WPF_* form variables which are XAML elements whose x:Name begins with an _ underscore character.
 
.DESCRIPTION
    This function displays a list of WPF_* variables of XAML Elements which can accessed and referenced directly,
 
.EXAMPLE
    Get-FormVariable
-----------------------------------------------------------------
Found the following intractable elements from our form
-----------------------------------------------------------------
Name Value
---- -----
WPF_dataGrid System.Windows.Controls.DataGrid Items.Count:0
WPF_saveButton System.Windows.Controls.Button: Save
-----------------------------------------------------------------
 
.NOTES
    Author: Brooks Vaughn
    Date: Wednesday, 19 February 2025 19:26
#>


Function Get-FormVariable {
    [CmdletBinding()]
    param ()

    if ($null -eq $ReadmeDisplay -or $ReadmeDisplay -eq $false) {
        Write-Host "If you need to reference this display again, run Get-FormVariables"
    }
    Write-Host ("`n$("-" * 65)`nFound the following intractable elements from our form`n$("-" * 65)")
    Get-Variable WPF*
    Write-Host ("$("-" * 65)`n")
}


<#
.SYNOPSIS
    Returns a PSObject containing details about an object's properties.
 
.DESCRIPTION
    The Get-ObjectPropertyDetail function returns a PSObject that lists the properties of the input object along with their names, values, and types.
 
.PARAMETER InputObject
    The object whose properties are to be detailed.
 
.EXAMPLE
    $details = Get-ObjectPropertyDetail -InputObject $form1
    $details | Format-Table -AutoSize
    $details | Format-Table -AutoSize -Force -Wrap -Property Property, Value, Type
 
    Retrieves the properties of the object $form1 and displays them in a formatted table.
 
.NOTES
    Author: Brooks Vaughn
    Date: 2025-02-18 15:41:05
#>

Function Get-ObjectPropertyDetail {
    [CmdletBinding()]
    Param (
        [parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Object]$InputObject
    )
    Begin {
        $ReturnObject = [System.Collections.Generic.List[PSCustomObject]]::new()
    }
    Process {
        ForEach ($property in $InputObject.PSObject.Properties) {
            $typeName = [Microsoft.PowerShell.ToStringCodeMethods]::Type([type]$property.TypeNameOfValue)
            $value = $property.Value

            If ($typeName -like '*IScriptExtent*') {
                $file = if ($null -eq $value.File) { "" } else { Split-Path -Leaf $value.File }
                $value = "{0} ({1},{2})-({3},{4})" -f $file, $value.StartLineNumber, $value.StartColumnNumber, $value.EndLineNumber, $value.EndColumnNumber
            }

            [void]$ReturnObject.Add([PSCustomObject]@{
                Property = $property.Name
                Value    = $value
                Type     = $typeName
            })
        }
    }
    End {
        $ReturnObject
    }
}


<#
.SYNOPSIS
    Helper function that processes and loads XAML (as string, filename, or as [XML] object) into a WPF Form Object.
 
.DESCRIPTION
    The New-XamlWindow function processes and loads XAML (as string, filename, or as [XML] object) into a WPF Form Object.
 
.PARAMETER xaml
    The XAML content to process and load.
 
.PARAMETER NoXRemoval
    A switch to prevent the removal of x:Name attributes.
 
.EXAMPLE
    # Test for XAML String
    try {
        $form1 = New-XamlWindow -xaml $inputXML
        Add-ClickToEveryButton -Element $form1 -ClickHandler $handler_button_Click
        $form1.ShowDialog()
    } catch {
        Write-Warning ($_ | Format-List | Out-String)
    }
 
.EXAMPLE
    # Test for File Path to XAML file with two approaches for Adding Click Events
    # Note: Code for $handler_MenuItem_Click can be generated using Build-HandlerCode()
    try {
        $form = New-XamlWindow -xaml 'C:\Git\SqlQueryEditor\src\resources\SqlQueryEditor.xaml'
        # Add-ClickToEveryMenuItem -MenuObj $WPF_menuMasterDataGrid -Handler $handler_MenuItem_Click
        # Add-ClickToEveryMenuItem -MenuObj $WPF_menuDetailDataGrid -Handler $handler_MenuItem_Click
        $elements = @()
        $elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.Primitives.ToggleButton'
        $elements += Find-EveryControl -Element $form -ControlType 'System.Windows.Controls.MenuItem'
        $elements.ForEach({$_.Element.Add_Click($handler_MenuItem_Click)})
        $form.ShowDialog()
    } catch {
        Write-Warning ($_ | Format-List | Out-String)
    }
 
.EXAMPLE
    # Test for [System.Xml.XmlDocument]
    try {
        $xaml = [xml]$inputXML
        $form2 = New-XamlWindow -xaml $xaml
        Add-ClickToEveryButton -Element $form2 -ClickHandler $handler_button_Click
        $form2.ShowDialog()
    } catch {
        Write-Warning ($_ | Format-List | Out-String)
    }
 
.NOTES
    Author: Brooks Vaughn
    Date: 2025-02-19 18:28:36
#>

Function New-XamlWindow {
    [CmdletBinding(SupportsShouldProcess = $true)]
    Param (
        [Parameter(Mandatory = $true)]
        [Alias("InputObject")]
        [System.Object]$xaml,

        [Parameter(Mandatory = $false)]
        [switch]$NoXRemoval
    )

    # Load the necessary assemblies
    Add-Type -AssemblyName PresentationFramework

    <#
    XAML Elements might use either x:Name="" or Name=""
    The x: refers to the namespace xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" which is not automatically defined in PowerShell.
    To use XPath queries during preprocessing of the XAML Text, you have to create a NamespaceManager and add the missing namespaces.
    For x:Name, the XPath query would be $xaml.SelectNodes("//*[@x:Name]", $nsManager).
    For Name, the XPath query would be $xaml.SelectNodes("//*[@Name]", $nsManager).
    By removing x:Name from the XAML string before converting to [System.Xml.XmlDocument], there is no need for the NamespaceManager and XPath needs only $xaml.SelectNodes("//*[@Name]").
    #>

    Function Remove-XName {
        [CmdletBinding(SupportsShouldProcess = $true)]
        Param (
            [Parameter(Mandatory = $true)]
            [string]$xamlString,

            [Parameter(Mandatory = $false)]
            [switch]$NoXRemoval
        )

        If ($NoXRemoval) {
            $xamlString
        } Else {
            If ($PSCmdlet.ShouldProcess("XAML String", "Remove x:Name attributes")) {
                ((($xamlString -replace 'mc:Ignorable="d"', '') -replace "x:Na", 'Na') -replace '^<Win.*', '<Window')
            } Else {
                $xamlString
            }
        }
    }

    # Define return and working variables
    $uiForm = $null
    $xamlReader = $null

    Try {
        # Process the $xaml input object into an XmlDocument and create reader
        If ($xaml -is [System.Xml.XmlDocument]) {
            $newXaml = $xaml
        } ElseIf ($xaml -is [System.String]) {
            If (Test-Path -Path $xaml -PathType Leaf -ErrorAction SilentlyContinue) {
                $xamlString = Remove-XName -NoXRemoval:$NoXRemoval -xamlString (Get-Content -Path $xaml -Raw)
                If ($xamlString -match '<(Window|Grid|StackPanel|Button|DataGrid|TextBox)') {
                    $newXaml = [xml]$xamlString
                } Else {
                    Throw "-xaml parameter as file path was valid but its content is not XAML"
                }
            } ElseIf ($xaml -match '<(Window|Grid|StackPanel|Button|DataGrid|TextBox)') {
                $xamlString = Remove-XName -NoXRemoval:$NoXRemoval -xamlString $xaml
                $newXaml = [xml]$xamlString
            } Else {
                Throw "-xaml parameter as XAML String has no valid XAML content"
            }
        } Else {
            Throw "-xaml is not a valid [System.Xml.XmlDocument], XAML string, or filepath to a XAML file"
        }

        # Perform XML data cleanup and create PowerShell $WPF_* Variables for XAML names that begin with "_"
        # Remove unsupported namespaces created by XAML editors like Blend / Visual Studio
        $newXaml.Window.RemoveAttribute('x:Class')
        $newXaml.Window.RemoveAttribute('d')
        $newXaml.Window.RemoveAttribute('mc:Ignorable')

        $problemObjects = $newXaml.SelectNodes("//*[@Name]").Where({
            $_.Click -or $_.TextChanged -or $_.SelectionChanged -or $_.Checked -or $_.Unchecked -or $_.ValueChanged}) | 
                Select-Object -Property Name, LocalName, Click, TextChanged, SelectionChanged, Checked, Unchecked, ValueChanged

        $errorHeading = "XAML String has unsupported events defined and cannot be converted by PowerShell WPF" + [System.Environment]::NewLine
        $errorText = ForEach ($obj in $problemObjects) {
            Switch ($obj) {
                { $_.Click } { "$($_.LocalName) named ($($_.Name)) has Click event and needs to be removed"; break }
                { $_.TextChanged } { "$($_.LocalName) named ($($_.Name)) has TextChanged event and needs to be removed"; break }
                { $_.SelectionChanged } { "$($_.LocalName) named ($($_.Name)) has SelectionChanged event and needs to be removed"; break }
                { $_.Checked } { "$($_.LocalName) named ($($_.Name)) has Checked event and needs to be removed"; break }
                { $_.Unchecked } { "$($_.LocalName) named ($($_.Name)) has Unchecked event and needs to be removed"; break }
                { $_.ValueChanged } { "$($_.LocalName) named ($($_.Name)) has ValueChanged event and needs to be removed"; break }
                Default { "Skipping: $($_.LocalName) named ($($_.Name))" }
            }
        }
        If ($problemObjects.Count -gt 0) {
            Write-Host ($errorHeading) -ForegroundColor Red
            Write-Host ($errorText | Out-String) -ForegroundColor Magenta
            Write-Host "Please remove the event definitions from the XAML and try again" -ForegroundColor Red
            Exit
        }

        # Create and Load the XAML Reader to create the WPF Form
        $xamlReader = (New-Object System.Xml.XmlNodeReader $newXaml)
        $uiForm = [System.Windows.Markup.XamlReader]::Load($xamlReader)

        # Create a PowerShell Variable $WPF_ for each Form Object defined in the XAML where the
        # element name begin with "_" as in Name="_MyObj" or x:Name="_MyObj"
        # For the most part, this allows the Code-Behind to directly access the element without needing to find it first
        $FormObjects = $newXaml.SelectNodes("//*[@Name]").Where({ $_.Name -like "_*" }).ForEach({
            $element = $uiForm.FindName($_.Name)
            If ($element) {
                Write-Output ("WPF$($_.Name)")
                Set-Variable -Name "WPF$($_.Name)" -Value $element -Force -Scope Global -Visibility Public
            } Else {
                Write-Warning "Unable to locate $($_.Name) element name"
            }
        })
        Write-Host "`nList of `$WPF_ named Variables of XAML Elements`n$($FormObjects | Out-String)`n" -ForegroundColor Green
        $uiForm
    } Catch {
        Write-Error ($_ | Format-List | Out-String)
    }
}