Friday, February 16, 2007

VB.NET 2005 MDI Docking to Floating Window

Have you ever wanted to create a tool panel that can dock on your GUI and free float as a tool window anywhere no the screen?

You may think that you can just create a child window and set its mdi parent and dock it to the left. But there are issues with this approach when you make it floating and try to redock it. Especially if you have a child window that is using that area of the screen. What I propose here is to to use panels to pull off the trick.



The first step is to create an MDI Parent window. Just create a new project in vs2005 (VB or C#) and set the "IsMdiContainer" property to True. Go ahead and add a menustrip and add a view option with sub options of Left, Right, Floating.

Next, we want to add a panel to the form and dock it. Go ahead and add panel1. Dock it to the left and throw a textbox on it for testing. Write the code in the menu events to change the dock property of your form. Leave the floating event blank. We'll get to that... If you run your app, you should be able to switch the docking to left and right.

Create a new form called frmTools. This window will be the container for our panel when we want it to float. Change the FormBorderStyle to SizableToolWindow. Now hit F7 to go to the code view. Add 2 member variable. 1 panel called mPanel and 1 Form1 call mParent. Modify the constructor of frmTools so that you can pass in a reference to Form1 and a reference to a panel. Set these variables before "Initializecomponent."

Next, create a private function call InitPanel(). What we'll do here is add the panel to the Controls Collection so that it can be rendered. You'll also want to set the Dock property to DockStyle.Fill and remember to use the SuspendLayout() and ResumeLayout(). Essentially, what we've done is create a secondary "Initializecomponent" function. I wouldn't recommend changing the "Initializecomponent" to do this code as it could cause issues during design time in Visual Studio 2005 designer. Once you've created this function call it in your constructor function after "InitializeComponent()."

Now, lets go back to Form1. In the event for View->Floating we'll create the frmTools window and call Show().

private void floatingToolStripMenuItem_Click(object sender, EventArgs e)
{
Form1 form = this;
frmTools tools = new frmTools(ref form, ref panel1);

this.SuspendLayout();
this.Controls.Remove(this.panel1);
this.ResumeLayout(false);
this.PerformLayout();
tools.Show();

}

Notice that I created a form and set it to "this" and then passed the reference to form into the function. There are other ways of referencing the main form. This was just a quick and dirty way. Maybe we'll get into that on another day. :)
In addition, we removed the item from the controls collection. Now, in testing I was able to remove this code completely and got the same results but lets play it safe. (BTW: If you know why this is so, please comment on the blog, thanks.)
Anyway, if you run your application you get your panel to float! Not to bad. One problem remains though. If you close the tool window your panel is now gone. Its been destroyed with the frmTool Control collection. So, we need to let the Parent window know that we are done with the panel. And we have to code a function in the parent window so that it can reinitialize the panel.
In Form1, create a new function called SetToolPanel(ref Panel panel).

public void SetToolPanel(ref Panel panel)
{
this.SuspendLayout();
this.panel1 = panel;
//set the dockstate to default.
panel1.Dock = DockStyle.Left;

this.Controls.Add(this.panel1);
this.Controls.SetChildIndex(this.panel1, 0);
this.ResumeLayout(false);
this.PerformLayout();
}

What we're doing here is something similar to "InitializeComponent()." We're just adding this control back in. Notice that I called "Controls.SetChildIndex." I did this because if you don't have these controls in the right order it could affect other docking controls. If you were to comment out this line and test, it would mess up the docking on your menustrip. So, remember to set it properly in your app.
Next, we need to modify the frmTools window so that it passes this panel back to the parent when its closing. In the properties window of frmTools, click the Events lightning bolt and override the "FormClosing" event under "Behavior." Call it onClosing. In this function we're only going to call "SetToolPanel" and remove the panel from the control collection so it doesn't get wiped out.

private void onClosing(object sender, FormClosingEventArgs e)
{
//Pass it off to the parent.
mParent.SetToolPanel(ref mPanel);
//Remove the control so it doesn't get ditched.
this.Controls.Remove(mPanel);
}
Thats it. Running the program you'll have a floatable dockable panel for your app. While testing, type something in the panel's textbox to show that its the same panel when it cycles through its states.
If you need source code let me know...
Happy Coding!
Jas