Share via


輸入概觀

更新:2007 年 11 月

Windows Presentation Foundation (WPF) 子系統提供強大的 API,以從各種裝置取得輸入,包括滑鼠、鍵盤和手寫筆。

本主題描述 WPF 提供的服務並說明輸入系統的架構。

這個主題包含下列章節。

  • 輸入 API
  • 事件路由
  • 處理輸入事件
  • 文字輸入
  • 焦點
  • 滑鼠位置
  • 滑鼠捕捉
  • 命令
  • 輸入系統和基底項目
  • 下一步
  • 相關主題

輸入 API

在這些基底項目類別中可以找到主要輸入 API 公開:UIElementContentElementFrameworkElementFrameworkContentElement。如需基底項目的詳細資訊,請參閱基底項目概觀。這些類別提供與按下按鍵、滑鼠按鈕、滑鼠滾輪、滑鼠移動、焦點管理和滑鼠捕捉等等相關的輸入事件功能。藉由在基底項目上置放輸入 API,而不將所有輸入事件視為服務,輸入架構可以讓輸入事件成為特定 UI 物件的來源,並支援事件路由配置,讓一個以上的項目有機會處理輸入事件。許多輸入事件都有相關的事件配對組。舉例來說,按住按鍵的事件就與 KeyDownPreviewKeyDown 事件相關。這些事件中的差異在於他們路由到目標項目的方式。預覽事件會在項目樹狀結構中從根項目以通道方式往下路由到目標項目。而反昇事件會從目標項目反昇向上到根項目。WPF 中事件路由的詳細資訊,會在本概觀稍後的內容和路由事件概觀中詳細討論。

鍵盤和滑鼠類別

除了基底項目類別上的輸入 API 之外,Keyboard 類別和 Mouse 類別也提供其他 API,用於處理鍵盤和滑鼠輸入。

Keyboard 類別的輸入 API 範例有 Modifiers 屬性 (會傳回目前按下的 ModifierKeys) 和 IsKeyDown 方法 (判斷是否按下了特定按鍵)。

下列範例使用 GetKeyStates 方法判斷 Key 是否處於按下的狀態。

// Uses the Keyboard.GetKeyStates to determine if a key is down.
// A bitwise AND operation is used in the comparison. 
// e is an instance of KeyEventArgs.
if ((Keyboard.GetKeyStates(Key.Return) & KeyStates.Down) > 0)
{
    btnNone.Background = Brushes.Red;
}

Mouse 類別的輸入 API 範例有 MiddleButton (會取得滑鼠中鍵的狀態) 和 DirectlyOver (會取得目前滑鼠指標所停留的項目)。

下列範例會判斷滑鼠的 LeftButton 是否處於 Pressed 狀態。

if (Mouse.LeftButton == MouseButtonState.Pressed)
{
    UpdateSampleResults("Left Button Pressed");
}

在本概觀中會更詳細地說明 MouseKeyboard 類別。

手寫筆輸入

WPF 具有 Stylus 的整合支援。Stylus 是在 Tablet PC 上很受歡迎的畫筆輸入。藉由使用滑鼠 API,WPF 應用程式可以將手寫筆當做滑鼠處理,但 WPF 也會公開使用類似於鍵盤和滑鼠模型的手寫筆裝置抽取。所有與手寫筆相關的 API 都包含 "Stylus" 一詞。

因為手寫筆可以當做滑鼠使用,所以只支援滑鼠輸入的應用程式仍然可以自動取得某些程度的手寫筆支援。當以這種方式使用手寫筆時,應用程式就有機會處理適當的手寫筆事件,進而處理相對應的滑鼠事件。除此之外,像筆墨輸入這類較高層級的服務也可以透過手寫筆裝置抽取來提供。如需以筆墨做為輸入的詳細資訊,請參閱筆墨入門

事件路由

FrameworkElement 可以在它的內容模型包含其他項目做為子項目,而形成項目的樹狀結構。在 WPF 中,父項目可以藉由處理事件,來參與指向其子項目或其他子代 (Descendant) 的輸入。這在建置小型控制項外的控制項時特別地有用,稱為「複合控制項」或「複合」的過程。如需項目樹狀結構以及項目樹狀結構與事件路由關係的詳細資訊,請參閱 WPF 中的樹狀結構

事件路由是將事件轉送到多個項目的過程,這樣路由過程中的特定物件或項目,可以選擇針對可能是以其他項目為來源的事件提供主要回應 (透過處理)。路由事件使用三個路由機制中的一個:直接、反昇和通道。在直接路由中,來源項目是唯一會告知的項目,且事件不會路由到任何其他項目。然而,相對於標準 CLR 事件,直接路由事件還是有一些僅針對路由事件提供的額外功能。反昇會在項目樹狀結構中向上運作,首先會告知事件來源的項目,然後才是父項目等等。通道則是從項目樹狀結構的根項目出發往下運作,而在原始來源項目結束。如需路由事件的詳細資訊,請參閱路由事件概觀

WPF 輸入事件通常會成對出現,該配對組包含通道事件和反昇事件。通道事件會使用 Preview 前置字元來跟反昇事件做為區分。例如,PreviewMouseMove 是滑鼠移動事件的通道版本,而 MouseMove 則是該事件的反昇版本。這個事件配對組慣例,是在項目層級實作的,而非 WPF 事件系統本來就有的功能。如需詳細資訊,請參閱路由事件概觀中的<WPF 輸入事件>一節。

處理輸入事件

若要接收項目的輸入,事件處理常式必須與該特定事件相關聯。在 XAML 中這是相當直接的:您會參考事件名稱做為將接聽這個事件的項目的屬性 (Attribute)。然後將屬性值依據委派,設定為您定義的事件處理常式的名稱。事件處理常式必須以 C# 這類的程式碼撰寫,並要能包含在程式碼後置 (Code-Behind) 的檔案中。

鍵盤事件的發生時機,是在鍵盤焦點位於項目上時作業系統回報發生按鍵動作。滑鼠和手寫筆事件各自歸為兩個分類:在指標位置相對於項目發生變更時回報的事件,以及回報裝置按鈕狀態變更的事件。

鍵盤輸入事件範例

下列範例會接聽向左鍵的按下動作。StackPanel 的建立具有 Button。接聽向左鍵按下動作的事件處理常式是附加到 Button 執行個體的。

範例第一部分會建立 StackPanelButton,並會附加 KeyDown 的事件處理常式。

<StackPanel>
  <Button Background="AliceBlue"
          KeyDown="OnButtonKeyDown"
          Content="Button1"/>
</StackPanel>
// Create the UI elements.
StackPanel keyboardStackPanel = new StackPanel();
Button keyboardButton1 = new Button();

// Set properties on Buttons.
keyboardButton1.Background = Brushes.AliceBlue;
keyboardButton1.Content = "Button 1";

// Attach Buttons to StackPanel.
keyboardStackPanel.Children.Add(keyboardButton1);

// Attach event handler.
keyboardButton1.KeyDown += new KeyEventHandler(OnButtonKeyDown);

第二部分是以程式碼撰寫,並會定義事件處理常式。當按下向左鍵且 Button 具有鍵盤焦點時,處理常式就會執行且 ButtonBackground 色彩就會變更。如果按下的按鍵不是向左鍵,則 ButtonBackground 色彩就會變更回原始色彩。

private void OnButtonKeyDown(object sender, KeyEventArgs e)
{
    Button source = e.Source as Button;
    if (source != null)
    {
        if (e.Key == Key.Left)
        {
            source.Background = Brushes.LemonChiffon;
        }
        else
        {
            source.Background = Brushes.AliceBlue;
        }
    }
}

滑鼠輸入事件範例

在下列範例中,ButtonBackground 色彩會在滑鼠指標進入 Button 時變更。當滑鼠離開 Button 時,Background 色彩就會還原。

範例第一部分會建立 StackPanelButton 控制項,並會將 MouseEnterMouseLeave 事件的事件處理常式附加到 Button

<StackPanel>
  <Button Background="AliceBlue"
          MouseEnter="OnMouseExampleMouseEnter"
          MouseLeave="OnMosueExampleMouseLeave">Button

  </Button>
</StackPanel>
// Create the UI elements.
StackPanel mouseMoveStackPanel = new StackPanel();
Button mouseMoveButton = new Button();

// Set properties on Button.
mouseMoveButton.Background = Brushes.AliceBlue;
mouseMoveButton.Content = "Button";

// Attach Buttons to StackPanel.
mouseMoveStackPanel.Children.Add(mouseMoveButton);

// Attach event handler.
mouseMoveButton.MouseEnter += new MouseEventHandler(OnMouseExampleMouseEnter);
mouseMoveButton.MouseLeave += new MouseEventHandler(OnMosueExampleMouseLeave);

本範例的第二部分以程式碼撰寫,並會定義事件處理常式。當滑鼠進入 Button 時,ButtonBackground 色彩會變更為 SlateGray。當滑鼠離開 Button 時,ButtonBackground 色彩會變更回 AliceBlue

private void OnMouseExampleMouseEnter(object sender, MouseEventArgs e)
{
    // Cast the source of the event to a Button.
    Button source = e.Source as Button;

    // If source is a Button.
    if (source != null)
    {
        source.Background = Brushes.SlateGray;
    }
}
private void OnMosueExampleMouseLeave(object sender, MouseEventArgs e)
{
    // Cast the source of the event to a Button.
    Button source = e.Source as Button;

    // If source is a Button.
    if (source != null)
    {
        source.Background = Brushes.AliceBlue;
    }
}

文字輸入

TextInput 事件能夠讓您以不受裝置限制的方式接聽文字輸入。鍵盤是主要的文字輸入方式,但是語音、手寫和其他輸入裝置也可以產生文字輸入。

對於鍵盤輸入,WPF 首先會傳送適當的 KeyDown/KeyUp 事件。如果沒有處理這些事件,而且按鍵是文字方面的 (而不是方向鍵或功能鍵這類的控制鍵),就會引發 TextInput 事件。在 KeyDown/KeyUpTextInput 事件間不一定有簡單的一對一對應,因為多個按鍵可以產生單一的文字字元輸入,而單一按鍵也可以產生多字元字串。對於中文、日文和韓文這些語言而言,特別是如此,因為這些語言使用輸入法 (IME),以其對應的符號系統來產生數以千計的可能字元。

當 WPF 傳送 KeyUp/KeyDown 事件時,如果按鍵可能成為 TextInput 事件的一部分,Key 會設定為 Key.System (例如按下 ALT+S 時)。這可以讓 KeyDown 事件處理常式中的程式碼檢查 Key.System,如果有找到的話,會留給接續引發的 TextInput 事件的處理常式來處理。在這些情況下,TextCompositionEventArgs 引數的各種屬性就可用於判斷原始的按鍵。同樣地,如果 IME 正在使用中,Key 具有 Key.ImeProcessed 的值,ImeProcessedKey 就會提供原始按鍵或按鍵。

下列範例會定義 Click 事件的處理常式和 KeyDown 事件的處理常式。

程式碼或標記的第一個區段會建立使用者介面。

<StackPanel KeyDown="OnTextInputKeyDown">
  <Button Click="OnTextInputButtonClick"
          Content="Open" />
  <TextBox> . . . </TextBox>
</StackPanel>
// Create the UI elements.
StackPanel textInputStackPanel = new StackPanel();
Button textInputeButton = new Button();
TextBox textInputTextBox = new TextBox();
textInputeButton.Content = "Open";

// Attach elements to StackPanel.
textInputStackPanel.Children.Add(textInputeButton);
textInputStackPanel.Children.Add(textInputTextBox);

// Attach event handlers.
textInputStackPanel.KeyDown += new KeyEventHandler(OnTextInputKeyDown);
textInputeButton.Click += new RoutedEventHandler(OnTextInputButtonClick);

而程式碼的第二個區段則包含事件處理常式。

private void OnTextInputKeyDown(object sender, KeyEventArgs e)
{
    if (e.Key == Key.O && Keyboard.Modifiers == ModifierKeys.Control)
    {
        handle();
        e.Handled = true;
    }
}

private void OnTextInputButtonClick(object sender, RoutedEventArgs e)
{
    handle();
    e.Handled = true;
} 

public void handle()
{
    MessageBox.Show("Pretend this opens a file");
}

因為輸入事件會反昇事件路由,所以不論是哪個項目持有按鍵焦點,StackPanel 都會收到輸入。TextBox 控制項會首先獲得告知,而只有在 TextBox 沒有處理輸入時才會呼叫 OnTextInputKeyDown 處理常式。如果是使用 PreviewKeyDown 事件,而非 KeyDown 事件,則首先會呼叫 OnTextInputKeyDown 處理常式。

在這個範例中,撰寫了兩次處理邏輯:一次是針對 CTRL+O,而另一次則是針對按鈕的按一下事件。藉由使用命令,取代直接處理輸入事件,就可以簡化這項作業。在本概觀和命令概觀中都會有命令的討論。

焦點

WPF 中有兩個關於焦點的主要概念:鍵盤焦點和邏輯焦點。

鍵盤焦點

鍵盤焦點是指收到鍵盤輸入的項目。在整個桌面上只能有一個項目具有鍵盤焦點。在 WPF 中,具有鍵盤焦點的項目的 IsKeyboardFocused 將會設成 true。靜態 Keyboard 方法 FocusedElement 會傳回目前擁有鍵盤焦點的項目。

鍵盤焦點的取得方式,可以是使用 TAB 鍵巡覽到某項目、或是在某些項目 (例如 TextBox) 上按一下滑鼠。另外,也可以使用 Keyboard 類別上的 Focus 方法,以程式設計方式取得鍵盤焦點。Focus 會嘗試將鍵盤焦點交給指定項目。Focus 傳回的項目是目前具有鍵盤焦點的項目。

為了要讓項目取得鍵盤焦點,Focusable 屬性和 IsVisible 屬性必須設定為 true。有些如 Panel 這類的類別,預設會將 Focusable 設為 false,因此,如果希望該項目能夠取得焦點,就必須將這個屬性設為 true。

下列範例使用 Focus,將鍵盤焦點設給 Button。應用程式中設定初始焦點的建議位置是在 Loaded 事件處理常式。

private void OnLoaded(object sender, RoutedEventArgs e)
{
    // Sets keyboard focus on the first Button in the sample.
    Keyboard.Focus(firstButton);
}

如需鍵盤焦點的詳細資訊,請參閱焦點概觀

邏輯焦點

邏輯焦點是指焦點範圍中的 FocusManager.FocusedElement。應用程式中可以有多個項目具有邏輯焦點,但在特定焦點範圍中只能有一個項目具有邏輯焦點。

焦點範圍是一個容器項目,會持續追蹤範圍內的 FocusedElement。當焦點離開焦點範圍時,具有焦點的項目會失去鍵盤焦點,但卻仍然保有邏輯焦點。當焦點返回焦點範圍時,具有焦點的項目將會取得鍵盤焦點。這樣可以讓鍵盤焦點在多個焦點範圍間變更,但可以確保在焦點返回時,焦點範圍內的焦點項目仍然維持是焦點項目。

項目可以進入焦點範圍內的方法是,在可延伸標記語言 (XAML) 中將 FocusManager 附加屬性 IsFocusScope 設定為 true,或是在程式碼中使用 SetIsFocusScope 方法設定附加屬性。

下列範例藉由設定 IsFocusScope 附加屬性,讓 StackPanel 進入焦點範圍。

<StackPanel Name="focusScope1" 
            FocusManager.IsFocusScope="True"
            Height="200" Width="200">
  <Button Name="button1" Height="50" Width="50"/>
  <Button Name="button2" Height="50" Width="50"/>
</StackPanel>
StackPanel focuseScope2 = new StackPanel();
FocusManager.SetIsFocusScope(focuseScope2, true);

WPF 中預設是焦點範圍的類別有 WindowMenuToolBarContextMenu

具有鍵盤焦點的項目,也會擁有所屬焦點範圍的邏輯焦點,因此,在 Keyboard 類別或是基底項目類別上使用 Focus 方法設定項目焦點,會嘗試賦予項目鍵盤焦點和邏輯焦點。

若要判斷焦點範圍中的焦點項目,請使用 GetFocusedElement。若要變更焦點範圍的焦點項目,請使用 SetFocusedElement

如需邏輯焦點的詳細資訊,請參閱焦點概觀

滑鼠位置

WPF 輸入 API 提供有關座標空間的有用資訊。舉例來說,(0,0) 是左上方座標,但是樹狀結構中哪個項目的左上方?是身為輸入目標的項目?還是您附加事件處理常式的項目?或是其他項目?為了避免混淆,WPF 輸入 API 需要您在處理透過滑鼠取得的座標時指定參考框架 (Frame)。GetPosition 方法會傳回相對於指定項目的滑鼠指標座標。

滑鼠捕捉

滑鼠裝置特別會持有稱為滑鼠捕捉的強制回應 (Modal) 特性。滑鼠捕捉是用於在啟動拖放作業時維持轉換輸入狀態,這樣牽涉到滑鼠指標的表面螢幕位置的其他作業就不一定要發生。在拖曳期間,使用者不能在不中止拖放作業的情況下執行按一下動作,這樣會在拖曳原點持有滑鼠捕捉時,讓大部分的滑鼠停留提示成為不適當的動作。輸入系統會公開用於判斷滑鼠捕捉狀態的 API,以及可以將滑鼠捕捉強制給特定項目或清除捕捉狀態的 API。如需拖放作業的詳細資訊,請參閱拖放概觀

命令

命令是以比裝置輸入具備更多語意的方式啟用輸入處理。命令是簡單的指示詞,例如 Cut, Copy、Paste 或 Open。命令有助於讓您集中命令邏輯。相同的命令可以從 ToolBarMenu 或是透過鍵盤快速鍵存取。命令也提供在無法使用命令時的控制項停用機制。

RoutedCommandICommand 的 WPF 實作。執行 RoutedCommand 時,PreviewExecutedExecuted 事件是在命令目標引發的,這兩者會像其他輸入一樣,在項目樹狀結構進行通道和反昇路由。如果沒有設定命令目標,具有鍵盤焦點的項目會成為命令目標。執行這個命令的邏輯是附加到 CommandBinding。當 Executed 事件抵達該特定命令的 CommandBinding 時,會呼叫 CommandBinding 上的 ExecutedRoutedEventHandler。這個處理常式會執行命令的動作。

如需命令的詳細資訊,請參閱命令概觀

WPF 提供一個通用命令程式庫,包括 ApplicationCommandsMediaCommandsComponentCommandsNavigationCommandsEditingCommands,或者您可以定義自己的。

下列範例顯示如何設定 MenuItem,這樣在 TextBox 具有鍵盤焦點的情況下,按下該功能表項目時會叫用 TextBox 上的 Paste 命令。

<StackPanel>
  <Menu>
    <MenuItem Command="ApplicationCommands.Paste" />
  </Menu>
  <TextBox />
</StackPanel>
// Creating the UI objects
StackPanel mainStackPanel = new StackPanel();
TextBox pasteTextBox = new TextBox();
Menu stackPanelMenu = new Menu();
MenuItem pasteMenuItem = new MenuItem();

// Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem);
mainStackPanel.Children.Add(stackPanelMenu);
mainStackPanel.Children.Add(pasteTextBox);

// Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste;

如需 WPF 中命令的詳細資訊,請參閱命令概觀

輸入系統和基底項目

附加事件這類由 MouseKeyboardStylus 類別所定義的輸入事件,是由輸入系統所引發,並會依據執行階段時視覺化樹狀結構的點擊測試 (Hit Testing),來插入物件模型中的特定位置。

MouseKeyboardStylus 定義為附加事件的每個事件,也會由基底項目類別 UIElementContentElement 重新公開為新的路由事件。基底項目路由事件是藉由類別處理原始的附加事件並重複使用事件資料而產生的。

當輸入事件透過其基底項目輸入事件實作而與特定來源項目產生關聯時,它可以透過事件路由的其餘部分依據邏輯和視覺化樹狀結構物件的組合來進行路由,並由應用程式的程式碼處理。通常,藉由使用 UIElementContentElement 上的路由事件,處理這些與裝置相關的輸入事件是比較方便的,因為您在 XAML 和程式碼中都可以使用更為直覺化的事件處理常式語法。您可以選擇改為處理初始該過程的附加事件,但會遇到幾個問題:基底項目類別處理可能會將附加事件標記為已處理,而且您需要使用存取子方法而非真正的事件語法,才能將處理常式附加到附加事件。

下一步

現在您知道許多處理 WPF 中之輸入的技巧。對於 WPF 使用的各種輸入事件類型和路由事件機制,您應該也有了進一步的了解。

另外也有詳細說明 WPF 架構項目和事件路由的其他資源。如需詳細資訊,請參閱下列概觀:命令概觀焦點概觀基底項目概觀WPF 中的樹狀結構路由事件概觀

請參閱

概念

焦點概觀

命令概觀

路由事件概觀

基底項目概觀

其他資源

屬性