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) } } |