Search code examples
c#silverlightapp-startup

detect when Application.Current.RootVisual is set (Silverlight)


Assume I am making an assembly (WindowsPhoneClassLibrary or PortableClassLibrary) for a Windows Phone Application (Silverlight).

Is there a way for me to automatically detect/register/subscribe for the moment where Application.Current.RootVisual will be not null?

My current (not working because of Exception) approach is in the idea of:

var rootVisualTask = new TaskCompletionSource<UIElement>();
var application = Application.Current;
TaskEx.Run(() =>
{
    while (application.RootVisual == null)
        Thread.Sleep(1);
    rootVisualTask.TrySetResult(application.RootVisual);
});
var rootVisual = await rootVisualTask.Task;

Edit

Answer to McGarnagle to explain how my assembly gets usually initialized.

In App.xaml.cs:

static readonly PhoneApplicationFrame RootFrame = new PhoneApplicationFrame();
public App()
{
    InitializeComponent();
    RootFrame.Navigated += RootFrame_Navigated;
}
void RootFrame_Navigated(object sender, NavigationEventArgs e)
{
    RootVisual = RootFrame;
    RootFrame.Navigated -= RootFrame_Navigated;
}
void Application_Launching(object sender, LaunchingEventArgs e)
{
    MyPlugin.Start();
}
void Application_Activated(object sender, ActivatedEventArgs e)
{
    MyPlugin.Start();
}

Things happen in this order:

  1. Application.Startup (unused)
  2. Application.Launching (plugin starts)
  3. RootFrame.Navigated (RootVisual is set, but RootFrame is private)

I could require a MyPlugin.HeyRootVisualIsSetAndNowYouCanUseIt() manually inserted after RootVisual = ... but I was trying to avoid that.

Edit

Unlike Obj-C, KVO is not implementable on Fields/Properties you don't own. Which means probably nobody is going to find a better solution.


Solution

  • After experimenting for hours, I found this working:

    var dispatcher = Deployment.Current.Dispatcher;
    var application = Application.Current;
    UIElement rootVisual = null;
    while (rootVisual == null)
    {
        var taskCompletionSource = new TaskCompletionSource<UIElement>();
        dispatcher.BeginInvoke(() =>
            taskCompletionSource.TrySetResult(application.RootVisual));
        rootVisual = await taskCompletionSource.Task;
    }
    

    The Thread.Sleep(1); from the loop of my question is replaced with a call to Deployment.Current.Dispatcher.BeginInvoke() to avoid Exception being thrown.