C # automatically hide the component by red_angelx
Http://www.csharphelp.com/archives/archive150.html
Introduction
One of the first features of C # that took my interest was the abilityDock
AControl
Onto the edge ofForm
. Now I cocould attachControl
(Or more likely a composite control by deriving fromUsercontrol
) OntoForm
Edge and quickly construct a useful looking application. but there is one crucial factor missing from this scenario. the user has no discretion over the size or positioning of this docked control. I want the user to be able to drag the control to a different edge and be able to resize the control so that they can customise the application area to suit their own preferences.
Composite Control for docking
to solve this problem we need to create a new composite control dockingcontrol
that is able to take a caller supplied control and manage its position and sizing. our composite control will need a resizing bar; a grab handle area that can be used to move its docking position and a place for the caller supplied Control to be displayed.
class dockingcontrol: usercontrol {private form _ form; private dockingresize _ resize; private dockinghandle _ HANDLE; private bordercontrol _ wrapper; Public dockingcontrol (Form form, dockstyle ds, control usercontrol) {// remember the form we are hosted on_form = form; // create the resizing bar, gripper handle and border control_resize = new dockingresize (DS ); _ HANDLE = new dockinghandle (this, DS); _ wrapper = new bordercontrol (usercontrol); // wrapper shoshould always fill remaining area_wrapper.dock = dockstyle. fill; // define our own initial docking position for when we are added to host formthis. dock = Ds; controls. addrange (new control [] {_ wrapper, _ HANDLE, _ resize});} public form hostform {get {return _ form ;}
the final line of code in the instance constructor adds the three child controls _ wrapper
, _ HANDLE
and _ resize
. the order of the controls in the initializer list is absolutely crucial because when the dockingcontrol
(or any other Control
) has its dock
style changed this ordering determines the position and size of the child controls. calculations are made starting with the last control added (which equates to last entry in the initializer list) back towards the first, which is the exact opposite of what I wowould have expected.
The_ Resize
Bar is first to be positioned (and so last in initializer list) as it shoshould always be shown spanning the entire length of the docking control. Next is_ HANDLE
As it shoshould be positioned under the sizing bar and finally_ Wrapper
Control, this is last because it always hasDock
StyleFill
And we want it to take up whatever space is leftover when all the other controls have finished being laid out.
Change of docking position
When the docking position of our composite control is changed we need to ensure that our child controls are also correctly positioned for the new docking style. So weOverride
The inheritedDock
Property and recalculate the correct size and positions as appropriate.
// Override the base class property to allow extra workpublic override dockstyle dock {get {return base. dock;} set {// Our size before docking position is changedsize size = This. clientsize; // remember the current docking positiondockstyle dsoldresize = _ resize. dock; // new handle size is dependant on the orientation of the new docking position_handle.sizetoorientation (value); // modify docking positi On of child controls based on our new docking position_resize.dock = dockingcontrol. resizestylefromcontrolstyle (value); _ HANDLE. dock = dockingcontrol. handlestylefromcontrolstyle (value); // now safe to update ourself through base classbase. dock = value; // change in orientation occured? If (dsoldresize! = _ Resize. dock) {// must update our client size to ensure the correct size is used when // the docking position changes. we have to transfer the value that determines // the vector of the control to the opposite dimensionif (this. dock = dockstyle. top) | (this. dock = dockstyle. bottom) size. height = size. width; elsesize. width = size. height; this. clientsize = size;} // repaint our controls_handle.invalidate (); _ resize. invalidate ();}}
Two static functions ( Resizestylefromcontrolstyle
And Handlestylefromcontrolstyle
) Are used to find the correct docking style for _ Resize
And _ HANDLE
Controls dependant on the new Dock
Style. Of special note is the code that checks for a change in docking orientation and then changes Width
Or Height
Of the control. Remember that when our control is docked to the top or bottom of the form then Width
Of the control is calculated for us by the form and Height
Determines how far inwards the docking control extends. When the orientation moves to be left or right then we need to updateWidth
Of the control to reflect how far from the edge we want the control to extend. So the new Width
Shocould be the old Height
, Otherwise Width
Will remain the same as the entire width of the form and so it wowould fill the entire client area.
The rest ofDockingcontrol
Class follows and consists of Static Properties for recovering GDI + objects (to be used by the child controls for drawing) and the previusly mentioned static methods used for calculating the new docking position of each child control based on the new position ofDockingcontrol
.
// Static variables defining colors for drawingprivate static pen _ lightpen = new pen (color. fromknowncolor (knowncolor. controllightlight); Private Static pen _ darkpen = new pen (color. fromknowncolor (knowncolor. controldark); Private Static brush _ plainbrush = brushes. lightgray; // static properties for read-only access to drawing colorspublic static pen lightpen {get {return _ lightpen;} public stat IC pen darkpen {get {return _ darkpen;} public static brush plainbrush {get {return _ plainbrush;} public static dockstyle resizestylefromcontrolstyle (dockstyle DS) {Switch (DS) {Case dockstyle. left: Return dockstyle. right; Case dockstyle. top: Return dockstyle. bottom; Case dockstyle. right: Return dockstyle. left; Case dockstyle. bottom: Return dockstyle. top; default: // shocould never happen! Throw new applicationexception ("invalid dockstyle argument") ;}} public static dockstyle handlestylefromcontrolstyle (dockstyle DS) {Switch (DS) {Case dockstyle. left: Return dockstyle. top; Case dockstyle. top: Return dockstyle. left; Case dockstyle. right: Return dockstyle. top; Case dockstyle. bottom: Return dockstyle. left; default: // shocould never happen! Throw new applicationexception ("invalid dockstyle argument ");}}}
Resizing
Our first child control is calledDockingresize
And provides an area of the docking control that the user can drag for resizing. notice that when the mouse is clickedOnmousedown
Remembers the current size of the parentDockingcontrol
And the screen position of the mouse. This is necessary so that whenOnmousemove
Is already ed it can calculate how far the mouse has been moved since it was pressed and so the new sizeDockingcontrol
. Also notice that it will set the cursor to indicate a resizing operation is allowed.
// A bar used to resize the parent dockingcontrolclass dockingresize: usercontrol {// class constantsprivate const int _ fixedlength = 4; // instance variablesprivate point _ pointstart; private point _ pointlast; private size _ size; Public dockingresize (dockstyle DS) {This. dock = dockingcontrol. resizestylefromcontrolstyle (DS); this. size = new size (_ fixedlength, _ fixedlength);} protected override void onmous Edown (mouseeventargs e) {// remember the mouse position and client size when capture occured_pointstart = _ pointlast = pointtoscreen (new point (e. x, E. y); _ size = parent. clientsize; // ensure delegates are calledbase. onmousedown (E);} protected override void onmousemove (mouseeventargs e) {// cursor depends on if we a vertical or horizontal resizeif (this. dock = dockstyle. top) | (this. dock = dockstyle. Bottom) This. cursor = cursors. heatmap; elsethis. cursor = cursors. vsplit; // can only resize if we have captured the mouseif (this. capture) {// find the new mouse positionpoint point = pointtoscreen (new point (e. x, E. y); // have we actually moved the mouse? If (point! = _ Pointlast) {// update the last processed mouse position_pointlast = point; // find Delta from original positionint xdelta = _ pointlast. x-_ pointstart. x; int ydelta = _ pointlast. y-_ pointstart. y; // resizing from bottom or right of form means inverse movementsif (this. dock = dockstyle. top) | (this. dock = dockstyle. left) {xdelta =-xdelta; ydelta =-ydelta;} // new size is original size plus deltaif (this. dock = dockstyle. top) | (this. dock = dockstyle. bottom) parent. clientsize = new size (_ size. width, _ size. height + ydelta); elseparent. clientsize = new size (_ size. width + xdelta, _ size. height); // force a repaint of parent so we can see changed appearanceparent. refresh () ;}}// ensure delegates are calledbase. onmousemove (E );}
The only other work needed in this class is the overrideOnpaint
That is used to draw the 3D appearance of the resizing bar itself. It uses static methods fromDockingcontrol
To recover the correct GDI + objects to use.
Protected override void onpaint (painteventargs PE) {// create objects used for drawingpoint [] ptlight = new point [2]; point [] ptdark = new point [2]; rectangle rectmiddle = new rectangle (); // drawing is relative to client areasize sizeclient = This. clientsize; // painting depends on orientationif (this. dock = dockstyle. top) | (this. dock = dockstyle. bottom) {// draw as a horizontal barptdark [1]. y = ptdark [0]. y = sizeclient. height-1; ptlight [1]. X = ptdark [1]. X = sizeclient. width; rectmiddle. width = sizeclient. width; rectmiddle. height = sizeclient. height-2; rectmiddle. X = 0; rectmiddle. y = 1;} else if (this. dock = dockstyle. left) | (this. dock = dockstyle. right) {// draw as a vertical barptdark [1]. X = ptdark [0]. X = sizeclient. width-1; ptlight [1]. y = ptdark [1]. y = sizeclient. height; rectmiddle. width = sizeclient. width-2; rectmiddle. height = sizeclient. height; rectmiddle. X = 1; rectmiddle. y = 0;} // use colors defined by docking control that is using uspe. graphics. drawline (dockingcontrol. lightpen, ptlight [0], ptlight [1]); PE. graphics. drawline (dockingcontrol. darkpen, ptdark [0], ptdark [1]); PE. graphics. fillrectangle (dockingcontrol. plainbrush, rectmiddle); // ensure delegates are calledbase. onpaint (PE );}}
Dragging
Our next child ControlDockinghandle
Has three tasks to perform. It must first of all ensure that it is sized correctly to reflect the current orientation of the parentDockingcontrol
. One of our dimensions will always be calculated determined for us as we are docked to one of the parent control edges. however, the other dimension showould always be fixed to reflect the space needed for drawing and allowing the user to grab it. the routineSizetoorientation
Performs this demo.
class dockinghandle: usercontrol {// class constantsprivate const int _ fixedlength = 12; private const int _ hotlength = 20; private const int _ offset = 3; private const int _ inset = 3; // instance variablesprivate dockingcontrol _ dockingcontrol = NULL; Public dockinghandle (dockingcontrol, dockstyle DS) {_ dockingcontrol = dockingcontrol; this. dock = dockingcontrol. handlestylefromcontrolstyle (DS); sizetoorientation (DS);} public void sizetoorientation (dockstyle DS) {If (DS = dockstyle. top) | (DS = dockstyle. bottom) This. clientsize = new size (_ fixedlength, 0); elsethis. clientsize = new size (0, _ fixedlength) ;}
the second task and the most interesting is saved med inside onmousemove
. here we need to convert the mouse position from our own client position to the client position in the host form. by testing how near the cursor is to each edge of the form we can decide which edge shocould become the new docking position of the parent dockingcontrol
. at the moment the Code uses a constant value of _ hotlength
to decide if the mouse is close enough to an edge for the docking edge to be changed. actually causing the docking to change is trivial, just change the dock
property on the dockingcontrol
.
Protected override void onmousemove (mouseeventargs e) {// can only move the dockingcontrol is we have captured the // mouse otherwise the mouse is not currently pressedif (this. capture) {// must have reference to parent objectif (null! = _ Dockingcontrol) {This. cursor = cursors. hand; // convert from client point of dockinghandle to client of dockingcontrolpoint screenpoint = pointtoscreen (new point (e. x, E. y); point parentpoint = _ dockingcontrol. hostform. pointtoclient (screenpoint); // find the client rectangle of the formsize parentsize = _ dockingcontrol. hostform. clientsize; // new docking position is defaulted to current styledockstyl E ds = _ dockingcontrol. dock; // find new docking positionif (parentpoint. x <_ hotlength) {DS = dockstyle. left;} else if (parentpoint. Y <_ hotlength) {DS = dockstyle. top;} else if (parentpoint. x> = (parentsize. width-_ hotlength) {DS = dockstyle. right;} else if (parentpoint. y> = (parentsize. height-_ hotlength) {DS = dockstyle. bottom;} // update docking position of dockingcontrol we are part ofif (_ dockin Gcontrol. Dock! = DS) _ dockingcontrol. Dock = DS ;}} elsethis. cursor = cursors. Default; // ensure delegates are calledbase. onmousemove (E );}
lastly the control needs to draw the two lines that decorate the control area.
Protected override void onpaint (painteventargs PE) {size sizeclient = This. clientsize; point [] ptlight = new point [4]; point [] ptdark = new point [4]; // depends on orientationif (_ dockingcontrol. dock = dockstyle. top) | (_ dockingcontrol. dock = dockstyle. bottom) {int ibottom = sizeclient. height-_ inset-1; int iright = _ Offset + 2; ptlight [3]. X = ptlight [2]. X = ptlight [0]. X = _ offset; ptlight [2]. y = ptlight [1]. y = ptlight [0]. y = _ inset; ptlight [1]. X = _ Offset + 1; ptlight [3]. y = ibottom; ptdark [2]. X = ptdark [1]. X = ptdark [0]. X = iright; ptdark [3]. y = ptdark [2]. y = ptdark [1]. y = ibottom; ptdark [0]. y = _ inset; ptdark [3]. X = iright-1;} else {int ibottom = _ Offset + 2; int iright = sizeclient. width-_ inset-1; ptlight [3]. X = ptlight [2]. X = ptlight [0]. X = _ inset; ptlight [1]. y = ptlight [2]. y = ptlight [0]. y = _ offset; ptlight [1]. X = iright; ptlight [3]. y = _ Offset + 1; ptdark [2]. X = ptdark [1]. X = ptdark [0]. X = iright; ptdark [3]. y = ptdark [2]. y = ptdark [1]. y = ibottom; ptdark [0]. y = _ offset; ptdark [3]. X = _ inset;} pen lightpen = dockingcontrol. lightpen; pen darkpen = dockingcontrol. darkpen; PE. graphics. drawline (lightpen, ptlight [0], ptlight [1]); PE. graphics. drawline (lightpen, ptlight [2], ptlight [3]); PE. graphics. drawline (darkpen, ptdark [0], ptdark [1]); PE. graphics. drawline (darkpen, ptdark [2], ptdark [3]); // shift coordinates to draw section grab barif (_ dockingcontrol. dock = dockstyle. top) | (_ dockingcontrol. dock = dockstyle. bottom) {for (INT I = 0; I <4; I ++) {ptlight [I]. X + = 4; ptdark [I]. X + = 4 ;}} else {for (INT I = 0; I <4; I ++) {ptlight [I]. Y + = 4; ptdark [I]. Y + = 4 ;}} PE. graphics. drawline (lightpen, ptlight [0], ptlight [1]); PE. graphics. drawline (lightpen, ptlight [2], ptlight [3]); PE. graphics. drawline (darkpen, ptdark [0], ptdark [1]); PE. graphics. drawline (darkpen, ptdark [2], ptdark [3]); // ensure delegates are calledbase. onpaint (PE );}}
Shrink the user supplied Control
When developing this code I noticed that placing the user supplied Control forDockingcontrol
Into the control wocould push that control right up against the edges of the resize bar and grab area. although there is nothing wrong with this it didn't look very tidy, so instead I use this small helper class that places a border around the provided control. theDockingcontrol
Creates an instance of thisBordercontrol
Passing in the user supplied control. Then thisBordercontrol
Is used to fillDockingcontrol
Rather than the user supplied one.
// Position the provided control inside a border to give a portrait picture extends tclass bordercontrol: usercontrol {// instance variablesprivate int _ borderwidth = 3; private int _ borderdoublewidth = 6; private control _ usercontrol = NULL; Public bordercontrol (control usercontrol) {_ usercontrol = usercontrol; controls. add (_ usercontrol);} // must reposition the embedded control whenever we change sizeprot Ected override void onresize (eventargs e) {// can be called before instance constructorif (null! = _ Usercontrol) {size sizeclient = This. size; // move the user control to enforce the border area we want_usercontrol.location = new point (_ borderwidth, _ borderwidth); _ usercontrol. size = new size (sizeclient. width-_ borderdoublewidth, sizeclient. height-_ borderdoublewidth);} // ensure delegates are calledbase. onresize (e );}}
Conclusion
this code was developed using a text editor and then calling the C # compiler on a command line. therefore the look is modelled on the appearance of the vc6 environment (which I have) and not the newer look and feel of docking controls/Windows from vc7 (which I don't have ). I think it wocould be very easy to build on this code to allow floating controls and multiple docking controls in a docking bar. if you have any ideas or make any changes to the Code then feel free to contact me as I wocould be very interested.