How to PgUp/PgDn with SendKeys to a foreground textbox - c#

I am trying to page up/down the contents of a simple textbox control in a simple Windows Form on NET 6.0, but something is wrong. Textbox shortcuts are enabled (probably why SendKeys.SendWait("^a"); works) and readonly is false.
I have a method (not on the UI thread) that I call to SendKeys.SendWait("{PgUp}"); to the foreground app (which is both the key sender and textbox (with focus) receiver.
If I type PgUp on the keyboard, the textbox pages up as expected.
If I SendKeys.SendWait("^a");, the textbox selects all text as expected.
If I Sendkeys.SendWait("{PgUp}");, the textbox adds a blank line to the bottom of the text.
From this I conclude that my code is working because it sends "^a" and the textbox receives it and selects all text. But somehow the textbox does not handle the "{PgUp}" key, even though it does when the PgUp key is sent by the keyboard.
I've read easily a dozen articles and posts on the web and SO that talk about paging using scrolling events, positioning the caret and then scrolling to the caret, and so on. But none of them say anything about why SendKeys(^a) and keyboard PgUp would work but SendKeys.SendWait("{PgUp}") would fail.
Can anyone tell me what I'm doing wrong and maybe what I need to do (or read) to fix it? Thank you
UPDATE: Jimi asked for some code, so here is the code that I use to send the ^a and the {PgUp} keys. I know this is not on the UI thread because it is executed from a voice-driven recognizer thread. The app is a voice-driven app that displays content in the textbox by textbox.AppendLines calls. I was trying to PgUp and PgDn the multi-line textbox by voice as well.
When I tried to use Send (I normally use .SendWait for everything in other programs), I received the following error message:
System.InvalidOperationException: 'SendKeys cannot run inside this
application because the application is not handling Windows messages.
Either change the application to handle messages, or use the
SendKeys.SendWait method.'
It is true that my app does not intercept Windows messages. I can't figure out why the app can receive and properly process my keyboard keys, and my "^a' shortcut keys, but not the SendWait("{PgUp}") key.
internal static void
HelperPageUp() {
var keys = "{PgUp}";
keys = "^a";
SendKeys.SendWait(keys);
}
I'm starting to think that {PgUp} is never handled by a textbox or control. Instead, probably {PgUp} must be handled by logic in a case statement that converts PgUp "orders" into sets of actions that implement whatever PgUp means to the app that receives the PgUp key. So maybe I will have to add a keystroke handler to the form. Maybe something like this:
textBox1_KeyUp(object sender, KeyEventArgs e) {
// identify the special key and implement what it means
if (e.KeyCode == Keys.PageDown) {
...
e.Handled = true;
}

Yes, my thought at the end of the question was correct. The ^a was handled by the textbox because I had textbox.EnableShortcuts=true;, so the textbox handled the popular ^a shortcut. But keys like {PgUp} are a different matter; they are not included in shortcuts.
The solution was to write code to handle the {PgUp} key explicitly in the form. Here is my code that worked.
void
textBox1_KeyUp(object sender, KeyEventArgs e) {
if (e.KeyCode == Keys.PageUp) {
// page the viewport up; watch for end of content
var charIndex = textBox1.SelectionStart;
var lineIndex = textBox1.GetLineFromCharIndex(charIndex);
// move 20 lines up, but not past zero
var newLine = lineIndex - 20;
var newIndex = Math.Max(0, newLine);
// set the new anchor and scroll to it
var newAnchor = textBox1.GetFirstCharIndexFromLine(newIndex);
textBox1.Select(newAnchor,0);
textBox1.ScrollToCaret();
e.Handled = true;
}

Related

Custom OSK: Listen to TextBox Focus

I have written a custom OnScreen Keyboard as an UserControl to have a better control over what the user can type (Alphanumeric/Numpad/Navigation Keys - stuff like that) and to have a better control over the screen layout at design time.
The OSK works by manipulating the text- and selection-properties/functions of a textbox-control.
My main Problem is how to find the right TextBox to inject text into.
My first, naive approach was to register every TextBox I want to use with the OSK Control manually and use the GotFocus/LostFocus of those registered TextBoxes to determine the active control:
public void RegisterInput(TextBox text) {
if (!_listeners.ContainsKey(text)) {
_listeners.Add(text, modes);
text.GotFocus += Input_OnGotFocus;
text.LostFocus += Input_OnLostFocus;
}
}
private void Input_OnLostFocus(object sender, RoutedEventArgs routedEventArgs) {
if (_focused == sender) {
_focused = null;
IsEnabled = false;
UpdateKeyboardMode(); // << Updates Keyboard layout (Alphanumerical vs Numpad) based on focused control
}
}
private void Input_OnGotFocus(object sender, RoutedEventArgs routedEventArgs) {
_focused = (TextBox) sender;
IsEnabled = true;
UpdateKeyboardMode();
Bindings.Update();
}
I work with Focus here, because I need to determine which kind of keyboard (full-size alphanumerical vs. short numpad) to display for each TextBox. The _focused TextBox is then used to directly inject the pressed keys into it. In the constructor of my Page which also contains the OSK-control I would call RegisterInput() with a reference of each and every TextBox I defined on the page. This works just fine — if I have those references.
But now I am working with UserControls. That also removes the TextBoxes out of reach for direct referencing, but I could write some kind of VisualTree-Scan after InitializeComponent() to find all references and call RegisterInput() on each reference I found. If I only need to do this once, it isn't a problem (altough it is still ugly).
One step further - ListBoxes with dynamicly changing contents and DataTemplates. Now I'd need to rescan the whole VisualTree explicitly everytime something changes. But how to detect those changes?
The question is: Can I get an event as soon as $any element in my VisualTree gets/looses focus, without knowing all those elements beforehand (thus replacing RegisterInput() completely)? Or can I listen to changes to the VisualTree to rescan all controls and then call RegisterInput() manually for every TextBox I found?
The goal is to get a handler called everytime a GetFocus/LostFocus event on any TextBox/Control in the UI is raised so that I can update the keyboard to either display a full-sized alphanumerical keyboard (for default textboxes) or a shortened numpad (e.g. for textboxes bound to numerical backing fields).
Alternatively: Is there any other way to inject text and call UpdateKeyboardMode() to update the keyboard layout as soon as the selected textbox changes?
Other options I thought about include:
Build a custom control which derives from a TextBox and let it register itself to the OSK. I'll probably resort to this method, if I don't find any better way. But this will destroy support for 3rd party libraries in which my control is not present and thus does not use the "special magical textbox with osk support".
Don't use events at all. Get the currently focused TextBox with the FocusManager as soon as the user presses a key on my OSK and inject text into the focused instance. Problem with this approach is, that it completely destroys the capability to adapt the OSK to different input types (alphanumerical vs only Numpad), because I cannot determine the keyboard type I need before pressing a key.
Rescan the VisualTree with a timer. Won't do that, thats simply too much of a hack.
Use the OnScreen-Keyboard supplied by Win10 IoT. Two problems: It has no designtime support and is displayed above elements, even if the focused element is directly underneath the keyboard (acceptable if neccessary), but I don't know of a way to change the keyboard "layout" between a full-sized alphanumeric keyboard and a shortened Numpad which only contains numbers and some keys. Also it does not allow to use custom keys (e.g. arrow keys for navigation, custom return key handling).
After a discussion in the chat forum, the actual problem isn't to create a Custom OSK control and use that to interact with the TextBoxs but instead, it's "being bound to use custom control" wrapping a textbox everywhere a OSK needs to be shown.
The Solution would be to listen to the OS-OSK events and when they are triggered, pop up the Custom OSK this ways you won't have to wrap a Textbox in a user control and use that throughout your project.
Link to the Documentation: - respond to the presence of the touch keyboard

Second TextBox showing same Text Selection as first

Long time listener, first time caller here. I'm having a strange issue with the TextBox in WinRT C#/XAML that I hope someone may be able to help me with.
Basically, I'm working on creating a Custom Control that essentially requires a second TextBox to be a copy of the first, including showing the same Text, and showing the same Selected Text. Obviously for the Text requirement I simply respond to the TextChanged event on the first TextBox and set the Text of the second TextBox to the Text from the first, which works great.
For the Selected Text requirement I started with a similar solution, and my code for this is as follows:
void TextBox1_SelectionChanged(object sender, RoutedEventArgs e)
{
this.TextBox2.Select(this.TextBox1.SelectionStart, this.TextBox1.SelectionLength);
}
This seemed to work pretty well when initially used with a mouse:
But I'm having a problem when selecting text with Touch. I double-tap within the TextBox to create the first "anchor" as you do in Touch, then drag to begin the selection; but I only ever manage to select a single character normally before the selection stops. The TextBox doesn't lose focus exactly, but the behaviour is similar to that; the selection anchors disappear and I can't continue selecting anything unless I re-double-tap to start a new selection. If I remove the code to select text in TextBox2 then the Touch selection behaves perfectly in TextBox1.
I've been trying to fix this for a while and cannot, I'm not sure if I can get the desired behaviour with WinRT TextBoxes. Does anyone have any ideas? Or perhaps another way to implement a solution with two TextBoxes with this behaviour?
Thanks a lot.
So this is far from an answer, but discovered a few things that maybe will help you or others come up with a potential workaround. Apologies if these are things you've already seen and noted.
First, it's not the call to TextBox2.Select() that's the problem per se. This for instance, works fine for me
private void txt1_SelectionChanged(object sender, RoutedEventArgs e)
{
var start = TextBox1.SelectionStart;
var length = TextBox1.SelectionLength;
TextBox2.Select(3, 5);
}
unfortunately, using start and length versus the hard-coded 3 and 5, that is, the following, DOES NOT WORK:
private void txt1_SelectionChanged(object sender, RoutedEventArgs e)
{
var start = TextBox1.SelectionStart;
var length = TextBox1.SelectionLength;
TextBox2.Select(start, length);
}
I also discovered that I could select TWO characters if I started from the end, but only one from the beginning. That got me to thinking about dispatching the call to set the second selection:
private void txt1_SelectionChanged(object sender, RoutedEventArgs e)
{
var start = TextBox1.SelectionStart;
var length = TextBox1.SelectionLength;
Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Low,
() => TextBox2.Select(start, length));
}
Now I can select 2 from the front and 3 and sometimes 4 from the back. Took it a step further, and was able to select as many as six or seven with a really fast swipe.
private void txt1_SelectionChanged(object sender, RoutedEventArgs e)
{
var start = TextBox1.SelectionStart;
var length = TextBox1.SelectionLength;
Dispatcher.RunIdleAsync((v) => Highlight());
}
public void Highlight()
{
TextBox2.Select(TextBox1.SelectionStart, TextBox1.SelectionLength);
}
Seems like the trick to working around this is not setting TextBox2 until whatever vestiges of the TextBox1 SelectionChanged event have completed.
This may be worth registering on Connect.
Mine is only a partial solution as well.
I did some debugging and noticed that the SelectionChanged event is fired throughout the text selection process. In other words, a single finger "swipe" will generate multiple SelectionChanged events.
As you found out, calling TextBox.Select during a text selection gesture affects the gesture itself. Windows seems to stop the gesture after the programmatic text selection.
My workaround is to delay as long as possible calling the TextBox.Select method. This does work well, except for one edge case. Where this method fails is in the following scenario:
The user begins a select gesture, say selecting x characters. The user, without taking their finger off the screen, pauses for a second or two. The user then attempts to select more characters.
My solution does not handle the last bit in the above paragraph. The touch selection after the pause does not actually select anything because my code will have called the TextBox.Select method.
Here is the actual code. As I mentioned above, there are multiple selection changed events fired during a single selection gesture. My code uses a timer along with a counter to only do the programmatic selection when there are no longer any pending touch generated selection changed events.
int _selectCounter = 0;
const int SELECT_TIMER_LENGTH = 500;
async private void TextBox1_SelectionChanged(object sender, RoutedEventArgs e)
{
// _selectCounter is the number of selection changed events that have fired.
// If you are really paranoid, you will want to make sure that if
// _selectCounter reaches MAX_INT, that you reset it to zero.
int mySelectCount = ++_selectCounter;
// start the timer and wait for it to finish
await Task.Delay(SELECT_TIMER_LENGTH);
// If equal (mySelectCount == _selectCounter),
// this means that NO select change events have fired
// during the delay call above. We only do the
// programmatic selection when this is the case.
// Feel free to adjust SELECT_TIMER_LENGTH to suit your needs.
if (mySelectCount == _selectCounter)
{
this.TextBox2.Select(this.TextBox1.SelectionStart, this.TextBox1.SelectionLength);
}
}

Why does pressing Ctrl+"+" produce a beep in a TextBox?

I am Working with C# and Windows Forms and want to use Ctrl+"Oemplus" as a function key for my application. I use a German keyboard and this key is located 2 keys right of the letter P (that ist then "+"). Whenever I press this key in combination with Ctrl and the focus is on a TextBox I get a beep.
This also happens when I switch to an US keyboard layout (still using my German keyboard). This is then the ] key.
The same happens when pressing this key while in Internet Explorers address bar.
My question is:
Why does this key combination produce a beep in a TextBox.
How can I avoid the beep?
Thanks for any efforts you put on this.
Update:
I tried it on an US/Thai keyboard and get the beep as well. This happens no matter what logical keyboard layout I use (German, US, Thai).
The beep also happens in Windows Explorer in the address bar but not in the search box.
It is very unclear what you hope to happen when you press that keystroke. TextBox leaves no doubt about it, it BEEPs! because it can see that the user is trying to do something special but it doesn't know exactly what. Good reason to beep you. Solution is to implement magic, in the //.. comment in this next snippet. With the extra code to stop the beep at the end:
private void textBox1_KeyDown(object sender, KeyEventArgs e) {
if (e.KeyData == (Keys.Control | Keys.Oemplus)) {
// Invoke magic
//...
// Magic is done now:
e.Handled = e.SuppressKeyPress = true;
}
}
What I believe is happening is that the key combination is not allowed for the Textbox, therefore you are getting the error. You can test for the Key Combination by this code( using the right bracket key in EN Windows) it is using SuppressKeyPress to prevent the Key Combination from being passed to the underlying control to prevent the beep.
private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
if (ModifierKeys == Keys.Control)
if (e.KeyValue == 221) // You may need to determine this value for your keyboard layout.
{
textBox1.Text += " + "; // Handle the Key combination.
e.SuppressKeyPress = true; // Prevents key from being passed to underlying control
}
}
After some time I came back on this. What I found unsatisfying with the previous solution was that every TextBox would need that handling for every 'beeping' key I use for something else. Sure I could subclass the TextBox but still I would have two places to change for every key with this behavior (the handling and the beep suppression).
Actually I use Ctrl+'+' as a command key. I did this with a global keyboard hook because the action should be available on and impact all forms.
Instead I handle this now in a base form using the following code:
// Avoid beep on Ctrl+'+' in TextBox.
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
Keys key = keyData & Keys.KeyCode;
bool alt = keyData.HasFlag(Keys.Alt);
bool shift = keyData.HasFlag(Keys.Shift);
bool control = keyData.HasFlag(Keys.Control);
if (key == Keys.Oemplus && !shift && control && !alt)
{
// Perform the action for Ctrl+'+'
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
For me this seems the "correct" Position and way to handle a command key. The beep is gone without any additional handling and I don't have to worry if additional command keys produce a beep or not.
When the form had a menu or toolbar this would be handled automatically by the shortcut defined for the menu item. But my forms don't have a menu or toolbar.

Alt keys and tab don't work in Windows Forms opened from a WPF application

I have lots of old Windows Forms applications that will eventually be ported to WPF (it is a large application so it can't be done in one sprint), and I have started the process by creating a main menu in WPF. The Windows Forms applications are separate windows opened from this menu.
The Windows Forms applications are opening and working without any problems except the issues I am having with the shortcut and Tab keys. The tab key is not moving focus to the next control, and the Alt key to trigger the &Search button no longer works.
What am I doing wrong?
A partial solution I discovered is to call this from your WPF constructor:
System.Windows.Forms.Integration.WindowsFormsHost.EnableWindowsFormsInterop();
(You need to reference the dll WindowsFormsIntegration.dll)
I say partial because not all key strokes function as expected. Eg, seems to work okay for simple forms.
See this:
http://msdn.microsoft.com/en-us/library/system.windows.forms.integration.windowsformshost.enablewindowsformsinterop(v=vs.100).aspx
I finally managed to fix the issue by hosting the winform inside a WindowsFormsHost control inside a WPF form.
public partial class MyWindow : Window
{
public MyWindow()
{
InitializeComponent();
Form winform = new Form();
// to embed a winform using windowsFormsHost, you need to explicitly
// tell the form it is not the top level control or you will get
// a runtime error.
winform.TopLevel = false;
// hide border because it will already have the WPF window border
winform.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.windowsFormsHost.Child = winform;
}
}
Please note that you may also need to hook up the winform close event if you have a button to close the form.
This is by design. Shortcut keys are handled at the message loop level, detected before the Windows message gets dispatched to the window with the focus. That's the reason those keys can work regardless of the focus.
Problem is, you don't have the Winforms message loop pumping the messages. Application.Run() is implemented by WPF in your program, not Winforms. So any of the code in Winforms that processes keyboard messages to implement shortcut keystrokes won't run.
There's no good solution for this, it is pretty fundamentally the "can't get a little pregnant" problem. This code in Winforms is locked up heavily since it would allow CAS bypass. The only workaround is to display a Form derived class that contain Winforms controls with its ShowDialog() method. That method pumps a modal message loop, the Winforms one, good enough to revive the shortcut keystroke handling code. Restructure your approach by converting the main windows first, dialogs last.
Another solution I found to handle focus on the Tab key is to override OnKeyDown like this:
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyCode == Keys.Tab)
{
HandleFocus(this, ActiveControl);
}
else
{
base.OnKeyDown(e);
}
}
internal static void HandleFocus(Control parent, Control current)
{
Keyboard keyboard = new Keyboard();
// Move to the first control that can receive focus, taking into account
// the possibility that the user pressed <Shift>+<Tab>, in which case we
// need to start at the end and work backwards.
System.Windows.Forms.Control ctl = parent.GetNextControl(current, !keyboard.ShiftKeyDown);
while (null != ctl)
{
if (ctl.Enabled && ctl.CanSelect)
{
ctl.Focus();
break;
}
else
{
ctl = parent.GetNextControl(ctl, !keyboard.ShiftKeyDown);
}
}
}
The advantage of this solution is that it doesn't require neither a WindowsFormsHost nor a message pump which can be a hassle to implement. But I don't know if it is possible to handle shortcuts keys like this because I didn't need it.
Check if IsTabStop="True" and TabIndex is assigned. For Alt + Key shortcut, try using the underscore (_) character instead of the ampersand (&).

C# textbox deletion question

I am working on a project that I use textbox as telnet terminal.
The terminal has "->" as the command prompt in the textbox.
Is there a way to disable the delete or backspace once it reach the "->" prompt?
I don't want to delete the command prompt.
Thanks
Dave is right.
The best way to do this is to make a label on the left side of the textbox that says ->.
You can remove the textbox's border and put them both in a white (or non-white) box to make it look real.
This will be much easier for you to develop and maintain, and will also be more user-friendly. (For example, the Home key will behave better)
Two options:
Make the prompt ("->") an image or label, instead of being part of the textbox.
If it's a web app, handle the textchanged event in javascript and cancel the textchanged if it represents a deletion of the prompt. If its not a web app, do the same thing in c# rather than JS.
You could always make sure that when deleting, the index of the character you're deleting is > 1 (since -> would occupy positions 0 & 1)
This is a naive example, but you should be able to figure it out from here. You can peak at the keydown event and cancel it, when desired.
private void testTextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Back && testTextBox.SelectionStart == 2)
{
e.SuppressKeyPress = true;
}
}

Categories