Search code examples
regexpowershellreplacetextboxtextchanged

PowerShell WinForms textbox, prevent unwanted regex pattern characters


In PowerShell I've been trying different ways of stripping out all characters from a WinForms textbox other than the pattern I need and I cannot come up with a better solution for what I'm after; I am learning regex and need some guidance. This feels basic to me but I feel like I'm missing something obvious. I'm using the textbox's TextChanged event to prevent illegal characters from being typed and I have (what feels like) a bloated solution.

Goal:

  • User allowed to type a single leading dollar sign followed by up to 6 chars from [A-V0-9]
    • If no $ is typed, the first legal character typed should produce a leading $ followed by said legal character (typing A will produce $A in the textbox)
    • If an illegal character is typed first, no leading $ should be produced in the textbox
  • Illegal characters won't show up in the textbox
  • Pasted text is processed/stripped as efficiently as possible
  • The last character deleted will also delete the remaining $
    • If textbox shows $T and user backspaces the T leaving a single $, the remaining $ will also be deleted


Pasting into the textbox examples:

  • $$-#AZY$B9=% would become $AB9
  • Z))%ABC$YD144%$ would become $ABCD14
  • $ZY%*#@! produces nothing in the textbox


Things I've tried so far:

  • Using -split '^\${1}' and -replace '[^A-V0-9]' to strip out everything out, then adding the $ back in
    • I sincerely apologize - I cannot remember what issues surfaced from this solution, but it didn't behave the way I expected in certain circumstances
  • Using a ForEach loop to inspect each character in the string and replace those that do not match [A-V0-9] then inserting a $ afterwards
    • This is the bloated solution I'm currently using
  • Using a masked textbox, but the mask string doesn't accept a range of characters
  • Setting $txtTextbox.MaxLength = 8
    • This prevents pasted text from being processed appropriately
  • Quite a bit of Googling and fiddling with regexes, using regex101.com, playing with lookarounds etc
  • Using a boolean flag to determine whether or not a single leading $ was manually typed/pasted in the first place so it doesn't get deleted as soon as it's typed (yet retain the ability to clear the textbox upon the last legal character being deleted whether using Backspace/Delete/CTRL+X to cut etc)
    • Finding an elegant solution to this paradox is evading me (allow $ to be typed but delete $ if it's the only remaining character in the textbox)

This is the mostly working solution I have thus far in the textbox's TextChanged event:

Clear-Host
Add-Type -AssemblyName System.Windows.Forms

$btnExit = New-Object System.Windows.Forms.Button -Property @{
    Location        = '50, 100'
    Text            = 'E&xit'
}

$frmTestForm = New-Object System.Windows.Forms.Form -Property @{
    CancelButton    = $btnExit
    FormBorderStyle = 'Fixed3D'
    MaximizeBox     = $false
}

$txtTextbox = New-Object System.Windows.Forms.TextBox -Property @{
    Location        = '50, 50'
    CharacterCasing = 'Upper'
}

$frmTestForm.Controls.Add($btnExit)
$frmTestForm.Controls.Add($txtTextbox)

$txtTextbox.Add_TextChanged({

    $TempString = $txtTextbox.Text
    foreach ($Character in $txtTextbox.Text.ToCharArray() )
    {
        # Working, but doesn't allow user to type a '$'
        if (! ([regex]::IsMatch($Character, '^[A-V0-9]$' ) ))
        {
            $TempString = $TempString.Replace([string]$Character, "")
        }
    }
    if ($TempString.Length -ne 0 -and $TempString[0] -ne '$')
    {
        # Add a leading '$' if there isn't one
        $txtTextbox.Text = '$' + $TempString.Substring(0, [System.Math]::Min(6, $TempString.Length))

    }
    elseif ($txtTextbox.Text -eq '$')
    {
        # Deletes the $ automatically once the last remaining (legal) character is deleted.
        # This also unfortunately prevents the user from manually typing a leading '$'
        $txtTextbox.Clear()
    }
    else
    {
        # Prevent leading illegal characters from showing up/producing '$' and nothing else
        $txtTextbox.Text = ''
    }
    
    $txtTextbox.Select($txtTextbox.Text.Length, 0)
})

$btnExit.Add_Click({
    $frmTestForm.Close()
})

$frmTestForm.Add_Shown({
    $txtTextbox.Select()
})

[System.Windows.Forms.Application]::Run($frmTestForm)

It doesn't allow the user to manually type in a $ as noted in the comments, but that might be something I'm willing to give up if allowing this would introduce a lot more bloat. At the end of the day, my question is simply:


Would a regex that's along the lines of:
$txtTextbox.Text = $txtTextbox.Text -replace 'everything that isn't a leading $ in addition to [^A-V0-9]{0,6}' be simpler/faster/more efficient compared to evaluating each character one at a time? What would that regex look like?

Thank you in advance for any insight or (hopefully constructive) criticism.

-Paul


Solution

  • Using your test strings , I came up with this:

    $txtTextbox.Add_TextChanged({
        $TempString = $this.Text
        if ($TempString -ne '$') { 
            $TempString = ($this.Text).TrimStart('$') -replace '[^A-V0-9]'
            if ($TempString.Length -gt 6) { $this.Text = '${0}' -f $TempString.Substring(0,6) }
            elseif ($TempString.Length) { $this.Text = '${0}' -f $TempString }
            else { $this.Clear() }
        }
        elseif ($this.Tag -eq 2) { 
            # user backspaced the last character before the '$'
            $this.Clear()
        } 
    
        $this.Tag = $this.Text.Length       # keep track of the current text length
        $this.Select($this.Text.Length, 0)  # set the cursor to the end
    })