ScintillaNET: how to get surrounding symbols of a clicked word - c#

I'm using ScintillaNET in VisualStudio/C#.
When the user clicks (LMB or RMB) a specific word inside the text, I need to get the surrounding symbols. For example:
This is <a test> to show my <problem>
In this case, if the user clicks over the word "test", I want to retrieve the entire block between "<" and ">", so I need to get <a test>.
If the user clicks over "problem" I need to get <problem>.
I know that I can get the caret position then "navigate" (for loop) before the position (going left) to find the first occurence of "<", then "navigate" after the caret position (going right) to find the first occurrence of ">".
But is there any other better way to achieve this? Does Scintilla supply some methods to find them?
Thank you for your help!

I used this code to find a workaround, but frankly speaking I wish to find a better solution:
const char CHAR_START_BLOCK = '[';
const char CHAR_END_BLOCK = ']';
if(e.Button == MouseButtons.Right) {
int leftPos = -1;
int rightPos = -1;
//
// Search for CHAR_START_BLOCK
//
for(int i = scintilla1.CurrentPosition; i >= 1; i--) {
if(scintilla1.GetCharAt(i) == CHAR_START_BLOCK) {
leftPos = i;
break;
}
if(scintilla1.GetCharAt(i) == '\r') {
break;
}
if( (scintilla1.GetCharAt(i) == CHAR_END_BLOCK) && (i != scintilla1.CurrentPosition)) {
break;
}
}
if(leftPos != -1) {
//
// Search for CHAR_END_BLOCK
//
string currentLine = scintilla1.Lines[scintilla1.CurrentLine].Text;
for(int i = scintilla1.CurrentPosition; i <= (scintilla1.CurrentPosition + currentLine.len()); i++) {
if(scintilla1.GetCharAt(i) == CHAR_END_BLOCK) {
rightPos = i;
break;
}
}
LogManager.addLog("LEFT/RIGHT: " + scintilla1.GetTextRange(leftPos, (rightPos + 1 - leftPos)));
}
}

Related

Preserve word index on TextSelectionChanged

I've got a find next and previous function and edited it so that when the user selects text in a textbox and clicks on either Find Next or Find Previous button, the find feature will start it's index from the selected character and go through each search result (initially the feature wasn't there). To get the starting index of the selected text I created a function:
private int GetIntialCharPos(string Text)
{
int row = Variables._TextBox.GetLineIndexFromCharacterIndex(Variables._TextBox.CaretIndex);
int col = Variables._TextBox.CaretIndex - Variables._TextBox.GetCharacterIndexFromLineIndex(row);
return col;
}
The function which does the Find Next and Previous goes as follows:
private List<int> _matches;
private string _textToFind;
private bool _matchCase;
private int _matchIndex;
private void MoveToNextMatch(string textToFind, bool matchCase, bool forward)
{
if (_matches == null || _textToFind != textToFind || _matchCase != matchCase)
{
int startIndex = 0, matchIndex;
StringComparison mode = matchCase ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase;
_matches = new List<int>();
while (startIndex < Variables._TextBox.Text.Length && (matchIndex = Variables._TextBox.Text.IndexOf(textToFind, startIndex, mode)) >= 0)
{
_matches.Add(matchIndex);
startIndex = matchIndex + textToFind.Length;
}
_textToFind = textToFind;
_matchCase = matchCase;
_matchIndex = forward ? _matches.IndexOf(GetIntialCharPos(textToFind)) : _matches.IndexOf(GetIntialCharPos(textToFind)) - 1;
}
else
{
_matchIndex += forward ? 1 : -1;
if (_matchIndex < 0)
{
_matchIndex = _matches.Count - 1;
}
else if (_matchIndex >= _matches.Count)
{
_matchIndex = 0;
}
}
if (_matches.Count > 0)
{
Variables._TextBox.SelectionStart = _matches[_matchIndex];
Variables._TextBox.SelectionLength = textToFind.Length;
Variables._TextBox.Focus();
}
}
My issue is that once the user has selected the text he needs to search, and goes through the find next and previous buttons, and then he decides to select the text from a different index, rather than continuing the search from the selected index, it will maintain the default initial order which it goes by rather than starting from the selected index and going through each result from that. I created a small gif video here so you can take a better look at this problem.
How do I preserve the selected word index so every time the user selects from a different index it can start the search from the index in which the user selected rather than always starting from the start.
private int _matchIndex;
That's your problem variable. It retains the last match index but you don't know when the user changes it by himself. The TextBox class does not have a SelectionChanged event to tell you about it so there is no simple way to reset the variable.
Simply use a RichTextBox instead, it does have that event.
But it is much easier, this bug occurred because you added state unnecessarily by splitting of the searching operation into a separate class. State is in general a bad thing, it is a bug generator. You can trivially make the entire operation stateless by using the TextBox object directly:
private static void MoveToNextMatch(TextBoxBase box, bool forward) {
var needle = box.SelectedText;
var haystack = box.Text;
int index = box.SelectionStart;
if (forward) {
index = haystack.IndexOf(needle, index + 1);
if (index < 0) index = haystack.IndexOf(needle, 0);
}
else {
if (index == 0) index = -1;
else index = haystack.LastIndexOf(needle, index - 1);
if (index < 0) index = haystack.LastIndexOf(needle, haystack.Length - 1);
}
if (index >= 0) {
box.SelectionStart = index;
box.SelectionLength = needle.Length;
}
box.Focus();
}
Use the Last/IndexOf() method that takes a StringComparison to implement the matchCase argument.

How to look for unknown character by richTextBox.find in c#?

There is a richTextBox on a windows form in C#. I'm looking for a special string by find method.
For example I'm looking for 'cat' string in the text. As you know there could be a lot of string depends on cat like: cat or cats or cat's or Cat or CAT or cat. or cat, or cat: or cat\n or even something like category or catalyst or Catia and other types. How can I look for just cat as an animal like cat, or cat: or cat. or cat or cat's or cats and etc. Is there any character that I can use instead? something like ' cat?' or ' cat*' to look for all of characters.
This is the method I'm using:
private void treeView1_AfterCheck(object sender, TreeViewEventArgs e)
{
if (e.Node.Checked)
{
CheckedNodes.Add(e.Node);
}
else
{
CheckedNodes.Remove(e.Node);
}
richTextBox1.SelectAll();
richTextBox1.SelectionBackColor = Color.White;
richTextBox1.DeselectAll();
for (int counter = 0; counter < CheckedNodes.Count; counter++)
{
int location = 0;
while (location != -1)
{
location = FindMyText(" " + CheckedNodes[counter].Text + "", location + 1, richTextBox1.TextLength);
if (location != -1)
{
FindMyText(CheckedNodes[counter].Text, location + 1, richTextBox1.TextLength);
richTextBox1.SelectionBackColor = CheckedNodes[counter].ForeColor;
}
}
}
richTextBox1.DeselectAll();
}
public int FindMyText(string searchText, int searchStart, int searchEnd)
{
// Initialize the return value to false by default.
int returnValue = -1;
// Ensure that a search string and a valid starting point are specified.
if (searchText.Length > 0 && searchStart >= 0)
{
// Ensure that a valid ending value is provided.
if (searchEnd > searchStart || searchEnd == -1)
{
// Obtain the location of the search string in richTextBox1.
int indexToText = richTextBox1.Find(searchText, searchStart, searchEnd, RichTextBoxFinds.MatchCase);
// Determine whether the text was found in richTextBox1.
if (indexToText >= 0)
{
// Return the index to the specified search text.
returnValue = indexToText;
}
}
}
return returnValue;
}
Here you can find an Enhanced Rich text box control, the only problem that its in VB.net,
http://www.codeproject.com/Articles/32793/RichTextBox-Control-with-Find-functionality
but it solves your problem.

Parsing user input as a user types in a winforms Textbox control

I am trying to create some sort of license verification text box that automatically splits the user inputs into chunks separated by hyphens. My license is 25 characters long and it separated as such:
XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
I have come up with the following code to parse the user input while he is typing or through copy/paste by handling the TextChanged event of the Textbox Control like so:
public static string InsertStringAtInterval(string source, string insert, int interval)
{
StringBuilder result = new StringBuilder();
int currentPosition = 0;
while (currentPosition + interval < source.Length)
{
result.Append(source.Substring(currentPosition, interval)).Append(insert);
currentPosition += interval;
}
if (currentPosition < source.Length)
{
result.Append(source.Substring(currentPosition));
}
return result.ToString();
}
private bool bHandlingChangeEvent = false;
private void txtLicense_TextChanged(object sender, EventArgs e)
{
if (bHandlingChangeEvent == true)
return;
bHandlingChangeEvent = true;
string text = txtLicense.Text.Replace("-", "").Replace(" ","");
int nPos = txtLicense.SelectionStart;
if((text.Length==5||text.Length==10||text.Length==15||text.Length==20) && txtLicense.Text[txtLicense.Text.Length-1]!='-')
{
txtLicense.Text += "-";
txtLicense.SelectionStart = nPos + 1;
}
if(text.Length>=25)
{
string tmp = text.Substring(0, 25);
tmp = InsertStringAtInterval(tmp, "-", 5);
txtLicense.Text = tmp;
txtLicense.SelectionStart = nPos;
}
bHandlingChangeEvent = false;
}
While this is working perfectly when I user types and pastes inside the box. My only problem is that when the user tries to delete characters from the entered key by either pressing backspace or delete.
Due to the forced hyphen insertion # positions 5,10,15,20 once a user reaches one of these marks on a backspace press the logic above forces the hyphen addition to the string and the user cannot go beyond that.
I tried fiddling with KeyDown events but couldn't come up with anything useful. can someone help please.
I also tried using a MaskedTextbox but that thing is ugly as I don't want the mask/hyphens to be visible on focus and I certainly don't want to replace the prompt with white spaces as it will give some confusion when clicking inside the box as the cursor will not always position at the beginning of the box when its "supposedly" empty.
This sort of thing has been the cause of a lot of suffering for me over the years. The way I approach it is to treat each change to the text as if it were pasted from scratch - I strip out the hyphens, then put them back in in the appropriate position. Then you can just handle the special case where the user has deleted the last character. In this case, you compare the text from just before the TextChanged event to the text afterward. If they are the same, but the previous text is one character longer, just don't do anything special.
Here is some code that seems to work for text entry, cut/copy/paste, etc. It could be improved with a little fancy LINQ, some error handling, etc. but hopefully it gets the idea across.
private string previousText = string.Empty;
private bool processing = false;
private void textBox1_TextChanged(object sender, EventArgs e)
{
// If we're already messing around with the text, don't start again.
if (processing)
return;
processing = true;
// The current textbox text
string newText = textBox1.Text;
// What was there before, minus one character.
string previousLessOne = previousText == string.Empty ? string.Empty : previousText.Substring(0, previousText.Length - 1);
// Get where the user is, minus any preceding dashes
int caret = textBox1.SelectionStart - (textBox1.Text.Substring(0, textBox1.SelectionStart).Count(ch => ch == '-'));
// If the user has done anything other than delete the last character, ensure dashes are placed in the correct position.
if (newText.Length > 0 && newText != previousLessOne)
{
textBox1.Text = string.Empty;
newText = newText.Replace("-", "");
for (int i = 0; i < newText.Length; i += 5)
{
int length = Math.Min(newText.Length - i, 5);
textBox1.Text += newText.Substring(i, length);
if (length == 5)
textBox1.Text += '-';
}
}
// Put the user back where they were, adjusting for dashes.
textBox1.SelectionStart = caret + (caret / 5);
previousText = textBox1.Text;
processing = false;
}
You can try this version:
void txtLicense_KeyDown(object sender, KeyEventArgs e) {
if (e.KeyCode == Keys.Back) {
int index = txtLicense.SelectionStart;
while (index > 0 && txtLicense.Text[index - 1] == '-') {
--index;
}
if (index > 0) {
--index;
}
txtLicense.Select(index, txtLicense.SelectionLength + Math.Max(index, 1));
txtLicense.SelectedText = string.Empty;
e.SuppressKeyPress = true;
}
}
I've done something similar to this before. What I would do is not add a hyphen until there is a character after its insertion point. For example, instead of adding a hyphen after 5 chars have been entered, do it at 6 so that it is never added at the end.
Here is the algorithm I use to insert hyphens at specific places:
public static string FormatLicenseNumber(string str)
{
if (string.IsNullOrEmpty(str))
return str;
else
{
str = str.Replace("-", string.Empty);
if (str.Length > 20)
str = str.Insert(20, "-");
if (str.Length > 15)
str = str.Insert(15, "-");
if (str.Length > 10)
str = str.Insert(10, "-");
if (str.Length > 5)
str = str.Insert(5, "-");
return str;
}
}

Numbered list on Richtextbox

I'm trying to add numbered list functionality to a text editor. RichTextbox already provides the SelectionBullet property to change a selection to a bulleted list. But i was unable to find a similar property to generate numbered list. Is there any standard way to create a numbered list on Richtextbox. If not, i would have to implement it myself so code snips that could help me do that will help, Thank you.
I know that a link is not gernerally accepted as a good answer, however the article RichTextBox with Search Line Numbering, Bulleting, Printing, Searching Support on CodeProject could probably help you out quite a bit with what you are looking for.
In this article, the author extends the RichTextBox control into something that can do what you are asking (and more), plus the code is posted there for all to see.
Well, i implemented it as follows.
private void btnNumbers_Click(object sender, EventArgs e)
{
string temptext = rtbMain.SelectedText;
int SelectionStart = rtbMain.SelectionStart;
int SelectionLength = rtbMain.SelectionLength;
rtbMain.SelectionStart = rtbMain.GetFirstCharIndexOfCurrentLine();
rtbMain.SelectionLength = 0;
rtbMain.SelectedText = "1. ";
int j = 2;
for( int i = SelectionStart; i < SelectionStart + SelectionLength; i++)
if (rtbMain.Text[i] == '\n')
{
rtbMain.SelectionStart = i + 1;
rtbMain.SelectionLength = 0;
rtbMain.SelectedText = j.ToString() + ". ";
j++;
SelectionLength += 3;
}
}
private void rtbMain_KeyDown(object sender, KeyEventArgs e)
{//this piece of code automatically increments the bulleted list when user //presses Enter key
int tempNum;
if (e.KeyCode == Keys.Enter)
try
{
if (char.IsDigit(rtbMain.Text[rtbMain.GetFirstCharIndexOfCurrentLine()]))
{
if (char.IsDigit(rtbMain.Text[rtbMain.GetFirstCharIndexOfCurrentLine() + 1]) && rtbMain.Text[rtbMain.GetFirstCharIndexOfCurrentLine() + 2] == '.')
tempNum = int.Parse(rtbMain.Text.Substring(rtbMain.GetFirstCharIndexOfCurrentLine(),2));
else tempNum = int.Parse(rtbMain.Text[rtbMain.GetFirstCharIndexOfCurrentLine()].ToString());
if (rtbMain.Text[rtbMain.GetFirstCharIndexOfCurrentLine() + 1] == '.' || (char.IsDigit(rtbMain.Text[rtbMain.GetFirstCharIndexOfCurrentLine() + 1]) && rtbMain.Text[rtbMain.GetFirstCharIndexOfCurrentLine() + 2] == '.'))
{
tempNum++;
rtbMain.SelectedText = "\r\n" + tempNum.ToString() + ". ";
e.SuppressKeyPress = true;
}
}
}
catch{}
}
Here is my answer... which is easily readable and refineable. I took a much different approach but added the ability to remove the numbered list within the selection if it already exists. Please note that so far I have only lightly tested it and it seems to work good... but it may need further refinement.
private void btnOrdered_Click(object sender, EventArgs e)
{
string[] splitSelection = null;
// If selection split selection else split everything
if (this.txtCaptionEditor.SelectionLength > 0)
{
splitSelection = this.txtCaptionEditor.SelectedText.Replace("\r\n", "\n").Split("\n".ToCharArray());
}
else
{
splitSelection = this.txtCaptionEditor.Text.Replace("\r\n", "\n").Split("\n".ToCharArray());
}
bool Exists = false;
for (int i = 0; i < splitSelection.GetLength(0); i++)
{
// If Ordered List Allready exists in selection then remove else add
if (!string.IsNullOrEmpty(splitSelection[i]))
{
if (splitSelection[i].Substring(0, 2) == "1.") { Exists = true; }
}
}
for (int i = 0; i < splitSelection.GetLength(0); i++)
{
int lineCount = (i + 1);
if (Exists)
{
this.txtCaptionEditor.Text = this.txtCaptionEditor.Text.Replace(Convert.ToString(lineCount) + ". ", "");
}
else
{
if(!string.IsNullOrEmpty(splitSelection[i]))
{
this.txtCaptionEditor.Text = this.txtCaptionEditor.Text.Replace(splitSelection[i], Convert.ToString(lineCount) + ". " + splitSelection[i]);
}
}
}
}
private void txtCaptionEditor_KeyDown(object sender, KeyEventArgs e)
{
string[] splitSelection = this.txtCaptionEditor.Text.Replace("\r\n", "\n").Split("\n".ToCharArray());
if (e.KeyCode == Keys.Enter)
{
// Get Current Line Position
int currentLine = this.txtCaptionEditor.GetLineFromCharIndex(this.txtCaptionEditor.SelectionStart);
// Only Run if the previous line is greater than zero
if ((currentLine) >= 0)
{
// Loop through 100 possible numbers for match you can go higher
// If you think your numbered list could go above 100
for (int i = 0; i < 100; i++)
{
if (splitSelection[(currentLine)].Substring(0, 2) == Convert.ToString((i + 1)) + ".")
{
// If the substring of the current line equals a numbered list value.. enumerate next line
this.txtCaptionEditor.SelectedText = "\n" + (i + 2) + ". ";
e.SuppressKeyPress = true;
}
}
}
}
}

WPF TextBox ScrollToLine not updating if visible

I have a Navigation-bar in my program that allows you to navigate the different sections in my TextBox, but the problem I have is that this doesn't work if the Text I am scrolling to is already visible on the screen.
Like in this example, if I try to jump from Section 1 to Section 3, it won't work as it's already visible.
But, in this example if I jump to Section 3, it works fine as it's not already visible.
The scrolling function I use is very simple:
if (nLine > 0 && nLine <= textBox.LineCount)
textBox.ScrollToLine(nLine - 1);
I hope that someone can shed some light on an alternative solution that allows me to scroll even if the text is already visible.
Edit: Added solution.
This is a code snippet from my project.
private static void ScrollToLineCallback(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
var textBox = (TextBox)target;
int newLineValue;
if (Int32.TryParse(e.NewValue.ToString(), out newLineValue))
{
if (newLineValue > 0 && newLineValue <= textBox.LineCount) // Validate
{
textBox.ScrollToLine(newLineValue - 1); // Scroll to Line
// Check and see if we are at the line we want.
if (textBox.GetFirstVisibleLineIndex() <= newLineValue && textBox.GetLastVisibleLineIndex() >= newLineValue)
{
// If not lets move to the desired location
int newLineCorrectionValue = newLineValue - textBox.GetFirstVisibleLineIndex() - 2; // How much further do we need to scroll down?
for (int i = 0; i < newLineCorrectionValue; i++)
{
textBox.LineDown(); // Scroll down
}
}
}
}
}
You could use GetCharacterIndexFromLineIndex to get the index of the beginning of the desired line and then set the CaretIndex to that value.
Because I don't really know, what you are trying to achieve, another possibility is to use LineUp and LineDown in conjunction with GetFirstVisibleLineIndex and GetLastVisibleLineIndex.
private void TextBoxGotoLine(
TextBox textbox1,
int linenum)
{
var target_cpos = textbox1.GetCharacterIndexFromLineIndex(linenum);
var target_char_rect = textbox1.GetRectFromCharacterIndex(target_cpos);
var first_char_rect = textbox1.GetRectFromCharacterIndex(0);
textbox1.ScrollToVerticalOffset(
target_char_rect.Top -
first_char_rect.Top
);
}
I found out if Wrapping is enabled its more complications:
private void TextBoxGotoLine(TextBox textbox1, int linenum)
{
// int Linenum is the Absolute Line, not including
// effect of Textbox Wrapping.
if (textbox1.TextWrapping == TextWrapping.Wrap)
{
// If textbox Wrapping is on, we need to
// Correct the Linenum for Wrapping which adds extra lines
int cidx = 0;
bool found = false;
int ln = 0;
char[] tmp = textbox1.Text.ToCharArray();
for (cidx = 0; cidx < tmp.Length; cidx++)
{
if (tmp[cidx] == '\n')
{
ln++;
}
if (ln == linenum)
{
found = true;
break;
}
}
if (!found)
return;
linenum = textbox1.GetLineIndexFromCharacterIndex(cidx+1);
}
// Non-Wrapping TextBox
var target_cpos = textbox1.GetCharacterIndexFromLineIndex(linenum);
var target_char_rect = textbox1.GetRectFromCharacterIndex(target_cpos);
var first_char_rect = textbox1.GetRectFromCharacterIndex(0);
textbox1.ScrollToVerticalOffset(target_char_rect.Top - first_char_rect.Top);
}

Categories

Resources