miércoles, 21 de diciembre de 2011

15-Puzzle

Hace unos días empecé a aprender a programar aplicaciones para Android. El primer ejercicio interesante que aparece es crear un juego N-puzzle. Mientras adquiero los conocimientos necesarios en Android para poder llevarlo a cabo y me deshago de la gripe que padezco, he decidido hacerlo en .NET. Es una versión bastante simple (hay que añadir la posiblidad de seleccionar el nivel de dificultad y alguna otra mejora) pero ayuda a practicar algunos conceptos interesantes. El resultado os lo muestro a continuación con el código y las imágenes.




using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace nPuzzle01
{
    public partial class Form1 : Form
    {
        private int indiceHueco = 16;

        public Form1()
        {
            InitializeComponent();
        }

        private void btn_Click(object sender, EventArgs e)
        {
            Button btn;
            String nombre = "";
            int indice = 0;
            int numero = 0;

            //Si el botón no es el hueco
            btn = (Button)sender;
            if ((btn.Tag!=null) && (btn.Tag.ToString() != ""))
            {
                //Comprobamos si el hueco es uno de los botones adyacentes
                nombre = btn.Name;
                indice = Convert.ToInt32(nombre.Substring(nombre.IndexOf('n') + 1));
                numero = Convert.ToInt32(btn.Tag);
           

                if (indice - 1 == indiceHueco)
                {
                    //Cambiamos los contenidos de los botones
                    nombre = "btn" + (indice - 1).ToString();
                    ((Button)pnl.Controls[nombre]).Tag = btn.Tag;
                    ((Button)pnl.Controls[nombre]).BackgroundImage = btn.BackgroundImage;
                    btn.BackgroundImage = null;
                    btn.BackColor = Color.LightGray;
                    indiceHueco = Convert.ToInt32(btn.Name.Substring(nombre.IndexOf('n') + 1));
                    btn.Tag = "";
                }

                if (indice + 1 == indiceHueco)
                {
                    //Cambiamos los contenidos de los botones
                    nombre = "btn" + (indice + 1).ToString();
                    ((Button)pnl.Controls[nombre]).Tag = btn.Tag;
                    ((Button)pnl.Controls[nombre]).BackgroundImage = btn.BackgroundImage;
                    btn.BackgroundImage = null;
                    btn.BackColor = Color.LightGray;
                    indiceHueco = Convert.ToInt32(btn.Name.Substring(nombre.IndexOf('n') + 1));
                    btn.Tag = "";
                }

                if (indice - 4 == indiceHueco)
                {
                    //Cambiamos los contenidos de los botones
                    nombre = "btn" + (indice - 4).ToString();
                    ((Button)pnl.Controls[nombre]).Tag = btn.Tag;
                    ((Button)pnl.Controls[nombre]).BackgroundImage = btn.BackgroundImage;
                    btn.BackgroundImage = null;
                    btn.BackColor = Color.LightGray;
                    indiceHueco = Convert.ToInt32(btn.Name.Substring(nombre.IndexOf('n') + 1));
                    btn.Tag = "";
                }

                if (indice + 4 == indiceHueco)
                {
                    //Cambiamos los contenidos de los botones
                    nombre = "btn" + (indice + 4).ToString();
                    ((Button)pnl.Controls[nombre]).Tag = btn.Tag;
                    ((Button)pnl.Controls[nombre]).BackgroundImage = btn.BackgroundImage;
                    btn.BackgroundImage = null;
                    btn.BackColor = Color.LightGray;
                    indiceHueco = Convert.ToInt32(btn.Name.Substring(nombre.IndexOf('n') + 1));
                    btn.Tag = "";
                }

            }
        }

        private void btnSeleccionar_Click(object sender, EventArgs e)
        {
            Image img;
            OpenFileDialog ofd;
            string ruta = "";
            string nombre = "";

            ofd = new OpenFileDialog();
            if (ofd.ShowDialog()== DialogResult.OK) {
                ruta = ofd.FileName;
                img = Bitmap.FromFile(ruta);

                Bitmap bmp = new Bitmap(300, 300);

                Graphics g = Graphics.FromImage(bmp);
                g.DrawImage(img, 0, 0, 300, 300);

                Bitmap bmp1, img1;

                for (int i = 1; i <= 4; i++)
                {
                    for (int j = 1; j <= 4; j++)
                    {
                        if (!(i == 1 && j == 1))
                        {
                            bmp1 = CropImage(bmp, new Rectangle((((j - 1) * bmp.Width) / 4),
                                (((i - 1) * bmp.Height) / 4), bmp.Width / 4, bmp.Height / 4));
                            img1 = new Bitmap(bmp.Width / 4, bmp.Height / 4);
                            Graphics g21 = Graphics.FromImage(img1);
                            g21.DrawImage(bmp1, 0, 0);
                            nombre = "btn" + (((4-i) * 4) + (5-j)).ToString();
                            ((Button)pnl.Controls[nombre]).BackgroundImage = img1;
                        }
                    }
                }

                //Cargamos el previo de la imagen
                pbPrevio.BackgroundImage = bmp;
                pbPrevio.BackgroundImageLayout = ImageLayout.Stretch;
            }
        }

        public Bitmap CropImage(Bitmap origen, Rectangle seccion)
        {
            //Un bitmap vacío para almacenar la imagen recortada
            Bitmap bmp = new Bitmap(seccion.Width, seccion.Height);
            Graphics g = Graphics.FromImage(bmp);

            //Dibuja el área dada (seccion) de la imagen original
            //en la posición (0,0) del bitmap vacío (bmp)
            g.DrawImage(origen, 0, 0, seccion, GraphicsUnit.Pixel);

            return bmp;
        }
    }
}



viernes, 16 de diciembre de 2011

Imprimir tickets

Un modo de generar un documento con un formato no muy complejo e imprimirlo es utilizando un formulario, añadiendole los controles deseados hasta obtener el aspecto requerido y después imprimirlo. Yo he puesto en práctica esta opción para la creación de tickets y facturas de venta.
A continuación, muestro como se realizaría esta tarea en el caso de los ticket. Para las facturas se hace de forma muy parecida, sólo hay que añadir los datos del cliente y cambiar un poco el formato.


Cuando se crea un objeto de la clase fTicket se le debe pasar una lista que incluye los artículos incluidos en la cuenta para la que se va a generar el ticket. La lista de la cuenta será una lista de objetos que contengan la información relevante de cada concepto a mostrar en el ticket.

Public Class fTicket
    Private _listaCuenta As ArrayList

    Public Sub New(ByRef cuenta As ArrayList)
        InitializeComponent()
        _listaCuenta = cuenta
    End Sub

Al mostrar el formulario se añadirán todos los controles (etiquetas, en este caso) necesariios para lograr el formato deseado. A continuación se muestra como se añadiría una línea por cada concepto en la cuenta, y posteriormente las lineas con los totales y el pie de página. La cabecera del ticket ya se ha creado en vista de diseño en el formulario ya que no cambia nunca.


    Private Sub fTicket_Shown(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Shown
        Dim itemCuenta As ItemCuenta
        Dim i As Integer = 0
        Dim yMax As Integer, yPie As Integer

        'Para cada elemento en la lista debemos crear una línea en
        'en el ticket (añadir las etiquetas correspondientes al formulario)
        For Each o As Object In _listaCuenta
            itemCuenta = CType(o, ItemCuenta)
            'Descartamos el primer elemento porque no contiene un artículo,
            'sólo la cantidad entregada por el cliente
            If itemCuenta.nombreArticulo <> "Entregado" Then

                Dim lblDescr As Label = New Label
                lblDescr.Location = New Point(6, 134 + (i * 15))
                lblDescr.Text = itemCuenta.nombreArticulo
                lblDescr.AutoSize = False
                lblDescr.Size = New System.Drawing.Size(117, 13)
                Dim lblCantidad As Label = New Label
                lblCantidad.Location = New Point(135, 134 + (i * 15))
                lblCantidad.Text = itemCuenta.cantidadArticulo.ToString()
                lblCantidad.AutoSize = False
                lblCantidad.Size = New System.Drawing.Size(19, 13)
                lblCantidad.TextAlign = System.Drawing.ContentAlignment.MiddleRight
                Dim lblPVP As Label = New Label
                lblPVP.Location = New Point(160, 134 + (i * 15))
                lblPVP.Text = itemCuenta.precioArticulo.ToString("0.00")
                lblPVP.AutoSize = False
                lblPVP.Size = New System.Drawing.Size(45, 13)
                lblPVP.TextAlign = System.Drawing.ContentAlignment.MiddleRight
                Dim lblImporte As Label = New Label
                lblImporte.Location = New Point(211, 134 + (i * 15))
                lblImporte.Text = itemCuenta.importeArticulo.ToString("0.00")
                lblImporte.AutoSize = False
                lblImporte.Size = New System.Drawing.Size(59, 13)
                lblImporte.TextAlign = System.Drawing.ContentAlignment.MiddleRight

                Me.Controls.Add(lblDescr)
                Me.Controls.Add(lblCantidad)
                Me.Controls.Add(lblPVP)
                Me.Controls.Add(lblImporte)
                i += 1
            End If

        Next

        'Obtenemos la posición de la última línea de la cuenta para desplazar
        yMax = 134 + ((i - 1) * 15)
        yPie = yMax + 30

        'Base Imponible
        Dim lblBaseImp As Label = New Label()
        lblBaseImp.AutoSize = True
        lblBaseImp.Location = New System.Drawing.Point(117, yPie)
        lblBaseImp.Size = New System.Drawing.Size(42, 13)
        lblBaseImp.Text = "BASE IMP."
        Me.Controls.Add(lblBaseImp)

        'Obtenemos el importe total que se corresponde con el IVA
        '
        Dim lblBaseImp2 As Label = New Label()
        lblBaseImp2.AutoSize = False
        lblBaseImp2.Location = New System.Drawing.Point(117, yPie + 17)
        lblBaseImp2.Size = New System.Drawing.Size(56, 13)
        lblBaseImp2.TextAlign = System.Drawing.ContentAlignment.MiddleRight
        lblBaseImp2.Text = Utilidades.CalcularImporteTotalSinIVA(_listaCuenta).ToString("0.00")

        'Importe IVA
        Dim lblImporteIVA As Label = New Label()
        lblImporteIVA.AutoSize = True
        lblImporteIVA.Location = New System.Drawing.Point(195, yPie)
        lblImporteIVA.Size = New System.Drawing.Size(36, 13)
        lblImporteIVA.Text = "IMPORTE IVA"
        Me.Controls.Add(lblImporteIVA)

        'Obtenemos la suma del importe total de los artículos de la cuenta sin IVA
        '
        Dim lblImporteIVA2 As Label = New Label()
        lblImporteIVA2.AutoSize = False
        lblImporteIVA2.Location = New System.Drawing.Point(198, yPie + 17)
        lblImporteIVA2.Size = New System.Drawing.Size(73, 13)
        lblImporteIVA2.TextAlign = System.Drawing.ContentAlignment.MiddleRight
        lblImporteIVA2.Text = Utilidades.CalcularImporteIVA(_listaCuenta).ToString("0.00")

        '
        Me.Controls.Add(lblBaseImp2)
        Me.Controls.Add(lblImporteIVA2)

        '
        Dim lblTotalPagar As Label = New Label()
        lblTotalPagar.AutoSize = True
        lblTotalPagar.Location = New System.Drawing.Point(98, yPie + 50)
        lblTotalPagar.Size = New Size(95, 13)
        lblTotalPagar.Text = "TOTAL A PAGAR:"

        '
        Dim lblTotalPagar2 As Label = New Label()
        lblTotalPagar2.AutoSize = False
        lblTotalPagar2.Location = New System.Drawing.Point(201, yPie + 50)
        lblTotalPagar2.TextAlign = System.Drawing.ContentAlignment.MiddleRight
        lblTotalPagar2.Size = New Size(70, 13)
        lblTotalPagar2.Text = Utilidades.CalcularImporteTotalCuenta(_listaCuenta).ToString("0.00")

        '
        Me.Controls.Add(lblTotalPagar)
        Me.Controls.Add(lblTotalPagar2)

        '
        Dim lblEntregado As Label = New Label()
        lblEntregado.AutoSize = True
        lblEntregado.Location = New System.Drawing.Point(98, yPie + 72)
        lblEntregado.Size = New Size(95, 13)
        lblEntregado.Text = "ENTREGADO:"

        '
        Dim lblEntregado2 As Label = New Label()
        lblEntregado2.AutoSize = False
        lblEntregado2.Location = New System.Drawing.Point(201, yPie + 72)
        lblEntregado2.TextAlign = System.Drawing.ContentAlignment.MiddleRight
        lblEntregado2.Size = New Size(70, 13)
        lblEntregado2.Text = CType(_listaCuenta(0), ItemCuenta).precioArticulo.ToString("0.00")

        '
        Me.Controls.Add(lblEntregado)
        Me.Controls.Add(lblEntregado2)

        '
        Dim lblCambio As Label = New Label()
        lblCambio.AutoSize = True
        lblCambio.Location = New System.Drawing.Point(98, yPie + 94)
        lblCambio.Text = "CAMBIO:"

        '
        Dim lblCambio2 As Label = New Label()
        lblCambio2.AutoSize = False
        lblCambio2.Location = New System.Drawing.Point(201, yPie + 94)
        lblCambio2.TextAlign = System.Drawing.ContentAlignment.MiddleRight
        lblCambio2.Size = New Size(70, 13)
        lblCambio2.Text = (CType(_listaCuenta(0), ItemCuenta).precioArticulo - Utilidades.CalcularImporteTotalCuenta(_listaCuenta)).ToString("0.00")

        '
        Me.Controls.Add(lblCambio)
        Me.Controls.Add(lblCambio2)

        'IVA Incluido
        Dim lblIVAIncluido As Label = New Label()
        lblIVAIncluido.Location = New System.Drawing.Point(193, yPie + 118)
        lblIVAIncluido.Text = "IVA INCLUIDO"

        '
        Me.Controls.Add(lblImporteIVA)
        Me.Controls.Add(lblImporteIVA2)

        'Gracias por su visita
        Dim lblGraciasVisita As Label = New Label()
        lblGraciasVisita.AutoSize = True
        lblGraciasVisita.Location = New System.Drawing.Point(13, yPie + 145)
        lblGraciasVisita.TextAlign = System.Drawing.ContentAlignment.MiddleRight
        'lblGraciasVisita.Size = New Size(78, 13)
        lblGraciasVisita.Text = "GRACIAS POR SU VISITA"

        '
        Me.Controls.Add(lblIVAIncluido)
        Me.Controls.Add(lblGraciasVisita)

        If yPie + 170 > Me.Height Then
            Me.MinimumSize = New Size(Me.Width, yPie + 190)
        End If

        PrintDocument1.Print()
    End Sub


En el código que se muestra a continuación se puede ver como se realiza la impresión del ticket. La idea consiste en dibujar el texto contenido en las etiquetas en el evento e en la misma posición en la que se situaron dichas etiquetas dentro del formulario.
Para llevar a cabo este proceso se recorren todos los controles del formulario y se descartan todos aquellos que no sean del tipo que queremos imprimir. Los que sean del tipo que deseamos imprimir se dibujarán en el objeto e en una posición acorde a la que tienen en el formulario.


    Private Sub PrintDocument1_PrintPage(ByVal sender As System.Object, ByVal e  As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage

        DibujarTicket(e.Graphics())
    End Sub





    Private Sub DibujarTicket(ByVal g As Graphics)
        ' Create the source rectangle from the BackgroundImage Bitmap Dimensions
        Dim srcRect As Rectangle = New Rectangle(0, 0, Me.Width, Me.Height)
        Dim nWidth As Integer = Me.Width       
        Dim nHeight As Integer = Me.Height
        Dim destRect As Rectangle = New Rectangle(0, 0, nWidth, CInt(Math.Ceiling(nHeight / 2)))
        Dim lbl As Label

        Dim scalex As Double = 1 * CSng(destRect.Width / srcRect.Width)
        Dim scaley As Double = 2 * CSng(destRect.Height / srcRect.Height)
        Dim aPen As New Pen(Brushes.Black, 1)
        ' Recorre todos los controles. Determina si es un label y dibuja el valor de la propiedad text
        ' en la posición correcta (en función de la propiedad location del control label en cuestión)

        For Each o As Object In Me.Controls
            If o.GetType() = Label1.GetType() Then
                lbl = CType(o, Label)
                If lbl.TextAlign = ContentAlignment.MiddleRight Then
                    g.DrawString(lbl.Text, lbl.Font, Brushes.Black, CSng((lbl.Bounds.Right - 6 * lbl.Text.Length) * scalex),
                                 CSng(lbl.Bounds.Top * scaley), New StringFormat())
                Else
                    g.DrawString(lbl.Text, lbl.Font, Brushes.Black, CSng((lbl.Bounds.Left) * scalex),
                                 CSng(lbl.Bounds.Top * scaley), New StringFormat())

                End If

            End If
        Next

        Me.Close()
    End Sub

Autentificación de usuarios

Últimamente, he estado trabajando en un pequeño proyecto y por eso no he añadido contenido al blog. Sin embargo, ahora que he terminado, he decidido crear algunas entradas aquí con cosas que creo que se utilizan muy a menudo en los proyectos de programación y que espero que sean útiles.
En esta entrada voy a mostrar el funcionamiento de un formulario de autentificación que yo he utilizado.


Public Class fAutentificacion

    Private Sub btnEntrar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEntrar.Click
        If txtUsuario.Text = "" And txtContrasena.Text = "" Then
            MessageBox.Show("Introduzca un usuario y una contraseña", "Autentificación", MessageBoxButtons.OK, MessageBoxIcon.Information)
        Else
            If txtUsuario.Text = "" Or Validacion.Usuario(txtUsuario.Text) = False Then
                MessageBox.Show("Introduzca un nombre de usuario con el formato correcto", "Autentificación", MessageBoxButtons.OK, MessageBoxIcon.Information)
            Else
                'Comprobamos si las credenciales son correctas
                If BD.CredencialesCorrectas(txtUsuario.Text, BD.PasswordHash(txtContrasena.Text)) Then
                    'Autentificación correcta
                    Dim fGestion As fAdmin
                    'Ocultamos el formulario del TPV
                    Me.Owner.Visible = False
                    'Ocultamos el formulario de autentificación
                    Me.Hide()
                    'Abrimos el formulario al que queremos acceder
                    fGestion = New fAdmin()
                    fGestion.ShowDialog(Me.Owner)
                Else
                    'Autentificación incorrecta
                    MessageBox.Show("Autentificación incorrecta", "Autentificación", MessageBoxButtons.OKCancel, MessageBoxIcon.Error)
                End If
            End If

        End If
    End Sub


    Private Sub fAutentificacion_FormClosing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
        Me.Owner.Visible = True
    End Sub

    Private Sub fAutentificacion_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        txtUsuario.Select()
    End Sub
End Class

En el código anterior se utilizan algunos métodos que yo he creado especificamente y que muestro a continuación para aclarar totalmente el funcionamiento del código.


    'Validación nombre usuario
    Public Shared Function Usuario(ByVal cadena As String) As Boolean

        If Regex.IsMatch(cadena, "^[a-zA-ZñÑ_][a-zA-ZñÑ0-9._]+$") Then
            Return True
        Else
            Return False
        End If
    End Function

El código anterior se ocupa de comprobar que el formato del nombre de usuario introducido es correcto. En este caso, sólo se admiten caracteres alfabéticos (mayúsculas y minúsculas y ñ y Ñ), numéricos y los símbolos de puntuación '.' y '_'. Además, el carácter '.' no podrá utilizarse como carácter inicial.
Para realizar la validación se utiliza una expresión regular.

    'Autentificación de las credenciales aportadas contra la base de datos
    Public Shared Function CredencialesCorrectas(ByRef usuario As String, ByRef contrasena As String) As Boolean
        Dim cadenaConexion As String
        Dim conexion As OleDbConnection
        Dim comando As OleDbCommand
        Dim resultado As Integer
        Dim consulta As String

        cadenaConexion = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & Environment.CurrentDirectory & "\ficheroBD.accdb;Persist Security Info=False;"
        conexion = New OleDbConnection(cadenaConexion)
        consulta = "SELECT COUNT(*) FROM PERSONAL WHERE StrComp(Nombre,'" & usuario & "',0)=0 AND Contrasena='" & contrasena & "' AND Acceso='Administrador'"

        conexion.Open()
        comando = New OleDbCommand(consulta, conexion)
        resultado = CType(comando.ExecuteScalar(), Integer)

        conexion.Close()

        If resultado > 0 Then
            Return True
        Else
            Return False
        End If

    End Function

El código arriba mostrado realiza la comparación entre los datos aportados por el usuario y los guardados en la base de datos para el nombre de usuario indicado.


    'Obtención de la función hash SHA1 de la contraseña (para no mantener
    'las contraseñas almacenadas en texto plano)
    Public Shared Function PasswordHash(ByVal password As String) As String
        Dim bytes() As Byte = Encoding.Unicode.GetBytes(password)
        Dim inArray() As Byte = HashAlgorithm.Create("SHA1").ComputeHash(bytes)

        Return Convert.ToBase64String(inArray)
    End Function