Custom DateTimePicker – Custom controls WinForm C#

Hola :), esta vez haremos un DateTimePicker personalizado, como la mayoría de ustedes saben, este es un control nativo de Windows y no tiene muchas opciones de personalización de apariencia, limitándonos a usar un solo estilo y diseño. Por eso hoy les enseñaré a romper esos límites.

Bien empecemos con el tutorial.

1.- Agregar los iconos

Primeramente agregaremos los iconos (CalendarIconDark & CalendarIconWhite) a los recursos del proyecto, puedes descargar los iconos desde alguna plataforma, o puedes descargar los iconos utilizados en este proyecto a través del siguiente botón.

2.- Crear clase

Como es de costumbre, agregaremos una clase para el DateTimePicker personalizado en nuestro proyecto de Windows Form. Pueden colocar el nombre que prefieran, en mi caso RJDatePicker.

3.- Importar librería Windows.Form y Drawing

Para hacer cualquier control personalizado, es necesario importar la librería Windows Forms y la librería de dibujos (Drawing).

using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;

4.- Heredar del control tradicional DateTimePicker

Puedes heredar un UserControl, pero en este caso simplemente heredaremos del control DateTimePicker de la librería Windows Form y así ampliar su funcionalidad y modificar la apariencia. Además de esta manera el control es mucho mas ligero y barato en tiempo de ejecución, y también fácil y rápido de personalizar mediante el evento Paint.

public class RJDatePicker : DateTimePicker
{

}

5.- Definir campos

En la clase, declararemos campos para la apariencia del botón y asignar sus valores predeterminados, por ejemplo: el color de fondo, color de texto, color de borde, y el tamaño de borde. También necesitaremos otros campos para almacenar otros valores del control, como, un campo para obtener o establecer si el calendario está desplegado o no.

//Fields
//-> Appearance
private Color skinColor = Color.MediumSlateBlue;
private Color textColor = Color.White;
private Color borderColor = Color.PaleVioletRed;
private int borderSize = 0;

//-> Other Values
private bool droppedDown = false;
private Image calendarIcon = Properties.Resources.calendarWhite;
private RectangleF iconButtonArea;
private const int calendarIconWidth = 34;
private const int arrowIconWidth = 17;

6.- Generar propiedades

Generamos propiedades para exponer los campos de apariencia, definidos anteriormente.

//Properties
public Color SkinColor
{
    get { return skinColor; }
    set
    {
        skinColor = value;
        if (skinColor.GetBrightness() >= 0.8F)
            calendarIcon = Properties.Resources.calendarDark;
        else calendarIcon = Properties.Resources.calendarWhite;
        this.Invalidate();
    }
}

public Color TextColor
{
    get { return textColor; }
    set
    {
        textColor = value;
        this.Invalidate();
    }
}

public Color BorderColor
{
    get { return borderColor; }
    set
    {
        borderColor = value;
        this.Invalidate();
    }
}

public int BorderSize
{
    get { return borderSize; }
    set
    {
        borderSize = value;
        this.Invalidate();
    }
}

7.- Constructor

En el constructor, especificamos el estilo y el comportamiento del control. En este caso, el control será pintado por el usuario y no por el sistema operativo. Tambien establecemos el tamaño mínimo del control, de está manera podemos cambiar el alto del DateTimePicker, también puedes hacerlo posteriormente desde el cuadro de propiedades.

//Constructor
public RJDatePicker()
{
    this.SetStyle(ControlStyles.UserPaint, true);
    this.MinimumSize = new Size(0, 35);
    this.Font = new Font(this.Font.Name, 9.5F);
}

8.- Anular Eventos de Comportamiento y Pintura

Será necesario anular el método de evento DropDown y CloseUp, para establecer el estado del calendario desplegable. Anular el método de evento KeyPress para indicar que si se controló el evento, esto para evitar cambiar el valor del DateTimePicker con las teclas numéricas. Anular el método de evento Paint para volver a dibujar el control, y finalmente anular el método de evento HandleCreated y MouseMove para cambiar el cursor del puntero del mouse cuando esté sobre el botón de icono del DateTimePicker, ya que no es posible saber si el puntero está sobre el icono desplegable, debido a que el tamaño del botón de icono del DateTimePicker varía según el ancho del control.

//Overridden methods
protected override void OnDropDown(EventArgs eventargs)
{
    base.OnDropDown(eventargs);
    droppedDown = true;
}
protected override void OnCloseUp(EventArgs eventargs)
{
    base.OnCloseUp(eventargs);
    droppedDown = false;
}
protected override void OnKeyPress(KeyPressEventArgs e)
{
    base.OnKeyPress(e);
    e.Handled = true;
}
protected override void OnPaint(PaintEventArgs e)
{
    using (Graphics graphics = this.CreateGraphics())
    using (Pen penBorder = new Pen(borderColor, borderSize))
    using (SolidBrush skinBrush = new SolidBrush(skinColor))
    using (SolidBrush openIconBrush = new SolidBrush(Color.FromArgb(50, 64, 64, 64)))
    using (SolidBrush textBrush = new SolidBrush(textColor))
    using (StringFormat textFormat = new StringFormat())
    {
        RectangleF clientArea = new RectangleF(0, 0, this.Width - 0.5F, this.Height - 0.5F);
        RectangleF iconArea = new RectangleF(clientArea.Width - calendarIconWidth, 0, calendarIconWidth, clientArea.Height);
        penBorder.Alignment = PenAlignment.Inset;
        textFormat.LineAlignment = StringAlignment.Center;

        //Draw surface
        graphics.FillRectangle(skinBrush, clientArea);
        //Draw text
        graphics.DrawString("   " + this.Text, this.Font, textBrush, clientArea, textFormat);
        //Draw open calendar icon highlight
        if (droppedDown == true) graphics.FillRectangle(openIconBrush, iconArea);
        //Draw border 
        if (borderSize >= 1) graphics.DrawRectangle(penBorder, clientArea.X, clientArea.Y, clientArea.Width, clientArea.Height);
        //Draw icon
        graphics.DrawImage(calendarIcon, this.Width - calendarIcon.Width - 9, (this.Height - calendarIcon.Height) / 2);

    }
}
protected override void OnHandleCreated(EventArgs e)
{
    base.OnHandleCreated(e);
    int iconWidth = GetIconButtonWidth();
    iconButtonArea = new RectangleF(this.Width - iconWidth, 0, iconWidth, this.Height);
}
protected override void OnMouseMove(MouseEventArgs e)
{
    base.OnMouseMove(e);
    if (iconButtonArea.Contains(e.Location))
        this.Cursor = Cursors.Hand;
    else this.Cursor = Cursors.Default;
}

Si no te gusta anular los métodos de evento, puedes suscribir los eventos desde el constructor. Yo lo hice de esa manera para agilizar el video tutorial.

9.- Definir método para obtener el ancho del botón de icono

crearemos un método privado para obtener el ancho del botón de icono y así poder cambiar el cursor del puntero del mouse.

//Private methods
private int GetIconButtonWidth()
{
    int textWidh = TextRenderer.MeasureText(this.Text, this.Font).Width;
    if (textWidh <= this.Width - (calendarIconWidth + 20))
        return calendarIconWidth;
    else return arrowIconWidth;
}

Eso es todo, sin embargo, aún falta optimizar algunas cosas, por ejemplo, no tiene soporte para cambiar la hora, con un poco más trabajo puedes agregar esta funcionalidad o simplemente crear otro control selector de hora, y si ves algún problema, puedes ajustar los valores o agregarlo.

Video Tutorial