สนุกสนานมีสีสันมากยิ่งขึ้นด้วย 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 ของคุณได้





