Changing SelectionStart in TextChanging handler - c#

I'm writing a text control with input filtering based on TextBox.
The main idea consists of handling TextChanging event and set correct Text and SelectionStart values.
This worked until Windows 10 Creators Update. Now when I change SelectionStart in TextChanging handler I see correct caret position in UI but next character goes to the old place.
I can not share any code right now but you can reproduce it by adding arbitrary character on each TextChanging call (and setting SelectionStart after this character).
Have Microsoft broken the backward compatibility? Have I done anything wrong?
UPD: So I've found a dirty workaround:
async void OnTextChanging(TextBox sender, Text​Box​Text​Changing​Event​Args args)
{
var newText = HandleText(Text);
var newSelectionStart = HandleTextDiff(newText, Text);
Text = newText;
await Task.Yield();
SelectionStart = newSelectionStart;
}
Task.Yield makes the assignment of SelectionStart to run asynchronously after the control internal assignment.
That's very dirty and ugly because it causes caret blinking in some cases but the only working solution I can find.

Maybe you should try setting the SelectionLength to 0
Another option might be to use SetCaretPos Api call
[DllImport("User32.dll")]
static extern bool SetCaretPos(int x, int y);

Related

How to PgUp/PgDn with SendKeys to a foreground textbox

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;
}

Partially read-only textbox in c#

I've got a list of illegal positions and characters that can't move from those positions. How would I prevent those from being modified in the TextChanged event? Every solution I've come up with has been extremely hacky and unreliable, or has relied on the KeyDown event, which doesn't prevent the user from deleting the read-only text in other ways (select the text and delete it, or just press the backspace key).
I've thought of doing something like this:
//CharPos is a class with an int (CharPos.Position) and a char that
//should be at that int's position (CharPos.Ch)
foreach (CharPos p in IllegalPositions)
{
console.Text = console.Text.Remove(p.Position, 1);
console.Text = console.Text.Insert(p.Position, p.Ch.ToString());
}
But it completely messes up and goes on an infinite loop. And even if I stopped it from doing that, it wouldn't work if you changed the character count of the text, by using the delete key for example. Maybe I could use regex somehow? (I dunno, I have no experience at all with regex).
Can you combine multiple TextBoxes (for editable parts) with TextBlocks (for readonly parts)? You would just need to play with styling of the two to match
Could you please check the link below:
Disabling or making it readonly a part of the values in a Text Box (.net)
rtBox.Select(0, (rtBox.Text = "I am fixed content").Length);
rtBox.SelectionProtected = true;
rtBox.AppendText(Environment.NewLine);

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);
}
}

TextBox Scrollbar. Scrolling to end of TextBox within Thread

I've tried all the answers suggest in Stack Overflow to get my scrollbar to move to the bottom as text is being updated, but I have a feeling its not working because it's within a thread. My code is below ...
foreach(HtmlAgilityPack.HtmlNode paginationUser in paginationUsers) {
String userUrl = paginationUser.GetAttributeValue("id","");
this.Invoke((MethodInvoker)delegate {
txtLog.AppendText("...... Added " + userUrl + Environment.NewLine);
txtLog.Select(txtLog.Text.Length, 0);
txtLog.ScrollToCaret();
});
}
Is it the thread thats causing the code not to work? And what's a better solution?
Try to add this code :
TextBox.SelectionStart = txtLogEntries.Text.Length;
TextBox.ScrollToCaret();
at onTextChanged TextBox event .
I feel using this code is better:
TextBox.AppendText("your text")
it will automatically scroll to the end of the newly appended text & the auto scrolling animation seems more smoother compared to TextBox.ScrollToCaret() method
you can put this code at TextChanged TextBox event
I am having the same problem with WPF, using a thread to write to the textbox. It works fine until I add the ScrollToEnd.
I have no solution, just some remarks.
You are not locking the control. You should if you are filling it from a tread.
If I use Invoke it does work (but the UI becomes unresponsive). I use BeginInvoke, which is smoother but then it totally locks up if I use ScrollToEnd.
It seems to be some kind of triggering issue, one event causing the other.
Try feeding text slowly and see what happens, the worker thread may be flooding the textbox, giving it a really hard time, not allowing the main thread to do its thing.

How to get a textbox to only display Uppercase letters?

Using Windows Mobile 6.5 and C#
The CharacterCasing property seems to be missing from WinMo 6.5 and I decide to just catch the textchanged event and set the text with ToUpper.
This works - but on every keypress, it sends the cursor back to the beginning of the string, not the end.
I know this is old, but hopefully this can help somebody else. I implemented the KeyPress event like the following.
private void TextBox_KeyPress(object sender, KeyPressEventArgs e)
{
e.KeyChar = Char.ToUpper(e.KeyChar);
}
Ritu, just to comment on your answer. You should keep in mind that this might be confusing for a user if the user has positioned the caret in the middle of the string to perform some edit and then the caret jumps to the end of the string on the key press.
An alternative might be to change the text to upper case when the edit control loses focus.
Try here such implementation.
public MainForm()
{
InitializeComponent();
InitializeUpperCaseTextBox();
}
private void InitializeUpperCaseTextBox()
{
txtbox.CharacterCasing = CharacterCasing.Upper;
//... etc.
}
The soluttion of setting the text position to the end of the string seems like would be a hassle if you ever need to edit text that you have already entered.
It's been a while since I thought about the C# event model but, one alternative might be to catch the KeyPress event and change any lowercase KeyChar values to uppercase before passing them on to the next handler.
The way you are approaching seems to be wrong.
There are so many different ways to insert data into that textbox. What about copy & paste for instance?
Just perform a .Text.ToUpper() when accessing the value of the Textbox
Save the SelectionStart and SelectionLength before changing the text. The ToUpper should make no changes to the length, so you can simply set the SelctionStart and SelectionLength back to what they had been.
Also, I would expect to get a changed event again when you set it ToUpper. I'm not sure if you also need to check that the ToUpper actually changed anything before you set Text again. It may be smart enough to check that for you when you assign the text and avoiding giving you an infinite recursive loop of change events. However, you probably don't want to alter the selection in the event handler call for the case where you aren't making a further change, only in the outer call where you are assigning back to Text. So you might as well guard recursion directly.
Something like:
bool m_InMyTextChanged = false;
private void txtMyText_TextChanged(object sender, EventArgs e)
{
if (m_InMyTextChanged)
return; // Recursive! We can bail quickly.
m_InMyTextChanged = true; // Prevent recursion when we change it.
int selectionStart = txtMyText.SelectionStart;
int selectionLength = txtMyText.SelectionLength;
string originalText = txtMyText.Text;
string newText = originalText.ToUpper();
if (newText != originalText)
{
txtMyText.Text = newText; // Will cause a new TextChanged event.
// Set the selection back *after* the assignment, which has reset them.
txtMyText.SelectionStart = selectionStart;
txtMyText.SelectionLength = selectionLength;
}
m_InMyTextChanged = false; // Allow it for next time.
}
could work. I haven't worked in Windows Mobile, but I would think this would work the same as in general for .NET.
I figured it out. So on the textChanged event, I replace the entered text with the ToUpper version. Then I set the SelectionStart property to the Text.Length to move the cursor to the end.

Categories