Wpf RichTextBox execute command by sending programmatic key strokes

I need to send keystrokes to a RichTextBox which at the end should execute some commands like ToggleBold or SelectAll etc.

I call the following code in a loop to iterate through SomeEvent and SomeKey pairs.

PresentationSource presentationSource = PresentationSource.FromVisual(RichTextBoxControl);

if (presentationSource == null)
{
    return;
}

if(SomeEvent == KeyDownEvent)
{
    //InputManager.Current.ProcessInput(new KeyEventArgs(Keyboard.PrimaryDevice, presentationSource, 100, SomeKey) { RoutedEvent = PreviewKeyDownEvent });
    InputManager.Current.ProcessInput(new KeyEventArgs(Keyboard.PrimaryDevice, presentationSource, 100, SomeKey) { RoutedEvent = KeyDownEvent });
}
else if (SomeEvent == KeyUpEvent)
{
    //InputManager.Current.ProcessInput(new KeyEventArgs(Keyboard.PrimaryDevice, presentationSource, 100, SomeKey) { RoutedEvent = PreviewKeyUpEvent });
    InputManager.Current.ProcessInput(new KeyEventArgs(Keyboard.PrimaryDevice, presentationSource, 100, SomeKey) { RoutedEvent = KeyUpEvent });
}

For example to activate ToggleBold I call following pairs in a loop:

KeyDownEvent, Keys.Control
KeyDownEvent, Keys.B
KeyUpEvent, Keys.B
KeyUpEvent, Keys.Control

I have tried KeyDownEvent - KeyUpEvent, PreviewKeyDownEvent - PreviewKeyUpEvent, and PreviewKeyDownEvent - KeyDownEvent - PreviewKeyUpEvent - KeyUpEvent, but none of them worked.

The RichTextBox actually catches the events but it does not react to them. This is also the case if I send alphanumeric characters (They are not printed on RichTextBox). However if I send Keys.Back it actually recognizes and deletes a character from the text.

The solutions like SendKeys and keybd_event are not applicable since the program will be on the background while the keystrokes are sent.

Is there a way to send keystrokes to a control like they really are sent by the user?

Upvotes: 2

Views: 1170

Answers (1)

dbc
dbc

Reputation: 116670

Update

You asked

Is there a way to send keystrokes to a control like they really are sent by the user?

The solutions like SendKeys and keybd_event are not applicable since the program will be on the background while the keystrokes are sent.

From browsing the WPF reference source it appears that it may not be possible to meet your requirement of doing this in the background:

  1. When a RichTextBox is constructed, a call is made to TextEditor.RegisterCommandHandlers() to register global commands for the RichTextBox class, including the shortcuts.

  2. TextEditor.RegisterCommandHandlers() in turn calls methods like TextEditorParagraphs._RegisterClassHandlers() to register specific commands for the class.

  3. These in turn call CommandHelpers.RegisterCommandHandler() to register each command and associated (hardcoded) shortcut for the class. The shortcuts are encoded as a KeyGesture.

  4. RegisterCommandHandler() in turn calls CommandManager.RegisterClassInputBinding() to register each shortcut gesture for the class.

  5. The CommandManager stores the shortcut gestures in a private static dictionary:

     private static HybridDictionary _classInputBindings = new HybridDictionary();
    
  6. There is no "get" access to this dictionary. The only other place it is used is in CommandManager.TranslateInput(IInputElement targetElement, InputEventArgs inputEventArgs):

        // Step 2: If no command, check class input bindings
        if (command == null)
        {
            lock (_classInputBindings.SyncRoot)
            {
                Type classType = targetElement.GetType();
                while (classType != null)
                {
                    InputBindingCollection classInputBindings = _classInputBindings[classType] as InputBindingCollection;
                    if (classInputBindings != null)
                    {
                        InputBinding inputBinding = classInputBindings.FindMatch(targetElement, inputEventArgs);
                        if (inputBinding != null)
                        {
                            command = inputBinding.Command;
                            target = inputBinding.CommandTarget;
                            parameter = inputBinding.CommandParameter;
                            break;
                        }
                    }
                    classType = classType.BaseType;
                }
            }
        }
    
  7. classInputBindings.FindMatch() calls KeyGesture.Matches().

  8. But KeyGesture.Matches() uses the global static Keyboard device rather than the InputEventArgs to determine which key modifier is pressed:

    public override bool Matches(object targetElement, InputEventArgs inputEventArgs)
    {
        KeyEventArgs keyEventArgs = inputEventArgs as KeyEventArgs;
        if(keyEventArgs != null && IsDefinedKey(keyEventArgs.Key))
        {
            return ( ( (int)Key == (int)keyEventArgs.RealKey ) && ( this.Modifiers == Keyboard.Modifiers ) );
        }
        return false;
    }
    

    KeyEventArgs doesn't even have a property to represent whether a control or alt key is pressed.

If you remove the requirement to do this in the background, P/Invoking SendInput becomes a solution.

So I think the workaround of hardcoding commands may be your best solution.

Original Answer

Not exactly an answer to your specific question, but may solve your problem.

You can execute any of the standard public static editing commands such as ToggleBold or standard public static application commands such as Undo by using

        command.Execute(null, richTextBox);

Where command is any of the static commands in the documentation linked above. In addition, you can programmatically insert text at the current caret location using this answer

        richTextBox.CaretPosition = richTextBox.CaretPosition.GetPositionAtOffset(0, LogicalDirection.Forward);

        richTextBox.CaretPosition.InsertTextInRun("Hello, I am some text\n\n\n");

        richTextBox.CaretPosition.InsertTextInRun("And some more text\n\n\n");

For instance, the following will insert some text at the current caret location of a specified RichTextBox, then animate various editing changes to all the text:

    const int delayInterval = 500;

    static void Do( RoutedUICommand command, RichTextBox richTextBox, ref int delay)
    {
        // https://stackoverflow.com/questions/15599884/how-to-put-delay-before-doing-an-operation-in-wpf
        var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(delay = delay + delayInterval)};
        timer.Start();
        timer.Tick += (sender, args) =>
        {
            timer.Stop();
            command.Execute(null, richTextBox);
        };
    }

    public static void TestTestProgrammaticCommandExecution(RichTextBox richTextBox)
    {
        // Insert some text at the caret position.
        // https://stackoverflow.com/questions/2497291/how-do-i-move-the-caret-a-certain-number-of-positions-in-a-wpf-richtextbox
        richTextBox.CaretPosition = richTextBox.CaretPosition.GetPositionAtOffset(0, LogicalDirection.Forward);

        richTextBox.CaretPosition.InsertTextInRun("Hello, I am some text\n\n\n");

        richTextBox.CaretPosition.InsertTextInRun("And some more text\n\n\n");

        int delay = 0;

        // Do a bunch of editing commands.

        Do(EditingCommands.MoveToDocumentStart, richTextBox, ref delay);

        Do(EditingCommands.SelectToDocumentEnd, richTextBox, ref delay);

        Do(EditingCommands.AlignCenter, richTextBox, ref delay);

        Do(EditingCommands.AlignLeft, richTextBox, ref delay);

        Do(EditingCommands.AlignRight, richTextBox, ref delay);

        Do(EditingCommands.DecreaseFontSize, richTextBox, ref delay);

        Do(EditingCommands.IncreaseFontSize, richTextBox, ref delay);

        Do(EditingCommands.IncreaseFontSize, richTextBox, ref delay);

        Do(EditingCommands.IncreaseFontSize, richTextBox, ref delay);

        Do(EditingCommands.IncreaseFontSize, richTextBox, ref delay);

        Do(EditingCommands.Backspace, richTextBox, ref delay);

        Do(ApplicationCommands.Undo, richTextBox, ref delay);

        Do(EditingCommands.ToggleBold, richTextBox, ref delay);

        Do(EditingCommands.ToggleBold, richTextBox, ref delay);

        Do(EditingCommands.ToggleBold, richTextBox, ref delay);

        Do(ApplicationCommands.Undo, richTextBox, ref delay);

        Do(ApplicationCommands.Undo, richTextBox, ref delay);

        Do(ApplicationCommands.Undo, richTextBox, ref delay);

        Do(ApplicationCommands.Redo, richTextBox, ref delay);

        Do(ApplicationCommands.Redo, richTextBox, ref delay);

        Do(ApplicationCommands.Redo, richTextBox, ref delay);
    }
}

Upvotes: 2

Related Questions