I have a Form where one can Sign In and it could take a while till the data gets loaded into the Form. So I wanted to create a seperate Form (loadScreen.cs) with a Progress Bar when the Form is loading. I tried this in the loadScreen.cs Form:
private void Form1_Load(object sender, EventArgs e)
{
worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.ProgressChanged +=
new ProgressChangedEventHandler(worker_ProgressChanged);
worker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
int percentFinished = (int)e.Argument;
while (!worker.CancellationPending && percentFinished < 100)
{
percentFinished++;
worker.ReportProgress(percentFinished);
System.Threading.Thread.Sleep(50);
}
e.Result = percentFinished;
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.Close();
}
I've read that the worker_DoWork method should have the code which takes longer to load. I don't know how to handle this since my button is in Form1. When it's clicked then I go to another class with
private void signIn_Click(object sender, EventArgs e)
{
var logIn = new LogIn(this);
logIn.checkUserInput(this);
}
and there I execute the operations which load certain things. How to connect everything? I need help!
I'm actually in the process of creating a general-purpose dialogue for this sort of thing. It's not going to be ready in time to be of use to you but I would suggest that you go along similar lines. Create your "Loading" dialogue so that it accepts a delegate and invokes it in the DoWork event handler. The main form can then contain a method that does the work and you can pass a delegate for that method to the dialogue. I'll post a very basic example.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private DataTable table;
private void button1_Click(object sender, EventArgs e)
{
var work = new Action(GetData);
using (var f2 = new Form2(work))
{
f2.ShowDialog();
this.dataGridView1.DataSource = this.table;
}
}
private void GetData()
{
this.table = new DataTable();
using (var adapter = new SqlDataAdapter("SELECT * FROM MyTable", "connectionstring here"))
{
adapter.Fill(table);
}
}
}
public partial class Form2 : Form
{
private Action work;
public Form2(Action work)
{
InitializeComponent();
this.work = work;
}
private void Form2_Load(object sender, EventArgs e)
{
this.backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
this.work();
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.DialogResult = DialogResult.OK;
}
}
Note that there's no real way to measure progress when using a data adapter so you could only really display a marquee progress bar in this case.
Related
On .NET Windows form, I have Background worker component that works fine. I have 5 forms, that has basically same Background worker on it with same code.
Can I extract this code to other class and somehow use it, considering this is an event? This is code I have on form. It takes 20 lines of code, and it would be nice if this can be refactored. Note: as you can see, I have already put it to other class BackgroundWorkerHelper, but can I also somehow refactor this events on Background worker, so that it is in other class as well, this way code is less and reused.
private void RunBackgroundWorker(string infoLabelText, int imageIndex)
{
BackgroundWorkerHelper.Run(backgroundWorker, progressBar, infoLabelText, imageIndex);
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorkerHelper.DoWork(backgroundWorker);
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
BackgroundWorkerHelper.ProgressChanged(sender, e, progressBar);
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
BackgroundWorkerHelper.RunWorkerCompleted(sender, e, progressBar);
}
Note: for now I would like to avoid using user control. I know I could do it, but then you have code that handles placing user control and so on. I am still not very good in it.
Here is solution, thanks to rory who gave me idea how to do it. First, I made this class:
public class BackgroundWorkerHelper
{
private static string _infoLabelText = string.Empty;
public BackgroundWorker _BackgroundWorker;
private BarEditItem _marqueeInfo;//this is marquee progress bar
public BackgroundWorkerHelper(BarEditItem marqueeInfo)
{
_marqueeInfo = marqueeInfo;
_BackgroundWorker = new BackgroundWorker();
_BackgroundWorker.WorkerReportsProgress = true;
_BackgroundWorker.WorkerSupportsCancellation = true;
_BackgroundWorker.DoWork += backgroundWorker_DoWork;
_BackgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
_BackgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
}
public void Run(string labelText, int imageIndex)
{
_marqueeInfo.Caption = labelText;
_marqueeInfo.ImageIndex = imageIndex;
if (!_BackgroundWorker.IsBusy)
_BackgroundWorker.RunWorkerAsync();
else
_marqueeInfo.Caption = "Busy processing saving data, please wait...";
}
public void DoWork()
{
for (int i = 0; i <= 5; i++)
{
_BackgroundWorker.ReportProgress(i); // call backgroundWorker_ProgressChanged event and pass i (which is e argument e.ProgressPercentage) to update UI controls
Thread.Sleep(250);
}
}
public void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
_marqueeInfo.Visibility = BarItemVisibility.Always;
}
public void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_marqueeInfo.Visibility = BarItemVisibility.Never;
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
DoWork();
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ProgressChanged(sender, e);
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
RunWorkerCompleted(sender, e);
}
then in FORM, in class level above constructor place
private readonly BackgroundWorkerHelper _backgroundWorkerHelper;
then in Form Constructor instantiate class
_backgroundWorkerHelper = new BackgroundWorkerHelper(marqueeInfo);
and then I just call it in my form
_backgroundWorkerHelper.Run("Saving", 14);
I am attempting to use a Backgroundworker to keep my Main UI thread open and not freezing up. I am stepping thro my code and have set a breakpoint on both the backgroundWorker1.RunWorkerAsync(); which once hits just leaves the method and on the foreach line -> which is never hit.
What is the proper way to use a Backgroundworker?
public Form1()
{
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
}
private void btnQuery_Click(object sender, EventArgs e)
{
grid1.Rows.Clear();
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
foreach (string name in studentRoster)
{
InsertIntoDB();
}
}
Here is your code with the handlers added and some comments.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.DoWork += new DoWorkEventHandler(BackgroundWorker_DoWork);
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(BackgroundWorker_ProgressChanged);
backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundWorker_RunWorkerCompleted);
}
private void btnQuery_Click(object sender, EventArgs e)
{
grid1.Rows.Clear();
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
foreach (string name in studentRoster)
{
InsertIntoDB();
// You can report progress by calling the following function.
//backgroundWorker1.ReportProgress(int percentProgress, object userState)
// You can set the percentProgress to any valid integer value,
// and userState can be any object you want.
// You can also check to see if this operation has been sent a request to cancel.
if (backgroundWorker1.CancellationPending)
{
e.Cancel = true;
return;
}
}
// You can send information back to the main thread by setting e.Result to any object you want.
}
private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Do something with the event that is being raised.
// To pass a value back through to this event, use the percentProgress and userState
// parameters of the ReportProgress function.
// the userState object that you pass will be received here as e.UserState
}
private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// This event is raised by the background worker when the DoWork method is completed.
// You can receive information back from the worker thread by evaluating e.Result
}
}
}
I want to load new form in background and show it after loaded.
but I always got this error : "invalid cross-thread access" with the code under:
How to make it work?!
public partial class f1 : Form
{
private Form f2;
public f1()
{
InitializeComponent();
BackgroundWorker bgw = new BackgroundWorker();
bgw.RunWorkerAsync();
bgw.DoWork += new DoWorkEventHandler(dowork);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(completed);
}
void dowork(object sender, DoWorkEventArgs e)
{
f2 = new f2();
}
void completed(object sender, RunWorkerCompletedEventArgs e)
{
f2.showdialog();
this.Close();
}
}
you may have to separate the data loading for form and form UI initialization into two different faze.
I have changed your code sample to the following.
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
BackgroundWorker bgw = new BackgroundWorker();
bgw.RunWorkerAsync();
bgw.DoWork += new DoWorkEventHandler(dowork);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(completed);
}
void dowork(object sender, DoWorkEventArgs e)
{
e.Result = InitializeNeededDataToShowOnTheSecondform();
}
void completed(object sender, RunWorkerCompletedEventArgs e)
{
BeginInvoke(new MethodInvoker(() =>
{
var f2 = new Form3(e.Result);
f2.ShowDialog();
this.Close();
}));
}
private object InitializeNeededDataToShowOnTheSeconDform()
{
//time consuming data initialization and/or any other time consuming process
return 10;
}
}
I am currently creating a Windows Form Application and I am wanting to use a BackgroundWorker. I have created a very simple example which works perfectly:
public partial class Form1 : Form
{
private BackgroundWorker bgw = new BackgroundWorker();
public Form1()
{
InitializeComponent();
bgw.WorkerReportsProgress = true;
bgw.DoWork += new DoWorkEventHandler(DoWork);
bgw.ProgressChanged += new ProgressChangedEventHandler(ProgressChanged);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Completed);
}
private void button1_Click(object sender, EventArgs e)
{
bgw.RunWorkerAsync();
}
private void DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 10; i++)
{
bgw.ReportProgress(i * 10, i.ToString());
Thread.Sleep(1000);
}
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = string.Format("{0}% : Message = '{1}'", e.ProgressPercentage, e.UserState.ToString());
}
private void Completed(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Completed");
}
}
Now, when I move the same code to my current application it does not fire. The only difference is that instead of running the code at the Form level, I am attempting to run it inside a custom User Control. As such:
public partial class LobbyForm : UserControl
{
private BackgroundWorker bgw = new BackgroundWorker();
public LobbyForm()
{
InitializeComponent();
bgw.WorkerReportsProgress = true;
bgw.DoWork += new DoWorkEventHandler(DoWork);
bgw.ProgressChanged += new ProgressChangedEventHandler(ProgressChanged);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Completed);
}
public LobbyForm(List<TaskFile> tasks)
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
bgw.RunWorkerAsync();
}
private void DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 10; i++)
{
bgw.ReportProgress(i * 10, i.ToString());
Thread.Sleep(1000);
}
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label5.Text = string.Format("{0}% : Message = '{1}'", e.ProgressPercentage, e.UserState.ToString());
}
private void Completed(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Completed");
}
}
Any thoughts on if I am missing something? Perhaps something I am misunderstanding with attempting to run this from a User Control?
I just copied your code and tested it and it worked perfectly if you drag-drop the user control using the designer.
However, if you create he control at runtime and add it to your form, make sure you're using the correct constructor.
LobbyForm lf = new LobbyForm();
this runs this constructor:
public LobbyForm()
{
InitializeComponent();
bgw.WorkerReportsProgress = true;
bgw.DoWork += new DoWorkEventHandler(DoWork);
bgw.ProgressChanged += new ProgressChangedEventHandler(ProgressChanged);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Completed);
}
and not
LobbyForm lf = new LobbyForm(tasks);
which runs this constructor (that doesn't hook up events):
public LobbyForm(List<string> tasks)
{
InitializeComponent();
}
Solution (Call the default constructor from the second one)
public LobbyForm(List<string> tasks) : this()
{
//InitializeComponent();
}
Please note I'm trying to do the following, when I hover the mouse on a button I want the panel to be visible, when the mouse leaves the button or panel, the panel should not be visible. Below you can see the code I have, but the panel it's not staying visible.
private void FormMain()
{
buttonMenu.MouseEnter += new EventHandler(buttonMenu_MouseEnter); //open panel
buttonMenu.MouseLeave += new EventHandler(buttonMenu_MouseLeave);
panelMenu.MouseEnter += new EventHandler(panelMenu_MouseEnter);
panelMenu.MouseLeave += new EventHandler(panelMenu_MouseLeave);
mbB1.MouseEnter += new EventHandler(mbB1_MouseEnter);//button in panel
mbB2.MouseEnter += new EventHandler(mbB2_MouseEnter);//button in panel
}
private void buttonMenu_MouseEnter(object sender, EventArgs e)
{
this.panelMenu.Visible = true;
}
private void buttonMenu_MouseLeave(object sender, EventArgs e)
{
this.panelMenu.Visible = false;
}
private void panelMenu_MouseEnter(object sender, EventArgs e)
{
this.panelMenu.Visible = true;
}
private void panelMenu_MouseLeave(object sender, EventArgs e)
{
this.panelMenu.Visible = false;
}
private void mbB1_MouseEnter(object sender, EventArgs e)
{
this.panelMenu.Visible = true;
}
private void mbB2_MouseEnter(object sender, EventArgs e)
{
this.panelMenu.Visible = true;
}
One solution that came to my mind is using a short timer. You may further optimize it to cut down on LoC, but it works. You may also lower the timer's delay, but 100ms is what I think would be a safe one.
On a side note, I don't think this is a good design. If you want this kind of behaviour, you should either use a ContextMenuStrip or a Click event on the button, and a hide event on panelMenu.MouseLeave. Still, if it's what you really need, this is how I solved it:
public partial class Form1 : Form
{
private bool mouseInPanel;
private Timer hideTimer;
public Form1()
{
InitializeComponent();
buttonMenu.MouseEnter += new EventHandler(button_MouseEnter);
buttonMenu.MouseLeave += new EventHandler(button_MouseLeave);
mbB1.MouseEnter += panelButton_MouseEnter;
mbB2.MouseEnter += panelButton_MouseEnter;
panelMenu.MouseEnter += new EventHandler(panelMenu_MouseEnter);
panelMenu.MouseLeave += new EventHandler(panelMenu_MouseLeave);
hideTimer = new Timer {Interval = 100};
hideTimer.Tick += hidePanel;
}
private void button_MouseEnter(object sender, EventArgs e)
{
this.panelMenu.Visible = true;
}
private void button_MouseLeave(object sender, EventArgs e)
{
hideTimer.Start();
}
private void panelMenu_MouseEnter(object sender, EventArgs e)
{
mouseInPanel = true;
this.panelMenu.Visible = true;
}
private void panelMenu_MouseLeave(object sender, EventArgs e)
{
mouseInPanel = false;
hideTimer.Start();
}
private void panelButton_MouseEnter(object sender, EventArgs e)
{
mouseInPanel = true;
this.panelMenu.Visible = true;
}
private void hidePanel(object sender, EventArgs e)
{
hideTimer.Stop();
if (!mouseInPanel) this.panelMenu.Visible = false;
}
}
What I figured is your mouse actions needed a little bit of a delay, else the panel (along with your buttons mbB1 and mbB2) gets hidden before those buttons' actions could be triggered.
This is because by entering the panel buttons you leave the panel, and it disappears (along with it's capability to receive mouse actions) right before mbB1/mbB2's action can trigger.