在 WPF C# 中获取键盘布局
Posted
技术标签:
【中文标题】在 WPF C# 中获取键盘布局【英文标题】:Getting the Keyboard Layout in WPF C# 【发布时间】:2019-05-28 01:01:44 【问题描述】:我需要为我的 WPF 应用程序获取当前 Windows 键盘布局,以正确映射每个键并处理 AZERTY 以及 QWERTY 和 QWERTZ(等等......)
我注意到一个问题,因为我使用的是法语布局 (azerty),但我的窗口显示为英文。
我尝试了各种方法来正确获取布局但没有结果:
var test1 = InputLanguageManager.Current.CurrentInputLanguage;
和
var test2 = CultureInfo.CurrentCulture;
我尝试让 ENG 语言与 AZERTY 布局、ENG 语言与 QWERTY 布局和 FRA 语言与 AZERTY 布局,但我的测试输出总是不同。我可以正确显示语言(en-GB),但不能正确显示布局。
【问题讨论】:
Dupish ...detect-current-keyboard-language-layout-name-in-multi-language-computer - 但它使用与您相同的方式...所以可能不是解决方案? 我也试过了...var HKL = NativeMethods.GetCurrentKeyboardLayout(); `var aszasza = Thread.CurrentThread.CurrentCulture; `var nfaoiure = new CultureInfo("fr"); `var oui = CultureInfo.CurrentCulture; `var frefrefre = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName; `var formLang = InputLanguageManager.Current.CurrentInputLanguage;
【参考方案1】:
以下 PowerShell1 脚本声明了 Get-KeyboardLayoutForPid
函数,该函数可靠地获取任何进程的当前 Windows 键盘布局2。
if ( $null -eq ('Win32Functions.KeyboardLayout' -as [type]) )
Add-Type -MemberDefinition @'
[DllImport("user32.dll")]
public static extern IntPtr GetKeyboardLayout(uint thread);
'@ -Name KeyboardLayout -Namespace Win32Functions
Function Get-KeyboardLayoutForPid
[cmdletbinding()]
Param (
[parameter(Mandatory=$False, ValueFromPipeline=$False)]
[int]$Id = $PID,
# used formerly for debugging
[parameter(Mandatory=$False, DontShow=$True)]
[switch]$Raw
)
$InstalledInputLanguages = [System.Windows.Forms.InputLanguage]::InstalledInputLanguages
$CurrentInputLanguage = [System.Windows.Forms.InputLanguage]::DefaultInputLanguage # CurrentInputLanguage
$CurrentInputLanguageHKL = $CurrentInputLanguage.Handle # just an assumption
### Write-Verbose ('CurrentInputLanguage: 0, 0x1:X8 (2), 3' -f $CurrentInputLanguage.Culture, ($CurrentInputLanguageHKL -band 0xffffffff), $CurrentInputLanguageHKL, $CurrentInputLanguage.LayoutName)
Function GetRealLayoutName ( [IntPtr]$HKL )
$regBase = 'Registry::' +
'HKLM\SYSTEM\CurrentControlSet\Control\Keyboard Layouts'
$LayoutHex = '0:x8' -f ($hkl -band 0xFFFFFFFF)
if ( ($hkl -band 0xFFFFFFFF) -lt 0 )
$auxKeyb = Get-ChildItem -Path $regBase |
Where-Object
$_.Property -contains 'Layout Id' -and
(Get-ItemPropertyValue -Path "Registry::$($_.Name)" `
-Name 'Layout Id' `
-ErrorAction SilentlyContinue
) -eq $LayoutHex.Substring(2,2).PadLeft(4,'0')
| Select-Object -ExpandProperty PSChildName
else
$auxKeyb = $LayoutHex.Substring(0,4).PadLeft(8,'0')
$KbdLayoutName = Get-ItemPropertyValue -Path (
Join-Path -Path $regBase -ChildPath $auxKeyb
) -ErrorAction SilentlyContinue -Name 'Layout Text'
$KbdLayoutName
# Another option: grab localized string from 'Layout Display Name'
# Function GetRealLayoutName
Function GetKbdLayoutForPid
Param (
[parameter(Mandatory=$True, ValueFromPipeline=$False)]
[int]$Id,
[parameter(Mandatory=$False, DontShow=$True)]
[string]$Parent = ''
)
$Processes = Get-Process -Id $Id
$Weirds = @('powershell_ise','explorer') # not implemented yet
$allLayouts = foreach ( $Proces in $Processes )
$LayoutsExtra = [ordered]@
$auxKLIDs = @( for ( $i=0; $i -lt $Proces.Threads.Count; $i++ )
$thread = $Proces.Threads[$i]
## The return value is the input locale identifier for the thread:
$LayoutInt = [Win32Functions.KeyboardLayout]::GetKeyboardLayout( $thread.Id )
$LayoutsExtra[$LayoutInt] = $thread.Id
)
Write-Verbose ('0,6 (1,6) 2: 3' -f $Proces.Id, $Parent,
$Proces.ProcessName, (($LayoutsExtra.Keys |
Select-Object -Property @ N='Handl';E=('0:x8' -f ($_ -band 0xffffffff)) |
Select-Object -ExpandProperty Handl) -join ', '))
foreach ( $auxHandle in $LayoutsExtra.Keys )
$InstalledInputLanguages | Where-Object $_.Handle -eq $auxHandle
$ConHost = Get-WmiObject Win32_Process -Filter "Name = 'conhost.exe'"
$isConsoleApp = $ConHost | Where-Object $_.ParentProcessId -eq $Proces.Id
if ( $null -ne $isConsoleApp )
GetKbdLayoutForPid -Id ($isConsoleApp.ProcessId) -Parent ($Proces.Id -as [string])
if ( $null -eq $allLayouts )
# Write-Verbose ('0,6 (1,6) 2: 3' -f $Proces.Id, $Parent, $Proces.ProcessName, '')
else
$allLayouts
# GetKbdLayoutForPid
$allLayoutsRaw = GetKbdLayoutForPid -Id $Id
if ( $null -ne $allLayoutsRaw )
if ( ([bool]$PSBoundParameters['Raw']) )
$allLayoutsRaw
else
$retLayouts = $allLayoutsRaw |
Sort-Object -Property Handle -Unique |
Where-Object $_.Handle -ne $CurrentInputLanguageHKL
if ( $null -eq $retLayouts ) $retLayouts = $CurrentInputLanguage
$RealLayoutName = $retLayouts.Handle |
ForEach-Object GetRealLayoutName -HKL $_
$ProcessAux = Get-Process -Id $Id
$retLayouts | Add-Member -MemberType NoteProperty -Name 'ProcessId' -Value "$Id"
$retLayouts | Add-Member -MemberType NoteProperty -Name 'ProcessName' -Value ($ProcessAux | Select-Object -ExpandProperty ProcessName )
# $retLayouts | Add-Member -MemberType NoteProperty -Name 'WindowTitle' -Value ($ProcessAux | Select-Object -ExpandProperty MainWindowTitle )
$retLayouts | Add-Member -MemberType NoteProperty -Name 'RealLayoutName' -Value ($RealLayoutName -join ';')
$retLayouts
<#
.Synopsis
Get the current Windows Keyboard Layout for a process.
.Description
Gets the current Windows Keyboard Layout for a process. Identify the process
using -Id parameter.
.Parameter Id
A process Id, e.g.
- Id property of System.Diagnostics.Process instance (Get-Process), or
- ProcessId property (an instance of the Win32_Process WMI class), or
- PID property from "TaskList.exe /FO CSV", …
.Parameter Raw
Parameter -Raw is used merely for debugging.
.Example
Get-KeyboardLayoutForPid
This example shows output for the current process (-Id $PID).
Note that properties RealLayoutName and LayoutName could differ (the latter is wrong; a bug in [System.Windows.Forms.InputLanguage] implementation?)
ProcessId : 2528
ProcessName : powershell
RealLayoutName : United States-International
Culture : cs-CZ
Handle : -268368891
LayoutName : US
.Example
. D:\PShell\tests\Get-KeyboardLayoutForPid.ps1 # activate the function
Get-Process -Name * |
ForEach-Object Get-KeyboardLayoutForPid -Id $_.Id -Verbose
This example shows output for each currently running process, unfortunately
even (likely unusable) info about utility/service processes.
The output itself can be empty for most processes, but the verbose stream
shows (hopefully worthwhile) info where current keboard layout is held.
Note different placement of the current keboard layout ID:
- console application (cmd, powershell, ubuntu): conhost
- combined GUI/console app (powershell_ise) : the app itself
- classic GUI apps (notepad, notepad++, …) : the app itself
- advanced GUI apps (iexplore) : Id ≘ tab
- "modern" GUI apps (MicrosoftEdge*) : Id ≟ tab (unclear)
- combined GUI/service app (explorer) : indiscernible
- etc… (this list is incomplete).
For instance, iexplore.exe creates a separate process for each open window
or tab, so their identifying and assigning input languages is an easy task.
On the other side, explorer.exe creates the only process, regardless of
open visible window(s), so they are indistinguishable by techniques used here…
.Example
gps -Name explorer | % Get-KeyboardLayoutForPid -Id $_.Id | ft -au
This example shows where the function could fail in a language multifarious environment:
ProcessId ProcessName RealLayoutName Culture Handle LayoutName
--------- ----------- -------------- ------- ------ ----------
5344 explorer Greek (220);US el-GR -266992632 Greek (220)
5344 explorer Greek (220);US cs-CZ 67699717 US
- scenario:
open three different file explorer windows and set their input languages
as follows (their order does not matter):
- 1st window: let default input language (e.g. Czech, in my case),
- 2nd window: set different input language (e.g. US English),
- 3rd window: set different input language (e.g. Greek).
- output:
an array (and note that default input language window isn't listed).
.Inputs
No object can be piped to the function. Use -Id pameter instead,
named or positional.
.Outputs
[System.Windows.Forms.InputLanguage] extended as follows:
note the <…> placeholder
Get-KeyboardLayoutForPid | Get-Member -MemberType Properties
TypeName: System.Windows.Forms.InputLanguage
Name MemberType Definition
---- ---------- ----------
ProcessId NoteProperty string ProcessId=<…>
ProcessName NoteProperty System.String ProcessName=powershell
RealLayoutName NoteProperty string RealLayoutName=<…>
Culture Property cultureinfo Culture get;
Handle Property System.IntPtr Handle get;
LayoutName Property string LayoutName get;
.Notes
To add the `Get-KeyboardLayoutForPid` function to the current scope,
run the script using `.` dot sourcing operator, e.g. as
. D:\PShell\tests\Get-KeyboardLayoutForPid.ps1
Auhor: https://***.com/users/3439404/josefz
Created: 2019-11-24
Revisions:
.Link
.Component
P/Invoke
<##>
# Function Get-KeyboardLayoutForPid
if ( -not ('System.Windows.Forms.InputLanguage' -as [type]) )
Add-Type -AssemblyName System.Windows.Forms
Get-KeyboardLayoutForPid
函数包含一个位于其主体末尾的基于注释的帮助。 希望它的原理在C#
easy中可以实现。
我的方法的主要思想:
-
假设给定进程的当前键盘布局 (
CurrentInputLanguage
) 是(取决于用户的)默认键盘布局 (DefaultInputLanguage
em>)。
收集与给定进程的每个线程关联的键盘布局 (allLayoutsRaw
)。请注意控制台应用程序的这个技巧:收集与子 conhost
进程的每个线程相关联的键盘布局。
如果 allLayoutsRaw
集合中的键盘布局与 DefaultInputLanguage
不同,那么它就是受欢迎的键盘布局 (retLayouts
em>)。
1 在 PowerShell Core (pwsh.exe
) 中不起作用。
2 在多语言环境中explorer
进程可能会失败,请参阅基于注释 帮助中的失败场景示例。
【讨论】:
【参考方案2】:我不确定这个问题 - 您是想知道当前的键盘布局还是要设置键盘布局。
在这两种情况下,InputLanguageManager 都应该有所帮助。
您可以尝试将输入语言管理器设置为适当的cultureInfo 对象。 这应该会更改 WPF 应用程序的键盘布局
InputLanguageManager.Current.CurrentInputLanguage = new CultureInfo("fr-FR");
【讨论】:
我想获取用户的当前布局,我使用自己的布局进行测试,因为我的布局与我的语言偏好不匹配。当我使用计算机时,语言设置为 en-GB,但键盘布局是 AZERTY。InputLanguageManager
获取的不是布局而是语言(除非我错了)以上是关于在 WPF C# 中获取键盘布局的主要内容,如果未能解决你的问题,请参考以下文章