sábado, 17 de octubre de 2009

[ASP.NET] Pasar información entre User Control

 

Introducción


El desarrollo modular de aplicaciones web requiere la confección de componentes que permitan encapsular ciertos compartimientos que serán reutilizados, o que necesitan ser tratados de una forma unificada para facilitar la mantenibilidad del desarrollo.

Es por eso que en ciertas ocasiones el uso de User Controls es una buena práctica, aunque un aspecto no menor es la comunicación entre estos componentes encapsulados con el resto de la aplicación, o con otros componentes.

En este texto explicare como lograr la comunicación entre dos User Control, pasando datos de uno a otro de una forma desacoplada.

 

Diseño


El planteo se basará en la utilización de dos users control muy simples que estarán contenidos en una misma página web.

El primer user control encapsulará una grilla la cual cargara un listado de empleados.

El segundo contiene una serie de Textbox que serán utilizados para visualizar el registro seleccionado del primer user control.

 

User Control – Listado


Este estará formado por un control GridView.

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;

namespace WebUserControlsCommunicated
{
    public partial class ucGrid : System.Web.UI.UserControl
    {
        public delegate void GridSelectorCommandEventHandler(GridSelectorCommandEventArgs e);
        public event GridSelectorCommandEventHandler GridSelectorChanged;

        public class GridSelectorCommandEventArgs
        {
            public int Id { get; protected set; }
            public string Nombre { get; protected set; }
            public string Cargo { get; protected set; }

            public GridSelectorCommandEventArgs(int id, string nombre, string cargo)
            {
                this.Id = id;
                this.Nombre = nombre;
                this.Cargo = cargo;
            }
        }


        public DataTable DataSource { get; set; }


        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                GridView1.DataSource = this.DataSource;
                GridView1.DataBind();
            }
        }

        protected void GridView1_SelectedIndexChanged(object sender, EventArgs e)
        {
            int Id = Convert.ToInt32(GridView1.SelectedRow.Cells[1].Text);
            string nombre = Convert.ToString(GridView1.SelectedRow.Cells[2].Text);
            string cargo = Convert.ToString(GridView1.SelectedRow.Cells[3].Text);


            if (GridSelectorChanged != null)
                GridSelectorChanged(new GridSelectorCommandEventArgs(Id, nombre, cargo));

            
        }
    }
}

Los puntos importantes a destacar del código serian:

- línea 30, define la propiedad que permitirá asignar el origen de datos que se usará para cargar el GridView. Esta propiedad será utilizada en el evento Page_Load del user control, asignando el contenido.

- líneas 12 y 13, en las mismas se define el handler y evento que permitirán que la información  de los items seleccionados del GridView viajen desde el interior del user control hacia la página.

- líneas 15-27, definen el argumento del evento que será usado para realizar el pasaje de los datos seleccionados en la grilla. Es por medio de este argumento que se podrá recuperar los valores elegidos, ya que desde fuera del user control no se tendra acceso directo al GridView.

Habría una alternativas a esta opción, la cual consiste en dejar disponible en una propiedad los valores seleccionados, imitando un poco a las propiedades como ser el SelectedRow, o SelectedItems que poseen otro controles.

- líneas 42-53, estas defines el evento local al user control que se ejecutará al utilizar el botón de selección del GridView, básicamente será una especie de conversión de eventos, en donde un evento atrapado localmente, es transformado en un evento exterior, el cual es lanzado en la línea 50.

Como ser observara las primeras acciones son las de recuperar los valores de la columnas de la fila seleccionada, para poderlos usar como parámetros del argumento del evento que se lanzara.

Es muy importante cuando se va a lanzar un evento validar que este tenga algún método adjunto, ya que de no tener ninguno y ejecutarse este producirá un fallo, la validación por distinto de null evita este problema.

 

User Control – Listado de TextBox


Este user control contendrá una serie de TextBox, destinado a la visualización de la información seleccionada en el user control que contiene el GridView

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebUserControlsCommunicated
{
    public partial class ucTextView : System.Web.UI.UserControl
    {

        public int Id { get; set; }
        public string Nombre { get; set; }
        public string Cargo { get; set; }

        protected void Page_Load(object sender, EventArgs e)
        {

        }

        public void Refresh(int id, string nombre, string cargo)
        {
            this.Id = Id;
            this.Nombre = nombre;
            this.Cargo = cargo;

            this.Refresh();
        }

        public void Refresh()
        {
            txtId.Text = Convert.ToString(this.Id);
            txtNombre.Text = this.Nombre;
            txtCargo.Text = this.Cargo;
        }
    }
}

Este control tiene bastante menos lógica que implementar ya que su funcionalidad se reduce a recibir los valores y asignarlos a los TextBox que correspondan.

Como se observa cuenta con una serie de propiedades que representan cada atributo de la entidad.

Y un método Refresh() que será el encargado de realizar en concreto la asignación de cada propiedad con su respectivo control.

 

Integración – Default.aspx


Bien llego el momento de poner todo en conjunto a funcionar.

Para ello se hará uso del formulario web, Default.aspx, es allí donde se arrastrara cada user control en el diseñador de la pagina, haciendo uso de una tabla para dar algo de formato y ubicación.

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;

namespace WebUserControlsCommunicated
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            ucGrid1.GridSelectorChanged += new ucGrid.GridSelectorCommandEventHandler(ucGrid1_GridSelectorChanged);

            if (!IsPostBack)
            {
                ucGrid1.DataSource = CargarTabla();
            }
        }

        void ucGrid1_GridSelectorChanged(ucGrid.GridSelectorCommandEventArgs e)
        {
            ucTextView1.Id = e.Id;
            ucTextView1.Nombre = e.Nombre;
            ucTextView1.Cargo = e.Cargo;

            ucTextView1.Refresh(); 
        }



        private DataTable CargarTabla()
        {
            DataTable dt = new DataTable();
            dt.Columns.Add("Id");
            dt.Columns.Add("Nombre");
            dt.Columns.Add("Cargo");

            DataRow row = dt.NewRow();
            row["Id"] = 1;
            row["Nombre"] = "Andres";
            row["Cargo"] = "Developer";
            dt.Rows.Add(row);

            row = dt.NewRow();
            row["Id"] = 2;
            row["Nombre"] = "Federico";
            row["Cargo"] = "PM";
            dt.Rows.Add(row);

            row = dt.NewRow();
            row["Id"] = 3;
            row["Nombre"] = "Leonardo";
            row["Cargo"] = "Developer";
            dt.Rows.Add(row);

            return dt;

        }

    }
}

En la línea 14 es donde se define la utilización del evento expuesto por el user control que contiene la grilla, la asignación del handler requiere de un método declarado en las líneas 22-29.

Es en este método ucGrid1_GridSelectorChanged donde se hará uso de los parámetro del argumento del evento GridSelectorCommandEventArgs, para especificar los datos al segundo user control.

Como se habrá notado en este caso se asignan las propiedades directamente al segundo user control, para llamar por ultimo el método Refresh() que realizara la asignación de estos valores dentro del control; pero también se podría haber utilizado para tal fin el método Refresh() sobrecargado con los valores de cada propiedad necesaria para desplegar la información.

[C#] 
 

lunes, 12 de octubre de 2009

C# – Word – Utilización de Tablas

 

Introducción

El hacer uso de las librerías de Interop de Office puede llega a ser complejo en ciertas circunstancias, mas cuando se necesita hacer uso de posiciones relativas a otros elemento en un documento, es por ello que en este ejemplo intento reflejar como hacer uso de tablas en Word manipulándolas desde código.

Pasos previos

Se debe remarcar que para que estos ejemplos puedan ser ejecutados es necesario contar un Office instalado en el equipo, ya que las librerías de Interop requieres que se encuentren presentes los componente COM que utilizan, y es necesario que Word sea instalado para ello.

Notarán en el código en sus primeras líneas la inclusión de un alias en el using para poder referenciar a la funcionalidad de Office Word

using Office = Microsoft.Office.Interop.Word;
Introducir una tabla dentro de otra

La primera aproximación que tendremos con las tablas será para utilizarlas de forma anidadas, o sea una tabla dentro de otra.

Para ellos contaremos con el siguiente código:

        private void btnTabladentroTabla_Click(object sender, EventArgs e)
        {
            Office.ApplicationClass app = new Office.ApplicationClass();
            
            //
            // Agrego un nuevo documento a la aplicacion Word
            //
            object missing = System.Reflection.Missing.Value;

            Office.Document doc = app.Documents.Add(ref missing, ref missing,
                                 ref missing, ref missing);



            //
            // Adiciono una tabla sobre el documento
            //
            object DefaultTableBehavior = Office.WdDefaultTableBehavior.wdWord9TableBehavior;
            object WdAutoFitBehavior = Office.WdAutoFitBehavior.wdAutoFitWindow;

            Office.Table table1 = doc.Tables.Add(app.Selection.Range, 4, 3, ref DefaultTableBehavior,ref WdAutoFitBehavior);
            
            //
            // Inserto al segunda tabla, pero a esta le indicare que el range donde debe
            // incluirse esta dentro de la primer tabla, mas precisamente en la primer columna
            // segunda fila
            //
            Office.Table table2 = doc.Tables.Add(table1.Cell(2,1).Range, 2, 3, ref DefaultTableBehavior,ref WdAutoFitBehavior);

            //
            // Agrego una fila nuevo a la tabla
            //
            table2.Rows.Add(ref missing);

            //
            // Guardo el archivo
            //
            object fileNameSave = Path.Combine(Application.StartupPath, "ArchivoGenerado.doc");

            doc.SaveAs(ref fileNameSave, ref missing, ref missing, 
                        ref missing, ref missing, ref missing, ref missing,
                        ref missing, ref missing, ref missing, ref missing, 
                        ref missing, ref missing, ref missing, ref missing, ref missing);


            doc.Close(ref missing, ref missing, ref missing);

            MessageBox.Show("El documento se generó correctamente");

        }

El código de por si, al seguirlo paso a paso esta bastante claro en las operaciones que realiza, pero por ahí hay algunos puntos a remarcar.

Uno de ellos es la línea 23 se notará que el rango utilizado en la creación de la tabla incluye la selección de una celda de la tabla creada previamente, este es el punto clave para insertar una tabla dentro de otra, la sección correcta del lugar donde ubicarla.

Una aspecto bastante raro que me encontré cuando realizaba las pruebas es que por mas que hacia uso de los parámetros NumRows la tabla interna siempre aparecía con una sola fila, es por ellos que adicione una línea de código que inserta una fila adicional. Esto se visualiza en la línea 27 del código.

Insertar una tabla en medio de otras dos

Aquí también es importante jugar con una correcta selección de los objetos, y ubicar correctamente la posición para insertar la nueva tabla.

        private void btnTablaEntreTablas_Click(object sender, EventArgs e)
        {
            Office.ApplicationClass app = new Office.ApplicationClass();

            //
            // Realizo al apertura de un archivo existente, el cual contendra 4 tablas
            //
            object missing = System.Reflection.Missing.Value;
            object fileNameAbrir = Path.Combine(Application.StartupPath, "ArchivoOriginal.doc");

            Office.Document doc = app.Documents.Open(ref fileNameAbrir, ref missing, ref missing, ref missing, 
                                                    ref missing, ref missing, ref missing, 
                                                    ref missing, ref missing, ref missing, 
                                                    ref missing, ref missing, ref missing, 
                                                    ref missing, ref missing, ref missing);

            //
            // Selecciono la segunda tabla del documento
            //
            doc.Tables[2].Range.Select();
 
            //
            // de la seleccion anterio me desplazo un posicion hacia abajo
            //
            object moveUnit = Office.WdUnits.wdLine;
            object moveLength = 1;

            app.Selection.MoveDown(ref moveUnit, ref moveLength, ref missing);

            //
            // Agrego un espacio, o entrer, para generar la separacion para la proxima tabla
            //
            app.Selection.TypeParagraph();


            //
            // Inserto a la nueva tabla
            //
            object DefaultTableBehavior = Office.WdDefaultTableBehavior.wdWord9TableBehavior;
            object WdAutoFitBehavior = Office.WdAutoFitBehavior.wdAutoFitWindow;

            Office.Table table1 = doc.Tables.Add(app.Selection.Range, 4, 3, ref DefaultTableBehavior, ref WdAutoFitBehavior);

            Office.Range cellrange = table1.Cell(1, 1).Range;
            cellrange.Text = "Tabla Intermedia";

            //
            // Guardo el documento
            //
            object fileNameSave = Path.Combine(Application.StartupPath, "ArchivoGenerado.doc");

            doc.SaveAs(ref fileNameSave, ref missing, ref missing,
                        ref missing, ref missing, ref missing, ref missing,
                        ref missing, ref missing, ref missing, ref missing,
                        ref missing, ref missing, ref missing, ref missing, ref missing);


            doc.Close(ref missing, ref missing, ref missing);

            MessageBox.Show("El documento se genero correctamente");

        }

En este ejemplo a diferencia del anterior, se parte de un documento ya existente, al cuál se le agregara una tabla intermedia entre otras dos tablas.

En la línea 17 se visualiza como se selecciona la tabla debajo de la cual se quiere insertar la nueva. Las siguientes líneas 21-27 son importantes para posicionar el cursor de forma correcta.

Debe remarcarse como se hace uso de la propiedad Selection del objeto Application, en este caso no se hace uso del Document. El objetivo, una vez que se tiene la tabla seleccionada, se baja una posición con el cursor, y luego agregar un enter o párrafo, para lograr separar las tablas.

Sino se agrega un párrafo intermedio entonces las tablas se crearán unidas, no logrando el efecto deseado.

[C#]
 

sábado, 3 de octubre de 2009

C# - Crystal Reports – Sumatoria Condicional (Conditional Sum)

 
Introducción

Cuando se requiere trabajar con Crystal en la creación de reportes un punto interesante es poder agregar condiciones a los campos de agregación, como se una sumatoria al final de la pagina, para ello tenemos herramientas en Crystal que utilizaremos en este ejemplo

Desarrollo

Para el ejemplo haremos uso de un ambiente simple en donde tendremos una lista de usuarios y sus asistencias a determinado evento.

Para ello ene le ejemplo se hará uso como origen de datos un DataSet Tipado, el cual era cargado en este caso de forma manual, pero bien podría hacerse accediendo a una base de datos

Como próximo paso se creara el reporte agregando a la sección de detalle los campos definidos en el DataSet.

Una vez que tenemos el reporte armado, se procederá a la creación del campo de sumatoria, para ello se realiza un click sobre el campos al cual se le quiere aplicar la suma y se selecciona Insert –> Running Total …

En el dialogo que se visualizara se podrá cambiar el nombre en este caso se utilizo “AsistenciasTotal”, y la parte mas importante será definir la formula que usara, para ello se deberá utilizar la opción que dice “Use a formula”

Al presionarla se visualizara otro cuadro en donde se introducirá la formula que es requerida en este caso filtraremos por un nombre, por ejemplo “Carlos”, por último se acepta el cuadro con el botón superior “Save and close”.

Cuando se acepte el ultimo cuadro seguramente los campos pueden quedar encimados, pera solucionarlo solo hace falta moverlos, ubicándolos donde sea requerido.

El reporte final, luego de ubicar correctamente los campos, quedara de esta forma:

Y al ejecutarlo el resultado será el siguiente:

Debe notarse además que si se visualiza el “Field Explorer” se encontrara el campo de sumatoria recién creado.

 

[C#]