I am trying to highlight words in richtextbox. When user mouse over the word in richtextbox the word should be highlighted.
Below is the code I am so far. Errors: last highlight point is good but start point is not accurate, When i place a new paragrah or new line then start point goes so away then expected result.
private void richTextBox1_MouseMove(object sender, MouseEventArgs e)
{
richTextBox1.Focus();
selectWordOnMouseOver();
}
public void selectWordOnMouseOver()
{
if (richTextBox1 == null)
return;
TextPointer cursurPosition = richTextBox1.GetPositionFromPoint(Mouse.GetPosition(richTextBox1), false);
if (cursurPosition == null)
return;
int offset = richTextBox1.Document.ContentStart.GetOffsetToPosition(cursurPosition);
// offset = offset;
//MessageBox.Show("Offset = " + offset.ToString());
int spaceAfter = FindSpaceAfterWordFromPosition(cursurPosition, " ");
int spaceBefore = FindSpaceBeforeWordFromPosition(cursurPosition, " ");
TextPointer wholeText = richTextBox1.Document.ContentStart;
TextPointer start = wholeText.GetPositionAtOffset(spaceBefore);
TextPointer end = wholeText.GetPositionAtOffset(spaceAfter);
if (start == null || end == null )
return;
richTextBox1.Selection.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Black);
richTextBox1.Selection.Select(start, end);
// MessageBox.Show("Mouse Over On = " + offset.ToString() + ": Word Start = " + (spaceBefore).ToString() + ": Word End = " + (spaceAfter).ToString() + " : Word is = " + richTextBox1.Selection.Text);
}
int FindSpaceBeforeWordFromPosition(TextPointer position, string word)
{
while (position != null)
{
if (position.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.Text)
{
string textRun = position.GetTextInRun(LogicalDirection.Backward);
// Find the starting index of any substring that matches "word".
int spaceIndexBeforeMouseOver = textRun.LastIndexOf(word);
return spaceIndexBeforeMouseOver;
}
position = position.GetNextContextPosition(LogicalDirection.Backward);
}
// position will be null if "word" is not found.
return 0;
}
int FindSpaceAfterWordFromPosition(TextPointer position, string word)
{
while (position != null)
{
if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
{
string textRun = position.GetTextInRun(LogicalDirection.Forward);
// Find the starting index of any substring that matches "word".
int spaceIndexAfterMouseOver = textRun.IndexOf(word);
int lastIndexAfterMouseOverIndex = textRun.Count();
int mouseOverIndex = richTextBox1.Document.ContentStart.GetOffsetToPosition(position);
if (spaceIndexAfterMouseOver >= 0)
{
return spaceIndexAfterMouseOver + mouseOverIndex;
}
else//if space index not found the select to to the last word of text box
return mouseOverIndex + lastIndexAfterMouseOverIndex;
}
position = position.GetNextContextPosition(LogicalDirection.Forward);
}
// position will be null if "word" is not found.
return 0;
}
results when no new line.
results when there are 3 new line feeds.
UPDATE 1:
I recently after testing found one thing that I am getting wrong offset.
Point nMousePositionCoordinate = Mouse.GetPosition(richTextBox1);
// System.Diagnostics.Debug.WriteLine(nMousePositionCoordinate.ToString());
TextPointer cursurPosition = richTextBox1.GetPositionFromPoint(nMousePositionCoordinate, false);
if (cursurPosition == null)
return;
int offset = richTextBox1.Document.ContentStart.GetOffsetToPosition(cursurPosition);
System.Diagnostics.Debug.WriteLine(offset.ToString());
Related
richTextBox1 contains text
I click on a word and Console displays that word for me and it highlights/selects the word i clicked on.
To do this, I keep the index of the character I clicked on then go left and right until I hit a space, -1, or end of file. Now I have the indexes of the beginning and end of the word. The last two lines are supposed to select the word between those two indexes.
However, what happens is that sometimes it highlights the word I want and sometimes it highlights all the words to the right of the character I clicked on.
"omar hello where are you going"
If I click on h in hello, it highlights hello where instead of highlighting hello
If I click on o in going, it will highlight going only as it should
If I click on o in you, it will highlight you going
I used console to check the start and end indexes of the word and they're always right yet for some reason, other words are selected in addition to the word i clicked on
private void richTextBox1_Click(object sender, EventArgs e)
{
int length = richTextBox1.Text.Length;
int rightPart = richTextBox1.SelectionStart;
int leftPart = richTextBox1.SelectionStart - 1;
string rightText = "";
string leftText = "";
while (rightPart != length)
{
if (richTextBox1.Text[rightPart].ToString().CompareTo(" ") != 0)
{
rightText += richTextBox1.Text[rightPart];
rightPart++;
}
else
{
break;
}
}
while (leftPart != -1)
{
if (richTextBox1.Text[leftPart].ToString().CompareTo(" ") != 0)
{
leftText = richTextBox1.Text[leftPart] + leftText;
leftPart--;
}
else
{
break;
}
}
leftPart++;
Console.WriteLine("\nSelected word is " + leftText + rightText + "\n");
richTextBox1.SelectionStart = leftPart;
richTextBox1.SelectionLength = rightPart;
}
The problem appears to be that you are setting the SelectionLength equal to rightPart. Remember this property represents the length of the selection, not the last index of the selection.
Instead, try changing your code to calculate the length by getting the difference between leftPart and rightPart:
richTextBox1.SelectionLength = rightPart - leftPart;
For what it's worth, your code can be shortened a little:
private void richTextBox1_Click(object sender, EventArgs e)
{
if (richTextBox1.TextLength == 0) return;
int rightPart = richTextBox1.SelectionStart;
int leftPart = richTextBox1.SelectionStart - 1;
while (rightPart < richTextBox1.TextLength && richTextBox1.Text[rightPart] != ' ')
{
rightPart++;
}
while (leftPart > -1 && richTextBox1.Text[leftPart] != ' ')
{
leftPart--;
}
leftPart++;
Console.WriteLine($"\nSelected word is " +
richTextBox1.Text.Substring(leftPart, rightPart - leftPart) + "\n");
richTextBox1.SelectionStart = leftPart;
richTextBox1.SelectionLength = rightPart - leftPart;
}
Or even a little more, using IndexOf and LastIndexOf instead of loops to find the spaces:
private void richTextBox1_Click(object sender, EventArgs e)
{
if (richTextBox1.TextLength == 0) return;
// Find the space before this word and after this word
var selStart = Math.Min(richTextBox1.SelectionStart, richTextBox1.TextLength - 1);
var firstSpace = richTextBox1.Text.LastIndexOf(' ', selStart);
var lastSpace = richTextBox1.Text.IndexOf(' ', selStart);
var start = firstSpace + 1;
var length = (lastSpace < 0 ? richTextBox1.TextLength : lastSpace) - start;
Console.WriteLine($"\nSelected word is {richTextBox1.Text.Substring(start, length)} \n");
richTextBox1.SelectionStart = start;
richTextBox1.SelectionLength = length;
}
In my RichtextBox, if I have written as below.
This is my pen,
his pen is beautiful.
Now I search word "is" then
output would be as below.
All "is" should be highlighted.
What about:
static class Utility {
public static void HighlightText(this RichTextBox myRtb, string word, Color color) {
if (word == string.Empty)
return;
int s_start = myRtb.SelectionStart, startIndex = 0, index;
while((index = myRtb.Text.IndexOf(word, startIndex)) != -1) {
myRtb.Select(index, word.Length);
myRtb.SelectionColor = color;
startIndex = index + word.Length;
}
myRtb.SelectionStart = s_start;
myRtb.SelectionLength = 0;
myRtb.SelectionColor = Color.Black;
}
}
Looks like this would do it.
http://www.dotnetcurry.com/ShowArticle.aspx?ID=146
int start = 0;
int indexOfSearchText = 0;
private void btnFind_Click(object sender, EventArgs e)
{
int startindex = 0;
if(txtSearch.Text.Length > 0)
startindex = FindMyText(txtSearch.Text.Trim(), start, rtb.Text.Length);
// If string was found in the RichTextBox, highlight it
if (startindex >= 0)
{
// Set the highlight color as red
rtb.SelectionColor = Color.Red;
// Find the end index. End Index = number of characters in textbox
int endindex = txtSearch.Text.Length;
// Highlight the search string
rtb.Select(startindex, endindex);
// mark the start position after the position of
// last search string
start = startindex + endindex;
}
}
public int FindMyText(string txtToSearch, int searchStart, int searchEnd)
{
// Unselect the previously searched string
if (searchStart > 0 && searchEnd > 0 && indexOfSearchText >= 0)
{
rtb.Undo();
}
// Set the return value to -1 by default.
int retVal = -1;
// A valid starting index should be specified.
// if indexOfSearchText = -1, the end of search
if (searchStart >= 0 && indexOfSearchText >=0)
{
// A valid ending index
if (searchEnd > searchStart || searchEnd == -1)
{
// Find the position of search string in RichTextBox
indexOfSearchText = rtb.Find(txtToSearch, searchStart, searchEnd, RichTextBoxFinds.None);
// Determine whether the text was found in richTextBox1.
if (indexOfSearchText != -1)
{
// Return the index to the specified search text.
retVal = indexOfSearchText;
}
}
}
return retVal;
}
// Reset the richtextbox when user changes the search string
private void textBox1_TextChanged(object sender, EventArgs e)
{
start = 0;
indexOfSearchText = 0;
}
This will show all the searched criteria at the same time.
Using: 1 Textbox (to enter the text to search for) and 1 Button (to Run the Search).
Enter your search criteria inside the textbox and press search button.
// On Search Button Click: RichTextBox ("rtb") will display all the words inside the document
private void btn_Search_Click(object sender, EventArgs e)
{
try
{
if (rtb.Text != string.Empty)
{// if the ritchtextbox is not empty; highlight the search criteria
int index = 0;
String temp = rtb.Text;
rtb.Text = "";
rtb.Text = temp;
while (index < rtb.Text.LastIndexOf(txt_Search.Text))
{
rtb.Find(txt_Search.Text, index, rtb.TextLength, RichTextBoxFinds.None);
rtb.SelectionBackColor = Color.Yellow;
index = rtb.Text.IndexOf(txt_Search.Text, index) + 1;
rtb.Select();
}
}
}
catch (Exception ex) { MessageBox.Show(ex.Message, "Error"); }
}
}
}
If you only want to match the whole word you can use this, note that this ignores case and also the |s\b means that plurals get highlighted e.g. Cat matches cats but not caterpiller :
public static void HighlightText(RichTextBox myRtb, string word, Color color)
{
if (word == string.Empty)
return;
var reg = new Regex(#"\b" + word + #"(\b|s\b)",RegexOptions.IgnoreCase);
foreach (Match match in reg.Matches(myRtb.Text))
{
myRtb.Select(match.Index, match.Length);
myRtb.SelectionColor = color;
}
myRtb.SelectionLength = 0;
myRtb.SelectionColor = Color.Black;
}
private void button3_Click(object sender, EventArgs e)
{
if (textBox1.Text != "")
{
for (int i = 0; i < richTextBox1.TextLength; i++)
{
richTextBox1.Find(textBox1.Text, i, RichTextBoxFinds.None);
richTextBox1.SelectionBackColor = Color.Red;
}
}
else
{
for (int i = 0; i < richTextBox1.TextLength; i++)
{
richTextBox1.SelectAll();
richTextBox1.SelectionBackColor = Color.White;
}
}
}[lets make it!][1]
I would do it like that because all the other answers highlight the text, but doesnt change it back after you searched again.
Use the RichText Find Method to find the starting index for the searching word.
public int FindMyText(string searchText, int searchStart, int searchEnd)
{
int returnValue = -1;
if (searchText.Length > 0 && searchStart >= 0)
{
if (searchEnd > searchStart || searchEnd == -1)
{
int indexToText = richTextBox1.Find(searchText, searchStart, searchEnd, RichTextBoxFinds.MatchCase);
if (indexToText >= 0)
{
returnValue = indexToText;
}
}
}
return returnValue;
}
Use a Button or TextChangeListener and Search for your word.
private void button1_Click(object sender, EventArgs e)
{
// Select the first char in your Richtextbox
richTextBox1.SelectionStart = 0;
richTextBox1.SelectionLength = richTextBox1.TextLength;
// Select until the end
richTextBox1.SelectionColor = Color.Black;
// Make the Text Color black
//Use an Inputfield to add the searching word
var word = txtSearch.Text;
//verify the minimum length otherwise it may freeze if you dont have text inside
if (word.Length > 3)
{
int s_start = richTextBox1.SelectionStart, startIndex = 0, index;
while ((index = FindMyText(word, startIndex, richTextBox1.TextLength)) != -1)
{
// goes through all possible found words and color them blue (starting index to end)
richTextBox1.Select(index, word.Length);
richTextBox1.SelectionColor = Color.Blue;
startIndex = index + word.Length;
}
// Color everything between in color black to highlight only found words
richTextBox1.SelectionStart = startIndex;
richTextBox1.SelectionLength = 0;
richTextBox1.SelectionColor = Color.Black;
}
}
I would highly recommend to set a minimum word length to avoid freezing and high memory allocation.
text box area that automatically cuts a long paragraph text into 30 characters sentences
i am trying run this code but occurs exception [exception of type 'System.StackOverflowException']
private void txtCutParagraph_TextChanged(object sender, EventArgs e)
{
int limitNum = 30;
string sentence = txtCutParagraph.Text;
string[] words = sentence.Split(' ');
string line = "";
foreach (string word in words)
{
if ((line + word).Length > limitNum)
{
newLine += line + "\r\n";
line = "";
}
line += word + " ";
}
if (line.Length > 0)
newLine += line + "\r\n";
txtCutParagraph.Text = newLine;
}
If the form is freezing is because that txtCutParagraph_TextChanged event is firing infinitely, because you are changing the text of textbox at the end of the event: txtCutParagraph.Text = newLine;, so it means that change text in the textbox, and the event will fire again and again.
To prevent this form from freezing please move your code to another event of textbox, named KeyPress as:
private void txtCutParagraph_KeyPress(object sender, KeyPressEventArgs e)
{
int limitNum = 30;
string sentence = txtCutParagraph.Text;
string[] words = sentence.Split(' ');
string line = "";
foreach (string word in words)
{
if ((line + word).Length > limitNum)
{
newLine += line + "\r\n";
line = "";
}
line += word + " ";
}
if (line.Length > 0)
newLine += line + "\r\n";
txtCutParagraph.Text = newLine;
}
What you are trying to do is called Word wrapping. TextBox class has Wordwrap option by default. unfortunately you cant limit number of characters per line.
You have to write an algorithm instead. I have noticed that your algorithm does not work correctly. so i decided to write one my self (as it was a good practice!). It is hard to handle all situations that can happen inside text formatting. I tried my best anyway you have to write one your self if you are not satisfied with results.
Before using this algorithm you have to disable Wordwrap feature of Textbox. So they will not Interfere each other. In InitializeComponent inside Form Designer add this line.
this.textBox1.WordWrap = false;
Now use this algorithm to do it for you! Note that textbox1 is a multi line text box.
private StringBuilder stringBuilder = new StringBuilder();
private bool _isInsideTextChanged = false;
private const int MaximumChars = 30; // Maximum characters
private StringBuilder WrapText(StringBuilder text, ref int position)
{
StringBuilder newStringBuilder = new StringBuilder(text.ToString());
int charsPerLine = 0;
int lastSpace = -1; // index of last space per line
for (int i = 0; i < newStringBuilder.Length; i++)
{
if (newStringBuilder[i] == ' ')
{
if (newStringBuilder.Length > i + 2 && newStringBuilder.ToString(i + 1, 2) == "\r\n")
{
if (newStringBuilder.Length > i + 3)
{
int next = newStringBuilder.ToString().IndexOf(' ', i + 3);
if (next != -1 && charsPerLine + next - i <= MaximumChars || charsPerLine + newStringBuilder.Length - i - 2 <= MaximumChars)
{
newStringBuilder.Remove(i + 1, 2);
if (i <= textBox1.SelectionStart)
{
position -= 2;
}
continue;
}
}
i++;
continue;
}
if (newStringBuilder.Length > i + 1 && newStringBuilder[i + 1] != ' ')
{
lastSpace = i;
}
}
if (newStringBuilder[i] == '\n' || newStringBuilder[i] == '\r')
{
lastSpace = -1;
charsPerLine = 0;
}
if (++charsPerLine > MaximumChars && lastSpace != -1)
{
newStringBuilder.Insert(lastSpace + 1, "\r\n");
if (lastSpace <= textBox1.SelectionStart)
{
position += 2;
}
charsPerLine = i - lastSpace;
lastSpace = -1;
i++;
}
}
return newStringBuilder;
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
if (_isInsideTextChanged) return;
_isInsideTextChanged = true;
stringBuilder.Clear();
stringBuilder.Append(textBox1.Text);
int position = textBox1.SelectionStart;
string newText = WrapText(stringBuilder, ref position).ToString();
textBox1.Text = newText;
textBox1.SelectionStart = position;
_isInsideTextChanged = false;
}
Here is the test that shows the results.
How this wroks?
This algorithm will count the number of characters from last line break index (default value is 0) up to last space character index per line.(default value is -1 means no space in that line). Then it will put line break after last space if the number of characters on that line is more than 30. How ever this algorithm test other things too to better handle text formatting.
This is done every time a textbox value is changed. StringBuilder is used instead of string to increase performance.
To prevent stack overflow exception as described by #KhaksarWeqar I used a boolean value _isInsideTextChanged with TextChanged event:
private bool _isInsideTextChanged = false;
private void textBox1_TextChanged(object sender, EventArgs e)
{
if (_isInsideTextChanged) return; // return if was inside TextChanged.
_isInsideTextChanged = true; // inside TextChanged
// Do stuff...
_isInsideTextChanged = false; // outside TextChanged
}
There is also a better way explained on wiki. you can create your own even better!. https://en.wikipedia.org/wiki/Line_wrap_and_word_wrap
I have a .NET Winforms-based program I wrote in C# where I programmatically select text within a TextBox using the Select() method. I can see the selected text on the screen and the SelectionLength member reports an accurate amount of selected characters, but the SelectedText member doesn't contain any text:
int indexPeriod = strLectureText.IndexOfAny(terminators, indexStart);
thisTextbox.Focus();
if (indexPeriod > -1)
{
thisTextbox.Select(indexStart, (indexPeriod - indexStart) + 1);
}
else
{
thisTextbox.Select(indexStart, thisTextbox.Text.Length);
}
Log.Debug("jtext len=" + thisTextbox.SelectionLength + " txt=" + thisTextbox.SelectedText);
thisTextbox.ScrollToCaret();
What's going on?
Update Nov 16 2013
I am adding additional code per #KingKing's request:
delegate void delegateMoveToNextTextFragment(ref TextBox thisTextbox, char[] terminators);
private void MoveToNextTextFragment(ref TextBox thisTextbox, char[] terminators)
{
string strLectureText = String.Empty;
strLectureText = thisTextbox.Text;
int currentStartPos = thisTextbox.SelectionStart + thisTextbox.SelectionLength - 1
// search the rest of the buffer after the currently-selected string to find the next period
int skipLastChar = 0;
// don't include last selected character in search
try
{
if ((thisTextbox.SelectionLength > 0) & (strLectureText[currentStartPos] != '.'))
{
skipLastChar = 1;
}
}
catch (Exception ex)
{
Debug.WriteLine("exception caught! ex=" + ex.Message);
skipLastChar = 0;
}
int indexStart = 0;
if (currentStartPos == 0)
{
indexStart = 0;
}
else
{
indexStart = currentStartPos + 1;
}
int indexPeriod = strLectureText.IndexOfAny(terminators, indexStart);
if (indexPeriod > -1)
{
thisTextbox.Select(indexStart, (indexPeriod - indexStart) + 1);
}
else
{
thisTextbox.Select(indexStart, thisTextbox.Text.Length);
}
thisTextbox.Focus();
Log.Debug("jtext len=" + thisTextbox.SelectionLength + " txt=" + thisTextbox.SelectedText);
thisTextbox.ScrollToCaret();
}
I have two methods which search through a text document in my WPF app. When search for a word in the first search it works fine, but when I add a word to it, it will crash and come up with a null exception. Can someone please help?
Crashes on:
TextRange result = new TextRange(start, start.GetPositionAtOffset(searchText.Length));
Stacktrace:
{"Value cannot be null.\r\nParameter name: position2"}
Example:
if the text said this.
And I search for "if the", then I search for "if the text said" it would crash.
private void btnSearch_Click(object sender, RoutedEventArgs e)
{
string searchText = searchBox.Text.Trim();
searchText = searchText.ToLower();
if (String.IsNullOrWhiteSpace(searchText))
{
MessageBox.Show("Please enter a search term!");
searchBox.Clear();
searchBox.Focus();
newSearch = true;
return;
}
if (!String.IsNullOrEmpty(lastSearch))
{
if (lastSearch != searchText)
newSearch = true;
}
TextRange searchRange;
RichTextBox _body = ((DockPanel)((TabItem)tabControl.Items[tabControl.SelectedIndex]).Content).Children[1] as RichTextBox;
_body.Focus();
if (newSearch)
{
searchRange = new TextRange(_body.Document.ContentStart, _body.Document.ContentEnd);
lastSearch = searchText;
TextPointer position2 = _body.Document.ContentEnd;
}
else
{
backupSearchRange = new TextRange(_body.CaretPosition.GetLineStartPosition(1) == null ?
_body.CaretPosition.GetLineStartPosition(0) : _body.CaretPosition.GetLineStartPosition(1), _body.Document.ContentEnd);
TextPointer position1 = _body.Selection.Start.GetPositionAtOffset(1);
TextPointer position2 = _body.Document.ContentEnd;
searchRange = new TextRange(position1, position2);
}
TextRange foundRange = newSearchFunction(searchRange, searchText);
if (foundRange == null)
{
if (newSearch)
{
MessageBox.Show("\'" + searchBox.Text.Trim() + "\' not found!");
newSearch = true;
lastOffset = -1;
}
else
{
MessageBox.Show("No more results!");
newSearch = true;
lastOffset = -1;
}
}
else
{
_body.Selection.Select(foundRange.Start, foundRange.End);
_body.SelectionBrush = selectionHighlighter;
newSearch = false;
}
}
private TextRange newSearchFunction(TextRange searchRange, string searchText)
{
int offset = searchRange.Text.ToLower().IndexOf(searchText);
offset = searchRange.Text.ToLower().IndexOf(searchText);
if (offset < 0)
return null;
if (lastOffset == offset)
{
//searchRange = backupSearchRange;
offset = searchRange.Text.ToLower().IndexOf(searchText);
if (offset < 0)
return null;
for (TextPointer start = searchRange.Start.GetPositionAtOffset(offset); start != searchRange.End; start = start.GetPositionAtOffset(1))
{
TextRange result = new TextRange(start, start.GetPositionAtOffset(searchText.Length));
if (result.Text.ToLower() == searchText)
{
lastOffset = offset;
return result;
}
}
}
for (TextPointer start = searchRange.Start.GetPositionAtOffset(offset); start != searchRange.End; start = start.GetPositionAtOffset(1))
{
TextRange result = new TextRange(start, start.GetPositionAtOffset(searchText.Length));
if (result.Text.ToLower() == searchText)
{
lastOffset = offset;
return result;
}
}
return null;
}
Method GetPositionAtOffset can return null if it cannot find this position. See TextPointer.GetPositionAtOffset. In your case you see it because you do the search until you don't reach end of your search range, but in the case when for example your search range contains 100 symbols and your search text has 10 symbols after you reach pointer at index 91 - you will call GetPositionAtOffset with offset 10 - and this will be 101 symbol, which gives you null in this case.
You can do simple check in your for loops, something like:
for (
TextPointer start = searchRange.Start.GetPositionAtOffset(offset);
start != searchRange.End;
start = start.GetPositionAtOffset(1))
{
var end = start.GetPositionAtOffset(searchText.Length);
if (end == null)
{
break;
}
TextRange result = new TextRange(start, end);
if (result.Text.ToLower() == searchText)
{
lastOffset = offset;
return result;
}
}
You have one more similar for loop, just add this special check in it too.
It looks like you are doing search, so just want to give you two recommendations:
Use string.Compare method instead ToLower. See String.Compare Method (String, String, Boolean, CultureInfo). In your case it should be string.Compare(text1, text2, ignoreCase: true, culture: CultureInfo.CurrentCulture). In this case your application will support all languages.
If you really want to use ToLower and do search in this way - consider to change it to ToUpper, because some languages can be tricky when you do ToLower. Check this article What's Wrong With Turkey?. When you do ToLower(I) with Turkish locale you will get dotless i, which is different from i. Wikipedia about: Dotted and dotless I.