I'm creating an application using Caliburn.Micro. The application communicates with an api during startup which is why I need to show a Splashscreen to the users. I've created my own animated Splashscreen as a Window which is activated from bootstrapper in the OnStartup method.
The startup process is managed by the splashscreens viewmodel.
When all startup related processes are finished how do I tell the bootstrapper to close the splashscreen and activate another window?
I thought about raising an event but I cannot subscribe the bootstrapper to the IEventaggregator.
I tried displaying the Splashscreen inside of a contentcontrol in the ShellView and just switch to a different vm after the loading is done. The problem here is that the splash should be displayed on a transparent, borderless window which cannot be changed after the window is created.
public class Bootstrapper : BootstrapperBase
{
private SimpleContainer _container = new SimpleContainer();
public Bootstrapper()
{
Initialize();
}
protected override void Configure()
{
_container
.Singleton<IWindowManager, WindowManager>()
.Singleton<IEventAggregator, EventAggregator>();
GetType().Assembly.GetTypes()
.Where(type => type.IsClass)
.Where(type => type.Name.EndsWith("ViewModel"))
.ToList()
.ForEach(viewModelType => _container.RegisterPerRequest(
viewModelType, viewModelType.ToString(), viewModelType));
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
FrameworkElement.LanguageProperty.OverrideMetadata(
typeof(FrameworkElement),
new FrameworkPropertyMetadata(
XmlLanguage.GetLanguage(
CultureInfo.CurrentCulture.IetfLanguageTag
)
)
);
DisplayRootViewFor<AnimatedSplashViewModel>();
//DisplayRootViewFor<ShellViewModel>();
}
protected override object GetInstance(Type service, string key)
{
return _container.GetInstance(service, key);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return _container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
}
public class AnimatedSplashViewModel : Screen
{
private IEventAggregator _events;
private string _splashMessage;
public string SplashMessage
{
get { return _splashMessage; }
set
{
_splashMessage = value;
NotifyOfPropertyChange(() => SplashMessage);
}
}
public AnimatedSplashViewModel(IEventAggregator events)
{
_events = events;
SplashMessage = "Please wait";
// Simulation of long tasks
var worker = new BackgroundWorker();
worker.DoWork += Worker_DoWork;
worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
worker.RunWorkerAsync();
}
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_events.PublishOnUIThread(new SplashFinishedEvent());
}
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(10000);
}
}
You should either use a ShellViewModel for the root view and replace the splash screen view with a "main" view, or you could just wait to display the root view until the splash screen has been closed:
protected override void OnStartup(object sender, StartupEventArgs e)
{
Application.ShutdownMode = ShutdownMode.OnExplicitShutdown;
var windowManager = IoC.Get<IWindowManager>();
var eventAggregator = IoC.Get<IEventAggregator>();
windowManager.ShowDialog(new AnimatedSplashViewModel(eventAggregator));
DisplayRootViewFor(typeof(ShellViewModel));
}
...
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
TryClose();
}
I case anyone wondering about the final solution:
First I used the WindowManager to create a Dialog of the Splashscreen and let the SplashscreenViewModel do all the work.
Turned out this approach takes ages to load. So when I tried to execute it took around 8 seconds for the Dailog to show up. This is far too long for my impatient users.
I think this was because I used IoC to inject alot of dependecies into the SplashscreenViewModel.
windowManager.ShowDialog(new AnimatedSplashViewModel(locationEndpoint, userEndpoint, applicationEndpoint, adUser, clientInfo, locationInfo, loggedInUser));
Second approach was to create the Splashscreen as a dialog and use a BackgroundWorker for all the computing and api stuff inside the Bootstrapper.
While this worked quite fast I felt that there must be a better approach.
Third and final solution:
The Bootstrapper calls the ShellViewModel.
public Bootstrapper()
{
Initialize();
DisplayRootViewFor<ShellViewModel>();
}
In the OnInitialize method I've created a BackgroundWorker which executes all the long running tasks while displaying the SplashScreen as a Dialog using the WindowManager.
protected override void OnInitialize()
{
var windowManager = new WindowManager();
using (BackgroundWorker bw = new BackgroundWorker())
{
bw.DoWork += InitializeApplication;
bw.RunWorkerCompleted += InitializationCompleted;
bw.RunWorkerAsync();
windowManager.ShowDialog(new AnimatedSplashViewModel(_events));
}
}
The AnimatedSplashscreenViewModel now only requires one dependency which is the EventAggregator. I let it handle a custom Event named SplashMessageChangedEvent.
public class SplashMessageChangedEvent
{
public string Content { get; set; }
public bool CloseDialog { get; set; } = false;
public SplashMessageChangedEvent(string content)
{
Content = content;
}
public SplashMessageChangedEvent(bool closeDialog)
{
CloseDialog = closeDialog;
}
}
In the InitializationCompleted Event in the ShellViewModel I publish the following event to close the Dialog:
private void InitializationCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_events.PublishOnUIThread(new SplashMessageChangedEvent(true));
}
Now this final approach is much faster than the other two.
The Splashscreen is shown instantly after starting the executable.
Related
I've created a TimerManager class for my WPF application.
This class handles the start and stop the dispatcher timer.
Here is the class:
public static class TimerManager
{
static DispatcherTimer disTimer;
static Model m = Model.GetInstance();
static TimerManager()
{
disTimer = new DispatcherTimer();
disTimer.Tick += disTimer_tick;
disTimer.Interval = new TimeSpan(0, 0, 1);
}
public static void StartTimer()
{
disTimer.Start();
}
public static void StopTimer()
{
disTimer.Stop();
}
private static void disTimer_tick(object sender, EventArgs e)
{
m.Tick++;
}
}
And I've created a Model class that represents the ticking in the UI.
(Binding in MainWindow.xaml -> xy textbox text field "{Binding Tick}").
class Model : INotifyPropertyChanged
{
private Model()
{
}
static Model instance;
public static Model GetInstance()
{
if (instance == null)
{
instance = new Model();
}
return instance;
}
int tick;
public event PropertyChangedEventHandler PropertyChanged;
public void OnNotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
public int Tick
{
get
{
return tick;
}
set
{
tick = value;
OnNotifyPropertyChanged();
}
}
}
And here is the MainWindow class:
Model m;
public MainWindow()
{
InitializeComponent();
m = Model.GetInstance();
this.DataContext = m;
}
private void startButton_Click(object sender, RoutedEventArgs e)
{
ThreadPool.QueueUserWorkItem(o =>
{
TimerManager.StartTimer();
});
//TimerManager.StartTimer();
}
private void stopButton_Click(object sender, RoutedEventArgs e)
{
TimerManager.StopTimer();
}
When I click the start button I use the ThreadPool.QueueUserWorkItem() method. In that method, I start the timer but the timer tick is not run at every one second.
When I don't use ThreadPool this works. But this solution is not good for me; ThreadPool is important for me because I use an HTTP web server (in local).
My question is: why is the ticking not working if I use ThreadPool?
The DispatcherTimer object has thread affinity. That is, it is tied to a specific thread. In particular, it is designed specifically to raise its Tick event in the thread in which it was created, using the Dispatcher for that thread.
Your ThreadManager class's static constructor will be called when the type is first used. In your non-working example, this occurs in the queued work item method, causing the static constructor to be executed in the thread pool thread used to execute that work item method. This in turn causes the DispatcherTimer object you create to be owned by that thread, and to have its Tick event raised in that thread by the Dispatcher for that thread.
Except, thread pool threads don't have Dispatchers. So there's no Dispatcher there to raise the Tick event for the DispatcherTimer object. Even if there was, without a call to Application.Run() to have the dispatcher loop executed, the Dispatcher wouldn't actually get to dispatch anything, including the Tick event.
What you need is to make sure that when you create the DispatcherTimer object, the code that creates that object is executed in the dispatcher thread, which is your main UI thread.
There are a couple of ways to do that. IMHO, the best way is to make your ThreadManager class not a static class and to create an instance of it in your MainWindow constructor. For example:
class TimerManager
{
DispatcherTimer disTimer;
Model m = Model.GetInstance();
public TimerManager()
{
disTimer = new DispatcherTimer();
disTimer.Tick += disTimer_tick;
disTimer.Interval = new TimeSpan(0, 0, 1);
}
public void StartTimer()
{
disTimer.Start();
}
public void StopTimer()
{
disTimer.Stop();
}
private void disTimer_tick(object sender, EventArgs e)
{
m.Tick++;
}
}
and:
public partial class MainWindow : Window
{
TimerManager _timerManager = new TimerManager();
public MainWindow()
{
InitializeComponent();
this.DataContext = Model.GetInstance();
}
private void startButton_Click(object sender, RoutedEventArgs e)
{
ThreadPool.QueueUserWorkItem(o =>
{
_timerManager.StartTimer();
});
}
private void stopButton_Click(object sender, RoutedEventArgs e)
{
_timerManager.StopTimer();
}
}
Since you know your MainWindow object has to be created in the dispatcher thread, and you know that non-static field initialization happens at the same time the constructor is called, in that same dispatcher thread, the above ensures that your TimerManager object is created in the dispatcher thread.
This gives you complete control over the lifetime of the TimerManager object, particularly when it's created but of course also when it can be discarded. Given the nature of the DispatcherTimer object itself, it's my opinion that this is better than maintaining a statically-held instance.
This approach also gives you the option of having a manager object for each dispatcher thread (in rare cases, a program might have more than one…you should try very hard to avoid getting into that situation, but it can be useful for types to at least be compatible with such a situation).
That said, if you really want to keep the static implementation, you can do that by providing a method that can be called explicitly when you want to initialize the class, so you can make sure that the initialization happens in the right thread:
static class TimerManager
{
static DispatcherTimer disTimer;
static Model m = Model.GetInstance();
public static void Initialize()
{
disTimer = new DispatcherTimer();
disTimer.Tick += disTimer_tick;
disTimer.Interval = new TimeSpan(0, 0, 1);
}
public static void StartTimer()
{
disTimer.Start();
}
public static void StopTimer()
{
disTimer.Stop();
}
private static void disTimer_tick(object sender, EventArgs e)
{
m.Tick++;
}
}
Then in your MainWindow class:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = Model.GetInstance();
StaticTimerManager.Initialize();
}
private void startButton_Click(object sender, RoutedEventArgs e)
{
ThreadPool.QueueUserWorkItem(o =>
{
StaticTimerManager.StartTimer();
});
}
private void stopButton_Click(object sender, RoutedEventArgs e)
{
StaticTimerManager.StopTimer();
}
}
All you need to do here is make sure you call the Initialize() method from the main UI thread where you actually have a running dispatcher, before you attempt to call either of the other two static methods in the class.
This approach could also be made to work with multiple threads (i.e. if you have more than one dispatcher thread), but it would be trickier, especially if you want to be able to call the StartTimer() method from a different thread that actually owns the timer object. I'd recommend against the static class approach if you really did wind up in that situation.
I created a Service in C#. I needed a GUI for configuration of the Service so I added a WinForms project to my solution. My plans was to create the Form in the Service and show it in the OnStart() Method of the Service. However, it won't show. The WriteEntry() Methods of the EventLog are all firing, so my code definitely is processed. Anybody know what I'm doing wrong here?
public partial class UrlWatcherService : ServiceBase
{
private UrlWatcherForm _urlwatcherform;
private EventLog _eventLog;
private string _eventLogName = "UrlWatcherEventLog";
private string _eventLogSource = "UrlWatcherSource";
public UrlWatcherService()
{
InitializeComponent();
LoadVariables();
}
public void OnDebug()
{
OnStart(null);
}
private void LoadVariables()
{
_urlwatcherform = new UrlWatcherForm();
_eventLog = new EventLog();
CanPauseAndContinue = true;
if (!EventLog.SourceExists(_eventLogSource))
EventLog.CreateEventSource(_eventLogSource, _eventLogName);
_eventLog.Source = _eventLogSource;
_eventLog.Log = _eventLogName;
_eventLog.WriteEntry("Url Watcher Log Created", EventLogEntryType.Information);
}
protected override void OnStart(string[] args)
{
_eventLog.WriteEntry("Url Watcher Service Started", EventLogEntryType.Information);
_urlwatcherform.Show();
_eventLog.WriteEntry("Url Watcher Form Created", EventLogEntryType.Information);
}
protected override void OnPause()
{
base.OnPause();
_eventLog.WriteEntry("Url Watcher Service Paused", EventLogEntryType.Information);
}
protected override void OnContinue()
{
base.OnContinue();
_eventLog.WriteEntry("Url Watcher Log Continued", EventLogEntryType.Information);
}
protected override void OnStop()
{
_eventLog.WriteEntry("Url Watcher Service Stopped", EventLogEntryType.Information);
}
}
public partial class UrlWatcherForm : Form
{
public UrlWatcherForm()
{
InitializeComponent();
}
private void btnAdd_Click(object sender, EventArgs e)
{
}
private void UrlWatcherGui_Resize(object sender, EventArgs e)
{
if (FormWindowState.Minimized == WindowState)
Hide();
}
private void UrlWatcherGui_FormClosing(object sender, FormClosingEventArgs e)
{
Hide();
e.Cancel = true;
}
private void urlWatcherNofiyIcon_MouseDoubleClick(object sender, MouseEventArgs e)
{
Show();
}
}
EDIT: To clarify, if I debug it like below, the Form shows. I can put the thread to sleep but that won't let me interact with the Form anymore. But the Form definitely shows, it's just in an unresponsive state.
static void Main()
{
#if DEBUG
UrlWatcherService service = new UrlWatcherService();
service.OnDebug();
#else
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new UrlWatcherService()
};
ServiceBase.Run(ServicesToRun);
#endif
}
OK due to Steve's hint, I split the projects up. I use a merged module as per this MSDN article, and instead of referencing the GUI in the Service project, I separated them so I can put both their project outputs in the merged module. I then add the merged module to my installer and now I have the service running after install and the ability to call the form from my start menu. It's not what I originally wanted, but a very plausible alternative.
Thanks for Steve for the hint.
In my application I was using SystemEvents to add objects to an ObservableCollection (code shortened for this example)
public partial class App : Application
{
private ObservableCollection<StateChanged> _messages = new ObservableCollection<StateChanged>();
public ObservableCollection<StateChanged> messages { get { return _messages; } }
protected override void OnStartup(StartupEventArgs e)
{
SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
}
private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
messages.Insert(0, new StateChanged(DateTime.Now, State.Logoff));
}
}
Above code works without a problem.
Because I do not only have to handle SessionSwitch events, but also SessionEnding etc. I wrote a small class that should raise a 'unified' event for some of the SystemEvents (again shortened)
public class SystemEventArgs : EventArgs
{
public State newState { get; set; }
}
public delegate void SystemEventHandler(object sender, SystemEventArgs e);
class SystemEventCollector
{
public event SystemEventHandler SessionEvent;
protected virtual void RaiseSystemEvent(SystemEventArgs e)
{
SystemEventHandler handler = this.SessionEvent;
if (handler != null)
handler(this, e);
}
public SystemEventCollector()
{
SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
}
protected void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
SystemEventArgs ea = new SystemEventArgs();
ea.newState = State.Unknown;
RaiseSystemEvent(ea);
}
}
When I instanciate this class in my Application and subscribe to the SessionEvent, doing the same stuff, like this
public partial class App : Application
{
private ObservableCollection<StateChanged> _messages = new ObservableCollection<StateChanged>();
public ObservableCollection<StateChanged> messages { get { return _messages; } }
private SystemEventCollector _sysEventCollector = new SystemEventCollector();
protected override void OnStartup(StartupEventArgs e)
{
_sysEventCollector.SessionEvent += OnSessionEvent;
}
private void OnSessionEvent(object sender, SystemEventArgs e)
{
messages.Insert(0, new StateChanged(DateTime.Now, e.newState));
}
}
The messages.Insert() call raises an exception
This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
I do understand that I can not update a GUI element from another thread than the one it was created on and have worked around this problem by using the extension method mentioned in this SO answer.
My question is to why this happens? My assumptions are that events are handled on the same thread as they are raised, so why is there a difference between handling the SessionSwitch event directly and my approach of raising an event when handling the SessionSwitch event? How are the SystemEvents different from my event? Do I have the correct solution to the problem? Is there a simpler solution?
From some testing it seems that the error lies in the non-working code is the instanciation of SystemEventCollector object.
MS does all the necessary marshalling in their SessionEvents.*** handlers, this is why the first example works without problems. In the non-working code SystemEventCollector is no instanciated in the OnStartup function (which is called from the UI thread) but basically with the constructor. When marshalling from the SessionEvents is done, it goes to the wrong thread, leading to the problem.
Apart from my original solution, the problem can also be solved by instanciating the SystemEventCollector in the OnStartup function.
protected override void OnStartup(StartupEventArgs e)
{
_sysEventCollector = new SystemEventCollector();
_sysEventCollector.SessionEvent += OnSessionEvent;
}
I have a listbox with filenames. When the selected index is changed I load the file.
I want something like jQuery's HoverIntent that delays the action of loading the file for a short time so the user can use the down arrow and quickly cycle through the items in the list without the application trying to load each one. Thread.Sleep pauses the whole app so a user can't select another list item until the sleep completes, this is obviously not what I want.
This will work if your using WinForms, make a call to the InitTimer method in the Form constructor.
Load the file in the _timer_Tick event handler. To change the delay set the Interval property in InitTimer to another value.
private System.Windows.Forms.Timer _timer;
private void InitTimer()
{
_timer = new Timer { Interval = 500 };
_timer.Tick += _timer_Tick;
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
_timer.Stop();
_timer.Start();
}
private void _timer_Tick(object sender, EventArgs e)
{
_timer.Stop();
// TODO: Load file here
}
Use Threading to separate the loading from your GUI.
This should get you started:
public partial class MainWindow : Window
{
CancellationTokenSource cts;
bool loading;
private void SelectedIndexChanged(int index)
{
if (loading)
cts.Cancel();
cts = new CancellationTokenSource();
var loader = new Task.Delay(1000);
loader.ContinueWith(() => LoadFile(index))
.ContinueWith((x) => DisplayResult(x));
loader.Start();
}
private void DisplayResult(Task t)
{
// TODO: Invoke this Method to MainThread
if (!cts.IsCancellationRequested)
{
// Actually display this file
}
}
Could not test, as I'm still on .net 4 whereas Task.Delay() is .net 4.5
You may need to add another field in the form for the file content transfer from the tasks to the GUI.
Winforms:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private CancellationTokenSource _cancel;
private object _loadLock = new object();
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
lock (_loadLock)
{
handleCancellation();
var loader = new Task((chosenFileItemInListbox) =>
{
Thread.Sleep(1000);
LoadFile(chosenFileItemInListbox);
}, listBox1.SelectedItem, _cancel.Token);
}
}
private bool handleCancellation()
{
bool cancelled = false;
lock (_loadLock)
{
if (_cancel != null)
{
if (!_cancel.IsCancellationRequested)
{
_cancel.Cancel();
cancelled = true;
}
_cancel = null;
}
}
return cancelled;
}
private void LoadFile(object chosenFileItemInListbox)
{
if (handleCancellation())
{
return;
}
}
}
The code above could also be applied to WPF, but WPF contains some built in magic for handling delays and cancellation of previous updates.
<ListBox SelectedItem="{Binding Path=SelectedFile, Delay=1000}" />
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
}