Monday, August 15, 2011

Changing Application.RootVisual at runtime

If you are a Silverlight Island programmer, this will not applicable to you because you will never face a situation which demands to change the Application.RootVisual property. Don’t confuse the word Island programmer ,it simply refers to the programmers who are creating small sized Silverlight applications mainly with some animations to fit into a small portion of HTML page which may be static or emitted by technologies such as ASP.Net or PHP.

Sometimes I feel that the Silverlight is more suited to Island programs than full window business apps. There are so many reasons.One is its rich user interface capabilities.This is more needed for glazy island apps than business apps. When we talk about great user experience there is always some cost attached to it which reduces performance. The performance is more important when we come to business apps than the user interface. In some of the tests we could see that the UI rendering takes more time than the WCF service in the whole display cycle. This happens when the data elements are huge in number but small and has complex data templates.

Ok.Coming back to changing Application.RootVisual multiple times at runtime. There may be scenarios where you need to show your authentication UI first and then load your application XAPs and the actual UI pages. In this case you need to change the RootVisual for sure.But this is not supported by the Silverlight runtime. First of all if you simply change, it will not change.You will get the below exception when you try to set null first and assign another visual after that.

Invalid value set for application's RootVisual

ie the below code will result into System.InvalidOperationException' in System.Windows.dll

'This will result in System.InvalidOperationException occurred in System.Windows.dll
Application.Current.RootVisual = Nothing
Application.Current.RootVisual = New TextBox With {.Text = "changed"}

So what is the simple solution.Keep a ContentControl as your RootVisual and set your Login UI and Application UI pages as its content.In the Application_Startup event set a ContentControl as your RootVisual then add initial UI to that. Later cast the RootVisual as ContentControl and change its Content property.

Private Sub Application_Startup(ByVal o As Object, ByVal e As StartupEventArgs) Handles Me.Startup
'Initial UI.Here its button
Dim btnChangeRootVisual As New Button() With {.Content = "Change RootVisual"}
AddHandler btnChangeRootVisual.Click, AddressOf btnChangeRootVisual_Click

'ContentControl as RootVisual
Me.RootVisual = New ContentControl() With {.Content = btnChangeRootVisual}
End Sub
Private Sub btnChangeRootVisual_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
'Get the ContentControl and assign new UI to its content property
Dim rootContentControl As ContentControl = DirectCast(Application.Current.RootVisual, ContentControl)
rootContentControl.Content = New TextBox With {.Text = "changed"}
End Sub

This is just a hack for real business application programmers of Silverlight.


Bob & Michelle Hollister said...

I was creating a new instance of a UI element and setting it to that but getting a black screen. Your approach worked perfectly. Thanks!!! :-)

DBLWizard said...

This works great except I can't seem to adjust the Height/Width of the page. It opens in a small frame and none of the settings seem to have any effect. Any thoughts?

DBLWizard said...

I should clarify this ... none of the "auto" or xaml settings seem to have any effect. I can set the Height/Width in the constructor but I want the control to "fill" the page and where as this worked before using the "ContentControl" now those settings seem to be ignored.

Joy George said...

Hi DBLWizard,
What you mean by control to fill the page?

Did you try changing the horizontal & vertical alignments to stretch ?

DBLWizard said...

Yeah, It must be something I have somewhere in one of the Styles defined for one of the elements. I tried to create a simple example and I can't recreate the behavior.

I've added something like this in all the controls/pages:
d:DesignWidth="400" d:DesignHeight="250" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"

But its not having the desired effect. I'll post the culprit once I figure it out.

Vinícius Benitez Maretti said...

Instead of using a content control i'm using a Grid.

Grid grid = new Grid();
This.RootVisual = grid;
grid.Children.Add(new MainPage());

When i want to change my UI i do this
var grid = App.Current.RootVisual as Grid;
grid.Children.Add(new MyNewMainPage());

But i get this error:
Element is already child of another element.

I've tested to load MyNewMainPage directly on visual root an it worked.

Do you have any idea?

Joy George said...

This really seems wired to me.Are you sure you are creating new object everytime as follows?

grid.Children.Add(new MyNewMainPage());

Right now ,I am not in a position to test this in a sample.You may try clearing the children instead of removal etc...which may work

Daniel Schlieckmann said...

I took a Frame instead of a ContentControl. That solved the problem with the Width/Height.
But great hint - this solved my problem that I wanted to show a start page while loading a few things!

Joy George said...

Hi Daniel,

Thanks for sharing your experience.