C # and Winforms TextBox control: how do I get the text changed?
I have an event handler for the TextBox.TextChanged event on my form. To support cancellation, I would like to identify exactly what has changed in the TextBox so that I can undo the change if the user asks for it. (I know the inline textbox supports undo, but I would like to have one undo for the entire application)
Is there a sane way to do this? If not, is there a better way to support such a cancellation function?
EDIT: Seems like something like the following: Are there any better ideas? (It seems to me that I really would like .NET to have something like an STL algorithm std::mismatch
...
class TextModification
{
private string _OldValue;
public string OldValue
{
get
{
return _OldValue;
}
}
private string _NewValue;
public string NewValue
{
get
{
return _NewValue;
}
}
private int _Position;
public int Position
{
get
{
return _Position;
}
}
public TextModification(string oldValue, string newValue, int position)
{
_OldValue = oldValue;
_NewValue = newValue;
_Position = position;
}
public void RevertTextbox(System.Windows.Forms.TextBox tb)
{
tb.Text = tb.Text.Substring(0, Position) + OldValue + tb.Text.Substring(Position + NewValue.Length);
}
}
private Stack<TextModification> changes = new Stack<TextModification>();
private string OldTBText = "";
private bool undoing = false;
private void Undoit()
{
if (changes.Count == 0)
return;
undoing = true;
changes.Pop().RevertTextbox(tbFilter);
OldTBText = tbFilter.Text;
undoing = false;
}
private void UpdateUndoStatus(TextBox caller)
{
int changeStartLocation = 0;
int changeEndTBLocation = caller.Text.Length;
int changeEndOldLocation = OldTBText.Length;
while (changeStartLocation < Math.Min(changeEndOldLocation, changeEndTBLocation) &&
caller.Text[changeStartLocation] == OldTBText[changeStartLocation])
changeStartLocation++;
while (changeEndTBLocation > 1 && changeEndOldLocation > 1 &&
caller.Text[changeEndTBLocation-1] == OldTBText[changeEndOldLocation-1])
{
changeEndTBLocation--;
changeEndOldLocation--;
}
changes.Push(new TextModification(
OldTBText.Substring(changeStartLocation, changeEndOldLocation - changeStartLocation),
caller.Text.Substring(changeStartLocation, changeEndTBLocation - changeStartLocation),
changeStartLocation));
OldTBText = caller.Text;
}
private void tbFilter_TextChanged(object sender, EventArgs e)
{
if (!undoing)
UpdateUndoStatus((TextBox)sender);
}
a source to share
Yes, don't bind it directly to the text field. The state of your forms must be in some model object, somewhere that is not directly related to the form (MVC is one way to do this, MVVM is another). By unbinding them this way, you can compare the new value of the textbox with the current value of the model whenever a change request comes in.
a source to share
Actually, all I can think of is to have some sort of collection that stores different versions of the rows (so that you can undo many times, not just once). I would keep a reference to the TextBox collections in a TextBox.Tag, so it's easy to store / use.
Last but not least, you are updating your collection of strings during the TextChange event. Without a lot of work, you can keep the full history, get the previous value from your own structure.
a source to share
This is probably too large for what you are trying to accomplish, but CSLA supports n-level cancellation. CSLA is a great Business Object Infrastructure written by Rocky Lhotka. Business objects process the undo history and are brought into the user interface using data binding.
Switching your application to use CSLA would be a big commitment, but another option would be to look over the freely available source code to see how it implemented it.
a source to share
I'm actually doing my own syntax-highlighting system, so I need to know the modified text as well. My solution is to keep track of the input or space or cursor position. Since WinForms provide Keydown event, I used KeyEventArguments (e) and converted them to symbol.
After that, I store the character into a string like:
string i=""; i+=convertedToChar;//convertedToChar = kc.ConvertToString(e.KeyData)
And as soon as there is an input or a space or a deposit - "event" I delete the line.
Result: if the user enters multiple characters and presses a space, I can read the last characters (up to the last space). The advantage can be the fact that you can use any separator character for this (as soon as they are saved and provided with e.KeyCode)
However, I hope this is a solution for everyone watching this after 9 years: D. It's never too late.
a source to share