dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.08k stars 1.17k forks source link

In a RichTextBox within WPF, it seems that PreviewKeyDown cannot effectively block all events. #7839

Closed bbsuuo closed 1 year ago

bbsuuo commented 1 year ago

Description

I'm developing a page similar to a terminal using RichTextBox, so I want to control user input. I registered the PreviewKeyDown event, which theoretically should be able to block all events. However, when I select a portion of text and then press any key, the selected text gets deleted. It seems that this triggers a precondition for replacing text in the session. The code looks something like this:

ConsoleRichTextBox.PreviewKeyDown += OnPreviewKeyDown; void OnPreviewKeyDown(object sender, KeyEventArgs e) { e.Handled = true; }

I tried popping the stack in the TextChanged event (which gets triggered first in this case), and I got the following stack:

在 TranslateStudio.Views.Pages.UnityTranslate.UnityConsolePage.OnTextChanged(Object sender, TextChangedEventArgs e) 在 E:\GitLibrary\JustTranslate\ChatGptServer\TranslateStudio\Views\Pages\UnityTranslate\UnityConsolePage.xaml.cs 中: 第 114 行

在 System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)

在 System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)

在 System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)

在 System.Windows.Controls.Primitives.TextBoxBase.OnTextContainerChanged(Object sender, TextContainerChangedEventArgs e)

在 System.Windows.Documents.TextContainer.EndChange(Boolean skipEvents)

在 System.Windows.Documents.TextRangeBase.EndChange(ITextRange thisRange, Boolean disableScroll, Boolean skipEvents)

在 System.Windows.Documents.TextSelection.System.Windows.Documents.ITextRange.set_Text(String value)

在 System.Windows.Documents.TextEditor.SetText(ITextRange range, String text, CultureInfo cultureInfo)

在 System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)

在 System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)

在 System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)

在 System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)

在 System.Windows.Input.InputManager.ProcessStagingArea()

在 System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)

在 System.Windows.Interop.HwndKeyboardInputProvider.ProcessKeyAction(MSG& msg, Boolean& handled)

在 System.Windows.Interop.HwndSource.CriticalTranslateAccelerator(MSG& msg, ModifierKeys modifiers)

在 System.Windows.Interop.HwndSource.OnPreprocessMessage(Object param)

在 System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)

在 System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)

How can I prevent the Session from replacing the text?

Reproduction Steps

RichTextBox.PreviewKeyDown += OnPreviewKeyDown;

void OnPreviewKeyDown(object sender, KeyEventArgs e) { e.Handled = true; }

//You can reproduce this issue on any page.

Expected behavior

PreviewKeyDown should be able to block any text manipulation actions.

Actual behavior

PreviewKeyDown cannot effectively block all events.

Regression?

No response

Known Workarounds

Currently, it seems that modifying the events registered by TextEditor can only be achieved through reflection.

Impact

No response

Configuration

No response

Other information

No response

miloush commented 1 year ago

Expected behavior PreviewKeyDown should be able to block any text manipulation actions.

Where does this expectation come from? Even the name suggests at best this can prevent key presses to be processed. You can still paste text using context menu. You can still enter text using voice input or other input methods that do not necessarily involve key presses processing.

You can prevent the event going to the TextEditor if you create your own class deriving from RichTextBox and overriding the OnPreviewKeyDown method, but that still does not handle the case when the text change is initiated by an IME composition.

If you want to prevent input, make the textbox readonly. If you need input only on the bottom line, put another textbox at the bottom and remove the border between them. If you need fixed character space with random cursor placement then you probably need a different solution than a textbox.

bbsuuo commented 1 year ago

Expected behavior PreviewKeyDown should be able to block any text manipulation actions.

Where does this expectation come from? Even the name suggests at best this can prevent key presses to be processed. You can still paste text using context menu. You can still enter text using voice input or other input methods that do not necessarily involve key presses processing.

You can prevent the event going to the TextEditor if you create your own class deriving from RichTextBox and overriding the OnPreviewKeyDown method, but that still does not handle the case when the text change is initiated by an IME composition.

If you want to prevent input, make the textbox readonly. If you need input only on the bottom line, put another textbox at the bottom and remove the border between them. If you need fixed character space with random cursor placement then you probably need a different solution than a textbox.

Sorry... Upon rechecking the source code, I realized that I can indeed correct my issue by overriding the PreviewKeyDown of TextBoxBase. Initially, I mistook the root cause of the problem as OnTextContainerChanged.

miloush commented 1 year ago

The other thing you can do is set InputMethod.IsInputMethodSuspended (or IsInputMethodEnabled) on the RTB. That might help with preventing IME to modify your text without prior key events. Still does not help with paste, possibly drag&drop and there might be other ways to get text in.

bbsuuo commented 1 year ago

Expected behavior PreviewKeyDown should be able to block any text manipulation actions.

Where does this expectation come from? Even the name suggests at best this can prevent key presses to be processed. You can still paste text using context menu. You can still enter text using voice input or other input methods that do not necessarily involve key presses processing.

You can prevent the event going to the TextEditor if you create your own class deriving from RichTextBox and overriding the OnPreviewKeyDown method, but that still does not handle the case when the text change is initiated by an IME composition.

If you want to prevent input, make the textbox readonly. If you need input only on the bottom line, put another textbox at the bottom and remove the border between them. If you need fixed character space with random cursor placement then you probably need a different solution than a textbox.

Although I still believe it is not very reasonable, in TextBoxBase, it defaults to calling _textEditor.OnPreviewKeyDown(e);, which then calls the method TextEditorTyping.OnPreviewKeyDown(_uiScope, e);. In this method, This.SetText(This.Selection, String.Empty, InputLanguageManager.Current.CurrentInputLanguage); is called. In the comments, it is mentioned that this is done to address some issues with IMEs. However, in terms of the process, text manipulation should not be handled during the pre-notification input event phase in OnPreviewKeyDown.

bbsuuo commented 1 year ago

The other thing you can do is set InputMethod.IsInputMethodSuspended (or IsInputMethodEnabled) on the RTB. That might help with preventing IME to modify your text without prior key events. Still does not help with paste, possibly drag&drop and there might be other ways to get text in.

Thank you for your guidance! I am now attempting to write the code.