I have a button on my WinForm. Code works great until I observed my test users.
They don't wait for the PDF to launch. Instead they will click it several times (causing multiple instances of the Acrobat reader to launch
private void pictureBoxNewsLetter_Click(object sender, EventArgs e)
{
lockControls();
launchNewsLetter();
unlockControls();
}
private void launchNewsLetter()
{
// Newsletter
lockControls();
ProcessStartInfo psi = new ProcessStartInfo(#"Y:\Newsletter.pdf");
Process ps = new Process { StartInfo = psi };
ps.Start();
ps.WaitForExit();
Thread.Sleep(1000);
unlockControls();
}
--- Code to disable my controls.
private void lockControls()
{
dontRunHandler = false;
foreach (var pb in this.Controls.OfType<PictureBox>())
{
pb.Enabled = false;
}
}
private void unlockControls()
{
dontRunHandler = true;
foreach (var pb in this.Controls.OfType<PictureBox>())
{
pb.Enabled = true;
}
}
Related
I have a WinForms application that is calling a business class method that performs some heavy duty action taking about 5 seconds for each call. The main form calls this method in a loop. This loop can run from 10 times to maybe up to 10 thousand times.
The WinForms application sends a parameter to the business class and has an area to display the time taken for each method call and what the value returned by the method. How do I inform my main window and update a text area in the main winform with what the method has returned for each call?
Currently the data comes all at once after all the threads have finished. Is there a way to update the UI for all the iterations of the loop once the each call is done? I don't mind if it is done sequentially also.
The FORM
HeavyDutyClass hd;
public Form1()
{
InitializeComponent();
hd = new HeavyDutyClass();
}
//BUTTON CLICK
private void Start_Click(object sender, EventArgs e)
{
int filecount = 5000; //BAD - opening 5000 threads! Any other approach?
hd.FileProcessed += new EventHandler(hd_FileProcessed);
var threads = new Thread[filecount];
for (int i = 0; i < filecount; i++)
{
threads[i] = new Thread(() => { hd.LongRunningMethod(); });
threads[i].Start();
}
}
//BUSINESS CLASS EVENT THAT FIRES WHEN BUSINESS METHOD COMPELTES
void hd_FileProcessed(object sender, EventArgs e)
{
if (dgv.InvokeRequired)
{
dgv.Invoke((MethodInvoker)delegate { UpdateGrid(); });
}
}
private void UpdateGrid()
{
dgv.Rows.Add(1);
int i = dgv.Rows.Count;
dgv.Rows [ i-1].Selected = true;
dgv.FirstDisplayedScrollingRowIndex = i - 1;
}
The business HeavyDuty class
public event EventHandler FileProcessed;
public HeavyDutyClass()
{
}
protected virtual void OnMyEvent(EventArgs e)
{
if (FileProcessed != null)
{
FileProcessed(this, e);
}
}
public bool LongRunningMethod()
{
for (double i = 0; i < 199990000; i++)
{
//time consuming loop
}
OnMyEvent(EventArgs.Empty);
return true;
}
Add a Winforms Project, Drop a Label Control on the Form , Copy-Paste this code and Hit F5
[EDIT]: Updated with the business class comment from the user
NB: My form class is named Form3. You may have to change your Program.cs or vice-versa.
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public class BusinessClass
{
public int MyFunction(int input)
{
return input+10;
}
}
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);
}
}
}
With this you can achieve Cancel functionality also very easily.
Observe that during initialisation, I set the WorkerSupportsCancellation = true & then I check for _worker.CancellationPending in the DoWork. So, if you want to cancel the process by a Cancel Button click, then you will write this code in the button handler- _worker.CancelAsync();
In my Application I have two buttons "open" and "close".
When I click open button window will be opened, when I click close button window will be closed.
When I click open button 3 times, 3 windows will be opened. I want to close all window when I click close button.
Here is my code [Please don't try to Change the Thread because that is my requirement in my Application]
public partial class MainWindow : Window
{
Window ProgressWindow;
Thread ProgressThread;
public MainWindow()
{
InitializeComponent();
}
private void Open_Click(object sender, RoutedEventArgs e)
{
ProgressThread = new Thread(() =>
{
ProgressWindow = new Window();
ProgressWindow.Margin = new Thickness(0, 0, 50, 0);
ProgressWindow.WindowState = WindowState.Normal;
ProgressWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
ProgressWindow.Height = 180;
ProgressWindow.Width = 180;
ProgressWindow.Content = "Hello WPF";
ProgressWindow.ShowInTaskbar = false;
ProgressWindow.Show();
ProgressWindow.Closed += (sender2, e2) =>
ProgressWindow.Dispatcher.InvokeShutdown();
System.Windows.Threading.Dispatcher.Run();
});
ProgressThread.SetApartmentState(ApartmentState.STA);
ProgressThread.Start();
}
private void Close_Click(object sender, RoutedEventArgs e)
{
if (ProgressThread.IsAlive == true)
{
ProgressThread.Abort();
}
}
}
I would recommend to store references to created windows, your code can look like this:
Stack<Window> ProgressWindow=new Stack<Window>();
Thread ProgressThread;
private void Open_Click(object sender, RoutedEventArgs e)
{
ProgressThread = new Thread(() =>
{
var window = new Window();
window.Margin = new Thickness(0, 0, 50, 0);
window.WindowState = WindowState.Normal;
window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
window.Height = 180;
window.Width = 180;
window.Content = "Hello WPF";
window.ShowInTaskbar = false;
window.Show();
window.Closed += (sender2, e2) =>
window.Dispatcher.InvokeShutdown();
ProgressWindow.Push(window);
System.Windows.Threading.Dispatcher.Run();
});
ProgressThread.SetApartmentState(ApartmentState.STA);
ProgressThread.Start();
}
private void Close_Click(object sender, RoutedEventArgs e)
{
while (ProgressWindow.Count > 0)
{
ProgressWindow.Pop().Close();
}
}
thread aborting is not recommended if it is "normal" workflow of your application, i.e. window wasn't closed because of some critical error
I wouldn't recommend what you are doing and actually I don't really know if it works like this, but since you stated that it's your (strange) requirement to use threads like this, I will only comment on the actual problem:
You should save the threads in a List and then close all the threads from this list.
Edit:
public partial class MainWindow : Window
{
Window ProgressWindow;
List<Thread> ProgressThreads = new List<Thread>();
public MainWindow()
{
InitializeComponent();
}
private void Open_Click(object sender, RoutedEventArgs e)
{
ProgressThreads.Add(new Thread(() =>
{
ProgressWindow = new Window();
ProgressWindow.Margin = new Thickness(0, 0, 50, 0);
ProgressWindow.WindowState = WindowState.Normal;
ProgressWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
ProgressWindow.Height = 180;
ProgressWindow.Width = 180;
ProgressWindow.Content = "Hello WPF";
ProgressWindow.ShowInTaskbar = false;
ProgressWindow.Show();
ProgressWindow.Closed += (sender2, e2) =>
ProgressWindow.Dispatcher.InvokeShutdown();
System.Windows.Threading.Dispatcher.Run();
}));
ProgressThread.SetApartmentState(ApartmentState.STA);
ProgressThread.Start();
}
private void Close_Click(object sender, RoutedEventArgs e)
{
foreach(var ProgressThread in ProgressThreads)
{
if (ProgressThread.IsAlive == true)
{
ProgressThread.Abort();
}
}
}
}
You will need to keep a record of all threads you have opened when you click "Open". Then in your "Close" method loop over that list closing each one.
Member variable:
List<Thread> allThreads = new List<Thread>();
Then in your open handler add:
allThreads.Add(ProgressThread);
Then your close handler becomes:
foreach (Thread thread in allThreads)
{
if (thread.IsAlive)
{
thread.Abort();
}
}
That what you are trying is unorthodox should go without saying.
private void btnSend_Click(object sender, RoutedEventArgs e)
{
Button obj=(Button)sender;
obj.Content="Cancel";
SendImage send = new SendImage();
Thread t = new Thread(send.Image);
t.Start();
//run separate thread.(very long, 9 hours)
//so dont wait.
//but the button should be reset to obj.Content="Send"
//Can I do this?
}
I want the button to be reset to "Send" (after completion of thread). But form should not wait. Is this possible?
You can do this more elegantly using the BackgroundWorker class.
XAML for the Button:
<Button x:Name="btnGo" Content="Send" Click="btnGo_Click"></Button>
Code :
private BackgroundWorker _worker;
public MainWindow()
{
InitializeComponent();
_worker = new BackgroundWorker();
_worker.WorkerSupportsCancellation = true;
_worker.WorkerReportsProgress = true;
}
private void btnGo_Click(object sender, RoutedEventArgs e)
{
_worker.RunWorkerCompleted += delegate(object completedSender, RunWorkerCompletedEventArgs completedArgs)
{
Dispatcher.BeginInvoke((Action)(() =>
{
btnGo.Content = "Send";
}));
};
_worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
Dispatcher.BeginInvoke((Action)(() =>
{
btnGo.Content = "Cancel";
}));
SendImage sendImage = args.Argument as SendImage;
if (sendImage == null) return;
var count = 0;
while (!_worker.CancellationPending)
{
Dispatcher.BeginInvoke((Action)(() =>
{
btnGo.Content = string.Format("Cancel {0} {1}", sendImage.Name, count);
}));
Thread.Sleep(100);
count++;
}
};
if (_worker.IsBusy)
{
_worker.CancelAsync();
}
else
{
_worker.RunWorkerAsync(new SendImage() { Name = "Test" });
}
}
Make the Button a member of your Window/UserControl class (by giving it a Name in XAML). When the thread eventually finishes, do this before returning from the thread method:
myButton.Dispatcher.BeginInvoke(
(Action)(() => myButton.Content = "Send"));
I'm working on building a multi-threaded UI. I would like long processes to be handled by the BackgroundWorker class, and have a small timer on the UI to keep track of how long the process is taking. It's my first time building such a UI, so I'm reading up on related resources on the web. My test code is thus:
private BackgroundWorker worker;
private Stopwatch swatch = new Stopwatch();
private delegate void simpleDelegate();
System.Timers.Timer timer = new System.Timers.Timer(1000);
string lblHelpPrevText = "";
private void btnStart_Click(object sender, RoutedEventArgs e)
{
try
{
worker = new BackgroundWorker(); //Create new background worker thread
worker.DoWork += new DoWorkEventHandler(BG_test1);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BG_test1end);
worker.RunWorkerAsync();
simpleDelegate del = new simpleDelegate(clockTicker);
AsyncCallback callBack = new AsyncCallback(clockEnd);
IAsyncResult ar = del.BeginInvoke(callBack, null);
lblHelpText.Text = "Processing...";
}
finally
{
worker.Dispose(); //clear resources
}
}
private void clockTicker()
{
//Grab Text
simpleDelegate delLblHelpText = delegate()
{ lblHelpPrevText = this.lblHelpText.Text; };
this.Dispatcher.BeginInvoke(DispatcherPriority.Send, delLblHelpText);
//Start clock
timer.Elapsed += new ElapsedEventHandler(clockTick);
timer.Enabled = true;
swatch.Start();
}
private void clockTick(object sender, ElapsedEventArgs e)
{
simpleDelegate delUpdateHelpTxt = delegate()
{ this.lblHelpText.Text = String.Format("({0:00}:{1:00}) {2}", swatch.Elapsed.Minutes, swatch.Elapsed.Seconds, lblHelpPrevText); };
this.Dispatcher.BeginInvoke(DispatcherPriority.Send, delUpdateHelpTxt);
}
private void BG_test1(object sender, DoWorkEventArgs e)
{
//this.lblHelpText.Text = "Processing for 10 seconds...";
Thread.Sleep(15000);
}
private void BG_test1end(object sender, RunWorkerCompletedEventArgs e)
{
this.lblHelpText.Text = "Process done.";
this.timer.Enabled = false;
this.swatch.Stop();
this.swatch.Reset();
}
static void clockEnd(IAsyncResult ar)
{
simpleDelegate X = (simpleDelegate)((AsyncResult)ar).AsyncDelegate;
X.EndInvoke(ar);
}
The idea is when the button is clicked, we take the status text from a Label (e.g. "Processing...") then append the time onto it every second. I could not access the UI elements from the Timer class as it's on a different thread, so I had to use delegates to get and set the text.
It works, but is there a better way to handle this? The code seems much for such a basic operation. I'm also not fully understanding the EndInvoke bit at the bottom. I obtained the snippet of code from this thread Should One Always Call EndInvoke a Delegate inside AsyncCallback?
I understand the idea of EndInvoke is to receive the result of BeginInvoke. But is this the correct way to use it in this situation? I'm simply worried about any resource leaks but when debugging the callback appears to execute before my timer starts working.
Don't use a separate timer to read the progress of your BackgroundWorker and update the UI. Instead, make the BackgroundWorker itself "publish" its progress to the UI directly or indirectly.
This can be done pretty much anyway you want to, but there's a built-in provision exactly for this case: the BackgroundWorker.ProgressChanged event.
private void BG_test1(object sender, DoWorkEventArgs e)
{
for(var i = 0; i < 15; ++i) {
Thread.Sleep(1000);
// you will need to get a ref to `worker`
// simplest would be to make it a field in your class
worker.ReportProgress(100 / 15 * (i + 1));
}
}
This way you can simply attach your own handler to ProgressChanged and update the UI using BeginInvoke from there. The timer and everything related to it can (and should) go.
You can use timer to update UI. It is normal practice. Just instead of System.Timer.Timer I suggest use System.Windows.Threading.DispatcherTimer. The DispatcherTimer runs on the same thread as the Dispatcher. Also, instead of BackgroundWorker you can use ThreadPool.
Here is my sample:
object syncObj = new object();
Stopwatch swatch = new Stopwatch();
DispatcherTimer updateTimer; // Assume timer was initialized in constructor.
void btnStart_Click(object sender, RoutedEventArgs e) {
lock (syncObj) {
ThreadPool.QueueUserWorkItem(MyAsyncRoutine);
swatch.Start();
updateTimer.Start();
}
}
void updateTimer_Tick(object sender, EventArgs e) {
// We can access UI elements from this place.
lblHelpText.Text = String.Format("({0:00}:{1:00}) Processing...", swatch.Elapsed.Minutes, swatch.Elapsed.Seconds);
}
void MyAsyncRoutine(object state) {
Thread.Sleep(5000);
lock (syncObj)
Dispatcher.Invoke(new Action(() => {
swatch.Stop();
updateTimer.Stop();
lblHelpText.Text = "Process done.";
}), null);
}
private void button1_Click(object sender, EventArgs e)
{
string strFullFilePath = #"D:\Print.pdf";
ProcessStartInfo ps = new ProcessStartInfo();
ps.UseShellExecute = true;
ps.Verb = "print";
ps.CreateNoWindow = true;
ps.WindowStyle = ProcessWindowStyle.Hidden;
ps.FileName = strFullFilePath;
Process.Start(ps);
Process proc = Process.Start(ps);
KillthisProcess("AcroRd32");
}
public void KillthisProcess(string name)
{
foreach (Process prntProcess in Process.GetProcesses())
{
if (prntProcess.ProcessName.StartsWith(name))
{
prntProcess.WaitForExit(10000);
prntProcess.Kill();
}
}
}
Imagine the code below. Only the first window appears on the top, all of subsequent windows won't nor can they be programatically focused for some reason (they appear in the background). Any idea how to workaround this? BTW, static methods/properties are not allowed nor is any global property.
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Thread t1 = new Thread(CreateForm);
t1.SetApartmentState(ApartmentState.STA);
t1.Start();
t1.Join();
t1 = new Thread(CreateForm);
t1.SetApartmentState(ApartmentState.STA);
t1.Start();
t1.Join();
}
private static void CreateForm()
{
using (Form f = new Form())
{
System.Windows.Forms.Timer t = new System.Windows.Forms.Timer
{
Enabled = true,
Interval = 2000
};
t.Tick += (s, e) => { f.Close(); t.Enabled = false; };
f.TopMost = true;
Application.Run(f);
}
}
Hans Passant solved the problem: just use SetForegroundWindow() (P/Invoke). Shees, I should have though of that :-)