C# code refactoring Background worker - c#

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);

Related

Can't change form labels on event

I have two static labels that are supposed to show the users counters that I have kept on the back end but they do not change on the form. I have tried searching for answers but I can't understand most of them.
//Watchers
private static void CDdirWatcher_Created(object sender, FileSystemEventArgs e)
{
CDCreated += 1;
}
private static void LPdirWatcher_Created(object sender, FileSystemEventArgs e)
{
LPCreated += 1;
}
So the above are events where my counters go up
And below is the events I'm trying to make to create the change to the labels.
private void cdCounterLbl_TextChanged(object sender, EventArgs e)
{
cdCounterLBL.Text = CDCreated.ToString();
}
private void lpCounterLbl_TextChanged(object sender, EventArgs e)
{
lpCounterLBL.Text = Convert.ToString(LPCreated);
}
I'm trying
cdCounterLBL.TextChanged += cdCounterLbl_TextChanged;
lpCounterLBL.TextChanged += lpCounterLbl_TextChanged;
I've tried the above but the labels still don't change
Thank you for taking the time to share your problem.
It seems that you misunderstood what are things like classes, variables, methods and events, and how to use them.
That said and if I understood what you want to do, this may solve your problem.
TextChanged, for label, is raised when you change the Text property by code.
It is not because you change the values of your counters that some event will be raised for the labels.
Counters and labels are two separate things, totally different and unrelated, without links, but you can link as follows:
private static void CDdirWatcher_Created(object sender, FileSystemEventArgs e)
{
CDCreated += 1;
cdCounterLbl_TextChanged(this, null);
}
private static void LPdirWatcher_Created(object sender, FileSystemEventArgs e)
{
LPCreated += 1;
lpCounterLbl_TextChanged(this, null);
}
private void cdCounterLbl_TextChanged(object sender, EventArgs e)
{
cdCounterLBL.Text = CDCreated.ToString();
}
private void lpCounterLbl_TextChanged(object sender, EventArgs e)
{
lpCounterLBL.Text = LPCreated.ToString();
}
So that does not work because of static methods and we have a bad design because of calling TextChanged to change the Text.
Thus here how to do.
Add a static event:
private static Action CountersUpdated;
Add in the form's Load event:
CountersUpdated += DoUpdateCounters;
And in the form's FormClosed event:
CountersUpdated -= DoUpdateCounters;
With:
private void DoUpdateCounters()
{
cdCounterLBL.Text = CDCreated.ToString();
lpCounterLBL.Text = LPCreated.ToString();
}
Now you can write:
private static void CDdirWatcher_Created(object sender, FileSystemEventArgs e)
{
CDCreated++;
CallCountersUpdated();
}
private static void LPdirWatcher_Created(object sender, FileSystemEventArgs e)
{
LPCreated++;
CallCountersUpdated();
}
private static void CallCountersUpdated()
{
if ( CountersUpdated != null ) CountersUpdated();
}
You can also make two events for CDCounterUpdated and LPCounterUpdated instead of one CountersUpdated.
Have a good job and a good life in C# and OOP.

can't get text from clipboard | c#

It doesn't show me anything inside the logBox, it just stays blank
namespace Clipboard_Logger
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
backgroundWorker1.RunWorkerAsync();
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
logBox.SelectionStart = logBox.TextLength;
logBox.ScrollToCaret();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
if (Clipboard.ContainsText(TextDataFormat.Text))
logBox.Text = logBox.Text + Clipboard.GetText(TextDataFormat.Text) + "\r\n";
}
}
}
}
You are using a background thread ( BackGroundWorker.DoWork ) to access controls on the UI thread. Controls can only be accessed from the UI thread.
Try adding a BackGroundWorker.ProgressChanged event and access your control from that. ProgressChanged runs from the UI thread.
Edit from your comment:
No, that's not what I meant, your're creating a new backgroundworker, you should use the existing one, like this:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.ReportProgress(1);
}
Also, you need to copy text to the clipboard.

Progress Bar Implementation in windows form

I tried to implement progressbar in windows form but progress bar is showing result after completion of whole execution.
public Status()
{
InitializeComponent();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (ForReporting.FileProcessed <= ForReporting.TotalNumberFiles)
{
Thread.Sleep(100);
int temp=ForReporting.FileProcessed*100;
temp = temp / ForReporting.TotalNumberFiles;
backgroundWorker1.ReportProgress(temp);
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
private void Status_Load(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}

Background Worker loading screen in Winforms

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.

updating gui from another class c#

hey i am new to c# plz help.
i am writing a program that sorts data in a file and it is a time consuming process so i thought that i should run it in a separate thread and since it has alot of step so i made a new class for it. the problem is that i want to show the progress in the main GUI and i know for that i have to use Invoke function but the problem is that the form control variables are not accessible it this class. what should i do ??????
sample code:
public class Sorter
{
private string _path;
public Sorter(string path)
{
_path = path;
}
public void StartSort()
{
try
{
processFiles(_path, "h4x0r"); // Just kidding
}
catch (Exception e)
{
MessageBox.Show("Error: " + e.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void processFiles(string Dir, string[] key)
{
/* sorting program */
}
and it is used as
public partial class Form1 : Form
{
Sorter sort;
public Form1()
{
InitializeComponent();
}
private void browseBtn_Click(object sender, EventArgs e)
{
if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
textBox1.Text = folderBrowserDialog1.SelectedPath;
}
private void startBtn_Click(object sender, EventArgs e)
{
if (startBtn.Text == "Start Sorting")
{
Thread worker = new Thread(new ThreadStart(delegate() {
sort = new Sorter(textBox1.Text);
sort.StartSort(); }));
worker.start();
}
else
MessageBox.Show("Cancel");//TODO: add cancelling code here
}
}
plz help..
Add an Event to your class that is doing the multi-threaded work, that triggers when the progress changes. Have your form subscribe to this event and update the progress bar.
Note ProgressEventArgs is a little class that inherits EventArgs and has an Integer for the progress.
// delegate to update progress
public delegate void ProgressChangedEventHandler(Object sender, ProgressEventArgs e);
// Event added to your worker class.
public event ProgressChangedEventHandler ProgressUpdateEvent
// Method to raise the event
public void UpdateProgress(object sender, ProgressEventArgs e)
{
ProgressChangedEventHandler handler;
lock (progressUpdateEventLock)
{
handler = progressUpdateEvent;
}
if (handler != null)
handler(sender, e);
}
I would recommend you read up on the BackgroundWorker class. It is exactly for the problem you are trying to solve and makes things a lot easier than doing manual threading yourself.
Brief Example
public Form1()
{
InitializeComponent();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.WorkerSupportsCancellation = true;
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
}
void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
private void btnStart_Click(object sender, EventArgs e)
{
if (!backgroundWorker.IsBusy)
backgroundWorker.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i < 101; ++i)
{
if (backgroundWorker.CancellationPending)
{
e.Cancel = true;
break;
}
else
{
//Sort Logic is in here.
Thread.Sleep(250);
backgroundWorker.ReportProgress(i);
}
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
if (backgroundWorker.IsBusy && backgroundWorker.WorkerSupportsCancellation)
backgroundWorker.CancelAsync();
}
You could do something like this:
public delegate void StatusReporter(double progressPercentage);
public class MainClass
{
public void MainMethod()
{
Worker worker = new Worker(ReportProgress);
ThreadStart start = worker.DoWork;
Thread workThread = new Thread(start);
workThread.Start();
}
private void ReportProgress(double progressPercentage)
{
//Report here!!!
}
}
public class Worker
{
private readonly StatusReporter _reportProgress;
public Worker(StatusReporter reportProgress)
{
_reportProgress = reportProgress;
}
public void DoWork()
{
for (int i = 0; i < 100; i++ )
{
// WORK, WORK, WORK
_reportProgress(i);
}
}
}
There are a few option available to solve this sort of issue. In any case, you will have to fiddle with Invoke to get the UI to update.
You could...
...add an event that fires on your new class which your UI can listen to, and Invoke as applicable - you'd still need to pass the data to your worker class (by constructor, properties, method call, etc)
...keep the method as a method on your form, and pas that to start your new thread from (after all, a new thread doesn't have to be starting in a different class)
...change the access modifiers on your controls to be (say) internal such that any class within the same assembly can Invoke changes to the controls, or read from them.
...make your worker class a child of the form it needs to access - it can then see the privates of its parent, as long as it is passed a reference to the instance.

Categories