VB.Net and splash screens
Posted
Tue, Jan 20 2009 10:27
by
bill
One of the nice things about the My application framework in VB.NET is the ability to easily show splash screens. The splash screen is displayed using a separate thread and by default will close when your main form’s Load event is called. There is however a quirk with it sometimes.
Whilst testing, if I launched the application from Explorer, the splash screen would appear in front of the explorer window but the main form behind it. When the splash screen closed, the main form didn’t come to the foreground. This behaviour would depend on the system being tested and seems to only consistently show up when run from a VPC. The fix for this is reasonably simple: you just need to call Activate in your main form’s Load event
Private Sub Me_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
Me.Activate()
End Sub
Kudos to the VB team for the fix :)
When the VB team suggested this fix to me, I could have sworn I had already tried that. I had also looked into the my application framework code and saw it called Activate in it’s handler of the Load. So my first thoughts on it was it was a race condition, and hence I set up a single shot timer to call Activate, the timer being set to trigger a couple of hundred milliseconds after the load event. At first I thought that fixed it, but that was the “sometimes” effect tricking me <g>
Kevin from the VB team suggested I try Activate in the Load event, and sure enough it seems to work *ALL* the time. But this was even more puzzling as to why didn’t my timer approach work. So I tried overriding the OnLoad method :
Protected Overrides Sub OnLoad(ByVal e As EventArgs)
MyBase.OnLoad(e)
Me.Activate()
End Sub
That actually fails ( yes “sometimes”). But if you change that to:
Protected Overrides Sub OnLoad(ByVal e As EventArgs)
Me.Activate()
MyBase.OnLoad(e)
End Sub
Then it works. So the key is that Activate has to be called before the call to OnLoad completes. Suddenly the pieces of the puzzle all fit together.
What I think is happening is this: The my application framework hooks into the Load event and uses that to dispose of the splash screen. The problem occurs when the splash screen which has the UI input is closed before the call to Activate is made. Because the calling thread doesn’t have the UI Input, SetForeGround window doesn’t work. Based on this hypothesis, I suggested a fix to the WindowsFormsApplicationBase. Currently is has this method in it:
'''**************************************************************************
''' ;HideSplashScreen
''' <summary>
''' Hide the splash screeen. The splash screen was created on another thread so marshal
''' the call over to it.
''' </summary>
''' <remarks></remarks>
<EditorBrowsable(EditorBrowsableState.Advanced)> Protected Sub HideSplashScreen()
SyncLock m_SplashLock 'We can get called from two threads at once
If m_SplashScreen IsNot Nothing AndAlso Not m_SplashScreen.IsDisposed Then
Dim TheBigGoodbye As New DisposeDelegate(AddressOf m_SplashScreen.Dispose)
m_SplashScreen.Invoke(TheBigGoodbye)
m_SplashScreen = Nothing
End If
If Me.MainForm IsNot Nothing Then
Call New System.Security.Permissions.UIPermission(UIPermissionWindow.AllWindows).Assert()
Me.MainForm.Activate()
System.Security.PermissionSet.RevertAssert() 'CLR also reverts if we throw or when we return from this function
End If
End SyncLock
End Sub
My suggestion is to activate the main form before disposing of the splash screen :
<EditorBrowsable(EditorBrowsableState.Advanced)>
Protected Sub HideSplashScreen()
SyncLock m_SplashLock
'We can get called from two threads at once
If Me.MainForm
IsNot Nothing Then
Call New System.Security.Permissions.UIPermission(UIPermissionWindow.AllWindows).Assert()
Me.MainForm.Activate()
System.Security.PermissionSet.RevertAssert() 'CLR also reverts if we throw or when we return from this function
End If
If m_SplashScreen IsNot Nothing AndAlso Not m_SplashScreen.IsDisposed Then
Dim TheBigGoodbye As New DisposeDelegate(AddressOf m_SplashScreen.Dispose)
m_SplashScreen.Invoke(TheBigGoodbye)
m_SplashScreen = Nothing
End If
End SyncLock
End Sub
Hopefully that will fix this issue completely.
In the meanwhile, simply call Activate in your main form’s Load event :)