ค้นหาเว็บไซต์

สนุกสนานมีสีสันมากยิ่งขึ้นด้วย PowerShell และ WPF


มาดูวิธีใช้ PowerShell และแบบฟอร์ม Windows Presentation Foundation (WPF) เพื่อแสดงค่า [System. Drawing.Color] ต่อไป บทความนี้ต่อยอดจากโพสต์ก่อนหน้านี้ ดังนั้นหากคุณพลาด โปรดสละเวลาสักครู่เพื่อติดตามให้ทัน อย่างที่ฉันทำก่อนหน้านี้ ก่อนที่จะรันโค้ด WPF ใดๆ ใน PowerShell คุณควรโหลดแอสเซมบลีประเภทที่ต้องการ

Add-Type -AssemblyName PresentationFramework

ฉันควรชี้ให้เห็นว่านี่ไม่ใช่ข้อกำหนดที่แน่นอน เนื่องจากบางโฮสต์เช่น PowerShell ISE จะเรียกใช้โค้ด WPF โดยไม่มีมัน แต่คุณไม่มีทางรู้ว่าโค้ดของคุณจะถูกดำเนินการที่ไหน ดังนั้นการรันคำสั่ง Add-Type จึงไม่เสียหาย

จอแสดงผลหลายสี

ฉันจะสร้างโค้ดก่อนหน้านี้ต่อไป ฉันชอบทำงานไปเรื่อยๆ ช่วยให้ฉันสามารถระบุอุปสรรคที่อาจเกิดขึ้นและเทคนิคการทดสอบได้ รหัสชั่วคราวของฉันควรอนุญาตให้ฉันแสดงหลายสีได้ เนื่องจากนี่คือฟังก์ชันการพัฒนา ฉันจะจำกัดจำนวนสีที่จะทดสอบไว้ที่ 10 ฉันสามารถใช้การทดสอบ ValidateCount() สำหรับค่าพารามิเตอร์ได้

Param(
    [Parameter(
        Position = 0,
        Mandatory,
        HelpMessage = "Enter between 1 and 10 System.Drawing.Color names"
    )]
    [ValidateCount(1, 10)]
    [ValidatePattern("^\w+$")]
    [string[]]$Color
)

ฉันยังเพิ่มการทดสอบรูปแบบ regex เพื่อให้แน่ใจว่าค่าสีเป็นสตริงธรรมดาที่ไม่มีช่องว่าง ใช่ ฉันรู้ว่าฉันสามารถปรับปรุงรูปแบบนี้ได้ แต่จะเพียงพอสำหรับความต้องการในการพิสูจน์แนวคิดของฉัน

ไม่มีการเปลี่ยนแปลงกับโค้ดเพื่อสร้างแบบฟอร์ม WPF สำหรับการทดสอบนี้ ฉันจะใช้ StackPanel และเพิ่มแต่ละสีลงในสแต็ก

foreach ($item in $color) {
    $sdc = [System.Drawing.Color]::FromName($item)
    #Validate the color
    if ($sdc.IsKnownColor) {
        # <create a textbox for each color>
        # <convert color to html value>
        # <add the textbox to the stack>
    }
    else {
        Write-Warning "Cannot find $item as a System.Drawing.Color"
    }
}

ขณะที่ฉันกำลังทำสิ่งนี้ ฉันพบว่าฉันต้องรับมือกับสถานการณ์ที่ฉันป้อนสีไม่ถูกต้อง เมธอด FromName() จะทำงานเสมอ แม้ว่าจะมีค่าเช่น 'foo' ก็ตาม แต่ในกรณีนั้น ค่า IsKnownColor จะเป็นเท็จ นี่คือฟังก์ชันตัวอย่างที่สมบูรณ์

Function Show-SelectedColor {
# sample usage: Show-SelectedColor green,chartreuse,yellow,violet,darkorange
    [CmdletBinding()]
    Param(
        [Parameter(
            Position = 0,
            Mandatory,
            HelpMessage = "Enter between 1 and 10 System.Drawing.Color names"
        )]
        [ValidateCount(1, 10)]
        [ValidatePattern("^\w+$")]
        [string[]]$Color
    )

    $form = New-Object System.Windows.Window
    $form.Title = "Color Sample"
    $form.Height = 100
    $form.Width = 300
    $form.top = 200
    $form.left = 100
    $form.UseLayoutRounding = $True
    $form.WindowStartupLocation = "CenterScreen"
    $form.SizeToContent = "Height"

    $stack = New-Object System.Windows.Controls.StackPanel

    foreach ($item in $color) {
        $sdc = [System.Drawing.Color]::FromName($item)
        #Validate the color
        if ($sdc.IsKnownColor) {
            #create a textbox for each color
            $TextBox = New-Object System.Windows.Controls.TextBox
            $TextBox.Width = 275
            $textbox.Height = 30
            $TextBox.HorizontalAlignment = "Center"
            $textbox.TextAlignment = "center"
            $textbox.VerticalContentAlignment = "center"
            $textbox.FontSize = 16
            #The drawing color may need to be converted to a brush color
            #convert color to html value
            $htmlcode = "#{0:X2}{1:X}{2:X2}" -f $sdc.r, $sdc.g, $sdc.b
            $textbox.background = [System.Windows.Media.BrushConverter]::new().ConvertFromString($htmlcode)
            $TextBox.Text = "$($sdc.name) ($($sdc.R),$($sdc.G),$($sdc.B))"
            #add the textbox to the stack
            $stack.AddChild($TextBox)
        }
        else {
            Write-Warning "Cannot find $item as a System.Drawing.Color"
        }
    }
    $form.AddChild($stack)
    [void]$form.ShowDialog()

}

ตอนนี้ฉันมีโครงสร้างทั่วไปสำหรับโปรเจ็กต์สุดท้ายของฉันแล้ว

การเพิ่มตาราง WPF

แม้ว่าการใช้ StackPanel จะเป็นเรื่องง่าย แต่ฉันรู้ว่าฉันไม่ต้องการมีสแต็ก 134 เลเยอร์ ฉันจินตนาการว่ามีแถวละ 3 คอลัมน์หลายแถว แต่ฉันอยากให้สิ่งนี้เป็นแบบไดนามิกด้วย ฉันต้องการให้แบบฟอร์มปรับตามจำนวนสีที่ระบุโดยอัตโนมัติ ฉันตั้งใจให้ฟังก์ชันยอมรับไวด์การ์ดและรูปแบบ regex ด้วย เพราะเหตุใดจะไม่ได้ล่ะ!

ตอนนี้สิ่งต่าง ๆ เริ่มยุ่งยาก สำหรับเลย์เอาต์ประเภทนี้ ฉันต้องใช้ตัวควบคุมกริดแทน StackPanel

$grid = New-Object system.windows.Controls.Grid

ภายในตาราง ฉันต้องกำหนดแถวและคอลัมน์ ฉันสามารถคำนวณจำนวนแถวสูงสุดที่ต้องการได้โดยการหารจำนวนสีด้วย 3 อย่างไรก็ตาม ฉันต้องพิจารณากรณีขอบที่ฉันอาจมี 2 หรือ 4 สี

switch ($data.count ) {
    { $_ -eq 0 } { Write-Warning "No color values found." ; Return }
    { $_ -le 2 } { $maxRows = 1 ; Break }
    { $_ -le 5 } { $maxRows = 2 ; Break }
    Default { $maxRows = $data.count / 3 -as [int] }
}

ด้วยข้อมูลนี้ ฉันสามารถสร้างแถวและคอลัมน์และเพิ่มลงในตารางได้

for ($i = 0; $i -lt $maxRows; $i++) {
    $row = New-Object System.Windows.Controls.RowDefinition
    $row.Height = 30
    $grid.RowDefinitions.add($row)
}
  
for ($i = 0; $i -lt 3; $i++) {
    $col = New-Object System.Windows.Controls.ColumnDefinition
    $col.Width = 300
    $grid.ColumnDefinitions.Add($col)
}

ฉันสามารถใช้การแจงนับ ForEach ประเภทเดียวกันเพื่อสร้างกล่องข้อความสำหรับแต่ละสีได้ แต่ตอนนี้ฉันเจออุปสรรค ฉันต้องวางตำแหน่งกล่องข้อความแต่ละกล่องในชุดแถวและคอลัมน์ เมื่อใช้ XAML รูปแบบจะได้รับการจัดการเช่นนี้

<TextBox x:Name="textComputername" Grid.Column="1" HorizontalAlignment="Left" Height="20"
Margin="20,3,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="170"  ToolTip="Enter a
computername or a comma separated list" TabIndex="0"/>

ในตัวอย่าง XAML นี้จากโปรเจ็กต์อื่น กล่องข้อความจะอยู่ในคอลัมน์ตารางที่ 1 แต่ฉันไม่ได้ใช้ XAML

ฉันรู้ว่าฉันจะต้องติดตามตำแหน่งของตัวเอง ดังนั้นฉันจะเริ่มต้นตัวนับสำหรับแถวและคอลัมน์

$r = 0 #row index
$c = 0 #column index

น่าเสียดายที่มันไม่ง่ายอย่างที่ $textbox.grid.column.row=$r แต่สิ่งนี้อาศัยโครงสร้าง WPF ที่เรียกว่า DependencyProperty แทน ตัวควบคุม WPF มีเมธอด SetValue() ที่ทำให้งานนี้สำเร็จ

ต้องใช้เวลาพอสมควรในการค้นคว้า การทุบหัว และคำสาปเล็กน้อยเพื่อทำให้ฉันปวดหัว ในที่สุดฉันก็พบโค้ดบางส่วนจากโมดูล ShowUI ซึ่งช่วยให้ฉันเข้าใจได้ คุณสมบัติการพึ่งพาจะเป็น $grid.row และ $grid.column แม้ว่าฉันจะไม่ได้อ้างอิงตัวแปรของฉัน เช่น $grid แต่ฉันใช้การอ้างอิงถึงคลาส Grid และคุณสมบัติที่ต้องการแทน

ขณะที่ฉันสร้างกล่องข้อความแต่ละกล่อง ฉันสามารถ "วาง" กล่องข้อความนั้นในแถวและคอลัมน์ที่ถูกต้องได้

$textbox.SetValue([system.windows.controls.grid]::rowproperty, $r)
$textbox.SetValue([system.windows.controls.grid]::columnproperty, $c)

การจัดการแถวและคอลัมน์ส่วนสุดท้ายคือการติดตามและย้ายไปยังแถวถัดไปหลังจากสามคอลัมน์

$c++
$grid.AddChild($TextBox)
if ($c -eq 3) {
    $c = 0
    $r++
}

การเลื่อน

ต่อไป ฉันต้องพิจารณาว่าจะแสดงแบบฟอร์มอย่างไร ฉันไม่ได้ต้องการฟอร์มที่ใหญ่โต หลังจากการทดสอบ ฉันพบว่าหากมีแถวน้อยกว่า 15 แถว ฉันสามารถกำหนดขนาดฟอร์มอัตโนมัติได้

if ($maxRows -le 15) {
    $form.SizeToContent = "Height"
}

ไม่เช่นนั้น ฉันต้องการขนาดหน้าต่างที่กำหนดและความสามารถในการเลื่อนเนื้อหา แต่นี่ไม่ง่ายเหมือนกับการเปิดใช้งานแถบเลื่อนบนแบบฟอร์ม คิดว่า WPF เป็นชุดตุ๊กตาทำรังของรัสเซีย มีการควบคุมการเลื่อนแยกต่างหาก

$scroll = New-Object System.Windows.Controls.ScrollViewer
$scroll.VerticalScrollBarVisibility = "Visible"
$scroll.HorizontalScrollBarVisibility = "Auto"

เมื่อตารางเสร็จสมบูรณ์ ฉันสามารถเพิ่มมันลงใน ScrollViwer แล้วเพิ่ม ScrollViewer ลงในแบบฟอร์มได้

$scroll.AddChild($grid)
$form.AddChild($scroll)

การประมวลผลชื่อ

ส่วนสุดท้ายของโค้ดของฉันเกี่ยวข้องกับชื่อสี ฉันต้องการให้พฤติกรรมเริ่มต้นแสดงค่า [System. Drawing.Color] ทั้งหมด แต่ฉันยังต้องการระบุสีแต่ละสีตามชื่อ รวมถึงไวด์การ์ด เช่น "สีฟ้า", "สีเข้ม*", "สีม่วง" แล้วฉันก็รู้ว่าเหตุใดจึงจำกัดตัวเองให้อยู่แค่ไวด์การ์ดธรรมดา? ทำไมไม่รองรับรูปแบบ regex แบบเต็ม เช่น “(สีเขียว)|(สีน้ำเงิน)”

พารามิเตอร์ชื่อฟังก์ชันถูกกำหนดให้ยอมรับอาร์เรย์ของสตริงที่สามารถรวมอักขระตัวแทนได้ ฉันยังตัดสินใจเพิ่มพารามิเตอร์ตัวระบุที่เรียกว่า "Regex" ซึ่งจะระบุถึงการใช้ค่าพารามิเตอร์ Name เป็นรูปแบบนิพจน์ทั่วไป

Param(
    [Parameter(
        Position = 0,
        ValueFromPipelineByPropertyName,
        HelpMessage = "Specify a System.Drawing.Color name. Wildcards are permitted."
    )]
    [ValidateNotNullOrEmpty()]
    [string[]]$Name,
    [Parameter(HelpMessage = "Treat the Name paramter value as a regex pattern")]
    [switch]$Regex
)

พารามิเตอร์ Name สามารถรับอินพุตไปป์ไลน์ตามชื่อคุณสมบัติได้ ฉันคิดว่าฉันอาจต้องการส่งผลลัพธ์จากฟังก์ชัน Get- DrawingColor ของฉันไปที่อันนี้ ซึ่งหมายความว่าฟังก์ชันของฉันจะมีบล็อกสคริปต์เริ่มต้น ประมวลผล และสิ้นสุด

ใน Begin scriptblock ฉันจะกำหนดรายการทั่วไปที่จะได้รับการอัปเดตในบล็อกกระบวนการ

$data = [System.Collections.Generic.list[string]]::New()

ในบล็อกกระบวนการ หากมีค่าสำหรับพารามิเตอร์ Name และการใช้งานที่ระบุ -Regex PowerShell จะค้นหาผ่านรายการค่า [System. Drawing.Color] ที่สร้างขึ้นในบล็อก Begin เพื่อหาค่าที่ตรงกัน หากระบุชื่อโดยไม่มี -Regex ฉันจะค้นหารายการเดียวกันโดยใช้ตัวดำเนินการ -Like ฉันสามารถรวมขั้นตอนเหล่านี้ได้ แต่นั่นจะเพิ่มความซับซ้อนให้กับโค้ด หากผู้ใช้ไม่ระบุค่าชื่อใดๆ ฉันจะใช้รายการสีทั้งหมดที่สร้างไว้ก่อนหน้านี้

บล็อกกระบวนการไม่ได้ทำอะไรเลยนอกจากสร้างรายการ ฉันจะใส่โค้ดเพื่อสร้างกล่องข้อความใน End scriptblock และแสดงแบบฟอร์ม

แสดง ColorPalette

นี่คือฟังก์ชัน PowerShell สุดท้าย ฉันใช้ Write-Verbose เพื่อให้ข้อเสนอแนะในแต่ละขั้นตอนของกระบวนการ

Function Show-ColorPalette {
    [CmdletBinding()]
    Param(
        [Parameter(
            Position = 0,
            ValueFromPipelineByPropertyName,
            HelpMessage = "Specify a System.Drawing.Color name. Wildcards are permitted."
        )]
        [ValidateNotNullOrEmpty()]
        [string[]]$Name,
        [Parameter(HelpMessage = "Treat the Name paramter value as a regex pattern")]
        [switch]$Regex
    )

    Begin {
        Write-Verbose "Starting $($MyInvocation.MyCommand)"
        #get all valid color names
        $all = ([system.drawing.color].GetProperties().name).Where({ $_ -notmatch "^\bIs|Name|[RGBA]\b" })
        if ($Regex) {
            Write-Verbose "Using Regex name matching"
        }
        Write-Verbose "Creating Form"

        $form = New-Object System.Windows.Window
        $form.Title = "Color Samples"
        $form.Height = 500
        $form.Width = 940
        $form.top = 200
        $form.left = 100
        $form.UseLayoutRounding = $True
        $form.WindowStartupLocation = "CenterScreen"

        Write-Verbose "Creating Scrollviewer"
        $scroll = New-Object System.Windows.Controls.ScrollViewer
        $scroll.VerticalScrollBarVisibility = "Visible"
        $scroll.HorizontalScrollBarVisibility = "Auto"

        Write-Verbose "Creating Grid"
        $grid = New-Object system.windows.Controls.Grid
        #enable grid lines for testing and troubleshooting
        $grid.ShowGridLines = $False

        #initialize a collection to hold color names from pipeline
        #or parameter value
        $data = [System.Collections.Generic.list[string]]::New()

    } #Begin
    Process {
        Write-Verbose "Using these PSBoundparameters"
        Write-Verbose ($PSBoundParameters | Out-String)
        if ($PSBoundParameters.ContainsKey("Name")) {
            $pattern = $PSBoundParameters["Name"]
            if ($Regex) {
                Write-Verbose "Using Regex name matching"
                #search the list of all color names defined in the Begin
                #block for regex matches
                $all.Where({ $_ -match $pattern }) | ForEach-Object {
                    $data.Add($_)
                }
            }
            Else {
                $Name | ForEach-Object {
                    $find = $_
                    #Search the list of all color names for that are 
                    #like each name. This supports wildcard searches
                    $found = $all.where({ $_ -like "$find" })
                    if ($found) {
                        #use the value
                        $found | Foreach-Object {$data.Add($_)}
                    }
                    else {
                        Write-Warning "Failed to find any colors that match $find."
                    }
                }
            }
        }
        else {
            #-Name was not used so use all color names
            Write-Verbose "Using all defined color names"
            $all.ForEach({ $data.Add($_) })
        }
    } #process
    End {
        Write-Verbose "Displaying $($data.count) items"
        switch ($data.count ) {
            { $_ -eq 0 } { Write-Warning "No color values found." ; Return }
            { $_ -le 2 } { $maxRows = 1 ; Break }
            { $_ -le 5 } { $maxRows = 2 ; Break }
            Default { $maxRows = $data.count / 3 -as [int] }
        }
        Write-Verbose "Adding $maxRows rows"
        for ($i = 0; $i -lt $maxRows; $i++) {
            $row = New-Object System.Windows.Controls.RowDefinition
            $row.Height = 30
            $grid.RowDefinitions.add($row)
        }
        Write-Verbose "Adding columns"
        for ($i = 0; $i -lt 3; $i++) {
            $col = New-Object System.Windows.Controls.ColumnDefinition
            $col.Width = 300
            $grid.ColumnDefinitions.Add($col)
        }

        $r = 0 #row index
        $c = 0 #column index
        foreach ($item in $data) {
            $sdc = [System.Drawing.Color]::FromName($item)
            if ($sdc.IsKnownColor) {
                $TextBox = New-Object System.Windows.Controls.TextBox
                $TextBox.Width = 275
                $textbox.Height = 30
                $TextBox.HorizontalAlignment = "Center"
                $textbox.TextAlignment = "center"
                $textbox.VerticalContentAlignment = "center"
                $textbox.FontSize = 16
                #The drawing color may need to be converted to a brush color
                #convert color to html value
                $htmlcode = "#{0:X2}{1:X2}{2:X2}" -f $sdc.r, $sdc.g, $sdc.b
                Write-Verbose "Adding $item [$htmlcode]"
                $textbox.background = [System.Windows.Media.BrushConverter]::new().ConvertFromString($htmlcode)
                $TextBox.Text = "$item ($($sdc.R),$($sdc.G),$($sdc.B))"
                $textbox.SetValue([system.windows.controls.grid]::rowproperty, $r)
                $textbox.SetValue([system.windows.controls.grid]::columnproperty, $c)
                #move to the next column
                $c++
                $grid.AddChild($TextBox)
                if ($c -eq 3) {
                    $c = 0
                    $r++
                }
            }
            else {
                #this will probably never be called
                Write-Warning "Can't validate $item as a [System.Drawing.Color] value."
            }

        } #foreach item in data
        Write-Verbose "Adding child elements to the form"
        $scroll.AddChild($grid)
        $form.AddChild($scroll)
        #set the form to size to content is less than 15 rows
        if ($maxRows -le 15) {
            $form.SizeToContent = "Height"
        }
        Write-Verbose "Displaying the form"
        #the prompt will be blocked until the form is closed
        [void]$form.ShowDialog()
        Write-Verbose "Ending $($MyInvocation.MyCommand)"

    } #end
}

ฉันสามารถใช้ฟังก์ชันนี้เพื่อแสดงสีได้หลายสี รวมถึงการใช้ไวด์การ์ดด้วย

Show-ColorPalette -Name "*green","violet","orangered","skyblue"

ด้วยการแสดงออกปกติ

Show-ColorPalette -Name "(blue)|(green)" -Regex

หรือทุกอย่างโดยค่าเริ่มต้น

สรุป

ฉันไม่ได้คาดหวังให้คุณมีความต้องการทางธุรกิจที่น่าสนใจสำหรับฟังก์ชันเหล่านี้ แต่นั่นไม่ใช่ประเด็น ฉันหวังว่าคุณจะพบสิ่งที่มีค่าที่คุณสามารถใช้ในการเขียนสคริปต์ PowerShell ของคุณได้

บทความที่เกี่ยวข้อง