鍵盤事件
鍵盤事件和焦點
硬體和觸控式鍵盤都可能發生下列鍵盤事件。
活動 | 描述 |
---|---|
KeyDown | 發生於按下按鍵時。 |
KeyUp | 發生於放開按鍵時。 |
重要
某些 XAML 控制項會在內部處理輸入事件。 在這些情況下,輸入事件可能不會發生,因為您的事件接聽程式不會呼叫關聯的處理常式。 通常,類別處理常式將處理這些鍵的子集,以提供對基本鍵盤協助工具的內建支援。 例如,Button 類別會覆寫 Space 鍵和 Enter 鍵的 OnKeyDown 事件 (OnPointerPressed 也是如此),並將它們路由到控制項的 Click 事件。 當控制項類別處理按鍵時,不會引發 KeyDown 和 KeyUp 事件。
這提供與叫用按鈕等效的內建鍵盤,效果類似於使用手指點選或使用滑鼠右鍵。 除 Space 鍵或 Enter 鍵之外的按鍵仍會觸發 KeyDown 和 KeyUp 事件。 如需基於類別的事件處理之工作原理的詳細資訊 (特別是「控制項中的輸入事件處理常式」一節),請參閱事件和路由事件概述。
UI 中的控制項只有在具有輸入焦點時才會產生鍵盤事件。 當使用者直接按一下或點選版片配置中的某個控制項時,或使用 Tab 鍵進入內容區域內的定位順序時,該控制項會獲得焦點。
您也可以呼叫控制項的 Focus 方法來強制設定焦點。 當您實作快速鍵時,這是必要的,因為載入 UI 時預設不會設定鍵盤焦點。 如需詳細資訊,請參閱本主題後面部分的快速鍵範例。
若要讓控制項接收輸入焦點,則必須啟用和顯示該控制項,且 IsTabStop 和 HitTestVisible 屬性值 true。 這是大多數控制項的預設狀態。 當控制項具有輸入焦點時,它可以引發並回應鍵盤輸入事件,如本主題後面部分所述。 您也可以透過處理 GotFocus 和 LostFocus 事件來回應接收或失去焦點的控制項。
預設情況下,控制項的定位順序是它們在 Extensible Application Markup Language (XAML) 中出現的順序。 不過,您可以使用 TabIndex 屬性來修改此順序。 如需詳細資訊,請參閱實現鍵盤協助工具。
鍵盤事件處理常式
輸入事件處理常式會實作提供以下資訊的委派:
- 事件的傳送者。 傳送者報告附加事件處理常式的物件。
- 事件資料。 對於鍵盤事件,該資料會是 KeyRoutedEventArgs 的實例。 處理常式的委派是 KeyEventHandler。 對於大多數處理程式案例,KeyRoulatedEventArgs 最相關的屬性是 Key,並且可能為 KeyStatus。
- OriginalSource。 由於鍵盤事件是路由事件,因此事件資料提供 OriginalSource。 如果有意允許事件透過物件樹狀結構向上反昇,則 OriginalSource 有時是關注的物件而不是傳送者。 不過,這取決於您的設計。 如需如何使用 OriginalSource 而不是傳送者的詳細資訊,請參閱本主題中「鍵盤路由事件」一節,或事件與路由事件概觀。
附加鍵盤事件處理常式
可以為包含將事件做為成員的任何物件附加鍵盤事件處理常式函式。 這包括任何 UIElement 衍生類別。 以下 XAML 範例示範如何為 Grid 附加 KeyUp 事件的處理常式。
<Grid KeyUp="Grid_KeyUp">
...
</Grid>
也可以在程式碼中附加事件處理常式。 如需詳細資訊,請參閱事件與路由事件概觀。
定義鍵盤事件處理常式
以下範例顯示上一個範例中所附加的 KeyUp 事件處理程式之不完整事件處理常式定義。
void Grid_KeyUp(object sender, KeyRoutedEventArgs e)
{
//handling code here
}
Private Sub Grid_KeyUp(ByVal sender As Object, ByVal e As KeyRoutedEventArgs)
' handling code here
End Sub
void MyProject::MainPage::Grid_KeyUp(
Platform::Object^ sender,
Windows::UI::Xaml::Input::KeyRoutedEventArgs^ e)
{
//handling code here
}
使用 KeyRoutedEventArgs
所有鍵盤事件都會對事件資料使用 KeyRoutedEventArgs,而 KeyRoutedEventArgs 包含以下屬性:
虛擬鍵
如果按下按鍵,則會引發 KeyDown 事件。 同樣地,如果釋放按鍵,則會引發 KeyUp。 通常,您會接聽事件來處理特定鍵值。 若要判斷按下或釋放哪個按鍵,請檢查事件資料中的 Key 值。 Key 會傳回 VirtualKey 值。 VirtualKey 列舉包含所有支援的鍵。
輔助按鍵
輔助按鍵通常是使用者與其他鍵組合而按下的按鍵,例如:Ctrl 或 Shift。 您的應用程式可以使用這些組合作為自訂鍵盤快速鍵來叫用應用程式命令。
可偵測 KeyDown 和 KeyUp 事件處理常式中的快速鍵組合。 當非輔助按鍵發生鍵盤事件時,您可以接著檢查輔助按鍵是否處於按下狀態。
或者,也可使用 CoreWindow 的 GetKeyState() 函式 (透過 CoreWindow.GetForCurrentThread() 取得) 在按下非輔助按鍵時檢查輔助狀態。
下列範例會實作第二個方法,同時包含第一個實作的虛設常式程式碼。
注意
Alt 鍵是由 VirtualKey.Menu 值表示。
快速鍵範例
下面的範例示範如何實作一組自訂快捷鍵。 在此範例中,使用者可以使用 [播放]、[停止] 和 [暫停] 按鈕或 Ctrl+P、Ctrl+A 和 Ctrl+S 鍵盤快速鍵來控制媒體播放。 按鈕 XAML 透過使用工具提示和按鈕標籤中的 AutomationProperties 屬性來顯示快捷鍵。 此自編文件對於提高應用程式的可用性和協助功能非常重要。 如需詳細資訊,請參閱鍵盤協助工具。
另請注意,載入頁面時頁面將輸入焦點設為其自身。 如果沒有此步驟,則不會有控制項獲得初始輸入焦點,且在使用者手動設定輸入焦點之前,應用程式不會引發輸入事件 (例如:透過 Tab 瀏覽或按一下控制項)。
<Grid KeyDown="Grid_KeyDown">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<MediaElement x:Name="DemoMovie" Source="xbox.wmv"
Width="500" Height="500" Margin="20" HorizontalAlignment="Center" />
<StackPanel Grid.Row="1" Margin="10"
Orientation="Horizontal" HorizontalAlignment="Center">
<Button x:Name="PlayButton" Click="MediaButton_Click"
ToolTipService.ToolTip="Shortcut key: Ctrl+P"
AutomationProperties.AcceleratorKey="Control P">
<TextBlock>Play</TextBlock>
</Button>
<Button x:Name="PauseButton" Click="MediaButton_Click"
ToolTipService.ToolTip="Shortcut key: Ctrl+A"
AutomationProperties.AcceleratorKey="Control A">
<TextBlock>Pause</TextBlock>
</Button>
<Button x:Name="StopButton" Click="MediaButton_Click"
ToolTipService.ToolTip="Shortcut key: Ctrl+S"
AutomationProperties.AcceleratorKey="Control S">
<TextBlock>Stop</TextBlock>
</Button>
</StackPanel>
</Grid>
//showing implementations but not header definitions
void MainPage::OnNavigatedTo(NavigationEventArgs^ e)
{
(void) e; // Unused parameter
this->Loaded+=ref new RoutedEventHandler(this,&MainPage::ProgrammaticFocus);
}
void MainPage::ProgrammaticFocus(Object^ sender, RoutedEventArgs^ e)
{
this->Focus(Windows::UI::Xaml::FocusState::Programmatic);
}
void KeyboardSupport::MainPage::MediaButton_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
FrameworkElement^ fe = safe_cast<FrameworkElement^>(sender);
if (fe->Name == "PlayButton") {DemoMovie->Play();}
if (fe->Name == "PauseButton") {DemoMovie->Pause();}
if (fe->Name == "StopButton") {DemoMovie->Stop();}
}
bool KeyboardSupport::MainPage::IsCtrlKeyPressed()
{
auto ctrlState = CoreWindow::GetForCurrentThread()->GetKeyState(VirtualKey::Control);
return (ctrlState & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down;
}
void KeyboardSupport::MainPage::Grid_KeyDown(Platform::Object^ sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs^ e)
{
if (e->Key == VirtualKey::Control) isCtrlKeyPressed = true;
}
void KeyboardSupport::MainPage::Grid_KeyUp(Platform::Object^ sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs^ e)
{
if (IsCtrlKeyPressed())
{
if (e->Key==VirtualKey::P) { DemoMovie->Play(); }
if (e->Key==VirtualKey::A) { DemoMovie->Pause(); }
if (e->Key==VirtualKey::S) { DemoMovie->Stop(); }
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// Set the input focus to ensure that keyboard events are raised.
this.Loaded += delegate { this.Focus(FocusState.Programmatic); };
}
private void MediaButton_Click(object sender, RoutedEventArgs e)
{
switch ((sender as Button).Name)
{
case "PlayButton": DemoMovie.Play(); break;
case "PauseButton": DemoMovie.Pause(); break;
case "StopButton": DemoMovie.Stop(); break;
}
}
private static bool IsCtrlKeyPressed()
{
var ctrlState = CoreWindow.GetForCurrentThread().GetKeyState(VirtualKey.Control);
return (ctrlState & CoreVirtualKeyStates.Down) == CoreVirtualKeyStates.Down;
}
private void Grid_KeyDown(object sender, KeyRoutedEventArgs e)
{
if (IsCtrlKeyPressed())
{
switch (e.Key)
{
case VirtualKey.P: DemoMovie.Play(); break;
case VirtualKey.A: DemoMovie.Pause(); break;
case VirtualKey.S: DemoMovie.Stop(); break;
}
}
}
Private isCtrlKeyPressed As Boolean
Protected Overrides Sub OnNavigatedTo(e As Navigation.NavigationEventArgs)
End Sub
Private Function IsCtrlKeyPressed As Boolean
Dim ctrlState As CoreVirtualKeyStates = CoreWindow.GetForCurrentThread().GetKeyState(VirtualKey.Control);
Return (ctrlState & CoreVirtualKeyStates.Down) == CoreVirtualKeyStates.Down;
End Function
Private Sub Grid_KeyDown(sender As Object, e As KeyRoutedEventArgs)
If IsCtrlKeyPressed() Then
Select Case e.Key
Case Windows.System.VirtualKey.P
DemoMovie.Play()
Case Windows.System.VirtualKey.A
DemoMovie.Pause()
Case Windows.System.VirtualKey.S
DemoMovie.Stop()
End Select
End If
End Sub
Private Sub MediaButton_Click(sender As Object, e As RoutedEventArgs)
Dim fe As FrameworkElement = CType(sender, FrameworkElement)
Select Case fe.Name
Case "PlayButton"
DemoMovie.Play()
Case "PauseButton"
DemoMovie.Pause()
Case "StopButton"
DemoMovie.Stop()
End Select
End Sub
注意
在 XAML 中設定 AutomationProperties.AcceleratorKey 或 AutomationProperties.AccessKey 會提供字串資訊,這可記錄用於呼叫該特定動作的快速鍵。 這些資訊是由 Microsoft UI 自動化用戶端 (例如:朗讀程式) 擷取,並且通常會直接提供給使用者。
設定 AutomationProperties.AcceleratorKey 或 AutomationProperties.AccessKey 不會自行執行任何動作。 您仍然需要附加 KeyDown 或 KeyUp 事件的處理常式,才能在應用程式中實際實作鍵盤快速鍵行為。 此外,不會自動為便捷鍵提供下底線的文字效果。 如果您希望在 UI 中顯示標有下底線的文字,則必須明確對助憶鍵中特定鍵的文字標註下底線,作為內嵌 Underline 格式。
鍵盤路由事件
某些事件是路由事件,這些事件包括 KeyDown 和 KeyUp。 路由事件會使用事件反昇路由傳送策略。 反昇路由策略表示事件從子物件開始,然後向上路由到物件樹狀結構中的後續父物件。 這提供處理相同事件以及與相同資料資料互動的另一個機會。
請考慮以下 XAML 範例,該範例處理 Canvas 和兩個 Button 物件的 KeyUp 事件。 在這種情況下,如果在任一 Button 物件具有焦點的同時釋放鍵,則會引發 KeyUp 事件。 然後事件會反昇至父系 Canvas。
<StackPanel KeyUp="StackPanel_KeyUp">
<Button Name="ButtonA" Content="Button A"/>
<Button Name="ButtonB" Content="Button B"/>
<TextBlock Name="statusTextBlock"/>
</StackPanel>
以下範例顯示如何為前面範例中對應的 XAML 內容實作 KeyUp 事件處理常式。
void StackPanel_KeyUp(object sender, KeyRoutedEventArgs e)
{
statusTextBlock.Text = String.Format(
"The key {0} was pressed while focus was on {1}",
e.Key.ToString(), (e.OriginalSource as FrameworkElement).Name);
}
請注意上述處理常式中 OriginalSource 屬性的用法。 在此處,OriginalSource 會回報引發此事件的物件。 這個物件不可能是 StackPanel,因為 StackPanel 不是控制項,無法具有焦點。 StackPanel 中的兩個按鈕只有一個可能會引發此事件,但是是哪一個按鈕呢? 如果在父物件上處理此事件,可使用 OriginalSource 來辨別實際的事件來源物件。
事件資料中的 Handled 屬性
視您的事件處理策略而定,您可能只想要一個事件處理常式對反昇事件做出反應。 例如,如果已將特定的 KeyUp 處理常式附加到其中一個 Button 控制項,則這是處理該事件的第一次機會。 在這種情況下,您可能不希望父系面板也處理事件。 在此案例中,您可以使用事件資料中的 Handled 屬性。
路由事件資料類別中的 Handled 屬性用於報告先前在事件路由上註冊的另一個處理程式已經採取行動。 這會影響路由事件系統的行為。 在事件處理常式中將 Handled 設為 true 時,該事件停止路由,但不會傳送到後續的父元素。
AddHandler 和 -already-handled 的鍵盤事件
可以使用特殊技術來附加處理常式,處理常式可以對已標示為已處理的事件採取行動。 這項技術使用 AddHandler 方法註冊處理常式,而不是使用 XAML 屬性或特定語言的語法來新增處理常式,例如:C# 中的 +=。
這項技術的一般限制是 AddHandler API 採用 RoutedEvent 類型的參數,以識別有問題的路由事件。 並非所有路由事件都提供 RoatedEvent 識別碼,因此這種注意事項會影響在 Handled 情況下仍可以處理哪些路由事件。 KeyDown 和 KeyUp 事件在 UIElement 上具有路由事件識別碼 (KeyDownEvent 和 KeyUpEvent)。 但是,其他事件 (例如:TextBox.TextChanged) 沒有路由事件識別碼,因此不能使用 AddHandler 技術。
覆寫鍵盤事件和行為
您可以覆寫特定控制項 (例如:GridView) 的按鍵事件,以為各種輸入裝置 (包括鍵盤和遊戲台) 提供一致的焦點瀏覽。
在以下範例中,我們將控制項劃入子類別並覆寫 KeyDown 行為,以在按下任何方向鍵時將焦點移至 GridView 內容。
public class CustomGridView : GridView
{
protected override void OnKeyDown(KeyRoutedEventArgs e)
{
// Override arrow key behaviors.
if (e.Key != Windows.System.VirtualKey.Left && e.Key !=
Windows.System.VirtualKey.Right && e.Key !=
Windows.System.VirtualKey.Down && e.Key !=
Windows.System.VirtualKey.Up)
base.OnKeyDown(e);
else
FocusManager.TryMoveFocus(FocusNavigationDirection.Down);
}
}
注意
如果僅使用 GridView 進行版面配置,請考慮使用其他控制項,例如:ItemsControl 和 ItemsWrapGrid。
命令
少數 UI 元素提供對命令的內建支援。 命令會在其基礎實作中使用與輸入相關的路由事件。 它可藉由叫用單一命令處理常式,來處理相關的 UI 輸入,例如:特定指標動作或特定對應鍵。
如果命令可用於 UI 元素,請考慮使用其命令 API 而不是任何特定的輸入事件。 如需詳細資訊,請參閱 ButtonBase.Command。
也可以實作 ICommand 來封裝從普通事件處理常式中叫用的命令功能。 這使您甚至能夠在沒有任何可用 Command 屬性的情況下使用命令。
文字輸入和控制項
某些控制項會透過自身的處理來回應鍵盤事件。 例如,TextBox 是一個控制項,設計用於擷取並使用鍵盤以視覺化方式表示輸入的文字。 它以自己的邏輯使用 KeyUp 和 KeyDown 來擷取按鍵輸入,那麼即使在文字實際上已變更的情況下,還是會引發自己的 TextChanged 事件。
通常,仍然可以將 KeyUp 和 KeyDown 的處理常式新增至 TextBox 或預期處理文字輸入的任何相關控制項中。 但是,做為其預期設計,控制項可能不會對透過按鍵事指向它的所有鍵值作出回應。 每個控制項都有特定的行為。
例如,ButtonBase (Button 的基底類別) 處理 KeyUp,以便可以檢查空白鍵或 Enter 鍵。 ButtonBase 認為 KeyUp 等於按下滑鼠左鍵以引發 Click 事件。 在 ButtonBase 覆蓋虛擬方法 OnKeyUp 時,就會完成此事件的處理。 在實作過程中,它會將 Handled 設為 true。 結果是,在空白鍵的情況下,正在接聽按鍵事件之按鈕的任何父系都不會收到其自己處理常式的 already-handled 事件。
另一個範例是 TextBox。 某些按鍵 (例如:方向鍵) 不會被 TextBox 視為文字,而是被視為特定於控制項 UI 的行為。 TextBox 會將這些事件案例標示為已處理。
自訂控制項可以透過重寫 OnKeyDown / OnKeyUp 來實作自己類似的按鍵事件覆寫行為。 如果您的自訂控制項處理特定的對應鍵,或具有與 TextBox 所述案例類似的控制項或焦點行為,則應將此邏輯放在您自己的 OnKeyDown / OnKeyUp 中覆寫。
觸控鍵盤
文字輸入控制項為觸控式鍵盤提供自動支援。 當使用者使用觸控輸入將輸入焦點設定到文字控制項時,觸控式鍵盤會自動出現。 當輸入焦點不在文字控制項上時,會隱藏觸控式鍵盤。
觸控式鍵盤出現時,會自動重新定位 UI,以確保具有焦點的元素保持可見狀態。 這可能會導致 UI 的其他重要區域移出畫面。 但是,您可以停用預設行為並在出現觸控式鍵盤時調整自己的 UI。 如需詳細資訊,請參閱觸控式鍵盤範例。
如果您建立需文字輸入但並不是從標準文字輸入控制項衍生的自訂控制項,則可以透過實作正確的 UI 自動化控制項模式來新增觸控式鍵盤支援。 如需詳細資訊,請參閱觸控式鍵盤範例。
按下觸控式鍵盤上的按鍵會引發 KeyDown 和 KeyUp 事件,就像在硬體鍵盤上按下按鍵一樣。 但是,觸控式鍵盤不會針對 Ctrl+A、Ctrl+Z、Ctrl+X、Ctrl+C 和 Ctrl+V 引發輸入事件,這些事件是為輸入控制項中的文字操作保留的。
您可以設定文字控制項的輸入範圍,使其符合您預期使用者輸入的資料類型,讓使用者在您的應用程式中輸入資料時更加快速方便。 輸入範圍會提供控制項所預期之文字輸入類型的提示,讓系統可以為該輸入類型提供專用的觸控式鍵盤配置。 例如,如果文字方塊只用來輸入 4 位數 PIN,請將 InputScope 屬性設定為 Number。 這會告訴系統顯示數字小鍵盤,方便使用者輸入 PIN。 如需詳細資訊,請參閱使用輸入範圍變更觸控式鍵盤。