I have a RichTextBox (RTB) and next to it a DataGrid (DG). A user can paste a list of IDs into the RTB to perform some bulk actions on them. In order that users can be aware if an ID they passed into the RTB isn't in the DG, I want to change the colour of the text.
This is what I have at the moment:
private void RichTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
//TODO: Enforce 13 character line limit and replace any delimiters with new line
//Get all text
RichTextBox rtb = (RichTextBox)sender;
TextRange textRange = new TextRange(rtb.Document.ContentStart, rtb.Document.ContentEnd);
//Split into lines
string[] rtbLines = textRange.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
foreach (string line in rtbLines)
{
//remove comms, but could some others
line.Replace(",", "");
//Get the index of the start of an ID
int startPos = textRange.Text.IndexOf(line);
//this should be unncessary?
if (startPos > -1)
{
//Get a pointer to text we found
TextPointer tp = textRange.Start.GetPositionAtOffset(startPos, LogicalDirection.Backward);
//this should be unnecessary?
if (tp != null)
{
//Get the line start position
TextPointer tpLine = tp.GetLineStartPosition(0); //This throws StackOverflowException sometimes?
//Get start of next line
TextPointer testLineEnd = tp.GetLineStartPosition(1);
//In case we're at the end, default to end of the document
TextPointer tpLineEnd = (testLineEnd ?? tp.DocumentEnd).GetInsertionPosition(LogicalDirection.Backward);
//Get text range between line start/end
TextRange lineText = new TextRange(tpLine, tpLineEnd);
//Cast the data and check if our text is in it so we can check it
if (UnassignedMPANsListView.ItemsSource != null && UnassignedMPANsListView.ItemsSource.Cast<CrmUnassignedMPAN>().ToList().Any(x => x.MPAN == line))
{
lineText.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Red);
}
else
{
lineText.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Black);
}
}
}
}
}
The issue is that often the line TextPointer tpLine = tp.GetLineStartPosition(0); causes a StackOverflowException. It seems to be only when the user is typing an ID; pasting seems to be OK.
My thought is that this is caused because the RichTextBox_TextChanged event is being called repeatedly, whereas pasting is a single hit, but I don't understand why that would cause this exception rather than just become very slow.
Also open to any other suggestions for how to implement this feature. My key requirements are that a user can paste a list, type them 1 by 1, and the rows can be programmatically coloured indepedently.
You have exception because you have recursive call at the place lineText.ApplyPropertyValue(TextElement.ForegroundProperty this call does modify your text. To avoid it you can simply add the flag:
bool changeIsRunning = false;
private void RichTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
if(changeIsRunning)
{
return;
}
changeIsRunning = true;
try
{
//Put your logic here.
}
finally
{
changeIsRunning = false;
}
}
Be aware, that there are not only visible text, but also invisible element tags in the text(e.g. for the color).
Related
I'm trying to make a simple turtle program where you can type in multiple lines of code to draw on a PictureBox. The code is there for the graphics, but I'm struggling with the loops and getting it to read the next line of the string. I know it's going to be something stupidly simple but I've been trying for hours to no avail. I've added the first part of the code cos I'm certain that's where the problem is. I think I've made it way too complicated for myself.
It's reading from a rich text box and I'm trying to read it line by line but split those lines each time. So if something like this is inputted:
square 50
drawto 100,100
moveto 200,200
square 100
So far, when the button is clicked, it runs the first line, but I want it to go through each line and perform each action. I know my naming conventions aren't the best
private void buttonRun_Click(object sender, EventArgs e) //what happens when run is clicked - main part of code
{
Console.WriteLine("RUN PROGRAM");
int lineno = 0;
string Commandc1 = ProgramCommandWindow.Text.Trim().ToLower();
StringReader strReader = new StringReader(Commandc1);
string[] textlines = Commandc1.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
while (true)
{
textlines[lineno] = strReader.ReadLine(); //reads line of text
while (textlines[lineno] != null)
{
//Commandc1 = strReader.ReadLine();
string[] Commandc = textlines[lineno].Split(' ', ',');
while (Commandc[0].Equals("moveto") == true)
{
if (!Int32.TryParse(Commandc[+1], out positionx)) ; //translate string to int
if (!Int32.TryParse(Commandc[+2], out positiony)) ;
Picturebox.xPos = positionx;
Picturebox.yPos = positiony;
//Commandc1 = strReader.ReadLine();
Refresh();//refresh display
lineno++;
}
while (Commandc[0].Equals("drawto") == true) //what happens if draw line command is used
{
if (!Int32.TryParse(Commandc[+1], out positionx)) ; //translate string to int
if (!Int32.TryParse(Commandc[+2], out positiony)) ;
Picturebox.toX = positionx;
Picturebox.toY = positiony;
MyOutputWindow.DrawLine(Picturebox.toX, Picturebox.toY);
Console.WriteLine("COMMAND - DRAW LINE");
//Commandc1 = strReader.ReadLine();
Refresh();//refresh display
lineno++;
}
while (Commandc[0].Equals("square") == true) //what happens if draw square command is used
{
if (!Int32.TryParse(Commandc[+1], out positionshape)) ; //translate string to int
Picturebox.sizes = positionshape;
MyOutputWindow.DrawSquare(Picturebox.sizes);
Console.WriteLine("COMMAND - DRAW SQUARE");
//Commandc1 = strReader.ReadLine();
Refresh();//refresh display
lineno++;
}
break;
}
//else while (Commandc1 == "end")
//{
// break;
//}
Refresh();//refresh display
break;
}
}
I need to make a multi line textbox that changes the back ground for charaters after the line becomes longer than 80 characters. So that if someone types a sentence of 85 characters the last 5 characters will have a yellow background. I would like it to make this feature part of the style because currently we are tying to do this with logic in the code behind and it lags when someone types quickly.
current highlighting imlp
private void HighlightLines()
{
try
{
//// x is the distance in the row from the left side of the Textbox. e.g. Each character is one unit.
int x = 0;
//// y is the distance in the columns from the top of the Textbox. e.g. Each character is one unit.
int y = 0;
//// lines is the number of newline characters found in the Text.
int lines = 0;
//// Point 1 is the starting point of the range that needs to be highlighted.
TextPointer point1 = this.Document.ContentStart;
//// Point 2 is the end point of the range that needs to be highlighted.
TextPointer point2 = this.Document.ContentStart;
//// Range is the distance from Point 1 to Point 2 needed to apply the Yellow color to the area past 69 characters.
TextRange range;
//// Additional Ranges is the collection of all the ranges that need to be Yellow.
this.AdditionalRanges = new ObservableCollection<TextRange>();
//// Count the number of lines.
for (int i = 0; i < this.Text.Length; i++)
{
if (this.Text[i] == '\n')
{
lines++;
this.AdditionalRanges.Add(new TextRange(this.Document.ContentStart, this.Document.ContentEnd));
}
}
//// This map is used to differentiate which lines need to be colored. (True means the range is over 69 characters. False means the opposite).
bool[] map = new bool[lines];
//// Traverse the whole text.
for (int i = 0; i < this.Text.Length; i++)
{
var currentCharacter = this.Text[i];
var newLineCharacter = '\n';
if (currentCharacter == newLineCharacter)
{
var pointDifference = point1.GetOffsetToPosition(point2);
point1 = point1.GetPositionAtOffset(pointDifference);
x = 0;
y++;
}
else if (x > 69)
{
range = new TextRange(point1, point2);
this.AdditionalRanges[y] = range;
map[y] = true;
if (point2.GetNextInsertionPosition(LogicalDirection.Forward) != null)
{
point2 = point2.GetNextInsertionPosition(LogicalDirection.Forward);
}
}
else if (point1.GetNextInsertionPosition(LogicalDirection.Forward) != null && point2.GetNextInsertionPosition(LogicalDirection.Forward) != null)
{
point1 = point1.GetNextInsertionPosition(LogicalDirection.Forward);
point2 = point2.GetNextInsertionPosition(LogicalDirection.Forward);
x++;
}
}
//// Make everything white.
foreach (var item in this.AdditionalRanges)
{
item.ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.White);
}
//// Make the appropriate ranges Yellow.
for (int i = 0; i < this.AdditionalRanges.Count; i++)
{
if (map[i])
{
this.AdditionalRanges[i].ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.Yellow);
}
}
}
catch (Exception e)
{
// drop exception. this has only broke once and we dont exactly know why.
}
}
It's probably lagging because your HighlightLines() method is scanning the entire document every time, and I assume it gets called after every keypress. Ideally you want to only re-scan the portions of the text that have changed. Fortunately, the TextChanged event provides the exact offset of the changes.
The example code below was written to work with a RichTextBox but you should be able to adapt it. Also, it looks like your code was checking for 69 characters instead of 80, so this does the same:
RichTextBox txt;
...
bool suppressChanges = false;
private void Txt_TextChanged(object sender, TextChangedEventArgs e)
{
if (!suppressChanges)
{
// suppress changes because changing highlights will trigger the event again
suppressChanges = true;
foreach (var change in e.Changes)
{
var changeStart = txt.Document.ContentStart.GetPositionAtOffset(change.Offset);
TextRange changedRange;
if (change.AddedLength > 0)
changedRange = new TextRange(changeStart, changeStart.GetPositionAtOffset(change.AddedLength));
else
changedRange = new TextRange(changeStart, changeStart);
SetRangeColors(changedRange);
}
//unsuppress changes
suppressChanges = false;
}
}
void SetRangeColors(TextRange range)
{
// Scan one line at a time starting with the beginning of the range
TextPointer current = range.Start.GetLineStartPosition(0);
while (current != null && current.CompareTo(range.End) < 0)
{
// find the next line or the end of the document
var nextLine = current.GetLineStartPosition(1, out int lines);
TextPointer lineEnd;
if (lines > 0)
lineEnd = nextLine.GetNextInsertionPosition(LogicalDirection.Backward);
else
lineEnd = txt.Document.ContentEnd;
var lineRange = new TextRange(current, lineEnd);
// clear properties first or the offsets won't match the characters
lineRange.ClearAllProperties();
var lineText = lineRange.Text;
if (lineText.Length > 69)
{
var highlight = new TextRange(current.GetPositionAtOffset(70), lineEnd);
highlight.ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.Yellow);
}
// advance to the next line
current = lineEnd.GetLineStartPosition(1);
}
}
I have created two string arrays from a text file and populated a combobox with array1. What I would like to understand is, how do I get a textbox to show the index of array2 that matches the selected index of the combobox (array1)?
I thought something like this may work:
if(phoneComboBox.Text == cPhone[index])
{
nameTextBox.Text = cName[index]; //show equal index item to cPhone/phoneComboBox
}
But that doesn't seem to work. I've also tried a foreach loop, maybe I'm just doing it wrong. I have the reading of the text file and arrays in the window_loaded event and don't know if that's the issue. I've seen SelectedIndexChanged event mentioned a lot in similar questions, but I do not have that event to use, just SelectionChanged.
Can someone please point me on the right track with this? I know arrays may not be the best use here, but they are what I have used, so please help me to understand it properly.
This is how I've read the arrays:
private void Window_Loaded_1(object sender, RoutedEventArgs e)
{
//read file on start
int counter = 0;
string line;
StreamReader custSR = new StreamReader(cFileName);
line = custSR.ReadLine();
while (line != null)
{
Array.Resize(ref cPhone, cPhone.Length + 1);
cPhone[cPhone.Length - 1] = line;
counter++;
line = custSR.ReadLine();
Array.Resize(ref cName, cName.Length + 1);
cName[cName.Length - 1] = line;
counter++;
line = custSR.ReadLine();
phoneComboBox.Items.Add(cPhone[cPhone.Length - 1]);
}
custSR.Close();
/*string changeAll = string.Join(Environment.NewLine, cPhone);
string allOthers = string.Join(Environment.NewLine, cName);
MessageBox.Show(changeAll + allOthers);*/
//focus when program starts
phoneComboBox.Focus();
}
In the if you are comparing text strings, so you should use the function ".Equals" in stead of
"=="
if(phoneComboBox.Text.Equals(cPhone[index]))
{
nameTextBox.Text = cName[phoneComboBox.SelectedIndex];
}
You don't need to check the condition, just get the selected index:
nameTextBox.Text = cName[phoneComboBox.SelectedIndex];
and in WPF you have SelectionChanged.
SelectedIndexChanged is for winform.
Best Practice:
But I would suggest you a better way to achieve this using Tag property. You wont have to change a lot of code for it.
//Create "ComboBoxItem" instead of "array"
while (line != null)
{
//initialize
ComboBoxItem cmItem = new ComboBoxItem();
//set Phone as Display Text
cmItem.Content = line; //it is the Display Text
//get Name
line = custSR.ReadLine();
//set Tag property
cmItem.Tag = line; //it is the attached data to the object
//add to "Items"
phoneComboBox.Items.Add(ComboBoxItem);
}
and now its very simple to get the selected item in SelectionChanged event:
void phoneComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
nameTextBox.Content = (e.AddedItems[0] as ComboBoxItem).Tag;
}
You don't need to handle those arrays anymore.
Thanks for the other answers, this is the answer I came up with that works. Fixes the indexOutOfRange exception I was getting.
private void phoneComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (phoneComboBox.SelectedIndex != -1)
{
nameTextBox.Text = cName[phoneComboBox.SelectedIndex];
}
else
{
nameTextBox.Text = string.Empty;
}
}
Hey Im creating my own coding language and I already have the entire application set up with save, open, close, new, etc.
In order for the "Run" part to work, I need a way to scan and test every single line in richTextBox1.
Maybe for past Java users, something along the lines of the "java-util-scanner," but easier to use for testing each line.
Does anyone know a way to do this, where a string "scannedString" would be tested as so:
if(scannedString == "#load(moduleFiles)") {
//action here
}
string scannedStringNextLine = ???
if(scannedStringNextLine = "") {
//action here
}
Eventually it would look more like this:
if(scannedString == "code1" || scannedString == "code2" etc... ) {
if(scannedString == "code1") {
//action here
}
} else {
//error message
}
hope this is enough information...
To get lines of code of the RichTextBox you can split the content by a new line symbol:
var lines = this.richTextBox.Text.Split('\n').ToList();
The lines are in order of appearance. Then you can go through lines in a for or foreach loop:
foreach (var line in lines)
{
// access a line, evaluate it, etc.
}
One way to do it is to split the text on the newline charaters and then parse each line for the text you care about:
private void btnScan_Click(object sender, EventArgs e)
{
var code = richTextBox1.Text;
foreach (var line in code.Split(new []{'\n','\r'},
StringSplitOptions.RemoveEmptyEntries))
{
CheckForLoad(line);
}
}
void CheckForLoad(string line)
{
if (string.IsNullOrWhiteSpace(line)) return;
int i = line.IndexOf("#load");
if (i < 0) return;
int openParen = line.IndexOf("(", i + 1);
if (openParen < 0) return;
int closeParen = line.IndexOf(")", openParen + 1);
if (closeParen < 0) return;
string modules = line.Substring(openParen + 1, closeParen - openParen - 1);
MessageBox.Show(string.Format("Loading modules: {0}", modules));
}
Good day!
Sorry if I cant make it into code but here's my problem:
Im coding C#, using TextBox Control, Multiline = true.
Now, my problem is i need to know the user's input (per line) whether it already reaches maximum length PER LINE defined. If yes, it shouldn't be printed in the textbox (like MaxLength does).
Example of the input (Note: maximum length per line is 5):
Dog //3 characters
Cat //3 characters
Telephone //9 characters
Telephone shouldn't be printed coz it exceeded max length per line which is 5. It should be "Telep" only.
EDITED:
Acctually, I found my way to solve this. it was as simple as this!!!!
if (txtText.Lines[txtText.GetLineFromCharIndex(txtText.SelectionStart)].Length > Convert.ToInt32(txtMaxlen.Text) - 1 && e.KeyChar!=8)
{
e.Handled = true;
}
Just put that code in "OnKeyPress" event then VIOLA!
You can handle OnTextChanged event and then use Lines property to get array of lines. Then simply check whenever any line has more characters than you allow and if so - correct it and set as a Lines again. You have to also prevent event from rising when you do such correction, use the flag or unsubscribe/subscribe technique.
You can extract them from the lines of the Textbox like:
var myarr = myTextBox.Lines.Select(n => n.Remove(3)).ToArray();
Btw, you're most likely better off with a DataGridView Control.
textBox.OnTextChanged += (EventArgs e) => // invoked on every text change
{
var lines = myTextBox.Where(x.Length > 5); // Get the long lines
for (var i; i < lines.Count(); i++) // iterate over long lines
{
lines[i] = lines[i].Substring(0, 5); // replace the line with its substring(stripped) value
}
}
How about this, this should behave like the MaxLength property and handle the addition of the new char before it occurs, not after. When a key is pressed it checks the length of the line the cursor is currently on.
public class MaxPerLineTextBox : TextBox
{
public MaxPerLineTextBox()
{
base.Multiline = true;
}
public override bool Multiline
{
get
{
return true;
}
set
{
throw new InvalidOperationException("Readonly subclass");
}
}
public int? MaxPerLine { get; set; }
protected override void OnKeyPress(KeyPressEventArgs e)
{
if (char.IsControl(e.KeyChar))
{
base.OnKeyPress(e);
return;
}
int maxPerLine;
if (this.MaxPerLine.HasValue)
{
maxPerLine = this.MaxPerLine.Value;
}
else
{
base.OnKeyPress(e);
return;
}
var count = 0;
var lineLength = this.Lines.Select(line =>
{
count += line.Length;
return new { line.Length, count };
})
.Last(c => c.count < this.SelectionStart).Length;
if (lineLength < maxPerLine)
{
base.OnKeyPress(e);
return;
}
e.Handled = true;
}
}