How to create and add controls in run time with BackgroundWorker - c#

I don't know how to implement a method with a separate thread using the BackgroundWorker in WinForms.
I want this method (after every click on a button) to perform:
create ProgressBar (each new one under the previous one)
create Bitmap and BackgroundWorker
set color of every pixel in that Bitmap in the separate thread using BackgroundWorker
display a precentage progress on the ProgressBar
after completing: create a new form with bitmap on the background
after completing: remove the ProgressBar
My code:
List<BackgroundWorker> Workers;
List<ProgressBar> Progress;
int OperationsCount = 0;
private void ShowProgress(int n, int percent)
{
Progress[n].Value = percent;
}
private void Blend(object sender, DoWorkEventArgs e)
{
Bitmap BlendedImage = ... // creates a bitmap
for (int i = 0; i < BlendedImage.Width; i++)
{
for (int j = 0; j < BlendedImage.Height; j++)
{
... //changing colour of every pixel
}
this.Invoke(new Action(()=>ShowProgress((int)e.Argument, (int)(100 * (double)(i/BlendedImage.Width)))));
}
Form BlendedImage_Form = new Form();
BlendedImage_Form.Size = new Size(BlendedImage.Width, BlendedImage.Height);
BlendedImage_Form.BackgroundImage = BlendedImage;
BlendedImage_Form.BackgroundImageLayout = ImageLayout.Stretch;
this.Invoke(new Action(() => BlendedImage_Form.Show()));
}
private void PerformBlending_Button_Click(object sender, EventArgs e)
{
int n = OperationsCount++;
Progress.Add(new ProgressBar());
Progress[n].Size = ...
Progress[n].Location = ...
Progress[n].Maximum = 100;
this.Controls.Add(Progress[n]);
Workers.Add(new BackgroundWorker());
Workers[n].DoWork += new DoWorkEventHandler(Blend);
Workers[n].RunWorkerCompleted += (object _sender, RunWorkerCompletedEventArgs _e) =>
{
//OperationsCount--;
//Progress[n].Dispose();
//this.Controls.Remove(Progress[n]);
//Progress.RemoveAt(n);
};
Workers[n].RunWorkerAsync(n);
}
When I click the button only once then everything seems to be good but when I click the button two times then program:
creates the first ProgressBar which shows progress correctly and the new form and bitmap are displayed also correctly
creates the second ProgressBar but it doesn't show the progress at all and no form and no bitmap is displayed.
PS I'd rather use BackgroundWorker than other tools.

As per your comment here is the solution
public void DoSomething()
{
if (this.InvokeRequired)
{
this.Invoke(new Action(()=> DoSomething()));
}
else
{
// update the ui from here, no worries
}
}
In this code, I am modifying the object on main thread. If the calls made from non-UI thread this will goes in InvokeRequired.
// From this code you given in comment
https://pastebin.com/45jQXCt9
Please try with making instance inside invoke. It should work.

Related

C# - Animate a Winforms Control

I'm trying to develop some sort of "animation" for a control in winforms that will run in a new thread than the main one.
So the code I used for the animation is the one I leave you below (a label control that scrolls up pixel by pixel every few seconds until it reaches 0 pixels):
private void LabelAnimation(int amount)
{
this.Invoke((MethodInvoker)delegate
{
int currentX = Label.Location.X;
Label.Text = amount.ToString();
for (int h = 1; h < 7; h++)
{
int subtractHeight = h;
int currentY = Label.Location.Y;
Label.Location = new Point(currentX, (currentY - subtractHeight));
Thread.Sleep(200);
}
});
}
And the method in which the new thread is created:
private void ExecuteAnimation()
{
Thread t = new Thread(() => LabelAnimation(100));
t.Start();
}
The problem is that in itself it works but on a graphic level it sucks, I mean, instead of moving the entire control, the text string remained in the same position while the rectangle of the label moved in the indicated direction, covering its own string.
Use System.Timers.Timer.
It`s recommended to use a timer instead of sleeping the thread.
Here is one way to achieve this:
private System.Timers.Timer timer;
private int amount = 100;
private void ExecuteAnimation()
{
new Thread(() =>
{
// Set DoubleBuffered to true for smoother animation
this.DoubleBuffered = true;
if (timer == null)
{
timer = new System.Timers.Timer();
timer.Interval = 100;
timer.Elapsed += timer_tick;
}
timer.Start();
}).Start();
}
private void timer_tick(object sender, EventArgs e)
{
this.Invoke(new Action(() =>
{
int currentX = label1.Location.X;
label1.Text = amount.ToString();
if (label1.Location.Y > 0)
{
label1.Location = new Point(currentX, label1.Location.Y - 1);
}
}));
}
OUTPUT:
In addition to Jonathans fine answer, you might also be interested in this "Component Animator":
https://www.codeproject.com/Articles/548769/Animator-for-WinForms
If it could be useful to someone else, I finally solved the problem by mixing the #Jonathan Applebaum code and mine, like so:
private void LabelAnimation(int amount, int moveUp)
{
int currentX = 0;
this.Invoke(new Action(() =>
{
currentX = Label.Location.X;
Label.Text = amount.ToString();
}));
for (int h = 1; h < moveUp; h++)
{
this.Invoke(new Action(() =>
{
int currentY = Label.Location.Y;
Label.Location = new Point(currentX, (currentY - h));
}));
Thread.Sleep(150);
}
}
The thread execution always remains the same:
private void ExecuteAnimation()
{
Thread t = new Thread(() => LabelAnimation(100, 7));
t.Start();
}
In this way I make only the gets or sets related to the Label control run on the main thread while everything else, including the Thread.Sleep(150), in the secondary thread so as to avoid the Form getting stuck. In this way everything runs smoothly for me.

BackgroundWorker - reporting progress with "sub tasks"

A WinForms application with a custom control, LabelProgressBar, which has the ability to display both progress and some descriptive text and/or percentage completion. This is done by calling LabelProgressBar.statusInProgress(string message, int percentageCompletion).
One usage of this is as follows:
private void import_begin(System.Object sender, System.ComponentModel.DoWorkEventArgs args)
{
// first unpack the arguments
System.Object[] arguments = (System.Object[])args.Argument;
System.String filename = (System.String)arguments[0];
System.String why = (System.String)arguments[1];
// tasks:
// 1. read excel file and apply changes to model
// 2. gather changes and format them as XML
// 3. send request to server
// 4. commit/rollback changes
// grab the worker thread so we can report percentage progress
System.ComponentModel.BackgroundWorker worker = (System.ComponentModel.BackgroundWorker)sender;
// now do the work
#region Task1
Controller.Excel excel = new Controller.Excel(filename);
try
{
// the progress of this needs to be tracked
overall_result = excel.import_all(out modified_nodes);
}
catch (InvalidDataExcetpion invDataEx)
{
// deal with it
}
#endregion
worker.ReportProgress(25);
// complete remaining tasks...
}
The event handler for the worker reporting its progress is the following:
private void import_progress(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
Debug.WriteLine("Import percentage completion: " + e.ProgressPercentage);
labelProgressBar1.statusInProgress("Import", e.ProgressPercentage);
}
In short, the import_begin method is broken up into several "tasks". These are broken up into "subtasks". Taking the example of the import_all method:
public Command_Result import_all(out System.Collections.Generic.List<Model.Data_Node> nodes)
{
Command_Result overall_result = Command_Result.OK;
Command_Result this_result;
nodes = new System.Collections.Generic.List<Model.Data_Node>(excel.Workbook.Worksheets.Count);
Model.Data_Node destination;
// the intent is to report the progress of this particular subtask on the basis of how many worksheets have been processed in this for loop
foreach (OfficeOpenXml.ExcelWorksheet worksheet in excel.Workbook.Worksheets)
{
this_result = import_sheet(worksheet.Name, out destination);
nodes.Add(destination);
if (this_result > overall_result)
{
overall_result = this_result;
}
}
return overall_result;
}
The intent is to have this "subtask" report progress on the basis of how many sheets have been processed in the loop. Calculating a percentage for this is a trivial task, but it is not clear to me how this can be reported back to the import_begin method. When this "subtask" is completed, the overall task completion (from the POV of the import_begin method) should be 25%. Similarly for the other tasks. How can this be achieved?
import_begin don't really need to get the update, it can just call the subtasks, while also passing the BackgroundWorker, so the subtasks are responsible to directly report their progress. If "polluting" the subtasks with BackgroundWorker is unacceptable, then create a delegate to call the BackgroundWorker, so your subtasks will then call the delegate instead.
private void mainTask(object sender, DoWorkEventArgs e)
{
var worker = (BackgroundWorker)sender;
var report = new Action<int>(i => worker.ReportProgress(i)); //the delegate
smallTask1Clean(report); //this one pass the delegate
smallTask2(worker); //this one directly call background worker
worker.ReportProgress(100);
}
void smallTask1Clean(Action<int> a)
{
for (int i = 0; i < 20; i++)
{
Thread.Sleep(500);
a(i);
}
}
void smallTask2(BackgroundWorker w)
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(1000);
w.ReportProgress(i*80/5+20);
}
}
You can also insulate the subtasks from having to know their part in the larger tasks, in this case, the delegate should take two variables, the current internal progress of the subtasks and the total item it needs to process.
private void mainTask(object sender, DoWorkEventArgs e)
{
var worker = (BackgroundWorker)sender;
var preTaskProgress = 0;
var currentTaskTotalPercentage = 0;
var smarterDelegate = new Action<int, int>((current, total) =>
{
worker.ReportProgress(preTaskProgress + (current *currentTaskTotalPercentage/total));
});
currentTaskTotalPercentage = 30; //the following task will in total progressed the main task for 30%
smallTaskClean(smarterDelegate);
preTaskProgress = currentTaskTotalPercentage; //upate the main the progress before starting the next task
currentTaskTotalPercentage = 70; //the following task will in total progressed the main task for 70%
smallTaskClean(smarterDelegate);
worker.ReportProgress(100);
}
void smallTaskClean(Action<int,int> a)
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(1500);
a(i,5);
}
}

Blank components in a Form while updating it from a thread

I have made a Form that moves across the screen to the left, but all of the components on the Form are blank.
I put the code for the movement in comments and everything was fine, so the problem is in my movement code, but I don't know what the problem is.
System.Threading.Thread thread;
private void Form1_Load(object sender, EventArgs e)
{
thread = new System.Threading.Thread(loop);
thread.Start();
}
void loop()
{
this.BeginInvoke((Action)delegate () {
int x = 0;
int y = 0;
int MoveRate = 1;
Point TopLeft = this.Location;
Point TopRight = new Point (this.Location.X + this.Width, this.Location.Y);
while (true)
{
x = x + MoveRate;
this.Location = new Point(x, 150);
System.Threading.Thread.Sleep(10);
}
});
}
This should make the form move to the left, however the components on the Form are blank.
Let's look at the loop() method:
void loop()
{
this.BeginInvoke((Action)delegate () {
int x = 0;
int y = 0;
int MoveRate = 1;
Point TopLeft = this.Location;
Point TopRight = new Point (this.Location.X + this.Width, this.Location.Y);
while (true)
{
x = x + MoveRate;
this.Location = new Point(x, 150);
System.Threading.Thread.Sleep(10);
}
});
}
This code immediately invokes a delegate back on the main UI thread. The delegate runs a while(true) loop that never exits. As soon as the loop begins to execute, the UI thread is completely hosed with no hope of ever responding to other event messages, including paint events.
Try this instead:
void loop()
{
int x = 0;
int MoveRate = 1;
while(true)
{
x += MoveRate;
this.BeginInvoke((Action)delegate () { this.Location = new Point(x, 150); });
System.Threading.Thread.Sleep(16);
}
}
It's all the same code (except for the stuff that wasn't doing anything), but now arranged so the delegate is invoked inside the loop. The UI thread is only blocked for a brief time, and then control returns back to the loop thread, which will Sleep for a little while before bothering the UI again. The invoke even simple enough I was able to rewrite it as single line.
Notice I also increased the sleep time, because that still gives you 60 frames per second.
An Async variation of the same process, to test something different.
(The main reason why your thread wasn't working as expected has already been explained. If you use a thread and then invoke the UI thread in a close loop, it's more or less like not having your code run in a different thread at all: a Form doesn't have time to update itself or its controls).
This method add a termination to the scrolling procedure, when the Form is scrolled outside the current Screen bounds. When this condition is met, the while loop is exited and the Task ends, moving the Form in the center of the screen.
The Task is started in the Shown event. I think it's more appropriate than the Load event (the Form is ready to be presented, here).
Note that neither this Task or the Thread add any check on the Form.FormClosing event, to cancel the asynchronous proc: if the Form is closed while the scrolling is performed, you will most likely have a exception (the Form has been disposed, thus no more handle).
private async Task Scroller(int ScreenWidth)
{
int x = 0;
int MoveRate = 2;
while (true)
{
x += MoveRate;
this.BeginInvoke(new MethodInvoker(() => { this.Location = new Point(x, 150);}));
await Task.Delay(10);
if (x > ScreenWidth) break;
};
}
private async void Form_Shown(object sender, EventArgs e)
{
int ScreenWidth = Screen.FromHandle(this.Handle).Bounds.Width;
await this.Scroller(ScreenWidth);
this.Location = new Point((ScreenWidth - this.Width) / 2 , 150);
}

c# How to change/access WinForms controls from a different class

So I'm trying to change the text from a WinForms project, from another class than the Form class.
It should work like this:
But instead it does this:
The way I used to do it is pass along the object as a parameter to my other class and from that other class I could change the text. I do the same with the progressbar and it does work there, so it's weird that it works with the progressbar but not the label.
I use this method to change the progressbar:
public void IncreaseProgress(int progBarStepSize, String statusMsg, int currentProject=-1) {
if (currentProject != -1) {
lblStatus.Text = String.Format("Status: {0} | project {1} of {2}",statusMsg,currentProject,ProjectCount);
}
else {
lblStatus.Text = String.Format("Status: {0}",statusMsg);
}
pb.Increment(progBarStepSize);
}
And here is where I use the method:
public void InitialiseFile(List<string> filePaths, int eurojobType)
{
foreach (string sheet in outputSheets) {
switch (sheet) {
case "Summary":
for (int i = 0; i < filePaths.Count; i++) {
var filePath = filePaths[i];
IncreaseProgress(1, "Reading Summary", i);
worksheetIn = excelReader.ReadExcelSummary(filePath);
IncreaseProgress(1, "Writing Summary", i);
excelWriter.WriteExcelSummary(worksheetIn);
}
break;
case "Monthly_Cat1":
for (int i = 0; i < filePaths.Count; i++) {
var filePath = filePaths[i];
IncreaseProgress(1, "Reading Monthly", i);
worksheetIn = excelReader.ReadExcelMonthly(filePath);
IncreaseProgress(1, "Writing Monthly", i);
excelWriter.WriteExcelMonthly(worksheetIn);
}
break;
}
}
IncreaseProgress(1, "Completed!");
}
Now I know this code works because the progressbar increments. And it should jump in the first if-loop because i gets passed as a parameter, which is never -1.
//manager class
private Label lblStatus;
private ProgressBar pb;
public Manager(ProgressBar pb, Label lbl){
this.pb = pb;
lblStatus = lbl;
}
//Form class
Manager mgr = new Manager(progressBar1, lblStatus, projectFilePaths.Count, outputSheets.ToArray(), exportPath);
mgr.InitialiseFile(projectFilePaths, eurjobType);
You can call lblStatus.Refresh(); to force the control to be redrawn, after setting its Text.
But consider Slaks comment:
Don't do blocking work on the UI thread
You can consider using BackgroundWorker or Task.Run or async/await pattern instead.
As an example:
private async void button1_Click(object sender, EventArgs e)
{
await Task.Run(() =>
{
for (int i = 0; i < 10000; i++)
{
this.Invoke(new Action(() =>
{
label1.Text = i.ToString();
label1.Refresh();
}));
}
});
}
This way the numbers increase, the label refreshes and shows the new value, while the UI is responsive and for example you can move your form or click on other button.
You should put your UI related codes in an action fired by Invoke to prevent receiving cross thread operation exception.

UI refresh looping a Dispatcher.BeginInvoke

I'm trying to do a System.Windows.Shapes.Rectangle rotation in Y axis simulating a card rotation, showing all the route.
The problem is that the UI only refreshes at the end.
Simplified version of code
Call of method
for (i=0; i<=180; i++)
{
int j = i;
Dispatcher.BeginInvoke(new a_dispatcher(() => {
print_animation_of_card(card, i);
}),
null);
}
Method
private void print_animation_of_card(System.Windows.Shapes.Rectangle card)
{
...
System.Windows.Media.PlaneProjection p =
card.Projection as System.Windows.Media.PlaneProjection;
p.RotationY = i;
card.Projection = p;
}
I have also tried to put the FOR into the method, with same result...
How can I do to show all the rotation movement of the System.Windows.Shapes.Rectangle?
I believe the problem is that you are using BeginInvoke() instead of Invoke. This does not wait for one angle of rotation to complete before the next one is called, so the calls are all stacked on top of eachother.
Try it with Dispatcher.Invoke() or put the whole loop inside one Dispatcher.BeginInvoke() and see if the results are more to your satisfaction.
Finally I have done it with a BackgroundWorker, thanks #Jay for the answers.
Call of method
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += (object sender, DoWorkEventArgs e) =>
{
print_animation_of_card(card);
};
bw.RunWorkerAsync();
Method
private void print_animation_of_card(System.Windows.Shapes.Rectangle card)
{
for (int i = 0; i <= 180; i++)
{
Thread.Sleep(3);
Dispatcher.BeginInvoke(new a_dispatcher(() =>
{
//same code as topic method code
}),null);
}
}

Categories