C#: Unable to undo inserted text - c#

I am programatically adding text in a custom RichTextBox using a KeyPress event:
SelectedText = e.KeyChar.ToString();
The problem is that inserting text in such a way doesn't trigger the CanUndo flag.
As such, when I try to Undo / Redo text (by calling the Undo() and Redo() methods of the textbox), nothing happens.
I tried programatically evoking the KeyUp() event from within a TextChanged() event, but that still didn't flag CanUndo to true.
How can I undo text that I insert without having to create lists for Undo and Redo operations ?
Thanks

I finally decided to create my own undo-redo system using stacks.
Here's a quick overview of how I did it :
private const int InitialStackSize = 500;
private Stack<String> undoStack = new Stack<String>(InitialStackSize);
private Stack<String> redoStack = new Stack<String>(InitialStackSize);
private void YourKeyPressEventHandler(...)
{
// The user pressed on CTRL - Z, execute an "Undo"
if (e.KeyChar == 26)
{
// Save the cursor's position
int selectionStartBackup = SelectionStart;
redoStack.Push(Text);
Text = undoStack.Pop();
// Restore the cursor's position
SelectionStart = selectionStartBackup;
}
// The user pressed on CTRL - Y, execute a "Redo"
if (e.KeyChar == 25)
{
if (redoStack.Count <= 0)
return;
// Save the cursor's position
int selectionStartBackup = SelectionStart + redoStack.ElementAt(redoStack.Count - 1).Length;
undoStack.Push(Text);
Text = redoStack.Pop();
// Restore the cursor's position
SelectionStart = selectionStartBackup;
return;
}
undoStack.Push(Text);
SelectedText = e.KeyChar.ToString();
}

It's just an idea but what if you set the caret position to where you would insert your text and instead of modifying the Text property, just send the keys?
SendKeys.Send("The keys I want to send");
There are bound to be quirks but as I said, it's just an idea.

You can use TestBox.Paste. The documentation in the class overview, saying "Sets the selected text to the specified text without clearing the undo buffer.", seems confusing. I have just tried it and it sets the Undo as expected.
Is spite of its name it has no relation to Clipboard at all, it just replaces the currently selected text with the text you provide as an argument, and therefore seems just to do what the question asks for, in very simple manner.

Related

C# Window From Autoselects Text

I have not dealt with WinForms for a long time.
Now I'm stuck with something trivial but cannot figure it out.
I have a Winform and when a Timer Tick happens I want to show a message in a new form message box:
frmMessage frmM = new frmMessage();
frmM.txtMessage.Text = ConfigurationSettings.AppSettings["Message"];
frmM.Show();
It works but the text in the textbox shows as selected(with a blue background).
I tried
txtMessage.SelectionLength = 0;
Did not help.
Also tried to set focus to a different control, did not help either.
for now, as a workaround, I will use a Label.
This is a consequence of the way TextBox Class is implemented. If a selection is not specifically set, all text will be selected when the control gets focus.
From TextBox.OnGotFocus:
Protected override void OnGotFocus(EventArgs e) {
base.OnGotFocus(e);
If (!selectionSet) {
// We get one shot at selecting when we first get focus. If we don't
// do it, we still want to act Like the selection was set.
selectionSet = true;
// If the user didn't provide a selection, force one in.
If (SelectionLength == 0 && Control.MouseButtons == MouseButtons.None) {
SelectAll();
}
}
Additionally due to the way the SelectionLength Property is implemented, setting that property to zero does not set the selectionSet` flag as it is already zero.
Instead, set the TextBox.SelectionStart Property immediately after setting the text as this will set that flag.
txtMessage.SelectionStart = 0;
However, your work-a-round of using a Label to display a message is much more appropriate than using an input control.
This is not the best answer but it works. You can try this
frmMessage frmM = new frmMessage();
frmM.txtMessage.Text = "";
frmM.txtMessage.AppendText(ConfigurationSettings.AppSettings["Message"]);
frmM.Show();

Get caret position in RichTextBox_Click event

I'm working on a text editor which includes a RichTextBox. One of the features that I want to implement is to show in a TextBox the current Line and Column of the caret of the forementioned RichTextBox at any moment.
Here's part of the code that I use (the rest of my code has nothing to do with my issue):
int selectionStart = richTextBox.SelectionStart;
int lineFromCharIndex = richTextBox.GetLineFromCharIndex(selectionStart);
int charIndexFromLine = richTextBox.GetFirstCharIndexFromLine(lineFromCharIndex);
currentLine = richTextBox.GetLineFromCharIndex(selectionStart) + 1;
currentCol = richTextBox.SelectionStart - charIndexFromLine + 1;
At this point, I should mention that when someone is using a RichTextBox, there are three ways that the caret can change location:
By changing the Text of the RichTextBox
By using the arrow keys on the keyboard
By clicking anywhere on the RichTextBox
The code that I posted above works with no issues in the first two cases. However, it doesn't really work in the third case.
I tried using the Click event and I noticed that the selectionStart variable would always get the value of 0, which means that I always get the same and wrong results. Moreover, using the same code on other events like MouseClick and MouseUp did not solve my problem since selectionStart is 0 even in the duration of these events.
So, how can I get the current Line and column everytime the user clicks on the RichTextBox?
You want something like:
private void richTextBox1_MouseUp(object sender, MouseEventArgs e)
{
RichTextBox box = (RichTextBox)sender;
Point mouseLocation = new Point(e.X, e.Y);
box.SelectionStart = box.GetCharIndexFromPosition(mouseLocation);
box.SelectionLength = 0;
int selectionStart = richTextBox.SelectionStart;
int lineFromCharIndex = box.GetLineFromCharIndex(selectionStart);
int charIndexFromLine = box.GetFirstCharIndexFromLine(lineFromCharIndex);
currentLine = box.GetLineFromCharIndex(selectionStart) + 1;
currentCol = box.SelectionStart - charIndexFromLine + 1;
}
It seems to me that what you really want is to handle the TextBoxBase.SelectionChanged event. Then any action that causes the selection to change will invoke your code, and as an added benefit the current selection will have been updated by the time your event handler is called, and you'll be assured of getting correct values.
If that does not address your specific need, then I must not be understanding the question. In that case, please provide a good, minimal, complete code example that shows clearly what you're trying to do, with a precise description of what that code does and how that's different from what you want it to do.

How do I remove the old data of a RichTextBox without moving scrollbar/ cursor

I create a richtexbox for realtime log. If length of richtexbox is too large, I 'll remove old data ( such as 1/3 current data ).
Content.Invoke(new EventHandler(delegate
{
Content.AppendText(msg);
if(Content.Text.Length >100000)
{
Content.Select(0, 50000);
Content.SelectedText = "";
}
}));
My problem is whenever I remove data, scrollbar go up ( select text ) and go down ( append new text). Is there anyway to prevent scrollbar going up ?
I tried this in a timer and it looks fine to me. Obviously the scrollbar grows when you remove text, but that is supposed to happen..
private void timer1_Tick(object sender, EventArgs e)
{
richTextBox1.SuspendLayout();
richTextBox1.AppendText( richTextBox1.Text.Length.ToString() +
" some random text of no consequence.....\n");
if (richTextBox1.Text.Length > 10000)
{
richTextBox1.Select(0, 2000);
richTextBox1.SelectedText = "";
}
richTextBox1.SelectionStart = richTextBox1.Text.Length;
richTextBox1.SelectionLength = 0;
richTextBox1.ScrollToCaret();
richTextBox1.ResumeLayout();
}
I assume that you always want the end of the RTB visible, so you have to bring the caret to the end and in sight. If you don't do that the caret will be at the top after the removal of the top portion of the text..
No need to store the caret position, unless you want it to stay at some other position, maybe because the user has put it there..? In that case however you must act accordingly and calculate the correct position!

Add text (push) to textbox or richtextbox?

I'm trying to create a chat application like msn. When i do "textBox.Text = textBox.Text+text" it updates the textbox and the text i got selected is no longer selected. In MSN you can have selected text and still recieve messages in different colors etc.. How do they do it? I figure its something like push messages, maybe they create a new textbox under another textbox? Any clues?
I hope you guys know what i'm talking about here. I just want my text to behave like MSN used to do, not update the whole textbox, just push a new message under the current message etc.
If I understand your question, you just want text to stay selected when you append messages to a RichTextBox?
int selectionStart = textBox.SelectionStart;
int selectionLength = textBox.SelectionLength;
int carat = textBox.TextLength;
textBox.Text += Environment.NewLine;
textBox.Text += newText;
//optional styling code for newly appended text
textBox.Select(carat, newText.Length);
textBox.SelectionColor = //value;
//etc.
//reapply original selection
if(selectionStart >= 0 && selectionLength > 0)
{
textBox.Select(selectionStart, selectionLength);
}

Why is text in TextBox highlighted (selected) when form is displayed?

I have a form containing a TextBox in C# which I set to a string as follows:
textBox.Text = str;
When the form is displayed, why does the text in the texbox appear highlighted/selected?
The text box has a TabIndex of 0 and TabStop set to true. This means that the control will be given focus when the form is displayed.
You can either give another control the 0 TabIndex (if there is one) and give the text box a different tab index (>0), or set TabStop to false for the text box to stop this from happening.
The default behavior of a TextBox in Windows Forms is to highlight all of the text if it gets focused for the first time by tabbing into it, but not if it is clicked into. We can see this in Reflector by looking at the TextBox's OnGotFocus() override:
protected override void OnGotFocus(EventArgs e)
{
base.OnGotFocus(e);
if (!this.selectionSet)
{
this.selectionSet = true;
if ((this.SelectionLength == 0) && (Control.MouseButtons == MouseButtons.None))
{
base.SelectAll();
}
}
}
It's that if statement that is causing the behavior that we don't like. Furthermore, to add insult to injury, the Text property's setter blindly resets that selectionSet variable whenever the text is re-assigned:
public override string Text
{
get
{
return base.Text;
}
set
{
base.Text = value;
this.selectionSet = false;
}
}
So if you have a TextBox and tab into it, all the text will be selected. If you click into it, the highlight is removed, and if you re-tab into it, your caret position (and selection length of zero) is preserved. But if we programmatically set new Text, and tab into the TextBox again, then all of the text will be selected again.
If you are like me and find this behavior annoying and inconsistent, then there are two ways around this problem.
The first, and probably the easiest, is to simply trigger the setting of selectionSet by calling DeselectAll() on form Load() and whenever the Text changes:
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.textBox2.SelectionStart = this.textBox2.Text.Length;
this.textBox2.DeselectAll();
}
(DeselectAll() just sets SelectionLength to zero. It's actually SelectionStart that flips the TextBox's selectionSet variable. In the above case, the call to DeselectAll() is not necessary since we are setting the start to the end of the text. But if we set it to any other position, like the start of the text, then calling it is a good idea.)
The more permanent way is to create our own TextBox with the desired behavior through inheritance:
public class NonSelectingTextBox : TextBox
{
// Base class has a selectionSet property, but its private.
// We need to shadow with our own variable. If true, this means
// "don't mess with the selection, the user did it."
private bool selectionSet;
protected override void OnGotFocus(EventArgs e)
{
bool needToDeselect = false;
// We don't want to avoid calling the base implementation
// completely. We mirror the logic that we are trying to avoid;
// if the base implementation will select all of the text, we
// set a boolean.
if (!this.selectionSet)
{
this.selectionSet = true;
if ((this.SelectionLength == 0) &&
(Control.MouseButtons == MouseButtons.None))
{
needToDeselect = true;
}
}
// Call the base implementation
base.OnGotFocus(e);
// Did we notice that the text was selected automatically? Let's
// de-select it and put the caret at the end.
if (needToDeselect)
{
this.SelectionStart = this.Text.Length;
this.DeselectAll();
}
}
public override string Text
{
get
{
return base.Text;
}
set
{
base.Text = value;
// Update our copy of the variable since the
// base implementation will have flipped its back.
this.selectionSet = false;
}
}
}
You maybe tempted to just not call base.OnGotFocus(), but then we would lose useful functionality in the base Control class. And you might be tempted to not mess with the selectionSet nonsense at all and simply deselect the text every time in OnGotFocus(), but then we would lose the user's highlight if they tabbed out of the field and back.
Ugly? You betcha. But it is what it is.
The answers to this question helped me a lot with a similar problem, but the simple answer is only hinted at with a lot of other complex suggestions. Just set SelectionStart to 0 after setting your Text. Problem solved!
Example:
yourtextbox.Text = "asdf";
yourtextbox.SelectionStart = 0;
You can also choose the tab order for your form's controls by opening:
View->Tab Order
Note that this option is only available in "View" if you have the Form design view open.
Selecting "Tab Order" opens a view of the Form which allows you to choose the desired tab order by clicking on the controls.
To unhighlight a text field, with VS 2013, try init with:
myTextBox.GotFocus += new System.EventHandler(this.myTextBox_GotFocus);
And add the method:
public void myTextBox_GotFocus(object sender, EventArgs e)
{
myTextBox.SelectionLength=0;
}
I haven't tested this on C# but I ran into the same issue using a C++ WIN32 dialog box. Is seems like you can change the behavior by returning FALSE from OnInitDialog() or WM_INITDIALOG. Hope this helps.
Here is what worked for me
public void SetNotes(string notes)
{
notesTextBox.Text = notes;
notesTextBox.Select();
notesTextBox.SelectionLength = 0;
notesTextBox.SelectionStart = notes.Length;//place cursor at end of text
}

Categories