My application in WPF loads external resources, so I want to show a loading form while the program is loading.
I tried to create the form, and show before the loading code, and close when loading ended.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
LoadForm lf = new LoadForm();
lf.Visibility = Visibility.Visible;
// Al code that delays application loading
lf.Close();
}
But the only thing I get is that the form is showed when loading progress is complete and immediately closes.
I think that I need to use System.Threading but not sure.
Thanks.
Note I load all application external resources in Window_Loaded() method and not in the main class method.
You should look at this MSDN article on creating a SplashScreen in WPF. Essentially you add the Image you want to show to your project and set the Build Action to SplashScreen it will show when your program starts and disappear when your Main Application Window is shown.
You could also try importing the System.ComponentModel Class and use BackgroundWorker to Show your Loading Form, it will allow you to retain responsiveness of your UI.
public partial class MainWindow : Window
{
Window1 splash;
BackgroundWorker bg;
public MainWindow()
{
InitializeComponent();
bg = new BackgroundWorker();
bg.DoWork += new DoWorkEventHandler(bg_DoWork);
bg.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bg_RunWorkerCompleted);
}
void bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
splash.Hide();
}
void bg_DoWork(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(10000);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
splash = new Window1();
splash.Show();
bg.RunWorkerAsync();
}
}
You should put your time consuming code in a background thread (for that you can use BackgroundWorker, Task or Async Await, depending on your dot net framework version)
private void Window_Loaded(object sender, RoutedEventArgs e)
{
LoadForm lf = new LoadForm();
lf.Visibility = Visibility.Visible;
//start the time consuming task in another thread
}
HeavyTaskCompleteEvent()
{
lf.Close();
}
Also look out for the best way to show loading screen. You can show some animation in the main window as well. I don't think showing a form is the best way.
I made a Loader class a while ago you could use. It shows a Window while doing your loading-method, closes it when completed and gives you the output of the method:
public class Loader<TActionResult>:FrameworkElement
{
private Func<TActionResult> _execute;
public TActionResult Result { get; private set; }
public delegate void OnJobFinished(object sender, TActionResult result);
public event OnJobFinished JobFinished;
public Loader(Func<TActionResult> execute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
}
private Window GetWaitWindow()
{
Window waitWindow = new Window { Height = 100, Width = 200, WindowStartupLocation = WindowStartupLocation.CenterScreen, WindowStyle = WindowStyle.None };
waitWindow.Content = new TextBlock { Text = "Please Wait", FontSize = 30, FontWeight = FontWeights.Bold, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center };
return waitWindow;
}
public void Load(Window waitWindow = null)
{
if (waitWindow == null)
{
waitWindow = GetWaitWindow();
}
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate
{
Dispatcher.BeginInvoke(new Action(delegate { waitWindow.ShowDialog(); }));
Result = _execute();
Dispatcher.BeginInvoke(new Action(delegate() { waitWindow.Close(); }));
};
worker.RunWorkerCompleted += delegate
{
worker.Dispose();
if (JobFinished != null)
{
JobFinished(this, Result);
}
};
worker.RunWorkerAsync();
}
}
How to use it:
Loader<TResult> loader = new Loader<TResult>(MethodName);
loader.JobFinished += new Loader<TResult>.OnJobFinished(loader_JobFinished);
loader.Load();
void loader_JobFinished(object sender, TResult result)
{
// do whatever you want with your result here
}
Related
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 have the following WaitScreen class that shows a "please wait..." message when doing a background process:
public class WaitScreen
{
// Fields
private object lockObject = new object();
private string message = "Please Wait...";
private Form waitScreen;
// Methods
public void Close()
{
lock (this.lockObject)
{
if (this.IsShowing)
{
try
{
this.waitScreen.Invoke(new MethodInvoker(this.CloseWindow));
}
catch (NullReferenceException)
{
}
this.waitScreen = null;
}
}
}
private void CloseWindow()
{
this.waitScreen.Dispose();
}
public void Show(string message)
{
if (this.IsShowing)
{
this.Close();
}
if (!string.IsNullOrEmpty(message))
{
this.message = message;
}
using (ManualResetEvent event2 = new ManualResetEvent(false))
{
Thread thread = new Thread(new ParameterizedThreadStart(this.ThreadStart));
thread.SetApartmentState(ApartmentState.STA);
thread.Start(event2);
event2.WaitOne();
}
}
private void ThreadStart(object parameter)
{
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
ManualResetEvent event2 = (ManualResetEvent)parameter;
Application.EnableVisualStyles();
this.waitScreen = new Form();
this.waitScreen.Tag = event2;
this.waitScreen.ShowIcon = false;
this.waitScreen.ShowInTaskbar = false;
this.waitScreen.AutoSize = true;
this.waitScreen.AutoSizeMode = AutoSizeMode.GrowAndShrink;
this.waitScreen.BackColor = SystemColors.Window;
this.waitScreen.ControlBox = false;
this.waitScreen.FormBorderStyle = FormBorderStyle.FixedToolWindow;
this.waitScreen.StartPosition = FormStartPosition.CenterScreen;
this.waitScreen.Cursor = Cursors.WaitCursor;
this.waitScreen.Text = "";
this.waitScreen.FormClosing += new FormClosingEventHandler(this.WaitScreenClosing);
this.waitScreen.Shown += new EventHandler(this.WaitScreenShown);
Label label = new Label();
label.Text = this.message;
label.AutoSize = true;
label.Padding = new Padding(20, 40, 20, 30);
this.waitScreen.Controls.Add(label);
Application.Run(this.waitScreen);
Application.ExitThread();
}
private void WaitScreenClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
}
}
private void WaitScreenShown(object sender, EventArgs e)
{
Form form = (Form)sender;
form.Shown -= new EventHandler(this.WaitScreenShown);
ManualResetEvent tag = (ManualResetEvent)form.Tag;
form.Tag = null;
tag.Set();
}
// Properties
public bool IsShowing
{
get
{
return (this.waitScreen != null);
}
}
}
The way I use it is:
waitScreen = new WaitScreen();
waitScreen.Show("Please wait...");
I have a MainForm, inside a mainform I have a button, when clicked I show a Dialog that on load will get some data from database in a Backgroundworker. Before running the backgroundworker I show my WaitScreen.
Its working great but when the WaitScreen is displayed and If I click on the back Dialog then the WaitScreen is gone. So I want to block so I can't click on the back Dialog until the worker has finish and then I close my WaitScreen.
Any clue on how to do that?
Thanks a lot.
I think you are overcomplicating this. What you want is a modal dialog window which the user can't close which will close when a given task is finished.
You can make a standard class which derives from Form and implements a constructor or a property which you can pass a Task or a callback.
Here's what the code could look like in your WaitScreen form:
public partial class WaitScreen : Form
{
public Action Worker { get; set; }
public WaitScreen(Action worker)
{
InitializeComponent();
if (worker == null)
throw new ArgumentNullException();
Worker = worker;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
Task.Factory.StartNew(Worker).ContinueWith(t => { this.Close(); }, TaskScheduler.FromCurrentSynchronizationContext());
}
}
Here's what your code would look like in the consumer of this WaitScreen form:
private void someButton_Click(object sender, EventArgs e)
{
using (var waitScreen = new WaitScreen(SomeWorker))
waitScreen.ShowDialog(this);
}
private void SomeWorker()
{
// Load stuff from the database and store it in local variables.
// Remember, this is running on a background thread and not the UI thread, don't touch controls.
}
You probably want to use FormBorderStyle.None in the WaitScreen so that the user can't close it. Then the task is complete the WaitScreen will close itself and the caller will continue execution of code after the ShowDialog() call. ShowDialog() blocks the calling thread.
Hey everyone, can someone let me know what they see wrong with this code ?
it throws "Cross-thread operation not valid" exception, on
_DialogueThread.Start();
but if i remove "owner" from
_progressDialogue = new Progresser{Owner = _owner, StartPosition = FormStartPosition.CenterParent};
the exception wont be thrown but the progressDialouge will be shown then hidden right away .
now i understand why this the error is thrown if i set the progressDialouge.Owner to a parent form that was created on a different thread. but why dose the form disappears when i dont ? what am i doing wrong ?
thanks
class Sampleer : BackgroundWorker
{
private Progresser _progressDialogue;
private Thread _DialogueThread;
private Form _owner;
private bool _SampleSuccess;
public Sampleer(Form owner)
{
_owner = owner;
_progressDialogue = new Progresser{Owner = _owner, StartPosition = FormStartPosition.CenterParent};
_progressDialogue.Closed += ProgressDialogueClosed;
WorkerReportsProgress = true;
WorkerSupportsCancellation = true;
DoWork += Sampleer_DoWork;
RunWorkerCompleted += Sampleer_RunWorkerCompleted;
ProgressChanged += Sampleer_ProgressChanged;
}
private void Sampleer_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//UPDATE STATUS CODE IS HERE
}
void ProgressDialogueClosed(object sender, EventArgs e)
{
CancelAsync();
Dispose();
}
void Sampleer_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//FINISH PROCESS
}
void Sampleer_DoWork(object sender, DoWorkEventArgs e)
{
_DialogueThread = new Thread(_progressDialogue.Show);
_DialogueThread.Start();
//DO LONG PROCESS HERE
}
}
In your action (button click), i would create the progress dialog, and then fire off the background worker. The background worker then reports back to the dialog in the ProgressChanged event.
public partial class MainWindow : Window {
private void btnDoSomething_Click(object sender0, RoutedEventArgs e0) {
_progressDialogue = new Progresser{Owner = _owner, StartPosition = FormStartPosition.CenterParent};
_progressDialogue.Closed += ProgressDialogueClosed;
_progressDialogue.Show();
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += delegate(object sender, DoWorkEventArgs e) {
DoSomething();
e.Result = result;
};
worker.ProgressChanged += delegate(object sender, ProgressChangedEventArgs e) {
progressDialogue.Update()
};
worker.RunWorkerCompleted += delegate(object sender, RunWorkerCompletedEventArgs e) {
progressDialogue.Close()
};
worker.RunWorkerAsync(new CustomArgs() {
SomeValue = txtValue.Text,
});
}
}
There are a few mistakes in your approach. Let me point them out one by one.
You inherit BackgroundWorker. That is fine. But you create another thread inside (_DialogueThread). There is no need. DoWork() method runs in a separate thread.
You create/use/manipulate a UI element in another thread. Now, always remember. A Thread never creates a UI element. Its the other way around. A UI element creates a Thread. Progresser in your case should be creating a new Thread or using BackgroundWorker to do any background work you require.
`
Yes decyclone is right, there are many mistakes in the code and in your approach to solution.
You are inherting BackgroundWorker type but subscribing to its own events?
Instead override the methods that are responsible for raising the event in your class.
ex: Instead of subscribing to DoWork, override OnDoWork method.
I've created a sample application, in which a Form (when performing a background task) shows another Form (BackgroundWorkUINotification) and starts the background task in BackgroundWorker thread. The BackgroundWorkUINotification notifies the main form when the Form's CancelButton is clicked.
The main Form when notified, closes the notifier and cancels the background task.
Code below: BackgroundWorkUINotification Form
public partial class BackgroundWorkUINotification : Form
{
public event EventHandler CancelClicked;
public BackgroundWorkUINotification()
{
InitializeComponent();
// call code to animate progress bar..
// probably in another BackgroundWorker that reports progress..
this.cancelButton.Click += HandleCancelButtonOnClick;
}
protected virtual void OnCancelClicked()
{
if (CancelClicked != null)
this.CancelClicked(this, EventArgs.Empty);
}
private void HandleCancelButtonOnClick(Object sender, EventArgs e)
{
this.OnCancelClicked();
}
}
Main Form
public partial class Form1 : Form
{
private BackgroundWorker backgroundWorker;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
this.backgroundWorker = new BackgroundWorker();
this.backgroundWorker.DoWork += HandleBackgroundWorkerOnDoWork;
this.backgroundWorker.RunWorkerCompleted += HandleBackgroundWorkerOnRunWorkerCompleted;
this.backgroundWorker.WorkerSupportsCancellation = true;
}
private void HandleDataRequest()
{
// show UI notification...
BackgroundWorkUINotification backgroundWorkUINotification = new BackgroundWorkUINotification();
backgroundWorkUINotification.CancelClicked += HandleBackgroundWorkUINotificationOnCancelClicked;
backgroundWorkUINotification.Show(this);
// start the background worker
this.backgroundWorker.RunWorkerAsync();
}
private void HandleBackgroundWorkUINotificationOnCancelClicked(Object sender, EventArgs e)
{
// UI notification Form, Cancelled
// close the form...
BackgroundWorkUINotification backgroundWorkUINotification = (BackgroundWorkUINotification)sender;
backgroundWorkUINotification.Close();
// stop the background worker thread...
if (backgroundWorker.IsBusy)
backgroundWorker.CancelAsync();
}
private void HandleBackgroundWorkerOnRunWorkerCompleted(Object sender, RunWorkerCompletedEventArgs e)
{
}
private void HandleBackgroundWorkerOnDoWork(Object sender, DoWorkEventArgs e)
{
// do some work here..
// also check for CancellationPending property on BackgroundWorker
}
}
Some days ago I had the same trouble.
This was helped me: "MSDN. How to: Make Thread-Safe Calls to Windows Forms Controls"
I used first aproach (checking InvokeRequired) because it is easiest way.
Hope it is helpful advise!
I am creating an application and I would like to implement a progress window that appears when a lengthy process is taking place.
I've created a standard windows form project to which I've created my app using the default form. I've also created a new form to use as a progress window.
The problem arises when i open the progress window (in a function) using:
ProgressWindow.ShowDialog();
When this command is encountered, the focus is on the progress window and I assume it's now the window who's mainloop is being processed for events. The downside is it blocks the execution of my lengthy operation in the main form.
If I open the progress window using:
ProgressWindow.Show();
Then the window opens correctly and now doesn't block the execution of the main form but it doesn't act as a child (modal) window should, i.e. allows the main form to be selected, is not centered on the parent, etc..
Any ideas how I can open a new window but continue processing in the main form?
You probably start your lengthy operation in a separate worker thread (e.g. using a background worker). Then show your form using ShowDialog() and on completion of the thread close the dialog you are showing.
Here is a sample - in this I assume that you have two forms (Form1 and Form2). On Form1 I pulled a BackgroundWorker from the Toolbox. Then I connected the RunWorkerComplete event of the BackgroundWorker to an event handler in my form. Here is the code that handles the events and shows the dialog:
public partial class Form1 : Form
{
public Form1() {
InitializeComponent();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
Thread.Sleep(5000);
e.Result = e.Argument;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
var dlg = e.Result as Form2;
if (dlg != null) {
dlg.Close();
}
}
private void button1_Click(object sender, EventArgs e) {
var dlg = new Form2();
this.backgroundWorker1.RunWorkerAsync(dlg);
dlg.ShowDialog();
}
}
I implemented something very similar to this for another project. This form allows you to popup a modal dialog from within a worker thread:
public partial class NotificationForm : Form
{
public static SynchronizationContext SyncContext { get; set; }
public string Message
{
get { return lblNotification.Text; }
set { lblNotification.Text = value; }
}
public bool CloseOnClick { get; set; }
public NotificationForm()
{
InitializeComponent();
}
public static NotificationForm AsyncShowDialog(string message, bool closeOnClick)
{
if (SyncContext == null)
throw new ArgumentNullException("SyncContext",
"NotificationForm requires a SyncContext in order to execute AsyncShowDialog");
NotificationForm form = null;
//Create the form synchronously on the SyncContext thread
SyncContext.Send(s => form = CreateForm(message, closeOnClick), null);
//Call ShowDialog on the SyncContext thread and return immediately to calling thread
SyncContext.Post(s => form.ShowDialog(), null);
return form;
}
public static void ShowDialog(string message)
{
//Perform a blocking ShowDialog call in the calling thread
var form = CreateForm(message, true);
form.ShowDialog();
}
private static NotificationForm CreateForm(string message, bool closeOnClick)
{
NotificationForm form = new NotificationForm();
form.Message = message;
form.CloseOnClick = closeOnClick;
return form;
}
public void AsyncClose()
{
SyncContext.Post(s => Close(), null);
}
private void NotificationForm_Load(object sender, EventArgs e)
{
}
private void lblNotification_Click(object sender, EventArgs e)
{
if (CloseOnClick)
Close();
}
}
To use, you'll need to set the SyncContext from somewhere in your GUI thread:
NotificationForm.SyncContext = SynchronizationContext.Current;
Another option:
Use ProgressWindow.Show() & implement the modal-window behavior yourself. parentForm.Enabled = false, position the form yourself, etc.