Blog alskare

.Net y lo que surja

Archivo de 19 septiembre 2009

C# Personalizar la ordenación de listas con IComparable

Publicado por alskare en 19/09/2009

Hace poco, designios extraños de la vida, me ha tocado hacer una ordenación de una serie de objetos string tal como se hacía en la antigüedad. Cuando hablo de la antigüedad, tan sólo dejo correr un poco la memoria hasta los tiempos en que vestía con un jersey de cuello alto y unos pantalones cortos. Entonces, al pasar por los pasillos de las aulas, no era nada extraño escuchar una especie de canto gregoriano (pero a lo pobre) en el que una panda de alumnos que pensaban más en el pájaro del árbol de enfrente que en lo que estaban haciendo, solían recitar: a, b, c, ch (che), d, e , f, g, h, i, j, k, l, m, n, ñ, o, p, q, r, rr (erre doble), s, t, u, v, w, x, y, y zeta.

Bueno, el caso es que esta vez no me ha tocado hacer nada sobre la rr, pero sí que he tenido que hacer un sistema de ordenación en el que quedase contemplado la letra che. Por cierto, dicen que la curiosidad mata al gato y, como soy muy cotilla, se me ha ocurrido buscar la letra en la R.A.E y, ¡sorpresa!, ¡Todavía existe! (http://buscon.rae.es/draeI/SrvltConsulta?TIPO_BUS=3&LEMA=che).

Siempre me voy por las ramas, así que… al lío:

Vamos a crear un ejemplo con la socorrida clase Persona y, como haremos uso de una colección List<Persona>, le implementamos la interfaz IComparable. Aunque ya sabemos que el resultado no será el que andamos buscando, nos dará una idea de cómo nos realizaría la ordenación cualquier colección que nos permita realizar una ordenación de los objetos Persona.

using System;
using System.Collections.Generic;

namespace OrdenacionStrings
{
    class Ordenando
    {
        static void Main(string[] args)
        {
            List<Persona> gente = new List<Persona>();
            gente.Add(new Persona("Jaime", "Cebado Sánchez"));
            gente.Add(new Persona("Victoria", "Chacón Gómez"));
            gente.Add(new Persona("Anibal", "Chacón Gómez"));
            gente.Add(new Persona("Andrés", "Cullera Ramón"));
            gente.Add(new Persona("Juan Manuel", "Damián Rodriguez"));

            gente.Sort();

            foreach (Persona p in gente)
            {
                Console.WriteLine(p.Apellidos + ", " + p.Nombre);
            }
            Console.ReadKey();
        }
    }


    public class Persona : IComparable
    {
        public string Nombre { get; set; }
        public string Apellidos { get; set; }

        public Persona(string nombre, string apellidos)
        {
            Nombre = nombre;
            Apellidos = apellidos;
        }

        public int CompareTo(object obj)
        {
            try
            {
                Persona p = (Persona)obj;
                return (this.Apellidos + " " + this.Nombre).CompareTo(p.Apellidos + " " + p.Nombre);
            }
            catch { return 0; }
        }
    }

}

En el momento en que ejecutemos el código, veremos que la ordenación que hace es la actual, es decir, tendremos un resultado en el cual, los “Chacón” estarán antes de “Cullera”, cuando en realidad deberían salir después, puesto que contienen la letra (o dígrafo) che.

Cebado Sánchez, Jaime
Chacón Gómez, Anibal
Chacón Gómez, Victoria
Cullera Ramón, Andrés
Damián Rodriguez, Juan Manuel

Una de las ventajas de la Interfaz IComparable es que podemos implementar el método CompareTo como nos plazca (gracias, OO) y, con una serie de pequeños cambios en la clase Persona, podemos realizar un control de tal manera que, analizando las dos primeras letras de la cadena, podemos indicarle si la ch irá antes o después de la c.

Así, con una implementación distinta de la anterior del métodos CompareTo, el resultado será el deseado desde el principio:

public int CompareTo(object obj)
 {
     try
     {
         Persona p = (Persona)obj;
         if (!this.Apellidos.ToLower().StartsWith("ch"))
         {
             return (this.Apellidos + " " + this.Nombre).CompareTo(p.Apellidos + " " + p.Nombre);
         }
         else
         {
             if (!p.Apellidos.ToLower().StartsWith("ch"))
             {
                 return (string.Compare((p.Apellidos + " " + p.Nombre).ToLower(), "d") * -1);

                 /* la línea anterior intenta simplificar el conjunto de operaciones siguientes.
                  * Sólo se trata de realizar una inversión en la comparación de la ch y la d
                  */ 

                 //if (string.Compare((p.Apellidos + " " + p.Nombre).ToLower(), "d") == 1)
                 //    return -1;
                 //else if (string.Compare((p.Apellidos + " " + p.Nombre).ToLower(), "d") == -1)
                 //    return 1;
                 //else return 0;
             }
             else
             {
                 return (this.Apellidos + " " + this.Nombre).CompareTo(p.Apellidos + " " + p.Nombre);
             }
         }
     }
     catch { return 0; }
 }
    }

Resultado obtenido:

Cebado Sánchez, Jaime
Cullera Ramón, Andrés
Chacón Gómez, Anibal
Chacón Gómez, Victoria
Damián Rodriguez, Juan Manuel

 

Dejo aquí el código de la clase al completo para no tener que ir buscando entre los “cachitos” de código del post.

using System;
using System.Collections.Generic;

namespace OrdenacionCorrecta
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Persona> gente = new List<Persona>();
            gente.Add(new Persona("Jaime", "Cebado Sánchez"));
            gente.Add(new Persona("Victoria", "Chacón Gómez"));
            gente.Add(new Persona("Anibal", "Chacón Gómez"));
            gente.Add(new Persona("Andrés", "Cullera Ramón"));
            gente.Add(new Persona("Juan Manuel", "Damián Rodriguez"));

            gente.Sort();

            foreach (Persona p in gente)
            {
                Console.WriteLine(p.Apellidos + ", " + p.Nombre);
            }
            Console.ReadKey();
        }
    }


    public class Persona : IComparable
    {
        public string Nombre { get; set; }
        public string Apellidos { get; set; }

        public Persona(string nombre, string apellidos)
        {
            Nombre = nombre;
            Apellidos = apellidos;
        }


        public int CompareTo(object obj)
        {
            try
            {
                Persona p = (Persona)obj;
                if (!this.Apellidos.ToLower().StartsWith("ch"))
                {
                    return (this.Apellidos + " " + this.Nombre).CompareTo(p.Apellidos + " " + p.Nombre);
                }
                else
                {
                    if (!p.Apellidos.ToLower().StartsWith("ch"))
                    {
                        return (string.Compare((p.Apellidos + " " + p.Nombre).ToLower(), "d") * -1);
                    }
                    else
                    {
                        return (this.Apellidos + " " + this.Nombre).CompareTo(p.Apellidos + " " + p.Nombre);
                    }
                }
            }
            catch { return 0; }
        }
    }
    
}

Publicado en C#, NetFramework | 2 Comentarios »

C# Mantener la configuración de tamaño y posición de los formularios

Publicado por alskare en 15/09/2009

No sería la primera vez que los clientes piden que, en los formularios WinForms, se guarde tanto la posición como el tamaño del formulario, incluso después de cerrar la aplicación. Bajo esta premisa, me inicio en un nuevo post que tiene como finalidad, precisamente esto, almacenar la información de tamaño del formulario y recuperarla cada vez que se abra de nuevo el mismo formulario.

Framework.Net nos ofrece una serie de posibilidades amplias para poder guardar información en el área de trabajo del usuario. Una de estas opciones es el namespace System.XML, el cual nos permite poder trabajar con ficheros XML, tanto a nivel de lectura como de escritura. Aprovechando estas características, se me ocurre la creación de un fichero en el que poder almacenar los datos de tamaño y posición del formulario. Ahora bien, el problema de una aplicación es la cantidad de formularios existentes, así que empiezo a pensar en un fichero con una estructura similar a la siguiente:

<?xml version="1.0" encoding="utf-8"?>
<Configuracion>
    <NombreForm1>
        <WindowState>ValorWindowStateForm1</WindowState>
        <Top>ValorTopForm1</Top>
        <Left>ValorTopForm1</Left>
        <Width>ValorTopForm1</Width>
        <Height>ValorTopForm1</Height>
    </NombreForm1>
    <NombreForm2>
        <WindowState>ValorWindowStateForm2</WindowState>
        <Top>ValorTopForm2</Top>
        <Left>ValorTopForm2</Left>
        <Width>ValorTopForm2</Width>
        <Height>ValorTopForm2</Height>
    </NombreForm2>
</Configuracion>

De esta manera, si los datos de cada formulario representa un nodo dentro del archivo XML, puede resultar más o menos sencilla la adaptación de la librería a cualquier otro valor.

Una vez está decidido cuál será el medio de almacenamiento, cabe comenzar a pensar en cómo se realizarán las operaciones de lectura y escritura sobre el fichero XML, así que, recordando las clases de Programación Orientada a Objeto, se me ocurre la idea de crear un formulario base del que heredarán todos aquellos formularios que necesiten almacenar dicha información.

Manos a la obra. Comenzamos creando un nuevo proyecto que será una Biblioteca de controles de WindowsForms, que contendrá un formulario denominado FormularioBase. Éste será un formulario estándar en el que primarán dos cosas:

  1. En el evento FormClosing se almacenarán los datos que interesen en el fichero de configuración
  2. En el evento Load se recuperarán, en caso de existir previamente, los datos de configuración y se aplicarán al formulario

Así, una vez creado el campo que contendrá el fichero de configuración,

private string ficheroXML = Path.Combine(Application.StartupPath, "ConfForm.xml");

nos ponemos a implementar el proceso de almacenamiento de los datos:

/// <summary>
 /// Almacena la información del formulario actual en fichero XML
 /// </summary>
 private void GuardaConfiguracionForm()
 {
     string nombreForm = this.Name;
     XmlNode nodoPrincipal;

     try
     {
         // Intancia del documento XML que permitirá guardar la configuración
         XmlDocument doc = new XmlDocument();

         // Se comprueba que exista el fichero
         if (!File.Exists(ficheroXML))
         {
             // No existe el fichero. Se añaden los nodos iniciales
             doc.AppendChild(doc.CreateComment("Configuración de formularios - älskare, Sep/09"));

             // Creación del nodo principal
             nodoPrincipal = doc.CreateNode(XmlNodeType.Element, "Configuracion", null);

             // Se añade el nodo principal al documento
             doc.AppendChild(nodoPrincipal);
         }
         else
         {
             // El fichero existe previamente. Se recupera la información
             doc.Load(ficheroXML);

             // Lectura del nodo principal
             nodoPrincipal = doc.SelectSingleNode("Configuracion");
         }

         // Se comprueba si existe un nodo con el nombre del form actual
         XmlNode nodoFormActual = nodoPrincipal.SelectSingleNode(nombreForm);
         if (nodoFormActual == null)
             nodoFormActual =
                   nodoPrincipal.AppendChild(
                     doc.CreateNode(XmlNodeType.Element, nombreForm, null));

         // Almacenamiento de los valores
         NodoValor(doc, nodoFormActual, "WindowState", this.WindowState);
         NodoValor(doc, nodoFormActual, "Top", this.Top);
         NodoValor(doc, nodoFormActual, "Left", this.Left);
         NodoValor(doc, nodoFormActual, "Width", this.Width);
         NodoValor(doc, nodoFormActual, "Height", this.Height);


         // Se guarda el fichero de configuración en disco
         XmlTextWriter tw = new XmlTextWriter(ficheroXML, Encoding.UTF8);
         tw.Indentation = 4;
         tw.IndentChar = " "[0];
         tw.Formatting = Formatting.Indented;

         doc.Save(tw);
         doc = null;

         tw.Flush();
         tw.Close();
     }
     catch { }
 }

 /// <summary>
 /// Crea un nuevo par clave-valor en el nodo actual en caso de que no exista
 /// <returns></returns>
 XmlNode NodoValor(XmlDocument doc, XmlNode nodoPadre, string Clave, Object Valor)
 {
     XmlNode nodoActual = nodoPadre.SelectSingleNode(Clave);
     if (nodoActual == null)
         nodoActual = nodoPadre.AppendChild(doc.CreateNode(XmlNodeType.Element, Clave, null));
     nodoActual.InnerText = Valor.ToString();
     return nodoActual;
 }

 

Una vez realizado el proceso de escritura del fichero, falta poder recuperarlo para aplicar los cambios en el formulario cuando se abra.

/// <summary>
/// Realiza una lectura del fichero de configuración y aplica los cambios al form
/// </summary>
private void LecturaConfiguracionForm()
{
    // No se controlan errores. En caso de existir cualquier error
    // no se modifica nada en el formulario actual
    try
    {
        string nombreForm = this.Name;
        XmlDocument doc = new XmlDocument();
        doc.Load(ficheroXML);

        // Lectura del nodo principal
        XmlNode nodoPrincipal = doc.SelectSingleNode("Configuracion");

        // Lectura del nodo del formulario actual
        XmlNode nodoActual = nodoPrincipal.SelectSingleNode(nombreForm);

        // Lectura del modo de presentación de la ventana
        this.WindowState = (FormWindowState)Enum.Parse(typeof(FormWindowState), GetValor(nodoActual, "WindowState").ToString(), false);

        if (this.WindowState == FormWindowState.Normal)
        {
            this.Top = Convert.ToInt32(GetValor(nodoActual, "Top"));
            this.Left = Convert.ToInt32(GetValor(nodoActual, "Left"));
            this.Width = Convert.ToInt32(GetValor(nodoActual, "Width"));
            this.Height = Convert.ToInt32(GetValor(nodoActual, "Height"));
        }
    }
    catch { }
}

/// <summary>
/// Obtiene el valor de la clave
/// </summary>
private object GetValor(XmlNode nodoPadre, string clave)
{
    XmlNode valor = nodoPadre.SelectSingleNode(clave);
    return valor.LastChild.Value;
}

Implementadas tanto la escritura como la lectura de los valores, quedará aplicar dichos cambios al formulario actual. En esta parte, debo reconocer que no he conseguido eliminar un efecto un tanto raro que se da al abrir el formulario, sobre todo, si el formulario a mostrar está contenido en otro en modo MDI. Seguiremos trabajando sobre el tema para ver si consigo eliminar dicho efecto antiestético.

public FormularioBase()
{
    this.SuspendLayout();
    InitializeComponent();
    this.ResumeLayout();

    this.Load += new EventHandler(FormularioBase_Load);
    this.FormClosing += new FormClosingEventHandler(FormularioBase_FormClosing);
}

void FormularioBase_Load(object sender, EventArgs e)
{
    LecturaConfiguracionForm();
}
void FormularioBase_FormClosing(object sender, FormClosingEventArgs e)
{
    GuardaConfiguracionForm();
}

Bueno, pues ya está. A partir de ahora, cada vez que cree un nuevo WinForms, tan sólo añadiré una referencia a la librería en la que tengo este formulario base y, por supuesto, haré que cada formulario herede de FormularioBase.

public partial class Form2 : jnSoftware.Utiles.FormularioBase 
    {
        public Form2()
        {
            InitializeComponent();
        }
    }

 

Si le interesa a alguien, dejo enlace en el que se puede descargar un ejemplo de uso: descargar.

Publicado en C#, Controles, WinForms | 2 Comentarios »

Personalizar el HTMLEditor de AjaxControlToolkit

Publicado por alskare en 10/09/2009

Primeros pasos

Una de las novedades que nos ha traído el Ajax Control Toolkit (May 2009 Release) ha sido un editor HTML para nuestras páginas la mar de práctico. Hasta el momento, cada vez que he tenido que hacer uso de algún control similar con la finalidad de que el usuario pueda escribir negritas o subrayados, siempre he tenido que echar mano de algún componente externo. Debo reconocer que alguno de estos controles han sido realmente buenos y han cumplido a la perfección el objetivo. No obstante, al hacer uso de componentes de varios desarrolladores, llega un momento que el tamaño de la aplicación crece y, sobre todo, cualquier actualización se eterniza. Todo esto, sin contar con que cada control suele incluir sistemas propios de configuración (no será la primera vez que pierdo horas buscando dónde se indica un path para, por ejemplo, añadir una colección de botones propia).

Al ver por vez primera el HTMLEditor debo reconocer que me entusiasmé, puesto que veía la gran utilidad que tenía tener englobado el control con el resto de extensores que suelo usar a menudo. Propiedades y métodos más o menos conocidos, personalización de opciones…todo con una misma “filosofía”!!!!

Este entusiasmo inicial comenzó a decaer nada más empezar a utilizarlo (igual es que nos contagiamos de los clientes y cada día nos volvemos más exigentes). Y, por supuesto, nada más empezar a utilizarlo significa hacer uso de una estructura mínima para poder hacer uso del mismo

Estructura básica para hacer uso del HTMLEditor

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit.HTMLEditor"
    TagPrefix="AjaxCT" %>
    
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>HTML Editor - Pruebas iniciales</title>
    <style type="text/css" media="all">
        * {
            font-family: Arial, Helvetica, sans-serif;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="sm" runat="server" />
    <div>
        <h1>HTML Editor</h1>
        <AjaxCT:Editor ID="MiEditor" runat="server"  />
    </div>
    </form>
</body>
</html>

Con este código conseguimos añadir un control HTMLEditor en una de nuestras páginas. Apreciamos que no se ha cambiado un ápice de la “estructura original”, es decir, que el resultado que obtendremos también será el de una estructura básica:

image

Primeros pasos y… Primeros problemas

Una vez pasada la euforia inicial, cuando empezamos a aplicar el control en algún tipo de aplicación, comenzamos a apreciar que, en ocasiones, un control tan “extenso” puede llegar a ser demasiado para situaciones concretas. Por ejemplo, en ocasiones he contado con un espacio ínfimo para crear un control de este estilo y, en estos casos, hay que empezar a apurar botones (otra situación podría ser la de eliminar el tamaño de letra para evitar que los usuarios creen mensajes en los foros al estilo HOIGAN).

Así, de buenas a primeras, me marco dos objetivos iniciales:

  1. Aprender a personalizar el número de botones que aparecerán en las barras de herramientas del control
  2. Aprender a mostrar/ocultar estas barras de herramientas en tiempo de diseño/ejecución.

Primeras soluciones

Mirando un poco el explorador de objetos, veo que la clase AjaxControlToolkit.HTMLEditor.Editor contiene un par de métodos que me llaman la atención:

  • FillTopToolbar
  • FillBottonToolbar.

Así, en el ejemplo siguiente vamos a crear un editor que incluya, exclusivamente, los botones de formato estándar Negrita, Cursiva y Subrayado. Por tanto, empezaremos creando una nueva clase en la carpeta App_Code, la cual, heredará directamente del editor original.

using AjaxControlToolkit.HTMLEditor;

namespace jnSoftware.WebControls
{
    public class jnHTMLEditor : Editor
    {

        /// <summary>
        /// Método en el que se colocarán los elementos de la barra superior del control
        /// </summary>
        protected override void FillTopToolbar()
        {
            // Botón de negrita
            this.TopToolbar.Buttons.Add(
                new AjaxControlToolkit.HTMLEditor.ToolbarButton.Bold());
            // Botón de cursiva
            this.TopToolbar.Buttons.Add(
                new AjaxControlToolkit.HTMLEditor.ToolbarButton.Italic());
            // Botón de subrayado
            this.TopToolbar.Buttons.Add(
                new AjaxControlToolkit.HTMLEditor.ToolbarButton.Underline());
        }


        /// <summary>
        /// Método en el que se colocarán los elementos de la barra inferior del control
        /// </summary>
        protected override void FillBottomToolbar()
        {
            // Vista Diseño
            this.BottomToolbar.Buttons.Add( 
                new AjaxControlToolkit.HTMLEditor.ToolbarButton.DesignMode());
            // Vista Previa
            this.BottomToolbar.Buttons.Add(
                new AjaxControlToolkit.HTMLEditor.ToolbarButton.PreviewMode());

            // base.FillBottomToolbar();
        }
    }
}

A partir de aquí, ya vemos que podemos empezar a personalizar la barra de herramientas, tanto superior, como inferior. De hecho, gracias al IDE de VisualStudio es realmente sencillo y cómodo añadir el resto de botones deseados.

image

 

Agregando propiedades

En alguna que otra ocasión, he hecho uso de los editores de texto mostrando u ocultando las barras de herramientas, dependiendo del contexto. Así, como lo necesito para el editor, lo que hago es crear una propiedad que permita mostrar u ocultar las barras de herramientas:

using AjaxControlToolkit.HTMLEditor;

namespace jnSoftware.WebControls
{
    public class jnHTMLEditor : Editor
    {

        /// <summary>
        /// Muestra u oculta la barra de herramientas superior
        /// </summary>
        public bool ShowTopToolBar
        {
            get { return showTopToolBar; }
            set { showTopToolBar = value; }
        }
        private bool showTopToolBar = true ;


        /// <summary>
        /// Muestra u oculta la barra de botones inferior
        /// </summary>
        public bool ShowBottonToolbar
        {
            get { return showBottomToolbar; }
            set { showBottomToolbar = value; }
        }
        private bool showBottomToolbar = true;


        /// <summary>
        /// Método en el que se colocarán los elementos de la barra superior del control
        /// </summary>
        protected override void FillTopToolbar()
        {
            if (showTopToolBar)
            {
                // Botón de negrita
                this.TopToolbar.Buttons.Add(
                    new AjaxControlToolkit.HTMLEditor.ToolbarButton.Bold());
                // Botón de cursiva
                this.TopToolbar.Buttons.Add(
                    new AjaxControlToolkit.HTMLEditor.ToolbarButton.Italic());
                // Botón de subrayado
                this.TopToolbar.Buttons.Add(
                    new AjaxControlToolkit.HTMLEditor.ToolbarButton.Underline());
            }
        }

        /// <summary>
        /// Método en el que se colocarán los elementos de la barra inferior del control
        /// </summary>
        protected override void FillBottomToolbar()
        {
            if (ShowBottonToolbar)
            {
                // Vista Diseño
                this.BottomToolbar.Buttons.Add(
                    new AjaxControlToolkit.HTMLEditor.ToolbarButton.DesignMode());
                // Vista Previa
                this.BottomToolbar.Buttons.Add(
                    new AjaxControlToolkit.HTMLEditor.ToolbarButton.PreviewMode());
            }
        }
    }
}

Una vez creada la clase, bastará con hacer alguna pequeña modificación en el fichero .ASPX para que se muestre el control creado:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Namespace="jnSoftware.WebControls" TagPrefix="jnControles" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>HTML Editor - Pruebas iniciales</title>
    <style type="text/css" media="all">
        * {
            font-family: Arial, Helvetica, sans-serif;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="sm" runat="server" />
    <div>
        <h1>HTML Editor</h1>
        <jnControles:jnHTMLEditor ID="MiEditor" runat="server"  />
    </div> 
    </form>
</body>
</html>

 

image

Publicado en ASP.Net, C#, Controles | 4 Comentarios »

Validar Número de IVA VIES

Publicado por alskare en 07/09/2009

El otro día, hablando con la contable de la empresa, me comentó la posibilidad de añadir en las aplicaciones la validación de unos “NIF” un poco especiales. Digo un poco especiales porque en principio no se trata de un número de identificación sino más bien es un registro de aquellas empresas “legalizadas” para poder trabajar con IVAs a nivel intracomunitario. Digo legalizada entre comillas puesto que desconozco totalmente si el medio para entrar en esa base de datos se realiza automáticamente al trabajar con empresas intracomunitarias o hace falta algún tipo de registro especial.

Bueno, el caso es que mala validación podemos crear si el origen es una base de datos, así que no me queda más remedio que empezar a buscar y encuentro lo que andaba buscando en lo que creo que es la AEAT Europea: http://ec.europa.eu/taxation_customs/vies/lang.do?fromWhichPage=vieshome&selectedLanguage=ES.

Dentro de los servicios informáticos con los que he trabajado, quizás éste sea uno de los más originales. De hecho, no existe una base de datos centralizada. Se trata de bases de datos con un ámbito del país local que va “replicando” con el resto de países (igual me he pasado al escribir “replicando”). Por otro lado, resulta que no todos los países atienden las 24 horas del día (igual los ordenadores se van a tomar un café): http://ec.europa.eu/taxation_customs/vies/viesspec.do

Al fin y al cabo, somos programadores y nuestros profes siempre nos han enseñado que debemos desglosar un problema gordo en problemas más pequeños para poder afrontarlos con mayor destreza, así que eso voy a hacer en estos momentos. Imagino que al final pasará como ha ocurrido con otra entrada en el blog (Validar NIF, NIE, CIF ), que empezó con una pequeña validación y se ha ido ampliando y mejorando con el tiempo. Así, el objetivo inicial será el de comprobar si el NIF introducido es correcto o no, siempre según el servicio de Fiscalidad y Unión Aduanera.

La primera me la doy en la frente. Resulta que para solicitar la validez de un Número de IVA, necesitamos el código del país y… ¿cómo no?, no encuentro ningún servicio que devuelva los países aceptados, así que hay que hacer una lectura del código HTML de la página para poder tenerlos todos. Para poder trabajar con los países y poder ponerlos como origen de datos de los controles se me ocurre hacer lo más sencillo, una clase List<T>.

Paises

using System;
using System.Collections.Generic;

namespace ValidacionVies
{
    class Pais : IComparable 
    {
        public string CodigoPais { get; set; }
        public string NombrePais { get; set; }

        public Pais(string codigoPais, string pais)
        {
            this.CodigoPais = codigoPais;
            this.NombrePais  = pais;
        }


        // Se implementa la interfaz IComparable para poder ordenar por el país 
        public int CompareTo(object obj)
        {
            return this.NombrePais.CompareTo(((Pais)obj).NombrePais);
        }
    }


    class Paises : List<Pais>
    {
        public Paises()
        {
            this.Add(new Pais("AT", "Austria"));
            this.Add(new Pais("BE", "Bélgica"));
            this.Add(new Pais("BG", "Bulgaria"));
            this.Add(new Pais("CY", "Chipre"));
            this.Add(new Pais("CZ", "Chequia"));
            this.Add(new Pais("DE", "Alemania"));
            this.Add(new Pais("DK", "Dinamarca"));
            this.Add(new Pais("EE", "Estonia"));
            this.Add(new Pais("EL", "Grecia"));
            this.Add(new Pais("ES", "España"));
            this.Add(new Pais("FI", "Finlandia"));
            this.Add(new Pais("FR", "Francia"));
            this.Add(new Pais("GB", "Reino Unido"));
            this.Add(new Pais("HU", "Hungría"));
            this.Add(new Pais("IE", "Irlanda"));
            this.Add(new Pais("IT", "Italia"));
            this.Add(new Pais("LT", "Lituania"));
            this.Add(new Pais("LU", "Luxemburgo"));
            this.Add(new Pais("LV", "Letonia"));
            this.Add(new Pais("MT", "Malta"));
            this.Add(new Pais("NL", "Países Bajos"));
            this.Add(new Pais("PL", "Polonia"));
            this.Add(new Pais("PT", "Portugal"));
            this.Add(new Pais("RO", "Rumania"));
            this.Add(new Pais("SE", "Suecia"));
            this.Add(new Pais("SI", "Eslovenia"));
            this.Add(new Pais("SK", "Eslovaquia"));

            this.Sort();
        }
    }

}

 

El problema lo encontraré el día que haya que hacer alguna modificación, así que tomo nota de la mejora que podría hacerse creando un XML o un simple TXT para poder almacenar los países.

Una vez tenemos los países, conociendo los resultados que nos devuelve el WebService, es cuestión de empezar a preparar un entorno de interacción con el usuario:

- <element name="checkVatResponse">
- <complexType>
- <sequence>
  <element name="countryCode" type="xsd:string" /> 
  <element name="vatNumber" type="xsd:string" /> 
  <element name="requestDate" type="xsd:date" /> 
  <element name="valid" type="xsd:boolean" /> 
  <element name="name" type="xsd:string" /> 
  <element name="address" type="xsd:string" /> 
  </sequence>
  </complexType>
  </element>

Como que me hace gracia jugar un poco con las llamadas asíncronas al Web Service, opto por crear una aplicación WinForms que me permita realizar las primeras pruebas, así, me sale algo así como lo siguiente (ya dice mi jefe que lo mío no es el diseño):

image

 

De hecho, en el formulario no hay nada raro, un combo en el que se cargan los países (cboPaises), un textBox en el que deberá teclearse el número a validar (txtVatNumber) y los textBox de resultado (rstXXXX).

Al lío:

Código del formulario

using System;
using System.Windows.Forms;
using ValidacionVies.eu.europa.ec;


namespace ValidacionVies
{
    public partial class Form1 : Form
    {
        checkVatService cvs;

        public Form1()
        {
            InitializeComponent();

            // Asignación de eventos
            this.Load += new EventHandler(Form1_Load);
            this.cmdValida.Click += new EventHandler(cmdValida_Click);
        }

        void Form1_Load(object sender, EventArgs e)
        {
            lblProgreso.Text = string.Empty;

            // Carga de paises y asignación al combo
            Paises paises = new Paises();
            cboPaises.DataSource = paises;
            cboPaises.DisplayMember = "NombrePais";
            cboPaises.ValueMember = "CodigoPais";
            cboPaises.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
            cboPaises.AutoCompleteSource = AutoCompleteSource.ListItems;

            // Asignación del evento IndexChanged para el combo.
            cboPaises.SelectedIndexChanged += new EventHandler(cboPaises_SelectedIndexChanged);
            txtCountryCode.Text = cboPaises.SelectedValue.ToString();
        }


        void cboPaises_SelectedIndexChanged(object sender, EventArgs e)
        {
            ComboBox cbo = (ComboBox)sender;
            txtCountryCode.Text = cbo.SelectedValue.ToString();
        }


        void cmdValida_Click(object sender, EventArgs e)
        {
            lblProgreso.Text = "Consultando";

            // Llamada asíncrona al WebService
            cvs = new checkVatService();
            cvs.checkVatCompleted += new checkVatCompletedEventHandler(cvs_checkVatCompleted);
            cvs.checkVatAsync(txtCountryCode.Text, txtVatNumber.Text);
        }


        void cvs_checkVatCompleted(object sender, checkVatCompletedEventArgs e)
        {
            // Respuesta de la llamada asíncrona al WebService
            try
            {
                if (!e.Cancelled)
                {
                    lblProgreso.Text = string.Empty;
                    rstAddress.Text = e.address;
                    rstCountryCode.Text = e.countryCode;
                    rstName.Text = e.name;
                    rstResult.Text = e.Result.ToShortDateString();
                    rstCorrecto.Text = e.valid ? "Sí" : "No";
                }
                else
                {
                    lblProgreso.Text = string.Empty;
                    rstAddress.Text = string.Empty;
                    rstCountryCode.Text = string.Empty;
                    rstName.Text = string.Empty;
                    rstResult.Text = string.Empty;
                    rstCorrecto.Text = string.Empty;
                }
            }
            catch (System.Reflection.TargetInvocationException ex)
            {
                lblProgreso.Text = ex.Message;
            }
            catch (Exception ex)
            {
                lblProgreso.Text = "Se ha producido un error " + ex.GetType().ToString();

            }
        }
    }
}

Ya está. ¡Qué bonitas se ven las cosas cuando están acabadas!

Por cierto, se si alguien tiene interés, puede descargar el proyecto en VisualStudio2008: Descargar

Publicado en C#, Helpers, WebServices, WinForms | 3 Comentarios »

 
Seguir

Get every new post delivered to your Inbox.