progressBar only updating last called value - c#

I am trying to update the progress bar as per the time taken by a function(which I wrote here in numerical terms) to be processed.But it only shows the last called value.
public static void updateProgress(int x)
{
Program.f.progressBar1.Visible = true;
Program.f.progressBar1.Enabled = true;
Program.f.progressBar1.Value +=x;
Thread.Sleep(5000);
}
above fn is used to update the progress bar.
public static Form1 f;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
f = new Form1();
f.progressBar1.Maximum = 100;
f.progressBar1.Minimum = 0;
f.progressBar1.Value = 0;
updateProgress(25); //fn1
updateProgress(50); //fn2
Application.Run(f);
}
The progressBar directly shows 75% progress.
Thanks

Wrong: you are doing something before form is displayed:
static void Main()
{
f = new Form1(); // form instance is created
f.progressBar1.Maximum = 100;
f.progressBar1.Minimum = 0;
f.progressBar1.Value = 0;
updateProgress(25); // you do something and change property
updateProgress(50); // you do something and change property
Application.Run(f); // here form is displayed and you see the most recent change
}
Correct: to simulate work, which run in background (while form is displayed) you can do something like:
static void Main()
{
f = new Form1(); // form instance is created
f.progressBar1.Maximum = 100;
f.progressBar1.Minimum = 0;
f.progressBar1.Value = 0;
// create and start task running in parallel
Task.Run(() =>
{
Thread.Sleep(3000); // wait long enough until form is displayed
updateProgress(25);
updateProgress(50);
});
Application.Run(f);
}
public static void updateProgress(int x)
{
// Invoke is required because we run it in another thread
f.Invoke((MethodInvoker)(() =>
{
Program.f.progressBar1.Visible = true;
Program.f.progressBar1.Enabled = true;
Program.f.progressBar1.Value +=x;
}));
Thread.Sleep(5000); // simulate work
}

Related

How to create and add controls in run time with BackgroundWorker

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.

Render to winforms picturebox over and over again

I have some bugs that need fixing, one of them involves an out of memory error.
Does anyone know how to do this properly? Thanks, I don't want it to be too messy, or too complicated. I just want to treat a new image as a buffer to render another image to (because of positional changes), and do it via a background thread. Not the UI thread (Too slow likely).
I get out of memory errors, and such. Also not able to access members of Form1 from within the thread function (images and the like throw access errors such as "Object already in use")
Here is my code:
System.Threading.Thread t;
public Image b;
public Bitmap c;
public Bitmap d;
public Bitmap e;
public Bitmap bg;
public Bitmap spr;
int spritex = 0;
int spritey = 0;
int spritedir = 1;
public Form1()
{
InitializeComponent();
Text = "Escape The Hypno Mansion!!".ToString();
t = new System.Threading.Thread(DoThisAllTheTime);
t.Start();
textBox1.Text = "Press Begin button to start!";
pictureBox1.Image = Image.FromFile(#"Images\introgirl.jpg");
b = new Bitmap(#"Images\introgirl.jpg");
c = new Bitmap(#"Images\sprite.png");
var graphics = Graphics.FromImage(b);
Pen blackpen = new Pen(Color.Black, 3);
graphics.DrawLine(blackpen, 0, 0, 100, 100);
graphics.DrawImage(c, new Point(500, 500));
pictureBox1.Image = b;
//pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
}
public void DoThisAllTheTime()
{
while (true)
{
Point p = new Point(spritex, spritey);
bg = new Bitmap(#"Images\test.bmp");
spr = new Bitmap(#"Images\sprite.png");
using (var graphics = Graphics.FromImage(bg))
{
graphics.DrawImage(spr, p);
}
if (pictureBox1.Image != null)
{
pictureBox1.Image.Dispose();
}
pictureBox1.Image = bg;
pictureBox1.Invalidate();
if (spritedir == 1) { spritex += 5; }
if (spritedir == 2) { spritex -= 5; }
if (spritex < 0) { spritex = 0; spritedir = 1; }
if (spritex > 700) { spritex = 700; spritedir = 2; }
}
}
The reason you can't change the image in your picturebox is because the thread that created the image is not the thread that created the picturebox.
In a debugger you can check this by asking the picturebox for InvokeRequired (function Control.IsInvokeRequired) just before changing the function.
So let's rewrite your function and show that modern classes Like Task are much easier to use the your thread.
I'll start your task when the form is loading, and try to stop it when the form is closing.
private Task myTask = null;
private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private void OnFormLoading(object sender, EventArgs e)
{
// Start the task
this.myTask = Task.Run( () => DoMyWork(this.cancellationTokenSource.Token));
}
private void OnFormClosing(object sender, FormClosingEventArgs e)
{
// if the Task is still running, ask it to stop itself:
if (myTask != null && !myTask.IsCompleted)
{ // ask the task to stop and wait until it is completed:
this.cancellationTokenSource.Cancel();
// all Tokens extractes from this source will get state CancellationRequested
// wait maximum 5 seconds until the task is completed:
this.UseWaitCursor = true;
this.myTask.Wait(TimeSpan.FromSeconds(5));
this.UseWaitCursor = false;
// cancel closing if the task is still not completed
e.Cancel = !this.myTask.Completed;
}
}
Now the function DoMyWork:
private void DoMyWork(CancellationToken cancellationToken)
{
// Do the same as in your DoThisAllTheTime
// except that you regularly check cancellationToken.IsCancelRequested:
while(!cancellationToken.IsCancelRequested)
{
// calculate the image to display
var imageToDisplay = ...
this.DisplayImage(imageToDisplay);
}
}
void DisplayImage(Image imageToDisplay)
{
if (this.pictureBox1.InvokeRequired)
{
this.Invoke(new MethodInvoker( () => this.DisplayImage(imageToDisplay)));
}
else
{
this.PictureBox1.Image = imageToDisplay;
}
}
See:
How to cancel a Task and its children
Use InvokeRequired with lambda expression
Dispose every disposable instances before the loop ends. Your memory leak is related with disposable items not being cleaned from memory, so you'll eventually run out of memory in your infinite loop.
At the very least, you'll want to dispose both bitmaps at the end of the loop:
bg = new Bitmap(#"Images\test.bmp");
spr = new Bitmap(#"Images\sprite.png");

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.

Partial WinForm UI freeze when switching windows or apps via Windows taskbar for Excel VSTO

I have a weird problem (possibly a threading issue) that has been troubling me. I would like to have a progress bar for a task that I run in Excel/VSTO, that is started by clicking a button on the ribbon.
Since all access to the Excel Object model must occur in the main thread, I show my progress form modally on a separate thread. This works great most of the time, but odd things happen when I switch apps or windows via the Windows task bar. For example, if I am in the middle of a run and Excel is maximized, and I click Chrome on my taskbar to give it focus, then I click back to Excel and click between open Excel workbooks in the taskbar previews, sometimes the UI will partially freeze in the progress bar.
In Excel 2007/2010, I can still see the progress bar being updated but I can't drag the toolbar or click the Cancel button; this is why I say the UI partially freezes. Similar stuff happens in Excel 2013/2016 but I haven't tested as much on them.
If Excel has focus and we don't switch to another window during the execution of the task, then the progress bars work perfectly.
Does anyone have an idea what could be going wrong?
Is there another way I can go about displaying the progress bar in Excel to give consistent and reliable behavior? Note the limitation that the actual task needs to run on the main Excel UI thread and that for a progress form to be interactive and display properly, it needs to be on a secondary UI thread. Because of this, solution like using a BackgroundWorker won't work.
class RunSampleProgressTask
{
private ProgressForm _form;
internal volatile bool CancelPending;
internal volatile AutoResetEvent SignalEvent = new AutoResetEvent(false);
internal RunSampleProgressTask()
{
//create a new workbook
Globals.ThisAddIn.Application.Workbooks.Add(Microsoft.Office.Interop.Excel.XlWBATemplate.xlWBATWorksheet);
int hwnd = GetHwnd();
var thread = new Thread(() => ShowProgressForm(hwnd));
thread.SetApartmentState(ApartmentState.STA);
thread.Priority = ThreadPriority.Highest;
thread.Name = "ProgressFormThread";
thread.IsBackground = true;
thread.Start();
SignalEvent.WaitOne();
//In SDI Excel a newly created workbook won't be shown until the function ends so we use a timer to show the new workbook before running Run()
if (IsSDI)
ExecuteTaskIn(200,Run);
else
Run();
}
internal static void ExecuteTaskIn(int milliseconds, Action action)
{
var timer = new System.Windows.Forms.Timer();
timer.Tick += (s, e) =>
{
((System.Windows.Forms.Timer)s).Stop(); //s is the Timer
action();
};
timer.Interval = milliseconds;
timer.Start();
}
//returns true if it is Excel 2013 or 2016 that use the new Single Document Interface
public static bool IsSDI
{
get
{
string appVersion = Globals.ThisAddIn.Application.Version;
return appVersion.StartsWith("15") || appVersion.StartsWith("16");
}
}
private void ShowProgressForm(int hwnd)
{
_form = new ProgressForm(this) {StartPosition = FormStartPosition.CenterScreen};
_form.ShowInTaskbar = false;
_form.FormBorderStyle = FormBorderStyle.FixedSingle;
_form.ShowDialog(new Win32Window(hwnd));
}
protected void ReportProgress(int percent)
{
if (_form == null || !_form.IsHandleCreated) return;
_form.BeginInvoke(new Action(() => _form.SetProgress(percent)));
}
protected void CloseForm()
{
if (_form == null || !_form.IsHandleCreated) return;
_form.BeginInvoke(new Action(() => _form.Close()));
}
internal static int GetHwnd()
{
if (IsSDI)
{
var window = Globals.ThisAddIn.Application.ActiveWindow;
int hwnd = (int)window.GetType().InvokeMember("Hwnd", BindingFlags.GetProperty, null, window, null); //late binding call to get Window.Hwnd
return hwnd;
}
else
{
return Globals.ThisAddIn.Application.Hwnd;
}
}
private const int BufRowSize = 16384; //must be a factor of RowsPerPage
private const int RowsPerPage = 1048576;
private const int BufColSize = 10;
private const int Repetitions = 80;
internal void Run()
{
ReportProgress(0);
Globals.ThisAddIn.Application.ScreenUpdating = false;
Globals.ThisAddIn.Application.EnableEvents = false;
var buf = new string[BufRowSize, BufColSize];
//fill the buffer with sample data
int cnt = 0;
for (int i = 0; i < BufRowSize; i++)
for (int j = 0; j < BufColSize; j++)
buf[i, j] = "String" + (++cnt);
var workbook = Globals.ThisAddIn.Application.ActiveWorkbook;
var sheet = workbook.ActiveSheet;
int currRow = 1;
for (int i = 0; i < Repetitions; i++)
{
if (CancelPending)
{
CloseForm();
Globals.ThisAddIn.Application.ScreenUpdating = true;
Globals.ThisAddIn.Application.EnableEvents = true;
return;
}
sheet.Range[sheet.Cells[currRow, 1], sheet.Cells[currRow + BufRowSize - 1, BufColSize]].Value2 = buf;
currRow += BufRowSize;
if (currRow > RowsPerPage)
{
currRow = 1;
sheet = workbook.Sheets.Add(Missing.Value, sheet);
}
int percent = 100 * (i + 1) / Repetitions;
ReportProgress(percent);
}
CloseForm();
Globals.ThisAddIn.Application.ScreenUpdating = true;
Globals.ThisAddIn.Application.EnableEvents = true;
}
}
public partial class ProgressForm : Form
{
private ProgressBar _progressBar;
private Label _lblStatus;
private Button _btnCancel;
private RunSampleProgressTask _task;
internal ProgressForm(RunSampleProgressTask task)
{
InitializeComponent();
this.Width = 320;
this.Height = 120;
this.ControlBox = false;
_task = task;
_progressBar = new ProgressBar(){Left=10, Top = 10, Width= 300, Height = 30};
_lblStatus = new Label(){Left = 10, Top = 50};
_btnCancel = new Button(){Text = "Cancel", Left = _lblStatus.Right + 10, Top = 50, Width = 100};
_btnCancel.Click += _btnCancel_Click;
this.Controls.Add(_progressBar);
this.Controls.Add(_lblStatus);
this.Controls.Add(_btnCancel);
this.Shown += ProgressForm_Shown;
}
void ProgressForm_Shown(object sender, EventArgs e)
{
_task.SignalEvent.Set();
}
void _btnCancel_Click(object sender, EventArgs e)
{
_task.CancelPending = true;
_lblStatus.Text = "Cancelling...";
}
internal void SetProgress(int percent)
{
_progressBar.Value = percent;
_lblStatus.Text = percent + "%";
}
}
public class Win32Window : IWin32Window
{
public Win32Window(int hwnd)
{
Handle = new IntPtr(hwnd);
}
public Win32Window(IntPtr handle)
{
Handle = handle;
}
public IntPtr Handle { get; private set; }
}

Thread not starting after Thread Sleeep

In this i am using stopwatch. when stop watch value between 0 to 15 it will play video on screen 1 and after 15 it will display on screen 0 but thread is not starting after Thread.sleep()
public partial class Form2 : Form
{
int[] screentimings = new int[2] { 20, 20 };
Stopwatch sp;
Thread thread1;
//private System.Timers.Timer _timer = new System.Timers.Timer();
public Form2()
{
InitializeComponent();
thread1 = new Thread(new ThreadStart(A));
thread1.SetApartmentState(ApartmentState.STA);
sp = new Stopwatch();
sp.Start();
thread1.Start();
}
[STAThread]
public void showOnMonitor(int showOnMonitor)
{
Screen[] sc;
sc = Screen.AllScreens;
Form1 f = new Form1();
f.FormBorderStyle = FormBorderStyle.None;
f.Left = sc[showOnMonitor].Bounds.Left;
f.Top = sc[showOnMonitor].Bounds.Top;
f.Height=sc[showOnMonitor].Bounds.Height;
f.Width=sc[showOnMonitor].Bounds.Width;
f.StartPosition = FormStartPosition.Manual;
f.ShowDialog();
}
[STAThread]
private void A()
{
long i = sp.Elapsed.Seconds;
if (i > 0 && i < 15)
{
showOnMonitor(1);
}
else
{
showOnMonitor(0);
}
Thread.Sleep(500);
}
}
showOnMonitor(1) code is executed but after 15 seconds showOnMonitor(0) is not working.
I am new with thread don't know whats wrong with it. It might be because of [STAThread] without this it giving Single Thread Exception.
You don't need a thread at all. Threads are used to do more than one action concurrently. Explaining it will be out of scope of this question. Please read more about threads here.
Since you're in .Net 4.5 you can use async/await to accomplish your goal very easily.
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
protected async override void OnLoad(EventArgs e)
{
base.OnLoad(e);
await ShowForms();//Show the forms
}
private async Task ShowForms()
{
ShowOnMonitor(1);
await Task.Delay(15000);//15 seconds, adjust it for your needs.
ShowOnMonitor(2);
}
private void ShowOnMonitor(int showOnMonitor)
{
Screen[] allScreens = Screen.AllScreens;
Rectangle screenBounds = allScreens[showOnMonitor - 1].Bounds;
Form1 f = new Form1
{
FormBorderStyle = FormBorderStyle.None,
Left = screenBounds.Left,
Top = screenBounds.Top,
Height = screenBounds.Height,
Width = screenBounds.Width,
StartPosition = FormStartPosition.Manual
};
f.Show();//Use show, not ShowDialog.
}
}

Categories