Long story short, due to shifting business requirements, I need to be able to show the end user the progress of a file archival process controlled by a C# console application. The console app essentially gets a list of local files from a db and then copies them to an archive location. This was originally supposed to be a background process triggered by a scheduled task. Now, however, users can launch it manually with various arguments from the command line, so I was recently tasked with letting the user know the status of the archive process.
I thought I would just use a WPF ProgressBar control for this, but now I'm going in circles trying to sort out the best way to do this. I've been working with the answer from #JamesWilkins here: WPF window from a Console project?
I've added the ProgressBar window to the console application, and added the following to the Main method in the console(super simplified for clarity):
[STAThread]
static void Main(string[] args)
{
// EXISTING CONSOLE LOGIC
ParseCommandLineArgs();
Configure();
// ADDED
InitializeWindows(); // opens the WPF window and waits here
// EXISTING CONSOLE LOGIC
BeginArchival();
}
static void InitializeWindows()
{
WinApp = new Application();
WinApp.Run(ProgressBar = new ProgressBar()); // blocking call
}
Then in the ProgressBar.xaml code behind:
public partial class ProgressBar : Window
{
public ProgressBar()
{
InitializeComponent();
}
private void ProgressBar_OnContentRendered(object sender, EventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += worker_DoWork;
worker.ProgressChanged += worker_ProgressChanged;
worker.RunWorkerAsync();
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
// foreach file that is archived, report progress to the
// ProgressBar.
for (int i = 0; i < 100; i++)
{
(sender as BackgroundWorker).ReportProgress(i);
}
}
private void worker_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
PbArchiveStatus.Value = e.ProgressPercentage;
}
}
The process waits at the InitializeWindows() method until the progress bar is closed, so it doesn't hit any of the archive logic that I need the progress bar to show progress for. It seems that I essentially need to put all of the existing console logic inside the ProgressBar.worker_DoWork() method, but at this point my brain is starting to hurt so I thought I'd reach out.
Am I on the right track, or is there a better way to add a GUI-based progress bar to a console utility? Let me know if I can clarify anything at all.
Related
What I want
I want to do something whenever the timezone in Windows is changed.
What I have so far
For this purpose I have implemented the event SystemEvents.TimeChanged as follows:
In the constructor:
SystemEvents.TimeChanged += SystemEvents_TimeChanged;
The event body:
void SystemEvents_TimeChanged(object sender, EventArgs e)
{
MessageBox.Show("Test1");}
What is the problem
The event is not fired when the time or the time zone is changed in Windows.
What I have tried
When I write the above code in a clean WinForms application, everything works as expected. But not in my application, which of cause contains a lot of other code.
I do not see that I have any other events, which should block the firing of the above event.
My question is
Does anyone have an idea what could cause the above code to not be firing in my application, but work as expected when I create a new project/application only containing the above code?
UPDATE 1
It happens because I show a splash screen in a separate thread before calling
Application.Run(new FormMain());
Then SystemEvents sticks to the thread created by the splash screen, even this thread is terminated when the application has loaded.
The question is now if there is a way to tell SystemEvents that is should now use the "correct" UI thread when the application has loaded?
This answer regarding to question UPDATE 1 part, hence the provided code example in the original question is working.
I have spent some time to figure this it out. Hence I do not have full overview of your code I have improvised 2 WinForms for this solution (one as splash and the other as main), of course this is just example to illustrate the concept.
From what I understand you when you start your software, it starts with splash part as separate thread and when splash is done, than FormMain start afterwards. You can do something better, use ApplicationContext. You make your own context class that is extended from ApplicationContext and in that class you declare your Splash and FormMain with their respective own logic. Now in your case you need to make sure FormMain starts at some point after Splash or some thing like that (I do not know how your software works/flow).
In context class you create the methods to subscribe and unsubscribe to SystemEvents.TimeChanged so you can listen to time changing. I have for demonstration purpose also created a BindingList to demonstrate time changes.
Now lets show some code:
public static void Main()
{
// use own created context
MainApplicationContext context = new MainApplicationContext();
Application.Run(context);
}
// just quick way to demonstrate how we collect time changes
public static BindingList<string> Logs { get; private set; }
private class MainApplicationContext : ApplicationContext
{
private int _formCount;
public MainApplicationContext()
{
Logs = new BindingList<string>();
_formCount = 0;
// splash screen
var splash = new FormSplash();
splash.Closed += OnFormClosed;
splash.Load += OnFormOpening;
splash.Closing += OnFormClosing;
_formCount++;
splash.Show();
// For demo, make some logic that close splash when program loaded.
Thread.Sleep(2000);
var main = new FormMain();
main.Closed += OnFormClosed;
main.Load += OnFormOpening;
main.Closing += OnFormClosing;
_formCount++;
splash.Close();
main.Show();
}
private void OnFormOpening(object sender, EventArgs e)
{
SystemEvents.TimeChanged += SystemEvents_TimeChanged;
}
private void OnFormClosing(object sender, CancelEventArgs e)
{
SystemEvents.TimeChanged -= SystemEvents_TimeChanged;
}
private void OnFormClosed(object sender, EventArgs e)
{
_formCount--;
if (_formCount == 0)
{
ExitThread();
}
}
private void SystemEvents_TimeChanged(object sender, EventArgs e)
{
var text = $"TimeChanged, Time changed; it is now {DateTime.Now.ToLongTimeString()}";
Logs.Add(text);
}
}
Now in our FormMain, create listbox call it LogListBox:
public FormMain()
{
InitializeComponent();
Load += ListChanged;
}
// this keep list of time changes events updated if changed this could be log or some thing else.
private void ListChanged(object sender, EventArgs e)
{
LogListBox.DataSource = Program.Logs;
}
And here how it works:
Documentation
https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.application.run?view=netframework-4.8
https://learn.microsoft.com/en-us/dotnet/api/microsoft.win32.systemevents?view=netframework-4.8
Before showing the splash screen in a separate thread, you can call the method bellow from the main thread, to associate this thread with the system thread that listens for system events.
private static void InitializeSystemEvents()
{
var timerId = SystemEvents.CreateTimer(1);
SystemEvents.KillTimer(timerId);
}
This way you will be able to subscribe later to SystemEvents from your main Form, which runs in the main thread.
Update: the root of the problem is that the system thread that listens for system events is killed when the splash screen is closed. So another way to solve this problem would be to keep the splash screen open (but hidden) during the whole lifetime of the application.
I'm trying to implement a BackgroundWorker for monitoring a FileSystemWatcher service.
My code is divided as:
A Classes.cs wich contains all methods , variables and FileSystemWatcher implementation. And the main Form1 , wich contains form data and calls for the buttons\etc. When i run my program all that happens is the cursor to change (this was already expected) - the action happens in the background (things get done) but no report is shown on my progress bar. I got the example from a website and adapted it to my code - is there anything wrong i'm doing ? I believe there's something involved with the fact the only thing i call is the filesystemwatcher - but i expected that it would report the progress based on the action running "on background".
Any help is appreciated. Thanks
My form1 code (the BackgroundWorker part) and the FileSystemWatcher follows:
namespace PPF_Converter_v10
{
public partial class Form1 : Form
{
private FileManipulation prg;
//private FileManipulation FileOp;
public Form1()
{
InitializeComponent();
//FileOp = new FileManipulation();
prg = new FileManipulation();
//Load config before the program begins - loading sample config or newly generated config
prg.LoadConfig();
FillTextBox();
bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
}
BackgroundWorker CODE:
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
if (!textBox1.Text.Contains("\\"))
{
MessageBox.Show("Please define the input folder before starting");
}
else if (!textBox2.Text.Contains("\\"))
{
MessageBox.Show("Please define the XML Output folder before starting");
}
else if (!textBox3.Text.Contains("\\"))
{
MessageBox.Show("Please define the Converted PPF Output Folder before starting");
}
else if (!textBox4.Text.Contains("\\"))
{
MessageBox.Show("Please define the Invalid PPF Output Folder before starting");
}
else
{
// calls the watcher
// prg.FileWatcher.SynchronizingObject = progressBar1.
prg.ProgramProcessing(textBox1.Text);
}
// do some long-winded process here
// this is executed in a separate thread
int maxOps = 1000000;
for (int i = 0; i < maxOps; i++)
{
//rtbText.AppendText(i.ToString() + "\r\n");
// report progress as a percentage complete
bgWorker.WorkerReportsProgress = true;
bgWorker.ReportProgress(100 * i / maxOps);
}
}
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// update the progress bar
pbProgress.Value = e.ProgressPercentage;
}
private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// return to "normal" mode of operation
this.Cursor = Cursors.Default;
btnGo.Enabled = true;
}
private void btnGo_Click_1(object sender, EventArgs e)
{
// give the appearance of something happening
this.Cursor = Cursors.WaitCursor;
btnGo.Enabled = false;
// call RunWorkerAsync to start the background thread
bgWorker.RunWorkerAsync();
}
Exception thrown when the RichtextBox is enabled:
Additional information: Cross-thread operation not valid: Control 'rtbText' accessed from a thread other than the thread it was created on.
You're invoking a MessageBox from the background thread on the foreground thread. That is like doing UI in two separate threads which is a no-no.
What you can do is either use events or an event aggregator from your background thread. I would probably go for the latter. This way, your background thread, when something is wrong, can (and should) abort immediately and notify through a message that it was unable to process the file.
Think of a background task as something that has no UI whatsoever. It is there in the background and can only communicate to the UI thread using events or messages.
I'm developing a user interface for a program, and something very strange is happening.
I have a text view, 2 buttons and a progress bar. I redirectioned the output on the console to my text view. so wen I click the buttons I should receive output messages. in the beginning it was fine, but then I used some longer routines, I'm trying to log in into a web service and use web-requests.
my code works almost as It was supposed to work, I can log in and make my web requests just fine. but because the answers can become slow I created some output messages, and there my problem started.. My interface wont update until all the code I created on my event handler end's running. and when that code ends executing, I receive all the output messages all at once. I cant even move the window while the program is running..
I´m programing on c# for my first time, I had to use it because I need to use dll's.. and this kind of problem never happened before. I usually use Java.
It's like the code isn't running on the right order and it doesn´t make sense to me.. because I know my code is right because it runs on the console, and it runs while the program isn't responding..
I cant seem to understand this, should I make my events handling using threads?
class MainClass
{
public static void Main (string[] args)
{
Application.Init ();
UIMain win = new UIMain ();
win.ShowAll ();
Application.Run ();
}
}
public partial class UIMain : Gtk.Window
{
public UIMain () :
base (Gtk.WindowType.Toplevel)
{
System.Windows.Forms.Application.EnableVisualStyles ();
this.Build ();
Console.SetOut (new ControlWritter(this.textview1));
}
protected void OnButton2Clicked (object sender, EventArgs e)
{
if (entry1.Text.Equals(String.Empty) || entry2.Text.Equals(String.Empty)) {
Console.WriteLine("random output");
}
ConstantesSetup.autoSetup ();
button1.Sensitive = true;
if (!ConstantesSetup.var1) {
ConstantesSetup.routine6 ();
ConstantesSetup.routine5 ();
ConstantesSetup.routine4 ();
ConstantesSetup.routine3 ();
ConstantesSetup.routine2 ();
ConstantesSetup.var1 = true;
}
}
protected void OnButton1Clicked (object sender, EventArgs e)
{
switch (ConstantesSetup.erp) {
case "ERP":
eti_scp.autoSync (this);
break;
}
}
}
I'm sorry for the lack of code, but I don't even know were to start looking for the problem..
thanks for your time ;)
You are blocking the UI thread with long running synchronous operations. You need to run these long running operations asynchronously so that the button click event handler can return right away while your tasks run in the background.
There are several options for running tasks asynchronously but one simple option is using a BackgroundWorker. In your event handler you could do something like:
var worker = new BackgroundWorker();
worker.DoWork += (o, args) =>
{
//call long running processes here
};
worker.RunWorkerAsync();
The BackgroundWorker will also dispatch these operations onto the UI thread for you so you can update controls in the form inside the DoWork callback method.
I want delete java folder with C#,
but I have little problem.
this is the code
private void setDebug(string value)
{
debug.Text = value;
}
private void buildButton_Click(object sender, EventArgs e)
{
// delete java folder
string java_folder = #"C:\Program Files\Java";
if (Directory.Exists(java_folder))
{
setDebug("Deleting Java folder...");
Directory.Delete(java_folder, true);
progressBar.Value += 10;
}
}
when I click on the button the program stuck, but when i delete the line Directory.Delete
it change the debug label to "Deleting java folder..."
I know that the program stuck because it delete the folder but i want it change the debug first before it delete the folder.
what to do? thanks for help :)
The reason that your code doesn't appear to be working is because of the way WinForms handles UI updates.
The UI will not repaint until your method buildButton_Click completes - and nor will the UI be responsive until that time. The Directory.Delete line is something that will take a long time to run, and so your program "sticks" and you don't see the debug label.
What you need to do is look into using threading - read up on Threads, Tasks or the BackgroundWorker class in order to understand how they work. Then, instead of calling Directory.Delete directly in your method, use one of those techniques to run the delete on a background thread.
You need to wrap your code in a different thread, like this:
private void setDebug(string value)
{
debug.Text = value;
}
private void buildButton_Click(object sender, EventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
string java_folder = #"C:\Program Files\Java";
if (Directory.Exists(java_folder))
{
setDebug("Deleting Java folder...");
worker.DoWork += (s, args) => // this is the off-thread code
{
// delete java folder
Directory.Delete(java_folder, true);
};
worker.RunWorkerCompleted += (s,args)=> // this goes off when .DoWork is done
{
progressBar.Value += 10;
};
// this invokes .DoWork handler (which we defined above)
worker.RunWorkerAsync();
}
}
I'm currently writing a lightweight program that consolidates many command line and other external processes into one application.
Currently, I am faced with the challenge of pulling system information using the system info process.
I have successfully coded the button to call the system info process, and redirect the output to a text field.
What I am now attempting is to have a progress bar at the bottom of my WPF window, since it takes a moment to load the system information.
Since I don't know of a way to get an accurate duration from an external process, I am attempting to use the Marquee style.
I've been following examples here on stackoverflow (Windows Forms ProgressBar: Easiest way to start/stop marquee?), as well as other sites, but haven't been able to determine where to put the code so that the progress bar scrolls while systeminfo is running and stops when it is finished.
My current code (without the progressbar1.Style = ProgressBarStyle.Marquee;) is below.
Any suggestions as to where to place the code, or what syntax to use would be greatly appreciated. Thank you in advance!
private void btnSystemInfo_Click(object sender, RoutedEventArgs e)
{
// Get system info
Process info = new Process();
info.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
info.StartInfo.FileName = "C:\\Windows\\system32\\systeminfo.exe";
info.StartInfo.Arguments = "-S " + context;
info.StartInfo.UseShellExecute = false;
info.StartInfo.CreateNoWindow = true;
info.StartInfo.RedirectStandardOutput = true;
info.Start();
string infoOutput = info.StandardOutput.ReadToEnd();
info.WaitForExit();
// Write to the txtInfo text box
txtInfo.Text = "System Info: " + infoOutput;
txtInfo.Foreground = Brushes.Black;
txtInfo.VerticalScrollBarVisibility = ScrollBarVisibility.Visible;
// Switch to the info tab
tabControl.SelectedIndex = 3;
}
What you need to do is move the code that gathers system information in the BackgroundWorker thread and in main UI thread start a marquee. Once you get the signal from BackgroundWorker thread that it's work has complete, stop the marquee and display the information in the textBox
void ButtonClickEvent()
{
BackgroundWorker bg = new BackgroundWorker();
bg.DoWork += new DoWorkEventHandler(MethodToGetInfo);
bg.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bg_RunWorkerCompleted);
//show marquee here
bg.RunWorkerAsync();
}
void MethodToGetInfo(Object sender, DoWorkEventArgs args)
{
// find system info here
}
void bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs args)
{
//this method will be called once background worker has completed it's task
//hide the marquee
//update the textbox
//NOTE that this is not a UI thread so you will need BeginInvoke to execute something in the UI thread
}
If you have several processes you want to run at the same time, then you should look into the tasking library. You can make the tasks run in parallel or serial, depending on your system resources. You can also keep track of what gets done so you could display a % of work done.