Background worker for search and highlight feature - c#

I have a simple while loop that allows me to find text within a textbox, but when I am searching for a word that appears several times in the textbox, it locks up the interface for a while. I'd like to move it into a background worker, but I don't think this can be done because the interface elements (i.e. textbox3.text) are on the main thread. How can I make a background worker when the main interface elements are involved?
I found decent information on the web, but I am having trouble implementing other solutions I have read into my particular situation.
public void button2_Click(object sender, EventArgs e)
{
//Highlight text when search button is clicked
int index = 0;
while (index < dragDropRichTextBox1.Text.LastIndexOf(textBox3.Text))
{
dragDropRichTextBox1.Find(textBox3.Text, index, dragDropRichTextBox1.TextLength, RichTextBoxFinds.None);
dragDropRichTextBox1.SelectionBackColor = Color.Orange;
index = dragDropRichTextBox1.Text.IndexOf(textBox3.Text, index) + 1;
}
}
Thanks for the help.

I guess what you want to do is create sub thread doing the job not to block the UI thread ( pseudo-code, aka not tested ) :
public void button2_Click(object sender, EventArgs e)
{
// Copy text in a non-thread protected string, to be used within the thread sub-routine.
string searchText = textBox3.Text;
string contentText = dragDropRichTextBox1.Text;
// Create thread routine
ThreadPool.QueueUserWorkItem(o => {
// Iterate through all instances of the string.
int index = 0;
while (index < contentText.LastIndexOf(searchText))
{
dragDropRichTextBox1.Invoke((MethodInvoker) delegate {
// Update control within UI thread
//Highlight text when search button is clicked
dragDropRichTextBox1.Find(searchText, index, contentText.Length, RichTextBoxFinds.None);
dragDropRichTextBox1.SelectionBackColor = Color.Orange;
}
// Go to next instance
index = contentText.IndexOf(searchText, index) + 1;
}
});
}
Again, this is untested, but that would give you the idea.
-- EDIT --
You don't need threading at all, doing all the work between a dragDropRichTextBox1.SuspendLayout() and dragDropRichTextBox1.ResumeLayout() is enough.
private void button1_Click(object sender, EventArgs e)
{
// Copy text in a non-thread protected string, to be used within the thread sub-routine.
string searchText = textBox1.Text;
string contentText = richTextBox1.Text;
// Suspend all UI refresh, so time won't be lost after each Find
richTextBox1.SuspendLayout();
// Iterate through all instances of the string.
int index = 0;
while (index < contentText.LastIndexOf(searchText))
{
//Highlight text when search button is clicked
richTextBox1.Find(searchText, index, contentText.Length, RichTextBoxFinds.None);
richTextBox1.SelectionBackColor = Color.Orange;
// Go to next instance
index = contentText.IndexOf(searchText, index) + 1;
}
// Finally, resume UI layout and at once get all selections.
richTextBox1.ResumeLayout();
}

You just need to use invoke when you're manipulating the UI elements in your background thread
https://msdn.microsoft.com/en-us/library/vstudio/ms171728(v=vs.100).aspx

You can call the Invoke Method on the control.
dragDropRichTextBox1.Invoke((MethodInvoker) delegate {
//Your code goes here for whatever you want to do.
}
);
This should fix your problem.

Related

How to use functions step by step

Sorry for the strange title, I just don't know how to name this question.
So I have such a function say().
void say(string printedText) {
gameText.text = printedText;
}
And I need to use it several times. Something like this:
say("test text 1");
say("test text 2");
say("test text 3");
...
I need to change text by clicking Space button. Of course I need to use something like this:
if(Input.GetKeyDown(KeyCode.Space)) {
...
}
But I can't understand how to show text step by step. So for example if I click Space button once I should see "test text 1". Next click should show me "test text 2" etc.
How can I realise it? Thanks in advance.
Depending on your needs you could store different texts in a List<string> or maybe even Queue<string> and do
List example
// Add your texts in the editor or by calling texts.Add(someNewString)
public List<string> texts = new List<string>();
private int index = 0;
if(Input.GetKeyDown(KeyCode.Space))
{
// have a safety check if the counter is still within
// valid index values
if(texts.Count > index) say(texts[index]);
// increase index by 1
index++;
}
Array example
Basically the same as the List<string> but you can't add or remove elements "on the fly" (at least not that simple)
public string[] texts;
private int index = 0;
if(Input.GetKeyDown(KeyCode.Space))
{
// have a safety check if the counter is still within
// valid index values
if(texts.Length > index) say(texts[index]);
// increase index by 1
index++;
}
Queue example
public Queue<string> texts = new Queue<string>();
for adding a new text to the end of the queue do
texts.Enqueue(someNewString);
and then
if(Input.GetKeyDown(KeyCode.Space))
{
// retrieves the first entry in the queue and at the same time
// removes it from the queue
if(texts.Count > 0) say(texts.Dequeue());
}
Simple counter
If it is really just about having a different int value then yes simply use a field
private int index;
if(Input.GetKeyDown(KeyCode.Space))
{
// uses string interpolation to replace {0} by the value of index
say($"test text {0}", index);
// increase index by one
index++;
}
define a class field like this:
int count = 0;
and now everytime space is hit:
if(Input.GetKeyDown(KeyCode.Space)) {
say("test text " + count);
count = count + 1;
}
This Code:
if(Input.GetKeyDown(KeyCode.Space)) {
...
}
only works for Unity, In Visual Studio you'll have to create an Event to whatever object you want to do this, for example, if you want to call a void each time you press spacebar youll have to do this, it is easy: (Image below)
In the propieties window press the bolt icon and after double click on the event you want to create (samples): TextChanged, LocationChnaged, MouseMove, etc...
I will use KeyDown on the TextBox object
image
Now in your code this void should be generated
image
Inside this void i writed the code and this is how it looks:
(put int n = 1 before voids)
private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
if(e.KeyCode == Keys.Space)
{
//int n = 1; must be defined
textBox1.Text = "test text " + n;
n++;
}
}
Now each time you'll press or stay pressed spacebar the textbox will be filled with "test text " and the value will be 1 more each time.

Changing The Label Text in Button Click - Understanding For Loop

When I clicked button starts for loop from i=0 and I want to see on the label value of i. However I only see last values of i.
public partial class Form1 : Form
{
int i;
public Form1()
{
InitializeComponent();
}
private void btnclick_Click(object sender, EventArgs e)
{
for ( i = 0; i < 3; i++)
{
lblForLoopExample.Text = i.ToString();
System.Threading.Thread.Sleep(1000);
}
}
}
when I run the my code I only see on the label ; 2 .
I want to see such a like below;
When the started For loop, i = 0, I must see the 0 on the Label.Text. An then when the i = 1, I must see 1 on the label.Text. And when i = 2, I must see 2 on the Label.Text.
I added Thread.Sleep(1000) however, result didn't change.
Where I am make mistake?
Please help me,
if you help me , I will appreciate you.
Thanks,
You need to append the lbl to get all the values. Right now, it finishes the loop and give you the last value in your label
public partial class Form1 : Form
{
int i;
public Form1()
{
InitializeComponent();
}
private void btnclick_Click(object sender, EventArgs e)
{
for ( i = 0; i < 3; i++)
{
lblForLoopExample.Text + = i.ToString();
}
}
}
Your problem is that you're doing work on the UI thread while expecting the UI thread to update your form.
While you process your loop, the UI thread is actually executing this code. The UI thread is therefore unable to update the form with the intermediate values you are setting within the loop. Once your code completes, the UI thread is then free to update the form. That's why you see the last value only.
You can see this better if you updated your code to loop ten million times instead of 3. Your form will become unresponsive and will appear locked up. That's because Windows knows your UI thread is locked in an intensive process and is unable to update the UI.
The solution is to use a background thread to run your process and synchronize updates with the UI thread. You'll also have to slow your loop down to see the changes, as others have suggested.
To learn more about how the UI thread works, and how to synchronize background threads with it, read this article (it's about WPF, but it covers the general case).
Each loop you re-write the string. Instead of saying
lblForLoopExample.Text = i.ToString();
You need to add to the string on each iteration. I'd create a variable to hold the value, and so something like this:
string myString = string.Empty;
for(i=0;i < 3; i++)
{
myString += i.ToString() + ", ";
}
lblForLoopExample.Text = myString.substring(0, (myString.length - 1));
substring is just so you don't have that trailing comma. It's kind of dirty code, but it will work.
First of all, your code won't compile as you are missing the exact format of the ToString() method in i.ToString along with the ;
lblForLoopExample.Text = i.ToString();
^^^
As per your code, you should try this:
for ( i = 0; i < 3; i++)
{
lblForLoopExample.Text += i.ToString() + ", ";
}

Pause the while loop until the button is pressed w/o using event handler C#

I am struggling to workout how to create something that essentially pauses my while loop until my button1 is pressed, I know about the event handler button1_Click but I don't think that will work in this situation as I have lots of loops nested in each other on my form_load.
Any help would be highly appreciated!
This is a snipped of my code where I want the loop to be 'paused' with the notes:
while (reader2.Read())
{
QuestionSpace = Convert.ToString(reader2["Question Space"]);
label1.Text = QuestionSpace;
if (button1.Click = true) // if the button is clicked)
{
// continue with the while loop (I am going to add an INSERT SQL query in here later)
}
else
{
// pause until the button is pressed
}
}
My whole code for the form:
public partial class CurrentlySetTestForm : Form
{
private int QuestionID { get; set; }
private string QuestionSpace { get; set; }
public CurrentlySetTestForm()
{
InitializeComponent();
}
private void CurrentlySetTestForm_Load(object sender, EventArgs e)
{
string y = GlobalVariableClass.Signedinteacher;
MessageBox.Show(y);
Convert.ToInt32(y);
string connectionString = ConfigurationManager.ConnectionStrings["myconnectionstring"].ConnectionString;
SqlConnection connect = new SqlConnection(connectionString);
connect.Open();
SqlCommand command18 = new SqlCommand("SELECT [QuestionID] FROM QuestionStudentAssociation WHERE ( [StudentID]=#Signedinstudent)", connect);
command18.Parameters.AddWithValue("#Signedinstudent", y);
var reader = command18.ExecuteReader();
while (reader.Read())
{
QuestionID = Convert.ToInt32(reader["QuestionID"]);
SqlCommand command19 = new SqlCommand(#"SELECT [Question Space] FROM Questions WHERE ( [QuestionID] = #currentQID )", connect);
command19.Parameters.AddWithValue("#currentQID", QuestionID);
try
{
var reader2 = command19.ExecuteReader();
while (reader2.Read())
{
QuestionSpace = Convert.ToString(reader2["Question Space"]);
label1.Text = QuestionSpace;
if (button1.Click = true) // if the button is clicked)
{
// continue with the while loop (I am going to add an INSERT SQL query in here later)
}
else
{
// pause until the button is pressed
}
}
}
catch (SyntaxErrorException ex)
{
MessageBox.Show(ex.Message);
}
finally
{
MessageBox.Show("Done one loop");
}
}
}
}
Sounds like your not ready to learn TPL
So maybe a BackgroundWorker , you can paint it on the form
To make the click cancel the background worker have a look at Cancel backgroundworker
I would some time to learn TPL as its going to create a simpler and more elegant solution.
As for pausing I would refactor the code, you should not keep the reader open waiting on the user.
You do want event-driven response to UI events, always. However, I guess that you don't want to split your logic into a state machine by hand (where each event triggers progress to the next state). Well, you're in luck, the C# compiler has some keywords to build state machines automagically so you don't have to manage the details.
There are actually two different mechanisms for continuation-passing style implemented in C#. The old one, yield return, works great if your UI events are pretty much interchangeable (or you're only interested in one). Works like this:
IEnumerator<int> Coroutine;
// this could be a Form_Load, but don't you need to get the user information before making the database connection?
void BeginQuiz_Click( object sender, EventArgs unused )
{
Coroutine = RunQA();
}
IEnumerator<int> RunQA()
{
// connect to DB
// show first question on UI
return ContinueQA();
}
IEnumerator<int> ContinueQA()
{
// you can use a while loop instead if you really want
for( int question = 0; question < questionCount; ++question )
{
// check answer
if (/* too many wrong answers*/) {
// report failure in DB
yield break;
}
// get next question from DB
// show new question on the UI
// wait for UI action
yield return question;
}
// report score in DB
// update UI with completion certificate
}
void AnswerButton_Click( object sender, EventArgs unused )
{
answer = sender;
Coroutine.MoveNext(); // MAGIC HAPPENS HERE
}
void TimeoutTimer_Tick( object sender, EventArgs unused )
{
answer = TimeoutTimer;
Coroutine.MoveNext();
}
The magic comes from yield return. Every time the function reaches yield return, the compiler saves what you were doing. When the button click event comes and calls MoveNext, the compiler generates code that starts where yield return paused everything, and keeps going from there until the next yield return.
Important note, the code inside ContinueQA doesn't start when RunQA() does return ContinueQA(); It actually starts on the first MoveNext(). So split your code between RunQA() and ContinueQA accordingly.
If you need different pause reasons at different places in your code, then async/await will be more helpful.
A better way to handle this would be the use of a timer. This would allow the form to draw it's controls and handle all input, such as clicking the button.
Adjust the timer interval (ms) to your needs.
Another way of doing this would be, as Mehrzad Chehraz said, to use multi-threading.
On a side note, I would strongly recommend condition checks over the try/catch checks if possible.
Enable/Disable the timer using the button and call the loop when the timer ticks.
Example:
Timer loopTimer = new Timer();
private void Form1_Load(object sender, EventArgs e)
{
loopTimer.Interval = 100;
loopTimer.Tick += loopTimer_Tick;
loopTimer.Enabled = true;
}
void loopTimer_Tick(object sender, EventArgs e)
{
//perform the loop here at the set interval
}
private void button1_Click(object sender, EventArgs e)
{
//pause/play the loop
loopTimer.Enabled = !loopTimer.Enabled;
}

How to display data received from serial port in a textbox without the text disappearing in Visual Studio C#?

So, I'm trying to develop a simple application in visual C# which gets data from serial port and displays it in a textbox (to monitor temperature). I'm acquiring and displaying the data successfully, using the DataReceived event to update a global string variable and a timer to update the text field on my text box, as shown:
private void port_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
try
{
globalVar.updateTemp = port.ReadLine(); //This is my global string
}
catch (IOException)
{
}
catch (InvalidOperationException)
{
}
catch (TimeoutException)
{
}
}
private void timer1_Tick(object sender, EventArgs e)
{
tempDisplayBox.Text = globalVar.updateTemp; //This is my textbox updating
}
The only issue I have is that the value shown in the textbox keeps flashing, making it hard to read. My timer is set to trigger every 10 ms (which should be fast enough, right?). Is there any way to make it more stable? I realize this may be a newb question, but to be fair I am a newb :) Any help is appreciated! Thanks!
Do you really need it updating every 10ms? What about every 500 ms or if not that then 100ms. 100ms will require your update method run 10 times less and therefore update 10 times less. The flickering you are expiriencing is due to the refresh speed. You could create custom method which will only update the temp only when target Label or textBox value is different than source port. But that will only sort the flickering when temp is steady, when temp will start vary it will bring back the flickering. Good luck ;-)
UPDATE
Hi I tried to reproduce the conditions and could not make my textbox nor Label flash. The way I tested it was by assigning int ntick = 0; and then increment the ++ntick; inside of the timer_tick method. The results didn't make any of the controls flash and were updated even every milisecond at some point. I also tried string.Format to put some load on the method. Is your app responsive?
The trick is to use double buffering. This way the operating system will redraw the Control off-screen, and only show the control when it is fully redrawn.
I have had the same problem, and solved it by extending the TextBox control like this:
public FastLogBox()
{
InitializeComponent();
_logBoxText = new StringBuilder(150000);
timer1.Interval = 20;
timer1.Tick += timer1_Tick;
timer1.Start();
SetStyle(ControlStyles.DoubleBuffer, true);
}
void timer1_Tick(object sender, EventArgs e)
{
if (_timeToClear)
{
_logBoxText.Clear();
_timeToClear = false;
}
if (_logQueue.Count <= 0) return;
while (!_logQueue.IsEmpty)
{
string element;
if (!_logQueue.TryDequeue(out element)) continue;
{
_logBoxText.Insert(0, element + "\r\n");
}
}
if (_logBoxText.Length > 150000)
{
_logBoxText.Remove(150000, _logBoxText.Length - 150001);
}
Text = _logBoxText.ToString();
}
public new void Clear()
{
_timeToClear = true;
while (!_logQueue.IsEmpty)
{
string element;
_logQueue.TryDequeue(out element);
}
}
public void AddToQueue(string message)
{
_logQueue.Enqueue(message);
}
}
I also use a timer and a concurrentQueue to avoid using Invoke to update the control from another thread. I also use a StringBuilder to prepare the string before putting it into the TextBox. StringBuilder is faster when building larger strings.
You can use ReadExisting() to read the whole data at a time.
You need to handle DataReceived Event of SerialPort
serialPort1.ReadExisting();
Sample:
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
String myData=serialPort1.ReadExisting();
}
Example Code: Here i would like to show you the code to Read Data(RFID Tag Code which is basically of length 12)
String macid = "";
private void DoWork()
{
Invoke(
new SetTextDeleg(machineExe ),
new object[] { macid });
macid = "";
}
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
string str1;
macid += serialPort1.ReadExisting();
if (macid.Length == 12)
{
macid = macid.Substring(0, 10);
Thread t = new Thread(new ThreadStart(DoWork));
t.Start();
}
}
public void machineExe(string text)
{
TextBox1.Text=text;
}
Thank you so much for the answers! I found a way to work around this issue:
Instead of replacing the contents of my textbox by rewriting the TextBox.Text property - which, as HenningNT implied, refreshes the control and causes the flickering - I'm now using the TextBox.AppendText method. Though, as I want to display only one line of data at a time, I use the textbox in multiline mode and the Environment.NewLine to jump to a new line before appending the text. As for the method of updating, I've gone back to using the timer because with the invoke method was crashing my application when I close the form, for some reason. Also, enabling double buffering didn't do me much good, although I guess I was doing it wrong... It still flickers a bit, but it's much better now :) I know this is not really a perfect solution (much more of a workaround), so I'll keep looking for it. If I find it, I'll be sure to update it here ;) My code:
private void timer1_Tick(object sender, EventArgs e) //Timer to update textbox
{
if (tempDisplayBox.Text != globalVar.updateTemp) //Only update if temperature is different
{
try
{
tempDisplayBox.AppendText(Environment.NewLine);
tempDisplayBox.AppendText(globalVar.updateTemp);
}
catch (NullReferenceException)
{
}
}
}

Slide object using For Loop (C#)

I did quite a bit of searching around and didn't find anything of much help.
Is it possible to "slide" or "move" using C#, an object from one Location to another using a simple For loop?
Thank you
I would suggest you rather use a Timer. There are other options, but this will be the simplist if you want to avoid threading issues etc.
Using a straight for loop will require that you pump the message queue using Application.DoEvents() to ensure that windows has the opportunity to actually render the updated control otherwise the for loop would run to completion without updating the UI and the control will appear to jump from the source location to the target location.
Here is a QAD sample for animating a button in the Y direction when clicked. This code assumes you put a timer control on the form called animationTimer.
private void button1_Click(object sender, EventArgs e)
{
if (!animationTimer.Enabled)
{
animationTimer.Interval = 10;
animationTimer.Start();
}
}
private int _animateDirection = 1;
private void animationTimer_Tick(object sender, EventArgs e)
{
button1.Location = new Point(button1.Location.X, button1.Location.Y + _animateDirection);
if (button1.Location.Y == 0 || button1.Location.Y == 100)
{
animationTimer.Stop();
_animateDirection *= -1; // reverse the direction
}
}
Assuming that the object you're talking about is some kind of Control you could just change the Location property of it.
So something like this:
for(int i = 0; i < 100; i++)
{
ctrl.Location.X += i;
}
Should work I think.

Categories

Resources