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

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

Fly Panels, Fly!

Goals of this Article

What you want to achieve in this article is to get a borderless Form to be moved around on the screen by capturing it with the mouse and dragging it around, and to be able to resize the form, the same way as a normal sizeable Form could be sized by dragging its edges.

Note: To understand the design and construction of this DockablePanel Controler, you need to follow the steps in Part one of this article series. So, if you haven’t read Part One yet, please first read and download Part 1 of this article series.

This article will teach you how to work with the base class ‘Control‘ to pass Mouse events from controls that are placed on a Form to the Panel’s Mouse events in a manner that you will have the Panel’s MouseMove event thrown independent if the Form is covered with lots of different controls or there is only one very big control set on the Form, which covers it totally. You will be able to have the MouseMove event thrown wherever and whenever the mouse hovers over the Panel, This will be done without any usage of hooking. The funny trick on this is that, for the user, all this events seems to be the Forms mouse events, because at every time he never sees the original Form the programmer has created at Designtime. Let me give you a short review to fully get the point.

In the first part of this article series, you created the DockableForm. Remember, this Form in reality is created as a borderless Form. The surface you see on the screen is the DockablePanel where all the Controls you had on the Form will be added now with this article.

Figure 1: The DockableForm is fully covered with the DockablePanel control.

If you were to try to move the Form using the Mouse by dragging it around, nothing happens because:

  1. A borderless Form has no Titlebar that you can use to move it around.
  2. You never will reach the Form with any mouse action because the Form’s surface is totally covered by the Panel, and what you see as the Form’s Titlebar is actually your GradientPanel class, which is used here to build the Headerbox of your new Control.

To achieve this, you need to implement two operations; you need to get the Panel moving and you need to be able to resize the panel using the mouse by dragging its borders during runtime, just as a normal resizable Form acts.

To move a normal Form around, you move the mouse cursor over the Titlebar, press the left mouse button down, and keep the left mouse button depressed while moving the mouse around. When the left mouse button is released, the Form is no longer captured so you can stop moving the form. All this works only when the mouse is over the title of the Form; in this case, this is represented by the HeaderBox of the DockablePanel.

So, the first thing, obviously, you need to do is to create the MouseUp, MouseDown, and MouseMove delegates for the HeaderBox. To do this, select the DockablePanel; in the Designer View, select the HeaderBox; in the Properties Window choose ‘Events‘, and double-Click on the needed events. This will create the following code (add the #region markings so you keep a neat and tidy code).

#region Delegates
private void HeaderBox_MouseDown(object sender, MouseEventArgs e) {

}

private void HeaderBox_MouseUp(object sender, MouseEventArgs e) {

}

private void HeaderBox_MouseMove(object sender, MouseEventArgs e) {

}
#endregion

Now, you need to add code to these Delegates. To get the global Screen coordinates of your Mouse, you will use the API call ClientToScreen(), so add the necessary namespace on top of the DockablePanel codepage.

using DockingControls.WinAPI;
namespace DockingControls.Forms
{
 . . . .

internal sealed partial class DockablePanel : UserControl{
  . . . .
   // we add to the Private Fields just after
   private bool _isMouseDown = false;
   // the following needed fields
   private WinAPI.POINT _mousePos;
   private int _lastX;
   private int _lastY;

And in the MouseDown Delegate, you check whether the left Button was pressed when the MouseDown is fired.

private void HeaderBox_MouseDown(object sender, MouseEventArgs e) {
   if (e.Button == MouseButtons.Left) {
          LeftMouseButtonPressed(e);
   }
}

private void LeftMouseButtonPressed(MouseEventArgs e) {
   // store the actual mouseposition
   _mousePos.x = e.X;
   _mousePos.y = e.Y;
   // change it to global-Screen Coordinates so we have
   // the real position of the mouse instead of
   // its position in the header
   APICall.ClientToScreen(HeaderBox.Handle, ref _mousePos);
   // the internal notification that the mouse is just down
   _isMouseDown = true;
   // the global position of the DockablePanels Left-Top
   // corner is the global Position of the mouse
   // reduced by the position in the header, so we get
   _lastX = _mousePos.x - e.X;
   _lastY = _mousePos.y - e.Y;
}

This way, you get the start position of your DockableForm where it is placed on the screen, upon starting the Form move. The following picture demonstrates the coordinate calculations.

Figure 2: Calculation of Global Screen Coordinates of the Panels Left Top Corner

In the MouseUp Delegate, you simply set the internal notification (_isMouseDown) to false because you have released the button; it is not pressed down any more. At the moment, there is nothing else to do with this delegate.

private void HeaderBox_MouseUp(object sender, MouseEventArgs e){
    if (e.Button == MouseButtons.Left){
        _isMouseDown = false;
    }
}

With the MouseMove delegate, you need to check the value of the internal boolean (_isMouseDown) to determine whether the form should still move or not. If the mousebutton was released, _isMouseDown will be false, meaning you are no longer moving. This is done with the next code segment.

private void HeaderBox_MouseMove(object sender, MouseEventArgs e) {
   if (_isMouseDown ){
       MouseMoves(e);
   }
}

private void MouseMoves(MouseEventArgs e){
    WinAPI.POINT mousePos;
    // we read the actual mouse position, which will change
    // during moving
    mousePos.x = (short)e.Location.X;
    mousePos.y = (short)e.Location.Y;
    // we calculate the global Position again
    APICall.ClientToScreen(HeaderBox.Handle, ref mousePos);
    // and the moving method itself
    Moving(mousePos);
}

With the Moving procedure (which follows next), you need to ensure that you are attaching and detaching the panel to and from the carrier form, depending whether it is moved or docked. Basically, with this method you are using the difference between the previous position and the current position.

private WinAPI.POINT Moving(WinAPI.POINT mousePos){

if (_carrierAttached){
      _carrierForm.Left += (mousePos.x - _mousePos.x);
      _carrierForm.Top  += (mousePos.y - _mousePos.y);
   }else{
      // if all is working well this only will happen
      // in the first moment, when we are remove a docked panel
      // from its docking position
      this.Left += (mousePos.x - _mousePos.x);
      this.Top += (mousePos.y  - _mousePos.y);
   }
   _mousePos.x = mousePos.x;
   _mousePos.y = mousePos.y;
   return mousePos;
}

If you were to compile and run the project, you would be able to move the panel in the same way as you can move a form—through its Titlebar.

A Note before compiling the project: Remember that, back in Part 1 when you created the buttonstrip, you made them as visible. Now, because they are in the correct place, you should hide them because you want them hidden when the program starts.

Edit CreateAllElements in the DockingManager class, to look like the following, to hide the Buttonstrips when the program starts:

public void CreateAllElements() {
   // Buttonstrips to hide the panels
   LeftButtonStrip   = CreateButtonStrip(DockStyle.Left,false );
   RightButtonStrip  = CreateButtonStrip(DockStyle.Right,false);
   TopButtonStrip    = CreateButtonStrip(DockStyle.Top, false);
   BottomButtonStrip = CreateButtonStrip(DockStyle.Bottom,false);
}

More by Author

Must Read