Creating Arbitrarily Shaped Controls

Introduction

When implementing a project, I needed to create controls of an arbitrary (convex) shape. The shape should not only influence the appearance, but also determine what region of the control should respond to mouse activity. There should be some restrictions concerning the region: It should have no cavities and should be convex. The significance of the restrictions will be described in detail later.

For the control to be conveniently used, I decided to specify its shape with the help of an image (a 32-bit bitmap). The control’s boundary is presented in the picture as a closed line. The control is extracted from the image during the analysis as a sequence of points defining the curve. The region restricted by the curve is a client’s resulting region of the control.

The control’s functionality is implemented in the convenient SkinControlbase class. By inheriting a control from it, you may create your own controls that have a non-standard shape.

Using the Controls Inherited from the SkinControl Class

The project available for download presents three controls implemented by the KB_Soft Group company for its internal needs. These controls are based on the SkinControl common base class and are described below:

  • KBSoft.Components.SkinButton: The button whose appearance is defined by four images (one for each state—pressed, disabled, hot, and normal).
  • KBSoft.Components.SkinTextbox: The text box whose appearance and boundaries are specified by an image.
  • KBSoft.Components.Skintooltip: The pop-up window supporting the first two controls placed on it. It has a number of animations.

The SkinControl base class has the following properties and methods:

Property or Method Description
PatternBitmap Sets the image, whose boundaries will be a source of information for forming the region that restricts the control
TransparentColor The color defining the region of the image. It is specified by the PatternBitmap property that is not included in the control
UseCashing Being set, the property indicates that the control’s region is calculated only once during the first creation. After that, it is stored to the static collection that allows the time for the repeated control’s initialization to be considerably shortened

The SkinControl class is inherited from the UserControl class and implements the ISupportInitialize interface. The EndInit method calculates the control’s region if the UseCashing flag is not set. The method is added automatically to the code of the control’s initialization when it is created with the help of the Visual Studio 2003 designer. Below is an example of manually creating the SkinButton button without using the shape designer.

  1. First, create a new Windows application and add a new variable to the main window class (Form1, by default):

    private KBSoft.Components.SkinButton skinButton = null;
  2. Then, add the constructor the following code:
  3. //creating an object.
    skinButton = new SkinButton();
    
    //setting the color that defines the image's regions that
    //should be excluded.
    skinButton = new SkinButton();
    
    //the control's location.
    skinButton.Location = new Point;
    
    //getting an image from the resources. Do not forget that it
    //is formed in VS 7.1 as a name of the default namespace +
    //name of all the folders in Solution Explorer that contain
    //the resource + the resource name.
    
    Assembly currentAssembly =
       Assembly GetAssembly( this.GetType() );
    Bitmap bmp = (Bitmap)Bitmap.FromStream(
       currentAssembly.GetManifestResourceStream(
          "TestControls.Power.png" ) );
    
    //Setting the image - the pattern for calculating
    //control's regions.
    skinButton.PatternBitmap = bmp;
    
    //Setting the image that is displayed in the initial state
    //of the button.
    skinButton.NormalImage = bmp;
    
    //The image for the pressed state.
    skinButton.PressedImage = (Bitmap)Bitmap.FromStream(
       currentAssembly.GetManifestResourceStream(
         "TestControls.Power_p.png");
    );
    
    //The image for the disabled state.
    skinButton.DisabledImage = (Bitmap)Bitmap.FromStream(
       currentAssembly( GetManifestResourceStream(
          "TestControls.Power_d.png" ) );
    
    //The image for the hot state.
    skinButton.HotImage = (Bitmap)Bitmap.FromStream(
       currentAssembly.GetManifestResourceStream(
          "TestControls.Power_f.png" ) );
    
    //Calling the method performing the needed calculations.
    skinButton.EndInit();
    
    //Don't forget to define which window owns our button.
    skinButton.Parent = this;
    
  4. The result of the application work is presented in the following figures:

A simpler way is to add the DLL containing the components to the toolbox and to set all the control’s properties from the property window.

More by Author

Must Read