Avoid using MessageBox.Show() to debug this. It pumps a message loop, disturbing the normal flow of events. The Load event is triggered by Windows sending the WM_SHOWWINDOW message, just before the window becomes visible. There is no Windows notification for “your window is now fully shown”, so the WF designers came up with a trick to generate the Shown event. They use Control.BeginInvoke(), ensuring the OnShown() method gets called as soon as the program goes idle again and re-enters the message loop.
This trick has lots of other uses, particularly when you have to delay the execution of code started by an event. However, in your case it falls apart because you use MessageBox.Show(). Its message loop dispatches the delegate registered with BeginInvoke(), causing the Shown event to run before the window is shown.
There are lots of other ways to get diagnostics beyond MessageBox. Debug.Print() and Console.WriteLine() are handy, their output goes to the Visual Studio Output Window without having any detrimental effects on the normal event firing sequence. A simple breakpoint can do wonders too.