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);
}
}
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 want to get the text between ';' character. However if there are 3 matches like that, i want to get the text between ';' character where current cursor position. I am using multine textbox.
Example;
select * from database;
----> **Lets say my cursor is here**
select
orders from
customer;
select * from employees;
So, i only want to get 'select orders from customer' text.
Could you please share your thoughts on it?
To achieve this, you first have to find all indicies of ;. To do this, iterate through all indicies (source):
private List<int> AllIndicesOf(string strToSearch, string fullText)
{
List<int> foundIndices = new List<int>();
for (int i = fullText.IndexOf(strToSearch); i > -1; i = fullText.IndexOf(strToSearch, i + 1))
{
foundIndices.Add(i + 1);
}
return foundIndices;
}
Then you have to compare your position to those indices, since you only want the index (of ;) that follows immediately after your cursor:
List<int> indicies = AllIndicesOf(";", txtBxText.Text);
try
{
if (indicies.Count > 0)
{
int cursorPos = txtBxText.SelectionStart;
var indicesBefore = indicies.Where(x => x < cursorPos);
int beginIndex = indicesBefore.Count() > 0 ? indicesBefore.Last() : 0;
int endIndex = indicies.Where(x => x > beginIndex).First();
txtBxSelected.Text = txtBxText.Text.Substring(beginIndex, endIndex - beginIndex);
}
}
catch { }
The try-catch statement is used to prevent an Exception if your cursors position is after all other indices.
A sample project can be downloaded here.
This solution perfectly works although you need to check it again and consider some possible exceptions. I did not consider them myself because I thought it was better to be handled by you. I also used richTextBox which is better than the multi line text box. Enjoy the code bro
private void button1_Click(object sender, EventArgs e)
{
var ultimateResult = string.Empty;
var cusrPosition = richTextBox1.SelectionStart;
var currentStr = string.Empty;
var strDic = new Dictionary<string,int>();
var textArr = richTextBox1.Text.ToCharArray();
for (var i = 0; i < textArr.Count(); i++)
{
if (textArr[i] != ';')
currentStr = currentStr + textArr[i];
else
{
strDic.Add(currentStr,i);
currentStr = string.Empty;
}
}
ultimateResult = strDic.First(item => item.Value >= cusrPosition).Key;
textBox1.Text = ultimateResult;
}
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;
}
}
here is the game board just to give you an idea of how it looks like (this board will be expanded to a 7x6)
what i want to do is detect a winner when 2 colors are in a row similar to the game "conmect four" taking into account diagonal combos too. BUT i want to do this with out using brute-force enumeration..
this the code that goes behind the program i have made I'm not asking for solution i just need a bit of help on an effective algorithm
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private Button[] btns;
private Button[] btns2;
public Form1()
{
InitializeComponent();
btns = new Button[] { button2, button3 };
btns2 = new Button[] { button4, button5 };
}
private void Form1_Load(object sender, EventArgs e)
{
foreach (var btn in btns)
{
btn.Enabled = false;
btn.BackColor = Color.LightCyan;
}
foreach (var btn in btns2)
{
btn.Enabled = false;
btn.BackColor = Color.LightCyan;
}
}
public int state;
int cc = 0;
private void button1_Click(object sender, EventArgs e)
{
foreach (var btn in btns)
{
{
if (!btn.Enabled)
{
btn.Enabled = true;
if (cc == 0)
{
cc = 1;
btn.BackColor = Color.Red;
}
else
{
cc = 0;
btn.BackColor = Color.Yellow;
}
return;
}
}
}
}
private void button6_Click(object sender, EventArgs e)
{
foreach (var btn in btns2)
{
if (!btn.Enabled)
{
btn.Enabled = true;
if (cc == 0)
{
cc = 1;
btn.BackColor = Color.Red;
}
else
{
cc = 0;
btn.BackColor = Color.Yellow;
}
return;
}
}
}
}
}
First, for efficiency and sanity, I'd keep the state of my board in a 2D array.
Second, for detecting win states, given that you start the game with a (presumably) empty board, you can only get into a win state when a button changes state. And if the button changing state puts you into a win state, then that button must be involved in that win state (i.e. it must be part of you line).
So...you don't need to brute-force the whole board. You only need to determine if the button that just changed state is part of a line. In other words, look only at the buttons to above, below, to the left and to the right (and maybe diagonal, your question wasn't clear if you included diagonals) to see if they are the same color as the one you changed. If any one of them is, then this is a win state. This is where using a 2D array will make you life much easier. If the button at (x, y) is changed, then you only need to check (x-1, y), (x+1, y), (x, y-1) and (x, y+1), (and maybe diagonals) making sure to do appropriate boundary checks, of course.
Extending this to 3, 4 or more in a row isn't much more difficult, except you need to remember you might be in the middle of a row rather than one end or the other.
Unoptimized Pseudo Code for 2 in a row (note, I've switched to compass points to avoid up-left, up-right, etc because I feel it gets a bit unwieldy):
// cell is the cell that last changes, it has an x and y property and a color property
// board is a member variable, a 2D array of cells. Note [0,0] is the upper-left (NW) corner of the board.
// boardHeight and boardWidth are member variable with the dimensions of the board
// board[boardWidth-1, boardHeight-1] is the lower-right (SE) corner of the board
// returns true for a win, false otherwise
function checkWin(cell) returns bool {
// check west
if (cell.x > 0 && board[cell.x - 1, cell.y].color == cell.color)
return true;
// check northwest
if (cell.x > 0 && cell.y > 0 && board[cell.x-1, cell.y-1].color == cell.color)
return true;
// check north
if (cell.y > 0 && board[cell.x, cell.y-1].color == cell.color)
return true;
// check northeast
if (cell.y > 0 && cell.x < boardWidth && board[cell.x+1, cell.y-1].color == cell.color)
return true;
// checking the other directions is left as an exercise for the reader, hopefully you get the point
return false;
}
If you are doing more than 2, I'd think about a recursive function to count the number of matching cells to the left, right, up, down, and diagnoals
// k is the number of cells in a row for a win
function checkWin(cell) returns bool {
// check west / east
int count = checkWest(cell);
if (count > k)
return true;
count += checkEast(cell);
if (count > k)
return true;
// check nw / se
count = checkNW(cell);
if (count > k)
return true;
count += checkSE(cell);
if (count > k)
return true;
// and so on, checking N/S and NE/SW
return false;
}
function checkWest(cell) returns int {
// base case, check the boundaries!
if (cell.x == 0)
return 0;
// base case, the cell next to this one doesn't match
if (board[cell.x-1,cell.y].color != cell.color)
return 0;
// recursion, check the next cell in the line
return 1 + checkWest(board[cell.x-1,cell.y]);
}
For an n by m board and a winning combo of k in a row:
int n, m, k;
byte[,] color = new byte[n, m]; // for two colors, a 0 would correspond to blue, 1 would be red, or however you like
for (int i = 0; i <= n - k; i++) // don't check all the way to the right because there's no room to win
{
for (int j = 0; j <= m - k; j++) // don't check all the way down because there's no room to win
{
// Check here for a win. Check for a win to the right, down right, and down
}
}
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);
}