Reputation: 3305
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
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
andkeybd_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:
When a RichTextBox
is constructed, a call is made to TextEditor.RegisterCommandHandlers()
to register global commands for the RichTextBox
class, including the shortcuts.
TextEditor.RegisterCommandHandlers()
in turn calls methods like TextEditorParagraphs._RegisterClassHandlers()
to register specific commands for the class.
These in turn call CommandHelpers.RegisterCommandHandler()
to register each command and associated (hardcoded) shortcut for the class. The shortcuts are encoded as a KeyGesture
.
RegisterCommandHandler()
in turn calls CommandManager.RegisterClassInputBinding()
to register each shortcut gesture for the class.
The CommandManager
stores the shortcut gestures in a private static dictionary:
private static HybridDictionary _classInputBindings = new HybridDictionary();
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;
}
}
}
classInputBindings.FindMatch()
calls KeyGesture.Matches()
.
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