从WinForms将Powershell-Console带到前面

时间:2018-11-03 04:19:05

标签: windows forms powershell user-interface console

我试图将我的Powershell控制台放在最前面,即使已最小化。 我发现以下代码:

function Show-Process($Process, [Switch]$Maximize)
{
  $sig = '
    [DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
    [DllImport("user32.dll")] public static extern int SetForegroundWindow(IntPtr hwnd);
  '

  if ($Maximize) { $Mode = 3 } else { $Mode = 4 }
  $type = Add-Type -MemberDefinition $sig -Name WindowAPI -PassThru
  $hwnd = $process.MainWindowHandle
  $null = $type::ShowWindowAsync($hwnd, $Mode)
  $null = $type::SetForegroundWindow($hwnd) 
}
Show-Process -Process (Get-Process -Id $pid) 

它工作正常,但是当我从Button Click事件中调用该函数时,控制台不会显示。 问题是什么?使用WinForms GUI时,是否可以将Powershell控制台置于最前面?

这是示例GUI代码:

function Show-Process($Process, [Switch]$Maximize)
{
  $sig = '
    [DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
    [DllImport("user32.dll")] public static extern int SetForegroundWindow(IntPtr hwnd);
  '

  if ($Maximize) { $Mode = 3 } else { $Mode = 4 }
  $type = Add-Type -MemberDefinition $sig -Name WindowAPI -PassThru
  $hwnd = $process.MainWindowHandle
  $null = $type::ShowWindowAsync($hwnd, $Mode)
  $null = $type::SetForegroundWindow($hwnd) 
}

Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()

$Form                            = New-Object system.Windows.Forms.Form
$Form.ClientSize                 = '446,266'
$Form.text                       = "Form"
$Form.TopMost                    = $false

$Button1                         = New-Object system.Windows.Forms.Button
$Button1.text                    = "button"
$Button1.width                   = 60
$Button1.height                  = 30
$Button1.location                = New-Object System.Drawing.Point(75,29)
$Button1.Font                    = 'Microsoft Sans Serif,10'

$Button1.Add_Click({
    Show-Process -Process (Get-Process -Id $pid) 
})

$Form.controls.AddRange(@($Button1))

[void]$Form.ShowDialog()

3 个答案:

答案 0 :(得分:3)

由于@iRon的回答,我得以弄清楚我想要的方式。 对于任何好奇的人来说,问题是,只要不调用ShowDialog,就只能获得控制台MainwindowHandle。 因此,我将控制台句柄保存在一个变量中,并使用Form_Shown事件获取Form WindowHandle,因为Form_Load仍返回控制台句柄。

try: ... except AttributeError:

现在,如果我按下按钮,控制台会在前面弹出。 当用户在控制台中输入内容后,该表单再次出现在前面。

答案 1 :(得分:2)

不幸的是,我无法完全解决它,但也许其他人可能会根据我的发现进一步为您提供帮助:

首先,按钮单击事件中的进程与父PowerShell主机运行所在的进程空间不同。可以很容易地证明这一点,但可以在{中显示$hwhdWrite-Host $hwnd {1}}函数,并在调用Show-Process之前先调用Show-Process函数:

ShowDialog

换句话说:要修复此部分,您需要首先从PowerShell窗口捕获父Show-Process -Process (Get-Process -Id $pid) [void]$Form.ShowDialog()

$Pid

上面的代码段有效,但是一旦删除(或注释掉)$Button1.Add_Click({ Show-Process -Process $MyProcess }) $Form.controls.AddRange(@($Button1)) $MyProcess = Get-Process -Id $pid Show-Process -Process $MyProcess [void]$Form.ShowDialog() 行(在主机级别),它就会再次中断...

答案 2 :(得分:2)

您已经发现.MainWindowHandle不是 static 属性(来自链接的文档;已添加重点):

  

主窗口是由当前具有焦点的过程打开的窗口 [...]

因此,当显示表单时,当前进程的.MainWindowHandle属性的值从控制台窗口手柄更改为 WinForms窗口 [1]

在显示表单之前,先缓存控制台窗口句柄 绝对是一个选择,但是有一种更简单的方法,因为您已经在WinAPI P / Invoke声明中使用了Add-MemberGetConsoleWindow() WinAPI函数始终返回当前进程的控制台窗口句柄。

此外,您的$Forms表单实例具有.Handle属性,该属性直接返回表单的窗口句柄-无需进行(Get-Process -Id $pid).MainWindowHandle调用。

因此,以下解决方案不需要全局或脚本级变量,并且将查询窗口句柄的范围限制为按钮单击事件处理程序:

# P/Invoke signatures - note the addition of GetConsoleWindow():
$sig = '
[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")] public static extern int SetForegroundWindow(IntPtr hwnd);
[DllImport("kernel32.dll")] public static extern IntPtr GetConsoleWindow();'

$type = Add-Type -MemberDefinition $sig -Name WindowAPI -PassThru

Add-Type -AssemblyName System.Windows.Forms

$Form = New-Object system.Windows.Forms.Form -Property @{
  ClientSize = '446,266'
  text = "Form"
}

$Button1 = New-Object system.Windows.Forms.Button -Property @{
  text = "Test"
  location = New-Object System.Drawing.Point(75, 29)
}

$Button1.Add_Click({
    # Get this form's window handle.
    $handleForm = $Form.Handle # More generically: $this.FindForm().Handle
    # Get the console window's handle.
    $handleConsole = $type::GetConsoleWindow()
    # Activate the console window and prompt the user.
    $null = $type::ShowWindowAsync($handleConsole, 4); $null = $type::SetForegroundWindow($handleConsole)
    Read-Host -Prompt "Please Enter a Value"
    # Reactivate this form.
    $null = $type::ShowWindowAsync($handleForm, 4); $null = $type::SetForegroundWindow($handleForm)
  })

$Form.controls.AddRange(@($Button1))

$null = $Form.ShowDialog()

[1]请注意,已缓存进程对象不会动态更新其.MainWindowHandle值;您必须手动致电.Refresh()。  因为iRon's solution在显示表单之前 缓存了当前进程对象,所以它仍然碰巧反映了按钮单击处理程序中的控制台窗口句柄。