473,413 Members | 1,713 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes and contribute your articles to a community of 473,413 developers and data experts.

Creating custom controls -- A movable, resizable container control

GaryTexmo
1,501 Expert 1GB
In my last insight, http://bytes.com/topic/c-sharp/insig...ng-resolutions, I talked about scaling objects to a form's size so that it would always draw with the correct scale. In that, the method I used to create and store objects that could be drawn on my form was a manual approach; that is, I created and stored a list of objects, then manually drew them.

While that certainly works, it means there's a lot of code to be written if i wanted to make them interactive. Say, if I wanted to make them movable I'd have to scan every object in my list and check to see if my mouse point was within the bounds of the control. I'd also have to implement sort orders to make sure that I was only getting the top most item. This is certainly possible (and sometimes even fun!) but you might be thinking to yourself, "Waaaaaaaaitaminute, hasn't someone already done this for me?"

Well, if you were, you're absolutely right! Microsoft has provided us the Control Class that we can inherit from and get a lot of this functionality by default, we just need to implement what we want. Then we can create an object and throw it into the Controls list of anything that has one.

The example I'm going to go through here is creating a custom control called a NamedContainer, which will draw what appears to be a window and will allow the user to move and resize it. You will be able to specify the title text and the colour of the title bar.

Ok, lets get started. First thing's first, create a new VS Windows Forms project and put a panel on your form. I also like to set the background colour to pink so that I can differentiate it more easily from the form. Make your form fairly large and make sure your panel's anchors are set such that it will resize with the form.

Now lets get to the meat of things. Lets create our named container class.

Expand|Select|Wrap|Line Numbers
  1. public class NamedContainer : Control
  2. {
  3. }
This is actually enough to start adding NamedContainers to your panel's Controls list, of course, nothing will happen because it's not actually drawing anything. Lets fix that by overriding the OnPaint event and drawing something.

Expand|Select|Wrap|Line Numbers
  1. protected override void OnPaint(PaintEventArgs e)
  2. {
  3.     Graphics g = e.Graphics;
  4.  
  5.     // Draw Background
  6.     g.FillRectangle(new SolidBrush(this.BackColor), this.ClientRectangle);
  7.  
  8.     base.OnPaint(e);
  9.  
  10.     // Draw Border
  11.     g.DrawRectangle(Pens.Black, this.ClientRectangle.X, this.ClientRectangle.Y, this.ClientRectangle.Width - 1, this.ClientRectangle.Height - 1);
  12. }
NOTE: ClientRectangle defines the bounds of the Control's drawable area. Also, while I haven't found a way to prevent it even by excluding the call, I include the base paint event after my background draw but before my border draw. One of those things we're just not going to worry about right now.

Now you can add the following code to your form's constructor...

Expand|Select|Wrap|Line Numbers
  1. NamedContainer c = new NamedContainer();
  2. c.Location = new Point(50, 50);
  3. c.Size = new Size(300, 150);
  4. panel1.Controls.Add(c);



Sweet, we've got a rectangle! Lets do something a little more exciting with it, shall we? I mentioned wanting to draw a title bar and making it look like a window. Lets go ahead and do that, but first lets make a few properties so that we can easily get the size of our title bar and the colour of it.

Expand|Select|Wrap|Line Numbers
  1. [Browsable(false)]
  2. public Rectangle TitleBarRectangle
  3. {
  4.     get { return new Rectangle(this.ClientRectangle.X, this.ClientRectangle.Y, this.ClientRectangle.Width, (int)(this.Font.Height * 1.25)); }
  5. }
  6.  
  7. [Category("Appearance")]
  8. public Color TitleBarColor { get; set; }
I've decided here that our title bar will be the width of the entire form and will also be 1.25 times the height of the font, which should give some nice padding around the text we'll put inside it. I also set Browsable on TitleBarRectangle to false here because, since this object inherits from Control, it can show up in the Toolbox in the Form Designer for Visual Studio. I don't want the designer to see this particular property, especially because it's read only. For the TitleBarColor (I used an auto-implement property here, requires VS 2008 or higher), I want the designer to see it but I set the category so it shows up in Appearance along with other, similar properties.

We can now add code to the OnPaint to draw the title bar by drawing a filled rectangle on our TitleBarRectangle, and drawing the text centered inside the TitleBarRectangle. Put this directly after drawing the background.

Expand|Select|Wrap|Line Numbers
  1. // Draw Titlebar
  2. g.FillRectangle(new SolidBrush(this.TitleBarColor), this.TitleBarRectangle);
  3. g.DrawString(this.Text, this.Font, new SolidBrush(this.ForeColor), new Point(this.ClientRectangle.X + (int)(this.ClientRectangle.Width / 2 - g.MeasureString(this.Text, this.Font).Width / 2), this.ClientRectangle.Y + this.TitleBarRectangle.Height / 2 - this.Font.Height / 2));
Also, add the following line just after the call that draws our border rectangle so we get an outline for our title bar.

Expand|Select|Wrap|Line Numbers
  1. g.DrawLine(Pens.Black, new Point(this.ClientRectangle.X, this.TitleBarRectangle.Bottom), new Point(this.TitleBarRectangle.Right, this.TitleBarRectangle.Bottom));
Lets also take this opportunity to add a constructor and set a few values by default.

Expand|Select|Wrap|Line Numbers
  1. public NamedContainer()
  2.     :base()
  3. {
  4.     this.DoubleBuffered = true;
  5.     this.Size = new Size(300, 150);
  6.     this.TitleBarColor = SystemColors.ActiveCaption;
  7.     this.ForeColor = SystemColors.ActiveCaptionText;
  8. }
NOTE: I set the DoubleBuffered property to true so that we can improve our drawing performance. This will be more important later on.

Lets also update the code to create our object...

Expand|Select|Wrap|Line Numbers
  1. NamedContainer c = new NamedContainer();
  2. c.Location = new Point(50, 50);
  3. c.Size = new Size(300, 150);
  4. c.Text = "Our Named Container!";
  5. c.BackColor = Color.White;
  6. panel1.Controls.Add(c);
Now lets see what we have...



Oh hey, we're recreating Windows 3.1!

Up until now, all we've done we really could have done with the same methods we used in the object scaling example, so lets start doing something a little more exciting. I mentioned moving and resizing, so lets start with moving. To start, lets make an enumeration that we'll use to tell how we are moving our form.

Expand|Select|Wrap|Line Numbers
  1. private enum MoveMode
  2. {
  3.     NotMoving,
  4.     Form,
  5.     ResizeBoth
  6. }
Now, lets add two private member variables to our class. One will store the location the user clicked at, so we can use it to offset the movement of our object. The other will just store the type of moving we're currently doing.

Expand|Select|Wrap|Line Numbers
  1. private Point? m_clickLoc = null;
  2. private MoveMode m_moveMode = MoveMode.NotMoving;
Since we may or may not want our object to be movable, lets add a property to control this. I'm going to assign it to the Behavior category and give it a default value of true.

Expand|Select|Wrap|Line Numbers
  1. [Category("Behavior"), DefaultValue(true)]
  2. public bool Movable { get; set; }
Note that setting DefaultValue like this doesn't actually assign anything, it just tells the designer that the current value is either the default value or not. This means we should update our constructor to set a default value. I want my control to default to movable, so I added the following to my constructor...

Expand|Select|Wrap|Line Numbers
  1. this.Movable = true;
The next step is to do something when the mouse moves. Since we are inheriting from a control, we actually have a method available to use already. We can override the OnMouseMove event and do whatever logic we wish in there. In that mouse event, we want to...
  • Check to see if we are allowing our control to move.
  • Check to see if the left mouse button is pressed (as I've decided to say it's the left button that controls these things).
  • If our last click location doesn't exist, check to see if the mouse cursor is in a place where it can move things.
  • If it is, set the move mode (and update the last click location).
  • Now check if we're moving.
  • Check how we're moving and take the appropriate action.
  • If a mouse button isn't down, update the Control's cursor to reflect what it's hovering over.

So in writing the code for this...

Expand|Select|Wrap|Line Numbers
  1. protected override void OnMouseMove(MouseEventArgs e)
  2. {
  3.     // We only want stuff to happen if we say it can.
  4.     if (this.Movable)
  5.     {
  6.         if (e.Button == MouseButtons.Left)
  7.         {
  8.             // Figure out what we're doing... if it's something, we want to store the last click location.
  9.             if (m_clickLoc == null)                        
  10.             {
  11.                 if (this.TitleBarRectangle.Contains(e.Location))
  12.                     m_moveMode = MoveMode.Form;
  13.                 else
  14.                     m_moveMode = MoveMode.NotMoving;
  15.  
  16.                 if (m_moveMode != MoveMode.NotMoving)
  17.                     m_clickLoc = new Point(e.X, e.Y);
  18.             }
  19.  
  20.             // If we're not, not moving, figure out what we should be doing! (oooh poetry)
  21.             if (m_moveMode != MoveMode.NotMoving)
  22.             {
  23.                 switch (m_moveMode)
  24.                 {
  25.                     case MoveMode.Form:
  26.                         // Just moving, update the form's location
  27.                         this.Location = new Point(this.Location.X + e.X - m_clickLoc.Value.X, this.Location.Y + e.Y - m_clickLoc.Value.Y);
  28.                         break;
  29.                 }
  30.             }
  31.         }
  32.         else
  33.         {
  34.             // No desired button pressed lets clear all our states
  35.             m_moveMode = MoveMode.NotMoving;
  36.             m_clickLoc = null;
  37.  
  38.             // Update the cursor based on what it moved over
  39.             if (this.TitleBarRectangle.Contains(e.Location))
  40.                 this.Cursor = Cursors.SizeAll;
  41.             else
  42.                 this.Cursor = Cursors.Default;
  43.         }
  44.     }
  45.  
  46.     base.OnMouseMove(e);
  47. }
Now when you run your code, you should be able to hover your mouse over the title bar of your control. The cursor should change to one that indicates moving, at which point you can click and drag around to your heart's content!

Once you're done that, we can add support for resizing. First, we're going to have to draw a resize control on our forum, so lets add a property to define where that will be. I've decided it should be in the bottom right of the control and will be about 15x15 pixels.

Expand|Select|Wrap|Line Numbers
  1. [Browsable(false)]
  2. public Rectangle ResizeHandleRectangle
  3. {
  4.     get { return new Rectangle(this.ClientRectangle.Width - 15, this.ClientRectangle.Height - 15, 15, 15); }
  5. }
Then in the OnPaint method, lets draw something to indicate a resize handle. We only need to draw this when the form is able to move.

Expand|Select|Wrap|Line Numbers
  1. // Draw Resize Anchor if it can be used
  2. if (this.Movable)
  3. {
  4.     g.DrawLine(Pens.Black, this.ResizeHandleRectangle.Right - 3, this.ResizeHandleRectangle.Top + 3, this.ResizeHandleRectangle.Left + 3, this.ResizeHandleRectangle.Bottom - 3);
  5.     g.DrawLine(Pens.Black, this.ResizeHandleRectangle.Right - 3, this.ResizeHandleRectangle.Top + 6, this.ResizeHandleRectangle.Left + 6, this.ResizeHandleRectangle.Bottom - 3);
  6.     g.DrawLine(Pens.Black, this.ResizeHandleRectangle.Right - 3, this.ResizeHandleRectangle.Top + 9, this.ResizeHandleRectangle.Left + 9, this.ResizeHandleRectangle.Bottom - 3);
  7. }
This can go after the base.OnPaint call.

Running the code produces...



Look at that slick little resize handle! Ok, I'll be honest, I blatantly stole it from the one staring me in the face for Visual Studio. Admittedly, theirs is also cooler... you can draw whatever you like, or even use an image if you wish.

Now we need to modify the code so that it supports resizing. We already have the enum code, ResizeBoth (both meaning horizontal and vertical) so we really just need to use it. We're also going to want a way for our form to know what its minimum size is. This is because we don't want to allow the control to resize down to a speck. A reasonably good minimum size might be the width of the text we draw and twice the height of the title bar. That way we should still be able to resize it again to larger if we want. Lets create a private method that will give us a minimum size based on a string and a font.

Expand|Select|Wrap|Line Numbers
  1. private Size GetMinimumSize(string text, Font font)
  2. {
  3.     Graphics g = this.CreateGraphics();
  4.  
  5.     return new Size((int)g.MeasureString(text, font).Width, this.TitleBarRectangle.Height * 2);
  6. }
Now lets make a private variable to store the minimum size.

Expand|Select|Wrap|Line Numbers
  1. private Size m_minimumSize;
Because we inherit from Control and the Text or Font can be changed via a property, we'll want to override those properties so that we can update the minimum size anytime Text and Font are set.

Expand|Select|Wrap|Line Numbers
  1. public override string Text
  2. {
  3.     get
  4.     {
  5.         return base.Text;
  6.     }
  7.     set
  8.     {
  9.         base.Text = value;
  10.  
  11.         // The minimium size is based on the area the text takes up so we need to recalculate when the text changes.
  12.         m_minimumSize = this.GetMinimumSize(this.Text, this.Font);
  13.     }
  14. }
  15.  
  16. public override Font Font
  17. {
  18.     get
  19.     {
  20.         return base.Font;
  21.     }
  22.     set
  23.     {
  24.         base.Font = value;
  25.  
  26.         // The minimium size is based on the area the text takes up so we need to recalculate when the font changes.
  27.         m_minimumSize = this.GetMinimumSize(this.Text, this.Font);
  28.     }
  29. }
In the OnMouseMove method, where we detect if the click location does not exist (is null), we can add an else if condition to detect resize...

Expand|Select|Wrap|Line Numbers
  1. if (m_clickLoc == null)                        
  2. {
  3.     if (this.TitleBarRectangle.Contains(e.Location))
  4.         m_moveMode = MoveMode.Form;
  5.     else if (this.ResizeHandleRectangle.Contains(e.Location))
  6.         m_moveMode = MoveMode.ResizeBoth;
  7.     else
  8.         m_moveMode = MoveMode.NotMoving;
  9.  
  10.     if (m_moveMode != MoveMode.NotMoving)
  11.         m_clickLoc = new Point(e.X, e.Y);
  12. }
We can also add code to the switch statement to handle the resize.

Expand|Select|Wrap|Line Numbers
  1. case MoveMode.ResizeBoth:
  2.     Size newSize = new Size(this.Size.Width + e.X - m_clickLoc.Value.X, this.Size.Height + e.Y - m_clickLoc.Value.Y);
  3.     bool needsRedraw = false;
  4.  
  5.     // Handle width and heigh resizing separately so that when we hit the minimum size for one, we can still resize the other.
  6.     // Also, only allow sizing if the particular dimension of the new size is greater than the corresponding minimum.
  7.     // Finally, if we do a resize, we need to force a redraw and update the click location so we always resize based on a delta movement.
  8.  
  9.     if (newSize.Width > m_minimumSize.Width)
  10.     {
  11.         this.Size = new Size(newSize.Width, this.Size.Height);
  12.         m_clickLoc = new Point(e.X, m_clickLoc.Value.Y);
  13.         needsRedraw = true;
  14.     }
  15.  
  16.     if (newSize.Height > m_minimumSize.Height)
  17.     {
  18.         this.Size = new Size(this.Size.Width, newSize.Height);
  19.         m_clickLoc = new Point(m_clickLoc.Value.X, e.Y);
  20.         needsRedraw = true;
  21.     }
  22.  
  23.     if (needsRedraw)
  24.         this.Invalidate();
  25.  
  26.     break;
Finally, lets add code to where we set the cursor to show a resize cursor.

Expand|Select|Wrap|Line Numbers
  1. if (this.TitleBarRectangle.Contains(e.Location))
  2.     this.Cursor = Cursors.SizeAll;
  3. else if (this.ResizeHandleRectangle.Contains(e.Location))
  4.     this.Cursor = Cursors.SizeNWSE;
  5. else
  6.     this.Cursor = Cursors.Default;
Now when you run the code, you should be able to interact with the resize handle. Hover over it and click and drag, the control should resize. Hooray! If you want to see why we set the DoubleBuffered property to true, go back and turn it off and try resizing. Rather ugly, isn't it? Double buffering just tells windows to draw to a back buffer, then swap the image onto the screen. You don't get to watch all those redraws happen, which is a handy feature.

At this point we can also demonstrate a nifty feature of a Control object, which is that it can contain other controls. Since our container is inheriting from a Control, we can throw a button in there, just for fun. Lets update our main form constructor code to the following...

Expand|Select|Wrap|Line Numbers
  1. NamedContainer c = new NamedContainer();
  2. c.Location = new Point(50, 50);
  3. c.Size = new Size(300, 150);
  4. c.Text = "Our Named Container!";
  5. c.BackColor = Color.White;
  6. panel1.Controls.Add(c);
  7.  
  8. Button b = new Button();
  9. b.ForeColor = Color.Black;
  10. b.BackColor = SystemColors.Control;
  11. b.Font = this.Font;
  12. b.Text = "Click!";
  13. b.Location = new Point(12, c.TitleBarRectangle.Bottom + 12);
  14. c.Controls.Add(b);
Now when we run the code, we see...



Note that you can drag the control around and it will update the position of the button as well. This is because all client controls for a control draw themselves relative to their parent. Whoo!

At this point we're basically done; however, for the sake of fun and testing, and to demonstrate yet another advantage to inheriting from control, lets enhance our main class. We're going to make it so that right-clicking on the panel will add a control, middle clicking will remove a control, and left clicking will bring it to the front.

To start though, lets add a constructor overload to our NamedContainer class to more easily facilitate this process.

Expand|Select|Wrap|Line Numbers
  1. public NamedContainer(string text, int x, int y, int width, int height)
  2.     : this()
  3. {
  4.     this.Text = text;
  5.     this.Location = new Point(x, y);
  6.     this.Size = new Size(width, height);
  7. }
Ok, now in our main form, using the designer, add an event handler to panel1's MouseClick event. Use the following code for the event...

Expand|Select|Wrap|Line Numbers
  1. private void panel1_MouseClick(object sender, MouseEventArgs e)
  2. {
  3.     switch (e.Button)
  4.     {
  5.         case MouseButtons.Right:
  6.             NamedContainer c = new NamedContainer("Container " + (panel1.Controls.Count + 1).ToString(), e.X, e.Y, 450, 200);
  7.             c.MouseDown += new MouseEventHandler(c_MouseDown);
  8.             c.Font = new Font(c.Font.Name, 12f, FontStyle.Bold);
  9.             c.BackColor = Color.White;
  10.  
  11.             panel1.Controls.Add(c);
  12.  
  13.             c.BringToFront();
  14.             break;
  15.     }
  16. }
This will create a new container wherever the mouse is clicked with a name based on the current number of controls in the panel, then adds it to the panel's control list (also bringing it to the front). We're also adding an event listener to the control's MouseDown event, so we need to write some code for that.

Expand|Select|Wrap|Line Numbers
  1. void c_MouseDown(object sender, MouseEventArgs e)
  2. {
  3.     if (sender is NamedContainer)
  4.     {
  5.         ((NamedContainer)sender).BringToFront();
  6.  
  7.         if (e.Button == MouseButtons.Middle)
  8.         {
  9.             panel1.Controls.Remove((NamedContainer)sender);
  10.         }
  11.     }
  12. }
When any mouse interacts with the control, it will be brought to the front. If the middle button interacts with it, it gets removed from the panel's controls list.

Now run the project. Right-clicking on the panel will create containers, middle clicking will remove them. You can also move and resize the panels as desired. A much more complete and fun test.



At this point, I'll leave it up to you to experiment with this and/or adding new features. One feature to add might be the ability to resize by clicking and dragging at the edges of the control, similar to how you can do with a windows form.

Enjoy!

----------------------------------------------
What follows is the complete code for Form1.cs in my Visual Studio project. This is only the code part of the partial class as I didn't feel it was necessary to include an entire working project. It's generally also desirable to put your control into another source file, potentially another class library. I just put it at the bottom of my Form class in Form1.cs for simplicity.

Expand|Select|Wrap|Line Numbers
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Windows.Forms;
  9.  
  10. namespace WindowsFormsApplication1
  11. {
  12.     public partial class Form1 : Form
  13.     {
  14.         public Form1()
  15.         {
  16.             InitializeComponent();
  17.  
  18.             //NamedContainer c = new NamedContainer();
  19.             //c.Location = new Point(50, 50);
  20.             //c.Size = new Size(300, 150);
  21.             //c.Text = "Our Named Container!";
  22.             //c.BackColor = Color.White;
  23.             //panel1.Controls.Add(c);
  24.  
  25.             //Button b = new Button();
  26.             //b.ForeColor = Color.Black;
  27.             //b.BackColor = SystemColors.Control;
  28.             //b.Font = this.Font;
  29.             //b.Text = "Click!";
  30.             //b.Location = new Point(12, c.TitleBarRectangle.Bottom + 12);
  31.             //c.Controls.Add(b);
  32.         }
  33.  
  34.         private void panel1_MouseClick(object sender, MouseEventArgs e)
  35.         {
  36.             switch (e.Button)
  37.             {
  38.                 case MouseButtons.Right:
  39.                     NamedContainer c = new NamedContainer("Container " + (panel1.Controls.Count + 1).ToString(), e.X, e.Y, 450, 200);
  40.                     c.MouseDown += new MouseEventHandler(c_MouseDown);
  41.                     c.Font = new Font(c.Font.Name, 12f, FontStyle.Bold);
  42.                     c.BackColor = Color.White;
  43.  
  44.                     panel1.Controls.Add(c);
  45.  
  46.                     c.BringToFront();
  47.                     break;
  48.             }
  49.         }
  50.  
  51.         void c_MouseDown(object sender, MouseEventArgs e)
  52.         {
  53.             if (sender is NamedContainer)
  54.             {
  55.                 ((NamedContainer)sender).BringToFront();
  56.  
  57.                 if (e.Button == MouseButtons.Middle)
  58.                 {
  59.                     panel1.Controls.Remove((NamedContainer)sender);
  60.                 }
  61.             }
  62.         }
  63.     }
  64.  
  65.     public class NamedContainer : Control
  66.     {
  67.         #region Enumerations
  68.         /// <summary>
  69.         /// How is this object currently moving?
  70.         /// </summary>
  71.         private enum MoveMode
  72.         {
  73.             NotMoving,
  74.             Form,
  75.             ResizeBoth,
  76.             ResizeVert,
  77.             ResizeHoriz
  78.         }
  79.         #endregion
  80.  
  81.         #region Private Members
  82.         private Point? m_clickLoc = null;
  83.         private MoveMode m_moveMode = MoveMode.NotMoving;
  84.         private Size m_minimumSize;
  85.         #endregion
  86.  
  87.         #region Constructors
  88.         /// <summary>
  89.         /// Base constructor
  90.         /// </summary>
  91.         public NamedContainer()
  92.             :base()
  93.         {
  94.             this.DoubleBuffered = true;
  95.             this.Movable = true;
  96.             this.Size = new Size(300, 150);
  97.             this.TitleBarColor = SystemColors.ActiveCaption;
  98.             this.ForeColor = SystemColors.ActiveCaptionText;
  99.         }
  100.  
  101.         /// <summary>
  102.         /// Constructor
  103.         /// </summary>
  104.         /// <param name="text">The text for this control.</param>
  105.         /// <param name="x">The x coordinate of this control's position.</param>
  106.         /// <param name="y">The y coordinate of this control's position.</param>
  107.         /// <param name="width">This control's width.</param>
  108.         /// <param name="height">This control's height.</param>
  109.         public NamedContainer(string text, int x, int y, int width, int height)
  110.             : this()
  111.         {
  112.             this.Text = text;
  113.             this.Location = new Point(x, y);
  114.             this.Size = new Size(width, height);
  115.         }
  116.         #endregion
  117.  
  118.         #region Properties
  119.         /// <summary>
  120.         /// Whether or not this control can be moved via the mouse.
  121.         /// </summary>
  122.         [Category("Behavior"), DefaultValue(true)]
  123.         public bool Movable { get; set; }
  124.  
  125.         /// <summary>
  126.         /// The text for this control.
  127.         /// </summary>
  128.         public override string Text
  129.         {
  130.             get
  131.             {
  132.                 return base.Text;
  133.             }
  134.             set
  135.             {
  136.                 base.Text = value;
  137.  
  138.                 // The minimium size is based on the area the text takes up so we need to recalculate when the text changes.
  139.                 m_minimumSize = this.GetMinimumSize(this.Text, this.Font);
  140.             }
  141.         }
  142.  
  143.         /// <summary>
  144.         /// The font for this control.
  145.         /// </summary>
  146.         public override Font Font
  147.         {
  148.             get
  149.             {
  150.                 return base.Font;
  151.             }
  152.             set
  153.             {
  154.                 base.Font = value;
  155.  
  156.                 // The minimium size is based on the area the text takes up so we need to recalculate when the font changes.
  157.                 m_minimumSize = this.GetMinimumSize(this.Text, this.Font);
  158.             }
  159.         }
  160.  
  161.         /// <summary>
  162.         /// The color of the title bar.
  163.         /// </summary>
  164.         [Category("Appearance")]
  165.         public Color TitleBarColor { get; set; }
  166.  
  167.         /// <summary>
  168.         /// The rectangle area the titlebar encompasses.
  169.         /// </summary>
  170.         [Browsable(false)]
  171.         public Rectangle TitleBarRectangle
  172.         {
  173.             get { return new Rectangle(this.ClientRectangle.X, this.ClientRectangle.Y, this.ClientRectangle.Width, (int)(this.Font.Height * 1.25)); }
  174.         }
  175.  
  176.         /// <summary>
  177.         /// The rectangle area the resize handle encompasses.
  178.         /// </summary>
  179.         [Browsable(false)]
  180.         public Rectangle ResizeHandleRectangle
  181.         {
  182.             get { return new Rectangle(this.ClientRectangle.Width - 15, this.ClientRectangle.Height - 15, 15, 15); }
  183.         }
  184.         #endregion
  185.  
  186.         #region Private Methods
  187.         /// <summary>
  188.         /// Calculate the minimum size for this form based on a string and a font.
  189.         /// </summary>
  190.         /// <param name="text">The text.</param>
  191.         /// <param name="font">The font.</param>
  192.         /// <returns>The minimum size this form can be to still display the form properly.</returns>
  193.         private Size GetMinimumSize(string text, Font font)
  194.         {
  195.             Graphics g = this.CreateGraphics();
  196.  
  197.             return new Size((int)g.MeasureString(text, font).Width, this.TitleBarRectangle.Height * 2);
  198.         }
  199.         #endregion
  200.  
  201.         #region Overrides
  202.         protected override void OnPaint(PaintEventArgs e)
  203.         {
  204.             Graphics g = e.Graphics;
  205.  
  206.             // Draw Background
  207.             g.FillRectangle(new SolidBrush(this.BackColor), this.ClientRectangle);
  208.  
  209.             // Draw Titlebar
  210.             g.FillRectangle(new SolidBrush(this.TitleBarColor), this.TitleBarRectangle);
  211.             g.DrawString(this.Text, this.Font, new SolidBrush(this.ForeColor), new Point(this.ClientRectangle.X + (int)(this.ClientRectangle.Width / 2 - g.MeasureString(this.Text, this.Font).Width / 2), this.ClientRectangle.Y + this.TitleBarRectangle.Height / 2 - this.Font.Height / 2));
  212.  
  213.             base.OnPaint(e);
  214.  
  215.             // Draw Resize Anchor if it can be used
  216.             if (this.Movable)
  217.             {
  218.                 g.DrawLine(Pens.Black, this.ResizeHandleRectangle.Right - 3, this.ResizeHandleRectangle.Top + 3, this.ResizeHandleRectangle.Left + 3, this.ResizeHandleRectangle.Bottom - 3);
  219.                 g.DrawLine(Pens.Black, this.ResizeHandleRectangle.Right - 3, this.ResizeHandleRectangle.Top + 6, this.ResizeHandleRectangle.Left + 6, this.ResizeHandleRectangle.Bottom - 3);
  220.                 g.DrawLine(Pens.Black, this.ResizeHandleRectangle.Right - 3, this.ResizeHandleRectangle.Top + 9, this.ResizeHandleRectangle.Left + 9, this.ResizeHandleRectangle.Bottom - 3);
  221.             }
  222.  
  223.             // Draw Border
  224.             g.DrawRectangle(Pens.Black, this.ClientRectangle.X, this.ClientRectangle.Y, this.ClientRectangle.Width - 1, this.ClientRectangle.Height - 1);
  225.             g.DrawLine(Pens.Black, new Point(this.ClientRectangle.X, this.TitleBarRectangle.Bottom), new Point(this.TitleBarRectangle.Right, this.TitleBarRectangle.Bottom));
  226.         }
  227.  
  228.         protected override void OnMouseMove(MouseEventArgs e)
  229.         {
  230.             // We only want stuff to happen if we say it can.
  231.             if (this.Movable)
  232.             {
  233.                 if (e.Button == MouseButtons.Left)
  234.                 {
  235.                     // Figure out what we're doing... if it's something, we want to store the last click location.
  236.                     if (m_clickLoc == null)                        
  237.                     {
  238.                         if (this.TitleBarRectangle.Contains(e.Location))
  239.                             m_moveMode = MoveMode.Form;
  240.                         else if (this.ResizeHandleRectangle.Contains(e.Location))
  241.                             m_moveMode = MoveMode.ResizeBoth;
  242.                         else
  243.                             m_moveMode = MoveMode.NotMoving;
  244.  
  245.                         if (m_moveMode != MoveMode.NotMoving)
  246.                             m_clickLoc = new Point(e.X, e.Y);
  247.                     }
  248.  
  249.                     // If we're not, not moving, figure out what we should be doing! (oooh poetry)
  250.                     if (m_moveMode != MoveMode.NotMoving)
  251.                     {
  252.                         switch (m_moveMode)
  253.                         {
  254.                             case MoveMode.Form:
  255.                                 // Just moving, update the form's location
  256.                                 this.Location = new Point(this.Location.X + e.X - m_clickLoc.Value.X, this.Location.Y + e.Y - m_clickLoc.Value.Y);
  257.                                 break;
  258.                             case MoveMode.ResizeBoth:
  259.                                 Size newSize = new Size(this.Size.Width + e.X - m_clickLoc.Value.X, this.Size.Height + e.Y - m_clickLoc.Value.Y);
  260.                                 bool needsRedraw = false;
  261.  
  262.                                 // Handle width and heigh resizing separately so that when we hit the minimum size for one, we can still resize the other.
  263.                                 // Also, only allow sizing if the particular dimension of the new size is greater than the corresponding minimum.
  264.                                 // Finally, if we do a resize, we need to force a redraw and update the click location so we always resize based on a delta movement.
  265.  
  266.                                 if (newSize.Width > m_minimumSize.Width)
  267.                                 {
  268.                                     this.Size = new Size(newSize.Width, this.Size.Height);
  269.                                     m_clickLoc = new Point(e.X, m_clickLoc.Value.Y);
  270.                                     needsRedraw = true;
  271.                                 }
  272.  
  273.                                 if (newSize.Height > m_minimumSize.Height)
  274.                                 {
  275.                                     this.Size = new Size(this.Size.Width, newSize.Height);
  276.                                     m_clickLoc = new Point(m_clickLoc.Value.X, e.Y);
  277.                                     needsRedraw = true;
  278.                                 }
  279.  
  280.                                 if (needsRedraw)
  281.                                     this.Invalidate();
  282.  
  283.                                 break;
  284.                         }
  285.                     }
  286.                 }
  287.                 else
  288.                 {
  289.                     // No desired button pressed lets clear all our states
  290.                     m_moveMode = MoveMode.NotMoving;
  291.                     m_clickLoc = null;
  292.  
  293.                     // Update the cursor based on what it moved over
  294.                     if (this.TitleBarRectangle.Contains(e.Location))
  295.                         this.Cursor = Cursors.SizeAll;
  296.                     else if (this.ResizeHandleRectangle.Contains(e.Location))
  297.                         this.Cursor = Cursors.SizeNWSE;
  298.                     else
  299.                         this.Cursor = Cursors.Default;
  300.                 }
  301.             }
  302.  
  303.             base.OnMouseMove(e);
  304.         }
  305.         #endregion
  306.     }
  307. }
Attached Images
File Type: png pic1.png (5.2 KB, 16258 views)
File Type: png pic2.png (5.2 KB, 14474 views)
File Type: png pic3.png (5.2 KB, 14530 views)
File Type: png pic4.png (5.3 KB, 14492 views)
File Type: jpg pic5.jpg (29.3 KB, 15247 views)
Apr 15 '11 #1
1 26500
thank you for very interesting and useful topic
Jan 30 '12 #2

Sign in to post your reply or Sign up for a free account.

Similar topics

5
by: jen_designs | last post by:
How do I create custom controls for an embeded video. I need stop, play, pause, etc. Any thoughts?
0
by: Jai | last post by:
Dear All Creating Custom Controls but Not User Controls,How to create custom controls,i.e Extending the functionality of the Existing Controls.(Web Custom Controls). Previously i have...
2
by: Mike | last post by:
I would like to create a couple of custom controls which I would be able to add to my Toolbox in .Net 2003. Would this be a long process if so are there any examples out there that would outline...
2
by: Darryn Ross | last post by:
Hi I was wondering if anyone had any information that could help me understand how to create a custom control, i have had a look on the web and can't seem to find any simple beginner examples. ...
3
by: Kyle Fitzgerald | last post by:
I've started a web control library project and can build my own controls to add to the toolbox in the .NET environment. The problem I'm having is I want to create a control just like the HTML...
6
by: Suzanne | last post by:
Hi all, I really hope someone out there can help me as I've been tearing my hair out on this one for a good while and I'm getting really frustrated now! My problem is this - my custom...
1
by: Steve | last post by:
C# Visual Studio .Net 2003 I am trying to create a custom DataGrid. I have written the code, but how do I add this to the Visual Studio toolbar? Steve
1
by: Abdo Haji-Ali | last post by:
Previously I used to create user controls if I wanted to use a specific set of controls in multiple pages, however I want to deploy my control in other applications so I thought of creating custom...
5
by: gerry | last post by:
I am trying to create a custom container control that will only ever contain a specific type of control. At design time, when a control of a different type is added to the container I would like...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
0
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
1
by: Sonnysonu | last post by:
This is the data of csv file 1 2 3 1 2 3 1 2 3 1 2 3 2 3 2 3 3 the lengths should be different i have to store the data by column-wise with in the specific length. suppose the i have to...
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers,...
0
by: Hystou | last post by:
Overview: Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows...
0
by: conductexam | last post by:
I have .net C# application in which I am extracting data from word file and save it in database particularly. To store word all data as it is I am converting the whole word file firstly in HTML and...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.