diff --git a/README.md b/README.md
index ad824fb78..c79e866b9 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,7 @@ This repository hosts samples for the [Windows App SDK](https://github.com/micro
- [Push Notifications](Samples/Notifications/Push): This is a sample app that showcases Push Notifications.
#### User Interface and Input
+- [CustomEditControl](Samples/CustomEditControl): This sample demonstrates how to create a text edit control with customized UI and behavior.
- [Windowing](Samples/Windowing): This sample demonstrates how to manage app windows using the Windowing APIs.
- [Windows Input and Composition Gallery](https://github.com/microsoft/WindowsCompositionSamples): This collection of samples showcases Microsoft.UI.Composition and Microsoft.UI.Input APIs.
- [XAML Controls Gallery](https://github.com/microsoft/Xaml-Controls-Gallery/tree/winui3): This is a sample app that showcases all of the WinUI 3 controls in action.
diff --git a/Samples/CustomEditControl/README.md b/Samples/CustomEditControl/README.md
new file mode 100644
index 000000000..01a308e50
--- /dev/null
+++ b/Samples/CustomEditControl/README.md
@@ -0,0 +1,94 @@
+---
+page_type: sample
+languages:
+- csharp
+- cppwinrt
+- cpp
+products:
+- windows
+- windows-app-sdk
+name: "Custom Edit Control"
+urlFragment: CustomEditControl
+description: "Shows how to use the CoreTextEditContext class to create a rudimentary text control."
+extendedZipContent:
+- path: LICENSE
+ target: LICENSE
+---
+
+# Custom Edit Control sample
+
+Shows how to use the CoreTextEditContext class in the Windows.UI.Text.Core namespace
+to create a rudimentary text control. Note that this text control is not complete;
+it glosses over many details that would be necessary for a complete text edit control.
+
+The focus of the sample is in CustomEditControl to show how to implement and manage a text control.
+There is much more functionality built in to the CoreTextEditContext that is not covered in this sample.
+
+This sample demonstrates the following:
+
+* Managing the text and current selection of a custom edit control.
+ This sample uses a simple string to record the text.
+* Rendering the text and current selection in the custom edit control.
+ To illustrate that the app is completely responsible for the visual
+ presentation of the control, this sample takes the unusual decision
+ to use a globe to represent the caret.
+* Manually setting focus to and removing focus from the control
+ and the CoreTextEditContext based on application-defined criteria.
+ The CoreTextEditContext processes text input when it has focus.
+* Setting the CoreTextEditContext.InputPaneDisplayPolicy to Manual
+ and manually showing the software keyboard when the custom edit control
+ gains focus and hiding it when the custom edit control loses focus.
+* Responding to system events that request information about the
+ custom edit control or request changes to the text and selection of
+ the custom edit control.
+* Responding to layout information requests so that the IME candidate window
+ can be positioned properly.
+* Changing the selection and/or moving the caret when the user presses
+ an arrow key, and deleting text when the user presses the Backspace key.
+
+**Instructions on using this sample**
+
+* Click or tap on the custom edit control to give it focus,
+ and click or tap outside the custom edit control to remove focus.
+* Observe that the Input Pane appears (if applicable)
+ when the custom edit control gains focus,
+ and it disappears when the custom edit control loses focus.
+* Use the arrow keys to move the caret (shown as a globe).
+* Hold the shift key when pressing the arrow keys to adjust
+ the selection.
+* Use the Backspace key to delete text.
+* To demonstrate support for IME candidates:
+ * Install an IME by going to Settings, Time and Language,
+ Region and language. Click "Add a language" and select
+ Chinese (Simplified) "中文(中华人民共和国)".
+ * Set your input language to Chinese by using the language
+ selector in the bottom right corner of the taskbar (on Desktop)
+ or by swiping the space bar on the software keyboard (on Mobile).
+ * Put focus on the custom edit control and start typing.
+ IME candidate suggestions will appear as you type.
+
+**Features missing from this sample**
+
+* This sample does not properly handle surrogate pairs
+ or grapheme clusters.
+* This sample does not support common keyboard shortcuts
+ such as Home and End, nor does it support shortcuts such
+ as Ctrl+V to paste.
+* This sample does not show a context menu if the user right-clicks
+ or performs a press-and-hold gesture.
+* This sample does not support using the mouse or touch to
+ move the caret or adjust the selection.
+
+## Prerequisites
+
+* See [System requirements for Windows app development](https://docs.microsoft.com/windows/apps/windows-app-sdk/system-requirements).
+* Make sure that your development environment is set up correctly—see [Install tools for developing apps for Windows 10 and Windows 11](https://docs.microsoft.com/windows/apps/windows-app-sdk/set-up-your-development-environment).
+
+## Building and running any of the samples
+
+* Open the solution file (`.sln`) from the subfolder of your preferred sample in Visual Studio.
+* From Visual Studio, either **Start Without Debugging** (Ctrl+F5) or **Start Debugging** (F5).
+
+## Related Links
+
+- [CoreTextEditContext](https://msdn.microsoft.com/library/windows/apps/windows.ui.text.core.coretexteditcontext.aspx)
diff --git a/Samples/CustomEditControl/cpp-winui/App.idl b/Samples/CustomEditControl/cpp-winui/App.idl
new file mode 100644
index 000000000..8220787fc
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/App.idl
@@ -0,0 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace CustomEditControlCpp
+{
+}
diff --git a/Samples/CustomEditControl/cpp-winui/App.xaml b/Samples/CustomEditControl/cpp-winui/App.xaml
new file mode 100644
index 000000000..7988e33c9
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/App.xaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Samples/CustomEditControl/cpp-winui/App.xaml.cpp b/Samples/CustomEditControl/cpp-winui/App.xaml.cpp
new file mode 100644
index 000000000..28bb4e400
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/App.xaml.cpp
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#include "pch.h"
+
+#include "App.xaml.h"
+#include "MainWindow.xaml.h"
+
+using namespace winrt;
+using namespace Windows::Foundation;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Microsoft::UI::Xaml::Navigation;
+using namespace CustomEditControlCpp;
+using namespace CustomEditControlCpp::implementation;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+///
+/// Initializes the singleton application object. This is the first line of authored code
+/// executed, and as such is the logical equivalent of main() or WinMain().
+///
+App::App()
+{
+ InitializeComponent();
+
+#if defined _DEBUG && !defined DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION
+ UnhandledException([this](IInspectable const&, UnhandledExceptionEventArgs const& e)
+ {
+ if (IsDebuggerPresent())
+ {
+ auto errorMessage = e.Message();
+ __debugbreak();
+ }
+ });
+#endif
+}
+
+///
+/// Invoked when the application is launched normally by the end user. Other entry points
+/// will be used such as when the application is launched to open a specific file.
+///
+/// Details about the launch request and process.
+void App::OnLaunched(LaunchActivatedEventArgs const&)
+{
+ window = make();
+ window.Activate();
+}
diff --git a/Samples/CustomEditControl/cpp-winui/App.xaml.h b/Samples/CustomEditControl/cpp-winui/App.xaml.h
new file mode 100644
index 000000000..a6c20b1d4
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/App.xaml.h
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#pragma once
+
+#include "App.xaml.g.h"
+
+namespace winrt::CustomEditControlCpp::implementation
+{
+ struct App : AppT
+ {
+ App();
+
+ void OnLaunched(Microsoft::UI::Xaml::LaunchActivatedEventArgs const&);
+
+ private:
+ winrt::Microsoft::UI::Xaml::Window window{ nullptr };
+ };
+}
diff --git a/Samples/CustomEditControl/cpp-winui/Assets/LockScreenLogo.scale-200.png b/Samples/CustomEditControl/cpp-winui/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 000000000..7440f0d4b
Binary files /dev/null and b/Samples/CustomEditControl/cpp-winui/Assets/LockScreenLogo.scale-200.png differ
diff --git a/Samples/CustomEditControl/cpp-winui/Assets/SplashScreen.scale-200.png b/Samples/CustomEditControl/cpp-winui/Assets/SplashScreen.scale-200.png
new file mode 100644
index 000000000..32f486a86
Binary files /dev/null and b/Samples/CustomEditControl/cpp-winui/Assets/SplashScreen.scale-200.png differ
diff --git a/Samples/CustomEditControl/cpp-winui/Assets/Square150x150Logo.scale-200.png b/Samples/CustomEditControl/cpp-winui/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 000000000..53ee3777e
Binary files /dev/null and b/Samples/CustomEditControl/cpp-winui/Assets/Square150x150Logo.scale-200.png differ
diff --git a/Samples/CustomEditControl/cpp-winui/Assets/Square44x44Logo.scale-200.png b/Samples/CustomEditControl/cpp-winui/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 000000000..f713bba67
Binary files /dev/null and b/Samples/CustomEditControl/cpp-winui/Assets/Square44x44Logo.scale-200.png differ
diff --git a/Samples/CustomEditControl/cpp-winui/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/Samples/CustomEditControl/cpp-winui/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 000000000..dc9f5bea0
Binary files /dev/null and b/Samples/CustomEditControl/cpp-winui/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/Samples/CustomEditControl/cpp-winui/Assets/StoreLogo.png b/Samples/CustomEditControl/cpp-winui/Assets/StoreLogo.png
new file mode 100644
index 000000000..a4586f26b
Binary files /dev/null and b/Samples/CustomEditControl/cpp-winui/Assets/StoreLogo.png differ
diff --git a/Samples/CustomEditControl/cpp-winui/Assets/Wide310x150Logo.scale-200.png b/Samples/CustomEditControl/cpp-winui/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 000000000..8b4a5d0dd
Binary files /dev/null and b/Samples/CustomEditControl/cpp-winui/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/Samples/CustomEditControl/cpp-winui/CustomEditControl.idl b/Samples/CustomEditControl/cpp-winui/CustomEditControl.idl
new file mode 100644
index 000000000..b9f4cb44f
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/CustomEditControl.idl
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace CustomEditControlCpp
+{
+ [default_interface]
+ runtimeclass CustomEditControl : Microsoft.UI.Xaml.Controls.UserControl
+ {
+ CustomEditControl();
+ void SetAppWindow(Microsoft.UI.WindowId windowId);
+ void SetInternalFocus();
+ void RemoveInternalFocus();
+ }
+}
diff --git a/Samples/CustomEditControl/cpp-winui/CustomEditControl.xaml b/Samples/CustomEditControl/cpp-winui/CustomEditControl.xaml
new file mode 100644
index 000000000..9cf87557a
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/CustomEditControl.xaml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Full text:
+ Selection start index:
+ Selection end index:
+
+
+
diff --git a/Samples/CustomEditControl/cpp-winui/CustomEditControl.xaml.cpp b/Samples/CustomEditControl/cpp-winui/CustomEditControl.xaml.cpp
new file mode 100644
index 000000000..334dc580d
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/CustomEditControl.xaml.cpp
@@ -0,0 +1,574 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#include "pch.h"
+#include "CustomEditControl.xaml.h"
+#if __has_include("CustomEditControl.g.cpp")
+#include "CustomEditControl.g.cpp"
+#endif
+
+#include "InputPaneInterop.h"
+
+namespace winrt
+{
+ using namespace ::winrt::Microsoft::UI::Input;
+ using namespace ::winrt::Microsoft::UI::Windowing;
+ using namespace ::winrt::Microsoft::UI::Xaml;
+ using namespace ::winrt::Microsoft::UI::Xaml::Documents;
+ using namespace ::winrt::Microsoft::UI::Xaml::Input;
+ using namespace ::winrt::Microsoft::UI::Xaml::Media;
+ using namespace ::winrt::Windows::Foundation;
+ using namespace ::winrt::Windows::Graphics::Display;
+ using namespace ::winrt::Windows::System;
+ using namespace ::winrt::Windows::UI;
+ using namespace ::winrt::Windows::UI::Core;
+ using namespace ::winrt::Windows::UI::Text::Core;
+ using namespace ::winrt::Windows::UI::ViewManagement;
+}
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+// https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/advanced-scenarios-xaml-islands-cpp
+
+namespace winrt::CustomEditControlCpp::implementation
+{
+ CustomEditControl::CustomEditControl()
+ {
+ InitializeComponent();
+
+ // The CoreTextEditContext processes text input, but other keys are
+ // the app's responsibility.
+ // Create a CoreTextEditContext for our custom edit control.
+ winrt::CoreTextServicesManager manager = winrt::CoreTextServicesManager::GetForCurrentView();
+ _editContext = manager.CreateEditContext();
+
+ // For demonstration purposes, this sample sets the Input Pane display policy to Manual
+ // so that it can manually show the software keyboard when the control gains focus and
+ // dismiss it when the control loses focus. If you leave the policy as Automatic, then
+ // the system will hide and show the Input Pane for you. Note that on Desktop, you will
+ // need to implement the UIA text pattern to get expected automatic behavior.
+ _editContext.InputPaneDisplayPolicy(winrt::CoreTextInputPaneDisplayPolicy::Manual);
+
+ // Set the input scope to Text because this text box is for any text.
+ // This also informs software keyboards to show their regular
+ // text entry layout. There are many other input scopes and each will
+ // inform a keyboard layout and text behavior.
+ _editContext.InputScope(winrt::CoreTextInputScope::Text);
+
+ // The system raises this event to request a specific range of text.
+ _tokenTextRequested = _editContext.TextRequested({ this, &CustomEditControl::EditContext_TextRequested });
+
+ // The system raises this event to request the current selection.
+ _tokenSelectionRequested = _editContext.SelectionRequested({ this, &CustomEditControl::EditContext_SelectionRequested });
+
+ // The system raises this event when it wants the edit control to remove focus.
+ _tokenFocusRemoved = _editContext.FocusRemoved({ this, &CustomEditControl::EditContext_FocusRemoved });
+
+ // The system raises this event to update text in the edit control.
+ _tokenTextUpdating = _editContext.TextUpdating({ this, &CustomEditControl::EditContext_TextUpdating });
+
+ // The system raises this event to change the selection in the edit control.
+ _tokenSelectionUpdating = _editContext.SelectionUpdating({ this, &CustomEditControl::EditContext_SelectionUpdating });
+
+ // The system raises this event when it wants the edit control
+ // to apply formatting on a range of text.
+ _tokenFormatUpdating = _editContext.FormatUpdating({ this, &CustomEditControl::EditContext_FormatUpdating });
+
+ // The system raises this event to request layout information.
+ // This is used to help choose a position for the IME candidate window.
+ _tokenLayoutRequested = _editContext.LayoutRequested({ this, &CustomEditControl::EditContext_LayoutRequested });
+
+ // The system raises this event to notify the edit control
+ // that the string composition has started.
+ _tokenCompositionStarted = _editContext.CompositionStarted({ this, &CustomEditControl::EditContext_CompositionStarted });
+
+ // The system raises this event to notify the edit control
+ // that the string composition is finished.
+ _tokenCompositionCompleted = _editContext.CompositionCompleted({ this, &CustomEditControl::EditContext_CompositionCompleted });
+
+ // The system raises this event when the NotifyFocusLeave operation has
+ // completed. Our sample does not use this event.
+ // _editContext.NotifyFocusLeaveCompleted += EditContext_NotifyFocusLeaveCompleted;
+
+ // Set our initial UI.
+ UpdateTextUI();
+ UpdateFocusUI();
+ }
+
+ CustomEditControl::~CustomEditControl()
+ {
+ RemoveInternalFocus();
+ }
+
+ static winrt::Rect GetElementRect(winrt::FrameworkElement element)
+ {
+ winrt::GeneralTransform transform = element.TransformToVisual(nullptr);
+ winrt::Point point = transform.TransformPoint(winrt::Point());
+ return winrt::Rect(point, winrt::Size((float)element.ActualWidth(), (float)element.ActualHeight()));
+ }
+
+#pragma region Focus_management
+
+ void CustomEditControl::OnGotFocus(winrt::RoutedEventArgs args)
+ {
+ SetInternalFocus();
+ }
+
+ void CustomEditControl::OnLostFocus(winrt::RoutedEventArgs args)
+ {
+ RemoveInternalFocus();
+ }
+
+ void CustomEditControl::SetAppWindow(winrt::Microsoft::UI::WindowId windowId)
+ {
+ _appWindowId = windowId;
+
+ HWND hwnd = GetWindowFromWindowId(windowId);
+ if (hwnd)
+ {
+ // Get the Input Pane so we can programmatically hide and show it.
+ _inputPane = wil::capture_interop(&IInputPaneInterop::GetForWindow, hwnd);
+ }
+ }
+
+ void CustomEditControl::SetInternalFocus()
+ {
+ if (!_internalFocus)
+ {
+ // Update internal notion of focus.
+ _internalFocus = true;
+
+ // Update the UI.
+ UpdateTextUI();
+ UpdateFocusUI();
+
+ // Notify the CoreTextEditContext that the edit context has focus,
+ // so it should start processing text input.
+ _editContext.NotifyFocusEnter();
+ }
+
+ // Ask the software keyboard to show. The system will ultimately decide if it will show.
+ // For example, it will not show if there is a keyboard attached.
+ _inputPane.TryShow();
+ }
+
+ void CustomEditControl::RemoveInternalFocus()
+ {
+ if (_internalFocus)
+ {
+ //Notify the system that this edit context is no longer in focus
+ _editContext.NotifyFocusLeave();
+
+ RemoveInternalFocusWorker();
+ }
+ }
+
+ void CustomEditControl::RemoveInternalFocusWorker()
+ {
+ // Update the internal notion of focus
+ _internalFocus = false;
+
+ // Ask the software keyboard to dismiss.
+ _inputPane.TryHide();
+
+ // Update our UI.
+ UpdateTextUI();
+ UpdateFocusUI();
+ }
+
+ void CustomEditControl::EditContext_FocusRemoved(CoreTextEditContext sender, winrt::IInspectable args)
+ {
+ RemoveInternalFocusWorker();
+ }
+
+#pragma endregion Focus_management
+
+#pragma region Text_management
+
+ // Replace the text in the specified range.
+ void CustomEditControl::ReplaceText(winrt::CoreTextRange modifiedRange, winrt::hstring text)
+ {
+ // Modify the internal text store.
+ _text = _text.substr(0, modifiedRange.StartCaretPosition) +
+ text +
+ _text.substr(modifiedRange.EndCaretPosition);
+
+ // Move the caret to the end of the replacement text.
+ _selection.StartCaretPosition = modifiedRange.StartCaretPosition + text.size();
+ _selection.EndCaretPosition = _selection.StartCaretPosition;
+
+ // Update the selection of the edit context. There is no need to notify the system
+ // of the selection change because we are going to call NotifyTextChanged soon.
+ SetSelection(_selection);
+
+ // Let the CoreTextEditContext know what changed.
+ _editContext.NotifyTextChanged(modifiedRange, text.size(), _selection);
+ }
+
+ bool CustomEditControl::HasSelection()
+ {
+ return _selection.StartCaretPosition != _selection.EndCaretPosition;
+ }
+
+ // Change the selection without notifying CoreTextEditContext of the new selection.
+ void CustomEditControl::SetSelection(winrt::CoreTextRange selection)
+ {
+ // Modify the internal selection.
+ _selection = selection;
+
+ // Update the UI to show the new selection.
+ UpdateTextUI();
+ }
+
+ // Change the selection and notify CoreTextEditContext of the new selection.
+ void CustomEditControl::SetSelectionAndNotify(winrt::CoreTextRange selection)
+ {
+ SetSelection(selection);
+ _editContext.NotifySelectionChanged(_selection);
+ }
+
+ // Return the specified range of text. Note that the system may ask for more text
+ // than exists in the text buffer.
+ void CustomEditControl::EditContext_TextRequested(winrt::CoreTextEditContext sender, winrt::CoreTextTextRequestedEventArgs args)
+ {
+ winrt::CoreTextTextRequest request = args.Request();
+ request.Text(winrt::hstring(_text.substr(
+ request.Range().StartCaretPosition,
+ min(request.Range().EndCaretPosition, static_cast(_text.size())) - request.Range().StartCaretPosition)));
+ }
+
+ // Return the current selection.
+ void CustomEditControl::EditContext_SelectionRequested(winrt::CoreTextEditContext sender, winrt::CoreTextSelectionRequestedEventArgs args)
+ {
+ winrt::CoreTextSelectionRequest request = args.Request();
+ request.Selection(_selection);
+ }
+
+ void CustomEditControl::EditContext_TextUpdating(winrt::CoreTextEditContext sender, winrt::CoreTextTextUpdatingEventArgs args)
+ {
+ winrt::CoreTextRange range = args.Range();
+ winrt::hstring newText = args.Text();
+ winrt::CoreTextRange newSelection = args.NewSelection();
+
+ // Modify the internal text store.
+ _text = _text.substr(0, range.StartCaretPosition) +
+ newText +
+ _text.substr(min(static_cast(_text.size()), range.EndCaretPosition));
+
+ // You can set the proper font or direction for the updated text based on the language by checking
+ // args.InputLanguage. We will not do that in this sample.
+
+ // Modify the current selection.
+ newSelection.EndCaretPosition = newSelection.StartCaretPosition;
+
+ // Update the selection of the edit context. There is no need to notify the system
+ // because the system itself changed the selection.
+ SetSelection(newSelection);
+ }
+
+ void CustomEditControl::EditContext_SelectionUpdating(winrt::CoreTextEditContext sender, winrt::CoreTextSelectionUpdatingEventArgs args)
+ {
+ // Set the new selection to the value specified by the system.
+ CoreTextRange range = args.Selection();
+
+ // Update the selection of the edit context. There is no need to notify the system
+ // because the system itself changed the selection.
+ SetSelection(range);
+ }
+
+#pragma endregion Text_management
+
+#pragma region Formatting_and_layout
+
+ void CustomEditControl::EditContext_FormatUpdating(winrt::CoreTextEditContext sender, winrt::CoreTextFormatUpdatingEventArgs args)
+ {
+ // The following code specifies how you would apply any formatting to the specified range of text
+ // For this sample, we do not make any changes to the format.
+
+ // Apply text color if specified.
+ // A null value indicates that the default should be used.
+ if (args.TextColor() != nullptr)
+ {
+ // InternalSetTextColor(args.Range(), args.TextColor().Value());
+ }
+ else
+ {
+ // InternalSetDefaultTextColor(args.Range());
+ }
+
+ // Apply background color if specified.
+ // A null value indicates that the default should be used.
+ if (args.BackgroundColor() != nullptr)
+ {
+ // InternalSetBackgroundColor(args.Range(), args.BackgroundColor().Value());
+ }
+ else
+ {
+ // InternalSetDefaultBackgroundColor(args.Range());
+ }
+
+ // Apply underline if specified.
+ // A null value indicates that the default should be used.
+ if (args.UnderlineType() != nullptr)
+ {
+ // TextDecoration underline = new TextDecoration(args.Range(), args.UnderlineType.Value(), args.UnderlineColor.Value());
+
+ // InternalAddTextDecoration(underline);
+ }
+ else
+ {
+ // InternalRemoveTextDecoration(args.Range());
+ }
+ }
+
+ static Rect ScaleRect(Rect rect, float scale)
+ {
+ rect.X *= scale;
+ rect.Y *= scale;
+ rect.Width *= scale;
+ rect.Height *= scale;
+ return rect;
+ }
+
+ void CustomEditControl::EditContext_LayoutRequested(winrt::CoreTextEditContext sender, winrt::CoreTextLayoutRequestedEventArgs args)
+ {
+ winrt::CoreTextLayoutRequest request = args.Request();
+
+ // Get the screen coordinates of the entire control and the selected text.
+ // This information is used to position the IME candidate window.
+
+ // First, get the coordinates of the edit control and the selection
+ // relative to the Window.
+
+ Rect contentRect = GetElementRect(ContentPanel());
+ Rect selectionRect = GetElementRect(SelectionText());
+
+ // Next, convert to screen coordinates in view pixels.
+ winrt::AppWindow appWindow = winrt::AppWindow::GetFromWindowId(_appWindowId);
+ contentRect.X += appWindow.Position().X;
+ contentRect.Y += appWindow.Position().Y;
+ selectionRect.X += appWindow.Position().X;
+ selectionRect.Y += appWindow.Position().Y;
+
+ // Finally, scale up to raw pixels.
+ float scaleFactor = static_cast(XamlRoot().RasterizationScale());
+
+ contentRect = ScaleRect(contentRect, scaleFactor);
+ selectionRect = ScaleRect(selectionRect, scaleFactor);
+
+ // This is the bounds of the selection.
+ // Note: If you return bounds with 0 width and 0 height, candidates will not appear while typing.
+ request.LayoutBounds().TextBounds(selectionRect);
+
+ // This is the bounds of the whole control
+ request.LayoutBounds().ControlBounds(contentRect);
+ }
+
+#pragma endregion Formatting_and_layout
+
+#pragma region Input
+
+ // This indicates that an IME has started composition. If there is no handler for this event,
+ // then composition will not start.
+ void CustomEditControl::EditContext_CompositionStarted(winrt::CoreTextEditContext sender, winrt::CoreTextCompositionStartedEventArgs args)
+ {
+ }
+
+ void CustomEditControl::EditContext_CompositionCompleted(winrt::CoreTextEditContext sender, winrt::CoreTextCompositionCompletedEventArgs args)
+ {
+ }
+
+ void CustomEditControl::OnCharacterReceived(winrt::Microsoft::UI::Xaml::Input::CharacterReceivedRoutedEventArgs args)
+ {
+ // Do not process keyboard input if the custom edit control does not
+ // have focus.
+ if (!_internalFocus)
+ {
+ return;
+ }
+
+ if (isprint(args.Character()))
+ {
+ ReplaceText(_selection, winrt::to_hstring(args.Character()));
+ UpdateTextUI();
+ }
+ }
+
+ void CustomEditControl::OnKeyDown(winrt::KeyRoutedEventArgs args)
+ {
+ // Do not process keyboard input if the custom edit control does not
+ // have focus.
+ if (!_internalFocus)
+ {
+ return;
+ }
+
+ // This holds the range we intend to operate on, or which we intend
+ // to become the new selection. Start with the current selection.
+ winrt::CoreTextRange range = _selection;
+
+ // For the purpose of this sample, we will support only the left and right
+ // arrow keys and the backspace key. A more complete text edit control
+ // would also handle keys like Home, End, and Delete, as well as
+ // hotkeys like Ctrl+V to paste.
+ //
+ // Note that this sample does not properly handle surrogate pairs
+ // nor does it handle grapheme clusters.
+
+ switch (args.Key())
+ {
+ // Backspace
+ case winrt::VirtualKey::Back:
+ // If there is a selection, then delete the selection.
+ if (HasSelection())
+ {
+ // Set the text in the selection to nothing.
+ ReplaceText(range, L"");
+ }
+ else
+ {
+ // Delete the character to the left of the caret, if one exists,
+ // by creating a range that encloses the character to the left
+ // of the caret, and setting the contents of that range to nothing.
+ range.StartCaretPosition = max(0, range.StartCaretPosition - 1);
+ ReplaceText(range, L"");
+ }
+ break;
+
+ // Left arrow
+ case winrt::VirtualKey::Left:
+
+ // If the shift key is down, then adjust the size of the selection.
+ if (WI_IsFlagSet(winrt::InputKeyboardSource::GetKeyStateForCurrentThread(winrt::VirtualKey::Shift), winrt::CoreVirtualKeyStates::Down))
+ {
+ // If this is the start of a selection, then remember which edge we are adjusting.
+ if (!HasSelection())
+ {
+ _extendingLeft = true;
+ }
+
+ // Adjust the selection and notify CoreTextEditContext.
+ AdjustSelectionEndpoint(-1);
+ }
+ else
+ {
+ // The shift key is not down. If there was a selection, then snap the
+ // caret at the left edge of the selection.
+ if (HasSelection())
+ {
+ range.EndCaretPosition = range.StartCaretPosition;
+ SetSelectionAndNotify(range);
+ }
+ else
+ {
+ // There was no selection. Move the caret left one code unit if possible.
+ range.StartCaretPosition = max(0, range.StartCaretPosition - 1);
+ range.EndCaretPosition = range.StartCaretPosition;
+ SetSelectionAndNotify(range);
+ }
+ }
+ break;
+
+ // Right arrow
+ case winrt::VirtualKey::Right:
+
+ // If the shift key is down, then adjust the size of the selection.
+ if (WI_IsFlagSet(winrt::InputKeyboardSource::GetKeyStateForCurrentThread(winrt::VirtualKey::Shift), winrt::CoreVirtualKeyStates::Down))
+ {
+ // If this is the start of a selection, then remember which edge we are adjusting.
+ if (!HasSelection())
+ {
+ _extendingLeft = false;
+ }
+
+ // Adjust the selection and notify CoreTextEditContext.
+ AdjustSelectionEndpoint(1);
+ }
+ else
+ {
+ // The shift key is not down. If there was a selection, then snap the
+ // caret at the right edge of the selection.
+ if (HasSelection())
+ {
+ range.StartCaretPosition = range.EndCaretPosition;
+ SetSelectionAndNotify(range);
+ }
+ else
+ {
+ // There was no selection. Move the caret right one code unit if possible.
+ range.StartCaretPosition = min(static_cast(_text.length()), range.StartCaretPosition + 1);
+ range.EndCaretPosition = range.StartCaretPosition;
+ SetSelectionAndNotify(range);
+ }
+ }
+ break;
+ }
+ }
+
+ // Adjust the active endpoint of the selection in the specified direction.
+ void CustomEditControl::AdjustSelectionEndpoint(int direction)
+ {
+ CoreTextRange range = _selection;
+ if (_extendingLeft)
+ {
+ range.StartCaretPosition = max(0, range.StartCaretPosition + direction);
+ }
+ else
+ {
+ range.EndCaretPosition = min(static_cast(_text.length()), range.EndCaretPosition + direction);
+ }
+
+ SetSelectionAndNotify(range);
+ }
+
+#pragma endregion Input
+
+#pragma region UI
+
+ // Helper function to put a zero-width non-breaking space at the end of a string.
+ // This prevents TextBlock from trimming trailing spaces.
+ static winrt::hstring PreserveTrailingSpaces(winrt::hstring s)
+ {
+ return s + L"\ufeff";
+ }
+
+ void CustomEditControl::UpdateFocusUI()
+ {
+ BorderPanel().BorderBrush(_internalFocus ? winrt::SolidColorBrush(winrt::Colors::Green()) : nullptr);
+ }
+
+ void CustomEditControl::UpdateTextUI()
+ {
+ // The raw materials we have are a string (_text) and information about
+ // where the caret/selection is (_selection). We can render the control
+ // any way we like.
+
+ BeforeSelectionText().Text(PreserveTrailingSpaces(winrt::hstring(_text.substr(0, _selection.StartCaretPosition))));
+
+ if (HasSelection())
+ {
+ // There is a selection. Draw that.
+ CaretText().Visibility(winrt::Visibility::Collapsed);
+ SelectionText().Text(PreserveTrailingSpaces(
+ winrt::hstring(_text.substr(_selection.StartCaretPosition, _selection.EndCaretPosition - _selection.StartCaretPosition))));
+ }
+ else
+ {
+ // There is no selection. Remove it.
+ SelectionText().Text(L"");
+
+ // Draw the caret if we have focus.
+ CaretText().Visibility(_internalFocus ? winrt::Visibility::Visible : winrt::Visibility::Collapsed);
+ }
+
+ AfterSelectionText().Text(PreserveTrailingSpaces(winrt::hstring(_text.substr(_selection.EndCaretPosition))));
+
+ // Update statistics for demonstration purposes.
+ FullText().Text(_text);
+ SelectionStartIndexText().Text(winrt::to_hstring(_selection.StartCaretPosition));
+ SelectionEndIndexText().Text(winrt::to_hstring(_selection.EndCaretPosition));
+ }
+
+#pragma endregion UI
+}
diff --git a/Samples/CustomEditControl/cpp-winui/CustomEditControl.xaml.h b/Samples/CustomEditControl/cpp-winui/CustomEditControl.xaml.h
new file mode 100644
index 000000000..c48008b22
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/CustomEditControl.xaml.h
@@ -0,0 +1,115 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#pragma once
+
+#include "winrt/Microsoft.UI.Xaml.h"
+#include "winrt/Microsoft.UI.Xaml.Markup.h"
+#include "winrt/Microsoft.UI.Xaml.Controls.Primitives.h"
+#include "CustomEditControl.g.h"
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml::Input;
+
+namespace winrt::CustomEditControlCpp::implementation
+{
+ struct CustomEditControl : CustomEditControlT
+ {
+ CustomEditControl();
+ ~CustomEditControl();
+
+ void OnKeyDown(KeyRoutedEventArgs args);
+ void OnCharacterReceived(CharacterReceivedRoutedEventArgs args);
+
+ void OnGotFocus(winrt::Microsoft::UI::Xaml::RoutedEventArgs args);
+ void OnLostFocus(winrt::Microsoft::UI::Xaml::RoutedEventArgs args);
+
+ void SetAppWindow(winrt::Microsoft::UI::WindowId windowId);
+
+ void EditContext_TextRequested(
+ winrt::Windows::UI::Text::Core::CoreTextEditContext sender,
+ winrt::Windows::UI::Text::Core::CoreTextTextRequestedEventArgs args);
+ void EditContext_SelectionRequested(
+ winrt::Windows::UI::Text::Core::CoreTextEditContext sender,
+ winrt::Windows::UI::Text::Core::CoreTextSelectionRequestedEventArgs args);
+ void EditContext_TextUpdating(
+ winrt::Windows::UI::Text::Core::CoreTextEditContext sender,
+ winrt::Windows::UI::Text::Core::CoreTextTextUpdatingEventArgs args);
+ void EditContext_SelectionUpdating(
+ winrt::Windows::UI::Text::Core::CoreTextEditContext sender,
+ winrt::Windows::UI::Text::Core::CoreTextSelectionUpdatingEventArgs args);
+ void EditContext_FocusRemoved(
+ winrt::Windows::UI::Text::Core::CoreTextEditContext sender,
+ winrt::Windows::Foundation::IInspectable args);
+ void EditContext_FormatUpdating(
+ winrt::Windows::UI::Text::Core::CoreTextEditContext sender,
+ winrt::Windows::UI::Text::Core::CoreTextFormatUpdatingEventArgs args);
+ void EditContext_LayoutRequested(
+ winrt::Windows::UI::Text::Core::CoreTextEditContext sender,
+ winrt::Windows::UI::Text::Core::CoreTextLayoutRequestedEventArgs args);
+ void EditContext_CompositionStarted(
+ winrt::Windows::UI::Text::Core::CoreTextEditContext sender,
+ winrt::Windows::UI::Text::Core::CoreTextCompositionStartedEventArgs args);
+ void EditContext_CompositionCompleted(
+ winrt::Windows::UI::Text::Core::CoreTextEditContext sender,
+ winrt::Windows::UI::Text::Core::CoreTextCompositionCompletedEventArgs args);
+
+ void UpdateTextUI();
+ void UpdateFocusUI();
+
+ void SetInternalFocus();
+ void RemoveInternalFocus();
+ void RemoveInternalFocusWorker();
+
+ void ReplaceText(winrt::Windows::UI::Text::Core::CoreTextRange modifiedRange, winrt::hstring text);
+ bool HasSelection();
+ void SetSelection(winrt::Windows::UI::Text::Core::CoreTextRange selection);
+ void SetSelectionAndNotify(winrt::Windows::UI::Text::Core::CoreTextRange selection);
+
+ void AdjustSelectionEndpoint(int direction);
+
+ // The _editContext lets us communicate with the input system.
+ winrt::Windows::UI::Text::Core::CoreTextEditContext _editContext{ nullptr };
+
+ // We will use a plain text string to represent the
+ // content of the custom text edit control.
+ std::wstring _text;
+
+ // If the _selection starts and ends at the same point,
+ // then it represents the location of the caret (insertion point).
+ winrt::Windows::UI::Text::Core::CoreTextRange _selection = {};
+
+ // _internalFocus keeps track of whether our control acts like it has focus.
+ bool _internalFocus = false;
+
+ // If there is a nonempty selection, then _extendingLeft is true if the user
+ // is using shift+arrow to adjust the starting point of the selection,
+ // or false if the user is adjusting the ending point of the selection.
+ bool _extendingLeft = false;
+
+ // The input pane object indicates the visibility of the on screen keyboard.
+ // Apps can also ask the keyboard to show or hide.
+ winrt::Windows::UI::ViewManagement::InputPane _inputPane{ nullptr };
+
+ // The AppWindow that contains our control.
+ winrt::Microsoft::UI::WindowId _appWindowId = {};
+
+ winrt::event_token _tokenTextRequested;
+ winrt::event_token _tokenSelectionRequested;
+ winrt::event_token _tokenFocusRemoved;
+ winrt::event_token _tokenTextUpdating;
+ winrt::event_token _tokenSelectionUpdating;
+ winrt::event_token _tokenFormatUpdating;
+ winrt::event_token _tokenLayoutRequested;
+ winrt::event_token _tokenCompositionStarted;
+ winrt::event_token _tokenCompositionCompleted;
+ winrt::event_token _tokenNotifyFocusLeaveCompleted;
+ };
+}
+
+namespace winrt::CustomEditControlCpp::factory_implementation
+{
+ struct CustomEditControl : CustomEditControlT
+ {
+ };
+}
diff --git a/Samples/CustomEditControl/cpp-winui/CustomEditControlCpp.sln b/Samples/CustomEditControl/cpp-winui/CustomEditControlCpp.sln
new file mode 100644
index 000000000..dcd18c0a6
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/CustomEditControlCpp.sln
@@ -0,0 +1,43 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.32002.261
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CustomEditControlCpp", "CustomEditControlCpp.vcxproj", "{4F349AF3-4A2D-439C-BB5E-5E899BD234CA}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|arm64 = Debug|arm64
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|arm64 = Release|arm64
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {4F349AF3-4A2D-439C-BB5E-5E899BD234CA}.Debug|arm64.ActiveCfg = Debug|arm64
+ {4F349AF3-4A2D-439C-BB5E-5E899BD234CA}.Debug|arm64.Build.0 = Debug|arm64
+ {4F349AF3-4A2D-439C-BB5E-5E899BD234CA}.Debug|arm64.Deploy.0 = Debug|arm64
+ {4F349AF3-4A2D-439C-BB5E-5E899BD234CA}.Debug|x64.ActiveCfg = Debug|x64
+ {4F349AF3-4A2D-439C-BB5E-5E899BD234CA}.Debug|x64.Build.0 = Debug|x64
+ {4F349AF3-4A2D-439C-BB5E-5E899BD234CA}.Debug|x64.Deploy.0 = Debug|x64
+ {4F349AF3-4A2D-439C-BB5E-5E899BD234CA}.Debug|x86.ActiveCfg = Debug|Win32
+ {4F349AF3-4A2D-439C-BB5E-5E899BD234CA}.Debug|x86.Build.0 = Debug|Win32
+ {4F349AF3-4A2D-439C-BB5E-5E899BD234CA}.Debug|x86.Deploy.0 = Debug|Win32
+ {4F349AF3-4A2D-439C-BB5E-5E899BD234CA}.Release|arm64.ActiveCfg = Release|arm64
+ {4F349AF3-4A2D-439C-BB5E-5E899BD234CA}.Release|arm64.Build.0 = Release|arm64
+ {4F349AF3-4A2D-439C-BB5E-5E899BD234CA}.Release|arm64.Deploy.0 = Release|arm64
+ {4F349AF3-4A2D-439C-BB5E-5E899BD234CA}.Release|x64.ActiveCfg = Release|x64
+ {4F349AF3-4A2D-439C-BB5E-5E899BD234CA}.Release|x64.Build.0 = Release|x64
+ {4F349AF3-4A2D-439C-BB5E-5E899BD234CA}.Release|x64.Deploy.0 = Release|x64
+ {4F349AF3-4A2D-439C-BB5E-5E899BD234CA}.Release|x86.ActiveCfg = Release|Win32
+ {4F349AF3-4A2D-439C-BB5E-5E899BD234CA}.Release|x86.Build.0 = Release|Win32
+ {4F349AF3-4A2D-439C-BB5E-5E899BD234CA}.Release|x86.Deploy.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {D96337A7-121D-449D-B033-DEBE189016BC}
+ EndGlobalSection
+EndGlobal
diff --git a/Samples/CustomEditControl/cpp-winui/CustomEditControlCpp.vcxproj b/Samples/CustomEditControl/cpp-winui/CustomEditControlCpp.vcxproj
new file mode 100644
index 000000000..72d9b3858
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/CustomEditControlCpp.vcxproj
@@ -0,0 +1,191 @@
+
+
+
+
+
+
+ true
+ true
+ true
+ {4f349af3-4a2d-439c-bb5e-5e899bd234ca}
+ CustomEditControlCpp
+ CustomEditControlCpp
+
+ $(RootNamespace)
+ en-US
+ 16.0
+ false
+ true
+ Windows Store
+ 10.0
+ 10.0
+ 10.0.17763.0
+ true
+ true
+
+
+
+
+ Debug
+ Win32
+
+
+ Debug
+ x64
+
+
+ Debug
+ arm64
+
+
+ Release
+ Win32
+
+
+ Release
+ x64
+
+
+ Release
+ arm64
+
+
+
+ Application
+ v143
+ Unicode
+ true
+
+
+ true
+ true
+
+
+ false
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+ Use
+ pch.h
+ $(IntDir)pch.pch
+ Level4
+ %(AdditionalOptions) /bigobj
+
+
+
+
+ _DEBUG;%(PreprocessorDefinitions)
+
+
+
+
+ NDEBUG;%(PreprocessorDefinitions)
+
+
+ true
+ true
+
+
+
+
+
+
+
+ CustomEditControl.xaml
+ Code
+
+
+
+ App.xaml
+
+
+ MainWindow.xaml
+
+
+
+
+
+ Designer
+
+
+
+
+
+ CustomEditControl.xaml
+ Code
+
+
+ Create
+
+
+ App.xaml
+
+
+ MainWindow.xaml
+
+
+
+
+
+ Code
+ App.xaml
+
+
+ CustomEditControl.xaml
+ Code
+
+
+ Code
+ MainWindow.xaml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Samples/CustomEditControl/cpp-winui/CustomEditControlCpp.vcxproj.filters b/Samples/CustomEditControl/cpp-winui/CustomEditControlCpp.vcxproj.filters
new file mode 100644
index 000000000..2f8eb131d
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/CustomEditControlCpp.vcxproj.filters
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Assets
+
+
+ Assets
+
+
+ Assets
+
+
+ Assets
+
+
+ Assets
+
+
+ Assets
+
+
+ Assets
+
+
+
+
+ {4f349af3-4a2d-439c-bb5e-5e899bd234ca}
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Samples/CustomEditControl/cpp-winui/MainWindow.idl b/Samples/CustomEditControl/cpp-winui/MainWindow.idl
new file mode 100644
index 000000000..0ac098425
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/MainWindow.idl
@@ -0,0 +1,11 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace CustomEditControlCpp
+{
+ [default_interface]
+ runtimeclass MainWindow : Microsoft.UI.Xaml.Window
+ {
+ MainWindow();
+ }
+}
diff --git a/Samples/CustomEditControl/cpp-winui/MainWindow.xaml b/Samples/CustomEditControl/cpp-winui/MainWindow.xaml
new file mode 100644
index 000000000..0bb594581
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/MainWindow.xaml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+ Text input
+
+
+ When the custom text control has focus, it draws with a green border.
+ The caret is represented by a globe.
+ See the README for this sample for further instructions.
+
+
+
+
+
+
+
diff --git a/Samples/CustomEditControl/cpp-winui/MainWindow.xaml.cpp b/Samples/CustomEditControl/cpp-winui/MainWindow.xaml.cpp
new file mode 100644
index 000000000..c093bd9ab
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/MainWindow.xaml.cpp
@@ -0,0 +1,79 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#include "pch.h"
+#include "MainWindow.xaml.h"
+#if __has_include("MainWindow.g.cpp")
+#include "MainWindow.g.cpp"
+#endif
+
+// This include file is needed for the XAML Native Window Interop.
+#include "microsoft.ui.xaml.window.h"
+
+namespace winrt
+{
+ using namespace ::winrt::Microsoft::UI;
+ using namespace ::winrt::Microsoft::UI::Windowing;
+ using namespace ::winrt::Microsoft::UI::Xaml;
+ using namespace ::winrt::Microsoft::UI::Xaml::Input;
+ using namespace ::winrt::Microsoft::UI::Xaml::Media;
+ using namespace ::winrt::Windows::Foundation;
+}
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace winrt::CustomEditControlCpp::implementation
+{
+ MainWindow::MainWindow()
+ {
+ InitializeComponent();
+
+ MyCustomEditControl().SetAppWindow(GetAppWindowId());
+
+ Content().PointerPressed({ this, &MainWindow::PointerPressed });
+ }
+
+ winrt::WindowId MainWindow::GetAppWindowId()
+ {
+ winrt::AppWindow appWindow = nullptr;
+
+ // Get the HWND for the XAML Window
+ HWND hWnd;
+ winrt::Window window = this->try_as();
+ window.as()->get_WindowHandle(&hWnd);
+
+ // Get the WindowId for the HWND
+ return GetWindowIdFromWindow(hWnd);
+ }
+
+ static winrt::Rect GetElementRect(winrt::UIElement element)
+ {
+ winrt::GeneralTransform transform = element.TransformToVisual(nullptr);
+ winrt::Point point = transform.TransformPoint(winrt::Point());
+ return winrt::Rect(point, element.ActualSize());
+ }
+
+ void MainWindow::PointerPressed(const winrt::IInspectable& /*sender*/, const winrt::PointerRoutedEventArgs& args)
+ {
+ winrt::Rect contentRect = GetElementRect(MyCustomEditControl());
+ if (winrt::RectHelper::Contains(contentRect, args.GetCurrentPoint(nullptr).Position()))
+ {
+ // The user tapped inside the control. Give it focus.
+ MyCustomEditControl().SetInternalFocus();
+
+ // Tell XAML that our custom element has focus, so we don't have two
+ // focus elements. That is the extent of our integration with XAML focus.
+ MyCustomEditControl().Focus(winrt::FocusState::Programmatic);
+
+ // A more complete custom control would move the caret to the
+ // pointer position. It would also provide some way to select
+ // text via touch. We do neither in this sample.
+ }
+ else
+ {
+ // The user tapped outside the control. Remove focus.
+ MyCustomEditControl().RemoveInternalFocus();
+ }
+ }
+}
diff --git a/Samples/CustomEditControl/cpp-winui/MainWindow.xaml.h b/Samples/CustomEditControl/cpp-winui/MainWindow.xaml.h
new file mode 100644
index 000000000..89a9bebf2
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/MainWindow.xaml.h
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#pragma once
+
+#include "MainWindow.g.h"
+
+namespace winrt::CustomEditControlCpp::implementation
+{
+ struct MainWindow : MainWindowT
+ {
+ MainWindow();
+ void PointerPressed(const winrt::Windows::Foundation::IInspectable& /*sender*/, const winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs& /*args*/);
+ winrt::Microsoft::UI::WindowId GetAppWindowId();
+ };
+}
+
+namespace winrt::CustomEditControlCpp::factory_implementation
+{
+ struct MainWindow : MainWindowT
+ {
+ };
+}
diff --git a/Samples/CustomEditControl/cpp-winui/Package.appxmanifest b/Samples/CustomEditControl/cpp-winui/Package.appxmanifest
new file mode 100644
index 000000000..63ac24b2a
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/Package.appxmanifest
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+ CustomEditControlCpp
+ Microsoft Corporation
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Samples/CustomEditControl/cpp-winui/app.manifest b/Samples/CustomEditControl/cpp-winui/app.manifest
new file mode 100644
index 000000000..a2b4b53fe
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/app.manifest
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+ true/PM
+ PerMonitorV2, PerMonitor
+
+
+
diff --git a/Samples/CustomEditControl/cpp-winui/packages.config b/Samples/CustomEditControl/cpp-winui/packages.config
new file mode 100644
index 000000000..eb2857482
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/packages.config
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Samples/CustomEditControl/cpp-winui/pch.cpp b/Samples/CustomEditControl/cpp-winui/pch.cpp
new file mode 100644
index 000000000..40e691ba7
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/pch.cpp
@@ -0,0 +1,4 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#include "pch.h"
diff --git a/Samples/CustomEditControl/cpp-winui/pch.h b/Samples/CustomEditControl/cpp-winui/pch.h
new file mode 100644
index 000000000..4c873aab6
--- /dev/null
+++ b/Samples/CustomEditControl/cpp-winui/pch.h
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#pragma once
+#include
+#include
+#include
+#include
+
+// Undefine GetCurrentTime macro to prevent
+// conflict with Storyboard::GetCurrentTime
+#undef GetCurrentTime
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs.sln b/Samples/CustomEditControl/cs-winui/CustomEditControlCs.sln
new file mode 100644
index 000000000..89bb39149
--- /dev/null
+++ b/Samples/CustomEditControl/cs-winui/CustomEditControlCs.sln
@@ -0,0 +1,43 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.32014.148
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomEditControlCs", "CustomEditControlCs\CustomEditControlCs.csproj", "{3318A3E2-990F-41F7-9D09-3D1CDDA260ED}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|arm64 = Debug|arm64
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|arm64 = Release|arm64
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {3318A3E2-990F-41F7-9D09-3D1CDDA260ED}.Debug|arm64.ActiveCfg = Debug|arm64
+ {3318A3E2-990F-41F7-9D09-3D1CDDA260ED}.Debug|arm64.Build.0 = Debug|arm64
+ {3318A3E2-990F-41F7-9D09-3D1CDDA260ED}.Debug|arm64.Deploy.0 = Debug|arm64
+ {3318A3E2-990F-41F7-9D09-3D1CDDA260ED}.Debug|x64.ActiveCfg = Debug|x64
+ {3318A3E2-990F-41F7-9D09-3D1CDDA260ED}.Debug|x64.Build.0 = Debug|x64
+ {3318A3E2-990F-41F7-9D09-3D1CDDA260ED}.Debug|x64.Deploy.0 = Debug|x64
+ {3318A3E2-990F-41F7-9D09-3D1CDDA260ED}.Debug|x86.ActiveCfg = Debug|x86
+ {3318A3E2-990F-41F7-9D09-3D1CDDA260ED}.Debug|x86.Build.0 = Debug|x86
+ {3318A3E2-990F-41F7-9D09-3D1CDDA260ED}.Debug|x86.Deploy.0 = Debug|x86
+ {3318A3E2-990F-41F7-9D09-3D1CDDA260ED}.Release|arm64.ActiveCfg = Release|arm64
+ {3318A3E2-990F-41F7-9D09-3D1CDDA260ED}.Release|arm64.Build.0 = Release|arm64
+ {3318A3E2-990F-41F7-9D09-3D1CDDA260ED}.Release|arm64.Deploy.0 = Release|arm64
+ {3318A3E2-990F-41F7-9D09-3D1CDDA260ED}.Release|x64.ActiveCfg = Release|x64
+ {3318A3E2-990F-41F7-9D09-3D1CDDA260ED}.Release|x64.Build.0 = Release|x64
+ {3318A3E2-990F-41F7-9D09-3D1CDDA260ED}.Release|x64.Deploy.0 = Release|x64
+ {3318A3E2-990F-41F7-9D09-3D1CDDA260ED}.Release|x86.ActiveCfg = Release|x86
+ {3318A3E2-990F-41F7-9D09-3D1CDDA260ED}.Release|x86.Build.0 = Release|x86
+ {3318A3E2-990F-41F7-9D09-3D1CDDA260ED}.Release|x86.Deploy.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {3846DF2F-35AB-47BE-BBC3-928BBE1159E7}
+ EndGlobalSection
+EndGlobal
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/App.xaml b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/App.xaml
new file mode 100644
index 000000000..26cd10f3c
--- /dev/null
+++ b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/App.xaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/App.xaml.cs b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/App.xaml.cs
new file mode 100644
index 000000000..492817bce
--- /dev/null
+++ b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/App.xaml.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.UI.Xaml;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace CustomEditControlCs
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ public partial class App : Application
+ {
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ }
+
+ ///
+ /// Invoked when the application is launched normally by the end user. Other entry points
+ /// will be used such as when the application is launched to open a specific file.
+ ///
+ /// Details about the launch request and process.
+ protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
+ {
+ m_window = new MainWindow();
+ m_window.Activate();
+ }
+
+ private Window m_window;
+ }
+}
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/LockScreenLogo.scale-200.png b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 000000000..7440f0d4b
Binary files /dev/null and b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/LockScreenLogo.scale-200.png differ
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/SplashScreen.scale-200.png b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/SplashScreen.scale-200.png
new file mode 100644
index 000000000..32f486a86
Binary files /dev/null and b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/SplashScreen.scale-200.png differ
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/Square150x150Logo.scale-200.png b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 000000000..53ee3777e
Binary files /dev/null and b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/Square150x150Logo.scale-200.png differ
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/Square44x44Logo.scale-200.png b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 000000000..f713bba67
Binary files /dev/null and b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/Square44x44Logo.scale-200.png differ
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 000000000..dc9f5bea0
Binary files /dev/null and b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/StoreLogo.png b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/StoreLogo.png
new file mode 100644
index 000000000..a4586f26b
Binary files /dev/null and b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/StoreLogo.png differ
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/Wide310x150Logo.scale-200.png b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 000000000..8b4a5d0dd
Binary files /dev/null and b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/CustomEditControl.xaml b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/CustomEditControl.xaml
new file mode 100644
index 000000000..485df412d
--- /dev/null
+++ b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/CustomEditControl.xaml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Full text:
+ Selection start index:
+ Selection end index:
+
+
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/CustomEditControl.xaml.cs b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/CustomEditControl.xaml.cs
new file mode 100644
index 000000000..c04224705
--- /dev/null
+++ b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/CustomEditControl.xaml.cs
@@ -0,0 +1,557 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.UI;
+using Microsoft.UI.Input;
+using Microsoft.UI.Windowing;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Input;
+using Microsoft.UI.Xaml.Media;
+using System;
+using Windows.Foundation;
+using Windows.System;
+using Windows.UI.Core;
+using Windows.UI.Text.Core;
+using Windows.UI.ViewManagement;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace CustomEditControlCs
+{
+ public sealed partial class CustomEditControl : UserControl
+ {
+ // The _editContext lets us communicate with the input system.
+ CoreTextEditContext _editContext;
+
+ // We will use a plain text string to represent the
+ // content of the custom text edit control.
+ string _text = String.Empty;
+
+ // If the _selection starts and ends at the same point,
+ // then it represents the location of the caret (insertion point).
+ CoreTextRange _selection;
+
+ // _internalFocus keeps track of whether our control acts like it has focus.
+ bool _internalFocus = false;
+
+ // If there is a nonempty selection, then _extendingLeft is true if the user
+ // is using shift+arrow to adjust the starting point of the selection,
+ // or false if the user is adjusting the ending point of the selection.
+ bool _extendingLeft = false;
+
+ // The input pane object indicates the visibility of the on screen keyboard.
+ // Apps can also ask the keyboard to show or hide.
+ InputPane _inputPane;
+
+ // The WindowId for the AppWindow that contains our control.
+ WindowId _windowId;
+
+ public CustomEditControl()
+ {
+ this.InitializeComponent();
+
+ // The CoreTextEditContext processes text input, but other keys are
+ // the app's responsibility.
+
+ // Create a CoreTextEditContext for our custom edit control.
+ CoreTextServicesManager manager = CoreTextServicesManager.GetForCurrentView();
+ _editContext = manager.CreateEditContext();
+
+ // For demonstration purposes, this sample sets the Input Pane display policy to Manual
+ // so that it can manually show the software keyboard when the control gains focus and
+ // dismiss it when the control loses focus. If you leave the policy as Automatic, then
+ // the system will hide and show the Input Pane for you. Note that on Desktop, you will
+ // need to implement the UIA text pattern to get expected automatic behavior.
+ _editContext.InputPaneDisplayPolicy = CoreTextInputPaneDisplayPolicy.Manual;
+
+ // Set the input scope to Text because this text box is for any text.
+ // This also informs software keyboards to show their regular
+ // text entry layout. There are many other input scopes and each will
+ // inform a keyboard layout and text behavior.
+ _editContext.InputScope = CoreTextInputScope.Text;
+
+ // The system raises this event to request a specific range of text.
+ _editContext.TextRequested += EditContext_TextRequested;
+
+ // The system raises this event to request the current selection.
+ _editContext.SelectionRequested += EditContext_SelectionRequested;
+
+ // The system raises this event when it wants the edit control to remove focus.
+ _editContext.FocusRemoved += EditContext_FocusRemoved;
+
+ // The system raises this event to update text in the edit control.
+ _editContext.TextUpdating += EditContext_TextUpdating;
+
+ // The system raises this event to change the selection in the edit control.
+ _editContext.SelectionUpdating += EditContext_SelectionUpdating;
+
+ // The system raises this event when it wants the edit control
+ // to apply formatting on a range of text.
+ _editContext.FormatUpdating += EditContext_FormatUpdating;
+
+ // The system raises this event to request layout information.
+ // This is used to help choose a position for the IME candidate window.
+ _editContext.LayoutRequested += EditContext_LayoutRequested;
+
+ // The system raises this event to notify the edit control
+ // that the string composition has started.
+ _editContext.CompositionStarted += EditContext_CompositionStarted;
+
+ // The system raises this event to notify the edit control
+ // that the string composition is finished.
+ _editContext.CompositionCompleted += EditContext_CompositionCompleted;
+
+ // The system raises this event when the NotifyFocusLeave operation has
+ // completed. Our sample does not use this event.
+ // _editContext.NotifyFocusLeaveCompleted += EditContext_NotifyFocusLeaveCompleted;
+
+ // Set our initial UI.
+ UpdateTextUI();
+ UpdateFocusUI();
+ }
+
+ public void SetAppWindow(WindowId windowId)
+ {
+ _windowId = windowId;
+
+ // Get the Input Pane so we can programmatically hide and show it.
+ IntPtr hWnd = Win32Interop.GetWindowFromWindowId(_windowId);
+ if (hWnd != IntPtr.Zero)
+ {
+ _inputPane = InputPaneInterop.GetForWindow(hWnd);
+ }
+ }
+
+ #region Focus management
+ public void SetInternalFocus()
+ {
+ if (!_internalFocus)
+ {
+ // Update internal notion of focus.
+ _internalFocus = true;
+
+ // Update the UI.
+ UpdateTextUI();
+ UpdateFocusUI();
+
+ // Notify the CoreTextEditContext that the edit context has focus,
+ // so it should start processing text input.
+ _editContext.NotifyFocusEnter();
+ }
+
+ // Ask the software keyboard to show. The system will ultimately decide if it will show.
+ // For example, it will not show if there is a keyboard attached.
+ _inputPane.TryShow();
+
+ }
+
+ public void RemoveInternalFocus()
+ {
+ if (_internalFocus)
+ {
+ //Notify the system that this edit context is no longer in focus
+ _editContext.NotifyFocusLeave();
+
+ RemoveInternalFocusWorker();
+ }
+ }
+
+ void RemoveInternalFocusWorker()
+ {
+ //Update the internal notion of focus
+ _internalFocus = false;
+
+ // Ask the software keyboard to dismiss.
+ _inputPane.TryHide();
+
+ // Update our UI.
+ UpdateTextUI();
+ UpdateFocusUI();
+ }
+
+ void EditContext_FocusRemoved(CoreTextEditContext sender, object args)
+ {
+ RemoveInternalFocusWorker();
+ }
+
+ #endregion
+
+ #region Text management
+ // Replace the text in the specified range.
+ void ReplaceText(CoreTextRange modifiedRange, string text)
+ {
+ // Modify the internal text store.
+ _text = _text.Substring(0, modifiedRange.StartCaretPosition) +
+ text +
+ _text.Substring(modifiedRange.EndCaretPosition);
+
+ // Move the caret to the end of the replacement text.
+ _selection.StartCaretPosition = modifiedRange.StartCaretPosition + text.Length;
+ _selection.EndCaretPosition = _selection.StartCaretPosition;
+
+ // Update the selection of the edit context. There is no need to notify the system
+ // of the selection change because we are going to call NotifyTextChanged soon.
+ SetSelection(_selection);
+
+ // Let the CoreTextEditContext know what changed.
+ _editContext.NotifyTextChanged(modifiedRange, text.Length, _selection);
+ }
+
+ bool HasSelection()
+ {
+ return _selection.StartCaretPosition != _selection.EndCaretPosition;
+ }
+
+ // Change the selection without notifying CoreTextEditContext of the new selection.
+ void SetSelection(CoreTextRange selection)
+ {
+ // Modify the internal selection.
+ _selection = selection;
+
+ //Update the UI to show the new selection.
+ UpdateTextUI();
+ }
+
+ // Change the selection and notify CoreTextEditContext of the new selection.
+ void SetSelectionAndNotify(CoreTextRange selection)
+ {
+ SetSelection(selection);
+ _editContext.NotifySelectionChanged(_selection);
+ }
+
+ // Return the specified range of text. Note that the system may ask for more text
+ // than exists in the text buffer.
+ void EditContext_TextRequested(CoreTextEditContext sender, CoreTextTextRequestedEventArgs args)
+ {
+ CoreTextTextRequest request = args.Request;
+ request.Text = _text.Substring(
+ request.Range.StartCaretPosition,
+ Math.Min(request.Range.EndCaretPosition, _text.Length) - request.Range.StartCaretPosition);
+ }
+
+ // Return the current selection.
+ void EditContext_SelectionRequested(CoreTextEditContext sender, CoreTextSelectionRequestedEventArgs args)
+ {
+ CoreTextSelectionRequest request = args.Request;
+ request.Selection = _selection;
+ }
+
+ void EditContext_TextUpdating(CoreTextEditContext sender, CoreTextTextUpdatingEventArgs args)
+ {
+ CoreTextRange range = args.Range;
+ string newText = args.Text;
+ CoreTextRange newSelection = args.NewSelection;
+
+ // Modify the internal text store.
+ _text = _text.Substring(0, range.StartCaretPosition) +
+ newText +
+ _text.Substring(Math.Min(_text.Length, range.EndCaretPosition));
+
+ // You can set the proper font or direction for the updated text based on the language by checking
+ // args.InputLanguage. We will not do that in this sample.
+
+ // Modify the current selection.
+ newSelection.EndCaretPosition = newSelection.StartCaretPosition;
+
+ // Update the selection of the edit context. There is no need to notify the system
+ // because the system itself changed the selection.
+ SetSelection(newSelection);
+ }
+
+ void EditContext_SelectionUpdating(CoreTextEditContext sender, CoreTextSelectionUpdatingEventArgs args)
+ {
+ // Set the new selection to the value specified by the system.
+ CoreTextRange range = args.Selection;
+
+ // Update the selection of the edit context. There is no need to notify the system
+ // because the system itself changed the selection.
+ SetSelection(range);
+ }
+ #endregion
+
+ #region Formatting and layout
+ void EditContext_FormatUpdating(CoreTextEditContext sender, CoreTextFormatUpdatingEventArgs args)
+ {
+ // The following code specifies how you would apply any formatting to the specified range of text
+ // For this sample, we do not make any changes to the format.
+
+ // Apply text color if specified.
+ // A null value indicates that the default should be used.
+ if (args.TextColor != null)
+ {
+ //InternalSetTextColor(args.Range, args.TextColor.Value);
+ }
+ else
+ {
+ //InternalSetDefaultTextColor(args.Range);
+ }
+
+ // Apply background color if specified.
+ // A null value indicates that the default should be used.
+ if (args.BackgroundColor != null)
+ {
+ //InternalSetBackgroundColor(args.Range, args.BackgroundColor.Value);
+ }
+ else
+ {
+ //InternalSetDefaultBackgroundColor(args.Range);
+ }
+
+ // Apply underline if specified.
+ // A null value indicates that the default should be used.
+ if (args.UnderlineType != null)
+ {
+ //TextDecoration underline = new TextDecoration(args.Range,args.UnderlineType.Value,args.UnderlineColor.Value);
+
+ //InternalAddTextDecoration(underline);
+ }
+ else
+ {
+ //InternalRemoveTextDecoration(args.Range);
+ }
+ }
+
+ static Rect ScaleRect(Rect rect, double scale)
+ {
+ rect.X *= scale;
+ rect.Y *= scale;
+ rect.Width *= scale;
+ rect.Height *= scale;
+ return rect;
+ }
+
+ void EditContext_LayoutRequested(CoreTextEditContext sender, CoreTextLayoutRequestedEventArgs args)
+ {
+ CoreTextLayoutRequest request = args.Request;
+
+ // Get the screen coordinates of the entire control and the selected text.
+ // This information is used to position the IME candidate window.
+
+ // First, get the coordinates of the edit control and the selection
+ // relative to the Window.
+ Rect contentRect = MainWindow.GetElementRect(ContentPanel);
+ Rect selectionRect = MainWindow.GetElementRect(SelectionText);
+
+ // Next, convert to screen coordinates in view pixels.
+ AppWindow window = AppWindow.GetFromWindowId(_windowId);
+ contentRect.X += window.Position.X;
+ contentRect.Y += window.Position.Y;
+ selectionRect.X += window.Position.X;
+ selectionRect.Y += window.Position.Y;
+
+ // Finally, scale up to raw pixels.
+ double scaleFactor = XamlRoot.RasterizationScale;
+ contentRect = ScaleRect(contentRect, scaleFactor);
+ selectionRect = ScaleRect(selectionRect, scaleFactor);
+
+ // This is the bounds of the selection.
+ // Note: If you return bounds with 0 width and 0 height, candidates will not appear while typing.
+ request.LayoutBounds.TextBounds = selectionRect;
+
+ //This is the bounds of the whole control
+ request.LayoutBounds.ControlBounds = contentRect;
+ }
+ #endregion
+
+ #region Input
+ // This indicates that an IME has started composition. If there is no handler for this event,
+ // then composition will not start.
+ void EditContext_CompositionStarted(CoreTextEditContext sender, CoreTextCompositionStartedEventArgs args)
+ {
+ }
+
+ void EditContext_CompositionCompleted(CoreTextEditContext sender, CoreTextCompositionCompletedEventArgs args)
+ {
+ }
+
+ protected override void OnCharacterReceived(CharacterReceivedRoutedEventArgs e)
+ {
+ // Do not process keyboard input if the custom edit control does not
+ // have focus.
+ if (!_internalFocus)
+ {
+ return;
+ }
+
+ if (!Char.IsControl(e.Character))
+ {
+ ReplaceText(_selection, e.Character.ToString());
+ UpdateTextUI();
+ }
+ }
+
+ protected override void OnKeyDown(KeyRoutedEventArgs args)
+ {
+ // Do not process keyboard input if the custom edit control does not
+ // have focus.
+ if (!_internalFocus)
+ {
+ return;
+ }
+
+ // This holds the range we intend to operate on, or which we intend
+ // to become the new selection. Start with the current selection.
+ CoreTextRange range = _selection;
+
+ // For the purpose of this sample, we will support only the left and right
+ // arrow keys and the backspace key. A more complete text edit control
+ // would also handle keys like Home, End, and Delete, as well as
+ // hotkeys like Ctrl+V to paste.
+ //
+ // Note that this sample does not properly handle surrogate pairs
+ // nor does it handle grapheme clusters.
+
+ switch (args.Key)
+ {
+ // Backspace
+ case VirtualKey.Back:
+ // If there is a selection, then delete the selection.
+ if (HasSelection())
+ {
+ // Set the text in the selection to nothing.
+ ReplaceText(range, "");
+ }
+ else
+ {
+ // Delete the character to the left of the caret, if one exists,
+ // by creating a range that encloses the character to the left
+ // of the caret, and setting the contents of that range to nothing.
+ range.StartCaretPosition = Math.Max(0, range.StartCaretPosition - 1);
+ ReplaceText(range, "");
+ }
+ break;
+
+ // Left arrow
+ case VirtualKey.Left:
+ // If the shift key is down, then adjust the size of the selection.
+ if (InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down))
+ {
+ // If this is the start of a selection, then remember which edge we are adjusting.
+ if (!HasSelection())
+ {
+ _extendingLeft = true;
+ }
+
+ // Adjust the selection and notify CoreTextEditContext.
+ AdjustSelectionEndpoint(-1);
+ }
+ else
+ {
+ // The shift key is not down. If there was a selection, then snap the
+ // caret at the left edge of the selection.
+ if (HasSelection())
+ {
+ range.EndCaretPosition = range.StartCaretPosition;
+ SetSelectionAndNotify(range);
+ }
+ else
+ {
+ // There was no selection. Move the caret left one code unit if possible.
+ range.StartCaretPosition = Math.Max(0, range.StartCaretPosition - 1);
+ range.EndCaretPosition = range.StartCaretPosition;
+ SetSelectionAndNotify(range);
+ }
+ }
+ break;
+
+ // Right arrow
+ case VirtualKey.Right:
+ // If the shift key is down, then adjust the size of the selection.
+ if (InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down))
+ {
+ // If this is the start of a selection, then remember which edge we are adjusting.
+ if (!HasSelection())
+ {
+ _extendingLeft = false;
+ }
+
+ // Adjust the selection and notify CoreTextEditContext.
+ AdjustSelectionEndpoint(+1);
+ }
+ else
+ {
+ // The shift key is not down. If there was a selection, then snap the
+ // caret at the right edge of the selection.
+ if (HasSelection())
+ {
+ range.StartCaretPosition = range.EndCaretPosition;
+ SetSelectionAndNotify(range);
+ }
+ else
+ {
+ // There was no selection. Move the caret right one code unit if possible.
+ range.StartCaretPosition = Math.Min(_text.Length, range.StartCaretPosition + 1);
+ range.EndCaretPosition = range.StartCaretPosition;
+ SetSelectionAndNotify(range);
+ }
+ }
+ break;
+ }
+ }
+
+ // Adjust the active endpoint of the selection in the specified direction.
+ void AdjustSelectionEndpoint(int direction)
+ {
+ CoreTextRange range = _selection;
+ if (_extendingLeft)
+ {
+ range.StartCaretPosition = Math.Max(0, range.StartCaretPosition + direction);
+ }
+ else
+ {
+ range.EndCaretPosition = Math.Min(_text.Length, range.EndCaretPosition + direction);
+ }
+
+ SetSelectionAndNotify(range);
+ }
+ #endregion
+
+ #region UI
+ // Helper function to put a zero-width non-breaking space at the end of a string.
+ // This prevents TextBlock from trimming trailing spaces.
+ static string PreserveTrailingSpaces(string s)
+ {
+ return s + "\ufeff";
+ }
+
+ void UpdateFocusUI()
+ {
+ BorderPanel.BorderBrush = _internalFocus ? new SolidColorBrush(Microsoft.UI.Colors.Green) : null;
+ }
+
+ void UpdateTextUI()
+ {
+ // The raw materials we have are a string (_text) and information about
+ // where the caret/selection is (_selection). We can render the control
+ // any way we like.
+
+ BeforeSelectionText.Text = PreserveTrailingSpaces(_text.Substring(0, _selection.StartCaretPosition));
+ if (HasSelection())
+ {
+ // There is a selection. Draw that.
+ CaretText.Visibility = Visibility.Collapsed;
+ SelectionText.Text = PreserveTrailingSpaces(
+ _text.Substring(_selection.StartCaretPosition, _selection.EndCaretPosition - _selection.StartCaretPosition));
+ }
+ else
+ {
+ // There is no selection. Remove it.
+ SelectionText.Text = "";
+
+ // Draw the caret if we have focus.
+ CaretText.Visibility = _internalFocus ? Visibility.Visible : Visibility.Collapsed;
+ }
+
+ AfterSelectionText.Text = PreserveTrailingSpaces(_text.Substring(_selection.EndCaretPosition));
+
+ // Update statistics for demonstration purposes.
+ FullText.Text = _text;
+ SelectionStartIndexText.Text = _selection.StartCaretPosition.ToString();
+ SelectionEndIndexText.Text = _selection.EndCaretPosition.ToString();
+ }
+
+ #endregion
+ }
+}
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/CustomEditControlCs.csproj b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/CustomEditControlCs.csproj
new file mode 100644
index 000000000..8abe87001
--- /dev/null
+++ b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/CustomEditControlCs.csproj
@@ -0,0 +1,45 @@
+
+
+ WinExe
+ net6.0-windows10.0.19041.0
+ 10.0.17763.0
+ CustomEditControlCs
+ app.manifest
+ x86;x64;arm64
+ win10-x86;win10-x64;win10-arm64
+ win10-$(Platform).pubxml
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+
+
+
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/MainWindow.xaml b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/MainWindow.xaml
new file mode 100644
index 000000000..777ac1885
--- /dev/null
+++ b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/MainWindow.xaml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+ Text input
+
+
+ When the custom text control has focus, it draws with a green border.
+ The caret is represented by a globe.
+ See the README for this sample for further instructions.
+
+
+
+
+
+
+
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/MainWindow.xaml.cs b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/MainWindow.xaml.cs
new file mode 100644
index 000000000..e7edd86f0
--- /dev/null
+++ b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/MainWindow.xaml.cs
@@ -0,0 +1,63 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.UI;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Input;
+using Microsoft.UI.Xaml.Media;
+using System;
+using Windows.Foundation;
+using WinRT.Interop;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace CustomEditControlCs
+{
+ ///
+ /// An empty window that can be used on its own or navigated to within a Frame.
+ ///
+ public sealed partial class MainWindow : Window
+ {
+ public MainWindow()
+ {
+ this.InitializeComponent();
+ Content.PointerPressed += OnPointerPressed;
+
+ IntPtr hWnd = WindowNative.GetWindowHandle((Window)this);
+ WindowId windowId = Win32Interop.GetWindowIdFromWindow(hWnd);
+ MyCustomEditControl.SetAppWindow(windowId);
+ }
+
+ public static Rect GetElementRect(FrameworkElement element)
+ {
+ GeneralTransform transform = element.TransformToVisual(null);
+ Point point = transform.TransformPoint(new Point());
+ return new Rect(point, new Size(element.ActualWidth, element.ActualHeight));
+ }
+
+ private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
+ {
+ Rect contentRect = GetElementRect(MyCustomEditControl);
+ if (contentRect.Contains(e.GetCurrentPoint(null).Position))
+ {
+ // The user tapped inside the control. Give it focus.
+ MyCustomEditControl.SetInternalFocus();
+
+ // Tell XAML that this element has focus, so we don't have two
+ // focus elements. That is the extent of our integration with XAML focus.
+ MyCustomEditControl.Focus(FocusState.Programmatic);
+
+ // A more complete custom control would move the caret to the
+ // pointer position. It would also provide some way to select
+ // text via touch. We do neither in this sample.
+
+ }
+ else
+ {
+ // The user tapped outside the control. Remove focus.
+ MyCustomEditControl.RemoveInternalFocus();
+ }
+ }
+ }
+}
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Package.appxmanifest b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Package.appxmanifest
new file mode 100644
index 000000000..f558c6141
--- /dev/null
+++ b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Package.appxmanifest
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+ CustomEditControlCs
+ Microsoft Corporation
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Properties/PublishProfiles/win10-arm64.pubxml b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Properties/PublishProfiles/win10-arm64.pubxml
new file mode 100644
index 000000000..a132e44c6
--- /dev/null
+++ b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Properties/PublishProfiles/win10-arm64.pubxml
@@ -0,0 +1,20 @@
+
+
+
+
+ FileSystem
+ arm64
+ win10-arm64
+ bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\
+ true
+ False
+ False
+ True
+
+
+
\ No newline at end of file
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Properties/PublishProfiles/win10-x64.pubxml b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Properties/PublishProfiles/win10-x64.pubxml
new file mode 100644
index 000000000..26ea7e55c
--- /dev/null
+++ b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Properties/PublishProfiles/win10-x64.pubxml
@@ -0,0 +1,20 @@
+
+
+
+
+ FileSystem
+ x64
+ win10-x64
+ bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\
+ true
+ False
+ False
+ True
+
+
+
\ No newline at end of file
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Properties/PublishProfiles/win10-x86.pubxml b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Properties/PublishProfiles/win10-x86.pubxml
new file mode 100644
index 000000000..34d14d4d4
--- /dev/null
+++ b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Properties/PublishProfiles/win10-x86.pubxml
@@ -0,0 +1,20 @@
+
+
+
+
+ FileSystem
+ x86
+ win10-x86
+ bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\
+ true
+ False
+ False
+ True
+
+
+
\ No newline at end of file
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Properties/launchSettings.json b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Properties/launchSettings.json
new file mode 100644
index 000000000..4303eed02
--- /dev/null
+++ b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/Properties/launchSettings.json
@@ -0,0 +1,10 @@
+{
+ "profiles": {
+ "CustomEditControlSampleCs (Package)": {
+ "commandName": "MsixPackage"
+ },
+ "CustomEditControlSampleCs (Unpackaged)": {
+ "commandName": "Project"
+ }
+ }
+}
\ No newline at end of file
diff --git a/Samples/CustomEditControl/cs-winui/CustomEditControlCs/app.manifest b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/app.manifest
new file mode 100644
index 000000000..63509d8c6
--- /dev/null
+++ b/Samples/CustomEditControl/cs-winui/CustomEditControlCs/app.manifest
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+ true/PM
+ PerMonitorV2, PerMonitor
+
+
+
diff --git a/SamplesCI-CustomEditControl.yml b/SamplesCI-CustomEditControl.yml
new file mode 100644
index 000000000..a3c10be87
--- /dev/null
+++ b/SamplesCI-CustomEditControl.yml
@@ -0,0 +1,7 @@
+# Please see https://www.osgwiki.com/wiki/Windows_App_SDK_-_How_to_build_and_use_the_pipelines
+# for information on how to use the pipelines
+name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
+stages:
+- template: WindowsAppSDK-SamplesCI.yml
+ parameters:
+ FeatureDirectory: "CustomEditControl"
\ No newline at end of file