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

A custom range selector control in C# (with a little animating slider)

0.00/5 (No votes)
20 Aug 2008 4  
At times, business needs are so unique that we have to write new controls in addition to the existing Toolbox provided controls. This article explains how to write such a unique control, named Range Control. Control source and a sample app are included.

New version 1.5: Little animation and snap feature

Old version 1.0

The first link contains the binaries to watch a quick demo. The second link contains the Range control source code and a test application source code that uses this control.

RangeControlTestApp.JPG

Introduction

It's a fine morning. Your boss Alice says, "Hey Bob! It will be great if we can have a unique control matching our business needs instead of presenting a legacy one. bla bla bla....". And you think, "Well, it *was* a fine morning ... ". However, now you have to design a unique custom control matching your unique business needs.

This article details how to write your own unique custom control. Also, this article includes a sample animated range selection control (both binary and source code included). The initial sections of this article explains how to use the range control. If you are an advanced programmer, you may directly go to the 'Control code explained' section after seeing the demo.

Background

Business needs are not always very generic. Most of the time, business needs are very unique and require authoring a custom control.

Using the code

Steps to see a demo:

Step 1: Copy the binaries (download using the link from the top of this article) to a separate folder.

Step 2: It is a compressed file. Choose a temporary folder and copy all the files into the folder.

Step 3: Run TestApplication.exe.

Step 4: You should see an application as in the image above.

You can slide the slider thumb in both directions using your mouse. I chose to have a smooth scroll slider instead of a block move slider. However, the code can be modified to support both. Also, I added a little animation for good user experience.

Steps to use the assembly:

Consider you are done seeing the demo and would like to use it. The following steps may be followed to use the Range Selector custom control in your project:

Step 1: Create a Windows Forms application using Visual Studio 2005 or above. The VS2005 requirement is because, I compiled the assembly in VS2005. However, you can easily move it to any version of Visual Studio with very little effort

Step 2: In the Toolbox, right click, then select 'Choose Items'. Then, click the Browse button to choose the assembly. Click the OK button after selecting the assembly.

Step 3: Now, you will see an entry in the Toolbox with the name 'RangeSelectorControl'. Drag and drop it to any part of your Windows form to use the control.

Step 4: Select the control. Then, right click and select the Properties menu option.

Step 5: Add/ modify/ delete the required properties to match your requirements.

Step 6: Now, it's coding time. But, it's very minimal. All you have to do is register your method with the control assembly -- so that the control assembly will call your method when the user changes his choice (by sliding the range control). Otherwise, you can also query the control method QueryRange to find out the user choice. Both the choices are explained with a simple code snippet below.

private void button1_Click(object sender, System.EventArgs e)
{
    string strRange1;
    string strRange2;

    rangeSelectorControl1.QueryRange(out strRange1 , out strRange2);
    textBox1.Text = strRange1 + " - " + strRange2;
    textBox1.Update();
    object obj = strRange1;
    strRange1 = "100";
}

In the above code snippet, the first line declares two string variables strRange1 and strRange2. The next line queries the control assembly to get the current user choice. After which, the code updates a TextBox control in the form to showcase the user choice.

Now, another option is to get continuous update from the control whenever a user modifies his option. Look at the code snippet below:

private CustomRangeSelectorControl.NotifyClient objNotifyClient;

private void Form1_Load(object sender, System.EventArgs e)
{
    objNotifyClient = new CustomRangeSelectorControl.NotifyClient();
    
    rangeSelectorControl1.RegisterForChangeEvent(ref objNotifyClient);

}

In the above code, the first line declares a class level variable objNotifyClient. This variable is initialized in the Form_Load event. Next, this object is registered with the control assembly for continuous notifications.

However, I included a detailed sample test application for those who use this control assembly extensively. The sample source code and the control source code can be downloaded from the link at the top of this article.

As always, this is not a professional level code. Hence, I have not done extreme validation checks in the code. However, the code can be brushed up quickly.

Points of interest

Okay, now, about writing a control assembly. It's very simple. All you have to do is create a new control project. If you have created the project correctly, your main class will derive from System.Windows.Forms.UserControl. All you have to do then is manage your control's visual activity by overriding the void OnPaint(PaintEventArgs e) method. You can look at the source code of range selector control. It's very simple with a few math calculations. Based on the comments I have received, I will explain this in further detail.

These are a few interesting things I did in the range selector control:

  • The range selector control exposes as much properties as possible to give users an opportunity to see the control at design time rather than at runtime.
  • Ranges can be input using an XML file. This will help to load the control in a much generic way.
  • The range selector control accepts bitmaps.
  • The range selector control avoids any flicker while moving the slider using the mouse.
  • The range selector control automatically displays the range values at the bottom.
  • I have also added a little animation, which is explained in the next section (Control code explained - seventh section/region).

Control code explained

Refer to the range selector control source code (and the demo) that can be downloaded from the links at the top of this article.

I divided the control source code in the namespace CustomRangeSelectorControl.RangeSelectorControl into different regions. Let’s walk through the regions one by one.

The first region is 'Design Time Control Variables...'. These are private variables that will hold the values of the exposed properties to the users. For example, strXMLFileName is used to store the value of the exposed property XMLFileName.

#region Design Time Control Variables -- Private Variables
    private string strXMLFileName;         // XML File Name that is used for
                                           // picking up the Label Values
    private string strRangeString;         // The String that is displayed
                                           // at the bottom of the control. 
    private string strRange;               // An alternate to the XML File Name where
                                           // the Range Label values are stored
    private Font fntLabelFont;             // Font of the Label
    private FontStyle fntLabelFontStyle;   // Font Style of the Label 
    private float fLabelFontSize;          // Size of the Label 
    private FontFamily fntLabelFontFamily; // Font Family of the Label 
    private string strLeftImagePath;       // Left Thumb Image Path
    private string strRightImagePath;      // Right Thumb Image Path
    private float fHeightOfThumb;          // Height Of the Thumb
    private float fWidthOfThumb;           // Width of the Thumb
    private Color clrThumbColor;           // Color of the Thumb, If not Image
    private Color clrInFocusBarColor;      // In Focus Bar Colour
    private Color clrDisabledBarColor;     // Disabled Bar Color
    private Color clrInFocusRangeLabelColor; // In Focus Range Label Color
    private Color clrDisabledRangeLabelColor;// Disabled Range label Color
    private uint unSizeOfMiddleBar;        // Thickness of the Middle bar
    private uint unGapFromLeftMargin;      // Gap from the Left Margin to draw the Bar
    private uint unGapFromRightMargin;     // Gap from the Right Margin to draw the Bar
    private string strDelimiter;           // Delimiter used to seperate
                                           // the Labels in strRange variable
    private string strRange1;              // Thumb 1 Position bar
    private string strRange2;              // Thumb 2 Position in the bar
    private Font fntRangeOutputStringFont; // Range Output string font
    private float fStringOutputFontSize;   // String Output Font Size
    private Color clrStringOutputFontColor;// Color of the Output Font 
    private FontFamily fntStringOutputFontFamily; // Font Family to display the Range string
#endregion

The second region is 'Design Time Control Properties...'. These are the properties exposed to the user of the control at design time. For example, have a look at the first property XMLFileName. Open the demo in Visual Studio. Right click to see the properties of this control. If you browse through entry by entry, you will find the XMLFileName property exposed to the user. The user can type in the value using Visual Studio designer's property window. You can see all the properties such as RangeString, RangeValues, LabelFont etc., in this section. The code below is just a small snippet from this region.

#region Design Time Control Properties -- Public -- 
        Design Time User properites - Can also be changed runtime
/// <XMLFileName>
/// XMLFileName is a property that can be used to set the Range Labels
/// For Example:
/// <?xml version="1.0" encoding="utf-8" ?>
/// <RangeController>
/// <Values>
/// <Value> Excellent</Value>
/// <Value> Good</Value>
/// <Value> Fair</Value>
/// <Value> Poor</Value>
/// </Values>
/// </RangeController>
/// 
/// Here the values Excellent, Good, Fair and Poor
/// will be taken as Labels for the 
/// Control. 
/// </XMLFileName>
/// 
public string XMLFileName
{
    set
    {
        try
        {
            strXMLFileName = value;
            if (null != strXMLFileName)
            {
                xmlTextReader = 
                   new System.Xml.XmlTextReader(strXMLFileName);
                strRange = null;
                while(xmlTextReader.Read())
                {
                    switch(xmlTextReader.NodeType)
                    {
                    case System.Xml.XmlNodeType.Text:
                    strRange += xmlTextReader.Value.Trim();
                    strRange += strDelimiter;
                    break;
                    }
                }
                strRange = strRange.Remove(strRange.Length - 
                             strDelimiter.Length, strDelimiter.Length);
                CalculateValues();
                this.Refresh();
                OnPaint(ePaintArgs);
            }
        }
        catch
        {
            strXMLFileName = null;
            System.Windows.Forms.MessageBox.Show("The XML Path entered" + 
                   " may be invalid (or) The XML file is not well formed", 
                   "Error!");
        }
    }



    get
    {
        return strXMLFileName;
    }
}

/// <ControlProperties>
/// The Above are Design time (Also Runtime) Control
/// Variable properties. These variables 
/// can be used by the client to change the appearance of the control.
/// </ControlProperties>
/// 
#endregion

The third region is 'Variables used for computation'. These are the variables used to do the math calculations when the user moves the slider. Also, these variables are used to draw the bitmap, font, line, slider, and the bar at the right locations at the right time.

The fourth region is the constructor for this control. This constructor contains a region 'Initialization of variables'. This region contains code that initializes all the declared variables with default values for this class.

Fifth region is 'Methods exposed to client at runtime'. The users of this range control needs to receive feedback from the control about user activity. Consider that an end user of the application slides a bar in the range selector control, then the application needs to receive input to these events to act on. There are two methods exposed for this purpose. One is passive and the other is active. The passive one is a query method. The user of this control can query the control at any given time to get the current range values selected by the application's end user. Otherwise, the application can register for a notification to be sent out when the user slides the control. The method QueryRange does the passive job and the RegisterForChangeEvent method does the active job.

#region Methods Exposed to client at runtime
/// <QueryRange>
/// The client can query this method to get the range
/// </QueryRange>
/// 
public void QueryRange(out string strGetRange1, out string strGetRange2)
{
    strGetRange1 = strRange1.ToString();
    strGetRange2 = strRange2.ToString();
}

/// <RegisterForChangeEvent>
/// The client can Register for automatic
/// update whenever the values are changing
/// </RegisterForChangeEvent>
/// 
public void RegisterForChangeEvent(ref NotifyClient refNotifyClient)
{
    // If there's a valid object, the values are copied.
    try
    {
        if (null != refNotifyClient)
        {
            objNotifyClient = refNotifyClient;
            objNotifyClient.Range1 = strRange1;
            objNotifyClient.Range2 = strRange2;
        }
    }
    catch
    {
        System.Windows.Forms.MessageBox.Show("The Registered Event object has " + 
                             "a Bad memory. Please correct it", "Error!");
    }
}
#endregion

The sixth region is 'This is a Private method that calculates the values...". This section has only one private method CalculateValues. This method calculates the values to draw the various components of the control. The various components of the control are the bar, slider, range values etc. The code is well documented here. It gets the Graphics object, then calculates the positions of each component.

#region This is a Private method that calculates 
             the values to be placed while painting
private void CalculateValues()
{
    try
    {
        // Creating the Graphics object
        System.Drawing.Graphics myGraphics = this.CreateGraphics();
        // Split the Labels to be displayed below the Bar
        strSplitLabels = strRange.Split(strDelimiter.ToCharArray(), 1024);
        nNumberOfLabels = strSplitLabels.Length;
        // If there's an image load the Image from the file
        if (null != strLeftImagePath)
        {
            imImageLeft = System.Drawing.Image.FromFile(strLeftImagePath);
        }
        if (null != strRightImagePath)
        {
            imImageRight = System.Drawing.Image.FromFile(strRightImagePath);
        }
        // Calculate the Left, Right values based on the Clip region bounds
        RectangleF recRegion = myGraphics.VisibleClipBounds;
        fLeftCol = unGapFromLeftMargin;
        fLeftRow = recRegion.Height / 2.0f; // To display the Bar in the middle
        fRightCol = recRegion.Width - unGapFromRightMargin;
        fRightRow = fLeftRow;
        fThumb1Point = fLeftCol;
        fThumb2Point = fRightCol;
        fTotalWidth = recRegion.Width - (unGapFromRightMargin + unGapFromLeftMargin);
        fDividedWidth = fTotalWidth / (float)(nNumberOfLabels - 1);
        // This is used to calculate the Thumb Point from the Range1, Range2 Value
        for(int nIndexer = 0;nIndexer < nNumberOfLabels;nIndexer++)
        {
            if (strRange1.Equals(strSplitLabels[nIndexer]))
            {
                fThumb1Point = fLeftCol + fDividedWidth * nIndexer;
            }
            else if (strRange2.Equals(strSplitLabels[nIndexer]))
            {
                fThumb2Point = fLeftCol + fDividedWidth * nIndexer;
            }
        }
        // This is for Calculating the final Thumb points
        ptThumbPoints1[0].X = fThumb1Point;
        ptThumbPoints1[0].Y = fLeftRow - 3.0f;
        ptThumbPoints1[1].X = fThumb1Point;
        ptThumbPoints1[1].Y = fLeftRow - 3.0f - fHeightOfThumb;
        ptThumbPoints1[2].X = (fThumb1Point + fWidthOfThumb);
        ptThumbPoints1[2].Y = fLeftRow - 3.0f - fHeightOfThumb/2.0f;
        ptThumbPoints2[0].X = fThumb2Point;
        ptThumbPoints2[0].Y = fRightRow - 3.0f;
        ptThumbPoints2[1].X = fThumb2Point;
        ptThumbPoints2[1].Y = fRightRow - 3.0f - fHeightOfThumb;
        ptThumbPoints2[2].X = fThumb2Point - fWidthOfThumb;
        ptThumbPoints2[2].Y = fRightRow - 3.0f - fHeightOfThumb/2.0f;
    }
    catch
    {
        // In a more professional code, a proper error message is required here

    }
}
/// <CalculateValues>
/// The below is the method that calculates the values to be place while painting
/// </CalculateValues>
/// 
#endregion

The seventh region is 'Paint Method override'. This is the important one that draws the control on the screen. The code is very simple here. The first for loop draws the Labels on the screen. The next one draws the range values on the screen. The next draws Slider1 (mentioned in the code as Thumb1) and Slider2. The following lines in this method are to draw the in-focus and disabled colors properly based on the calculated values using the above section.

I implemented a very simple animation in this paint method. This is not a very professional one, but provides a basic understanding of the animation. Once the mouse is up, we set the bAnimateSlider to true. This is used in the paint method to repeatedly call the Draw method to animate. However, if the animation takes longer than a second (1000 ms), then we discard the animation.

private void OnPaintDrawSliderAndBar(System.Drawing.Graphics myGraphics, PaintEventArgs e)
{
    System.Drawing.Brush brSolidBrush;
    System.Drawing.Pen myPen;
// If Interesting mouse event happened on the Thumb1 Draw Thumb1
    if (bMouseEventThumb1)
    {
        brSolidBrush = new System.Drawing.SolidBrush(this.BackColor);
        if (null != strLeftImagePath)
        {
            myGraphics.FillRectangle(brSolidBrush, ptThumbPoints1[0].X, 
                        ptThumbPoints1[1].Y, fWidthOfThumb, fHeightOfThumb);
        }
        else
        {
            myGraphics.FillClosedCurve(brSolidBrush, ptThumbPoints1, 
                       System.Drawing.Drawing2D.FillMode.Winding, 0f);
        }
    }
    //if interesting mouse event happened on Thumb2 draw thumb2
    if (bMouseEventThumb2)
    {
        brSolidBrush = new System.Drawing.SolidBrush(this.BackColor);
        if (null != strRightImagePath)
        {
            myGraphics.FillRectangle(brSolidBrush, ptThumbPoints2[2].X, 
                ptThumbPoints2[1].Y, fWidthOfThumb, fHeightOfThumb);
        }
        else
        {
            myGraphics.FillClosedCurve(brSolidBrush, ptThumbPoints2, 
            System.Drawing.Drawing2D.FillMode.Winding, 0f);
        }
    }
    // The Below lines are to draw the Thumb and the Lines 
    // The Infocus and the Disabled colors are drawn properly based
    // onthe calculated values
    brSolidBrush = new System.Drawing.SolidBrush(clrInFocusRangeLabelColor);
    myPen = new System.Drawing.Pen(clrInFocusRangeLabelColor, unSizeOfMiddleBar);
    ptThumbPoints1[0].X = fThumb1Point;
    ptThumbPoints1[1].X = fThumb1Point;
    ptThumbPoints1[2].X = fThumb1Point + fWidthOfThumb;
    ptThumbPoints2[0].X = fThumb2Point;
    ptThumbPoints2[1].X = fThumb2Point;
    ptThumbPoints2[2].X = fThumb2Point - fWidthOfThumb;

    myPen = new System.Drawing.Pen(clrDisabledBarColor, unSizeOfMiddleBar);
    myGraphics.DrawLine(myPen, fLeftCol, ptThumbPoints1[2].Y, 
                        fThumb1Point, ptThumbPoints1[2].Y);
    myGraphics.DrawLine(myPen, fLeftCol, ptThumbPoints1[2].Y, fLeftCol, 
            ptThumbPoints1[2].Y + fntLabelFont.SizeInPoints);
    myGraphics.DrawLine(myPen, fRightCol, ptThumbPoints1[2].Y, fRightCol, 
            ptThumbPoints1[2].Y + fntLabelFont.SizeInPoints);
    brSolidBrush = new System.Drawing.SolidBrush(clrStringOutputFontColor);
    myGraphics.DrawString(strRangeString, fntRangeOutputStringFont, 
                          brSolidBrush, fLeftCol, 
                          fLeftRow * 2 - fntRangeOutputStringFont.Size - 3);
    myPen = new System.Drawing.Pen(clrInFocusBarColor, unSizeOfMiddleBar);
    myGraphics.DrawLine(myPen, ptThumbPoints1[2].X, ptThumbPoints1[2].Y, 
            fThumb2Point,ptThumbPoints1[2].Y);
    myPen = new System.Drawing.Pen(clrDisabledBarColor, unSizeOfMiddleBar);
    myGraphic    s.DrawLine(myPen, fThumb2Point, ptThumbPoints2[2].Y, fRightCol, 
                ptThumbPoints2[2].Y);
    // If the Thumb is an Image it draws the Image or else it draws the Thumb
    if (null != strLeftImagePath)
    {
        myGraphics.DrawImage(imImageLeft, ptThumbPoints1[0].X, 
                    ptThumbPoints1[1].Y, fWidthOfThumb, fHeightOfThumb);
    }
    else
    {
        brSolidBrush = new System.Drawing.SolidBrush(clrThumbColor);
        myGraphics.FillClosedCurve(brSolidBrush, ptThumbPoints1, 
                   System.Drawing.Drawing2D.FillMode.Winding, 0f);
    }
    // If the Thumb is an Image it draws the Image or else it draws the Thumb
    if (null != strRightImagePath)
    {
        myGraphics.DrawImage(imImageRight, ptThumbPoints2[2].X, ptThumbPoints2[1].Y, 
                    fWidthOfThumb, fHeightOfThumb);
    }
    else
    {
        brSolidBrush = new System.Drawing.SolidBrush(clrThumbColor);
        myGraphics.FillClosedCurve(brSolidBrush, ptThumbPoints2, 
                   System.Drawing.Drawing2D.FillMode.Winding, 0f);
    }
}
protected override void OnPaint(PaintEventArgs e)
{
     
    try
    {
        // Declaration of the local variables that are used.
        System.Drawing.Brush brSolidBrush;
        float fDividerCounter;
        float fIsThumb1Crossed, fIsThumb2Crossed;
        string strRangeOutput;
        string strNewRange1, strNewRange2;
        
        // Initialization of the local variables.
        System.Drawing.Graphics myGraphics = this.CreateGraphics();
        ePaintArgs = e;
        fDividerCounter = 0;
        brSolidBrush = new System.Drawing.SolidBrush(clrDisabledRangeLabelColor);
        strNewRange1 = null;
        strNewRange2 = null;

        
        // This loop is to draw the Labels on the screen.
        for(int nIndexer = 0;nIndexer < nNumberOfLabels;nIndexer++)
        {
            fDividerCounter = fLeftCol + fDividedWidth * nIndexer ;
            fIsThumb1Crossed = fDividerCounter + strSplitLabels[nIndexer].Length * 
                    fntLabelFont.SizeInPoints/2;
            fIsThumb2Crossed = fDividerCounter - 
                 (strSplitLabels[nIndexer].Length - 1) * fntLabelFont.SizeInPoints/2;
            if (fIsThumb1Crossed >= fThumb1Point && strNewRange1 == null)
            {
                // If Thumb1 Crossed this Label Make it in Focus color
                brSolidBrush = 
                  new System.Drawing.SolidBrush(clrInFocusRangeLabelColor);
                strNewRange1 = strSplitLabels[nIndexer];
            }
            if (fIsThumb2Crossed > fThumb2Point)
            {
                // If Thumb2 crossed this draw the labes
                // following this in disabled color
                brSolidBrush = 
                  new System.Drawing.SolidBrush(clrDisabledRangeLabelColor);
                //strNewRange2 = strSplitLabels[nIndexer];
            }
            else 
            {
                strNewRange2 = strSplitLabels[nIndexer];
            }
            myGraphics.DrawString(strSplitLabels[nIndexer], fntLabelFont, brSolidBrush, 
                                  fDividerCounter - ((fntLabelFont.SizeInPoints) * 
                                  strSplitLabels[nIndexer].Length)/2, fLeftRow);
        }
        // This is to draw exactly the Range String like "Range 10 to 100" 
        // This will draw the information only if there is a change. 
        if (strNewRange1 != null && strNewRange2 != null && 
        (!strRange1.Equals(strNewRange1) || !strRange2.Equals(strNewRange2)) ||
        (!bMouseEventThumb1 && !bMouseEventThumb2))
        {
            brSolidBrush = new System.Drawing.SolidBrush(this.BackColor);
            strRangeOutput = strRange1 + " - " + strRange2;
            myGraphics.DrawString(strRangeOutput , fntRangeOutputStringFont, brSolidBrush, 
                                  fLeftCol + fntRangeOutputStringFont.Size * 
                                  strRangeString.Length , 
                                  fLeftRow * 2 - fntRangeOutputStringFont.Size - 3);
            brSolidBrush = new System.Drawing.SolidBrush(clrStringOutputFontColor);
            strRangeOutput = strNewRange1 + " - " + strNewRange2;
            myGraphics.DrawString(strRangeOutput , fntRangeOutputStringFont, brSolidBrush, 
                        fLeftCol + fntRangeOutputStringFont.Size * strRangeString.Length , 
                        fLeftRow * 2 - fntRangeOutputStringFont.Size - 3);
            strRange1 = strNewRange1;
            strRange2 = strNewRange2;
    }

    if (bAnimateTheSlider)
    {
        float fTempThumb1Point = fThumb1Point;
        float fTempThumb2Point = fThumb2Point;
        int nToMakeItTimely = System.Environment.TickCount;
        for (fThumb1Point = fThumbPoint1Prev, fThumb2Point = fThumbPoint2Prev; 
        fThumb1Point <= fTempThumb1Point || fThumb2Point >= fTempThumb2Point; 
        fThumb1Point += 3.0f, fThumb2Point -= 3.0f)
        {
            bMouseEventThumb1 = true; 
            bMouseEventThumb2 = true;
            if (fThumb1Point > fTempThumb1Point)
            {
                fThumb1Point = fTempThumb1Point;
            }    


            if (fThumb2Point < fTempThumb2Point)
            {
                fThumb2Point = fTempThumb2Point;
            }
            OnPaintDrawSliderAndBar(myGraphics, e);
            if (System.Environment.TickCount - nToMakeItTimely >= 1000)
            {
                // Hey its not worth having animation for more than 1 sec. 
                break;
            }
            System.Threading.Thread.Sleep(1);
        }
        fThumb1Point = fTempThumb1Point;
        fThumb2Point = fTempThumb2Point;
        bMouseEventThumb1 = true;
        bMouseEventThumb2 = true;
        OnPaintDrawSliderAndBar(myGraphics, e);
        bAnimateTheSlider = false;
        bMouseEventThumb1 = false;
        bMouseEventThumb2 = false;
        OnPaintDrawSliderAndBar(myGraphics, e);
    }
    else
    {
        OnPaintDrawSliderAndBar(myGraphics, e);
    }
    // calling the base class.
    base.OnPaint (e);
}
catch
{
    //System.Windows.Forms.MessageBox.Show("An Unexpected Error occured. " + 
    //           "Please contact the tool vendor", "Error!");
    //throw;
}
}
}
/// <Paint >
/// The Above is the method that draws the control on the screen
/// </Paint >
#endregion

The eighth region is 'Methods used for handling mouse events'. This section has methods to capture the mouse events up, down, and move. These methods are used to see if the mouse activity by the end user is interesting enough.

#region Methods used for handling Mouse Events

private void RangeSelectorControl_MouseUp(object sender, 
             System.Windows.Forms.MouseEventArgs e)
{
    // If the Mouse is Up then set the Event to false
    bMouseEventThumb1 = false;
    bMouseEventThumb2 = false;
    // Storing these values for animating the slider
    fThumbPoint1Prev = fThumb1Point;
    fThumbPoint2Prev = fThumb2Point;

    CalculateValues();
    bAnimateTheSlider = true;
    this.Refresh();
}
private void RangeSelectorControl_MouseDown(object sender, 
             System.Windows.Forms.MouseEventArgs e)
{
    // If the Mouse is Down and also on the Thumb1
    if (e.X >= ptThumbPoints1[0].X && e.X <= ptThumbPoints1[2].X &&
        e.Y >= ptThumbPoints1[1].Y && e.Y <= ptThumbPoints1[0].Y)
    {
        bMouseEventThumb1 = true;
    }
    // Else If the Mouse is Down and also on the Thumb2
    else if (e.X >= ptThumbPoints2[2].X && e.X <= ptThumbPoints2[0].X &&
    e.Y >= ptThumbPoints2[1].Y && e.Y <= ptThumbPoints2[0].Y)
    {
        bMouseEventThumb2 = true;
    }
}
private void RangeSelectorControl_MouseMove(object sender, 
             System.Windows.Forms.MouseEventArgs e)
{
    // If the Mouse is moved pressing the left button on Thumb1
    if (bMouseEventThumb1 && e.Button == 
          System.Windows.Forms.MouseButtons.Left && e.X >= fLeftCol )
    {
        // The below code is for handlling the Thumb1 Point
        if (strRange1.Equals(strRange2))
        {
            if (e.X < fThumb1Point)
            {
                fThumb1Point = e.X;
                OnPaint(ePaintArgs);
            }
        }
        else if (fThumb2Point - fWidthOfThumb > e.X)
        {
            fThumb1Point = e.X;
            OnPaint(ePaintArgs);
        }
        else
        {
            bMouseEventThumb1 = false;
        }
    } 
    //Else If the Mouse is moved pressing the left button on Thumb2
    else if (bMouseEventThumb2 && e.Button == 
             System.Windows.Forms.MouseButtons.Left && e.X <= fRightCol)
    {
        // The below code is for handlling the Thumb1 Point
        if (strRange1.Equals(strRange2))
        {    
            if (e.X > fThumb2Point)
            {
                fThumb2Point = e.X;
                OnPaint(ePaintArgs);
            }
        }
        else if (fThumb1Point + fWidthOfThumb < e.X)
        {
            fThumb2Point = e.X;
            OnPaint(ePaintArgs);
        }
        else
        {
            bMouseEventThumb2 = false;
        }
    }
    // If there is an Object Notification
    if (null != objNotifyClient)
    {
        objNotifyClient.Range1 = strRange1;
        objNotifyClient.Range2 = strRange2;
    }
}

/// <MouseEvents>
/// The below are the methods used for handling Mouse Events
/// </Mouse Events>
/// 
#endregion

The last region is 'Notification class...'. This section contains a small class that the user may pass to get an active event notification.

/// <RangeSelectorControl_Resize>
/// The below is the small Notification class that can be used by the client
/// </RangeSelectorControl_Resize>
/// 
#region Notification class for client to register with the control for changes
public class NotifyClient
{
    private string strRange1, strRange2;
    public string Range1
    {
        set
        {
            strRange1 = value;
        }
        get
        {
            return strRange1;
        }
    }

    public string Range2
    {
        set
        {
            strRange2 = value;
        }
        get
        {
            return strRange2;
        }
    }
}
/// <RangeSelectorControl_Resize>
/// The Above is the small Notification class that can be used by the client
/// </RangeSelectorControl_Resize>
/// 
#endregion

History

  • 21 Aug 2008 - First version. Added source snippets in the 'Control code explained' section. Added snap feature as suggested by a reviewer. Added another version with a little animation.

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