史蒂夫·李的有用回答提供关键指针;让我补充一下背景信息:
PowerShell 提供两种基本的事件订阅机制:
方法(b)的回调方法只能及时起作用,而电源外壳控制前台线程,即not这里的情况,因为[Terminal.Gui.Application]::Run()
call blocks它。
因此,必须使用方法(a)。
Re (a):
C# offers 句法糖以运算符的形式+=
and -=
用于附加和分离事件处理程序委托, which look like 作业,但实际上被翻译成add_<Event>()
and remove_<Event>()
方法调用.
您可以使用以下命令查看这些方法名称:[powerShell]键入作为示例:
PS> [powershell].GetEvents() | select Name, *Method, EventHandlerType
Name : InvocationStateChanged
AddMethod : Void add_InvocationStateChanged(System.EventHandler`1[System.Management.Automation.PSInvocationStateChangedEventArgs])
RemoveMethod : Void remove_InvocationStateChanged(System.EventHandler`1[System.Management.Automation.PSInvocationStateChangedEventArgs])
RaiseMethod :
EventHandlerType : System.EventHandler`1[System.Management.Automation.PSInvocationStateChangedEventArgs]
PowerShell 提供no这种用于附加/删除事件处理程序的语法糖,因此必须直接调用这些方法。
不幸的是,两者都没有Get-Member制表符补全都不知道这些方法,而相反,原始事件names令人困惑地do即使您无法直接对它们进行操作,也可以通过制表符完成。
Github 建议 #12926旨在解决这两个问题。
用于事件定义的约定:
The EventHandlerType
上面的属性显示了事件处理程序委托的类型名称,在本例中正确地遵循习俗使用基于泛型类型的委托System.EventHandler<TEventArgs>,其签名为:
public delegate void EventHandler<TEventArgs>(object? sender, TEventArgs e);
TEventArgs
表示包含的实例的类型特定事件的信息。
其他习俗是这样的事件参数类型派生自System.EventArgs类,手头的类型,PSInvocationStateChangedEventArgs, is.
提供的活动no特定事件的信息按照惯例使用非通用的System.EventHandler代表:
public delegate void EventHandler(object? sender, EventArgs e);
据推测,因为该代表历史上用于all代表们,甚至对于那些with事件参数 - 之前generics.NET 2 中出现了 -EventArgs
参数仍然存在,约定是传递EventArgs.Empty
而不是null
表示没有参数。
同样,长期建立的框架类型定义了非通用的custom具有特定事件参数类型的代表,例如System.Windows.Forms.KeyPressEventHandler.
这些约定都不是enforced由CLR然而,正如所讨论的事件被定义为所证明的那样public event Action Clicked;
,它使用无参数委托作为事件处理程序。
通常建议遵守约定以免违背用户的期望,尽管这样做有时不太方便。
PowerShell 在使用脚本块时非常灵活({ ... }
)作为代表,尤其是not强制执行特定的参数签名 via param(...)
:
无论脚本块是否声明任何、太多或太少的参数,脚本块都会被接受,尽管那些绑定到脚本块参数的事件发起对象实际传递的参数必须是类型兼容的(假设脚本块的参数是显式键入的)。
因此,史蒂夫的代码:
$btn.Add_Clicked({
param($sender, $e)
[Terminal.Gui.Application]::RequestStop()
})
尽管有无用的参数声明,但仍然有效,因为没有任何参数被传递到脚本块,因为System.Action委托类型是无参数.
以下内容就足够了:
$btn.Add_Clicked({
[Terminal.Gui.Application]::RequestStop()
})
注意:即使没有声明参数,您也可以通过以下方式引用事件发送者(触发事件的对象):自动的$this多变的(在这种情况下,与$btn
).
简化的示例代码:
-
打电话很重要[Terminal.Gui.Application]::Shutdown()
为了退出应用程序后将终端返回到可用状态
-
至少其中之一Terminal.Gui
类型不适合 PowerShell:
- What are conceptually text properties aren't implemented as type
[string]
, but as [NStack.ustring]
; while you can use [string]
instances transparently to assign to such properties, displaying them again performs enumeration and renders the code points of the underlying characters individually.
- 解决方法:打电话
.ToString()
; e.g. $btn.Text.ToString()
-
从 PowerShell 7.3.2 开始,没有与 NuGet 包直接集成,因此将已安装包的程序集加载到 PowerShell 会话中非常麻烦 - 请参阅这个答案, which 展示了如何使用.NET核心SDK下载包并使其依赖项可用.
-
In PowerShell(核心)7.2+,问题可以解决在这种情况下: The Microsoft.PowerShell.ConsoleGuiTools module 附带 Terminal.Gui.dll
,这样您就可以安装该模块,并在那里引用 DLL。
-
注意Add-Type -AssemblyName
仅适用于位于current目录(而不是script's目录)或随 PowerShell 本身一起提供(PowerShell [Core] v6+)/位于 GAC (Windows PowerShell) 中。
-
鉴于目前从 PowerShell 使用 NuGet 包是多么麻烦,GitHub 功能建议 #6724要求Add-Type
进行增强以直接支持 NuGet 包。
using namespace Terminal.Gui
# Load the Terminal.Gui.dll assembly.
if ($PSVersionTable.PSVersion -ge '7.2') {
# Load the Terminal.Gui assembly via the 'Microsoft.PowerShell.ConsoleGuiTools'
# module, by installing that module on demand.
if (-not (Get-Module -ListAvailable Microsoft.PowerShell.ConsoleGuiTools)) {
Write-Verbose -Verbose "Installing module Microsoft.PowerShell.ConsoleGuiTools on demand, in the current user's scope."
Install-Module -Scope CurrentUser -ErrorAction Stop Microsoft.PowerShell.ConsoleGuiTools
}
# Terminal.Gui.dll is inside the module's folder.
try { Add-Type -LiteralPath (Join-Path (Get-Module -ListAvailable Microsoft.PowerShell.ConsoleGuiTools).ModuleBase Terminal.Gui.dll) } catch { throw }
}
else {
# Windows PowerShell (or earlier PS Core versions)
# Unfortunately, there's no easy way to gain access to Terminal.Gui.dll, and the
# best option is to use an aux. NET SDK project as shown in https://stackoverflow.com/a/50004706/45375
# The next command assumes that the steps there have been followed.
try { Add-Type -Path C:\Users\jdoe\.nuget-pwsh\packages-winps\terminal.gui\*\Terminal.Gui.dll } catch { throw }
}
# Initialize the "GUI".
# Note: This must come before creating windows and controls.
[Application]::Init()
$win = [Window] @{
Title = 'Hello World'
}
$btn = [Button] @{
X = [Pos]::Center()
Y = [Pos]::Center()
Text = 'Quit'
}
$win.Add($btn)
[Application]::Top.Add($win)
# Attach an event handler to the button.
# Note: Register-ObjectEvent -Action is NOT an option, because
# the [Application]::Run() method that isused to display the window is blocking.
$btn.add_Clicked({
# Close the modal window.
# This call is also necessary to stop printing garbage in response to mouse
# movements later.
[Application]::RequestStop()
})
# Show the window (takes over the whole screen).
# Note: This is a blocking call.
[Application]::Run()
# Required to restore the previous terminal screen
# and for being able to rerun the application in the same session.
[Application]::Shutdown()