Creating a Dockable Panel-Controlmanager Using C#, Part 1

Creating a Dockable Panel-Controlmanager Using C#, Part 1

Purpose: To make it easy to have panels which can be docked to the left, right, top, and bottom. It should be easy to add this control to the program and easy to understand how it works, so everybody who wants can adapt its features to his needs. The article is addressed to people who have already read and worked with a starter book about C# such as MS Visual C# 2005 Step by Step so maybe 'C# experts' will find it too overdone in explaining different parts.

Before you get started, you first have to discuss what you want to get and then, step by step, go down the road to achieve this goal. I did this because I need such a tool for my own programs and looking around I only saw much too simple things, which didn't fulfil my needs and much too complicated ones, which are a bit overcrowded for my needs and by that too complicated to what I want to get, and too difficult to adapt them to my needs.

The full construction cannot be done in one article, so this will be a serie of articles. It's adressed to everyone who wants to learn different techniques of programming skills using learning by doing. This first chapter will teach you the following: Attaching three different projects to each other. Creating and using different namespaces, designing an ownerdrawn control, Localisation of usercontrols, including localisation of the controls PropertyBrowser and Errormessages, localiced Discription of Items in the Propertybrowser, Hiding base class Properties in the Propertybrowser, Creating and usage of a TypeConverter, implementing of API calls to a C# program.

But now, start to create all the parts you need to have for an easy docking manager and during learning, you will know what you are doing.

General Design: Decisions

The general design was developed based on the idea to create user-defined panels that are all together connected with a new control named "DockingManager" that is needed to control the docking procedure. All these panels should be User controls, that means inherited from the class Control, by inheriting from the Control class, you have lots of possibilities to use existing other controls like a TabControl or a SplitControl for composing our own ideas. On the other side, for the usage of panels in applications, I would normally prefer to have Forms that I could use to add different controls on it.

But, normal Forms can't be used in a way that you can dock them wherever you want them to be docked. So, what you will try to achieve is a Form that is dockable, in all the ways you want them, but still useable as a Form. For getting this achieved, you will create a new type of Form that you will name DockableForm. The Class of this Form is derived of a Form.

Here is a picture to what you will achieve after some hours of work.

Figure 1: Your end result

As a first step, you have to create two Projects: one for building all the needed parts of our DockingManager and the other project to test the new elements on their workability. So at first, create an Project named DockingControlTestApp that is a standard windows application and a second project that you open in the same editor together with the Test-application. This is a Windows User control application. This you can nameDockingControls.

Right-click the DockingControlTestApp and select it as the Start project. Use this application now:

At first, you rename Form1 as MDIForm. Now in the Properties of that Form, you set IsMdiContainer to true.

Your Panels should basically have similar features as the IDE of Visual Studio 2005 used in C# has. So basically, there should be two possibilities where you could place a docking control:

  1. Anywhere in the MDI Form
  2. Sharing the same docking position with other panels

The following two figures show both these options:

Figure 2: Anywhere in the MDI Form

Figure 3: Sharing (in this case, the Top Position) Docking with another panel

The screenshot was done just when the mouse was on the Docking Button but not docked, so you can see the green Preview window too. It indicates where the window will be docked.

You also should be able to hide all docked windows and use them only when needed. To get this done, you would need tabs where you could re-open hidden windows again only by hovering over the tabs that contain the Caption of the particular Panel.

Figure 4: Tab registers to show hidden Panels again

It also should be possible to resize the Panels for personal needs and it should be possible to store a chosen combination of Panels and to reload them into the same place at the next program's start.

What Is not Part of This Project

The Visual studio IDE, and some of the other Docking managers I have seen, allow you to share the space for a window again and again. For example, you are sharing left docking for two windows and want to have a third window there. Three or more windows on one docking position (such as the Left) are only possible when the panels are drawn into the registerbutton. There, you can have as much as you want and the register itself can be docked on different places, as you see in Figure 1.

This was a short description of what we will get in the end.

Designing the Different Parts of the DockingManager

So, where do you begin? You want to have a control that allows you to position your panels and also hide them. So, you will need to have hidden strips where your Tab Buttons are placed to Show panels again, when they had been hidden.

So, do a simple start and construct these strips as parts of your DockingManager. Because you want to use existing controls for this, you will use MenuStrips here.


Figure 5: MDIForm with four empty toolstrips created by DockingManager

In Figure 5, you can see a menustrip, a toolstrip, and a statusstrip, which are part of the Test Application and built at design time, Also, you notice four empty toolstrips (no buttons on them; they are created dynamically by the docking manager at runtime).

This is your first goal to achieve. You need these toolstrips. Each of them should only be shown when you have docked to a side (left, right, top, or bottom) and the docked panel has just been set to hidden. In this case, the panel is visible only when you hover over the button that represents the docked window. If you set the stick (pin) button of the panel to 'permanent viewable' and no other panel is invisible, this toolstrip and the panelbutton are invisible. But that's later. First, you need to have the docking manager to create these toolstrips.

Keep in mind that there might be existing menus, Toolstrips, and statusstrips already on the screen. To have control over the mechanics of your DockingManager, you now provide your Test application with a menustrip, two Toolstrips, and a statusstrip. The names of them stay as they are: menuStrip1, statusStrip1, toolStrip1, and toolStrip2.

In your DockingControls, you rename the userControl1 to DockingManager.

In this class, which is derived from User Control, you have to add four private fields for the Toolstrips; you will name them Buttonstrips. This should now look like:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Resources;

namespace DockingControls{
   public partial class DockingManager : UserControl {
      private ToolStrip LeftButtonStrip;
      private ToolStrip RightButtonStrip;
      private ToolStrip TopButtonStrip;
      private ToolStrip BottomButtonStrip;

      public DockingManager()  {
         InitializeComponent();
      }
   }
}
Note: I have added some of the necessary 'using namespace' declarations. Everybody who wants to follow this project should already know more or less why and when they are needed. I'm adding here all you will need for this control, independent of in which stage of the program you really will need them. For more detailed explanation on namespaces, see MS 'Help or MSDN.

And now, you can create your ButtonStrips itself. First, create the following function in the Dockingmanager to create the toolstrips:

public void CreateAllElements()
{
   // For testing reasons we set the visibility to true
   // in the DockingManager, it will be false as they are
   // not to be seen when created
   CreateButtonStrip(DockStyle.Left,true  );
   CreateButtonStrip(DockStyle.Right,true );
   CreateButtonStrip(DockStyle.Top,true   );
   CreateButtonStrip(DockStyle.Bottom,true); 
   // here other needed elements will be created too
}

Hm... pretty easy. This reduces the problem on how to create a toolstrip and to place it in the correct position. So, you now only need to define this new Method CreateButtonStrip, which is creating one specific toolstrip. The enumeration DockStyle is the known enumeration used to dock controls in C#. This Method is defined like the following:

private void CreateButtonStrip(DockStyle pos, bool visible)
{
   ToolStrip buttonStrip = CreateButtonStrip(pos);
   if (buttonStrip != null)
      {
         buttonStrip.Visible = visible;
      }
}

Sometimes, I need the Method without the possibility to hide them, so I decided to have two different methods with the same name, but different properties I did the original method, CreateButtonStrip(Dockstyle pos) (the following code segment), the easy way; this means that I dropped a control to a form, copied the code, and then adapted it to my needs.

private ToolStrip CreateButtonStrip(DockStyle pos )
{
   ToolStrip buttonStrip = new ToolStrip();
   buttonStrip.AutoSize = false;
   buttonStrip.GripStyle = ToolStripGripStyle.Hidden;
   if (pos == DockStyle.Left || pos == DockStyle.Right)
   {
      // we need a vertical toolstrip 
      buttonStrip.LayoutStyle = ToolStripLayoutStyle.
      VerticalStackWithOverflow;
      buttonStrip.TextDirection =
         ToolStripTextDirection.Vertical90;
      buttonStrip.Size = new Size(24, 309);
      buttonStrip.Location = new Point(0, 49);
   }
   else
   {
      // Top or bottom are horizontal Toolstrips
      buttonStrip.LayoutStyle = ToolStripLayoutStyle.
         WHorizontalStackWithOverflow;
      buttonStrip.TextDirection = ToolStripTextDirection.Horizontal;
      buttonStrip.Size = new Size( 309,24);
      buttonStrip.Location = new Point(0, 49);
    }
   buttonStrip.RenderMode = ToolStripRenderMode.ManagerRenderMode;
   // lets sign the controls so we can access them easier
   switch (pos )
   {
      case DockStyle.Left:
         buttonStrip.Text = "Left";
         buttonStrip.Name = "bsLeft";
         break;
      case DockStyle.Right :
         buttonStrip.Text = "Right";
         buttonStrip.Name = "bsRight";
         break;
      case DockStyle.Top :
         buttonStrip.Text = "Top";
         buttonStrip.Name = "bsTop";
         break;
      case DockStyle.Bottom :
         buttonStrip.Text = "Bottom";
         buttonStrip.Name = "bsBottom";
         break;
   }
   if (Parent != null)
   {
      // now we are docking the control;
      buttonStrip.Dock = pos;
      Parent.Controls.Add(buttonStrip);
   }
   return buttonStrip;
}

Now, you will add some buttons to our strips on your testApplication so you will be able to differentiate between the controls added by the user and the toolstrips shown by the DockingManager.

Creating a Dockable Panel-Controlmanager Using C#, Part 1

In the Menustrip, create a menu named 'files' which doesn't need to contain anything at the moment. In toolStrip1, you create Buttons named 'tsbAddForm','tsbAddTechList', 'tsbAddBill', and 'tsbAddEditor'. Choose DisplayStyle Text for all of them and set the Text to 'Add Form', 'Add TechList', 'Add Bill', and 'Add Editor' respectively.

In toolStrip2, you can add some pictures, no matter what. The icons I used are in the attached sample; you can use them if you want. This Toolstrip is only for demonstration and testing purposes, so it's not very important what you are using there. In the StatusBar, add 'Ready' so it can be seen you have a Statusbar there.

Now, after compiling the project you should be able to draw your DockingManager from your Toolbox to the DockingControltestApplications MDI Form. Use dockManager as its name. Your Application should look like this.

[pic6.jpg]

Figure 6: The DockingControltestApp as it looks now (Design Mode)

If you start the program now, nothing happens. You first need to activate the DockingManager. This is done in the MDIForm, where you call dockManager.CreateAllElements(). Your test code now looks just like the following:

namespace DockingControlTestApp
{
   public partial class MDIForm : Form
   {
      public MDIForm() {
         InitializeComponent();
         dockManager.CreateAllElements();
      }
   }
}

Starting your Project again, you get this:

[pic7.jpg]

Figure 7: Look at the Created TabStrips. They are all at the wrong position, as you can see.

Obviously, they need to be in the correct Z-Order because the Z-Order positions them in a specific way: The last added control is outermost one. This means you need to rearrange all docked menustrips, toolstrips, and statusstrips. The best way to rearrange them is to simply arrange the control just after you have inserted it in the other controls. Change the code at the end of the CreateButtonStrip(DockStyle pos) as follows:

   if (Parent != null) {
      // now we are docking the control;
      buttonStrip.Dock = pos;
      Parent.Controls.Add(buttonStrip);
      // and here we will reorder all existing controls
      // independent if they are created by the docking manager
      // automatically or at designtime by the programmer
      // Reorder the controls to get it
      buttonStrip.BringToFront();
   }
   return buttonStrip;
}

Now, when you test it, you will see it working, as seen in Figure 5.

The next issue you should handle is that you want to see your control at designtime, but it should be invisible at runtime. In the constructor of the DockingManager I did the following:

public DockingManager()
{
   InitializeComponent();
   if (DesignMode == true)
   {
      this.Visible = true;
   }
   else
   {
      // Hiding at runtime
      this.Visible = false;
   }
}

This will be visible only at designtime and invisible at runtime. Use the picture DockingManager.ico, which is included in the attached sample and add it to the resources. Its size is 32x32 pixels. To have a nice outfit of your control at designtime, add this picture to your BackgroundImage controls and then set its AutoSizeMode to GrowAndShrink. Change the size of the control to 32x32 and you are ready. Now, in designtime, you should see your DockingManager; when you run the program, it should again look like Figure 5 but the DockingManager should no longer be visible.

How It Is Designed

There may be hundreds of possibilities to design a Dockable Panel. As I mentioned before, I wanted to use existing controls to get the work done. The problem you will have is that you cannot add a Form to a SplitContainer or a TabControl. That simply is impossible. If you try it you can compile it but you get a nice ArgumentException as the test shows.

[ArgumentException.JPG]

Figure 8: ArgumentException (German IDE so German warnings, sorry)

I decided to make the panels simple User defined Controls. Doing it that way also would have lots of restrictions especially when you don't use these user controls in the designed manner. What am I talking about here? User controls are usercontrols and are normally part of a Form or maybe in another way part of an application. They aren't Forms.

So, the idea is the following:

You are using a Form that you will name DockableForm and when you want to create a new panel, instead of using a Form you are using a Dockable Form. In that case, open menu->project and add an inherited Form.

[InheritedForm.JPG]

Figure 9: Adding an inherited Form

[loadingdockableForm.JPG]

Figure 10: Choosing DockableForm

This is the way you will work with your Forms. When the Form is loaded, you will be able to use it just like a normal Form. Only its Docking Properties are not to be used because this is done by the DockingManager according to where you drop the Form.

But, go on now to get this point. If you want it use your DockableForm drag controls on it and position them as needed, it will be sized according to your needs and doing all that needs to be done, so your code works as intended.

In designing your controls, you have to give attention to the point that they may be changed in width and height automatically, depending on where to you are docking them. More about that later.

When the application is compiled and run, at creation of the DockableForms the controls originally drawn on the Form will be transferred to the DockablePanel, a User defined Control that just will get the same optical design which your DockableForm has. The Controls are no longer part of the Forms collection but indeed part of our DockablePanel, which will be placed where the DockableForm should occur. For better understanding, the programmer sees the DockableForm at Design time, at runtime he sees the DockablePanel on the screen. The DockableForm is the carrier of the DockablePanel and cannot be seen this way.

To be totally exact: As long you have not docked your DockableForm the controls are part of the DockablePanel's Controls collection and the DockablePanel is docked onto the DockableForm, and part of the DockableForms Controls collection. When you are docking, the connection between the DockableForm and the DockablePanel breaks up in a way that DockableForm goes to be invisible and DockablePanel still visible, is no longer part of the Dockable Forms Controls.

This enables us to move around the Form even from outside your MDI, and when you are going to dock, the DockablePanel is docked into one of the DockingControlers that are created through a DockingControler class and is a nearly invisible User defined Control. Let me try to show this in a figure.

[DockableFormDockablePanel1.JPG]

Figture 11: DockableForm at Design time :

The programmer designs his Form derived from DockableForm class. The DockablePanel exists as a class as part of the DockingManager, but it's not part of the DockableForm at Design time.

At runtime, you call each DockableForm's CreateNew() method and by doing that, it transfers all the controls that are normally visible at runtime, like TextBoxes, Labels, Comboboxes—whatever. This is independent if they are visible or invisible at the moment, If they could become visible at runtime they are transferred. Controls, which may be visible at design time but invisible at runtime by their own behaviour, like the DockingManager itself, but also invisible Containers like ImageList, Timer, Dialog Fields such as FontDialog, OpenFileDialog—are not transferred. Because it's not their purpose to be visible on the Screen, they stay on the Form.

[DockableFormDockablePanel3.JPG]

Figure 12: Executing myDockableForm.CreateNew()

Now, a DockablePanel object is created and the Controls are transferred.

After this, you see the DockablePanel on your screen and the DockableForm is only the carrier for it as long as you are moving it around on the screen or it stays anywhere and isn't docked to your MDImain Form. Understand, it's NOT a child of MDI; it is simply a Form. You can move it inside the MDI form or outside. No problem.

But all of what you see, including the HeaderBox is not the Windows Form its your DockablePanel Surface that is now docked to your DockableForm using DockStyle.Fill. This is a necessary point for understanding: Wherever you click on this DockableForm, the Click is going through DockablePanel, all Mouse-events are going through DockablePanel—DockableForm is never reached!!

[DockableFormDockablePanel2.JPG]

Figure 13: You can only see the DockablePanel on the Screen every time.

Now, go into the details of this construction. As you see, you will need a HeaderBox that can deliver a similar design as you have in Windows XP, so you want to have it XP Style. This means that the Colour of the Header isn't a single (one colour), it's painted with gradient colours. You cannot use simple a panel for this header or only assign a rectangle to it. You have to create a special control for this.

Attention: This control is a control that will be used in your DockablePanel Control as the HeaderBox.
Note: Normally, you have to create a separate project for that, test it, and compile it as a DLL, so that the control can be used in this project as an already built control. When you decide to simply link it to the project, make sure it is compiled before you compile the DockingControls or you may 'lose' it in the designer of the DockablePanel (where it is used) and have to repair the code therein. If it isn't already compiled when the DockablePanel is to be compiled (which, for example, happens when you simply click on it to see it in design time), the designer cannot draw its objects and will fail. So, be careful to click on the DockablePanel to look at it in design time, because this may lead to trouble as long as this control isn't fully finished and compiled.

How to Create an Ownerdrawn Control

Now, you will add a third project to your Project, named 'DockingBasicControls', which is also a Windows Control Library. Here, you will need to create two classes; one is for Localization. I will later talk about why and how to do the other, the GradientPanel, that you need as HeaderBox for the DockablePanel class.

[DockingBasicControls.JPG]

Figure 14: Three Projects

The GradientPanel is derived from Control, so please add a CustomControl to your project and name it GradientPanel. This will be an ownerdrawn Control, so the code that's created by Visual Studio should look like the following: Delete the usercontrol that is added to the project automatically (if namespaces are missing, simply add them).

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace DockingBasicControls
{
   public partial class GardientPanel : Control
   {
      public GardientPanel () {
         InitializeComponent();
      }

      protected override void OnPaint(PaintEventArgs pe) {
         // Todo : insert your drawing code here
         base.OnPaint(pe);
      }
   }
}

Creating a Dockable Panel-Controlmanager Using C#, Part 1

When you create that control, you first need to think about it how it should work and what properties and methods will be needed so you can create it. The best way would be to make a short list of what properties, methods, and events will be needed and what datatype the properties need to contain.

Table 1: Table of Properties GradientPanel class

Name Field Type Prop. Type Browseable See Note #
Properties:
HeaderPicture Image Set yes  
Pinned Bool Get/Set no  
PinVisible Bool Get/Set no  
ActivatedState Bool Get/Set yes  
ActiveBackColor GradientColor Get/Set yes 5
InActiveBackColor GradientColor Get/Set yes  
Events:
ColorChanged ColorChangedDelegate Fired: OnPaint 1
ExitClick ExitClickDelegate Fired: MouseClick 1
PinClick PinClickDelegate Fired: MouseClick 1
Events to alter:
TextChanged       2
MouseDown       3
MouseUp       3
MouseClick       4

Notes:

  1. These are new delegates that need to be created.
  2. TextChanged needs the Control to be invalidated so the text is rewritten instantly.
  3. These Mouse Events need to be changed because they are not allowed to be fired when one of the buttons is pressed.
  4. The Click Event, when clicking to the buttons, needs to be a ButtonClick Event of Type ExitClick or Type PinClick. Clicking elsewhere should be a normal Click Event, meaning somebody has clicked on this control.
  5. This is a new class you need to create; you also need to create a possibility to show this class's Values in the Properties Window of the Control. To get this done, you need to create a Typeconverter Class so you can show this Type in the Propertybrowser.

The creation of this class should not make any trouble. For the needed pictures, you have to add a Resources File and Resources, which you will name Pictures. The file can be named Resources. Add the Icons PinVertical, PinHorizontal, and ExitCross to the Resources.

Now, do your definitions

namespace DockingBasicControls
{
   // We will need to create an EventArg class too
   public delegate void ColorChangedDelegate( ColorChangedEventArgs e);
   public delegate void ExitClickDelegate();
   public delegate void PinClickDelegate(bool pinned);

   // we will have a spezial Icon for it in the toolbox
   // additional it should be Serializeable too
   [ToolboxBitmap(typeof(GradientPanel)),
   Serializable()] 
   public partial class GradientPanel : Control
   {
      // do we have a HeaderPicture to show
   private bool _withPicture = false;
      // All the needed Pictures
      private Image _headerPicture = null;
      private Image _closeButton = null;
      private Image _pinnedButton = null;
      private Image _unPinnedButton = null;
      // Some Constants where we do the Buttons
      private const int ExitLeft = 26;
      private const int ExitRight = 8;
      private const int PinLeft = 47;
      private const int PinRight = 29;
      private const int Bt05Hght = 9;    // 0.5* BtHeight
   // Textbegin Left without any Picture 
      private const int TxtLeft = 11;
      // Textbegin Left when we have a Picture
      private const int TxtLeftWp = 26;
      // Left Position of HeaderPicture if there is one
      private const int PicLeft = 5;
      //PictureSize of the HeaderPicture
      private const int PicSizeXY = 16; 
      // Is the Panel activated it has to show its 
      // Colors defined for activated State if not it
      // shows Color of Inactive window State
      // and when we first Create the window it should be 
      // the active window
      private bool _activatedState = true;
      // is the Pin horizontal or Vertical
   private bool _pinned = true;
   // is the Pin Visible or not
      private bool _pinVisible= false;
      // we are setting some default Values here
      private Color _usedEndColor = Color.Blue;
      // GradientColor class see later in Text 
      private GradientColor _activeBackColor =new 
              GradientColor(Color.Azure,Color.Blue);
      private GradientColor _inActiveBackColor = new
              GradientColor(Color.Gold , Color.Gray);
      // and our events
      public event ColorChangedDelegate ColorChanged;
      public event ExitClickDelegate ExitClick;
      public event PinClickDelegate PinClick;
      // Class closes somewhere later  b&. }

That's basically all the definitions you need for that class.

Note: I have found some trouble sometimes in adding Icons without drawing them in the program itself. After doing it and all seems to be okay, you will get an error in the Constructor of your class that you will create now like that.
public GradientPanel()  {
   InitializeComponent();
   // reading the needed Pictures from the Resources
   _closeButton = global::DockingBasicControls.Pictures.ExitCross;
   _pinnedButton = global::DockingBasicControls.Pictures.PinVertikal;
   _unPinnedButton =
      global::DockingBasicControls.Pictures.PinHorizontal;
   // we need this delegate to invalidate the GradientPanel when
   // it is resized 
   this.Resize += new EventHandler(GradientPanel_Resize);
}

It tells you that you cannot convert an Icon to an Image. If you have an error like that, look into the Pictures.resx file. Its end should (if will be if it's correct) look like:

Attention: If you have to do any changes here, first make a copy of the whole project anywhere else on your hard disk!
<assembly alias="System.Windows.Forms"
          name="System.Windows.Forms,
          Version=2.0.0.0, Culture=neutral,
          PublicKeyToken=b77a5c561934e089" />
<data name="ExitCross"
      type="System.Resources.ResXFileRef, System.Windows.Forms">
   <value>Resources\ExitCross.ico;System.Drawing.Bitmap,
                    System.Drawing, Version=2.0.0.0, Culture=neutral,
                    PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="PinHorizontal" type="System.Resources.ResXFileRef,
                           System.Windows.Forms">
   <value>Resources\PinHorizontal.ico;System.Drawing.Bitmap,
                    System.Drawing, Version=2.0.0.0, Culture=neutral,
                    PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="PinVertikal" type="System.Resources.ResXFileRef,
                         System.Windows.Forms">
<value>Resources\PinVertikal.ico;System.Drawing.Bitmap,
                 System.Drawing, Version=2.0.0.0, Culture=neutral,
                 PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

But when you have that problem, your >value  line contains 'Icon' instead of 'Bitmap' in one or maybe all of those lines. It may look as in the following incorrect line:

<value>Resources\ExitCross.ico;System.Drawing.Icon,
   System.Drawing, Version=2.0.0.0, Culture=neutral,
   PublicKeyToken=b03f5f7f11d50a3a</value>

This basically tells you that the Icons are placed in the Resources Directory and should be read as Icons. But, storing them as Images is wrong; you need to store them as Bitmaps. I have found you can easily change it into 'Bitmap' in this XML text. Then store the file, close the IDE, and start it again. Recompile the program and the error should be handled.

The HeaderPicture itself  depends on what the programmer chooses for it, so this is a Property.

public Image HeaderPicture   {
   set
   {
      _headerPicture = value;
      if (_headerPicture != null) {
         // setting a flag so we know we have one
         _withPicture = true;
      }
      this.Invalidate();
   }
}

Also, the other properties are very simple. Because you are setting them by codem they should not be visible in your Propertybrowser. If you want to see their Properties in the Browser, simply set them to true or delete the Browseable = true line.

[Browsable(false)]
public bool Pinned {
   get { return _pinned; }
   set  {
      _pinned = value;
      this.Invalidate();
  }
}
[Browsable(false)]
public bool PinVisible {
   get { return _pinVisible; }
   set { 
      _pinVisible = value;
      this.Invalidate();
   }
}

Creating a Dockable Panel-Controlmanager Using C#, Part 1

Invalidating the control is necessary after every change because this causes it to repaint. For the same reason, you have your Resize delegate there.

void GradientPanel_Resize(object sender, EventArgs e){
   this.Invalidate(); 
}

[Browsable(true),
LocalizedDescription("GradientPanel_Activated_Description",
   "DockingBasicControls","BaseResource"),
LocalizedCategory("GradientPanel_GradientColor",
   "DockingBasicControls","BaseResource" )]
public bool ActivatedState {
   get{return _activatedState;}
   set {
        _activatedState = value;
        this.Invalidate();
   }
}


[Browsable(true),
LocalizedDescription("GradientPanel_ActiveBackColor",
   "DockingBasicControls","BaseResource"),
LocalizedCategory("GradientPanel_GradientColor",
   "DockingBasicControls","BaseResource" )]
public GradientColor ActiveBackColor
{
   get {return _activeBackColor; }
   set {
      _activeBackColor = value;
      // redraw when value changed and its in ActivatedState
      if (_activatedState)
         this.Invalidate(); 
   }
}

[Browsable(true),
LocalizedDescription("GradientPanel_InActiveBackColor",
   "DockingBasicControls","BaseResource"),
LocalizedCategory("GradientPanel_GradientColor",
   "DockingBasicControls","BaseResource" )]
public GradientColor InActiveBackColor
{
   get { return _inActiveBackColor; }
   set {
      _inActiveBackColor = value;
      // Redraw if Color Changes and its in InactiveState
      if (!_activatedState)
         this.Invalidate();
   }
}

There is nothing very complicated in it. You only have one specific additional thing here, named LoaclizedDescription

Because these are Properties that are to be seen in the Propertybrowser, you need to have an explanation there, right? Programmers like that. Me too. Maybe you have already seen from my text, English is not my mother tongue. You need to have all this in two languages, maybe more. If you, dear reader, are also from a country that doesn't have English as its standard language, this project also will explain how to do country-dependant files that are simply added as satellite files to the project and then you can use your control in every language you want. My own language, you may not have guessed, because I'm from Vienna, Austria, is (Austrian) German, which has aculture assignation of "de-AT."

To have localized text, never type any text into the code. Create an additional Resources Filethat you will name BaseResource and add it to the DockingBasicControls.

Also, it's easy to understand the following: OnTextChanged is the method that calls the TextChanged() event in the base class of your Control. So, to be sure that you are redrawn each time the text changes, you override this method. Inserting those changes in text will invalidate this Control, so it's redrawn instantly after any text has changed.

protected override void OnTextChanged(EventArgs e){
   this.Invalidate();
   base.OnTextChanged(e);
}

The following code overrides the Mouse events MouseDown, MouseUp, and MouseClick because these Events are fired by OnMouseDown(), OnMouseUp(), and OnMouseClick().

protected override void OnMouseDown(MouseEventArgs e){
   // Make sure there is no normal mouse down event sent
   // when indeed any button is pressed 
      if (!((e.X > (this.ClientRectangle.Width - ExitLeft) &&
         e.X < (this.ClientRectangle.Width - ExitRight)&&
         e.Y > (this.ClientRectangle.Height/2 b. Bt05Hght) && 
         e.Y < (this.ClientRectangle.Height/2 + Bt05Hght))
         // pinButton
         || (_pinVisible && 
         e.X > (this.ClientRectangle.Width - PinLeft) && 
         e.X < (this.ClientRectangle.Width - PinRight) &&
   e.Y > (this.ClientRectangle.Height/2 - Bt05Hght) && 
   e.Y < (this.ClientRectangle.Height/2 + Bt05Hght)))) {
        base.OnMouseDown(e);
   }
}

protected override void OnMouseUp(MouseEventArgs e) {
// Make sure there is no normal mouse up event sent
   // when indeed any button is pressed 
    if (!((e.X > (this.ClientRectangle.Width - ExitLeft) &&
         e.X < (this.ClientRectangle.Width - ExitRight) &&
         e.Y > (this.ClientRectangle.Height/2 - Bt05Hght) &&
         e.Y < (this.ClientRectangle.Height/2 + Bt05Hght))
         // pinButton
         || (_pinVisible  &&
         e.X > (this.ClientRectangle.Width - PinLeft) && 
         e.X < (this.ClientRectangle.Width - PinRight) &&
         e.Y > (this.ClientRectangle.Height/2 - Bt05Hght) &&
         e.Y < (this.ClientRectangle.Height/2 + Bt05Hght)))){
            base.OnMouseUp(e);
   }
}

protected override void OnMouseClick(MouseEventArgs e){
   // Firing the button Events when Mouse is over a button
   // and is clicked with the Left Button
   if (e.Button == MouseButtons.Left){
      // Note: we dont fire just on the border of the button
      // so we subtract 1 Pixel for Buttons Frame
      if (e.X > (this.ClientRectangle.Width - ExitLeft + 1)
         && e.X < (this.ClientRectangle.Width - ExitRight -1)
         && e.Y > (this.ClientRectangle.Height/2 - Bt05Hght + 1)
         && e.Y < (this.ClientRectangle.Height/2 + Bt05Hght -1)){
            // we are inside the Exit Button
            if (ExitClick != null){
               ExitClick();
            }
         }else if (e.X >(this.ClientRectangle.Width-PinLeft+1)
            && e.X <(this.ClientRectangle.Width - PinRight -1)
            && e.Y >(this.ClientRectangle.Height/2 -Bt05Hght+1)
            && e.Y <(this.ClientRectangle.Height/2+Bt05Hght-1))
            {
               if (_pinVisible){
                  // in ButtonSpace
                  if (PinClick != null){
                     PinClick(_pinned);
                  }
            }
      }
      else {
            base.OnMouseClick(e);
      }
   }
   else {
      // if it was no LeftClick with the mouse 
       base.OnMouseClick(e);
   }
}

Because you don't need right clicks with the mouse, I didn't write any special coding for that. Look at the point. For the Pinbutton, I have added "_pinVisible &&" in the OnMouseUp/Down Methods so this only is calculated when the pin is really visible.

Now, do your needed GradientColor class. It's really simple, so there shouldn't be any question to it after a short look at it.

[TypeConverter(typeof(GradientColorConverter))]
public class GradientColor {
   private Color _startColor;
   private Color _endColor;
   // the following Constructor will be needed for our
   // TypeConverter so its necessary to have that if you
   // want to uild a TypeConverter !!
   public GradientColor(Color startColor, Color endColor) {
      _startColor = startColor;
      _endColor = endColor;
   }
   public Color StartColor {
      get { return _startColor; }
      set { _startColor = value; }
   }

   public Color EndColor {
      get { return _endColor; }
      set { _endColor = value; }
   }
}

The only line that really needs special attention here is the first line. Oh, I'm a bad one; I just told you it's so simple and now you have 'a big egg' in the first line. You now have seen this '[ b& ]' different times. It's just another way to call the constructor(s) of a class or classes, depending on what is written inside this square brackets. In your case, you are calling a Typeconverter class named GradientColorConverter. But, before you look at how that is done, first you will finish your GradientPanel. Create the main method of this ownerdrawn Control now.

protected override void OnPaint(PaintEventArgs pe){
   Graphics g = pe.Graphics;
   Color startColor = Color.Aqua; // default value
   Color endColor = Color.Green;  // default value
   Rectangle srcRect;
   // Draw Background
   Rectangle rec = this.ClientRectangle;
   // All  the time redraw the full rectangle
   // but we don't draw when the rectangle is empty
   if (rec != Rectangle.Empty){
      if (_activatedState){
         // the colors are selected depening
         // if the Form is active or inactive
   if (_activeBackColor != null){
      startColor = _activeBackColor.StartColor;
      endColor = _activeBackColor.EndColor;
      }
   }else{
      if (_inActiveBackColor != null) {
         startColor = _inActiveBackColor.StartColor;
         endColor = _inActiveBackColor.EndColor;
         }
      }
      // Lets fire an event when the Color Changes
      // for getting informed we compare it with the previous
      // used endColor
      if( _usedEndColor != endColor){
            if (ColorChanged != null) {
                ColorChangedEventArgs e = new ColorChangedEventArgs();
                e.ChangedColor = endColor;
                ColorChanged(e);
            }
            _usedEndColor = endColor;
      }
      // define a GradientBrush
      LinearGradientBrush b = new LinearGradientBrush(rec, startColor,
                                                     endColor, 90);
      // First we fill the rectangle with the brush
      g.FillRectangle(b, rec);
      // we use the given Font of this Control can be changed
      // by simple changing the Font Property of this
      // Usercontrol, the same with ForeColor Property
      Font fnt = this.Font;
      // the brush for the text
      Brush btxt = new SolidBrush(this.ForeColor);
      // Measure the size of the Text
      SizeF aSize = g.MeasureString(this.Text,fnt); 
      // As we have the Size we can calculate the top 
      // prepare position of the Text
      int top = (int)(rec.Height - aSize.Height)/2;
      int left = TxtLeft;
      // Draw left Picture if there is one
      if (_withPicture) { 
            left =TxtLeftWp;
            // The rectangle of the picture
            Rectangle destRect = new Rectangle();
            destRect.X  = PicLeft;
            destRect.Height = PicSizeXY;
            destRect.Y = (rec.Height - PicSizeXY)/2;
            destRect.Width = PicSizeXY;
            srcRect = new Rectangle(new Point(0, 0),
               _headerPicture.Size);
            // now we draw the picture 
            g.DrawImage(_headerPicture, destRect, srcRect, g.PageUnit);
      }
      // draw Buttons
      if ( _closeButton != null){
            // the rectangle of the close Button
            Rectangle recClose = new Rectangle();
            recClose.X = rec.Width b. ExitLeft+1;
            recClose.Height = PicSizeXY;
            recClose.Y = (rec.Height - PicSizeXY)/2;
            recClose.Width = PicSizeXY;
            srcRect = new Rectangle(new Point(0,0),_closeButton.Size);
            g.DrawImage(_closeButton, recClose, srcRect, g.PageUnit); 
      }
      if (_pinVisible){
         // the Position of the pinbutton if there is one
         Rectangle recPin = new Rectangle();
         recPin.X = rec.Width b. PinLeft+1;
         recPin.Height = PicSizeXY;
         recPin.Y = (rec.Height - PicSizeXY )/2;
         recPin.Width = PicSizeXY;
         Image butImage = null;
         // Decide which picture should be drawn depending on
         // if we have a pinned or unpinned panel
            if (_pinned && _pinnedButton != null
                        && _unPinnedButton != null ) {
               butImage = _pinnedButton;
               srcRect = new Rectangle(
                  new Point(0, 0), _pinnedButton.Size);
            }else{
               butImage = _unPinnedButton;
               srcRect = new Rectangle(
                  new Point(0, 0), _unPinnedButton.Size);
            }
            g.DrawImage(butImage, recPin, srcRect, g.PageUnit);
        }
        g.DrawString(this.Text, fnt, btxt, new Point(left,top));
   }
   // OnPaint-Basisklasse wird aufgerufen
   base.OnPaint(pe);
}

Creating a Dockable Panel-Controlmanager Using C#, Part 1

When you look at your OnPaint code, there is an event fired, named ColorChanged. For this event, you need the ColorChangedEventArgs Class that is derived from EventArgs.

public class ColorChangedEventArgs : EventArgs {
   private Color _changedColor;
   // the event has a property which shows the 
   // Changed endColor of Type Color
   public Color ChangedColor {
      get { return _changedColor; }
      set { _changedColor = value; }
   }
}

We need to fire that event because when you change the colour of the header, you also need to change the border of your DockablePanel. For this, you fire the changed endColor to the panel, where you can change the border's colour then too.

What you have designed so far is the following, but because you still have no Localized Classes and you still don't have the TypeConverter that you are using in your code, you currently are unable to compile it. If you could see it, it would look like this (this is a short preview).

[HeaderPicture.JPG]

Figure 15: The GradientPanel when it is ready

How to Create a TypeConverter

As you have seen in the code before,

[TypeConverter(typeof(GradientColorConverter))]

I have used a TypeConverter. This is a very useful class if you want to add Properties of a class directly to the PropertyBrowser of a control. In your case, you have the GradientColor class that normally cannot be used in the Propertybrowser because it is no class known by the PropertyBrowser. So, you need to 'teach' it to know your class and how to handle it, so it can be used similarly to other classes such as Size or Location. The intention is that you will be able to fill all needed Values into this datatype directly without any need of splitting the type into different Properties and filling all these properties with separate fields and nobody would know that they are connected together and all members of the same class. By using the Typeconverter, the property can be filled by showing all the different values and editing them or, if you want to fill the property directly, you can type in the color names in one line separated by semi colons like 'Aqua;Green' or if you prefer RGB you also can do '120,60,18; 0,128,0' or also mixed if you want, whatever. In the end this looks like the following:

[Typeconverter1.JPG]

Figure 16: The result of using a TypeConverter can be seen here

Now, let me explain the needed code:

public class GradientColorConverter: ExpandableObjectConverter
{
   // We don't need to override CanConvertTo if converting
   // to string, as that's what base TypeConverter does.
   // So CanConvertFrom() and CanConvertTo() is both really simple
   // But we need to override ConvertFrom() and ConvertTo() because
   // its base converts from InstanceDescriptor

   public override bool CanConvertTo(ITypeDescriptorContext context,
                                     Type destinationType)
   {
      // if the destinationType can be converted to an
      // InstanceDescriptor then there is nothing to do 
      if (destinationType == typeof(InstanceDescriptor)){
         return true;
      }
      // otherwise lets check it from the base Method
      return base.CanConvertTo(context, destinationType);
   }

   public override bool CanConvertFrom(ITypeDescriptorContext context,
                                      Type sourceType)
   {
      //if we have a string we everytime can convert
      if (sourceType == typeof(string)) { return true; }
      // otherwise let's check it by the base class
      return base.CanConvertFrom(context, sourceType);
   }

   public override object ConvertFrom(ITypeDescriptorContext context,
                                      CultureInfo info, object value)
   {
      // If converting from a string
      if (value is string)
      {
         // Build a GradientColor type
         try
         {
            // Get GradientColor properties
            // The string which is in the PropertyBrowser
            string propertyList = (string)value;
            string[] properties = propertyList.Split(';');
            return new GradientColor( 
               Color.FromName(properties[0].Trim()),
               Color.FromName(properties[1].Trim()));
         }
         catch {
            throw new ArgumentException(
               Localize.GetResourceString(
                  "ERROR_GradientConverter_Arguments",
                  "DockingBasicControls",
                  "BaseResource"));
            }
      }
      return base.ConvertFrom(context, info, value);
   }

   public override object ConvertTo(ITypeDescriptorContext 
            ontext, CultureInfo culture, object value,
Type destinationType) {
      // If source value is a GradientColor type
      if (value is GradientColor){
         // Convert to string
         if ((destinationType == typeof(string))){
            GradientColor gradColor = (GradientColor)value;
         // we use ColorName, if it is not a named color
         // we build a string of RGB with commas in between
            string startColor = (gradColor.StartColor.IsNamedColor ?
                                 gradColor.StartColor.Name :
                                 gradColor.StartColor.R + ", " +
                                 gradColor.StartColor.G + ", " +
                                 gradColor.StartColor.B);
            // The same with endColor
            string endColor = (gradColor.EndColor.IsNamedColor ?
                               gradColor.EndColor.Name :
                               gradColor.EndColor.R + ", " +
                               gradColor.StartColor.G + ", " +
                               gradColor.StartColor.B);
            // now we Format it to a string Color1;Color2
            return string.Format("{0}; {1}", startColor, endColor);
         }
         // Convert to InstanceDescriptor if needed
         if ((destinationType == typeof(InstanceDescriptor)))
         {
            GradientColor gradColor = (GradientColor)value;
            object[] properties = new object[2];
            Type[] types = new Type[2];

            // Color
            types[0] = typeof(Color);
            properties[0] = gradColor.StartColor; 

            // Width
            types[1] = typeof(Color);
            properties[1] = gradColor.EndColor; 

            // Build constructor
            ConstructorInfo ci = typeof(GradientColor).
            GetConstructor(types);
                return new InstanceDescriptor(ci, properties);
         }
      }
      // Base ConvertTo if neither string or InstanceDescriptor required
      return base.ConvertTo(context, culture, value, destinationType);
   }

   public override bool
      GetCreateInstanceSupported(ITypeDescriptorContext context)
   {
      // Always force a new instance
      return true;
   }

   public override object CreateInstance(ITypeDescriptorContext context,
                                         IDictionary propertyValues)
   {

      // Use the dictionary to create a new instance
      return new GradientColor((Color)propertyValues["StartColor"],
                               (Color)propertyValues["EndColor"]);
   }

}    // End class GradientColorConverter

How to Do Localization in Case of Usercontrols Properties

Let me continue onwards to creating the missing parts so that you can compile and use this DockingBasicControls. Create a new class file called Localization.cs. There, you will do all the localization classes you need to localize all DockingBasicControls. If you have Forms to localize, you may use the Localize Property of the Form, but if you are creating a usercontrol for yourself, you have to do the localization yourself. You use the modern design of satellite Resources because you only need to add your localized file and your control will work in your language. There is no special recompilation of your control's DLL needed, no code change. (Oh, sorry, I haven't implemented right to left. You need do this yourself.) And here is the code:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Resources;
using System.Globalization;
using System.Text;
using System.ComponentModel;
using System.Windows.Forms;
using System.Diagnostics;
using System.Drawing; 


namespace DockingBasicControls
{
   // For defining the Category of our Properties
   // Note This was former done using IcategorizeProperties(C++, VB6-API)
   [AttributeUsage(AttributeTargets.All)]
   public sealed class LocalizedCategoryAttribute : CategoryAttribute
   {
      private string _nameSpace;
      private string _resource;
      // the constructor
      public LocalizedCategoryAttribute(string category,
         string nameSpace, string resource) : base(category) {
         _nameSpace = nameSpace;
         _resource = resource;
      }

      protected override string GetLocalizedString(string category)
      {
         // return localized category name from your resource
         // we need any adress which is stable for all known OS
         // where we can store our global resource files.
         string path = "C:\\WINDOWS\\Resources\\Globalisation\\"
                       + _nameSpace;
         try
         {
            ResourceManager resourceMngr = 
                       ResourceManager.CreateFileBasedResourceManager(
                                               _resource , path, null);
            return resourceMngr.GetString(category);
         }
         catch
         {
            // when we have no filebased resources then we use the
            // DLL bases resources
            // which has a fallback feature
            ResourceManager localResMngr = new
                         ResourceManager(_nameSpace + "." + _resource,
                                            this.GetType().Assembly);
            return localResMngr.GetString((category));
         }
      }
   }

   // Also Necessary the Description Attribute class for describing
   // in the Propertybrowser what a Property is needed for 
   [AttributeUsage(AttributeTargets.All)]
   public sealed class LocalizedDescriptionAttribute:
      DescriptionAttribute
   {
      private string _nameSpace;
      private string _resource;
      // the Constructor
      public LocalizedDescriptionAttribute(string category,
             string nameSpace, string resource) : base(category) {
         _nameSpace = nameSpace;
         _resource = resource;
         string path = "C:\\WINDOWS\\Resources\\Globalisation\\"
                       + _nameSpace;
         try {
            ResourceManager resourceMngr =
                     ResourceManager.CreateFileBasedResourceManager(
                                     _resource, path, null);
            // DescriptionValue is a property of the 
            //DuiscriptionAttributeClass
            DescriptionValue = resourceMngr.GetString(category);
            // DescriptionValue is a property of the 
            // DescriptionAttributeClass
         } catch {
            // when we have no filebased resources then 
            // we use the dll based resources 
            // which have a fallback feature
            ResourceManager localResMngr = new ResourceManager(
                            _nameSpace + "." + _resource,
                            this.GetType().Assembly);
            DescriptionValue = localResMngr.GetString(category);
         }
         // to get totally sure we dont get null as an output
         if (DescriptionValue == null)  {
            DescriptionValue = String.Empty;
         }

      }

      public override string Description {
         get {
            return DescriptionValue;
         }
      }
   }
   // At least we also need a class for Localization of all other
   // text which may occur in a program, e.g. such like errormessages
   public sealed class Localize  {
      public static string GetResourceString(string key,
            string nameSpace,string resource)
      {
         try
         {
            string path = "C:\\WINDOWS\\Resources\\Globalisation\\"
                          + nameSpace;
            ResourceManager resourceMngr = ResourceManager
               .CreateFileBasedResourceManager(
                  resource, path, null);
            return resourceMngr.GetString(key);
         } catch {
            ResourceManager localResMngr = new ResourceManager( 
               nameSpace + "." + resource,
               Assembly.GetCallingAssembly());
            return localResMngr.GetString(key);
         }
      }
   }    // end class Localize
}       // end namespace

Creating a Dockable Panel-Controlmanager Using C#, Part 1

Here, you can see these classes work in two ways. The first is to have a look on a specific place if there are any resources done. That's what I normally use; its done quickly and it's easy to handle. The other method is to look whether there is a resources DLL anywhere in the environment near the application itself. This mechanic has a fallback mechanism that way; if it doesn't find a resource in a DLL and hasn't found it before in the fixed path "C:\\WINDOWS\\Resources\\Globalisation\\DockingBasicControl" (because 'DockingBasicControl' is our nameSpace), in the end it reads the resources file which is still part of the control's DLL. So, anyway, you will get the resources text, at a minimum the default resources.

Consider your names. As you see, it's a must that the Resourcemanager gets a filename constructed of Namespace.Resourcename. In case of FileBasedResourceManager, consider that you have a correct path and that the name of the resource file doesn't include any extensions when used in the code; it's only 'BaseResource' in your case.

What additional has to be done to get this working:

  1. There is no such directory in Windows, so please create all needed directories within Windows to get this.
  2. To create a language-specific resource file, you may use different ways to create it. There are some free programs on the web, but you can simply do it as I do, by using the following method outside of your Visual Studio IDE:
    1. Copy the resource file that is in your case BaseResource.resx (only this one; the design file is not needed).
    2. Rename this copy to a language-specific resource file. For example, for German the main Culture name is 'de' and Country Specific you have 'de-DE' for Germany 'de-AT' for Austria, and so on. Because the ResourceManager in your program looks for the CurrentUI culture, you need to have the Country-Neutral Language-Specific code for German, which is simply 'de'. So, your resources file needs to be renamed to BaseResource.de.resx (or your language sign. See MSDN for other countries). Now, you have two files containing the same data; one is BaseResource.resx and the other is BaseResource.de.resx. This you now open using your Visual Studio IDE and changing the English Text Values to German or your favourite language. Don't change the Names because they are needed to get the values—only the Values. When ready, store and close the file.
    3. Now, you are using Visual Studio's command prompt. You may have wondered why this is there in the menu of VS 2005. I was wondering too. Is there really nobody interested to help MS to get a just-in-time IDE here for that purpose that delivers a graphical user interface, or is this also waiting on me to handle that for myself?? What you have to do is to run the ResourceGenerator on your file now. So, assume that you have transferred your BaseResource.de.resx to a temporarily created C:\SatResources directory as I did. I hate to type unnecessary long paths, so I call Resgen c:\SatResources\ BaseResource.de.resx. It prompts to be ready and you get a binary file named BaseResource.de.Resources, which is the binary equivalent for your Resources.de.resx file.
    4. You place this file in the given path "C:\\WINDOWS\\Resources\\Globalisation\\DockingBasicControl" and you are ready.
Note: For more information about this, look into the MSDN Help files for Localization or into the book I have added to the Credits List at the end of this article. Additionally, a good explanation can be found in an article at CodeProject. Here is the link: http://www.codeproject.com/dotnet/Localization.asp It's named .NET - Localization using Resource file.

Now, you only need to insert all the needed strings into your BaseResource.

Name Value
ERROR_GradientConverter_Arguments The arguments were not valid.
GradientPanel_Activated_Description Used to decide whether the Active or inactive Set of Gradientcolors is used to draw the Panel
GradientPanel_ActiveBackColor The GradientColors Shown when ActivatedState is set to Active
GradientPanel_GradientColor GradientColors
GradientPanel_InActiveBackColor The GradientColors Shown when ActivatedState is set to InActive

If you do your own table, create it as explained earlier.

Now, you are ready to compile your GradientPanel. After successfully compiling it, open your MDIForm and the toolbox and draw the GradientPanel to your Form. Understand: This is only for testing purposes. Now, you can test the working of ActiveBackColor, InactiveBackcolor, and ActivatedState to switch between them (you only see the changes of that GradientColor, which is just the used one). Also have a look at the description of the control.

[GradientPanelReady.JPG]

Figure 17: The final product of the DockingBasicControls

The figure shows you the selected GradientPanel and the PropertyBrowser where you have your new Category GradientColors that include ActivatedState, ActiveBackColor, and InactiveBackColor, both shown in their default Values, depending which one you select by using ActivatedState.

Forms and Panels

Before you continue, create a new Folder named Forms where you will put all the classes that are needed, and the "Fake Forms" that only simulates Forms, such as the DockablePanel Control. For all your messages, descriptions, and categories, you must create a resource named StringResource. Add the String Resource to the main namespace 'DockingControls'. Then, add a UserControl to the Forms Folder. This way, the namespace for the DockablePanel will be DockingControls.Forms.

You get the following code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace DockingControls.Forms
{
   public partial class DockablePanel : UserControl
   {
      public DockablePanel() {
         InitializeComponent();
      }
   }
}

Set the following property values in the property browser to create a nice Outfit.

Table 2: Dockable Panels Properties Listing

Name DockablePanel
Size 225;359
Margin 0;0;0;0
BackColor Green

Now, open the toolbox and draw a GradientPanel to the top left Corner of the UserControl, and set the following properties:

Table 3: HeaderBox Properties Listing

Name HeaderBox
Dock Top
Location 0;0
ActivatedState True
ActiveBackColor Aquamarine; Green
Size 225;24
Text Testing Text
ForeColor White
Font SansSerif;9ptstyle=Bold
InactiveColor Azure; Blue
TabIndex 0
Margin 0;0;0;0

Draw a Panel on that Usercontrol and set the following values.

Table 4: Filler Properties listing

Name filler
AllowDrop True
BorderStyle FixedSingle
BackColor Control
Size 217;331
TabIndex 1
Location 5;25
Margin 0;0;0;0
Visible True

Before you add the next Control, you need to rename the Resources of the Docking Controls. To do this, expand the Properties Folder and there rename Resources.resx to 'Pictures.resx'.

Attention: Every time you rename something like 'Resources', the different designers that use items in the Resources will not fully be able to correct all the access code to these items. So, after the change but before adding a new picture, do a new compilation and see whether everything is still working. At the moment, there is only the dockingManager icon in use, so even if all seems to work, close the IDE and restart it. Then, look again whether everything can be compiled again. If you get problems with the designer of the MDIForm, simply correct this address of the resource there, store it, and restart the IDE. After that, it should work properly. This restart seems to be necessary so the designer can change its internal data.

Lastly, draw a PictureBox to the Right/Bottom corner with the following properties.

Table 5: picGripTriangle Properties Listing

Name picGripTriangle
Location 200;312
Size 16;16
Image DockingControls.Properties.
Pictures.GripTriangle

Now, you have almost added everything that you need for this fake Form. Draw a timer control to your DockablePanel.

Table 6: MoverTickTimer Properties Listing

Name MoverTick
Enabled False
Interval 5

Next, you need to create the DockableForm. This is simply a Form. Add a Form and add it to the DockingControls Forms Folder by using DockableForm as its name.

Table 7: DockableForm Properties Listing

Name DockableForm
Size 233;292
FormBorderStyle SizeableToolWindow
Note: Some of these Values, such as Size, are of no importance, I'm only adding this data so that someone who has very little experience will also be able to follow this article. The created code shows it as a member of the DockingControls.Forms namespace.

Creating a Dockable Panel-Controlmanager Using C#, Part 1

The third and last Form you need is also a Form named TransparentForm. You add it with the following data.

Table 8: TransparentForm Properties Listing

Name TransparentForm
FormBorderStyle None
BackColor Lime
Ocupacy 0,5 (50%)
Size 160;90
ShowIcon False
ShowInTaskbar False

Double-click on the Load Event so that it will create the Load- Event- Delegate for you and add the following lines.

private void TransparentForm_Load(object sender, EventArgs e){
   this.TopMost = true;
}

By doing this, the TransparentForm will always be on top of other forms. That means you can easily see where your Form will be placed. (If you take the Visual Studio IDE as an example, it has the same behavior.)

This was the easiest Form, so this is totally finished. You will not need any more coding for this Form.

Coding the DockableForm

You need the following Namespaces:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Resources;
using System.Drawing.Design;
using System.ComponentModel.Design;
using System.Windows.Forms.Design;
using DockingBasicControls;

In a lot of cases, it's very useful to have Initialization of a Form supported, like your DockableForm. Because you don't know what the programmer would need, it should also be able to support this functionality. This is why you need to add the ISupportInitialize Interface to your Form. This is simple. By the way, I think most things are quite simple when you know two things:

a) how to do it, and b) why it should be done, what's the purpose of it? If you know the purpose, you easily can use it the proper way c) How is it used or who uses it or where is it used?

The purpose of ISupportInitialize

It simply tells the Form that it is just in a state of initializing so it can be used if you have some code on your Form that should not be executed as long as the form isn't ready with initialization. So, you use it every time you have code that needs to differ between normal processing at runtime and initialization of the form. The interface is used by the designer that sets it to true at the beginning and to false when initialization is finished. That's the 'why' and who of uses it. Here is the 'how':

If you are adding an Interface, you only have to implement its methods and it will work.

public class DockableForm : Form , ISupportInitialize {

   // Fields 
   // this field will be set to true during Iitialisation 
   private bool _initializing = false;
   // this Fields will be needed in the code which is explained 
   //in the next few pages
   private DockingManager _dockManager = null;
   private DockablePanel dockPanel = new DockablePanel();
   private BorderStyle _borderStyle = BorderStyle.Sizeable3D;
   private Image _windowImage = null;

   const int horDiff = 9; // SizeDifference because of Border
   const int verDiff = 28;// SizeDifference because of Header and 
      // Border in the Bottom
      // Constructor
    public DockableForm(){
         InitializeComponent();
   }

#region Interface ISupportInitialize
      void ISupportInitialize.BeginInit() { _initializing = true; }
      void ISupportInitialize.EndInit()   { _initializing = false; }
#endregion
}

You can see another 'dirty trick' in the above code. What am I talking about? I used regions to create more structured, easier-to-read code. You can do that as often you want, giving different names to regions, and your code will get much clearer to view because you might close sections when you just don't need them during coding a larger type of class. So, maybe this doesn't make much sense for you at the moment, but will help you when you use that method during coding different parts of the program.

Just implement it and click on its 'minus' button on the left side. You can see how it works.

Now, you will create some Properties. First, you need a connection to the dockingManager of the program. Create a DockingManager Property. It should not be seen in the Property Browser because it has to be set by code. It also should not be serialized in case someone wants to serialize this class.

[Browsable(false),  DesignerSerializationVisibility(
   DesignerSerializationVisibility.Hidden)]
public DockingManager DockingManager {
   get {
      return _dockManager;
   }
   set {
      _dockManager = value;
   }
}

Now, you want to get the FormBorderStyle Property that is normally used with Forms; it's not useable for the programmer because this should only be used by internal code. You have two ways to make this baseProperty invisible:

  1. Writing your own designer and this way keeps it from being shown in the Propertybrowser and in the intellisense. This is too much effort for this small action.
  2. Simply override it by using "new" and getting the overridden Property Browseable set to false and EditorBrowsable to EditorBrowsableState.Never. Instead of this Property, you use the 'BorderStyle' Property that normally doesn't exist in Forms this way, so you don't get in a collision with any baseproperty here.
Note: You will no longer see the FormBorderStyle Property in the PropertyBrowser, but you still see it in the Intellisense as long as you haven't a compiled version of this DockingControl and use it without having the projects open and connected together as you do for testing and debug purposes here. The same is for getting pictures into the Toolbox. They only occur in the usage of end-compiled DLLs.
[Browsable(false),
EditorBrowsable(EditorBrowsableState.Never )]
public new FormBorderStyle FormBorderStyle {
   get {
      return base.FormBorderStyle;
   }
   Set {
      base.FormBorderStyle = value;
   }
}

This is the new Method for the BorderStyle. Because I want it read-only at runtime, it wasn't possible to get it done as the former FormBorderStyle Property. This would fail in derived classes.

[Browsable(true),
LocalizedCategory("Category_Styles", "DockingControls",
   "StringResource"),
LocalizedDescription("DockableForm_BorderStyle_Description",
   "DockingControls", "StringResource"), 
EditorBrowsable(EditorBrowsableState.Never)]
public BorderStyle BorderStyle
{
   get
   {
      return _borderStyle;
   }
   set
   {
      // here we are using our ISupportInitialize Variable
      // So if we are just initializing or in Designmode then
      // the Property can be set but it fails when we try to do
      //  it at runtime
      if (_initializing != true && this.DesignMode == false)
      {
         throw new InvalidOperationException(
            Localize.GetResourceString(
               "ERROR_ReadOnlyProperty_FormBorderStyle",
               "DockingControls", "StringResource"));
      }
      Else // if its designmode or Initialisation
      {
         _borderStyle = value;
         switch (_borderStyle)
         {
            case BorderStyle.Fixed2D:
               base.FormBorderStyle =
                  FormBorderStyle.FixedToolWindow;
               break;
            case BorderStyle.Fixed3D:
               base.FormBorderStyle = 
                  FormBorderStyle.FixedToolWindow;
               break;
            case BorderStyle.Sizeable2D:
               base.FormBorderStyle =
                  FormBorderStyle.SizableToolWindow;
               break;
            case BorderStyle.Sizeable3D:
               base.FormBorderStyle =
                  FormBorderStyle.SizableToolWindow;
               break;
         }
      }
   }
}

Here come some Properties that will be needed by the derived windows because they are used by code only and are read-only.

[Browsable(false)]
public int InnerHeight
{
   get { return this.Height - verDiff; }
}

[Browsable(false)]
public int InnerWidth
{
   // 2 is a linedrawingcorrection on the right side
   get { return this.Width - horDiff - 2; }
}

You also need a Property to set the image for the Forms Header.

public Image WindowImage
{
   get {return _windowImage; }
   set { _windowImage = value; }
}

You fade out the Hide() and Show() methods of this Form and write your own because hiding the Form means hiding the panel and showing the Form is showing the panel.

public new void Hide(){
   dockPanel.Hide();
}

public new void Show(){
   dockPanel.Show();
}

This is most of the needed code. You cannot implement the transfer action from the Form to the DockablePanel and you also cannot create the Dockable Panel at this point in time because all its needed Properties of it need to be created first. Change to your DockablePanel.

Creating a Dockable Panel-Controlmanager Using C#, Part 1

Just on the top of the namespace of our DockablePanel, you must create the following enum that you need in different parts of your Control but you set it here. It's done just here and nowhere else, because in the dockingPanlel it could be mixed up with System Windows.Forms.BorderStyle, and that should not happen. So, when declaring it here, the compiler knows that all the mentioned BorderStyles in the code are your own defined enum.

namespace DockingControls.Forms{
   // put it here 
   public enum BorderStyle { Fixed2D, Fixed3D, Sizeable2D, Sizeable3D };
   // before the header of the class
   public partial class DockablePanel : UserControl{
. . . }
}

Next, you must change the visibility of your DockablePanel to 'internal' because it only could be used together with the DockingManager and as part of the DockableForm. You also may not be able to derive from this class, so please change the header line of your DockablePanel class to the following.

internal sealed partial class DockablePanel : UserControl {

   internal DockablePanel() {
      InitializeComponent();
      if (!DesignMode)
      {
         this.Visible = false;
      }
}

You want to use specific Colors for your Gradient Panel and it should be possible to change thse colors as settings for your DockingControls class. You are not using the Property Browser of the GradientPanel for your ColorValues but instead the Properties Menu of your Control, and there you do the following entries.

Table 9: Properties Adjustments

Name Type   Value
ActiveCOLOR System.Drawing.Color User Aqua
ActiveCOLORGradient System.Drawing.Color User Green
InActiveCOLOR System.Drawing.Color User Azure
InActiveCOLORGradient System.Drawing.Color User Blue

In your code, you install this just after InitializeComponent().

this.BackColor = Properties.Settings.Default.ActiveCOLORGradient;
this.HeaderBox.ActiveBackColor = new GradientColor(
   Properties.Settings.Default.ActiveCOLOR,
   Properties.Settings.Default.ActiveCOLORGradient);
this.HeaderBox.InActiveBackColor.StartColor = 
   Properties.Settings.Default.InActiveCOLOR;
this.HeaderBox.InActiveBackColor.EndColor = 
   Properties.Settings.Default.InactiveCOLORGradient; 
this.HeaderBox.ActivatedState = true;

You have set the ActiveBackColor, using the GradientClass, and added a new class to the HeaderBox instead of the InActiveColors you set them both separately. This is only different to have a test if setting color by code works both ways on the one side adding a new class or simply setting the Properties of the existing GradientColor class of the HeaderBox. Additionally, you now will install the delegates of the HeaderBox to get informed of ColorChanges, and Clicks to the Exit or the Pin Button.

Using the tools for getting the standardised delegate, you get the following:

this.HeaderBox.ColorChanged += new 
   ColorChangedDelegate(HeaderBox_ColorChanged);
this.HeaderBox.ExitClick += new 
   ExitClickDelegate(HeaderBox_ExitClick);
this.HeaderBox.PinClick += new 
   PinClickDelegate(HeaderBox_PinClick);

And the delegates at the moment, you will find some lines deeper:

void HeaderBox_PinClick(bool pinned) {
   throw new Exception("The method or operation is not implemented.");
}

void HeaderBox_ExitClick() {
   throw new Exception("The method or operation is not implemented.");
}

void HeaderBox_ColorChanged(ColorChangedEventArgs e) {
   throw new Exception("The method or operation is not implemented.");
}

You must change each of the "throw Exception" lines to the following:

Console.WriteLine('This method isn't implemented ');

This way we still would get informed when we press this button testing the control but we aren't getting an exception thrown

If you are compiling now for test reasons, you should be able to do it. You are not implementing the code for the above delegates. This is done when you are logically at that point. You will create the needed properties for your class first.

Attention: In the BorderStyle Property, you have to make sure that the filler panel is getting a border depending on your used BorderStyle, but the filler Panel uses the standard System.Windows.Forms.BorderStyle for BorderStyle. So, this method translats your own BorderStyle enumeration into the appropriate Windows.Forms BordersStyles. Because this enum is overridden in this Panel, you explicitly need to set the full name when you talk about the Windows.Forms.BorderStyle.
#region Properties
internal new BorderStyle  BorderStyle
{
   get {
      return _borderStyle;
   }
   set {
      _borderStyle = value;
      switch (_borderStyle)
      { 

         case BorderStyle.Fixed2D:
            filler.BorderStyle = 
               System.Windows.Forms.BorderStyle.FixedSingle;
            _sizeable = false;
            break;
         case BorderStyle.Fixed3D:
            filler.BorderStyle =
               System.Windows.Forms.BorderStyle.Fixed3D;
            _sizeable = false;
            break;
         case BorderStyle.Sizeable2D:
            filler.BorderStyle = 
               System.Windows.Forms.BorderStyle.FixedSingle;
            _sizeable = true;
            break;
         case BorderStyle.Sizeable3D:
            filler.BorderStyle = 
               System.Windows.Forms.BorderStyle.Fixed3D;
            _sizeable = true;
            break;
      }

      base.BorderStyle = System.Windows.Forms.BorderStyle.None;
      picGripTriangle.Visible = _sizeable;
   }
}

This translation of Borderstyles is obviously done twice: once in the DockableForm where you don't want the programmer to use an inadequate FormBorderStyle, which would not be possible in your DockingPanel, and to show him the result of the chosen Style on his DummyForm where he does the layout; and the second time here in the DockablePanel where you set the Border of the fillerPanel (see the drawing and look at the above design of the panel) to get a flat or 3D-looking surface.

Next you need a Property to set the BackColor of the DockablePanel. In your case, the BackColor of your Usercontrol is used for the Borders Color. The filler panel will show what you normally have as BackColor. This is needed to set all the Values that a programmer may choose for his form for the panel. So that you can follow my idea, you have to duplicate all these values when you create your DockablePanel fake From the DockableForm; that will be done in the CreateNew() Method of the DockableForm.

internal Color FormBackColor {
   Set {
      filler.BackColor = value;
   }
}
internal Color FormForeColor {
   set {
      filler.ForeColor = value;
   }
}

And now, you want to have a rope between your DockableForm and your Dockable Panel, so do a Property where the DockablePanel has access to the DockableForm. Because the DockableForm is the carrier of the DockablePanel when it is not docked to the MDI, I named this Property where you had access to the address of the DockableForm simply Carrier.

internal Form Carrier{
   get {
      return _carrierForm;
   }
   set {
      _carrierForm = value;
   }
}

It isn't necessary to give any specific explanation because its name says what you are looking for with this Property. Is the DockablePanel just attached to the Carrier or not? This means it will to be docked to MDI, or more exactly, to one of the Docking Controllers that the DockingManager docks to the MDIForm whenever needed.

internal  bool CarrierAttached {
   get {
      return _carrierAttached; 
   }
   set {
      _carrierAttached = value;
   }
}

The Caption property is used to transfer the input data directly to the Header.

internal  string Caption {
   get { return HeaderBox.Text; }
   set { HeaderBox.Text = value; }
}

This Property checks whether the _isMouseDown Flag in the DockablePanel is set. It's only read out, because this value allows checking the state of the mouse during she is over the DockabePanel.

internal bool IsMouseDown {
   get { return _isMouseDown; }
}

Minimum Width and Minimum Height are constant values that can be read out with this property.

internal Size MinSize {
   get { return new Size(MIN_WIDTH, MIN_HEIGHT); }
}

Your connection to the DockingManager:. This Property needs to be expanded at a later point in your program.

internal DockingManager DockingManager {
   get {
      return _dockingManager;
   }
   set {
      _dockingManager = value;
   }
}

Creating a Dockable Panel-Controlmanager Using C#, Part 1

The WindowImage Property allows you to transfer the Icon from the DockableForm to the HeaderBox of your DockablePanel.

internal Image WindowImage {
   get { return _windowImage; }
   set {
      this.HeaderBox.HeaderPicture = value;
      _windowImage = value;
   }
}

The property to set or get informed about the Docking Type of a DockablePanel named DockingType is an enumType DockType that needs to be declared in the Header of the DockManager.cs. You have to understand this property correctly. This Property itself doesn't dock the DockablePanel. It only informs you about how the Docking seems to be. Understand that the DockableForm is only a fake. What you will see onscreen is the dockable panel. This dockable panel could be dragged over some buttons that (when the mouse gets into their position) will blink. When you drop the panels there, which means left MouseButtonUp (you no longer press MouseButon down), then the DockablePanel is added to a SplitContainer or a TabControl; both can be placed on a hidden panel named DockingControler. In every case, it docks using DockStyle Fill so the DockablePanel itself never really docks left, right, top, or bottom—nor in any other way. That's all an illusion—it looks like it would do so, but in truth it doesn't dock other than DockStyle.Filling, but depending on what the fake shows to you, this property is set or to be read.

internal DockType DockingType {
   get {
      return _dockingType;
   }
   set {
      // remember only the panel's state, this doesn't dock itself;
      _dockingType = value;
      if (_dockingType != DockType.None) {
         // if we are docked in any way we have to make Visible the
         // Pin Needle on the DockablePanel 
         HeaderBox.PinVisible = true;
      }
   }
}

Now you also need every Panel that has a Key to have a clear identification of it. A magician once told me: You can do great illusions; people believe what they think they saw with their own eyes. But, if you do illusions, be very exact or you will fail terribly. This is why you need to have a key for every DockablePanel and this is why you need a separate administration of all the Dockable Panels and their state.

internal string Key {
   get {
      return _key;
   }
   Set {
      _key = value;
   }
}

The following Property is needed to set or get information about a Flag that is set when the DockablePanel is added to a Tabcontrol.

internal  bool IsTabbed {
   get {
      return _isTabbed;
   }
   Set {
      _isTabbed = value;
   }
}

This Property informs whether a Panel is set to unpinned.

internal bool IsUnPinned {
   get { return !_pinned; }
}

Here, you are informed about the undocked size of the panel just before it was docked. This value isn't changed when the control is changed in size by the Dockingcontroler, but it is changed when the size is changed during the Form is undocked.

internal Size UndockedSize {
   get {
      return _undockedSize; 
   }
   Set {
      _undockedSize = value;
   }
}
#endregion

In the DockingManager.cs, you define on top of the namespace.

public enum DockType {
   None = 0, Top = 1, Bottom = 2, Left = 3, Right = 4, Fill = 5,
   Upper = 6, Lower = 7, LeftSide = 8, RightSide = 9, Center = 10,
}

To add one of the DockableForms Controls to your Dockable Panel, you need to do as the following method shows.

internal void AddFormControl(Control c) {
   if (c != null) {
      filler.Controls.Add(c);
      }
   }

After adding that properties, you will now need to add all the fields to store the data that you are exchanging by using the Properties.

#region Fields
// Class constants
private const int MIN_WIDTH = 150;
private const int MIN_HEIGHT = 174;
private const int HEAD_VERT = 24;
private const int verDiff = 28;
private const int horDiff = 9;
// Private Fields
private bool _isMouseDown = false;
private BorderStyle _borderStyle;
private bool _sizeable = false;
private bool _carrierAttached = false;
private Form _carrierForm;
private Image _windowImage= null;
private DockingManager _dockingManager;
private DockType _dockingType;
private string _key;
private bool _isTabbed = false;
private bool _pinned;
private Size _undockedSize;
// Boolean for disallowing resizing during reattachment to Carrier
private bool _noResize = false;

#endregion

Additionally, you need to override the Show method of the DockablePanel because now it depends on the fact of whether or not it is attached to the CarrierForm. If Attached, you also need to get the Carrier visible.

Note: By using the debugger, I have found that when the form is switched to be visible again, there is a resizing cycle going on that itself is influencing the dockable pane's size. This is not wanted, so set the _noResize boolean value to true and in the resize method I disallow resizing this way.
public new void Show(){
   if (_carrierAttached) {
      // reference the attached CarrierForm
      try {
         _noResize = true;    // no Resizing when only attaching
                              // the Form
         _carrierForm.Visible = true;
         _noResize = false;
      }
      catch { }
      base.Show();
   }
}

Also, you need to have a resizing method for the panel that disallows a size smaller then MIN_WIDTH or less then MIN_HEIGHT, so if someone tries to resize it, it's reset immediately. This method will allow you to put all elements in their correct place when you need to resize the DockablePanel. By using a debugger and setting a breakpoint into this resize method, you will see that resizing is done relatively often during the simple creation cycle you have here.

private void DockablePanel_Resize(object sender, EventArgs e) {



   if (_noResize) return;

   if (this.Width < MIN_WIDTH) { this.Width = MIN_WIDTH; }
   if (this.Height < MIN_HEIGHT) { this.Height = MIN_HEIGHT; }
   // everytime resizing the panel needs to resize the headerbox,
   // the filler and the GripTriangle 
   HeaderBox.Width = this.Width;
   filler.Width = this.Width - horDiff;
   filler.Height = this.Height - verDiff;
   filler.Left = 4;
   filler.Top = 24;
   picGripTriangle.Left = filler.Width - picGripTriangle.Width  - 2;
   picGripTriangle.Top = filler.Height - picGripTriangle.Height - 2;
}

Now, you need to get back to the DockableForm because you need to add the code there to create the dockablePanel.

Similar to the Form_Load Event, your DockableForm throws a CreateForm Event so you are able to use a dockableForm_CreateForm delegate in the MDIForm. To create this, you will need to create an adequate delegate that can be used for this event. So, add the following definition on top of the namespace and add the CreateForm event To the DockableForm class. Add this to the namespace DockingControls.Forms on top of the DockableForm.cs.

public delegate void InitFormEventDelegate(object sender, EventArgs e);

And, add the event itself just after the definitions of the const values horDiff and verDiff.

public event InitFormEventDelegate CreateForm;

Here's your method to create the DockablePanel as a fake of the DockableForm.

public void CreateNew() {
   EventArgs e = new EventArgs();
   // First we throw the CreateForm Event
   OnCreateForm(e);
   // Copies of properties
   _dockPanel.Caption = this.Text;
   _dockPanel.WindowImage = _windowImage;
   _dockPanel.Location = this.Location;
   Size panelSize = new Size(this.ClientSize.Width + horDiff,
                            this.ClientSize.Height + verDiff);
   _dockPanel.Size = panelSize;
   _dockPanel.FormBackColor = this.BackColor;
   _dockPanel.FormForeColor = this.ForeColor;
   _dockPanel.Carrier = this;
   _dockPanel.BorderStyle = _borderStyle;
   // transfer controls to Panel
   TransferControlsToPanel(_dockPanel);
   // DockPanel specific Properties
   _dockPanel.DockingManager = this._dockManager;
   // now we add the new Panel to the DockingManager
   _dockManager.AddPanel(_dockPanel);
}

This method transfers all needed controls to the dockablePanel. There is simply a list in it that controls it; it is not allowed to be transferred. All others are to be transferred.

private void TransferControlsToPanel(DockablePanel dockPanel) {
   for (int i = this.Controls.Count - 1; i >= 0; i--) {
      Control c = this.Controls[i];
      // exclude all that controls which should 
      // not be transferred.
      if (c.GetType() != typeof(ToolStrip)
         && c.GetType() != typeof(MenuStrip)
         && c.GetType() != typeof(ContextMenuStrip)
         && c.GetType() != typeof(ToolStripContainer)
         && c.GetType() != typeof(ToolStripButton)
         && c.GetType() != typeof(ToolStripLabel)
         && c.GetType() != typeof(StatusBarPanel)
         && c.GetType() != typeof(StatusStrip)
         && c.GetType() != typeof(ImageList)
         && c.GetType() != typeof(ToolStripMenuItem)
         && c.GetType() != typeof(ImageList)
         && c.GetType() != typeof(ToolStripStatusLabel)
         && c.GetType() != typeof(Timer)
         ) {
         dockPanel.AddFormControl(c);
      }
   }
}

Creating a Dockable Panel-Controlmanager Using C#, Part 1

The method fires the CreateForm event.

protected virtual void OnCreateForm(EventArgs e) {
   // First we check if there is any 'receiver' connected
   // to the event. If not, we don't fire.
   if (CreateForm != null) {
      CreateForm(this, e);
   }
}

Now, you need to add some different methods to the DockingManager so that it is able to manage all the different DockablePanels in DockingManagers.cs. You need to add a new namespace:

using DockingControls.Forms;

And into the DockingManager class, you add some fields.

private long  _count = 0;
SortedList<string,DockablePanel> AllDockPanels = new
                           SortedList<string,DockablePanel>();
SortedList<string, Form> AllCarriers = new 
   SortedList<string, Form>();

Also, you have to add some methods to get the dockingManager to work.

internal void AddPanel(DockablePanel dockPanel) {
   string key = "P" + _count.ToString();
   // each DockablePanel gets a new number 
   // independing of some are maybe closed again.
   // we start with 'P0' p for panel
   _count++;
   dockPanel.Key = key;
   AllDockPanels.Add(key, dockPanel);
   // now we attach the carrier to the DockablePanel;
   AttachCarrier(dockPanel);
}

private void AttachCarrier(DockablePanel dockPanel) {
   Form carrierForm = ChangeCarrierStyle(dockPanel);
   // we calculate the Position in screen coordinates now
   Point location = _admin.PanelScreenLocation(
      dockPanel.Left, dockPanel.Top);
   // we set correct position
   carrierForm.Location = location;
   // we dock the dockable Panel to the DockableForm 
   dockPanel.Dock = DockStyle.Fill;
   // we add the Panel to the controls of the form
   carrierForm.Controls.Add(dockPanel);
   // we set the Form Topmost now
   carrierForm.TopMost = true;
   // we set a flag  that the carrier is attached  
   // The flag is stored in the dockablePanel
   dockPanel.CarrierAttached = true;
   // is not visible at this time -> 
   // gets visible by show() command in the Users pgm
}

Remember, your Carrier in truth is the DockableForm and has borderstyle ToolsWindow. You urgently need to change that; otherwise, you cannot add your DockablePanel to it (you would get a Form with the Toolswindow and the HeaderBox of your DockablePanel also). So, you do this:

private Form ChangeCarrierStyle(DockablePanel dockPanel) {
   Form carrierForm = dockPanel.Carrier;
   carrierForm.StartPosition = FormStartPosition.Manual;
   carrierForm.BackColor = SystemColors.Control;
   // lets get it without any Border.
   carrierForm.FormBorderStyle = FormBorderStyle.None;
   // we switch of the controlbox additionally
   carrierForm.ControlBox = false; 
   // adapt the size to be exact the size 
   // of the DockablePanel
   carrierForm.Size = dockPanel.Size;
   // The carrier has now no Icon
   carrierForm.ShowIcon = false;
   // and no margin 
   carrierForm.Margin = new Padding(0);
   // and none of our DockableForms could
   // be seen in the taskbar
   carrierForm.ShowInTaskbar = false;
   // Padding is set to 0 for being sure 
   carrierForm.Padding = new Padding(0);
   // we store the key of our DockablePanel into the Carrier
   // so we have the panels key stored in the carrier and 
   // the carrieradress stored into the DockablePanel
   carrierForm.Tag = dockPanel.Key;
   // Now we add the Carrier to the Carriers SortedList
   AllCarriers.Add(dockPanel.Key, carrierForm); 
   // we use the same Key as the panel has
   return carrierForm;
}

There is one point still missing in this code. I don't know whether you have seen it or if you are lost in between by all this coding, but in the calculation of the Forms Location done in the AttachCarrier Method, you find:

   // we calculate the Position in screen coordinates now
   Point location = _admin.PanelScreenLocation(
                    dockPanel.Left, dockPanel.Top);

Here you see that this problem isn't really solved; it's only moved out of this method, out of the DockingManager, into a new class named DockAdministration where _admin is the representative for this class situated in the DockingManager.

The next step you have to take is to create a new class. This is no control; it's a class that you need to administer your panels, where they are, which size they actually have, are they docked or not, pinned or unpinned.... All that needs to be administered because it has an effect on where to place the next panel. You need this to calculate the places where your DockButtons should be placed and which size the transparent Form will have.

[HowToAdministrate.JPG]

Figure 18: Look at this figure to verify the parts you are talking about.

Create the DockAdministration class. During the progress of your program, other additional classes will follow. First, create a Folder named Classes and therein create a new class named DockAdministration.

The namespace should now be DockingControls.Classes and the class should be class DockAdministration that you turn into the internal class DockAdministration.

Create your PanelScreenLocation method.

#region Methods
internal Point PanelScreenLocation(int distanceX, int distanceY) {
   // distances for panelpositions in our code are related to the
   // Mdi windows Left/Top Corner
   // so this can be used even when the Mdi isn't the parent
   // of the control
   WinAPI.POINT location;
   location.x = distanceX;
   location.y = distanceY;
   // the MDIForm can be accessed as the parent of the DockingManager
   APICall.ClientToScreen(_manager.Parent.Handle, ref location);
    return new Point(location.x, location.y);
}
#endregion

This way, dear friends, you have solved one issue, but got two new ones. The first is that you need an API call; the second is created from the first because the API Call needs the Handle of the MDIForm on which your DockingManager is placed. This is because all the coordinates are used for DockablePanels on creation time, even when you are undocked in your thinking are related to the MDIForm no matter where it's just placed by the user on which screenposition ever.

The solution is easy. The form where you place the DockingManager (in the example, named MDIForm) is the Parent of the DockingManager.

Here we go.

#region Properties
internal DockingManager DockingManager {
   set {
      _manager = value;

   }
}

#endregion

And additionally, this way:

#region Constructors
// Constructor
internal DockAdministraton(DockingManager dockManager)
{
   _manager = dockManager;
}
#endregion

The API calls that you need are all listed together in an API Class that I have added for your convenience. So, please simply add the class to your project now in the following way: Create a new File named WinAPI just as shown—API in big letters. Then, create a class, API Calls, and a document named Structs. (Hint: I normally do that by creating a class that I have named Structs; then in the .cs files, simply delete the class.) This way, the namespace is already written and I didn't need to rename the file.

And here are the files; all of them should have namespace DockingControls.WinAPI. You will need two calculations, one from the ClientWindow Coordinates to the Screen Coordinates and one from Screen Coordinates back to CientCoordinates. Why this? Look at the following figure:

[ScreenCoordinatesClientCoordinates.JPG]

Figure 19: Screen Coordinates and Client Coordinates

As you see, the top/left point of that panel has two different coordinates you are interested in; it has coordinates relative to the MDI Form and coordinates relative to the Screens top/left corner. As soon as a panel is docked, its coordinates are related to the window where it is docked. When you release it, you need to know where it is on the screen. If you do not know that data, the panel would suddenly jump to any spot on the screen. For example, when you have docked left, the panel coordinates left and top would be 0 because they are relative to the MDI then and the window is docked to the left top corner of that MDI Clientwindow. If you release it, it doesn't recalculate its position; it would instantly jump to the left/top corner of the screen itself because its Property Left = 0 and Top = 0 too. The API methods available for that are ClientToScreen and ScreenToClient calculation Methods. Here it is how they are implemented

public class APICall{

   [DllImport("User32.dll", CharSet=CharSet.Auto)]
   public static extern bool ClientToScreen(IntPtr hWnd, ref POINT pt);

   [DllImport("User32.dll", CharSet=CharSet.Auto)]
   public static extern bool ScreenToClient(IntPtr hWnd, ref POINT pt);
}

Creating a Dockable Panel-Controlmanager Using C#, Part 1

In Structs.cs, you should have the following:

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
   public int left;
   public int top;
   public int right;
   public int bottom;
}

[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
   public int x;
   public int y;
}
[StructLayout(LayoutKind.Sequential)]
public struct SIZE
{
   public int cx;
   public int cy;
}

[StructLayout(LayoutKind.Sequential)]
public struct MouseHookStruct
{
   public POINT Point;
   public IntPtr HWnd;
   public uint HitTestCode;
   public UIntPtr ExtraInfo;
}

You will need these structs for usage together with the API methods. By using them in the shown way, they are usable together with these API calls because their construction in the memory is exactly as you need it in the API calls.

Attention: Because these structs are simply data in a sequential layout (that means one after the other), you don't need to create them using new! They can be used without creating them. Just define them and you are able to use them.

You just can see that here in your PanelScreenLocation(b&) Method:

   WinAPI.POINT location;
   location.x = distanceX;
   location.y = distanceY;

Now, you are nearly ready, but you still have not built up any connection to the DockingManager. In the AttachCarrier() method, which is part of the DockingManager, you call _admin.PanelScreenLocation(b&) and there you have the DockAdministrator _admin still undefined. In the DockAdministration itself, the Dockmanager isn't addressed and the DockAdministration class in truth is even still not created. You need to handle this. Because you need the DockAdministration, containing all methods needed for calculation of different conditions and positions permanent in your DockingManager (I created this class only to get a better logical structure; otherwise, all would have been done in the DockingManager), you should create this class as soon as possible. Why not create it in the constructor of the DockingManager itself? Add all the missing parts now.

  1. Add Namespace in the DockAdministration:
  2. using DockingControls.WinAPI
  3. Add Namespace in the DockingManager:
  4. using DockingControls.Classes; 
  5. Define the DockAdministrator and create it. The way you have built up your constructor, you do both missing connections at the same time. You add the DockAdministration class to the DockingManager and address the dockManager in the Administration class at the same time.

    Private DockAdministrtion _admin;
    // Constructor
    public DockingManager(){
       InitializeComponent();
       if (DesignMode == true) {
          this.Visible = true;
       } else {
          this.Visible = false;
       }
       _admin = new DockAdministraton(this);
    }
    

Now, you will try to use this DockableForm. As explained before, create a new 'Derived Form' using DockableForm in the DockingControlTestApp; name it TechForm. Assume you have a service business and want to create an MDI Form where you can have panels that show your staff and other panels that show all the job numbers that have been done from this staff on a specific day, so in the main Window of the MDI Form you may have an MDI childForm that would show a form to bill the work of that different technicians, so you can add their results to their personal stats and will know how much you have cashed for this day. You have data about personal stats and total cash for every day and you are able to write bills for the done work. All done on simple one screen using the panels you are creating.

Note: Because you are more interested in the panels here, all the parts of the database connection, and how to load data aren't really done. You only will show how to get the workable panels in this crash course.

You will get this:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace DockingControlTestApp
{
   public partial class TechForm : DockingControls.Forms.DockableForm
   {
      public TechForm() {
         InitializeComponent();
      }
   }
}

And your form will look like this in a moment.

[TechForm.JPG]

Figure 20: Your First Form derived from DockableForm

You will set the following Properties.

Table 10: TechForm Properties

Name TechForm
Size 200;286
Text List of Technicians

Now, add a ListView, named lstTechnicans, on it.

Table 11: ListView Properties

Name lstTechnicans
Margin 0;0;0;0
Size 187;254
View Details

Table 11 a: Columns (Listing)

Name Text Width
colTechnican Technican 120
colSign Sign 60

Tab 11 b: Items (Listing)

Member Text SubItem Text
0 John Jo
1 Roman Ro
2 Tim Ti
3 Yussef Yu
4 Mike Mi

The result at last should look like the following:

[ListOfTechnicans.JPG]

Figure 21: The TechForm as it looks now

You still haven't done in the MDIForm name the Button that shows 'AddTechList' as tsbAddTechList (but you should have done so in the beginning). Now, double-click on that button to create the Click Delegate of it and you will get the MDIForm.cs.

private void tsbAddTechList_Click(object sender, EventArgs e){
   //note: todo fill in Code   
}

This now needs to be filled with the following code that is the standard method to create a DockableForm.

private void tsbAddTechList_Click(object sender, EventArgs e) {
   // Create a new TechForm object 
   TechForm frmTechList = new TechForm();
   // we contact it with the DockingManager
   frmTechList.DockingManager = dockManager;
   // we set a starpoint relative to the MDIForm
   Point startPoint = new Point(150, 150);
   frmTechList.Location = startPoint;
   // and now we are creating the DockablePanel
   frmTechList.CreateNew();
   // now lest display it 
   frmTechList.Show();
    // Done! form shows on Screen!
}

This would work, but instead of this, use a more general method. Extract the essence of it and you get a method that can be used for any type of form derived from DockableForm.

private void CreateDockableForm(Point where, DockableForm frm) {
   // we contact it with the DockingManager
   frm.DockingManager = dockManager;
   // we set a starpoint relative to the MDIForm
   frm.Location = where;
   // and now we are creating the DockablePanel
   frm.CreateNew();
   // now lest display it
   frm.Show();
    // Done! form shows on Screen!
}

And instead of the above, simply do the following:

private void tsbAddTechList_Click(object sender, EventArgs e) {
   // Setting the Point where to be loacted
   Point where = new Point(150, 150);
   // Create a new TechForm object 
   TechForm frmTechList = new TechForm();
   // Create the DockablePanel using this TechForm
   CreateDockableForm(where, frmTechList);
}

[ProductLesson1.JPG]

Figure 22: The final product of the first part of the lecture.

Compile it. You will see your Buttonstrips and you will see the Form you have created, but don't wonder; you are not able to move it. That's quite normal because your Form in the moment has no CaptionBox and no border. All what you see is illusion (fake) only.

References & Credits

I confess. I studied lots of code to get my own solution, so in that way many thanks to fellows who provided their solution in the web. Projects like these helped me tremendously:

My great thanks to all those great programmers!

Conclusion

If you have any troubles with this code and you cannot debug it yourself, look at the sample included with this article. I have put the working code of what I have explained here, so you may compare your results with what I did.

Note: Every zip file will contain only the code up to that point that is explained in the article. The full code of the workable full version will be added in the last article. If you have any suggestions or questions, feel free to contact me.

I hope you had fun and enjoyed reading this article and we will meet again in the next article where you will get these panels moving.



About the Author

Johann Schwarz

I first encountered computers in 1968, since then, I've written several programs in various Languages such as FORTRAN, C, C++, VB 6, and C#. Mostly I am a hobbiest, self taught programmer, but my computer and my programming is my second life

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Today's agile organizations pose operations teams with a tremendous challenge: to deploy new releases to production immediately after development and testing is completed. To ensure that applications are deployed successfully, an automatic and transparent process is required. We refer to this process as Zero Touch Deployment™. This white paper reviews two approaches to Zero Touch Deployment--a script-based solution and a release automation platform. The article discusses how each can solve the key …

  • On-demand Event Event Date: December 18, 2014 The Internet of Things (IoT) incorporates physical devices into business processes using predictive analytics. While it relies heavily on existing Internet technologies, it differs by including physical devices, specialized protocols, physical analytics, and a unique partner network. To capture the real business value of IoT, the industry must move beyond customized projects to general patterns and platforms. Check out this webcast and join industry experts as …

Most Popular Programming Stories

More for Developers

RSS Feeds