lunes, 21 de junio de 2010

[N-Tier] – Desarrollo en capas - Ejemplo Facturación – Parte 1

 

Introducción


Este articulo es el primero de una serie que tratara temas relacionados con el desarrollo en capas

La idea original es empezar desde el principio e ir paulatinamente agregando funcionalidad mientras se analiza las mejoras involucradas en el diseño

Es por eso que este primer articulo no mostrara nada en la separación en capas, sino que se desarrollara íntegramente en la presentación, todo el código estará en los eventos o método privados de los formularios

Esto será útil para poder comparar la evolución en la codificación empleada cuando no se tienen capas, y cuando se define una arquitectura que si la tiene.

 

Ejemplo de código


Como se comento al principio del articulo este ejemplo no reflejara una arquitectura en capas, sino que estará toda la lógica en los formularios.

Además se notara que en el ejemplo se hace uso de datatable dinámicos, o sea no se usan objetos tipados.

Sera al momento de armar una arquitectura con capas en donde esto se modificara para dar lugar a una correcta comunicación.

En la siguiente imagen se puede apreciar el diseño del formulario:

imagen1

Este permitirá seleccionar un cliente, mediante el botón de Búsqueda, mostrando un dialogo con una grilla con el listado de cliente.

Al costado derecho de la grilla se encuentra el botón con un signo de “+”, este permitirá agregar un nueva ítem de compra.

En la parte inferior un botón de confirmar procesara el pedido, previa validación la información ingresada.

 

Carga de los datos de inicialización del formulario


Durante la carga del formulario de compra, se tiene lugar varias acciones.

La primer tiene que ver con una consulta a la Base de  Datos en donde se recuperan el listado de “temas” que serán cargados en el combo del DataGridView que representa las líneas de compra.

Además por defecto se crea una línea de compra, usando el método NuevaLinea()

private void frmCompra_Load(object sender, EventArgs e)
{
    //
    // Obtengo la lista de Tracks
    //
    DataTable dt = new DataTable();

    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {
        conn.Open();

        string sql = @"SELECT TrackId, Name FROM Track ORDER BY Name";
        SqlCommand cmd = new SqlCommand(sql, conn);

        SqlDataAdapter da = new SqlDataAdapter(cmd);
        da.Fill(dt);

    }

    //
    // Cargo los items del combo
    //
    DataGridViewComboBoxColumn comboCol = dgvLineaCompra.Columns["Track"] as DataGridViewComboBoxColumn;
    comboCol.ValueMember = "TrackId";
    comboCol.DisplayMember = "Name";
    comboCol.DataSource = dt;

    //
    // 
    //
    NuevaLinea();
}
/// <summary>
/// Crea un item de compra en la grilla
/// </summary>
private void NuevaLinea()
{
    DataTable dt = null;

    if (dgvLineaCompra.DataSource == null)
    {
        dt = new DataTable();
        dt.Columns.Add("Track", typeof(int));
        dt.Columns.Add("PrecioUnitario");
    }
    else
        dt = dgvLineaCompra.DataSource as DataTable;

    DataRow row = dt.NewRow();

    dt.Rows.Add(row);

    dgvLineaCompra.DataSource = dt;
}

Este método valida si la grilla posee un origen da datos previamente asignado, en caso de contar con uno lo recupera y procede a agregar un ítem nuevo.

Sino hay datos presentes en la grilla, crea una nueva instancia de datos y procede de igual forma.

Al final concluye con la asignación del datatable a la grilla, para desplegar la información.

 

Obtener el precio desde un combo en el DataGridView


Para poder operar correctamente con el ComboBox contenido en una celda de la grilla, es necesario contar con mas de un evento.

La explicación del uso de estos eventos, esta detallada en este otro artículo:

[DataGridView] - Parte 6 - ComboBox y evento SelectedIndexChanged

Lo interesante es ver como se recuperar un valor especifico de la consulta haciendo uso del ExecuteScalar(), el cual devuelve contenido del primer registro y la primer columna de la consulta, en este caso por ser solo un valor cierra perfecto su uso.

DataGridViewComboBoxEditingControl dgvCombo;

private void dgvLineaCompra_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
    dgvCombo = e.Control as DataGridViewComboBoxEditingControl;

    if (dgvCombo != null)
    {
        dgvCombo.SelectedIndexChanged += new EventHandler(dgvLineaCompra_SelectedIndexChanged);
    }

}

private void dgvLineaCompra_SelectedIndexChanged(object sender, EventArgs e)
{

    ComboBox combo = sender as ComboBox;

    //
    // Se obtiene el precio
    // en base a la seleccion de combo en la celda de la grilla
    //
    decimal precio = 0;
    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {
        conn.Open();

        string sql = @"SELECT UnitPrice FROM Track WHERE TrackId = @idTrack";
        SqlCommand cmd = new SqlCommand(sql, conn);
        cmd.Parameters.AddWithValue("@idTrack", Convert.ToInt32(combo.SelectedValue));

        precio = Convert.ToDecimal(cmd.ExecuteScalar());

    }

    DataGridViewRow row = dgvLineaCompra.CurrentRow;

    row.Cells["PrecioUnitario"].Value = precio;
}

private void dgvLineaCompra_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{

    if (dgvCombo != null)
        dgvCombo.SelectedIndexChanged -= new EventHandler(dgvLineaCompra_SelectedIndexChanged);

}

Validación del formulario


La validación de los campos del formulario es un punto importante, en el método Validaciones(), no solo se verifican los campos individuales, sino que también se itera por las líneas de la grilla verificando que las celdas estén completas.

Se puede remarcar que al usuario se le informa de los puntos detectados durante la validación mediante el uso del control ErrorProvider, desplegándose así un icono al costado de cada control o línea de la grilla donde se detecta el problema.

/// <summary>
/// Valida que los controles contengan con informacion correcta 
/// antes de procesar la transaccion
/// </summary>
/// <returns></returns>
private bool Validaciones()
{
    bool result = true;

    //
    // inicializo los mensajes de validaciones que pudiera haber
    //
    errorProvider.Clear();

    //
    // verifico los campos del cliente
    //
    if (string.IsNullOrEmpty(txtNombre.Text))
    {
        errorProvider.SetError(txtNombre, "El nombre es obligatorio"); 
        result = false;
        
    }

    if (string.IsNullOrEmpty(txtApellido.Text))
    {
        errorProvider.SetError(txtApellido, "El apellido es obligatorio"); 
        result = false;
    }

    if (string.IsNullOrEmpty(txtEmail.Text))
    {
        errorProvider.SetError(txtEmail, "El Mail es obligatorio"); 
        result = false;
    }

    //
    // valido las lineas de compra
    //
    foreach (DataGridViewRow row in dgvLineaCompra.Rows)
    {
        //
        // inicializo las linea de error
        // en caso de tener un mensaje de error previo
        //
        row.ErrorText = "";

        //
        // se validan los campos de la fila
        //
        if (string.IsNullOrEmpty(Convert.ToString(row.Cells["Track"].Value)))
        {
            row.ErrorText = "Debe seleccionar in item de compra.";
            result = false;
        }

        if (string.IsNullOrEmpty(Convert.ToString(row.Cells["Cantidad"].Value)))
        {
            row.ErrorText = "Debe ingresar una cantidad";
            result = false;
        }
        else
        {
            int cantidad = 0;
            if (!Int32.TryParse(Convert.ToString(row.Cells["Cantidad"].Value), out cantidad))
            {
                row.ErrorText = "La cantidad ingresada debe ser un valor numerico";
                result = false;
            }
        }

    }

    return result;
}

 

Proceso de Confirmación de Compra


Este es el código mas extenso de todo el formulario, ya que allí se tomara la información y se procederá toda la transacción de compra realizando los INSERT o UPDATE que hagan falta para lleva a cabo la operación.

 

private void btnConfirmar_Click(object sender, EventArgs e)
{
    if (!Validaciones())
        return;

    //
    // inicializo la transacciones
    //
    using (TransactionScope scope = new TransactionScope())
    {
        using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
        {
            conn.Open();

            #region Creo\Actualizo la informacion del cliente
            int idCliente = 0;

            //
            // si el cliente se ha seleccionado lo actualizo, sino se crea uno nuevo
            //
            if (cliente == null)
            {
                string sql = @"INSERT INTO Customer (FirstName, LastName, Company, Address, Email) VALUES (@firstName, @lastName, @company, @address, @email)
                       SELECT SCOPE_IDENTITY()";

                SqlCommand cmd = new SqlCommand(sql, conn);

                cmd.Parameters.AddWithValue("@firstName", txtNombre.Text);
                cmd.Parameters.AddWithValue("@lastName", txtApellido.Text);
                cmd.Parameters.AddWithValue("@company", txtDireccion.Text);
                cmd.Parameters.AddWithValue("@address", txtCompañia.Text);
                cmd.Parameters.AddWithValue("@email", txtEmail.Text);


                idCliente = Convert.ToInt32(cmd.ExecuteScalar());

            }
            else
            {
                string sql = @"UPDATE Customer SET  
                                    FirstName = @firstName, 
                                    LastName = @lastName, Company = @company, 
                                    Address = @address,
                                    Email = @email
                            WHERE CustomerId = @customerid";

                SqlCommand cmd = new SqlCommand(sql, conn);

                cmd.Parameters.AddWithValue("@firstName", txtNombre.Text);
                cmd.Parameters.AddWithValue("@lastName", txtApellido.Text);
                cmd.Parameters.AddWithValue("@company", txtDireccion.Text);
                cmd.Parameters.AddWithValue("@address", txtCompañia.Text);
                cmd.Parameters.AddWithValue("@email", txtEmail.Text);
                cmd.Parameters.AddWithValue("@customerid", Convert.ToInt32(cliente["CustomerId"]));
                
                cmd.ExecuteNonQuery();
                idCliente = Convert.ToInt32(cliente["CustomerId"]);

            }
            #endregion

            #region Creo el Encabezado de la Factura
            
            int idEncabezadoFactura = 0;

            string sqlFactura = @"INSERT INTO Invoice (CustomerId, InvoiceDate, BillingAddress, Total) VALUES (@customerId, @date, @address, @total)
                       SELECT SCOPE_IDENTITY()";

            using (SqlCommand cmd = new SqlCommand(sqlFactura, conn))
            {

                cmd.Parameters.AddWithValue("@customerId", idCliente);
                cmd.Parameters.AddWithValue("@date", DateTime.Now.Date);
                cmd.Parameters.AddWithValue("@address", txtDireccion.Text);
                cmd.Parameters.AddWithValue("@total", 0);

                idEncabezadoFactura = Convert.ToInt32(cmd.ExecuteScalar());
            }

            #endregion
            
            #region Creo cada linea de la facturacion

            string sqlLineaFactura = @"INSERT INTO InvoiceLine (InvoiceId, TrackId, UnitPrice, Quantity) 
                                        VALUES (@invoiceid, @trackid, @unitprice, @quantity)
                                        SELECT SCOPE_IDENTITY()";

            using(SqlCommand cmd = new SqlCommand(sqlLineaFactura, conn))
            {
                foreach (DataGridViewRow row in dgvLineaCompra.Rows)
                {
                    //
                    // como se reutiliza el mismo objeto SqlCommand es necesario limpiar los parametros
                    // de la oepracion previa, sino estos se iran agregando en la coleccion, generando un fallo
                    //
                    cmd.Parameters.Clear();

                    cmd.Parameters.AddWithValue("@invoiceid", idEncabezadoFactura);
                    cmd.Parameters.AddWithValue("@trackid", Convert.ToInt32(row.Cells["Track"].Value));
                    cmd.Parameters.AddWithValue("@unitprice", Convert.ToDecimal(row.Cells["PrecioUnitario"].Value));
                    cmd.Parameters.AddWithValue("@quantity", Convert.ToInt32(row.Cells["Cantidad"].Value));

                    //
                    // Si bien obtenermos el id de linea de factura, este no es usado
                    // en la aplicacion
                    //
                    int idLineaFactura = Convert.ToInt32(cmd.ExecuteScalar());
                    
                }
            }
            #endregion

        }

        scope.Complete();

    }

    InicializarControles();

}

Los bloques de código principales incluyen:

- Creación o actualización de los datos del cliente

- Creación de la cabecera de la compra

- creación de las líneas de compra, asociadas a la cabecera.

En este punto sea interesante contar con la estructura de tablas con las cuales se trabajara:

imagen2

Si se analiza el código se vera que los id de las tablas son auto numéricos, por lo tanto en la misma operación de INSERT se adiciona la operación que permite recuperar el id generado por la tabla, haciendo uso del SCOPE_IDENTITY()

Para mas información sobre el uso de los campos auto numéricos revisar este otro articulo:

[ADO.NET] – Parte 6 - Ejemplos simples – Campos Auto numéricos (Identity)

Algo a destacar es el uso de transacciones, las cuales son muy simples de implementar al contar con la clase TransactionScope, la cual engloba toda la transacción, y por medio del método Complete() se confirma la operación, cualquier error que se produjera anularía las operaciones realizadas dejando las tablas con datos consistentes.

 

Conclusiones


Si bien el ejemplo se puede considerar prolijo y ordenado en el código, esto es solo porque se trata de una aplicación pequeña que forma parte de un ejemplos, aplicar esta misma técnica en aplicaciones de varios formulario y operaciones que interactúan puede resultar verdaderamente complejas.

Las principales desventajas de esta forma de codificar serian:

- No fomentan la reutilización, si se analiza el código cada consulta o conexión a la base de datos se realiza en los eventos, si fuera necesario en algún otro punto del código obtener la misma información abría que replicar el código.

Este punto se podría solucionar si se crean métodos que encapsulen esta funcionalidad, pero al estas usando formulario estos estarían local al mismo, no podrías accederse desde otros puntos de la aplicación, es necesario contar con otro sitio que permita acceder sin generar acoplamiento.

- El procesamiento de las transacciones generan código muy extenso, este punto es claramente visible en el botón de confirmación de la compra, esta es una operación simple en comparación con las que podrían crearse, sin embargo creo una buena cantidad de líneas de código.

- No hay separación de las responsabilidades, en un solo punto se crean cliente, y se graban la facturación, esto debería poder separarse para dividir la complejidad y aumentar la mantenibilidad de la aplicación.

Tener código que hace muchas operaciones distintas no es bueno, mas que nada para su mantenimiento futuro.

 

Consideración para el código


Los ejemplos fue desarrollado usando VS 2008 y SQL Server 2008 Express.

Es necesario tener el servicio de Sql Express iniciado localmente para poder ejecutar la solución.

 

[C#] 
[VB.NET] 

domingo, 20 de junio de 2010

ComboBox - DropDownList – Opción “Todos”

 

Introducción

En algunas situaciones se requiere agregar un ítem especial a los controles, como ser en controles del tipo combo, para que el usuario tenga la posibilidad de indicar que no hay un ítem escogido de la lista.

Esto se usa especialmente en circunstancias en donde el campo que representa el combo es opcional, o se quiere inicializar en un ítem que obligue al usuario a elegir un valor de la lista.

Es por esto que según cual sea la funcionalidad del combo, si actuara como filtro, o como lista con obligación de selección del usuario, es que tendrás distintas leyendas.

Si es un filtro podrías decir: “<Todos los items>”, o sea que no se aplicaría el ítem como filtro, o si el control actual sin una selección por defecto podrías decir: “<Seleccione un ítem de la lista>”, por supuesto este ultimo caso se validaría haya elegido un ítem.

En el articulo se intentara explicar las forma en que es posible añadir un ítem extra a los controles ya sea WinForms o Web.

 

WinForms ComboBox

El combobox de una aplicación estilo “Windows Application” puede recibir como origen de datos diferentes fuentes, podrías ser una clase especifica creada por uno, o podrías ser un dataset. Según cual sea el origen de datos cambia la solución que debe aplicarse para agregar este ítem extra opcional de la lista, ya que será el origen de datos donde se agrega la opción especial.

Agregar opción usando clases creadas por uno

En el ejemplo se ha definido una clase de nombre “Producto”, es muy simple, solo cumple la función de definir la información que necesitas el control.

public class Producto
{
    public string Descripcion { get; set; }
    public int Id { get; set; }
    public int Precio { get; set; }
}

El código que carga una lista de tipo Producto y la asigna como origen de datos al combo, es el siguiente:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        
        cbProductos.ValueMember = "Id";
        cbProductos.DisplayMember = "Descripcion";
        cbProductos.DataSource = ObtenerListItemOpcional();

    }

    private List<Producto> ObtenerListItemOpcional()
    {
        List<Producto> listproductos = ObtenerLista();

        listproductos.Insert(0, new Producto() { Id = 0, Descripcion = "<Seleccione un Item>", Precio = 0 });

        return listproductos;
    }

    private List<Producto> ObtenerLista()
    {
        return new List<Producto>()
        {
            new Producto(){ Id = 1, Descripcion = "Notebook HP Athlon Neo MV-40", Precio = 3100},
            new Producto(){ Id = 2, Descripcion = "Notebook ASUS ATOM N450", Precio = 2300},
            new Producto(){ Id = 3, Descripcion = "Notebook ACER N270", Precio = 2000},
            new Producto(){ Id = 4, Descripcion = "Notebook SONY Atom TM", Precio = 2600},
            new Producto(){ Id = 5, Descripcion = "Notebook DELL SU 4100", Precio = 3400},
            new Producto(){ Id = 6, Descripcion = "Notebook ASUS Core i3", Precio = 4500}
             
        };
    }
}

La línea clave en donde se agrega el ítem que permitirá la selección opcional es:

listproductos.Insert(0, new Producto() { Id = 0, Descripcion = "<Seleccione un Item>", Precio = 0 });

Lo importante a remarcar es como el modelo original de datos no es alterado, pudiéndose cargar, si se necesita, un combo simple sin un ítem adicional, se maximiza la reutilización de los métodos separándolos según agregue o no el ítem opcional.

Agregar opción usando un DataTable

En este caso se usara un datatable cuya estructura será definido en runtime, la clave aquí esta en el método ObtenerListItemOpcional() donde se crea la nueva fila en tiempo de diseño.

public partial class Form2 : Form
{
    public Form2()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        cbProductos.DisplayMember = "Descripcion";
        cbProductos.DataSource = ObtenerListItemOpcional();
    }

    private DataTable ObtenerListItemOpcional()
    {
        DataTable listproductos = ObtenerLista();

        DataRow row = listproductos.NewRow();
        row["Id"] = 0;
        row["Descripcion"] = "<Seleccione un Item>";
        row["Precio"] = 0;
        
        listproductos.Rows.InsertAt(row, 0);

        return listproductos;
    }

    private DataTable ObtenerLista()
    {
        DataTable dt = new DataTable();

        dt.Columns.Add("Id");
        dt.Columns.Add("Descripcion");
        dt.Columns.Add("Precio");

        DataRow row = dt.NewRow();
        row["Id"] = 1;
        row["Descripcion"] = "Notebook HP Athlon Neo MV-40";
        row["Precio"] = 3100;
        dt.Rows.Add(row);

        row = dt.NewRow();
        row["Id"] = 2;
        row["Descripcion"] = "Notebook ASUS ATOM N450";
        row["Precio"] = 2300;
        dt.Rows.Add(row);

        row = dt.NewRow();
        row["Id"] = 3;
        row["Descripcion"] = "Notebook ACER N270";
        row["Precio"] =  2000 ;
        dt.Rows.Add(row);

        row = dt.NewRow();
        row["Id"] = 4;
        row["Descripcion"] = "Notebook SONY Atom TM";
        row["Precio"] = 2600;
        dt.Rows.Add(row);

        row = dt.NewRow();
        row["Id"] = 5;
        row["Descripcion"] = "Notebook DELL SU 4100";
        row["Precio"] = 3400;
        dt.Rows.Add(row);

        row = dt.NewRow();
        row["Id"] = 6;
        row["Descripcion"] = "Notebook ASUS Core i3";
        row["Precio"] = 4500;
        dt.Rows.Add(row);

        return dt;
    }
}

 

En caso de usar un dataset tipado, la técnica es la misma solo que se dispondrá de método específicamente creados por el dataset en tiempo de diseño, en el ejemplo no se contemplo este caso, pero ante la necesidad de usarlo podría ser algo como lo siguiente:

private dtoProducto.ProductosDataTable ObtenerListItemOpcional()
{
    dtoProducto.ProductosDataTable dt = ObtenerLista();

    dtoProducto.ProductosRow row = dt.NewProductosRow();
    row.Id = 0;
    row.Descripcion = "<Seleccione un Item>";
    row.Precio = 0;
    dt.Rows.Add(row);

    return dt;
}

Es prácticamente idéntico solo que hay método especiales, por ejemplo, NewProductosRow() y los campos estas tipados en propiedades.

 

[C#]
[VB.NET]

 

ASP.NET DropDownList

La técnicas usadas para añadir un ítem a los controles web, difieren bastante de las usadas en una aplicación winforms.

Tomemos de ejemplo el siguiente html

    <form id="form1" runat="server">
    <div>
        <asp:DropDownList ID="DropDownList1" runat="server">
        </asp:DropDownList>
        
        <br/>
        <br/>
        
        <asp:DropDownList ID="DropDownList2" runat="server" AppendDataBoundItems="true">
            <asp:ListItem Value="0">&lt;Seleccione un Item&gt;</asp:ListItem>
        </asp:DropDownList>
    </div>
    </form>

Este solo contiene dos controles de lista, pero hay una diferencia entre ellos, el segundo hace uso de una propiedad especial que le permite añadir un ítem definido en código html del mismo, me refiero al AppendDataBoundItems="true"

Si ahora se analiza el código .net usado para cargar cada lista

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        DropDownList1.DataSource = ObtenerLista();
        DropDownList1.DataTextField = "Descripcion";
        DropDownList1.DataValueField = "Id";
        DropDownList1.DataBind();

        DropDownList1.Items.Insert(0, new ListItem("<Seleccione un Item>", "0"));


        DropDownList2.DataSource = ObtenerLista();
        DropDownList2.DataTextField = "Descripcion";
        DropDownList2.DataValueField = "Id";
        DropDownList2.DataBind();
    }

}

Para el primer dropdownlist se ha agregado el ítem programáticamente usando el método Insert(), indicando en el primer parámetro que el valor estará ubicado en la primer posición.

Mientras que el segundo no requiere del agregado de nada especial, el ítem se había definido en el html.

 

[C#]
[VB.NET]

Conclusión

Si se compara las técnicas usadas para cargar el ComboBox de una aplicación WinForms y el DropDownList de una aplicación asp.net, se notara rápidamente que el primer caso el ítem adicional se agrega a los datos antes de bindear el control

Mientras que el el desarrollo con asp.net permite agregar un ítem directo al control, ya sea que se defina el en html, o se use el Insert() en su colección de ítems

martes, 1 de junio de 2010

[Linq to XML] Unir Documentos – Uso del Join

 

Introducción

El articulo muestra la forma en que se puede hacer uso de linq to xml para unir dos documentos xml diferentes, pero que poseen un dato en común como vinculo

Se podría decir que la unión seria similar a un “join” entre tablas, pero la diferencia esta en que es xml.

El resultado será la creación de una lista (de uan clase concreta) que podrá ser bindeada a una grilla.

 

Unión de las listas

Para el ejemplo se ha creado una clase custom que será usada en la defunción del tipo a devolver.

Esto es necesario ya que un tipo anónimo definido como “var”, no puede ser devuelto en una función, es necesario definir el tipo concreto.

public class Producto
{
    public string NumeroSerie {get; set;}
    public string Nombre {get; set;}
    public string Alquilado {get; set;}
    public string Modelo { get; set; }
}

El código linq usado es el siguiente:

private List<Producto> ProcesarXML()
        {
            string xml1 = @" <Nodos>
                             <Nodo s='122531715' m='0'>
                              <Model id='029A' m='210ED441A02' vn='2.27' vp='1.94' fd='12/11/2007 00:00:00' dad='0107' pass='2' />
                              <Info s='8' lpt='15/04/2010 01:00:00' let='01/04/2010 19:36:25' lc='29/05/2010 13:09:45' in='False' iim='True' />
                              <En E0='00000515' />
                              <ICP I0='0' />
                             </Nodo>
                             <Nodo s='122531717' m='0'>
                              <Model id='0290' m='210ED441A02' vn='2.27' vp='1.94' fd='23/10/2007 00:00:00' dad='0119' pass='2' />
                              <Info s='8' lpt='15/04/2010 01:00:00' let='02/04/2010 09:24:16' lc='29/05/2010 16:09:47' in='False' iim='True' />
                              <En E0='00000859' />
                              <ICP I0='0' />
                             </Nodo>
                            </Nodos>";



             string xml2 =@"<Parcelas>
                             <Parcela trafo='1' Sector='K'>
                              <ID>1</ID>
                              <Nombre>Par 1 Sec. K</Nombre>
                              <Numero>122531715</Numero>
                              <Alquilada>No</Alquilada>
                             </Parcela>
                             <Parcela trafo='1' Sector='K'>
                              <ID>2</ID>
                              <Nombre>Par 2 Sec. K</Nombre>
                              <Numero>122531717</Numero>
                              <Alquilada>No</Alquilada>
                             </Parcela>
                            </Parcelas>";

            XElement xmldoc1 = XElement.Parse(xml1);
            XElement xmldoc2 = XElement.Parse(xml2);
            

            List<Producto> query = (from item1 in xmldoc1.Elements("Nodo")
                                    join item2 in xmldoc2.Elements("Parcela") 
                                    on item1.Attribute("s").Value equals item2.Element("Numero").Value
                                    select new Producto()
                                    {
                                        NumeroSerie = item1.Attribute("s").Value,
                                        Nombre = item2.Element("Nombre").Value,
                                        Alquilado = item2.Element("Alquilada").Value,
                                        Modelo = item1.Element("Model").Attribute("m").Value
                                    }).ToList<Producto>();

            return query;
            
        }

Si se observa en la consulta como se ha definido el join, se vera que se une el valor de un atributo del primer xml, con el valor de un nodo del segundo, al usarse:

on item1.Attribute("s").Value equals item2.Element("Numero").Value

 

Aclaraciones

En el ejemplo se hace uso del método XElement.Parse() para cargar desde variables del tipo string el xml

En caso de ser desde un archivo debería usarse: XElement.Load()

 

[C#]
 

[DataGridView] – Texto Celdas en Mayúscula

 

Introducción

En algunos casos es necesario que la entrada de datos sea siempre en mayúscula, lograr esto en un control Textbox es relativamente simple, ya que se dispone del evento KeyPress para detectar y convertir el valor ingresado.

Pero que sucede si esto mismo se quiere aplicar a las celdas de un DataGridView, bien este articulo demuestra como poder lograrlo.

Usando el evento EditingControlShowing

En el ejemplo se hará uso del evento que permitirá detecta cuando una celda entra en modo de edición.

Dentro del evento EditingControlShowing, se detecta si la columna que entro en modo de edición corresponde a la que se quiere controlar. En este ejemplo solo la columna “Descripcion” será afectada con el control en el input de datos.

Es necesario remarcar que al momento de adjunta el evento KeyPress al objeto e.Control, (que ha sido casteado a DataGridViewTextBoxEditingControl, para su correcta utilización), el handler del evento se aplicaría a todas las celdas de este mismo tipo. También a las de las columnas que no se quiere aplicar el control de mayúsculas, en este caso la de  “Cuenta”.

Es por eso que en el evento KeyPress también se control la columna que esta activa en ese momento.

El handler del evento queda adjunto aun cuando se sale de modo edición.

La utilización de la primer línea que quita el hadler al entrar en edición:

dText.KeyPress -= new KeyPressEventHandler(dText_KeyPress);

Solo sirve para que se adjunte un único evento, ya que sino estuviera se adjuntarían una detrás de otro produciéndose múltiples llamadas al evento KeyPress.

[C#]

private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
    int columnIndex = dataGridView1.CurrentCell.ColumnIndex;

    if (dataGridView1.Columns[columnIndex].Name == "Descripcion")
    {
        DataGridViewTextBoxEditingControl dText = (DataGridViewTextBoxEditingControl)e.Control;

        dText.KeyPress -= new KeyPressEventHandler(dText_KeyPress); 
        dText.KeyPress += new KeyPressEventHandler(dText_KeyPress);
    }
}

void dText_KeyPress(object sender, KeyPressEventArgs e)
{
    int columnIndex = dataGridView1.CurrentCell.ColumnIndex;

    if (dataGridView1.Columns[columnIndex].Name == "Descripcion")
    {
        e.KeyChar = char.ToUpper(e.KeyChar);
    }
}

[VB.NET]

 

Private Sub dataGridView1_EditingControlShowing(ByVal sender As Object, ByVal e As DataGridViewEditingControlShowingEventArgs)

    Dim columnIndex As Integer = dataGridView1.CurrentCell.ColumnIndex

    If dataGridView1.Columns(columnIndex).Name = "Descripcion" Then

        Dim dText As DataGridViewTextBoxEditingControl = DirectCast(e.Control, DataGridViewTextBoxEditingControl)

        RemoveHandler dText.KeyPress, AddressOf dText_KeyPress
        AddHandler dText.KeyPress, AddressOf dText_KeyPress

    End If

End Sub

Private Sub dText_KeyPress(ByVal sender As Object, ByVal e As KeyPressEventArgs)

    Dim columnIndex As Integer = dataGridView1.CurrentCell.ColumnIndex

    If dataGridView1.Columns(columnIndex).Name = "Descripcion" Then

        e.KeyChar = Char.ToUpper(e.KeyChar)

    End If

End Sub

 

[C#]
[VB.NET]

 

Resolución del problema en al asignación del evento

El problema comentado anteriormente podrías resolverse fácilmente con solo declarar la variable que contendrá la celda en edición de forma global al evento.

De esta forma se podrías hacer uso del evento que detecta cuando una celda ha dejado de editarse, removiendo el handler del evento.

Ahora el evento KeyPress no controla que columna se esta editando, esto solo se hace cuando se entra o sale del modo edición de la celda.

[C#]

DataGridViewTextBoxEditingControl dText = null;

private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
    int columnIndex = dataGridView1.CurrentCell.ColumnIndex;

    if (dataGridView1.Columns[columnIndex].Name == "Descripcion")
    {
        dText = (DataGridViewTextBoxEditingControl)e.Control;
        
        dText.KeyPress += new KeyPressEventHandler(dText_KeyPress);
    }
}

private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
    int columnIndex = dataGridView1.CurrentCell.ColumnIndex;

    if (dataGridView1.Columns[columnIndex].Name == "Descripcion")
    {
        dText.KeyPress -= new KeyPressEventHandler(dText_KeyPress);
    }
}

void dText_KeyPress(object sender, KeyPressEventArgs e)
{
   e.KeyChar = char.ToUpper(e.KeyChar);
}

[VB.NET]

Private dText As DataGridViewTextBoxEditingControl = Nothing

Private Sub dataGridView1_EditingControlShowing(ByVal sender As Object, ByVal e As DataGridViewEditingControlShowingEventArgs) Handles dataGridView1.EditingControlShowing

    Dim columnIndex As Integer = dataGridView1.CurrentCell.ColumnIndex

    If dataGridView1.Columns(columnIndex).Name = "Descripcion" Then

        dText = DirectCast(e.Control, DataGridViewTextBoxEditingControl)

        AddHandler dText.KeyPress, AddressOf dText_KeyPress

    End If
End Sub

Private Sub dataGridView1_CellEndEdit(ByVal sender As Object, ByVal e As DataGridViewCellEventArgs) Handles dataGridView1.CellEndEdit

    Dim columnIndex As Integer = dataGridView1.CurrentCell.ColumnIndex

    If dataGridView1.Columns(columnIndex).Name = "Descripcion" Then
        RemoveHandler dText.KeyPress, AddressOf dText_KeyPress
    End If

End Sub

Private Sub dText_KeyPress(ByVal sender As Object, ByVal e As KeyPressEventArgs)

    e.KeyChar = Char.ToUpper(e.KeyChar)

End Sub

 

[C#]
[VB.NET]