Crear un protector de pantalla con C#

Hace ya algún tiempo, en una de las empresas en las que colaboro, se me ocurrió la idea de hacer un protector de pantalla un poco personalizado. Como el tema del diseño no es que sea mi fuerte, opté por hacer algo sencillito y que, entre otras cosas, intentara tener la aceptación de los usuarios. Así, me marqué los siguiente objetivos:

  • Fondo negro ( ¿he dicho que el diseño no es lo mío? )
  • Movimiento
  • Sencillez

Es curioso el efecto que puede llegar a hacer un par de logotipos moviéndose por la pantalla de nuestro ordenador…así que nos ponemos manos a la obra.

¿ Qué necesito para crear un protector de pantalla?.

Nada. Sólo las herramientas que estamos usando día tras día. Igual ahora que estamos tan metidos todos en los temas de Web, el empezar un proyecto de WinForms nos da un poco de pereza, pero creo que todos hemos empezado por ahí. De hecho, un protector de pantalla tan sólo es un proyecto .EXE al cual, una vez compilado, le cambiaremos la extensión por .SCR. Vale… ya sé que un protector de pantalla debería tener una serie de opciones que el usuario podría cambiar y además se debería poder ver desde la ventana de Vista Previa de los protectores, pero estamos hablando de un protector sencillito y corporativo, de tal manera que, para simplificar, no vamos a dejar que el usuario modifique ninguna opción (Al fin y al cabo, cuando esté acabado, se lo impondremos mediante directiva de sistema, así que no hacen falta muchas opciones).

Empezamos pues, creando un proyecto WinForms, que tendrá un único formulario, al que cambiaremos las propiedades siguientes:

   1: public Form1()
   2: {
   3:     InitializeComponent();
   4:  
   5:     // Fondo negro
   6:     this.BackColor = Color.FromArgb(0, 0, 0);
   7:     // Ejecución maximizada
   8:     this.WindowState = FormWindowState.Maximized;
   9:     // Eliminamos los bordes del formulario
  10:     this.FormBorderStyle = FormBorderStyle.None;
  11:     // El formulario recibirá cualquier evento antes que sus controles
  12:     this.KeyPreview = true;
  13:     // El formulario, siempre en primer plano
  14:     this.TopLevel = true;
  15: }

Configuremos cómo cerrar el formulario

Antes de nada, intentaremos configurar el entorno para que podamos cerrar el protector. A mí siempre me han molestado los protectores de pantalla que se descargan cuando, sencillamente mueves el ratón, aunque sea por error, así que como ahora soy yo el que lo hace, lo haré a mi gusto: para descargar el protector, sólo será válida la tecla de ESC o un clic de ratón. Estas opciones son muy fáciles de modificar al gusto del consumidor.

   1: public Form1()
   2: {
   3:     ... // Código anterior
   4:     this.TopLevel = true;
   5:  
   6:     // Control de eventos
   7:     this.Click += new EventHandler(Form1_Click);
   8:     this.KeyPress += new KeyPressEventHandler(Form1_KeyPress);
   9: }
  10:  
  11: void Form1_Click(object sender, EventArgs e)
  12: { 
  13:     CierraFormulario(); 
  14: }
  15:  
  16: void Form1_KeyPress(object sender, KeyPressEventArgs e)
  17: {
  18:     if (e.KeyChar == (char)Keys.Escape)
  19:         CierraFormulario(); 
  20: }
  21:  
  22: /// <summary>
  23: /// Cierra el formulario
  24: /// </summary>
  25: private void CierraFormulario()
  26: {
  27:     this.Close();
  28: }

Vamos a rellenar el formulario

Un Timer será nuestro mejor aliado a la hora de conseguir el efecto del movimiento cada cierto tiempo, así que lo dejamos preparado para que acceda al método movimientoObjetos() cada segundo.

   1: #region Temporizador
   2:  
   3: Timer temporizador;
   4:  
   5: private void activaTemporizador()
   6: {
   7:     temporizador = new Timer();
   8:     temporizador.Tick += new EventHandler(temporizador_Tick);
   9:  
  10:     temporizador.Interval = 1000;
  11:     temporizador.Enabled = true;
  12: }
  13:  
  14: void temporizador_Tick(object sender, EventArgs e)
  15: {
  16:     movimientoObjetos();
  17: }
  18: #endregion

Ahora nos queda la parte divertida, es decir, cómo distribuir algún que otro objeto por la pantalla y que se vayan moviendo cada segundo, es decir, cada vez que sea llamado el método movimientoObjetos().

Como tenemos muy asumido que el diseño no es lo nuestro y que las coordenadas de la pantalla es una especie de película de ciencia-ficción, vamos a echar mano de un componente del namespace System.Windows.Forms, concretamente el TableLayoutPanel. Este componente tiene un método que nos solventará el problema de ir desplazando cualquier objeto. El método en sí es SetCellPosition, que permite, precisamente eso, colocar un objeto en una posición cualquiera. Lo único que deberemos mirar con anterioridad es conocer si ya existe algo en esa posición y podemos comprobarlo con otro método (GetControlFromPosition) del mismo control.

Así pues, crearemos dentro de nuestro formulario un control TableLayoutPanel que tenga un número determinado de filas y columnas y que estén distribuidos de una manera uniforme. En mi caso, he creado un ejemplo de tabla 10×10 y he añadido algún que otro ornamento (estos controles nos dan la ventaja que poder jugar con las propiedades RowSpan y ColumnSpan de los controles que añadimos en ellos, por si tenemos algún gráfico más grande).

image_thumb[12]

Por tanto, ya podemos hacer un bucle que se recorra todos los controles que hemos ido añadiendo, que aleatoriamente seleccione una celda, mire si está ocupada y, si no es así, que deje el elemento:

   1: /// <summary>
   2: /// Provoca el efecto de movimiento de los objetos en pantalla
   3: /// </summary>
   4: private void movimientoObjetos()
   5: {
   6:     int filas = tabla.RowCount;
   7:     int columnas = tabla.ColumnCount;
   8:  
   9:     Random r = new Random(System.DateTime.Now.Millisecond);
  10:     bool posicionado;
  11:  
  12:     lblReloj.Text = DateTime.Now.ToShortTimeString();
  13:  
  14:     foreach (Control ctr in tabla.Controls)
  15:     {
  16:         posicionado = false;
  17:  
  18:         while (!posicionado)
  19:         {
  20:             int posibleColumna = r.Next(columnas);
  21:             int posibleFila = r.Next(filas);
  22:  
  23:             if (tabla.GetControlFromPosition(posibleColumna, posibleFila) == null)
  24:             {
  25:                 tabla.SetCellPosition(ctr, new TableLayoutPanelCellPosition(posibleColumna, posibleFila));
  26:                 posicionado = true;
  27:             }
  28:         }
  29:     }
  30:     Application.DoEvents();
  31: }

 

Pues, en principio, ya tenemos el esqueleto de nuestro propio Protector de Pantalla. Sé que es sencillito tal cuál está, pero tiene la ventaja de poder personalizarse al gusto sin tocar casi nada de código. En la empresa, por ejemplo, añadí dos o tres logotipos que tienen por ahí del grupo y además añadí una frase aleatoria que introducen los mismos usuarios en un apartado de la intranet. Estas cosas a los jefes suelen gustarles.

¡Vale, muy bien!, pero, ¿qué pasa con el ratón?

Cierto, se me olvidaba un detalle con el que me peleé bastante rato. Si se te ocurre dirigir, desde el Form_Load un disparador al mover el ratón, te encontrarás que no llega a abrirse. Para ello, lo que hice fue lo siguiente: Guardar un punto en el que se almacena la posición del ratón, así se puede comparar con una nueva coordenada en caso de que se desplace.

   1: #region Control de ratón
   2:  
   3: private Point mouseXY;
   4: void tabla_MouseMove(object sender, MouseEventArgs e)
   5: {
   6:     Point puntoActual = new Point(e.X, e.Y);
   7:     if (!mouseXY.IsEmpty)
   8:     {
   9:         if (puntoActual != mouseXY)
  10:             CierraFormulario();
  11:  
  12:     }
  13:     mouseXY = puntoActual;
  14: }
  15: #endregion

 

Ya sólo nos queda compilarlo, cambiar la extensión de .EXE a .SCR y añadirlo a la carpeta %System%Windows para que aparezca entre el resto de protectores.

Dejo la aplicación por si alguien quiere entretenerse y modificarlo a su gusto: enlace

Generador de NIF/NIE

En el blog anterior, http://alskare.wordpress.com, un día se me ocurrió hacer una rutina que permitiese validar NIF, NIE y CIF . Sí, todo a la vez, así evitamos al usuario que tenga que introducir el tipo de documento. Por cierto, un día de estos tengo que ordenar el código, acabar de arreglarlo y dejarlo aquí. Bueno, el caso es que, desde que colgué ese post, uno de los mensajes que he recibido más a menudo hace referencia a ver si conocía algún lugar en el que existiese algún tipo de “generador” de NIFs para hacer pruebas.

Como me he comprometido a escribir un mínimo de un post quincenal, después de la broma de la presentación se me ha ocurrido que este podría ser un inicio como otro cualquiera, así que ahí queda.

La rutina, como puede comprobarse incluye tres métodos públicos que permiten diferenciar entre NIF y NIE (dependiendo de las pruebas se puede hacer uso de uno u otro). Además, he incluido un nuevo método que obtiene una lista de ambos tipos de documentos.

 

   1: using System;
   2: using System.Collections.Generic;
   3:  
   4: namespace JnSoftware.Generadores
   5: {
   6:  
   7:     /// <summary>
   8:     /// Generador de NIF/NIE al azar.
   9:     /// </summary>
  10:     public class NifNies
  11:     {
  12:  
  13:         Random semilla;
  14:         List<string> retVal;
  15:  
  16:         public NifNies()
  17:         {
  18:             semilla = new Random(DateTime.Now.Millisecond);
  19:         }
  20:  
  21:  
  22:         /// <summary>
  23:         /// Obtiene una lista de strings con <paramref name="numDocuments"/> NIFs aleatorios
  24:         /// </summary>
  25:         public List<string> GetNIFs(int numDocuments)
  26:         {
  27:             retVal = new List<string>(numDocuments);
  28:             for (int i = 0; i < numDocuments; i++)
  29:                 retVal.Add(getNif());
  30:             return retVal;
  31:         }
  32:  
  33:  
  34:         /// <summary>
  35:         /// Obtiene una lista de strings con <paramref name="numDocuments"/> NIFs aleatorios
  36:         /// </summary>
  37:         public List<string> GetNIEs(int numDocuments)
  38:         {
  39:             retVal = new List<string>(numDocuments);
  40:             for (int i = 0; i < numDocuments; i++)
  41:                 retVal.Add(getNie());
  42:             return retVal;
  43:         }
  44:  
  45:         
  46:         /// <summary>
  47:         /// Obtiene una lista de strings
  48:         /// </summary>
  49:         /// <param name="numNifs">Número de NIFs aleatorios que contendrá la lista</param>
  50:         /// <param name="numNies">Número de NIEs aleatorios que contendrá la lista</param>
  51:         public List<string> GetDocuments(int numNifs, int numNies)
  52:         {
  53:             // Es curioso. Primero he intentado:
  54:             // retVal = GetNIFs(numNifs);
  55:             // retVal.AddRange(GetNIEs(numNies));
  56:             // Pero sólo devuelve los Nies
  57:  
  58:             List<string> nies = GetNIEs(numNies);
  59:             retVal = GetNIFs(numNifs);
  60:             retVal.AddRange(nies);
  61:             return retVal;
  62:         }
  63:  
  64:  
  65:         /// <summary>
  66:         /// Obtiene un número de DNI
  67:         /// </summary>
  68:         /// <returns>
  69:         /// Un número de DNI consta de 7 dígitos + 1 carácter de control
  70:         /// </returns>
  71:         private string getNif()
  72:         {
  73:             int numDNI = semilla.Next(1000, 100000000);
  74:             return numDNI.ToString("00000000") + letraDNI(numDNI);
  75:         }
  76:  
  77:  
  78:         /// <summary>
  79:         /// Obtiene un número de NIE
  80:         /// </summary>
  81:         /// <returns>
  82:         /// Un NIE consta de un carácter (Y o X) + 6 dígitos + Carácter de control
  83:         /// </returns>
  84:         private string getNie()
  85:         {
  86:             int numDNI = semilla.Next(1000, 10000000);
  87:             return letraNie() + numDNI.ToString("0000000") + letraDNI(numDNI);
  88:         }
  89:  
  90:  
  91:         /// <summary>
  92:         /// Obtiene una letra aleatoria para el NIE
  93:         /// </summary>
  94:         private char letraNie()
  95:         {
  96:             string nie = "XY";
  97:             return nie[semilla.Next(0,2)];
  98:         }
  99:  
 100:  
 101:         /// <summary>
 102:         /// Cálculo de la letra del DNI/NIF
 103:         /// </summary>
 104:         private string letraDNI(int dni)
 105:         {
 106:             int indice = dni % 23;
 107:             return "TRWAGMYFPDXBNJZSQVHLCKET"[indice].ToString();
 108:         }
 109:     }
 110:  
 111: }

 

Como siempre, confío sea de utilidad. A mí me ha servidor bastante cuando he tenido que crear “datos de pruebas” en las bases de datos.

Post de presentación

using System;

namespace JnSoftware.Posts
{
    public class PrimerPost
    {

        /* Campos */
        private string responsable;
        private string mensaje;
        private bool gracias;
        private string blogAnterior;

        /// <summary>
        /// Obtiene o establece el valor que indica si está controlado el entorno
        /// </summary>
        /// <remarks>
        /// Entendamos por control del entorno la posibilidad de subir ficheros, imágenes
        /// y el formateo correcto del código
        /// </remarks>
        public bool EntornoControlado { get; set; }

        /// <summary>
        /// Constructor de la clase
        /// </summary>
        public PrimerPost()
        {
            gracias = true;
            blogAnterior = "http://alskare.wordpress.com";
            responsable = "Rodrigo Corral";
            mensaje = getMessage();
            EntornoControlado = false;
        }

        /// <summary>
        /// Creación del mensaje de presentación
        /// </summary>
        private string getMessage()
        {
            string m = "Antes de nada, me gustaría dar las gracias a {0} por la oportunidad " +
                        "que me ofrece con este nuevo blog.n" +
                        "Para aquellos que no me conozcan, todavía se puede consultar mi antiguo " +
                        "blog en {1}.n" +
                        "Confío estar a la altura de las circunstancias.n";
            return string.Format(m, responsable, blogAnterior);
        }

        /// <summary>
        /// Muestra el mensaje de inicio y agradecimiento.
        /// </summary>
        private void Presentacion()
        {
            if (gracias)
                Console.WriteLine(mensaje);
            if (EntornoControlado)
                Console.WriteLine("Manos a la obra");
        }

        /// <summary>
        /// Ejecución de la aplicación
        /// </summary>
        public static void Main()
        {
            PrimerPost post = new PrimerPost();

            // TODO Leer configuración para controlar subir imágenes y archivos.
            post.EntornoControlado = false; 

            post.Presentacion();
            Console.Write("Proceso finalizado...: ");
            Console.ReadKey(true);
        }
    }
}