I have a permanent message box in my windows forms application. Not to be confused with a pop up message box. The box I have displays text based on various buttons pushed in the application to update the user on whats happening. I successfully use the code below to display text in the box.
messageBox.Text += "I stick message in here" + Environment.NewLine;
I am interested in displaying some blinking dots in the box to indicate that something is coming. eg. "Finding stuff..."
I have found this code for Console.Write below.
How can I make this work in my message box
for (int dots = 0; dots <= 3; ++dots)
{
Console.Write("\rStuff will come{0}", new string('.', dots));
System.Threading.Thread.Sleep(500); // half a sec
}
I've solved a similar problem using a Tasks. One Task to do the background work and one to update the message text. Here is some sample code:
private void button1_Click(object sender, EventArgs e)
{
// call the helper to do something
Task.Factory.StartNew(() => { FakeSearch(); });
//Generate the update the waiting text
Task.Factory.StartNew(() => { updateWaiting(); });
}
private void FakeSearch()
{
_externalFlag = false;
Thread.Sleep(5000);
_externalFlag = true; // simulate completing the task
}
private bool _externalFlag = false;
private void updateWaiting()
{
int count = 0;
StringBuilder waitingText = new StringBuilder();
waitingText.Append("Finding stuff");
int baseLen = waitingText.Length;
while (!_externalFlag)
{
Thread.Sleep(1000); // time between adding dots
if (count >= 3) // number of dots
{
waitingText.Remove(baseLen, count);
count = 0;
}
waitingText.Append(".");
count++;
BeginInvoke(new Action( () => { updateText(waitingText.ToString()); }) );
}
BeginInvoke(new Action( () => { updateText("done"); }) );
}
private void updateText(string txt)
{
textBox1.Text = txt;
}
Related
I am filling Richtextbox with adding lines from another thread that extract links from web. when the web urls links increase and go more than 9000 it hangs the UI and take long time don't know why ! , using button click event to fire this method ( thread )
Using AppendText(Environment.NewLine) method to fill the richtextbox
Here is the snippet code of my work :
if (URLLMemoRichTxt.Lines.Length == 0)
{
XtraMessageBox.Show("You have to get some links first");
return;
}
var thd = new Thread(() =>
{
if (this.InvokeRequired)
{
if (URLLMemoRichTxt.InvokeRequired)
{
URLLMemoRichTxt.Invoke((MethodInvoker)delegate ()
{
foreach (string line in URLLMemoRichTxt.Lines)
{
if (!GetEmailsListArraylist.Contains(line) && line.Trim() != string.Empty)
{
if (LinksToGetEmailsRichTxt.InvokeRequired)
{
LinksToGetEmailsRichTxt.Invoke((MethodInvoker)delegate ()
{
GetEmailsListArraylist.Add(line);
// LinksToGetEmailsRichTxt.Text += line + "\n";
LinksToGetEmailsRichTxt.AppendText(Environment.NewLine + line);
LinksToGetEmailsLabel.Text = LinksToGetEmailsRichTxt.Lines.Length.ToString();
});
}
else
{
GetEmailsListArraylist.Add(line);
// LinksToGetEmailsRichTxt.Text += line + "\n";
LinksToGetEmailsRichTxt.AppendText(Environment.NewLine + line);
LinksToGetEmailsLabel.Text = LinksToGetEmailsRichTxt.Lines.Length.ToString();
}
}
}
});
}
else
{
foreach (string line in URLLMemoRichTxt.Lines)
{
if (!GetEmailsListArraylist.Contains(line) && line.Trim() != string.Empty)
{
GetEmailsListArraylist.Add(line);
// LinksToGetEmailsRichTxt.Text += line + "\n";
LinksToGetEmailsRichTxt.AppendText(Environment.NewLine + line);
LinksToGetEmailsLabel.Text = LinksToGetEmailsRichTxt.Lines.Length.ToString();
}
}
if (MainTabcontrol.InvokeRequired)
{
MainTabcontrol.Invoke((MethodInvoker)delegate ()
{
MainTabcontrol.SelectedTabPageIndex = 1;
});
}
else
{
MainTabcontrol.SelectedTabPageIndex = 1;
}
}
}
else
{
}
if (MainTabcontrol.InvokeRequired)
{
MainTabcontrol.Invoke((MethodInvoker)delegate ()
{
MainTabcontrol.SelectedTabPageIndex = 1;
});
}
else
{
MainTabcontrol.SelectedTabPageIndex = 1;
}
});
thd.SetApartmentState(ApartmentState.MTA);
thd.Start();
using button click event to fire this method ( thread )
Here is your code, boiled down:
private void button1_Click(object sender, EventArgs e)
{
new Thread(() =>
{
if (URLLMemoRichTxt.InvokeRequired)
{
URLLMemoRichTxt.Invoke((MethodInvoker)delegate ()
{
// .. do some "work" in here ...
Thread.Sleep(9000);
});
}
}).Start();
}
Let's analyze what's happening here:
User clicks the button.
Running in the UI thread, the Click event creates a New Thread and starts it.
Inside that new thread, immediately ask if we are running in a different thread than the one that owns the RichTextBox with InvokeRequired (the answer is, of course, yes!).
Tell the application to run some code on the thread that owns the RichTextBox with Invoke().
Running in the UI thread again, do the actual "work".
End Result?
All your "work" is run in the main UI thread as if no threading ever occurred.
Why create a thread and then tell it to run its work back on the UI thread?
How should it be done correctly?
Here's a slightly more robust version of the boiled down code, with only the part that updates the UI within the Invoke() block:
private void button1_Click(object sender, EventArgs e)
{
new Thread(() =>
{
// do some "work" in our new thread
for(int i = 1; i <= 10; i++)
{
Thread.Sleep(1000); // the "work"
// when you need to UPDATE THE UI, now is the time for Invoke()
// note that only the part that is updating the UI is within this block
// the rest of the code/loop is still running in the new thread
URLLMemoRichTxt.Invoke((MethodInvoker)delegate ()
{
URLLMemoRichTxt.AppendText("Line " + i.ToString() + "\r\n");
});
// ... possibly more work in the new thread ...
}
}).Start();
}
With this approach, the UI will remain responsive while the for loop runs and the work is done. Additionally, the RichTextBox will happily update with the new entries without blocking.
well i am new to C#, and implementing a code, in which i have two buttons, with one acting as starting of data acquisition and storing it in a csv file and other button to stop it.
well codes for all these are as follows:
//button for start DAQ
private void stdaq_Click(object sender, EventArgs e)
{
stopped = false;
process();
}
//button for stoping DAQ
private void spdaq_Click(object sender, EventArgs e)
{
stopped = true;
}
// process function
private process()
{
int iAvail = 0;
int iRead = 0;
string filename = #textBox3.Text;// taking csv file name from user
// jit:
//a function calculating the total number of values and storing it in iAvail
int[] iRawData = new Int32[iAvail];
double[] dScaledData = new Double[iAvail];
//a function transferring the data from buffer and storing it in dscaledData array
List<double> data = new List<double>();
for (int i = 0; i < iAvail; i++)
{
data.Add(dScaledData[i]);
}
Task myFirstTask = Task.Factory.StartNew(()
=>
{
while (stopped == false)
{
Write(data.ToArray(), filename);
// goto jit;
}
});
}
// csv creater and data writer
public static void Write(double[] data, string outputPath)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.GetLength(0); i++)
{
if (stopped) break;
sb.AppendLine(string.Join(",", data[i]));
}
if (File.Exists(outputPath))
{
File.AppendAllText(outputPath, sb.ToString());
}
else
{
File.WriteAllText(outputPath, sb.ToString());
}
}
this is what i am implementing, and the problem with this code is that when the data is first transferred and written to the file, then again the same data is written again and again irrespective of new data and i tried implementing that Goto statement(can be seen in comments) but it is giving error - " Control cannot leave the body of an anonymous method or lambda expression ", and if i don't use the While loop the data is not written at all.
So i want to call my process function and to transfer data to csv starting on press of a start button, take fresh data everytime and write it to csv or can say call the process method again from it's start point and to stop it on click of the stop button, but i am unable to do it irrespective of various tries with different loops and some threading functions also.
please help with this.
Assuming you only need to Write once, you should remove this or change it from while to if:
while (stopped == false)
The loop will cause Write to be called infinitely until stopped becomes true.
Also, you might want to change Write to return rather than break if stopped is true, so that you don't write anything if you are supposed to be stopping:
if (stopped) break;
to
if (stopped) return;
If you want to generate data again and really do want to loop forever, just move that code into the loop:
Task myFirstTask = Task.Factory.StartNew(()
=>
{
while (stopped == false)
{
List<double> data = new List<double>();
// TODO: Generate data here - move all relevant code here
Write(data.ToArray(), filename);
}
});
I think this is a job for the BackgroundWorker.
This code will start you up:
public partial class Form1 : Form
{
int loopCounter = 0; // variable just used for illustration
private static BackgroundWorker bw = new BackgroundWorker(); // The worker object
// This function does your task
public void doSomeStuff(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 1000; i++)
{
loopCounter = i; // Pass the loop count to this variable just to report later how far the loop was when the worker got cancelled.
Thread.Sleep(100); // Slow down the loop
// During your loop check if the user wants to cancel
if (bw.CancellationPending)
{
e.Cancel = true;
return; // quit loop
}
}
}
// This button starts your task when pressed
private void button1_Click(object sender, EventArgs e)
{
bw.WorkerSupportsCancellation = true; // Set the worker to support cancellation
bw.DoWork += doSomeStuff; // initialize the event
if (!bw.IsBusy) // Only proceed to start the worker if it is not already running.
{
bw.RunWorkerAsync(); // Start the worker
}
}
// This button stops your task when pressed
private void button2_Click(object sender, EventArgs e)
{
// Request cancellation
bw.CancelAsync();
textBox1.Text = "The bw was cancelled when 'loopCounter' was at: " + loopCounter.ToString();
}
}
I'm writing a program, that should replace or remove some entries from a logfile.txt.
The code is working fine ( at least for small LogFiles). If i use a big file (like 27 MB) its getting very slow and the UI freeze. I cant click anything.
On Button click i execute this method:
private string delete_Lines(string[] lines, string searchString)
{
for (int i = 0; i < lines.Length; i++)
{
if (lines[i].Contains(searchString))
{
rtbLog.Text += "Deleting(row " + (i + 1) + "):\n" + lines[i] + "\n";
progressBar1.Value += 1;
if (cbDB == true)
{
while (is_next_line_block(lines, i) == true)
{
i++;
rtbLog.Text += lines[i] + "\n";
progressBar1.Value += 1;
}
}
}
else
{
res += lines[i]+"\n";
progressBar1.Value += 1;
}
}
tssLbl.Text = "Done!";
rtbLog.Text += "...Deleting finished\n";
return res;
}
Lines is the array of the logfile i am trying to clean up. every entry is a single row . tssLbl is a notification label and rtbLog is a richTextBox, where i'am tracking which row i am deleting.
is_next_line_block is just another method, which is checking of the next lines are part of the block i want to delete. The params of this method are the whole lines array and the line position.
private bool is_next_line_block(string[] lines, int curIndex)
{
if (curIndex < lines.Length-1)
{
if (lines[curIndex + 1].StartsWith(" "))
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
Have anybody any idea, what is causing that freezes and is slowing down the program? I know, that i could speed my code up by parallelizing it, but i cant imagine, that it takes so long to check up a 27 MB txt file without parallelism.
You have several issues here:
You are reading the whole file in buffer (array of string), I am guessing you are calling File.ReadAllLines(). Reading big files in buffer will slow you down, as well as in extreme case run you out of memory.
You are using += operation for your rich textbox Text property. That is time consuming operation as UI has to render the whole rich text box every time you update the text property that way. Better option is to use string builder to load these text, and update rich text box periodically.
To fix this you need to read the file as stream. Progress can be monitored based on bytes read instead of line position. You can run the read operation async and monitor progression on a timer, as shown in example below.
private void RunFileOperation(string inputFile, string search)
{
Timer t = new Timer();
int progress = 0;
StringBuilder sb = new StringBuilder();
// Filesize serves as max value to check progress
progressBar1.Maximum = (int)(new FileInfo(inputFile).Length);
t.Tick += (s, e) =>
{
rtbLog.Text = sb.ToString();
progressBar1.Value = progress;
if (progress == progressBar1.Maximum)
{
t.Enabled = false;
tssLbl.Text = "done";
}
};
//update every 0.5 second
t.Interval = 500;
t.Enabled = true;
// Start async file read operation
System.Threading.Tasks.Task.Factory.StartNew(() => delete_Lines(inputFile, search, ref progress, ref sb));
}
private void delete_Lines(string fileName, string searchString, ref int progress, ref StringBuilder sb)
{
using (var file = File.OpenText(fileName))
{
int i = 0;
while (!file.EndOfStream)
{
var line = file.ReadLine();
progress = (int)file.BaseStream.Position;
if (line.Contains(searchString))
{
sb.AppendFormat("Deleting(row {0}):\n{1}", (i + 1), line);
// Change this algorithm for nextline check
// Do this when it is next line, i.e. in this line.
// "If" check above can check if (line.startswith(" "))...
// instead of having to do it nextline next here.
/*if (cbDB == true)
{
while (is_next_line_block(lines, i) == true)
{
i++;
rtbLog.Text += lines[i] + "\n";
progressBar1.Value += 1;
}
}*/
}
}
}
sb.AppendLine("...Deleting finished\n");
}
As a follow up to your question on Task.Factory.Start() usage, it's done this way (generally):
// you might need to wrap this in a Dispatcher.BeginInvoke (see below)
// if you are not calling from the main UI thread
CallSomeMethodToSetVisualCuesIfYouHaveOne();
Task.Factory.StartNew(() =>
{
// code in this block will run in a background thread...
}
.ContinueWith(task =>
{
// if you called the task from the UI thread, you're probably
// ok if you decide not to wrap the optional method call below
// in a dispatcher begininvoke...
Application.Current.Dispatcher.BeginInvoke(new Action(()=>
{
CallSomeMethodToUnsetYourVisualCuesIfYouHaveAnyLOL();
}));
}
Hope this helps!
Thanks to everybody for the help, especially loopedcode, That's the working version (Took loopedcode's code and made some edit):
private void RunFileOperation(string inputFile, string search)
{
Timer t = new Timer();
StringBuilder sb = new StringBuilder();
{
rtbLog.Text = "Start Deleting...\n";
}
// Filesize serves as max value to check progress
progressBar1.Maximum = (int)(new FileInfo(inputFile).Length);
t.Tick += (s, e) =>
{
rtbLog.Text += sb.ToString();
progressBar1.Value = progress;
if (progress == progressBar1.Maximum)
{
t.Enabled = false;
tssLbl.Text = "done";
}
};
//update every 0.5 second
t.Interval = 500;
t.Enabled = true;
// Start async file read operation
if (rbtnDelete.Checked)
{
if (cbDelete.Checked)
{
System.Threading.Tasks.Task.Factory.StartNew(() => delete_Lines(inputFile, search, ref progress, ref sb, ref res1));
}
}
else
{
//..do something
}
private void delete_Lines(string fileName, string searchString, ref int progress, ref StringBuilder sb, ref StringBuilder res1)
{
bool checkNextLine=false;
using (var file = File.OpenText(fileName))
{
int i = 0;
while (!file.EndOfStream)
{
i++;
var line = file.ReadLine();
progress = (int)file.BaseStream.Position;
if (line.Contains(searchString))
{
sb.AppendFormat("Deleting(row {0}):\n{1}\n", (i), line);
checkNextLine = true;
}
else
{
if (cbDB && checkNextLine && line.StartsWith(" "))
{
sb.AppendFormat("{0}\n", line);
}
else
{
checkNextLine = false;
res1.AppendLine(line);
}
}
}
}
sb.AppendLine("\n...Deleting finished!);
}
Is there any way i can display a String character by character? like in old RPGs
I've tried this:
string text1 ="this is a text";
for (int i = 0; i < text1.Length; i++)
{
textBox1.Text = "" + text1[i];
}
but it only replaces the last character on the text box.
Try This:
public partial class Form1 : Form
{
int count = 0;
string text1 = "this is a scrolling text";
System.Windows.Forms.Timer timer1 = new System.Windows.Forms.Timer();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
textBox1.ReadOnly = true;
SetTimer(500);
}
private void SetTimer(int milliseconds)
{
timer1.Tick+=new EventHandler(timer1_Tick);
timer1.Interval = milliseconds;
timer1.Start();
}
private void timer1_Tick(Object o, EventArgs e)
{
if (count < text1.Length)
{
textBox1.Text += text1[count];
count++;
}
else
{
timer1.Stop();
button1.Enabled = true;
textBox1.ReadOnly = false;
}
}
}
Output:
The following code should do it:
public Form1()
{
InitializeComponent();
TypeText("this is a text");
}
private void TypeText(string text)
{
textBox1.Clear(); // Make sure the textbox is empty
Thread thread = new Thread(delegate() // Create a new thread which fills the textbox periodically
{
button1.BeginInvoke((MethodInvoker)delegate { button1.Enabled = false; }); // Disables the button
for (int i = 0; i < text.Length; i++)
{
int temp = i; // Cache variable because without this, an 'ArgumentOutOfRange' Exception will be thrown
textBox1.BeginInvoke((MethodInvoker)delegate // Invoke to main thread
{
textBox1.Text += text[temp]; // Fill with next char
});
if (text[temp] != ' ') // This makes sure the user doesn't have to wait the double of the time when there is an empty space for the new character
Thread.Sleep(500); // This will stop the seperate thread for 500ms. Won't block the main thread
}
button1.BeginInvoke((MethodInvoker)delegate { button1.Enabled = true; }); // Reenables the button
});
thread.Start(); // Start the new thread and continue the main thread
}
I'm not sure exactly how that helps you. You might want to add why you are doing this. The issue you are having with your code is that your are not appending to the string. Try this line instead:
textBox1.Text += text1[i];
You need to clear textBox1.Text before you start (set it to "").
A better approach might be to print out the hex values so that non printing characters are easily seen. You can do something like this:
string text1 ="this is a text";
var sb = new StringBuilder();
for (int i = 0; i < text1.Length; i++) {
sb.AppendFormat("{0:X} ", text1[i]);
}
textBox1.Text = sb.ToString();
Reading Jeroen Vannevel makes me thing that you are trying to create a "typewriter" affect. In that case try something like:
string text1 ="this is a text";
textBox1.Text = "";
for (int i = 0; i < text1.Length; i++) {
textBox1.Text += text1[i];
Thread.Sleep(250); // 1/4 sec delay
}
This code was written modeled on the code given. There is an implied expectation that this is run from the main UI thread. This means that when you are sitting in a loop with a Sleep delay your UI will be unresponsive (since you are tying up the main thread). You can overcome this by using the Invoke method on the Dispatcher object (of your App) and running the code on a different thread. You should only Invoke the parts that are owned by the main UI (textBox1) in this case.
textBox1.Text = textBox1.Text + text1[i]
To me it seems that you want to break a string into its characters, there are several ways to do that:
1 - String.GetChar() Method:
http://msdn.microsoft.com/en-us/library/microsoft.visualbasic.strings.getchar%28v=vs.110%29.aspx
2 - String.Chars property:
http://msdn.microsoft.com/en-us/library/system.string.chars%28v=vs.110%29.aspx
TextBox1.Text += MyString.Chars[i];
Then if you want to show them one by one with a delay or something like that, you can use timers as others have suggested.
I'm trying to let a program post a bunch of text. The user enters text, the amount of messages and how fast these must be delivered. While the program is busy, the button text needs to be "Stop" instead of "Start". When you press the button to force it to stop after you've initially launched it, the text changes back to "Start", but this doesn't happen when the program stops after the given amount of messages are delivered, even though the code is in place and doesn't generate an error.
I have a feeling that this is because of the text not updating for some reason. I've tried to flush it with Invalidate() and Update(), but this isn't working. How to fix this?
Here is the code:
private void button1_Click(object sender, EventArgs e)
{
if (button1.Text == "Start")
{
isEvil = true;
button1.Text = "Stop";
Thread t = new Thread(StartTyping);
t.Start(textBox1.Text);
}
else
{
isEvil = false;
button1.Text = "Start";
}
}
private void StartTyping(object obj)
{
string message = obj.ToString();
int amount = (int)numericUpDown2.Value;
Thread.Sleep(3000);
for (int i = 0; i < amount; i++)
{
if (isEvil == false)
{
//////This does NOT work
//button1.Text = "Start";
//button1.Invalidate();
//button1.Update();
//button1.Refresh();
//Application.DoEvents();
break;
}
SendKeys.SendWait(message + "{ENTER}");
int j = (int)numericUpDown1.Value * 10;
Thread.Sleep(j);
}
}
You have four answers telling you to update UI stuff from the UI thread, but none of them address the logic flow problem with your code.
The reason why it doesn't happen is because it only happens in the for-loop when isEvil is false. When does isEvil get set to false? Only when you click "Stop", and nowhere else.
If you want the button to go back to "Start" after the thread finishes, without clicking "Stop", then you need to add code after the loop to do that, independent of the value of isEvil: (piggybacking off of VoidMain's answer)
private void StartTyping(object obj)
{
string message = obj.ToString();
int amount = (int)numericUpDown2.Value;
Thread.Sleep(3000);
for (int i = 0; i < amount; i++)
{
if (isEvil == false)
{
if (button1.InvokeRequired)
{
button1.BeginInvoke( new Action(() => { button1.Text = "Start"; }) );
}
else
{
button1.Text = "Start";
}
break;
}
SendKeys.SendWait(message + "{ENTER}");
int j = (int)numericUpDown1.Value * 10;
Thread.Sleep(j);
}
if (button1.InvokeRequired)
{
button1.BeginInvoke( new Action(() => { button1.Text = "Start"; }) );
}
else
{
button1.Text = "Start";
}
}
Now you have duplicated code, so you might want to split it off into a separate method.
You need to be on the UI thread to update the UI.
Try something called the SynchronizationContext. There are plenty of examples when you google it.
If you're in WPF or Silverlight, you could use the Dispatcher. Again, lots of examples if you search those keywords in google or StackOverflow.
You must update your controls from the UI thread. This is how you would do it for winforms.
for (int i = 0; i < amount; i++)
{
if (isEvil == false)
{
button1.Invoke(new Action(() => button1.Text = "Start"));
break;
}
SendKeys.SendWait(message + "{ENTER}");
int j = (int)numericUpDown1.Value * 10;
Thread.Sleep(j);
}
This will block till button1 get's its text updated. If you don't want it to block, replace Invoke with BeginInvoke
Your best bet is to use a BackgroundWorker. It's a bit too wieldy to add a concise example here but there's a decent tutorial from O'Reilly
Something like this (not tested) should work:
private void StartTyping(object obj)
{
string message = obj.ToString();
int amount = (int)numericUpDown2.Value;
Thread.Sleep(3000);
for (int i = 0; i < amount; i++)
{
if (isEvil == false)
{
if(button1.InvokeRequired)
{
button1.BeginInvoke( new Action(() => { button1.Text = "Start"; }) );
}
else
{
button1.Text = "Start";
}
break;
}
SendKeys.SendWait(message + "{ENTER}");
int j = (int)numericUpDown1.Value * 10;
Thread.Sleep(j);
}
}