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.
Related
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();
I made a fully functional touch-keyboard within my WPF application using both XAML and C# (ofc). Currently, every key pressed on the keyboard registers the respective key in the text box; however, if the user touches the TextBox (which is welcomed...I do not want to prevent it) to position the cursor earlier in the TextBox to fix an error they might have typed AND THEN interact with the keys (as expected), the position they requested does not register and the keys continue to be entered on the end.
For example, user types(note the | acting as the cursor)::
Justin is awesome, bt that is because it is his birthday.|
User sees error in the word "but" spelled as "bt" so decides to touch the screen to position the cursor between b and t like so:
Justin is awesome, b|t that is because it is his birthday.
User expects then to touch U on the keyboard and it to be entered where the cursor appears to be...unfortunately (currently) it ends up like this:
Justin is awesome, bt that is because it is his birthday.U|
If I enable the mouse, the same thing happens (so it may not be exclusive to touch). If I plug in a keyboard after touching a position, I can type with said plugged-in keyboard no problem.
The implemented touch keyboard just wont read the position correctly. Looking at this, the action seems to go no where because the Cursor Position goes from the TextBox to a Button (keyboard keys).
Here's a snippet of code for anyone to test (obviously can't provide the full keyboard):
XAML
<Grid>
<TextBox x:Name="txtBox" />
<Button x:Name="btnQ" Content="q" Click="btnQ_Click"/>
<Button x:Name="btnW" Content="w" Click="btnW_Click"/>
</Grid>
C#
private bool numberHitSinceLastOperator = false;
private void HandleKeyboard(string key)
{
//Ternary operator
string valueSoFar = numberHitSinceLastOperator ? txtBox.Text : "";
string newValue = valueSoFar + key.ToString();
txtBox.Text = newValue;
numberHitSinceLastOperator = true;
}
private void btnQ_Click(object sender, RoutedEventArgs e)
{
HandleKeyboard("Q");
}
private void btnW_Click(object sender, RoutedEventArgs e)
{
HandleKeyboard("W");
}
Your problem is this line:
string newValue = valueSoFar + key.ToString();
You are always appending at the end. To append at the right position, you can do something like this:
int oldCaretIndex = txtBox.CaretIndex;
txtBox.Text = txtBox.Text.Insert(text.CaretIndex, "a");
//changing Text resets the caret position to 0
txtBox.CaretIndex = oldCaretIndex + 1;
You could also try simulating key presses, see for example here: How can I programmatically generate keypress events in C#?
Or, like the commenter suggested, try the SurfaceSDK (though I haven't used it myself).
EDIT - Sorry folks, i guess i wanted to "obscure" my work code too much... i don't know why it got so many downvotes but anyway. see below for update/edit with actual code.
I am trying to insert a piece of text into an existing section of a line (<data) which resides at the beginning of a line in my RichTextBox control. However, whenever i do that in the following manner:
private void AddSelectedIntellisense(object sender, EventArgs e)
{
ToolStripItem x = sender as ToolStripItem;
int cursorpos = this.txt_Body.SelectionStart;
string final = this.txt_Body.Text.Insert(cursorpos, x.Text);
//final var at breakpoint is equal to "<data log=\"Original\""
//then i assign it/that to the RTB.Text
this.txt_Body.Text = final;
//when checked with breakpoint, this.txt_Body.Text is equal to
//"log=\"\"<data log=\"Original\""
this.txt_Body.SelectionStart = cursorpos + x.Text.Length;
}
I am thinking that it is the < character that is causing issues when i assign the string to the .Text property (because if i replace the < with a [ in my logic, no problems), but i don't know how to fix it... if you could help me i would really appreciate it.
I also checked all of the indexes manually and they all lign up perfectly... so i don't know why the RTB.Text value is different than the string but if someone knows please tell me.
Cheers!
You are first setting:
txt = this.RTB1.Text.Substring(starts, length);
Then on the next line you are replacing the value of txt:
txt = this.RTB1.Text.Insert(index,"log='test'></data>");
You are probably looking to concatenate the strings:
string txt = this.RTB1.Text.Substring(starts, length);
txt += this.RTB1.Text.Insert(index,"log='test'></data>");
this.RTB1.Text = txt;
Ok folks... i suppose i'll give it to Aaron, since it's like somewhat related and nobody else answered.
The answer was:
I am using the RTB.On_TextChanged event to fire off the intellisense based on a condition. However, because i am also setting text the RTB.Text value within the Intellisense, the condition became true twice and added the specific text twice. So i setup a flag when i add intellisense text and check it in the on_textchanged event.
Cheers and sorry for the confusion.
I want to implement something that programmatically changes the background of the text when provided with a documentline.(Something that looks very similar to a block selection of a text. I'm going to be using this for debug breakpoints of an IDE I'm designing). I don't want to have to use selection as it causes the textbox to scroll.
I think I need to make use of DocumentColorizingTransformer but I'm not 100% sure how to go about this.
public class ColorizeAvalonEdit : ICSharpCode.AvalonEdit.Rendering.DocumentColorizingTransformer
{
protected override void ColorizeLine(ICSharpCode.AvalonEdit.Document.DocumentLine line)
{
int lineStartOffset = line.Offset;
string text = CurrentContext.Document.GetText(line);
int start = 0;
int index;
if (line.LineNumber == LogicSimViewCodeWPFCtrl.currentLine)
{
while ((index = text.IndexOf(text, start)) >= 0)
{
base.ChangeLinePart(
lineStartOffset + index, // startOffset
lineStartOffset + index + text.Length, // endOffset
(VisualLineElement element) =>
{
element.TextRunProperties.SetBackgroundBrush(Brushes.Red);
});
start = index + 1; // search for next occurrence
}
}
}
}
currentLine is the portion that will be highlighted.
The above code does work properly.. only problem is if the currentLine ever changes while I am viewing that line, it doesn't highlight the updated line until I scroll to another portion of the document (hiding the updated line), and come back to the updated line.
Also, how do I make the line numbers start from zero?
Since it was their creation, I peeked at SharpDevelop's source and how they did it.
They defined a bookmark type (BreakpointBookmark) and added bookmark to the line.
bookmark itself sets the color of the line in CreateMarker method. It is strange that it is not possible to configure colors of the break-point in SharpDevelop.
Hope it helps.
protected override ITextMarker CreateMarker(ITextMarkerService markerService)
{
IDocumentLine line = this.Document.GetLine(this.LineNumber);
ITextMarker marker = markerService.Create(line.Offset, line.Length);
marker.BackgroundColor = Color.FromRgb(180, 38, 38);
marker.ForegroundColor = Colors.White;
return marker;
}
I found the answer
TxtEditCodeViewer.TextArea.TextView.Redraw();
Isn't this a duplicate of this question?
However it looks like you should call InvalidateArrange() on the editor or InvalidateVisual() on each changed visual.
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.