Optimizing code for searching a text in richtextbox windows form - c#

I have a windows form application that use a RichTextBox to display a text from a file. There is also a TextBox and a Button to perform a search in the text. Whenever the button is clicked, the content of the TextBox is searched and enlightened in the RichTextBox. Here is the code in the button_Click event handler:
String search = richtextboxContent.Text;
richtextboxContent.Text = "";
richtextboxContent.Text = search;
#region code Search
string keyword = textboxsearch.Text.Trim();
int startPosition = 0;
int endPosition = 0;
int endArticle = richtextboxContent.Text.Length;
for (int i = 0; i < endArticle; i = startPosition)
{
if (i == -1)
{
break;
}
startPosition = richtextboxContent.Find(keyword, startPosition, endArticle, RichTextBoxFinds.None);
if (startPosition >= 0)
{
count++;
richtextboxContent.SelectionBackColor = Color.Yellow;
endPosition = textboxsearch.Text.Length;
startPosition = startPosition + endPosition;
}
}
I have one text file of 20mb with 90.000++ lines, whenever I search one word (that resulted with 1300 matchs) the code below take more than 10 minutes to finish.
How can it be improved ?

Related

Connect 4 in C# windows form application

I've been trying to create connect 4 (the game) in windows forms applications in C# but I'm kinda struggling to create a method so the coins actually drop and not stay were the user clicks. Also I don't know where should I place the win condition method. Here is my code so far:
namespace ConnectFour
{
public partial class Form1 : Form
{
Button[] gameButtons = new Button[42]; //array of buttons for markers(red and blue)
bool blue = true; //blue is set to true if the next marker is to be a blue
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
this.Text = "Connect 4";
this.BackColor = Color.BlanchedAlmond;
this.Width = 500;
this.Height = 500;
for (int i = 0; i < gameButtons.Length; i++)
{
int index = i;
this.gameButtons[i] = new Button();
int x = 50 + (i % 7) * 50;
int y = 50 + (i / 7) * 50;
this.gameButtons[i].Location = new System.Drawing.Point(x, y);
this.gameButtons[i].Name = "btn" + (index + 1);
this.gameButtons[i].Size = new System.Drawing.Size(50, 50);
this.gameButtons[i].TabIndex = i;
//this.gameButtons[i].Text = Convert.ToString(index);
this.gameButtons[i].UseVisualStyleBackColor = true;
this.gameButtons[i].Visible = true;
gameButtons[i].Click += (sender1, ex) => this.buttonHasBeenPressed(sender1, index);
this.Controls.Add(gameButtons[i]);
}
}
private void buttonHasBeenPressed(object sender, int i)
{
if (((Button)sender).BackColor == Color.BlanchedAlmond)
{
if (blue == true)
{
((Button)sender).BackColor = Color.Red;
}
else
{
((Button)sender).BackColor = Color.Blue;
}
blue = !blue;
}
}
private void fourInARow(int a, int b, int c,int d)
{
if (gameButtons[a].BackColor == gameButtons[b].BackColor && gameButtons[a].BackColor == gameButtons[c].BackColor && gameButtons[a].BackColor==gameButtons[d].BackColor)
{
if (gameButtons[a].BackColor == Color.Blue)
{
MessageBox.Show("the winner is player 1");
}
else
{
MessageBox.Show("the winner is player 2");
}
}
}
}
}
You've probably figured it out by now, but what I would do is take a look at the BackColor of each Button below the one the user pressed until we either hit the bottom row or we find a Button that doesn't have a "BlanchedAlmond" BackColor, and then that's the one we change.
To find a Button directly below another one, we just look at the Button with an Index of 7 more than the current Index. And since we pass the Button object's index in the index argument to our function, we can use that.
Here's a commented code example:
private void ButtonHasBeenPressed(object sender, int index)
{
// Get the button that the user pressed
var pressedButton = (Button)sender;
// Only do something if they clicked a "neutral" button
if (pressedButton.BackColor == Color.BlanchedAlmond)
{
// The backcolor will be set based on whether or not blue is true or false
var newBackColor = blue ? Color.Red : Color.Blue;
// Get the index of the button that the user clicked
var buttonToChangeIndex = index;
// From where the user clicked, look down at each button below (index + 7)
// until we find the last button that has a BlanchedAlmond backcolor
while (buttonToChangeIndex + 7 < gameButtons.Count() &&
gameButtons[buttonToChangeIndex + 7].BackColor == Color.BlanchedAlmond)
{
buttonToChangeIndex += 7; // Set to the index to point to this button
}
// Now we set that button's backcolor
gameButtons[buttonToChangeIndex].BackColor = newBackColor;
// Flip our blue flag
blue = !blue;
}
}
The fact that you use a one-dimensional array to hold the buttons makes things a little more difficult. Since the layout of this “connect four” game is two-dimensional, it would seem logical to make a two-dimensional array. This will help facilitate the “Drop” in a column. Your current code will need to translate which cells in the one-dimensional array make up a column, namely the one the user clicked.
The code below solves this “Dropping” issue by using a two-dimensional array with a method that gets the ROW index of the next available space in a given column. This method returns the next available open slot index in the given column starting at the bottom and going up or returns -1 if the column is full.
You should seriously consider making a class of tokens/markers/buttons, then another Class that is a game board for these tokens. The logic to see if there are 4 consecutive colors horizontally, vertically and diagonally could possibly be complex. However, this logic would fit perfectly towards a GameBoard Object. In addition, the “GameBoard” object could keep track of all the moves etc…
This will greatly free up your code to focus on the visual aspects of the form and not worry about the logic of the game. I hope this makes sense.
Button[,] gameButtons = new Button[7,6]; //array of buttons for markers(red and blue)
bool blue = true; //blue is set to true if the next marker is to be a blue
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
this.Text = "Connect 4";
this.BackColor = Color.BlanchedAlmond;
this.Width = 500;
this.Height = 500;
int x;
int y;
for (int row = 0; row < gameButtons.GetLength(0); row++) {
x = 50 + (row % 7) * 50;
for (int col = 0; col < gameButtons.GetLength(1); col++) {
y = 50*col + 50;
Button newButton = new Button();
newButton.Location = new System.Drawing.Point(x, y);
newButton.Name = "btn" + (row + col + 1);
newButton.Size = new System.Drawing.Size(50, 50);
newButton.TabIndex = row + col;
newButton.UseVisualStyleBackColor = true;
newButton.Visible = true;
newButton.Click += (sender1, ex) => this.buttonHasBeenPressed(sender1);
gameButtons[row, col] = newButton;
Controls.Add(gameButtons[row,col]);
}
}
}
private void buttonHasBeenPressed(object sender) {
Button buttonClicked = (Button)sender;
int col = buttonClicked.Location.X / 50 - 1;
int targetRow = GetButtonRow(col);
if (targetRow >= 0) {
if (blue == true) {
gameButtons[col, targetRow].BackColor = Color.Red;
} else {
gameButtons[col, targetRow].BackColor = Color.Blue;
}
blue = !blue;
}
}
public int GetButtonRow(int colIndex) {
Button curButton;
for (int row = gameButtons.GetLength(1) - 1; row >= 0; row--) {
curButton = gameButtons[arrayColIndex, row];
if (curButton.BackColor != Color.Red && curButton.BackColor != Color.Blue) {
return row;
}
}
return -1;
}

How to move label's text right to left,when a character hides in left it shows on right?

Here is my key code:
using System;
using System.Windows.Forms;
namespace Scroller
{
public partial class Form1 : Form
{
int i, j;
bool k = false;
public Form1()
{
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
label1.Text = "Time:"+ System.DateTime.Now.ToString();
i--;
j = i + this.Width;
if (i < this.Width && i > 0)
{
label1.Left = i;
}
else
if (i < 0 && k == false)
{
label1.Left = i;
k = true;
}
else
if (i < 0 && k == true)
{
label1.Left = j;
k = false;
}
if (i < 0 - label1.Width)
{
i = this.Width - label1.Width;
}
}
private void Form1_Load(object sender, EventArgs e)
{
label1.Text = "Time:"+ System.DateTime.Now.ToString();
i = this.Width - label1.Width;
label1.Left = i;
}
}
}
The effect that I want to make is the whole time string move right to left. When a pixel of the text disappear on the left side (because it is out of the form's left border),the pixel will shows on the right side.
In other words, the effect can't be make by delete the first character of string and append it to the last.
I knew that it will be easier to use two label to do it. Set one's location in the form and hide the other right by the form. Move them in the same time.
When the first label hit the left border of the form, the second hit the right border of the form. And the first one move out, the second move in. Until the second totally move in, reset their x location.
But I just want to use one label. So I chose to quickly switch the label's location, and try to "cheat" user's eye. The problem is when the label switch between left and right, it flash very obviously. Even though I set timer's interval below 20,the problem still exist.
Could you help me dissolve the flash problem or enlighten me other ways which can just use one label and one timer to make the effect I need?
Thanks. If I didn't describer my problem clear enough or need more code, please let me know.
I don't think you can work out the flashing problem changing the label's location in a windows form.
Another solution would be to set the label width the same size as the form width, make the label text fill all the width using spaces and make the timer always get the last character and put it on the beginning of the string.
Sample code below.
private void timer1_Tick(object sender, EventArgs e)
{
label1.Text = label1.Text.Substring(label1.Text.Length - 1) + label1.Text.Remove(label1.Text.Length - 1);
}
private void Form1_Load(object sender, EventArgs e)
{
// The total spaces required to fill the form vary from form.width and the initial label.text.width
// Width | Spaces
// 177 | 13
// 228 | 30
// 297 | 53
// 318 | 60
// The spacesEnd = 60 work for a form with width 319
int spacesBegin = 0, spacesEnd = 60;
label1.Text = "Time:" + System.DateTime.Now.ToString();
label1.AutoSize = false;
label1.Left = -3;
label1.Width = this.Width - 1;
label1.Height = 15;
label1.BorderStyle = BorderStyle.FixedSingle;
for (int i = 0; i < spacesBegin; i++)
label1.Text = " " + label1.Text;
for (int i = 0; i < spacesEnd; i++)
label1.Text += " ";
Timer timer = new Timer();
timer.Tick += timer1_Tick;
timer.Interval = 50;
timer.Start();
}

TeeChart: Expand the "clickable" width of a Line/FastLine

I have a WinForms application where a number of lines are drawn in a TeeChart component. It is requested that it shall be possible to delete a line by right-clicking it.
Everything works fine, the clickseries event is captured and so on, but the user finds it difficult to hit the line on right click. The question is, is it possible to increase the region where the Line/FastLine object is sensible for clicking? That is, make the line wider without drawing the line any wider on the screen.
Tnx in advance
Yes, this is possible. The key to achieve that is PointInLineTolerance method. To achieve what you request you can combine it with NearestPoint's tool GetNearestPoint method as shown in this example:
public Form1()
{
InitializeComponent();
InitializeChart();
}
private void InitializeChart()
{
tChart1.Aspect.View3D = false;
tChart1.Series.Add(new Steema.TeeChart.Styles.Line()).FillSampleValues();
tChart1.MouseMove += TChart1_MouseMove;
}
private void TChart1_MouseMove(object sender, MouseEventArgs e)
{
var nearestPoint = new Steema.TeeChart.Tools.NearestPoint(tChart1[0]);
nearestPoint.Active = false;
var p = new Point(e.X, e.Y);
var index = nearestPoint.GetNearestPoint(p);
if (index != -1)
{
const int tolerance = 10;
var px = tChart1[0].CalcXPos(index);
var py = tChart1[0].CalcYPos(index);
var index2 = (index == tChart1[0].Count - 1) ? index - 1 : index + 1;
var qx = tChart1[0].CalcXPos(index2);
var qy = tChart1[0].CalcYPos(index2);
if (Steema.TeeChart.Drawing.Graphics3D.PointInLineTolerance(p, px, py, qx, qy, tolerance))
{
tChart1.Header.Text = "point " + index.ToString() + " clicked";
}
else
{
tChart1.Header.Text = "No point";
}
}
An alternative could be using an invisible fake series with same data as the original series.

programmatically move caret in textbox, line up and line down

I am struggling to move the caret in a textbox editing control within a DataGridView, one line up and one line down, just as a user would get when pressing up and down arrows.
So I don't mean lines as what is between newline characters, I mean lines as what is between the left and right side of a textbox.
I cannot use GetCharIndexFromPosition and GetPositionFromCharIndex because not all text will always be shown in the textbox display area.
Edit:
I cannot simulate KeyPress because I am dealing with a textbox cell within a DataGridView. My aim is in fact getting arrow keys to do what they would do in a normal textbox, instead of jumping from row to row.
This should work.
Point pOld = textBox1.GetPositionFromCharIndex(textBox1.SelectionStart);
Point pNew = new Point(pOld.X, pOld.Y + textBox1.Font.Height)
int charIndex = textBox1.GetCharIndexFromPosition(pNew);
textBox1.SelectionStart = charIndex;
I don't think it's the cleanest solution though. Maybe you should look into the DataGridView properties/key handling.
The methods GetPositionFromCharIndex() and GetCharIndexFromPosition() have two limitations:
they don't work for text beyond the borders of the textbox
The character index of TextBox.SelectionStart is the same for a caret at the end of a line and for a caret at the beginning of next line.
To correct this, you can:
scroll the textbox to show the relevant line before using the said methods.
use GetCaretPos function from user32.dll to compare it with the position of SelectionStart. If they are not equal, it means that caret is at the end of line.
simulate {END} key press to position caret at the end of a line.
Another problem I encountered is that TextBox.Lines refers to logic lines separated by new-line characters, while functions TextBox.GetLineFromCharIndex() and TextBox.GetFirstCharIndexFromLine() refer to visual lines as they are displayed in the textbox (that is, from side to side of TextBox, without there having to be new-line characters). Do not mix them up.
Resulting code (ugly as you may claim, but working) is as follows:
class Utils
{
[DllImport("user32.dll")]
static extern bool GetCaretPos(out System.Drawing.Point lpPoint);
public static void LineUp(TextBox tb)
{
int oldCharIndex = tb.SelectionStart;
int oldLineNo = tb.GetLineFromCharIndex(oldCharIndex);
System.Drawing.Point oldCharPos = tb.GetPositionFromCharIndex(oldCharIndex);
System.Drawing.Point oldCaretPos;
if (GetCaretPos(out oldCaretPos))
{
if (oldCharPos == oldCaretPos)
{
if (oldLineNo > 0)
{
tb.SelectionStart = tb.GetFirstCharIndexFromLine(oldLineNo - 1);
tb.ScrollToCaret();
System.Drawing.Point newPos = new System.Drawing.Point(oldCaretPos.X, oldCaretPos.Y - tb.Font.Height);
int newCharIndex = tb.GetCharIndexFromPosition(newPos);
if (tb.GetPositionFromCharIndex(newCharIndex).Y == newPos.Y)
{
tb.SelectionStart = newCharIndex;
}
else
{
tb.SelectionStart = tb.GetFirstCharIndexFromLine(oldLineNo - 1);
System.Windows.Forms.SendKeys.Send("{END}");
}
}
}
else
{
if (oldLineNo > 1)
{
tb.SelectionStart = tb.GetFirstCharIndexFromLine(oldLineNo - 2);
tb.ScrollToCaret();
System.Drawing.Point newPos = new System.Drawing.Point(oldCaretPos.X, oldCaretPos.Y - tb.Font.Height);
int newCharIndex = tb.GetCharIndexFromPosition(newPos);
if (tb.GetPositionFromCharIndex(newCharIndex).Y == newPos.Y)
{
tb.SelectionStart = newCharIndex;
}
else
{
tb.SelectionStart = tb.GetFirstCharIndexFromLine(oldLineNo - 2);
System.Windows.Forms.SendKeys.Send("{END}");
}
}
}
}
}
public static void LineDown(TextBox tb)
{
int oldCharIndex = tb.SelectionStart;
int oldLineNo = tb.GetLineFromCharIndex(oldCharIndex);
System.Drawing.Point oldCharPos = tb.GetPositionFromCharIndex(oldCharIndex);
System.Drawing.Point oldCaretPos;
if (GetCaretPos(out oldCaretPos))
{
if (oldCharPos == oldCaretPos)
{
if (oldLineNo < tb.GetLineFromCharIndex(tb.Text.Length - 1))
{
tb.SelectionStart = tb.GetFirstCharIndexFromLine(oldLineNo + 1);
tb.ScrollToCaret();
System.Drawing.Point newPos = new System.Drawing.Point(oldCaretPos.X, oldCaretPos.Y + tb.Font.Height);
int newCharIndex = tb.GetCharIndexFromPosition(newPos);
if (tb.GetPositionFromCharIndex(newCharIndex).Y == newPos.Y)
{
tb.SelectionStart = newCharIndex;
}
else
{
tb.SelectionStart = tb.GetFirstCharIndexFromLine(oldLineNo + 1);
System.Windows.Forms.SendKeys.Send("{END}");
}
}
}
else
{
System.Drawing.Point newPos = new System.Drawing.Point(oldCaretPos.X, oldCaretPos.Y + tb.Font.Height);
int newCharIndex = tb.GetCharIndexFromPosition(newPos);
if (tb.GetPositionFromCharIndex(newCharIndex).Y == newPos.Y)
{
tb.SelectionStart = newCharIndex;
}
else
{
tb.SelectionStart = tb.GetFirstCharIndexFromLine(oldLineNo);
System.Windows.Forms.SendKeys.Send("{END}");
}
}
}
}
}
Credit for the idea goes to this answer, and you may also want to take a look at MSDN reference on GetCaretPos and other Caret functions.
/// ------------------------------------------------------------------------------------
/// <summary>
/// Processes up key when a grid cell is in the edit mode. This overrides the default
/// behavior in a grid cell when it's being edited so using the up arrow will move the
/// IP up one line rather than moving to the previous row.
/// </summary>
/// ------------------------------------------------------------------------------------
protected virtual bool ProcessUpKey(TextBox txtBox)
{
// Don't override the default behavior if all the text is selected or not multi-line.
if (txtBox.SelectedText == txtBox.Text || !txtBox.Multiline)
return false;
int selectionPosition = txtBox.SelectionStart;
// Getting the position after the very last character doesn't work.
if (selectionPosition == txtBox.Text.Length && selectionPosition > 0)
selectionPosition--;
Point pt = txtBox.GetPositionFromCharIndex(selectionPosition);
if (pt.Y == 0)
return false;
pt.Y -= TextRenderer.MeasureText("x", txtBox.Font).Height;
txtBox.SelectionStart = txtBox.GetCharIndexFromPosition(pt);
return true;
}
/// ------------------------------------------------------------------------------------
/// <summary>
/// Processes down key when a grid cell is in the edit mode. This overrides the default
/// behavior in a grid cell when it's being edited so using the down arrow will move the
/// IP down one line rather than moving to the next row.
/// </summary>
/// ------------------------------------------------------------------------------------
protected virtual bool ProcessDownKey(TextBox txtBox)
{
// Don't override the default behavior if all the text is selected or not multi-line.
if (txtBox.SelectedText == txtBox.Text || !txtBox.Multiline)
return false;
int chrIndex = txtBox.SelectionStart;
Point pt = txtBox.GetPositionFromCharIndex(chrIndex);
pt.Y += TextRenderer.MeasureText("x", txtBox.Font).Height;
var proposedNewSelection = txtBox.GetCharIndexFromPosition(pt);
if (proposedNewSelection <= chrIndex)
return false; // Don't let "down" take you *up*.
txtBox.SelectionStart = proposedNewSelection;
return true;
}

How to prevent user to enter a special character in text box?

I want to prevent one particular character * (asterisk) from being entered or pasted into a text box.
I tried:
key_press event - but it does not handle the case when user pastes an asterisk to the text box.
text_changed event - but when I remove the character, the cursor position goes back to the beginning of the text.
So I am wondering how to handle it, preferably in one event.
use the text changed event, but save the location of the cursor (the SelectionStart and SelectionEnd properties) before you remove the asterisk, then re set the cursor position (less the number of asterisks removed before the cursor).
private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
var currentText = textBox1.Text;
var selectionStart = textBox1.SelectionStart;
var selectionLength = textBox1.SelectionLength;
int nextAsterisk;
while ((nextAsterisk = currentText.IndexOf('*')) != -1)
{
if (nextAsterisk < selectionStart)
{
selectionStart--;
}
else if (nextAsterisk < selectionStart + selectionLength)
{
selectionLength--;
}
currentText = currentText.Remove(nextAsterisk, 1);
}
if (textBox1.Text != currentText)
{
textBox1.Text = currentText;
textBox1.SelectionStart = selectionStart;
textBox1.SelectionLength = selectionLength;
}
}
This question may be of use to you. What you're looking for seems like either a MaskedTextBox or a TextBox with custom Validation logic. You should not simply erase an asterisk characters when it is input, because if a user has selected text, then typed an asterisk, they will have replaced the selected text with an asterisk before you have the chance to remove it.
You can set the cursor postion. For example:
textBox1.SelectionStart = textBox1.Text.Length;
EDIT:
Ok i took some time to write you a solution that works quite good. It keeps the edit cursor at the proper position and also covers the situation in which user pastes some * chars between chars.
int position = this.textBox1.SelectionStart;
string str = this.textBox1.Text;
int hit = 0;
for (int i = 0; i < position; i++)
{
if (str[i].Equals('*'))
hit++;
}
str = str.Replace("*", "");
this.textBox1.Text = str;
this.textBox1.SelectionLength = 0;
this.textBox1.SelectionStart = position - hit;
Here is the solution i found:-
On Text_changed event, here is what i am doing:-
txt1.Text = txt1.Text.Replace("*", string.Empty);
txt1.Select(txt1.Text.Length, 0);
Updated code:-
On Text_changed event:-
int curpos = 0;
bool isReplaced = false;
private void txt1_TextChanged(object sender, EventArgs e)
{
if (txt1.Text.Contains('*'))
{
curpos = txt1.SelectionStart;
isReplaced = true;
}
txt1.Text = txt1.Text.Replace("*", string.Empty);
if (isReplaced)
{
txt1.Select(curpos.Equals(0) ? 0 : curpos -1, 0);
isReplaced = false;
}
}
Final code and Tested :-
if (txt1.Text.Contains('*'))
{
foreach (char c in txt1.Text)
if (c.Equals('*'))
barredCharCount += 1;
curPosition = txt1.SelectionStart;
isTextReplaced = true;
}
txt1.Text = txt1.Text.Replace("*", string.Empty);
if (isTextReplaced)
{
txt1.Select(curPosition.Equals(0) ? 0 : curPosition - barredCharCount, 0);
isTextReplaced = false;
curPosition = barredCharCount = 0;
Console.Beep(); //does not work on 64 bit system
}
This piece of code is tested and working perfectly...

Categories

Resources