feat: 桌面拾取 v2——两阶段交互(悬停选目标/微调模式)、PageUp/PageDown 层级切换、高亮框跟随、UTF8 BOM
This commit is contained in:
parent
7920a61e12
commit
a4b7e24acd
@ -1,6 +1,6 @@
|
||||
param([string]$Mode = "element")
|
||||
$ErrorActionPreference = "Continue"
|
||||
Add-Type -AssemblyName UIAutomationClient, UIAutomationTypes
|
||||
Add-Type -AssemblyName UIAutomationClient, UIAutomationTypes, WindowsBase
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
Add-Type -MemberDefinition "[DllImport(`"user32.dll`")] public static extern short GetAsyncKeyState(int key); [DllImport(`"user32.dll`", CharSet = CharSet.Unicode)] public static extern int GetWindowTextW(IntPtr h, System.Text.StringBuilder sb, int max);" -Name PickW32 -Namespace RpaPick | Out-Null
|
||||
|
||||
@ -15,79 +15,112 @@ if ($Mode -eq "windows") {
|
||||
exit 0
|
||||
}
|
||||
|
||||
function Get-DeepestAtPoint([double]$px, [double]$py) {
|
||||
$point = New-Object System.Windows.Automation.Point($px, $py)
|
||||
$cur = [System.Windows.Automation.AutomationElement]::FromPoint($point)
|
||||
if (-not $cur) { return $null }
|
||||
for ($depth = 0; $depth -lt 30; $depth++) {
|
||||
$children = $cur.FindAll([System.Windows.Automation.TreeScope]::Children, [System.Windows.Automation.Condition]::TrueCondition)
|
||||
$next = $null
|
||||
$nextArea = [double]::MaxValue
|
||||
foreach ($c in $children) {
|
||||
try {
|
||||
$r = $c.Current.BoundingRectangle
|
||||
if ($r.Width -gt 0 -and $r.Height -gt 0 -and $px -ge $r.X -and $px -le ($r.X + $r.Width) -and $py -ge $r.Y -and $py -le ($r.Y + $r.Height)) {
|
||||
$area = $r.Width * $r.Height
|
||||
if ($area -lt $nextArea) { $next = $c; $nextArea = $area }
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
if (-not $next) { break }
|
||||
$cur = $next
|
||||
}
|
||||
return $cur
|
||||
}
|
||||
|
||||
# ---- 信息窗 ----
|
||||
$form = New-Object System.Windows.Forms.Form
|
||||
$form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::None
|
||||
$form.TopMost = $true
|
||||
$form.StartPosition = [System.Windows.Forms.FormStartPosition]::Manual
|
||||
$form.Location = New-Object System.Drawing.Point(20, 20)
|
||||
$form.Size = New-Object System.Drawing.Size(460, 130)
|
||||
$form.Size = New-Object System.Drawing.Size(480, 110)
|
||||
$form.BackColor = [System.Drawing.Color]::FromArgb(20, 60, 120)
|
||||
$label = New-Object System.Windows.Forms.Label
|
||||
$label.Dock = [System.Windows.Forms.DockStyle]::Fill
|
||||
$label.ForeColor = [System.Drawing.Color]::White
|
||||
$label.Font = New-Object System.Drawing.Font("Microsoft YaHei", 11)
|
||||
$label.Text = "移动鼠标选择元素..."
|
||||
$label.Font = New-Object System.Drawing.Font("Microsoft YaHei", 10)
|
||||
$form.Controls.Add($label)
|
||||
$form.Show()
|
||||
|
||||
$prevDown = $false
|
||||
$result = $null
|
||||
# ---- 高亮窗(半透明红块) ----
|
||||
$hl = New-Object System.Windows.Forms.Form
|
||||
$hl.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::None
|
||||
$hl.TopMost = $true
|
||||
$hl.ShowInTaskbar = $false
|
||||
$hl.StartPosition = [System.Windows.Forms.FormStartPosition]::Manual
|
||||
$hl.BackColor = [System.Drawing.Color]::Red
|
||||
$hl.Opacity = 0.35
|
||||
|
||||
# ---- 状态 ----
|
||||
$stage = "hover"
|
||||
$chain = New-Object System.Collections.ArrayList
|
||||
$selIdx = -1
|
||||
$prevPageUp = $false
|
||||
$prevPageDown = $false
|
||||
$prevSpace = $false
|
||||
|
||||
function Show-Element($el) {
|
||||
if ($null -eq $el) { $script:hl.Hide(); return }
|
||||
$r = $el.Current.BoundingRectangle
|
||||
if ($r.Width -le 0 -or $r.Height -le 0) { $script:hl.Hide(); return }
|
||||
$script:hl.Location = New-Object System.Drawing.Point([int]($r.X - 2), [int]($r.Y - 2))
|
||||
$script:hl.Size = New-Object System.Drawing.Size([int]($r.Width + 4), [int]($r.Height + 4))
|
||||
$script:hl.Show()
|
||||
}
|
||||
|
||||
$form.Show()
|
||||
$hl.Show()
|
||||
|
||||
while ($true) {
|
||||
$ptX = [System.Windows.Forms.Cursor]::Position.X
|
||||
$ptY = [System.Windows.Forms.Cursor]::Position.Y
|
||||
$el = Get-DeepestAtPoint $ptX $ptY
|
||||
$down = ([RpaPick.PickW32]::GetAsyncKeyState(32) -band 0x8000) -ne 0
|
||||
$space = ([RpaPick.PickW32]::GetAsyncKeyState(32) -band 0x8000) -ne 0
|
||||
$esc = ([RpaPick.PickW32]::GetAsyncKeyState(27) -band 0x8000) -ne 0
|
||||
$pgUp = ([RpaPick.PickW32]::GetAsyncKeyState(33) -band 0x8000) -ne 0
|
||||
$pgDn = ([RpaPick.PickW32]::GetAsyncKeyState(34) -band 0x8000) -ne 0
|
||||
|
||||
if ($esc) { break }
|
||||
if ($el) {
|
||||
$c = $el.Current
|
||||
$ctrlName = $c.ControlType.ProgrammaticName.Replace("ControlType.", "")
|
||||
$label.Text = "名称: " + $c.Name + [Environment]::NewLine + "类型: " + $ctrlName + [Environment]::NewLine + "空格=拾取 Esc=取消"
|
||||
}
|
||||
if ($down -and $el) {
|
||||
$c = $el.Current
|
||||
$rootName = ""
|
||||
$walker = [System.Windows.Automation.TreeWalker]::ControlViewWalker
|
||||
$anc = $walker.GetParent($el)
|
||||
while ($anc -and -not $anc.Equals([System.Windows.Automation.AutomationElement]::RootElement)) {
|
||||
$h = $anc.Current.NativeWindowHandle
|
||||
if ($h -ne 0) {
|
||||
$sb = New-Object System.Text.StringBuilder 512
|
||||
[RpaPick.PickW32]::GetWindowTextW([IntPtr]$h, $sb, 512) | Out-Null
|
||||
if ($sb.ToString()) { $rootName = $sb.ToString(); break }
|
||||
}
|
||||
$anc = $walker.GetParent($anc)
|
||||
|
||||
if ($stage -eq "hover") {
|
||||
$ptX = [System.Windows.Forms.Cursor]::Position.X
|
||||
$ptY = [System.Windows.Forms.Cursor]::Position.Y
|
||||
$point = New-Object System.Windows.Automation.Point($ptX, $ptY)
|
||||
$el = $null
|
||||
try { $el = [System.Windows.Automation.AutomationElement]::FromPoint($point) } catch {}
|
||||
if ($el) {
|
||||
$c = $el.Current
|
||||
$ctrlName = $c.ControlType.ProgrammaticName.Replace("ControlType.", "")
|
||||
$label.Text = "名称: " + $c.Name + [Environment]::NewLine + "类型: " + $ctrlName + [Environment]::NewLine + "空格=锁定 Esc=取消"
|
||||
Show-Element $el
|
||||
}
|
||||
if ($space -and -not $prevSpace -and $el) {
|
||||
# 进入微调:收集从根到命中的元素链
|
||||
$walker = [System.Windows.Automation.TreeWalker]::ControlViewWalker
|
||||
$chain.Clear()
|
||||
$cur = $el
|
||||
while ($cur -and -not $cur.Equals([System.Windows.Automation.AutomationElement]::RootElement)) {
|
||||
$cc = $cur.Current
|
||||
$rr = $cc.BoundingRectangle
|
||||
[void]$chain.Insert(0, @{ el = $cur; name = [string]$cc.Name; automationId = [string]$cc.AutomationId; controlType = $cc.ControlType.ProgrammaticName.Replace("ControlType.", ""); x = [int]$rr.X; y = [int]$rr.Y; w = [int]$rr.Width; h = [int]$rr.Height })
|
||||
$cur = $walker.GetParent($cur)
|
||||
}
|
||||
$selIdx = $chain.Count - 1
|
||||
$stage = "adjust"
|
||||
$label.Text = "微调模式:PageUp/PageDown 切换层级,空格确认,Esc 取消"
|
||||
}
|
||||
$ctrlName = $c.ControlType.ProgrammaticName.Replace("ControlType.", "")
|
||||
$result = @{ window = $rootName; name = $c.Name; automationId = $c.AutomationId; controlType = $ctrlName; className = $c.ClassName }
|
||||
break
|
||||
}
|
||||
$prevDown = $down
|
||||
Start-Sleep -Milliseconds 80
|
||||
|
||||
if ($stage -eq "adjust") {
|
||||
if ($pgUp -and -not $prevPageUp -and $selIdx -gt 0) { $selIdx -= 1 }
|
||||
if ($pgDn -and -not $prevPageDown -and $selIdx -lt ($chain.Count - 1)) { $selIdx += 1 }
|
||||
$item = $chain[$selIdx]
|
||||
if ($item) {
|
||||
$label.Text = "层级 " + ($selIdx + 1) + "/" + $chain.Count + " 名称: " + $item.name + [Environment]::NewLine + "类型: " + $item.controlType + [Environment]::NewLine + "PageUp/PageDown=切层级 空格=确认 Esc=取消"
|
||||
$hl.Location = New-Object System.Drawing.Point($item.x - 2, $item.y - 2)
|
||||
$hl.Size = New-Object System.Drawing.Size($item.w + 4, $item.h + 4)
|
||||
$hl.Show()
|
||||
}
|
||||
if ($space -and -not $prevSpace) {
|
||||
$item = $chain[$selIdx]
|
||||
# 窗口名 = 链中第一个带窗口句柄的层
|
||||
$rootName = ""
|
||||
foreach ($it in $chain) { if ($it.name) { $rootName = $it.name; break } }
|
||||
$result = @{ selected = $selIdx; window = $rootName; name = $item.name; automationId = $item.automationId; controlType = $item.controlType; x = $item.x; y = $item.y; w = $item.w; h = $item.h }
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
$prevPageUp = $pgUp
|
||||
$prevPageDown = $pgDn
|
||||
$prevSpace = $space
|
||||
Start-Sleep -Milliseconds 60
|
||||
}
|
||||
$form.Close()
|
||||
$hl.Close()
|
||||
|
||||
if ($result) { Write-Output (ConvertTo-Json $result -Compress) } else { Write-Output (ConvertTo-Json @{ cancelled = $true } -Compress) }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user