How to handle keyboard input in a Direct3D app for Windows Phone 8
[ This article is for Windows Phone 8 developers. If you’re developing for Windows 10, see the latest documentation. ]
This topic shows you how to implement a custom text box in which a user can enter text using the Software Input Panel (SIP) keyboard for Windows Phone Direct3D apps. Text entry is functionality that almost every app needs. With Windows Phone managed apps, you can easily add text input to your app by using the TextBox control. Unfortunately, you can’t use XAML controls in a Direct3D app. However, with a small amount of code, you can create a custom text box control that will cause the SIP keyboard to be displayed, and which is updated as the user types.
There are two different ways to implement a custom text box. You can use the KeyboardInputBuffer class, which aggregates keystrokes for you, and which provides a buffer that contains the current text, or you can use key events, which are triggered whenever the user presses a key. If you use key events, you must manage your own text buffer.
The IsKeyboardInputEnabled()()() allows you to toggle the visibility of the SIP keyboard on and off, but it’s not reliable for determining if the keyboard is currently displayed. If the user closes the keyboard by pressing the Back button, the value of this property is not updated. Instead, you should listen for the Hiding event, which is raised when the user dismisses the keyboard with the Back button. Also, you can check the OccludedRect property at any time and if the rectangle’s width and height are zero, then you know that the SIP is not currently displayed. A brief demonstration of these techniques are presented at the bottom of the walkthrough.
Note
To help to keep these procedures simple, the Direct3D code necessary for actually rendering text to the screen has been omitted from these examples.
Creating a custom text box using KeyboardInputBuffer
Create a new Direct3D project. This example assumes you name the project “NativeTextInput”.
In Visual Studio, go to Project->Add Class to add a new class to the project. Name the new class “InputBufferTextBox”.
Paste the following code into the InputBufferTextBox.h header file. All of the methods and member variables declared here will be explained in the steps that follow.
ref class InputBufferTextBox sealed { public: InputBufferTextBox(void); InputBufferTextBox(Windows::UI::Core::CoreWindow^ parentWindow, int x, int y, int width, int height, Platform::String^ initialText, Windows::Phone::UI::Core::CoreInputScope inputScope); bool HitTest(int x, int y); void SetFocus(bool hasFocus); property bool HasFocus { bool get() { return m_hasFocus; } } protected: void OnTextChanged(Windows::Phone::UI::Core::KeyboardInputBuffer^ sender, Windows::Phone::UI::Core::CoreTextChangedEventArgs^ args); void OnRender(); private: Windows::UI::Core::CoreWindow^ m_parentWindow; Windows::Phone::UI::Core::KeyboardInputBuffer^ m_inputBuffer; int m_x, m_y, m_width, m_height; bool m_hasFocus; Windows::Phone::UI::Core::CoreInputScope m_inputScope; };
At the top of the InputBufferTextBox.cpp file, below the #include directives, paste the following using statements for the namespaces the text box will use.
using namespace Windows::Foundation; using namespace Windows::UI::Core; using namespace Windows::Phone::UI::Core; using namespace Windows::System; using namespace Platform;
Paste the code for the custom constructor into InputBufferTextBox.cpp. In this method, the member variable m_parentWindow is assigned. The value will be the app’s CoreWindow instance. Next, the member variables that define the position and size of the text box are set, and the input scope is set, which determines what keys are displayed in the software keyboard. Then, an instance of the KeyboardInputBuffer object is initialized. The Text()()() property is set to the initial text to be displayed in the text box. Finally, the handler for the TextChanged event is set.
InputBufferTextBox::InputBufferTextBox(CoreWindow^ parentWindow, int x, int y, int width, int height, String^ initialText, CoreInputScope inputScope) { m_parentWindow = parentWindow; m_x = x; m_y = y; m_width = width; m_height = height; m_inputScope = inputScope; m_inputBuffer = ref new KeyboardInputBuffer(); m_inputBuffer->Text = initialText; m_inputBuffer->TextChanged += ref new TypedEventHandler<KeyboardInputBuffer^, CoreTextChangedEventArgs^>(this, &InputBufferTextBox::OnTextChanged); }
Paste the code that defines the HitTest method into InputBufferTextBox.cpp. The screen coordinates where the user tapped are passed to the method. It simply checks to see if the touch point is within the bounds of the text box.
bool InputBufferTextBox::HitTest(int x, int y) { if(x >= m_x && x <= m_x + m_width && y >= m_y && y <= m_y + m_height) { return true; } else { return false; } }
Paste the code that defines the SetFocus method into InputBufferTextBox.cpp. If this method is called with a true value, it means the text box has focus and should prepare to process text input. To do this, the KeyboardInputBuffer property of the app’s CoreWindow is set to the text box KeyboardInputBuffer member variable and the input scope for the keyboard is set. Then, the IsKeyboardInputEnabled()()() property of the CoreWindow is set to true. This is what causes the SIP keyboard to appear.
If SetFocus is called with a false value, IsKeyboardInputEnabled()()() is set to false, which causes the SIP keyboard to be hidden.
void InputBufferTextBox::SetFocus(bool hasFocus) { m_hasFocus = hasFocus; if(m_hasFocus) { m_inputBuffer->InputScope = m_inputScope; m_parentWindow->KeyboardInputBuffer = m_inputBuffer; m_parentWindow->IsKeyboardInputEnabled = true; } else { m_parentWindow->IsKeyboardInputEnabled = false; } }
Paste the implementation of the TextChanged event handler into InputBufferTextBox.cpp. This event is raised when the text in the text buffer is modified. This happens when the user taps a key on the SIP keyboard, but it can occur for other reasons, such as if the user taps on a word from the list of auto correction and replacement suggestions. Get the current text in the buffer by accessing the Text()()() property. This method checks to see if a newline is present in the text buffer, indicating that the user hit the enter key. If so, the focus of the text box is set to false. You could add logic here to tab to another text box. Also, if you are rendering your text box to an off-screen buffer, you can add code to alert your render loop to redraw the control here.
void InputBufferTextBox::OnTextChanged(KeyboardInputBuffer^ sender, CoreTextChangedEventArgs^ args) { auto text = m_inputBuffer->Text; if(std::find<const wchar_t*, wchar_t>(text->Begin(), text->End(), '\r') != text->End()) { SetFocus(false); // You could add logic here to tab to another control. } // Let your render loop know that this control needs to be rendered again. // MyRenderLoop::IsDirty = true; }
Paste the OnRender method into InputBufferTextBox.cpp. The Direct3D code needed to draw on the screen has been omitted from this example, but this simplified method illustrates a typical implementation. Draw the text box container and then loop through each character in the text buffer and draw a bitmap for that character. An actual implementation of this method will be more complicated, especially if you want to include multiline text, clipping, or scrolling.
void InputBufferTextBox::OnRender() { // Draw the text box boundary // MyDrawRectangle(m_x, m_y, m_width, m_height); auto characters = m_inputBuffer->Text->Data(); for(int i = 0; i < m_inputBuffer->Text->Length(); i++) { // Draw each character in the buffer // MyDrawCharacter(characters[i], m_x + i * characterWidth, y); } }
The next steps modify the code in the main app to use the text box. At the top of NativeTextInput.h, paste this line of code to include the header file for the text box.
#include "InputBufferTextBox.h";
In the private member variable section of NativeTextInput.h, paste the variable declaration for the text box.
InputBufferTextBox^ m_inputBufferTextBox;
In NativeTextInput.cpp, paste the following line into the OnSetWindow method, which is included as part of the project template. This code initializes the text box by setting its size, location, and initial text, and then passes a reference to the app’s CoreWindow.
m_inputBufferTextBox = ref new InputBufferTextBox(window, 0, 0, 480, 100, "Initial Text", Windows::Phone::UI::Core::CoreInputScope::Url);
Finally, in NativeTextInput.cpp, replace the OnPointerPressed method, included with the project template, with the following code. This method is called when the user taps the screen. It calls the text box’s HitTest method to see if the user tapped within the bounds of the text box. If the tap was inside the text box, the text box’s focus is set to true. If the tap is outside of the text box, and the text box currently has focus, the focus is set to false. Remember that in the text box’s SetFocus method, the IsKeyboardInputEnabled()()() is toggled to show or hide the SIP keyboard.
void NativeTextInput::OnPointerPressed(CoreWindow^ sender, PointerEventArgs^ args) { CoreWindow^ window = CoreWindow::GetForCurrentThread(); auto position = args->CurrentPoint->Position; bool isTouchInTextBox = m_inputBufferTextBox->HitTest(position.X, position.Y); if(isTouchInTextBox) { m_inputBufferTextBox->SetFocus(true); } else { IsInputPaneShowing(); if(m_inputBufferTextBox->HasFocus) { // If the touch is outside of the active text box, hide the keyboard m_inputBufferTextBox->SetFocus(false); } } }
Creating a custom text box using key events
Create a new Direct3D project. This example assumes you name the project “NativeTextInput”.
In Visual Studio, go to Project->Add Class to add a new class to the project. Name the new class “KeyEventTextBox”.
Paste the following code into the KeyEventTextBox.h header file. All of the methods and member variables declared here will be explained in the steps that follow.
ref class KeyEventTextBox sealed { public: KeyEventTextBox(void); KeyEventTextBox(Windows::UI::Core::CoreWindow^ parentWindow, int x, int y, int width, int height, Platform::String^ initialText, Windows::Phone::UI::Core::CoreInputScope inputScope); bool HitTest(int x, int y); void SetFocus(bool hasFocus); property bool HasFocus { bool get() { return m_hasFocus; } } protected: void OnKeyDown(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::KeyEventArgs^ args); void OnCharacterReceived(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::CharacterReceivedEventArgs^ args); void OnRender(); private: Windows::UI::Core::CoreWindow^ m_parentWindow; Windows::Phone::UI::Core::KeyboardInputBuffer^ m_inputBuffer; Windows::Foundation::EventRegistrationToken m_keydownToken; Windows::Foundation::EventRegistrationToken m_characterReceivedToken; int m_x, m_y, m_width, m_height; Platform::String^ m_text; bool m_hasFocus; Windows::Phone::UI::Core::CoreInputScope m_inputScope; };
At the top of the KeyEventTextBox.cpp file, below the #include directives, paste the following using statements for the namespaces the text box will use.
#include <string> using namespace Windows::Foundation; using namespace Windows::UI::Core; using namespace Windows::Phone::UI::Core; using namespace Windows::System; using namespace Platform; using namespace std;
Paste the code for the custom constructor into KeyEventTextBox.cpp. In this method, the member variable m_parentWindow is assigned. The value will be the app’s CoreWindow instance. Next, the member variables that define the position and size of the text box are set, and the input scope is set, which determines what keys are displayed in the software keyboard.. Finally, the m_text member variable that will serve as the text buffer is set to the initial text to be displayed in the text box.
KeyEventTextBox::KeyEventTextBox(CoreWindow^ parentWindow, int x, int y, int width, int height, String^ initialText, CoreInputScope inputScope) { m_parentWindow = parentWindow; m_x = x; m_y = y; m_width = width; m_height = height; m_text = initialText; m_inputScope = inputScope; }
Paste the code that defines the HitTest method into KeyEventTextBox.cpp. This method is passed the screen coordinates where the user tapped. It simply checks to see if the touch point is within the bounds of the text box.
bool KeyEventTextBox::HitTest(int x, int y) { if(x >= m_x && x <= m_x + m_width && y >= m_y && y <= m_y + m_height) { return true; } else { return false; } }
Paste the code that defines the SetFocus method into KeyEventTextBox.cpp. If this method is called with a true value, it means the text box has focus and should prepare to process text input. To do this, the input scope for the keyboard is set and then the IsKeyboardInputEnabled()()() property of CoreWindow is set to true. This is what causes the SIP keyboard to be shown. Next, handlers are registered for the KeyDown and CharacterReceived events. The tokens returned when these handlers are registered are saved so that they can be unregistered later.
If SetFocus is called with a false value, IsKeyboardInputEnabled()()() is set to false, which causes the SIP keyboard to be hidden. Then, the event handlers for the KeyDown and CharacterReceived events are removed.
void KeyEventTextBox::SetFocus(bool hasFocus) { m_hasFocus = hasFocus; if(m_hasFocus) { m_inputBuffer->InputScope = m_inputScope; m_parentWindow->IsKeyboardInputEnabled = true; m_keydownToken = m_parentWindow->KeyDown += ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &KeyEventTextBox::OnKeyDown); m_characterReceivedToken = m_parentWindow->CharacterReceived += ref new TypedEventHandler<CoreWindow^, CharacterReceivedEventArgs^>(this, &KeyEventTextBox::OnCharacterReceived); } else { m_parentWindow->IsKeyboardInputEnabled = false; m_parentWindow->KeyDown -= m_keydownToken; m_parentWindow->CharacterReceived -= m_characterReceivedToken; } }
Paste the following definition of the KeyDown event handler into KeyEventTextBox.cpp. This event is raised when the user taps a key on the SIP keyboard, before the CharacterReceived event is raised. This method checks to see which key was pressed. If it is the Back key, the user has tapped the backspace key. In this case, the last letter of the text buffer is removed, and the Handled property of the event args is set to true. This will stop the system from raising CharacterReceived, so that the backspace key stroke isn’t added to the text buffer. If the Enter key is hit, the focus of the text box is set to false, which in turn hides the SIP keyboard. Then, once again the Handled property of the event args is set to true to keep CharacterReceived from getting called.
void KeyEventTextBox::OnKeyDown(CoreWindow^ sender, KeyEventArgs^ args) { if(args->VirtualKey == VirtualKey::Back) { if(m_text->Length() > 0) { wstring text = wstring(m_text->Data()); text.resize(text.length() - 1); m_text = ref new String(text.c_str()); } args->Handled = true; } else if(args->VirtualKey == VirtualKey::Enter) { SetFocus(false); args->Handled = true; } }
Paste the following definition of the CharacterReceived event handler into KeyEventTextBox.cpp. This method simply adds the character in the event args to the end of the text buffer.
void KeyEventTextBox::OnCharacterReceived(CoreWindow^ sender, CharacterReceivedEventArgs^ args) { wchar_t c = args->KeyCode; m_text += ref new String(&c, 1); // Let your render loop know that this control needs to be rendered again. // MyRenderLoop::IsDirty = true; }
Paste the OnRender method into KeyEventTextBox.cpp. The Direct3D code needed to draw on the screen has been omitted from this example, but this simplified method illustrates a typical implementation. Draw the text box container and then loop through each character in the text buffer and draw a bitmap for that character. An actual implementation of this method will be more complicated, especially if you want to include multiline text, clipping, or scrolling.
The next steps modify the code in the main app to use the text box. At the top of NativeTextInput.h, paste this line of code to include the header file for the text box.
#include "KeyEventTextBox.h";
In the private member variable section of NativeTextInput.h, paste the variable declaration for the text box.
KeyEventTextBox^ m_keyEventTextBox;
In NativeTextInput.cpp, paste the following line into the OnSetWindow method, which is included as part of the project template. This code initializes the text box by setting its size, location, and initial text, and then passes a reference to the app’s CoreWindow.
Finally, in NativeTextInput.cpp, replace the OnPointerPressed method, which is included with the project template, using the following code. This method is called when the user taps the screen. It calls the text box’s HitTest method to see if the user tapped within the bounds of the text box. If the tap was inside the text box, the text box’s focus is set to true. If the tap is outside of the text box, and the text box currently has focus, the focus is set to false. Remember that in the text box’s SetFocus method, the IsKeyboardInputEnabled()()() is toggled to show or hide the SIP keyboard.
void NativeTextInput::OnPointerPressed(CoreWindow^ sender, PointerEventArgs^ args) { CoreWindow^ window = CoreWindow::GetForCurrentThread(); auto position = args->CurrentPoint->Position; bool isTouchInTextBox = m_keyEventTextBox->HitTest(position.X, position.Y); if(isTouchInTextBox) { m_keyEventTextBox->SetFocus(true); } else { // If the touch is outside of the active text box, hide the keyboard if(m_keyEventTextBox->HasFocus) { m_keyEventTextBox->SetFocus(false); } } }
Detecting when the software keyboard is displayed
Using either one of the example apps described above, add the following code to the protected methods section of the NativeTextInput.h file.
void OnInputPaneHiding(Windows::UI::ViewManagement::InputPane^ sender, Windows::UI::ViewManagement::InputPaneVisibilityEventArgs^ args); bool IsInputPaneShowing();
At the end of the SetWindow handler, add the following lines of code to get a reference to the app’s InputPane and hook up the Hiding event.
auto inputPane = InputPane::GetForCurrentView(); inputPane->Hiding += ref new TypedEventHandler<InputPane^, InputPaneVisibilityEventArgs^>(this, &NativeTextInput::OnInputPaneHiding);
Now add the implementation of the Hiding event handler. In this example, we call the SetFocus method of the text box class and pass in false. Depending on your app, you make take other actions when the input pane is hidden.
void NativeTextInput::OnInputPaneHiding(InputPane^ sender, InputPaneVisibilityEventArgs^ args) { m_inputBufferTextBox->SetFocus(false); }
And last, you can implement an IsInputPanelShowing method that checks the OccludedRect property to determine if the screen is occluded by the keyboard. If the rectangle has a width and height greater than zero, then the keyboard is currently being shown.
bool NativeTextInput::IsInputPaneShowing() { auto inputPane = InputPane::GetForCurrentView(); auto occ = inputPane->OccludedRect; return (occ.Width > 0 && occ.Height > 0); }