My form is going to run some code that might take a while to execute. I would like to display a "please wait" message while the operation is running on the background.
I'd like to have that message in a form, one that I can control its visibility, and also its text, from other forms.
I'd also like it to be set to start in the Program.cs file.
My code, so far:
namespace KAN
{
public partial class prosze_czekac : Form
{
public prosze_czekac()
{
InitializeComponent();
}
private delegate void OffVisible();
public void Wylacz()
{
if (this.InvokeRequired)
this.Invoke(new OffVisible(Wylacz));
else
this.Visible = false;
}
delegate void SetTextCallback(string text);
public void ZmienTekst(string text)
{
if (this.InvokeRequired)
{
//SetTextCallback d = new SetTextCallback(this.ZmienTekst);
Invoke(new SetTextCallback(this.ZmienTekst), text);
//Invoke(d, new object[] { text });
}
else
{
this.Visible = true;
this.Text = text;
this.lblKomunikat.Text = text;
this.Update();
}
}
}
}
I do not know how to run a form, how to create an instance and as editing text. All this in any form, any thread.
Is the above code is correct and how to use it to make it properly?
How am I so ready form "please wait" I would like to turn it on now in the initial class (Program.cs). Use it in any form design.
Sample code, do not know if correct:
namespace KAN
{
static class Program
{
public static prosze_czekac PleaseWait;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Thread thread = new Thread(new ThreadStart(PleaseWait.Show());
PleaseWait.ZmienTekst("Please wait... Running the program");
// long operation
PleaseWait.Wylacz();
Application.Run(new main());
}
}
}
namespace KAN
{
public partial class main: Form
{
public main()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
// examples of long task in another form
for (int i = 0; i < 5; i++)
{
Program.PleaseWait.ZmienTekst((i + 1).ToString());
System.Threading.Thread.Sleep(1000);
}
Program.PleaseWait.Wylacz();
}
}
}
The first time I ask a question, please bear with me.
PS
"Wylacz" is "exit" (void) and is meant to "hide" so that every time you do not initiate the form.
"prosze_czekac" is "please wait".
Use the BackgroundWorker. The following code assumes, you have a button 'button1' in your form, which executes the worker, which starts the long running task on a different thread:
BackgroundWorker _worker;
// button click starts the execution of the lung running task on another thread
private void button1_Click(object sender, EventArgs e)
{
label1.Visible = true; // show the label "please wait"
_worker.RunWorkerAsync();
}
private void Form1_Load(object sender, EventArgs e)
{
// initialize worker
_worker = new BackgroundWorker();
_worker.DoWork += new DoWorkEventHandler(worker_DoWork);
_worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_worker_RunWorkerCompleted);
}
// executes when long running task has finished
void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// hide the label
label1.Visible = false;
}
// is called by 'RunWorkerAsync' and executes the long running task on a different thread
void worker_DoWork(object sender, DoWorkEventArgs e)
{
// long running task (just an example)
for (int i = 0; i < 1000000000; i++)
{
}
}
Related
I have a Windows Form application and managed DLL in one solution. DLL contains some time consuming functions during which I wish to update the Form contents (callback from the DLL to the Form with progess updates). I have the following code:
Form code, where I initialize the DLL and give it a callback function in the Initialize method. I also start a separate Thread to periodicly check the message_queue for new messages from the DLL. The DLL function is also called in a separate Thread (non blocking for the UI).
private LibraryDLL library_dll;
private ConcurrentQueue<string> message_queue;
public MainForm()
{
InitializeComponent();
library_dll = new LibraryDLL();
message_queue = new ConcurrentQueue<string>();
library_dll.Initialize(ProcessMessage);
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
string message;
if (message_queue.TryDequeue(out message))
{
PrintMessage(message);
}
}).Start();
}
private void ProcessMessage(string message)
{
message_queue.Enqueue(message);
}
private void PrintMessage(string message)
{
this.Invoke((MethodInvoker)delegate
{
listBox_rows.Items.Add(message);
});
}
private void button_send_Click(object sender, EventArgs e)
{
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
library_dll.DoWork();
}).Start();
}
In DLL code, I use the callback method to report progress:
private CallBack callback;
public delegate void CallBack(string message);
public LibraryDLL() { }
public void Initialize(CallBack callback)
{
this.callback = callback;
}
public void DoWork()
{
callback("working...")
Thread.Sleep(500);
callback("working...")
Thread.Sleep(500);
callback("working...")
Thread.Sleep(500);
}
My problem is, that instead of string "working" appearing every 500ms, it appears 3 times after 1500ms (only after the Thread in which the DoWork method is running ends). I also tried the Invalidate()-Update()-Refresh() sequence in the Form's PrintMessage function, but without any effect.
Thanks for the advice!
EDIT1:
I modified the code to use the BackgroundWorker, however, the problem remains (nothing for 1500ms, than all 3 strings at once).
BackgroundWorker bck_worker;
public MainForm()
{
InitializeComponent();
library_dll = new LibraryDLL();
library_dll.Initialize(bck_worker);
bck_worker = new BackgroundWorker();
bck_worker.ProgressChanged += new ProgressChangedEventHandler(bckWorker_ProgressChanged);
bck_worker.WorkerReportsProgress = true;
bck_worker.WorkerSupportsCancellation = true;
}
private void bckWorker_DoWork(object sender, DoWorkEventArgs e)
{
library_dll.DoWork();
}
private void bckWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
PrintMessage((string)e.UserState);
}
private void button_send_Click(object sender, EventArgs e)
{
bck_worker.DoWork += new DoWorkEventHandler(bckWorker_DoWork);
bck_worker.RunWorkerAsync();
}
private void PrintMessage(string message)
{
listBox_rows.Items.Add(message);
}
And the DLL:
private BackgroundWorker bck_worker;
public LibraryDLL() { }
public void Initialize(BackgroundWorker bck_worker)
{
this.bck_worker = bck_worker;
}
public void DoWork()
{
bck_worker.ReportProgress(25, "working...");
Thread.Sleep(500);
bck_worker.ReportProgress(50, "working...");
Thread.Sleep(500);
bck_worker.ReportProgress(75, "working...");
Thread.Sleep(500);
}
EDIT2:
OK, I now tried to add the Invalidate-Update-Refresh sequence at the end of the PrintMessage function and it finaly works (with the BackgroundWorker approach)!
Use background worker and workers's report progress to update your UI: background worker doc
Is it possible to show just splash screen (without showing main form)?
SplashScreen splash;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
splash = new SplashScreen();
splash.Show();
BackgroundWorker backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += BackgroundWorker_DoWork;
backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
backgroundWorker.RunWorkerAsync();
// var mainForm = MainForm();
// Application.Run(layoutForm); // I don't want to call this from here
}
private static void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
splash.Close();
// This never gets called, coz application ended
}
private static void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(100);
}
}
You would call your Splash screen from your main form.
public partial class mainform : Form
{
public mainform()
{
InitializeComponent();
}
public mainform_Load(object sender, EventArgs e)
{
this.Visible = false;
using (SplashScreen ss = new SplashScreen())
{
ss.ShowDialog();
SetTheme(ss.LoadedTheme);
this.Visible = true;
}
}
private void SetTheme(Theme theme)
{
//Put your theme setting code here.
}
}
Here's how your SplashScreen code will look:
public partial class SplashScreen : Form
{
public Theme LoadedTheme { get; private set; }
public SplashScreen()
{
InitializeComponent();
}
public void SplashScreen_Load(object sender, EventArgs e)
{
bwSplashScreenWorker.RunWorkerAsync();
}
public void bwSplashScreenWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Load in your data here
LoadedTheme = LoadTheme();
}
public void bwSplashScreenWorker_Completed(object sender, RunWorkerCompletedEventArgs e)
{
DialogResult = DialogResult.OK;
}
}
Now, your application will start, when the mainform is loaded it will hide itself, open the SplashScreen in a blocking manner. The splashscreen will load in your theme data in a background thread and save it into the LoadedTheme property. When the background worker completes it will set the DialogResult to OK which closes the SplashScreen and returns control to mainform_Loaded. At this point you call your SetTheme method passing in the public property LoadedTheme from your SplashScreen. Your SetTheme method sets up your theme and returns back to mainform_Loaded where it sets the mainform to visible.
I have a singletone form that can be opened from a ribbon button or that will check every minute whether it should be open after passing a few conditional checks.
When opening the form from the ribbon button, it works correctly every time.
When opening on the timer, the form does not get rendered correctly, any place a control should be is just displayed as a white rectangle. Screenshots below.
ThisAddIn.cs
using Timer = System.Timers.Timer;
public partial class ThisAddIn
{
private Timer ticker;
private void ThisAddIn_Startup(object sender, System.EventArgs e) {
ticker = new Timer(5 * 60 * 1000);
ticker.AutoReset = true;
ticker.Elapsed += new System.Timers.ElapsedEventHandler(checkForOverdue);
ticker.Start();
}
private void checkForOverdue(object sender, System.Timers.ElapsedEventArgs e)
{
bool overdue = false;
foreach (Reminder reminder in reminders)
{
DateTime now = DateTime.Now;
if (reminder.time <= now)
{
overdue = true;
break;
}
}
if (overdue)
{
RemindersList form = RemindersList.CreateInstance();
if (form != null)
{
form.Show();
}
}
}
}
Ribbon.cs
public partial class Ribbon
{
private void reminderListButton_Click(object sender, RibbonControlEventArgs e)
{
RemindersList form = RemindersList.CreateInstance();
if (form != null)
{
form.Show();
}
}
}
RemindersList.cs
public partial class RemindersList : Form
{
private static RemindersList _singleton;
private RemindersList()
{
InitializeComponent();
this.FormClosed += new FormClosedEventHandler(f_formClosed);
}
private static void f_formClosed(object sender, FormClosedEventArgs e)
{
_singleton = null;
}
public static RemindersList CreateInstance(List<Reminder> rs)
{
if (_singleton == null)
{
_singleton = new RemindersList(rs);
_singleton.Activate();
// Flash in taskbar if not active window
FlashWindow.Flash(_singleton);
return _singleton;
}
else
{
return null;
}
}
}
EDIT - SOLUTION
Per sa_ddam213's answer, I changed out the System.Timers.Timer for a Windows.Forms.Timer and it's now working just how I wanted.
Code changes:
ThisAddIn.cs
using Timer = System.Windows.Forms.Timer;
public partial class ThisAddIn {
private void ThisAddIn_Startup(object sender, System.EventArgs e) {
ticker = new Timer();
ticker.Interval = 5 * 60 * 1000;
ticker.Tick += new EventHandler(checkForOverdue);
ticker.Start();
}
// Also needed to change the checkForOverdue prototype as follows:
private void checkForOverdue(object sender, EventArgs e)
}
You can't touch UI controls/elements with any other thread than the UI thread, in your case the System.Timer is running on another thread and the window will never open
Try switching to a Windows.Forms.Timer
Or invoke the call back to the UI thread.
private void checkForOverdue(object sender, System.Timers.ElapsedEventArgs e)
{
base.Invoke(new Action(() =>
{
/// all your code here
}));
}
I suspect that the timer event handler is not launched on the UI thread, which could cause all sorts of problems. I would check that first and ensure that the UI stuff is actually done on the UI thread.
I am trying to run a function in a different class than the dispatcher through a backgroundworker and have it update the progress on every iteration. I am getting no errors and the backgroundworker is functioning properly, but my textbox never updates...
public partial class MainWindow : Window
{
public BackgroundWorker worker = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
worker.WorkerReportsProgress = true;
worker.DoWork += new DoWorkEventHandler(workerDoWork);
worker.ProgressChanged += new ProgressChangedEventHandler(workerProgressChanged);
}
private void myButtonClick(object sender, RoutedEventArgs e)
{
worker.RunWorkerAsync();
}
void workerDoWork(object sender, DoWorkEventArgs e)
{
yv_usfm.convert(worker);
}
void workerProgressChanged(object sender, ProgressChangedEventArgs e)
{
myTextBox.Text = "some text";
}
}
public class yv_usfm
{
public static void convert(BackgroundWorker worker)
{
int i = 1;
while (i < 100)
{
worker.ReportProgress(i);
i++;
}
}
}
What makes you say the BackgroundWorker is functioning properly? I see no call to worker.RunWorkerAsync(), and without that it will never start.
You're not starting the worker!
worker.RunWorkerAsync();
Try This:
void DoWork(...)
{
YourMethod();
}
void YourMethod()
{
if(yourControl.InvokeRequired)
yourControl.Invoke((Action)(() => YourMethod()));
else
{
//Access controls
}
}
Hope This help.
my aim is that in the function "Dummy" i can change the controls like labels etc of the form from which the thread is initiating..how to do it..please don't suggest completely different strategies or making a worker class etc...modify this if you can
Thread pt= new Thread(new ParameterizedThreadStart(Dummy2));
private void button1_Click(object sender, EventArgs e)
{
pt = new Thread(new ParameterizedThreadStart(Dummy2));
pt.IsBackground = true;
pt.Start( this );
}
public static void Dummy(........)
{
/*
what i want to do here is to access the controls on my form form where the
tread was initiated and change them directly
*/
}
private void button2_Click(object sender, EventArgs e)
{
if (t.IsAlive)
label1.Text = "Running";
else
label1.Text = "Dead";
}
private void button3_Click(object sender, EventArgs e)
{
pt.Abort();
}
}
}
what i plan is that i could do this in the "Dummy" function
Dummy( object p)
{
p.label1.Text = " New Text " ;
}
You could do this, supposing you're passing an instance of the form to the thread method using the t.Start(...) method:
private void Form_Shown(object sender)
{
Thread t = new Thread(new ParameterizedThreadStart(Dummy));
t.Start(this);
}
....
private static void Dummy(object state)
{
MyForm f = (MyForm)state;
f.Invoke((MethodInvoker)delegate()
{
f.label1.Text = " New Text ";
});
}
EDIT
Added thread start code for clarity.
You can't do this. You can only access a UI control on the same thread that created it.
See the System.Windows.Forms.Control.Invoke Method and the Control.InvokeRequired property.
Can use something like this:
private void UpdateText(string text)
{
// Check for cross thread violation, and deal with it if necessary
if (InvokeRequired)
{
Invoke(new Action<string>(UpdateText), new[] {text});
return;
}
// What the update of the UI
label.Text = text;
}
public static void Dummy(........)
{
UpdateText("New text");
}