how can i update a second Window (It's a simple WPF Window with a Progressbar to show a progress) from within my MainWindow inside a loop?
Well, i am loading a couple of Files which can cost about 10 Seconds. While loading i want my ProgressWindow to show how many Files have been loaded.
Here is my MainThread:
private void loadFiles(String[] paths) {
// Show Progress Window
MyProgressWindow progWindow = new MyProgressWindow();
progWindow.ShowProgressWindow(0, paths.Length);
//Globals.MyProgressController.ShowProgressWindow(0, paths.Length);
foreach (String path in paths) {
// Load the file
loadFile(path);
// Refresh the Table
refreshTableAsync();
// Update Progress Window
progWindow.UpdateProgressWindow(1);
//Globals.MyProgressController.UpdateProgressWindow(1);
}
// Close Progress Window
progWindow.CloseProgressWindow();
//Globals.MyProgressController.CloseProgressWindow();
}
Here is the ProgressWindowController
public class MyProgressWindow {
private ProgressWindow progressWindow;
public void ShowProgressWindow(int startIndex, int maxIndex) {
progressWindow = new ProgressWindow();
progressWindow.Initialize(startIndex, maxIndex);
progressWindow.Show();
}
public void UpdateProgressWindow(int value) {
progressWindow.Update(value);
//progressWindow.Update();
}
public void CloseProgressWindow() {
progressWindow.Close();
}
}
And finally the ProgressWindow itself:
public partial class ProgressWindow : Window {
private int actualValue = 0;
private int maxValue = 0;
public ProgressWindow() {
InitializeComponent();
}
public void Initialize(int startValue, int maxValue) {
this.actualValue = startValue;
this.maxValue = maxValue;
}
public void Update(int value) {
actualValue += value;
lblPercentage.Content = actualValue + " / " + maxValue;
pbProgress.Value = ((double)value / maxValue) * 100;
}
}
I have read thati should do it via threading, but i cannot get it to work, and all code I've found which helped other people, doesn't do what i need.
Thanks for your help
Your question is posed incorrectly. You are updating the second window as per your question, however the suggested advice of using a separate thread is what you're really asking about.
I suggest reading about the BackgroundWorker class Here which even explains progress updates.
Related
This question already has answers here:
Communicate between two windows forms in C#
(12 answers)
Closed 6 years ago.
I have a form with a label, also an external class. In my class, I have a for loop of 1 to 1000. How can I show the value of 1 to 1000 from my class to my form label?
//external class
public class TestClass
{
public void myLoop()
{
for (int i = 1; i <= 1000; i++)
{
// show value of i to label
}
}
}
Assuming you have a reference to your form as form1, and that form has a label named label1 that is public/accessible to TestClass:
public class TestClass
{
public void myLoop()
{
for (int i = 1; i <= 1000; i++)
{
// show value of i to label
form1.label1.Text = i.ToString();
// allow message pumping to redraw the label
Application.DoEvents();
// pause long enough to see it before the next one happens
System.Threading.Thread.Sleep(100);
}
}
}
I wouldn't recommend using Application.DoEvents for production code normally; but if you are running the UI thread and not using async code, this would be the "hacky" way to get all the window events pumping (mostly WM_PAINT to get the label to redraw itself) during your loop.
A better way is to use events:
public class TestClass {
public class ProgressEventArgs : EventArgs {
public int Value { get; set; }
}
public event EventHandler<ProgressEventArgs> Progress;
public void myLoop() {
for (int i = 0; i <= 1000; ++i) {
var evt = Progress;
if (evt != null) {
evt.Invoke(this, new ProgressEventArgs() { Value = i; });
}
}
}
}
and handle that event in the form:
public class TestForm : Form {
private somevent_click(object sender, EventArgs evt) {
var test = new TestClass();
test.Progress += test_Progress;
test.myLoop();
}
private void test_Progress(object sender, TestClass.ProgressEventArgs evt) {
label1.Text = evt.Value;
}
}
Note that these will happen in the same thread, so depending on what else you do in your loop, you may not get message pumping. Consider using a background thread or async code instead.
I'm working with an C# .Net application that uses Cplex DLL's for an optimization operation, and during that operation I want to write status progress to a statusbar on the that initiated the operation.
This is the general layout of the specific form;
namespace ActResMain
{
public class FormOptimize : System.Windows.Forms.Form
{
private callCplex()
{
//...
cplex.Use(new Cplex_ContinuousCallback());
cplex.Solve()
}
public void Update_OptimizeStatusbarPanel(String strText)
{
statusBarPanel_1.Text = strText;
statusBar1.Refresh();
}
internal class Cplex_ContinuousCallback : Cplex.ContinuousCallback
{
FormOptimize formOpt = new FormOptimize();
public override void Main()
{
//From here I want to edit the statusbar at FormOptimize. I can write progress to console without any problems, but cannot reach function "Update_OptimizeStatusbarPanel".
//If I include "FormOptimize formOpt = new FormOptimize" here, i get Visual studio exception on illegal window reference.
}
}
}
}
I have also tried invoking the Update_OptimizeStatusbarPanel function like this:
internal class Cplex_ContinuousCallback : Cplex.ContinuousCallback
{
FormOptimize formOpt = new FormOptimize();
public override void Main()
{
FormCollection fc = Application.OpenForms;
var mpc = fc[1];
Type type = mpc.GetType();
MethodInfo dynMethod = type.GetMethod("Update_OptimizeStatusbarPanel");
dynMethod.Invoke(mpc, new object[] { String.Format("Running Optimization: {0} iterations ", Niterations)});
}
}
But then I get an exception from visual studio stating that an object created by one thread cannot be modified from another thread.
Maybe this is something stupid that I have missed, but help is greatly appriciated
EDIT: I edited the code as per Mohammad Dehghans suggestion,
public class FormOptimize : System.Windows.Forms.Form
{
private callCplex()
{
cplex.Use(new Cplex_ContinuousCallback(this));
cplex.Solve()
}
internal class Cplex_ContinuousCallback : Cplex.ContinuousCallback
{
FormOptimize _formOptimize;
public Cplex_ContinuousCallback(FormOptimize formOptimize)
{
this._formOptimize = formOptimize;
}
public override void Main()
{
if (Niterations % 10 == 0)
{
_formOptimize.Update_OptimizeStatusbarPanel(0, String.Format("Running Optimization: {0} iterations ", Niterations), 0);
}
}
}
public void Update_OptimizeStatusbarPanel(short panelIndex, String strText, short severity)
{
if (statusBar1.InvokeRequired)
statusBar1.Invoke(new Action<short, string, short>(Update_OptimizeStatusbarPanel), panelIndex, strText, severity);
else
{
if (panelIndex == 0)
{
//...
statusBarPanel_0.Text = strText;
}
else if (panelIndex == 1)
{
//...
statusBarPanel_1.Text = strText;
}
statusBar1.Refresh();
}
}
}
But by doing that I apparently broke something, as the application just ..stops after statusBar1.Invoke() is called the first time. If I pause the debugger it says that cplex.Solve() is executing, but then nothing more happens.
First of all, you need to pass the instance of your form to the implemented callback class, so when the Main method is called, you have access to the exact instance that is being shown on the screen.
Secondly, you need to use Invoke method to update the UI controls from anther thread (I've not worked with CPLEX so far, but I guess the callback is invoked from another thread. That's usual).
Read this for more information.
The complete code could be:
public class FormOptimize : System.Windows.Forms.Form
{
private callCplex()
{
//Misc code
cplex.Use(new Cplex_ContinuousCallback(this)); // <-- passing `this`
cplex.Solve()
//Misc code
}
public void Update_OptimizeStatusbarPanel(String strText)
{
if (statusBarPanel_1.InvokeRequired)
statusBarPanel_1.Invoke(Action<string>(Update_OptimizeStatusbarPanel), strText);
else
{
statusBarPanel_1.Text = strText;
statusBar1.Refresh();
}
}
internal class Cplex_ContinuousCallback : Cplex.ContinuousCallback
{
FormOptimize _formOptimize;
public Cplex_ContinuousCallback(FormOptimize formOptimize)
{
this._formOptimize = formOptimize;
}
public override void Main()
{
//...
_formOptimize.Update_OptimizeStatusbarPanel(String.Format("Running Optimization: {0} iterations ", Niterations));
}
}
}
I have my progressbar in form1. and i have another class called process.cs
In the main form I have these two functions...
public void SetProgressMax(int max)
{
uiProgressBar.Value = 0;
uiProgressBar.Minimum = 0;
uiProgressBar.Maximum = max;
}
public void IncrementProgress()
{
uiProgressBar.Increment(1);
}
How can I call these functions from my process.cs class?
You're creating a "tightly coupled" solution which requires the process class to have a reference to the Form (I'll use Form1 in this example).
So in your process class, you need to create a variable to store the reference to the form, and allow a way to pass that reference in. One way is to use the constructor of the class:
public class process
{
private Form1 f1 = null;
public process(Form1 f1)
{
this.f1 = f1;
}
public void Foo()
{
if (f1 != null && !f1.IsDisposed)
{
f1.SetProgressMax(10);
f1.IncrementProgress();
f1.IncrementProgress();
f1.IncrementProgress();
}
}
}
Here's an example of creating the process class from within Form1 and passing the reference in:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
process p = new process(this);
p.Foo();
}
public void SetProgressMax(int max)
{
uiProgressBar.Value = 0;
uiProgressBar.Minimum = 0;
uiProgressBar.Maximum = max;
}
public void IncrementProgress()
{
uiProgressBar.Increment(1);
}
}
--- EDIT ---
Here's a boiled down version of the "loosely coupled" events approach (ignoring multi-threading issues for simplicity):
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
process p = new process();
p.Progress += p_Progress;
p.Foo();
}
void p_Progress(int value)
{
uiProgressBar.Value = value;
}
}
public class process
{
public delegate void dlgProgress(int value);
public event dlgProgress Progress;
public void Foo()
{
// ... some code ...
// calcuate the current progress position somehow:
int i = (int)((double)3 / (double)10 * (double)100); // 30% complete
// raise the event if there are subscribers:
if (Progress != null)
{
Progress(i);
}
}
}
Note that in this approach the process class has no reference to the form and has no idea what is being done with the progress value. It simply reports the progress and the subscriber (the form in this case) decides what to do with that information.
I have this code in my Form1:
public partial class Form1 : Form
{
public static int hours;
public static int minutes;
public static int seconds;
FinishGate finishgate = new FinishGate();
public Form1()
{
InitializeComponent();
txtHours.MaxLength = 2;
txtMinutes.MaxLength = 2;
txtSeconds.MaxLength = 2;
lblFinished.Text = Convert.ToString(gate.Total);
}
private void btnFinish_Click(object sender, EventArgs e)
{
hours = Convert.ToInt32(txtHours.Text);
minutes = Convert.ToInt32(txtMinutes.Text);
seconds = Convert.ToInt32(txtSeconds.Text);
lblFinished.Text = Convert.ToString(gate.Total);
// Check if a runner has been selected
if (lstRunners.SelectedIndex > -1)
{
// Obtain selected runner
Runner selectedRunner = (Runner)lstRunners.SelectedItem;
// If runner hasn't finished
if (selectedRunner.HasFinished == false)
{
// Call the method in FinishGate class to process the runner
FinishGate.ProcessRunner(selectedRunner);
}
else
{
// Runner has finished / been processed so increase the total that have completed the climb by one
finishgate.Total++;
}
}
}
}
Here is the FinishGate.cs:
class Gate
{
private int total;
public int Total
{
get { return total; }
set { total = value; }
}
public static void ProcessRunner(Runner selectedRunner)
{
}
}
What I want to happen is that when a runner in the listbox is selected and the Process button is clicked, the boolean hasFinished in ProcessRunner is changed to true and the Total integer is increased by one, which then updates the lblFinished to also increase by one, but I can't get it to work.
My two main issues are: I'm not sure what the code would be in ProcessRunner() to say if hasFinished == false change it to true, else leave the boolean the way it is. The other issue is getting the lblFinished to update accordingly when the integer increments.
Any advice over where I'm going wrong and how to prevent this in the future would be great.
You could try this:
Put this line
lblFinished.Text = Convert.ToString(gate.Total);
at the end of the btnFinish_Click() event instead of the beginning. This way it will update after ProcessRunner() is run.
Also, add this to your ProcessRunner() event in your Gate class:
if (selectedRunner.hasFinished == false)
{
selectedRunner.hasFinished = true;
}
//You don't need to do anything if it isn't false.
I hope this is of use to you. Let me know if I need to be more specific.
I have a library with an Interface.
Public Interface Progress
{
int ProgressValue{get;set;},
string ProgressText{get;set;},
}
Library has a method Create (dummy code):
Public Class TestLibrary
{
Progress _progress;
Public void Create()
{
foreach(var n in TestList)
{
// Do Something
_progress.ProgressValue = GetIndex(n);
_progress.ProgressText = "Updating..." + n;
}
}
}
I have a project that references this library and calls Create method. It even Implements Interface Progress.
Public Class TestProject : Progress
{
public int ProgressValue
{
get{return progressBar1.Value;}
set{progressBar1.Value = value;}
}
public int ProgressText
{
get{return label1.Text;}
set{label1.Text = value;}
}
}
Now when I run the application, Progress Bar behaves properly and shows the progress correctly, but the Text of label1 does not change at all. But it do change in the end of for loop and shows the last item in loop. Can anyone help me out in this?
Note: All these codes are written directly without testing as I don't have my application now with me. Sorry for any syntax errors.
It sounds like all your work is being done on the UI thread. Don't do that.
Instead, run the loop itself in a background thread, and use Control.Invoke or Control.BeginInvoke (probably in the Progress implementation) to marshal a call across to the UI thread just to update the UI. This will leave your UI responsive (and able to update labels etc) while it's still processing.
Used a Label instead of ProgressBar. You can try this code [using BackGroundWorker] -
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form3 : Form
{
private BackgroundWorker _worker;
BusinessClass _biz = new BusinessClass();
public Form3()
{
InitializeComponent();
InitWorker();
}
private void InitWorker()
{
if (_worker != null)
{
_worker.Dispose();
}
_worker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
_worker.DoWork += DoWork;
_worker.RunWorkerCompleted += RunWorkerCompleted;
_worker.ProgressChanged += ProgressChanged;
_worker.RunWorkerAsync();
}
void DoWork(object sender, DoWorkEventArgs e)
{
int highestPercentageReached = 0;
if (_worker.CancellationPending)
{
e.Cancel = true;
}
else
{
double i = 0.0d;
int junk = 0;
for (i = 0; i <= 199990000; i++)
{
int result = _biz.MyFunction(junk);
junk++;
// Report progress as a percentage of the total task.
var percentComplete = (int)(i / 199990000 * 100);
if (percentComplete > highestPercentageReached)
{
highestPercentageReached = percentComplete;
// note I can pass the business class result also and display the same in the LABEL
_worker.ReportProgress(percentComplete, result);
_worker.CancelAsync();
}
}
}
}
void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
// Display some message to the user that task has been
// cancelled
}
else if (e.Error != null)
{
// Do something with the error
}
}
void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = string.Format("Result {0}: Percent {1}",e.UserState, e.ProgressPercentage);
}
}
public class BusinessClass
{
public int MyFunction(int input)
{
return input+10;
}
}
}
Posted the same a few days ago here
The code you posted uses one thread. That means every operation is excuted in sequence right after the previous one finished.
Since you can update GUI elements right away, I suppose the code to be run from main thread (a.k.a "GUI thread"). Blocking the GUI thread results in the GUI ("Graphical User Interface") not updating until there is some idle time for it.
Use following example to refresh your label every iteration so that it updates your UI.
label1.Text = i.ToString();
label1.Refresh();