Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Creating a professional looking GDI+ drawn custom control

0.00/5 (No votes)
2 May 2004 6  
Shows how to create a custom scrolling label control with GDI+ and proper double buffering

Introduction

Recently I had a need for a marquee type control for a WinForm application I was writing. I wanted a label like control where the text would scroll across the control. It didn�t have to have much user interaction, just look pretty. I figured surely there was a control like this out there, but all I could find were User Controls that contained an inner label control that had its position updated every few ticks. But I didn�t really like that solution; I wanted clean, professional looking (and constructed) GDI+ drawn control. So after an hour of looking I decided to just write my own, which is the topic of this article.

(*Note: the images above are saved as a gif file, which really messes up how smooth this control really looks :-) )

Requirements

First, lets go over the requirements of the control. In essence, I wanted to create a label control that scrolls the text across the width of the control. I want the text to be able to scroll from left to right (once the text scrolls of the control on the right side, it starts again from the left), from right to left, or to bounce back and forth between the two sides. I also want the text to be able to be vertically aligned to the top, middle or bottom of the control. The user of the control should be able to programmatically control the speed of the text scrolling as well. Since I�m inheriting from the Control class (more about this in a bit), I don�t wont get any built in border UI functionality, so I�ll need a way to turn a border on and off, as well as set the color of the border. I also like the look of some of the custom brushes that .Net allows you to create, so I want the user of the control to be able to easily assign custom a brush to the control�s background and foreground. The last requirement, and most important, is I want the scrolling text to act like a hyperlink. When the user moves the mouse over the moving text, the cursor should change to a hand, and if the user clicks on the text, then an event needs to be fired. I also want to add an option to make the text stop scrolling when the user mouse�s over the text, and start scrolling once the user moves the mouse off the text.

Creating Custom WinForm Controls

First step is to decide which class you need to derive (inherit) your new control from. You have 4 basic choices for this. First you could use the UserControl class. The UserControl derives from the ScrollableControl class, which in turn derives from the Control class. If you create UserControl, Visual Studio will give you a designer so that you can drag and drop other controls onto it. So if you need to make something that is a combination of several controls this is the way to go. Next, you could inherit from an existing control, such as the Label control in this case, and override some of its methods to change its behavior. Finally, you could inherit from either the Control class or the ScrollableControl class, neither of which give you a designer to drag and drop other controls onto, so you�ll have to write GDI+ code to handle the entire look and feel of the control.

The UserControl doesn�t give you much more than the ScrollableControl class, in the way of properties, methods and events. It gives you an OnLoad, MouseDown, and WndProc event that you don�t get with the ScrollableControl or the Control class, but that�s about it. The major difference is that UserControl gives you a design time designer to drag and drop other controls into it. So if you don�t need to use any existing controls to create your new one, there isn�t much reason to use the UserControl class.

Since I want to handle all painting myself, I created a new class called ScrollingText and derived it from the base Control class. The neat thing about writing custom controls is that you don�t have to run the project to actually see the fruits of your labor. Whenever I decide to create a new control, my solution always has two projects in it. The first project is a ClassLibrary project that contains my control. The second one is a WinForms project that I use to test my custom control. Once you have both of these projects created, open the designer for Form1 of your WinForms project. On the Toolbox window, click the �My User Controls� tab to open it, then right click on it and select �Add / Remove Items�. When the �Customize Toolbox� dialog comes up, click the Browse button and navigate to your dll assembly that contains your custom control. Click �OK� and your new control will show up on the �My User Controls� toolbox tab. (As shown below)

Once you have your control in the toolbox, you can click / drag it onto your test form. You�ll probably see nothing at this point because you haven�t done any custom painting to your control yet (I�ll cover this next).

In your control�s constructor you should assign values to two properties by default, Control.Name and Control.Size. I set the Name property to �ScrollingText�, which is the name the control shows when you add it to toolbox. The other property, ControlSize, should also be set just so you see something when you drop the control on the test form (otherwise your control�s default size will be 0,0).

Custom Control Painting

Now we�re ready to start making our control look pretty. To start with I�m going to create public properties in my control for the following list: TextScrollSpeed, TextScrollDistance, ScrollText, ScrollDirection, VerticleTextPosition, ShowBorder, BorderColor, StopScrollOnMouseOver, ForegroundBrush, and BackgroundBrush. As I go along, I�ll explain how each of these is used.

The next thing to think about is notifying the control to paint itself. To do this, we�ll use the System.Windows.Forms.Timer class. In the control�s constructor I setup the timer class and assign a method to its Tick delegate. This delegate will get called every time the timer interval expires. By default I�m going to set the timer�s enabled property to true and set the timer interval to 25 milliseconds. This is shown below.

public ScrollingText()
{
  // Setup default properties for ScrollingText control

  InitializeComponent();

  //setup the timer object

  timer = new Timer();
  timer.Interval = 25;  //default timer interval

  timer.Enabled = true;
  timer.Tick += new EventHandler(Tick);      
}                        

Next we need to create a Tick method that the timer delegate will call. This method is pretty simple. All it does is call the Control.Invalidate() method. The Invalidate() method sends a WM_PAINT message to your control�s message queue, telling it to paint the entire control�s rectangle. There is a problem with this though. Tons of messages are being put in the control�s message queue all the time and the control processes them in order that they are received, so sometimes your control wont paint as fast as you want. To handle this, you can also call the Control.Update() method. When you call the Update() method just after Invalidate() it forces the OS to make the WM_PAINT message to bypass the message the message queue and go directly to the control�s window procedure to be processed.

//Controls the animation of the text.

private void Tick(object sender, EventArgs e)
{
  //repaint the control      

  this.Invalidate();
  this.Update();
}

When the control�s window procedure gets a WM_PAINT message, it calls the OnPaint method, which we need to override in order to handle our own custom GDI+ painting. I don�t like to put very much code in the OnPaint override, just to keep it simple, so I create a private method called DrawScrollingText() and pass in the Graphics object. Since DrawScrollingText() handles all the painting for the control, its not necessary to call the base control�s OnPaint() method (shown below, but commented out). It wont hurt the control to call the base method, but its just extra processing that isn�t needed. But if your custom control only handles part of the painting, such as when you derive from UserControl, then you�ll need to call the base control�s OnPaint() method, or else only what you paint will show up. One other thing to remember about calling the base.OnPaint() method, is that if it doesn�t get called, the user of the control cant register a method with the control�s Paint event. Since I don�t want the user to do this, I ignore this method.

//Paint the ScrollingTextCtrl.

protected override void OnPaint(PaintEventArgs pe)
{  
  //Paint the text to its new position

  DrawScrollingText(pe.Graphics);

  //pass on the graphics obj to the base Control class

  //base.OnPaint(pe);

}

There is another way to intercept all WM_PAINT messages, and that is to use the Control�s Paint event to call a method you register with the controls PaintEventHandler delegate. This would work, but overriding the OnPaint method is much faster than having a delegate�s Invoke() method calling for every WM_PAINT message.

One note, you might be tempted to use either the System.Threading.Timer class or System.Timers.Timer class to notify the control to paint itself, but don�t. These timer classes are specifically designed for use in a multithreaded environment, and both use threads from the ThreadPool to run their respective delegates. You could use them to update the control, but you�d have to use the Control.Invoke() method to make the call thread safe, and its more of a pain in the butt than its worth (and not especially fast). Especially since the System.Windows.Forms.Timer class is specifically designed to update the UI of a Form.

Painting the Control with GDI+

I don�t have the time or know-how to write a complete introduction to GDI+, especially since it is a huge topic. But I will go over some of the basics and specifically what is needed for this control. The best resources on GDI+ that I�ve found are �WinForms Programming in C#� by Chris Sells and �Graphics Programming with GDI+� by Mahesh Chand, both published by Addison Wesley.

The main thing to remember is that almost any object you create and use in GDI+ has a Dispose() method and needs to be cleaned up, otherwise you risk creating a fun memory leak to track down. This is especially important when creating controls, because the code you write in the overridden OnPaint() method will actually get executed whenever the control has been placed on a form and the form�s designer is showing. This is a great development feature, since all you just compile your code and you get instant visual feedback. You don�t even have to run the exe, just look at the form�s designer and the OnPaint() code gets executed. The only bad thing is that you cant debug and step through the code when the form designer is showing, so if you have a nasty bug in the OnPaint() method you�ll have to actually run the exe to debug it.

This feature of control design caused me great trouble a few months back. I was creating a custom control, which did all its own painting, and I found that after working in Visual Studio for 5 minutes, my OS would just slow down and eventually freeze up. I�d have to reboot to get everything working fine again. What had happened is I was creating a GDI+ Brush object each time the OnPaint() method was called, but I never called Dispose() on it. This caused a memory leak in my code, which materialized even when I was in Visual Studio�s design mode. I hade to use notepad in order to figure out where I was missing a Dispose() and correct it.

The basic flow of the DrawScrollingText() method is to calculate the new position of the text, which gets updated each time the method is called (I don�t show the code for this in the article because its pretty basic stuff and you can look at it in the provided source). Then I paint the background of the control. If the user has set a custom brush via the BackgroundBrush property, I use that to paint the background control. The Graphics class has a FillRectangle() method that I use to do this. Just set the x and y position of the upper left point of the control, and the control�s width and height. If the user didn�t set their own brush object, I call the Graphics.Clear() method, passing in the control�s BackColor.

Next I draw the control�s border, if needed. To do this I use the Graphics.DrawRectangle() method. This method is very similar to the FillRectangle, except it takes a Pen object instead of a Brush object. Since my BorderColor property only takes a Color struct, we have to create a new Pen object out of the Color struct.

I really like C#�s �using()� functionality, which automatically wraps the code in the using block in a try block, and puts the variable the using keyword is executed on in a finally block, calling the variable�s Dispose Method. For example, take the following code.

using (Pen borderPen = new Pen(borderColor))
{
  canvas.DrawRectangle(borderPen, 0, 0, this.ClientSize.Width-1, 
    this.ClientSize.Height-1);          
}

// Once this code gets compiled into MSIL, it 

// gets translated to the following


Pen borderPen = new Pen(borderColor);
try
{
  canvas.DrawRectangle(borderPen, 0, 0, this.ClientSize.Width-1, 
    this.ClientSize.Height-1);          
}
finally
{
  borderPen.Dispose();
}

This makes your code read easier and most importantly ensures that no matter what happens in your code, even if an exception is thrown, the object�s Dispose method will get executed. This is essential when working with GDI+ objects, since probably 93.7% of them implement IDisposable.

Next I to use GDI+ to draw the text in it�s new, updated position. To do this, use the Graphics.DrawString() method. This method takes the string that you want drawn to the control, the font, a brush that determines what color the text will be, and the x and y position of the upper right hand corner of the text.

Then code for drawing all this is shown below.

//Draw the scrolling text on the control    

public void DrawScrollingText(Graphics canvas)
{
  //Calculate x and y position of text each tick

  .
  .
  .
  
  //Clear the control with user set BackColor

  if (backgroundBrush != null)
  {
    canvas.FillRectangle(backgroundBrush, 0, 0, 
    this.ClientSize.Width, this.ClientSize.Height);
  }
  else
    canvas.Clear(this.BackColor);          

  // Draw the border

  if (showBorder)
    using (Pen borderPen = new Pen(borderColor))
      canvas.DrawRectangle(borderPen, 0, 0, 
      this.ClientSize.Width-1, this.ClientSize.Height-1);          

  // Draw the text string in the bitmap in memory

  if (foregroundBrush == null)          
  {
    using (Brush tempForeBrush = 
      new System.Drawing.SolidBrush(this.ForeColor))
      canvas.DrawString(this.text, this.Font, 
      tempForeBrush, staticTextPos, yPos);  
  }
  else
    canvas.DrawString(this.text, this.Font, 
      foregroundBrush, staticTextPos, yPos);  

}

One final thing that should be added to the control at this point is an overridden Dispose() method. Since the control stores several objects at the class level that need to be disposed, the control�s Dispose() method is the place to handle this, as shown below.

protected override void Dispose( bool disposing )
{
  if( disposing )
  {
    //Make sure our brushes are cleaned up

    if (foregroundBrush != null)
      foregroundBrush.Dispose();

    //Make sure our brushes are cleaned up

    if (backgroundBrush != null)
      backgroundBrush.Dispose();

    //Make sure our timer is cleaned up

    if (timer != null)
      timer.Dispose();
  }
  base.Dispose( disposing );
}

Double Buffering...The Old Fashioned Way

At this point, we have a working control that scrolls the text left to right or right to left. Yea, we�re done! Well, not quite. If you compiled the control and ran it, you�d see the control flickering just a bit. And if you applied a custom brush, such as a gradient brush, to the control�s background you�d see some major flashing going on as the control gets drawn to the screen. The reason for this is that each time GDI+ is used to paint something via the Graphics object, the control gets updated on the screen. In the DrawScrollingText() method, the Graphics object is used 3 times, once for the background, once for the border, and once for the text. This happens every time the OnPaint event gets called. These multiple updates to the control�s UI are what cause the flicker as the text scrolls across the control.

The tried and true method to fix this flickering problem is something called Double Buffering, which is a common practice used in C++ whenever handling the WM_PAINT message. What double buffering means is the code creates a bitmap in memory the same size as the window it�s going to update. The code applies all the GDI+ updates to the bitmap, then copies the bitmap directly to the control. This way, only one update is made to the control per WM_PAINT message, instead of several.

Below is a modified version of the DrawScrollingText() method which employs double buffering. The first thing I do is create a new bitmap in memory that has the same dimensions as the control. Next, I create a Graphics object to update the temporary bitmap with the Graphics.FromImage() method. This is the graphics object that I will use to do the individual GDI+ updates. Then, at the very bottom of the method, I apply the newly updated bitmap to the control�s Graphics object, which will update the UI all in one shot. This way I cut the number of updates to the screen from three per WM_PAINT message down to one.

//Draw the scrolling text on the control

public void DrawScrollingText(Graphics graphics)
{
//Calculate x and y position of text each tick

  .
  .
  .

using (Bitmap scrollingTextBmp = new Bitmap(this.ClientSize.Width, 
  this.ClientSize.Height))
  {
    using (Graphics canvas = Graphics.FromImage(scrollingTextBmp))
    {            
      //Clear the control with user set BackColor

      if (backgroundBrush != null)
      {
        canvas.FillRectangle(backgroundBrush, 
        0, 0, this.ClientSize.Width, this.ClientSize.Height);
      }
      else
        canvas.Clear(this.BackColor);          
      // Draw the border

      if (showBorder)
        using (Pen borderPen = new Pen(borderColor))
          canvas.DrawRectangle(borderPen, 
          0, 0, this.ClientSize.Width-1, this.ClientSize.Height-1);          

      // Draw the text string in the bitmap in memory

      if (foregroundBrush == null)          
      {
        using (Brush tempForeBrush = 
          new System.Drawing.SolidBrush(this.ForeColor))
            canvas.DrawString(this.text, this.Font, tempForeBrush, 
            staticTextPos, yPos);  
      }
      else
        canvas.DrawString(this.text, this.Font, 
        foregroundBrush, staticTextPos, yPos);  

      //Double Buffering: draw the bitmap in memory onto the control!

      graphics.DrawImage(scrollingTextBmp, 0, 0);  
    }
  }            
}

Double Buffering...The .Net Way

The double buffering technique shown above helps the flickering a great deal, but doesn�t handle it entirely. There is still a faint flicker every now and then, especially when using a GradientBrush applied to the control�s background.

Fortunately the good people at Microsoft saw fit to build double buffering into the System.Windows.Forms namespace. Instead of manually creating a bitmap in memory, drawing on the bitmap, and then copying the bitmap to the control, all you have to do is set 3 easy, little properties. Not only that, but Microsoft does double buffering much smoother than the manual way.

The code below shows the three lines, which should be added to the control�s constructor.

//This turns off internal double buffering of all custom GDI+ drawing

this.SetStyle(ControlStyles.DoubleBuffer, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.UserPaint, true);

The first line turns on the internally handled double buffering (default is off). This tells the CLR to update all graphic changes to an internal buffer, and then output the result to the screen. When you set this property, you also need to set the ControlStyles.AllPaintingInWmPaint and ControlStyles.UserPaint properties to true as well. AllPaintingInWmPaint tells the CLR to ignore the WM_ERASEBKGND message, which sets the back color of the window, and to handle all painting with the WM_PAINT message. UserPaint tells the CLR that the control overrides the OnPaint method and will handle all its own painting.

Once the built in double buffering is in place, the text control animation is completely smooth, absolutely no flickering, even when a gradient brush has been applied. Sweet!

Making the scrolling text a link

The next requirement I want to cover is the making the scrolling text act like a hyperlink label. When the user moves their mouse over the moving text, the default cursor should change to the hand cursor. When the cursor then moves off the text, the cursor changes back to the default cursor. Also, when the user clicks on the text, a TextClicked event should fire, which the user can write code to register to this event.

To do this I created the method EnableTextLink, shown below. Passed into the method is a rectangle structure that represents the size and position of the text being drawn to the control. Next, I have to get the cursor position. Because the Cursor.Position property returns the position relative to the desktop as a whole, I need to find a way to get the position relative to the upper left hand corner of the control. The Control.PointToClient() property does just that. It returns a point structure representing the cursor position within the client control.

Next I just do a simple boundary check to see if the cursor point falls within the rectangle. When I first wrote this control, I did this manually, but when playing around I found that the Rectangle structure actually has a Contains() method that does this check for you! Anyway, if the rectangle does contain the point, I want to set the Control.Cursor property to the hand cursor. If the rectangle does not contain the point, then I set the Control.Cursor back to its default cursor value.

The other thing I handle in this method is stopping the text scrolling when the mouse moves over the text. Remember the StopScrollOnMouseOver public property I mentioned at the beginning of the article? If the user sets this property to true, then this method will set a private scrollOn property to false when the mouse is moved over the text. This field is used to determine if the text x and y position should be updated each time the OnPaint is called. So when scrollOn is set to false, the x and y position of the text is not updated, which stops the scroll.

private void EnableTextLink(RectangleF textRect)
{
  Point curPt = this.PointToClient(Cursor.Position);

  //Check to see if the users cursor is over the moving text

  //if (curPt.X > textRect.Left && curPt.X < textRect.Right

  //  && curPt.Y > textRect.Top && curPt.Y < textRect.Bottom)    

  if (textRect.Contains(curPt))
  {
    //Stop the text of the user mouse's over the text

    if (stopScrollOnMouseOver)
      scrollOn = false;        

    //Set the cursor to the hand cursor

    this.Cursor = Cursors.Hand;              
  }
  else
  {
    //Make sure the text is scrolling if 

    //user's mouse is not over the text

    scrollOn = true;
    
    //Set the cursor back to its default value

    this.Cursor = Cursors.Default;
  }
}

Now that we have that part handled we need to write functionality to trigger an event when the user clicks on the text. First, I create a ScrollingText_Click() method to handle all click events for the control. Then, in the constructor of the control, I register this new method with the Control.Click() event. Once this is in place I need to create a delegate and event to handle our custom TextClicked event. When the user clicks on the control, the code first checks to see if the Cursor is a hand. If it is, it knows that the cursor is still over the text, and calls the OnTextClicked event handler, which in turn invokes the delegate. The user can use this event to register their own method to it, which will get called anytime the text is clicked. This is shown below.

private void ScrollingText_Click(object sender, System.EventArgs e)
{
  //Trigger the text clicked event if the user clicks while the mouse 

  //is over the text.  This allows the text to act like a hyperlink

  if (this.Cursor == Cursors.Hand)
    OnTextClicked(this, new EventArgs());
}

public delegate void TextClickEventHandler(object sender, EventArgs args);  
public event TextClickEventHandler TextClicked;

private void OnTextClicked(object sender, EventArgs args)
{
  //Call the delegate

  if (TextClicked != null)
    TextClicked(sender, args);
}

Advanced Control Painting with Regions

At this point we have a working scrolling text control, that doesn�t flash and is pretty fast and efficient. But it could be a bit more efficient. Remember the paint method? Every time it�s called we paint the entire control. But is this really necessary? Nope. The only part of the control that really needs to be repainted is where the text is moving to and where the text is moving from. The rest of the control is doesn�t change. Repainting the whole control every time just waists computing cycles that could be used for something else.

The way to tell a control what part of itself to paint is by passing in a Region object into the Control.Invalidate() method. The Region class�s constructor can take a rectangle object that represents the exact x and y position of the upper left corner, as well as the width and height of the area it needs to paint. The OS ignores every other part of the control. That�s the nice thing about invalidating just a region of the control. You don�t have to write code in your OnPaint() method to only paint the invalid region. The OS takes care of that for you.

For example, lets say you call Graphics.FillRectangle() in your OnPaint() method, which takes a gradient brush. This is a fairly heavy paint command. If Control.Invalidate() is called with a region that only covers the left third of the control, FillRectangle() will only update the left third of the control on the screen, even though FillRectangle should update the entire control. The OS takes care of these details for you.

So, lets apply this to the scrolling control. The control�s timer object controls how often Control.Invalidate() is called. In the timer delegate�s Tick() method we need to create a region object and pass it into the Invalidate() method.

The first thing to figure out is what area do we need to repaint. If the text is moving left to right, we need to have enough area in the rectangle to cover where the text was (so the left most pixels can be set to the background), and where the text is going to be (so the right most pixels of the text can be drawn). We�ll also need to write logic to handle the text if it is scrolling the other direction. Since I�ve already created a rectangle structure for use in the mouse over logic, we can just use this same rectangle and just modify its width to suite our needs.

One thing I�ve found, is if you are very exact with your rectangle size, and the text scrolls entirely off the edge of the control, then a WM_PAINT message is not sent to the control (the OnPaint() method never gets fired). This is because the OS sees that the region to paint is totally off the control, so there is no reason to send the WM_PAINT message to the control. The problem with this is, the OnPaint() method is where the new text position is calculated. If the text scrolls off the control, and OnPaint() is never called, the text will never get repositioned to the other side of the control. This took a bit of time to figure out, but to fix it I added a 10 pixel buffer zone to the left and the right of the text�s rectangle to make sure that part of the region that gets invalidated is still on the control, and the WM_PAINT message gets sent. The nice thing about the Rectangle structure is that it comes with an Inflate() method that takes the height and width you want to increase the rectangle by. This is shown below in the Tick() method.

//Controls the animation of the text.

private void Tick(object sender, EventArgs e)
{
  //update rectangle to include where to paint for new position

  //lastKnownRect.X -= 10;    //Don�t need to use this

  //lastKnownRect.Width += 20;

  lastKnownRect.Inflate(10, 0);    //Use the Inflate() method


  //create region based on updated rectangle

  Region updateRegion = new Region(lastKnownRect);      
  
  //repaint the control only where needed

  Invalidate(updateRegion);
  Update();
}

Now that this is in place, we�ve optimized the drawing of the control pretty well. Only the region of the control that needs to be repainted will actually get updated to the screen.

Giving Your Control a Professional Design Time Look and Feel

The only thing left to do is a few housecleaning tasks that will give your control a more professional design time look and feel. These aren�t absolutely necessary for the control to work properly, but make it easier for other developers to use your control.

The first one is totally aesthetic. All Microsoft WinForm controls have an icon associated with them that show up in the toolbar. But custom controls all get the same stock icon unless you specifically add your own. The way to give you�re control its own icon is by adding a 16x16 icon or bitmap to your control solution. Do this by right clicking on the project, then select Add | Add Existing Item�, then navigate to the icon you want and click ok. Once the icon is part of your project, you�ll want to change its �Build Action� property to �Embedded Resource� (in the property window). Once this is done, the last step is to add a class level attribute to your control, called ToolboxBitmapAttribute. This attribute�s constructor should take two parameters; the type of the control to associate the icon to, and the icon name. This is shown below.

[ToolboxBitmapAttribute(typeof(ScrollingTextControl.ScrollingText), 
  "ScrollingText.bmp")]
public class ScrollingText : System.Windows.Forms.Control
{
}

Once this is done, you wont immediately see your icon show up in the toolbox because Visual Studio only looks for the icon when you add the control to the toolbox. So you�ll have to right click on your control in the toolbox and select Delete to remove it. Next, recompile your control and go through the steps to add your control back to the toolbox. You should see the default custom control icon replaced with your new icon like the picture below.

If you don�t, there are a few things to check. Make sure your icon isn�t a 32x32 icon. Also make sure the icon name in the attribute is correct, and the icon�s Build Action property is properly set. Also, I�ve found that if you add and remove a custom control from the toolbox over and over, Visual Studio starts to complain, so try to get it right the first time.

The second thing to add to your new control is a default event. A default event is the event that is auto-created by Visual Studio when you double click on the control when its on a form. For example, if you double click a button control, the button�s Click event is automatically created for you in the code file. To set the default event for your control, if you want one, you add another class level attribute called DefaultEventAttribute. This class�s only constructor is a string of the name of the event. When the control gets double clicked in the designer, Visual Studio uses reflection to find the event that matches the name you set in the attribute, then it generates the appropriate code for that event handler. The new class attribute list is shown below.

[
ToolboxBitmapAttribute(typeof(ScrollingTextControl.ScrollingText), 
  "ScrollingText.bmp"),
DefaultEvent("TextClicked")
]
public class ScrollingText : System.Windows.Forms.Control
{�
}

The last thing I want to go over are the list of attributes that you can apply to your control�s public properties. The first one that I use is the BrowsableAttribute. If set to false, then the property will not show up in the property browser for the control. By default any public property will show up in the property browser. But some properties, like my Brush property shown below, cant be set without code, so there is no reason to make this visible.

The second attribute is CategoryAttribute. If you like to sort the properties in the property browser, then you can use this attribute to group similar properties. I like to use this to group all my custom public properties.

The third attribute is DescriptionAttribute. The text you set in this attribute is the text that shows up at the bottom of the property browser when you click on that property. This is very helpful for the users of your control. An example of these attributes are all shown below.

[
Browsable(true),
CategoryAttribute("Scrolling Text"),
Description("Determines if the text will stop scrolling" + 
  " if the user's mouse moves over the text")
]
public bool StopScrollOnMouseOver
{
  set{stopScrollOnMouseOver = value;}
  get{return stopScrollOnMouseOver;}
}

false)>
public Brush ForegroundBrush
{
  set{foregroundBrush = value;}
  get{return foregroundBrush;}
}

There are a few other attributes that you can apply to a custom control, as well as a whole slew of stuff you can do to customize how your control is used at design time, but that is a topic for a whole other article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here