I have a windows form (Form1) which when a button is clicked on it I want to open another windows form(form2) in a thread.
I want to open it in a thread because when it starts up I have it read and color syntax of 10k+ line long files which takes about 5 or 6 min.
My problems are I do not know the "proper" way to do it. I have found out how to do it and it works right, but I want to be able to have a progress bar telling me how far along Form2 is with its processing. (probably with a Background worker object)
Here is my layout
Form1 has a RichTextBoxes and one buton, click the button and it opens a form in another thread that colors the text in form1's rtb.
Form2 has a Rtb also, this rtb has a method ProcessAllLines which processes the lines and does the work that I want done in another thread.
This is why I think I am having difficulty. The textbox is doing the work while the form is waiting there to load.
Here is how I open the Form2:
private void button1_Click(object sender, EventArgs e)
{
ColoringThread colorer = new ColoringThread(this.m_bruteView.Text);
Thread theThread = new Thread(new ThreadStart(colorer.OpenColorWindow));
theThread.Start();
}
public class ColoringThread
{
string text;
public ColoringThread(string initText)
{
text = initText;
}
public void OpenColorWindow()
{
Form2 form2 = new Form2(text);
form2.ShowDialog();
}
};
And here is how form2 does work:
string initText;
public Form2(string initTextInput)
{
initText = initTextInput;
InitializeComponent();
}
private void InitializeComponent()
{
this.m_ComplexSyntaxer = new ComplexSyntaxer.ComplexSyntaxer();
this.SuspendLayout();
//
// m_ComplexSyntaxer
//
this.m_ComplexSyntaxer.Dock = System.Windows.Forms.DockStyle.Fill;
this.m_ComplexSyntaxer.Location = new System.Drawing.Point(0, 0);
this.m_ComplexSyntaxer.Name = "m_ComplexSyntaxer";
this.m_ComplexSyntaxer.Size = new System.Drawing.Size(292, 273);
this.m_ComplexSyntaxer.TabIndex = 0;
this.m_ComplexSyntaxer.Text = initText;
this.m_ComplexSyntaxer.WordWrap = false;
// THIS LINE DOES THE WORK
this.m_ComplexSyntaxer.ProcessAllLines();
//
// Form2
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.Add(this.m_ComplexSyntaxer);
this.Name = "Form2";
this.Text = "Form2";
this.ResumeLayout(false);
}
And here is where the work is done:
public void ProcessAllLines()
{
int nStartPos = 0;
int i = 0;
int nOriginalPos = SelectionStart;
while (i < Lines.Length)
{
m_strLine = Lines[i];
m_nLineStart = nStartPos;
m_nLineEnd = m_nLineStart + m_strLine.Length;
m_nLineLength = m_nLineEnd - m_nLineStart;
ProcessLine(); // This colors the current line!
i++;
nStartPos += m_strLine.Length + 1;
}
SelectionStart = nOriginalPos;
}
Sooo, what is the "proper" way to open this form2 and have it report progress to Form1 to show to the user? (I ask because I was told this is not the right thing to do, I will get a "cross-thread" violation apparently?)
I want to open it in a thread because when it starts up I have it read and color syntax of 10k+ line long files which takes about 5 or 6 min.
Why not read the data in a separate thread but then keep the UI itself on the same thread as the first form?
In my experience if you use just one thread for UI operations, it makes life significantly simpler.
As for how you should start a new thread to read the data, there are various options:
You could use BackgroundWorker; which would probably make the progress reporting simpler. See the MSDN article for more information.
Create a new Thread directly and start it
Use a thread-pool thread with ThreadPool.QueueUserWorkItem
If you're using .NET 4, use Task.Factory.StartNew
The other form doesn't need to run in another explicit thread in my opinion.
Sure you want to have the long work done in another thread (or a background worker), but you just want to call another form, and that other form will perform the task in another thread (or background worker).
Related
I have created a Windows Forms Console Application in which I am reading a file which has been written by another console application.
The other console application will write about the status of some process and the Windows Forms application will read the status and accordingly update the status text box.
I wrote the following code for above scenario.
while (true)
{
if ((new FileInfo(filename)).Length > 0)
{
Status = File.ReadAllText(filename, Encoding.ASCII);
System.IO.File.WriteAllText(filename, string.Empty);
Statustb.Text = Status;
Statustb.Refresh();
if (Status.Equals("Data Got Loaded"))
{
Environment.Exit(0);
}
}
}
When I am running the Windows Forms application it shows "Form Not Responding" but when I comment out these lines then it will run smoothly. But for me it is important to update the status.
You have to understand the architecture of a GUI application.
All interactions with the user happen on one thread.
This includes reacting to things like mouse and keyboard events etc.
If one of these events happens you can handle that event and do something in response to it.
But, until you return from the handler, the application will not be able to receive any further notifications (Windows Messages aka events).
I suspect you have the above code in either the constructor or in one or other event handler. Since you never exit (infinite loop, due to while(true) without a return or break, the operating system cannot send any further events to the application. They get put in a queue to be sent, but never get picked up.
Windows will detect this situation and give you the Not Responding dialog message.
I suggest, that, instead of having the code inside the while(true) loop, you create a Timer with a suitable Interval, and put the body of the while statement (ie the bit between the { and }, but not the while(true) itself ) in the Tick handler.
It is better to use the code inside a timer.
Still, you need to make sure that no two different threads at the same time accessing a file. You should have used lock while reading and writing it.
I have a pattern that I use for getting long running tasks off of the UI thread. To see it, create a Winforms project and open the code-behind for Form1.cs. Delete the content and copy the following into that file. It should run and it has comments describing what it is doing.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace POC_DoEvents_alternate
{
public partial class Form1 : Form
{
private Button button1;
private Button button2;
private TextBox textbox1;
public Form1()
{
InitializeComponent();
// programmatically create the controls so that the
// entire source code is contained in this file.
// normally you wouldn't do this.
button1 = new Button();
button1.Name = "button1";
button1.Enabled = true;
button1.Location = new Point(12, 12);
button1.Size = new Size(144, 35);
button1.Text = "button1";
button1.Click += button1_Click;
this.Controls.Add(button1);
button2 = new Button();
button2.Name = "button2";
button2.Enabled = false;
button2.Location = new Point(12, 53);
button2.Size = new Size(144, 35);
button2.Text = "button2";
button2.Click += button2_Click;
this.Controls.Add(button2);
textbox1 = new TextBox();
textbox1.Name = "textbox1";
textbox1.Location = new Point(12, 94);
textbox1.ReadOnly = true;
textbox1.Size = new Size(258, 22);
this.Controls.Add(textbox1);
this.Load += new System.EventHandler(this.Form1_Load);
}
private void Form1_Load(object sender, EventArgs e)
{
textbox1.Text = "You can't press button 2 yet...";
button1.Enabled = true;
button2.Enabled = false;
this.Cursor = Cursors.AppStarting;
// start the long running task in a separate background thread
ThreadPool.QueueUserWorkItem(Async_LongRunningTask, "Form1_Load");
// calling the QueueUserWorkItem will not block. Execution will
// contiune immediately with the lines below it.
textbox1.BackColor = Color.LightPink;
// this event handler finishes quickly so the form will paint and
// be responsive to the user.
}
private void button1_Click(object sender, EventArgs e)
{
textbox1.Text = "Button 1 pressed";
}
private void button2_Click(object sender, EventArgs e)
{
textbox1.Text = "Button 2 pressed";
}
private void Async_LongRunningTask(object state)
{
// put all your long running code here, just don't put any
// UI work on this thread
Thread.Sleep(5000); // simulates a long running task
// put any UI control work back on the UI thread
this.Invoke((MethodInvoker)delegate
{
button2.Enabled = true;
textbox1.Text = "End of long running task: " + state.ToString();
textbox1.BackColor = SystemColors.Control;
this.Cursor = Cursors.Default;
// as with anything on the UI thread, this delegate
// should end quickly
});
// once the delegate is submitted to the UI thread
// this thread can still do more work, but being a
// background thread, it will stop when the application
// stops.
Thread.Sleep(2000); // simulates a long running task
}
}
}
You can add using System.Windows.Forms;
Application.DoEvents();
in the While
c# .net Winforms,IDE: VS 2010
I having two windows from F1, F2.
F1 is the caller, and F2 is the form which I want to load in thread because it is having lots of rich controls on it.
I am able to load the f2 in child thread but it just get visible and goes, because its on child thread.(See Case 1 Code)
Case 1 Code
private void button1_Click(object sender, EventArgs e)
{
StartProgress();
Thread th = new Thread(new ThreadStart(LoadForm));
th.Start();
}
private void StartProgress()
{
progressBar1.Maximum = 100;
progressBar1.Step = 1;
for (int i = 0; i <= 100; i++)
{
label1.Text = i + "%";
progressBar1.PerformStep();
Thread.Sleep(10);
label1.Refresh();
}
}
private void LoadForm()
{
Form2 f2 = new Form2();
f2.Show();
}
}
Then I did reverse that is I loaded progress bar in child thread and loaded f2 on main thread.(See Case 2 Code)
//Code Case2:
case 2 when progress bar is on child thread.
private void button1_Click(object sender, EventArgs e)
{
LoadForm();
Thread th = new Thread(new ThreadStart(StartProgress));
th.Start();
LoadForm();
}
private void StartProgress()
{
progressBar1.Maximum = 100;
progressBar1.Step = 1;
for (int i = 0; i <= 100; i++)
{
label1.Text = i + "%";
progressBar1.PerformStep();
Thread.Sleep(10);
label1.Refresh();
}
}
private void LoadForm()
{
Form2 f2 = new Form2();
f2.Show();
}
but case 2 having 2 problems.
Problem 1: It loads the f2 as usual with flicking.
Problem 2: Cross Thread Opration progressbar1
//Pls suggest how to load the f2 in background and show it after the progress bar is loaded.
If you need to load a secondary window in the background, a better approach would be to load the data in the background of your current UI and then pass that information to the new Window.
public void btnNewWindow_Click(object sender, EventArgs args)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += WorkerOnDoWork;
worker.ProgressChanged += WorkerOnProgressChanged;
worker.RunWorkerCompleted += WorkerOnRunWorkerCompleted;
worker.RunWorkerAsync();
}
private List<string> _data = new List<string>();
private void WorkerOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
{
//Work has finished. Launch new UI from here.
Form2 f2 = new Form2(_data);
f2.Show();
}
private ProgressBar progressBar1;
void WorkerOnProgressChanged(object sender, ProgressChangedEventArgs e)
{
//Log process here
}
private void WorkerOnDoWork(object sender, DoWorkEventArgs e)
{
//Perform work you need to load the data.
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(50);
_data.Add("Test" + i);
}
}
I put this together real quick in WPF but you should be able to make this work with the same principles. I will reiterate that you shouldn't use a second UI thread. Load the data up front and then pass along to the window when ready and available.
If all you want to do is avoid the flicker, create F2 as hidden, and only show it once its initialization is complete.
It's been a while since I did any Windows Forms programming, but I guess you can show the form in your _Load event handler.
You shouldn't have more than one User Interface (UI) thread. The complexity to keep the data between your interface will be quite difficult and often make the application Not Thread Safe. Which will cause error after error for you.
The primary goal should keep the User Interface (UI) thread responsive. This way as the user interacts with your application the interface is responsive as the application performs computational task.
Here is a great article on keeping your UI thread responsive here.
You may want to note what Microsoft says here:
Note: Not all changes to the UI are necessarily done on the UI thread.
There's a separate render thread that can apply UI changes that won't
affect how input is handled or the basic layout. For example many
animations and transitions that inform users that a UI action has
taken place can actually run on this render thread. But if it's your
code that is changing UI elements somewhere in your pages, it's best
to assume that your code could have the potential to block the UI
thread, unless you're familiar with those APIs or subsystems and know
for certain they don't affect the UI thread.
Update:
I didn't realize you were using Windows Forms, the article listed above is for XAML. You can use this article to help build a responsive UI with computational task here for Windows Forms. (Link is for WPF, you'll need to port for Windows Forms).
I am using toolStripStatusLabel in multithreading and although it is another thread which interact with the toolStripStatusLabel my invokeRequired method of the statusStrip always return false (I enforce the creation of the handle also). In this thread I can access the toolStripStatusLabel (from else clause), update it but my editing could not be shown in the main UI. Here is my code:
public void safeThreaded()
{
Form2 form = new Form2();
StatusStrip ss = (StatusStrip)form.Controls["statusStrip1"];
ToolStripStatusLabel label = (ToolStripStatusLabel)ss.Items["toolStripStatusLabel1"];
string text = "Written by the background thread.";
if (!(ss.IsHandleCreated))
{
IntPtr handler = ss.Handle;
}
if (ss.InvokeRequired)
{
ss.Invoke(new updateTextCallback(updateText), new object[]{"Text generated on non-UI thread."});
}
else
{
// It's on the same thread, no need for Invoke
label.Text = text + " (No Invoke)";
MessageBox.Show(label.Text.ToString());
ss.Refresh();
}
}
private void updateText(string text)
{
Form2 form = new Form2();
StatusStrip ss = (StatusStrip)form.Controls["statusStrip1"];
ToolStripStatusLabel label = (ToolStripStatusLabel)ss.Items["toolStripStatusLabel1"];
label.Text = text;
}
public delegate void updateTextCallback(string text);
It's because the owning thread is whatever thread you're calling safeThreaded() from - you're creating the Form with the StatusStrip and ToolStripStatusLabel, and then editing it all in the same thread.
If you were to create the Form in one thread, and then run your safeThreaded() function (without creating the Form2 in it) in a separate thread, then you should see InvokeRequired be true.
I am trying to write a simple multithreaded program in C#. It has a button pressing which creates a new label on form, and then a for loop runs displaying loop value in label. So if you press button 3 times, it will create 3 threads with 3 labels on form with loop.
When I press the button once, it works fine. But when I press it more than once to create more labels, it runs into following problems:
As soon as button is pressed more than once, it stops the loop in previous thread and runs loop of new thread. If it is multithreaded then it should not stop first loop.
When loop of second label is finished, it gives following error
Object reference not set to an instance of an object
Here is my complete code. The line which throws error is at the end "mylabel[tcount].Text = i.ToString();".
Screenshot of program: http://i.imgur.com/IFMIs.png
Screenshot of code http://i.imgur.com/sIXtc.png
namespace WindowsFormsApplication2{
public partial class Form1 : Form{
public Form1(){
InitializeComponent();
}
private int tcount = 0;
private int y_point = 0;
Thread[] threads = new Thread[5];
Label[] mylabel = new Label[5];
private void button1_Click(object sender, EventArgs e){
threads[tcount] = new Thread(new ThreadStart(work));
threads[tcount].Start();
}
private void work(){
if (this.InvokeRequired){
this.Invoke(new MethodInvoker(delegate{
mylabel[tcount] = new Label();
mylabel[tcount].Text = "label" + tcount;
mylabel[tcount].Location = new System.Drawing.Point(0, y_point + 15);
y_point += 25;
this.Controls.Add(mylabel[tcount]);
for (int i = 0; i < 10000; i++){
mylabel[tcount].Text = i.ToString();
Application.DoEvents();
}
}));
}
tcount++;
}
}
}
If it is multithreaded then it should not stop first loop.
But it is not multithreaded.
this.Invoke(new MethodInvoker(delegate{
This switches via invoker the context back to the UI Thread, so while you open a lot of threads in the background, you basically then put all the processing back into one main thread.
This:
Application.DoEvents();
Then gives other queued work a chance. Still only on the UI thread.
And finally you never parametrize the threads so they all work on the same variables. There is only one non thread save (no lock, no volatile) variable named tCount - bang.
Basically you demonstrate:
Your problem is not solvable multi threaded - any UI element manipulation HAS to happen on the UI thread (which is why you invoke) and as this is all you do you basically can not multithread.
You lack a basic understanding on how UI programs work with threads and the message pump.
You lack a basic understanding on variable scoing and access patterns between threads.
Back to reading documentation I would say.
The problem is the scope of tcount, as all threads acces the same instance of it, so as soon as the second thread starts the first thread also wirtes into the second label.
Also you invoke your whole worker method which will let it run in the UI-Thread again -> not actually multithreaded...
Your worker method should look something like this:
private void work()
{
int tIndex = tCount; //store the index of this thread
tcount++;
mylabel[tIndex] = new Label();
mylabel[tIndex].Text = "label" + tcount;
mylabel[tIndex].Location = new System.Drawing.Point(0, y_point + 15);
y_point += 25;
Invoke((MethodInvoker)delegate() { this.Controls.Add(mylabel[tIndex]); });
for (int i = 0; i < 10000; i++)
{
//doWork
Invoke((MethodInvoker)delegate() { mylabel[tIndex].Text = i.ToString(); });
}
}
Jep, you need to copy tcount to a local variable. As soon as you hit the button twice while a thread has not yet terminated, it is manipulating the second one.
I have a program, which creates one pictureBox in Form1, and then creates an instance of a class that I called InitialState. The InitialState puts the source to the Image so that it is displayed, and after some time has passed, for which I used a Timer, it creates the next class, MainMenuState. Now, in that MainMenuState class that I've created, I would like to create another pictureBox and make it display on that Form1. Later on, I would like to make the pictures inside it change a bit, and then (possibly) destroy that pictureBox. After that, the program enters the next state (which is in yet another class), and again I would like that class to add a picture box to the original form, and so on.
Basically, I would like to dynamically add controls to the main Form1, but not in the said form, but from the classes I create later on. I've been searching on the internet for a way to do that, and it seems like I would have to use a delegate in order to invoke the Controls.Add method of the Form1 class. I've tried that, and the code compiles, but the pictureBox still doesn't show up.
Here's my code:
Form1 class:
public const string RESOURCE_PATH = "C:/Users/Noel/Documents/Visual Studio 2010/Projects/A/Resources/Animations/";
public Form1()
{
InitializeComponent(); //here, the first pictureBox shows
iInitializeComponent();
zacetnaAnimacija.Dock = DockStyle.Fill; //zacetnaAnimacija is the first pictureBox that appears
zacetnaAnimacija.Anchor = AnchorStyles.Top | AnchorStyles.Left;
zacetnaAnimacija.SizeMode = PictureBoxSizeMode.StretchImage;
InitialState intialState = new InitialState(this, zacetnaAnimacija); //entering InitialState
}
InitialState class:
class InitialState : State
{
System.Timers.Timer initialTimer;
PictureBox pictureBox1;
Form1 form;
public InitialState (Form1 form, PictureBox pictureBox1) {
this.form = form;
GifImage zacetnaSlika = new GifImage(Form1.RESOURCE_PATH + "Presenting.gif"); //this is just a .gif picture I'm displaying
Image trenutnaSlika = zacetnaSlika.GetFrame(0); //a method that plays the .gif
pictureBox1.Image = trenutnaSlika; //makes the first .gif display
this.pictureBox1 = pictureBox1;
initialTimer = new System.Timers.Timer(2500);
initialTimer.Enabled = true;
initialTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
initialTimer.Enabled = false;
MainMenuState menuState = new MainMenuState(form, pictureBox1); //enters main menu state with the Form1 argument passed on
}
MainMenuState class:
class MainMenuState : State
{
Form1 form;
public MainMenuState (Form1 form, PictureBox pictureBox1) {
this.form = form;
GifImage zacetnaSlika = new GifImage(Form1.RESOURCE_PATH + "animated.gif");
Image trenutnaSlika = zacetnaSlika.GetFrame(0);
pictureBox1.Image = trenutnaSlika; //this simply makes another .gif appear in the picture box instead of the first one
PictureBox a = new PictureBox(); //HERE'S my problem, when I want to add ANOTHER pictureBox to that form.
a.BackgroundImage = trenutnaSlika;
a.Location = new System.Drawing.Point(0, 0);
a.Name = "zacetnaAnimacija";
a.Size = new System.Drawing.Size(150, 150);
a.TabIndex = 1;
a.TabStop = false;
AddControl(a); //calling the delegate
}
public delegate void AddControls(PictureBox a);
public void AddControl(PictureBox a)
{
if (form.InvokeRequired)
{
AddControls del = new AddControls(AddControl);
form.Invoke(del, new object[] { a });
}
else
{
form.Controls.Add(a);
}
}
As I've said, the code compiles, but it doesn't create the PictureBox a on the Form1, when the MainMenuState is created. The thing is, if I don't use the delegate in the MainMenuState and just try to do something like form.Controls.Add(a), then I get a "cross-thread operation not valid" exception, and it doesn't even compile. That's why I used the delegate, but even now, it doesn't work.
Can someone please help me?
initialTimer = new System.Timers.Timer(2500);
That's part of the reason you're having trouble. The Elapsed event runs on a threadpool thread, forcing you to do the BeginInvoke song and dance. Use a System.Windows.Forms.Timer instead, its Tick event runs on the UI thread.
You'll also run into trouble with memory management, these classes need to implement IDisposable.
Oh my God, I just found the reason X_x
It was the fact that since the first pictureBox was covering the entire form, and the second one, which was created by the delegate, showed behind it! I just need to bring it to front!
Thank you guys, nonetheless, I probably wouldn't have come to that without you.
Edit: However, may I ask how to bring that control to the front? The a.BringToFront() function doesn't seem to work.
Instead of
form.Invoke(del, new object[]{a});
try:
form.Invoke(new ThreadStart(delegate
{
form.Controls.Add(a);
}
));