Custom ProgressBar – WinForms & C#

En este tutorial crearemos una barra de progreso personalizada con una apariencia muy elegante y atractiva. Donde podemos personalizar los colores o tamaño de todos los componentes a voluntad. Por lo tanto, la barra de progreso personalizada permite las siguientes personalizaciones:

  • Cambiar el color y la altura del canal o rastreador.
  • Cambiar el color y la altura del deslizador o indicador.
  • Mostrar el valor actual (izquierda, derecha, centro, deslizante, No mostrar).
  • Mostrar el valor máximo.
  • Agregar un símbolo o cualquier texto antes o después del valor.
  • Cambiar el color y el tamaño del texto del valor.
  • Cambiar la ubicación del texto del valor.
  • Cambiar la ubicación del eje Y del texto del valor.
  • Y mantiene todas las funciones y comportamientos de la barra de progreso convencional, excepto el cambio de dirección del deslizador o indicador.

Bien, comencemos el tutorial.

1.- Preparar una clase y una enumeración

  • Agregar una nueva clase para la barra de progreso personalizada (Yo lo nombraré RJProgressBar).
  • Importar la librería de Windows Forms, Drawing y ComponentModel.
  • Crear una enumeración para la posición del texto del valor de la barra de progreso.
  • Finalmente, heredar del control ProgressBar.
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.ComponentModel;

namespace CustomControls.RJControls
{
    public enum TextPosition
    {
        Left,
        Right,
        Center,
        Sliding,
        None
    }

    class RJProgressBar : ProgressBar
    {

2.- Definir campos

  • Definir los campos de apariencia y asignar sus valores predeterminados.
  • Definir 2 campos booleanos para detener la pintara del fondo del control, o toda la pintura del control.
    //Fields
    //-> Appearance
    private Color channelColor = Color.LightSteelBlue;
    private Color sliderColor = Color.RoyalBlue;
    private Color foreBackColor = Color.RoyalBlue;
    private int channelHeight = 6;
    private int sliderHeight = 6;
    private TextPosition showValue = TextPosition.Right;
    private string symbolBefore = "";
    private string symbolAfter = "";
    private bool showMaximun = false;

    //-> Others
    private bool paintedBack = false;
    private bool stopPainting = false;

3.- Constructor

  • Especificar el estilo y el comportamiento del control. En este caso, el control será pintado por el usuario y no por el sistema operativo. Hacerlo es muy importante, ya que solo entonces es posible anular el evento Paint en los controles nativos de Windows.
  • Y finalmente establecer un color de texto predeterminado.
    //Constructor
    public RJProgressBar()
    {
        this.SetStyle(ControlStyles.UserPaint, true);
        this.ForeColor = Color.White;
    }

4.- Generar propiedades

Generar las propiedades de los campos de apariencia declarados anteriormente, y así poder cambiar la apariencia del control desde el cuadro de propiedades. Luego, en los descriptores de acceso Set, invocar el método Invalidate, para repintar el control y así actualizar la apariencia.

   //Properties
    [Category("RJ Code Advance")]
    public Color ChannelColor
    {
        get { return channelColor; }
        set
        {
            channelColor = value;
            this.Invalidate();
        }
    }

    [Category("RJ Code Advance")]
    public Color SliderColor
    {
        get { return sliderColor; }
        set
        {
            sliderColor = value;
            this.Invalidate();
        }
    }

    [Category("RJ Code Advance")]
    public Color ForeBackColor
    {
        get { return foreBackColor; }
        set
        {
            foreBackColor = value;
            this.Invalidate();
        }
    }

    [Category("RJ Code Advance")]
    public int ChannelHeight
    {
        get { return channelHeight; }
        set
        {
            channelHeight = value;
            this.Invalidate();
        }
    }

    [Category("RJ Code Advance")]
    public int SliderHeight
    {
        get { return sliderHeight; }
        set
        {
            sliderHeight = value;
            this.Invalidate();
        }
    }

    [Category("RJ Code Advance")]
    public TextPosition ShowValue
    {
        get { return showValue; }
        set
        {
            showValue = value;
            this.Invalidate();
        }
    }

    [Category("RJ Code Advance")]
    public string SymbolBefore
    {
        get { return symbolBefore; }
        set
        {
            symbolBefore = value;
            this.Invalidate();
        }
    }

    [Category("RJ Code Advance")]
    public string SymbolAfter
    {
        get { return symbolAfter; }
        set
        {
            symbolAfter = value;
            this.Invalidate();
        }
    }

    [Category("RJ Code Advance")]
    public bool ShowMaximun
    {
        get { return showMaximun; }
        set
        {
            showMaximun = value;
            this.Invalidate();
        }
    }

    [Category("RJ Code Advance")]
    [Browsable(true)]
    [EditorBrowsable(EditorBrowsableState.Always)]
    public override Font Font
    {
        get { return base.Font; }
        set
        {
            base.Font = value;
        }
    }

    [Category("RJ Code Advance")]
    public override Color ForeColor
    {
        get { return base.ForeColor; }
        set
        {
            base.ForeColor = value;
        }
    }

5.- Pintar el fondo y el canal

Para pintar el fondo y el canal de la barra de progreso personalizada, debe anular el método de evento OnPaintBackground y realizar los dibujos o la pintura como se muestra en el siguiente código.

//-> Paint the background & channel
protected override void OnPaintBackground(PaintEventArgs pevent)
{
    if (stopPainting == false)
    {
        if (paintedBack == false)
        {
            //Fields
            Graphics graph = pevent.Graphics;
            Rectangle rectChannel = new Rectangle(0, 0, this.Width, ChannelHeight);
            using (var brushChannel = new SolidBrush(channelColor))
            {
                if (channelHeight >= sliderHeight)
                    rectChannel.Y = this.Height - channelHeight;
                else rectChannel.Y = this.Height - ((channelHeight + sliderHeight) / 2);

                //Painting
                graph.Clear(this.Parent.BackColor);//Surface
                graph.FillRectangle(brushChannel, rectChannel);//Channel

                //Stop painting the back & Channel
                if (this.DesignMode == false)
                    paintedBack = true;
            }
        }
        //Reset painting the back & channel
        if (this.Value == this.Maximum || this.Value == this.Minimum)
            paintedBack = false;
    }
}

6.- Pintar el deslizador o indicador

En este caso, para pintar el deslizador de la barra de progreso personalizada, debe anular el método de evento OnPaint como se muestra en el siguiente código.

//-> Paint slider
protected override void OnPaint(PaintEventArgs e)
{
    if (stopPainting == false)
    {
        //Fields
        Graphics graph = e.Graphics;
        double scaleFactor = (((double)this.Value - this.Minimum) / ((double)this.Maximum - this.Minimum));
        int sliderWidth = (int)(this.Width * scaleFactor);
        Rectangle rectSlider = new Rectangle(0, 0, sliderWidth, sliderHeight);
        using (var brushSlider = new SolidBrush(sliderColor))
        {
            if (sliderHeight >= channelHeight)
                rectSlider.Y = this.Height - sliderHeight;
            else rectSlider.Y = this.Height - ((sliderHeight + channelHeight) / 2);

            //Painting
            if (sliderWidth > 1) //Slider
                graph.FillRectangle(brushSlider, rectSlider);
            if (showValue != TextPosition.None) //Text
                DrawValueText(graph, sliderWidth, rectSlider);
        }
    }
    if (this.Value == this.Maximum) stopPainting = true;//Stop painting
    else stopPainting = false; //Keep painting
}

7.- Pintar el texto del valor

Crear el siguiente método e invocarlo en el método de evento OnPaint, como se muestra en el bloque de arriba y en el siguiente código.

//-> Paint value text
private void DrawValueText(Graphics graph, int sliderWidth, Rectangle rectSlider)
{
    //Fields
    string text = symbolBefore + this.Value.ToString() + symbolAfter;
    if (showMaximun) text = text + "/" + symbolBefore + this.Maximum.ToString() + symbolAfter;
    var textSize = TextRenderer.MeasureText(text, this.Font);
    var rectText = new Rectangle(0, 0, textSize.Width, textSize.Height + 2);
    using (var brushText = new SolidBrush(this.ForeColor))
    using (var brushTextBack = new SolidBrush(foreBackColor))
    using (var textFormat = new StringFormat())
    {
        switch (showValue)
        {
            case TextPosition.Left:
                rectText.X = 0;
                textFormat.Alignment = StringAlignment.Near;
                break;

            case TextPosition.Right:
                rectText.X = this.Width - textSize.Width;
                textFormat.Alignment = StringAlignment.Far;
                break;

            case TextPosition.Center:
                rectText.X = (this.Width - textSize.Width) / 2;
                textFormat.Alignment = StringAlignment.Center;
                break;

            case TextPosition.Sliding:
                rectText.X = sliderWidth - textSize.Width;
                textFormat.Alignment = StringAlignment.Center;
                //Clean previous text surface
                using (var brushClear = new SolidBrush(this.Parent.BackColor))
                {
                    var rect = rectSlider;
                    rect.Y = rectText.Y;
                    rect.Height = rectText.Height;
                    graph.FillRectangle(brushClear, rect);
                }
                break;
        }
        //Painting
        graph.FillRectangle(brushTextBack, rectText);
        graph.DrawString(text, this.Font, brushText, rectText, textFormat);
    }
}

Video tutorial