domingo, 21 de febrero de 2010

[Winforms] - ListView, como usarlos y pasar ítems entre ellos

 

Introducción


En este articulo tratare de mostrar como se hace uso de ListView de manera que se puedan visualizar los registros, y manipularlos, en este caso se editara individualmente un ítem de la lista, y se trabajara mediante la el pasaje de la selección a otro ListView.

Algunas propiedades importante que se han usado en el ejemplo son

- View= Details, permite definir el layout de los ítem dentro del ListView, por tratarse de ítem con estilo de grilla, se usa el modo de detalle.

- FullRowSelect = True, esta propiedad permite que al seleccionar un ítem este sea se resalte a lo largo del registro

- HideSelection = False, cuando el control pierde el foco este mantiene un un control griseada la selección activa.

La creación de las columnas fue realizada en tiempo de diseño, para los cual se hizo uso las opciones mostradas en la siguiente imagen

 

 

Carga del ListView


El primer paso que se realizaría será la carga de los ítems dentro del control.

private void Form1_Load(object sender, EventArgs e)
{
    DataTable dt = ObtenerDatos();

    foreach (DataRow row in dt.Rows)
    {
        ListViewItem item = new ListViewItem(Convert.ToString(row["id"]));
        item.SubItems.Add(Convert.ToString(row["descripcion"]));
        item.SubItems.Add(Convert.ToString(row["precio"]));

        lswProductos.Items.Add(item);  

    }
}

A modo de ejemplo en este caso se ha usado como origen de dato un DataTable cargado manualmente desde código:

private DataTable ObtenerDatos()
{
    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"] = "Prod 1";
    row["precio"] = 140;
    dt.Rows.Add(row);

    row = dt.NewRow();
    row["id"] = 2;
    row["descripcion"] = "Prod 2";
    row["precio"] = 30;
    dt.Rows.Add(row);

    row = dt.NewRow();
    row["id"] = 3;
    row["descripcion"] = "Prod 3";
    row["precio"] = 60;
    dt.Rows.Add(row);

    row = dt.NewRow();
    row["id"] = 4;
    row["descripcion"] = "Prod 4";
    row["precio"] = 110;
    dt.Rows.Add(row);
    return dt;
}

 

En este caso por cada ítem del origen de datos se recorrer en un ciclo del foreach y se van creando los ListViewItem que definen cada registro.

A cada ítem del ListView se le definen sus SubItems que representaran las columnas.

Debe aclararse que la primer columna en el ListView es representada por el propio ListViewItem, es por eso que no hay un SubItem que represente la columna “Id”.

 

Pasaje de ítems entre ListView


Una de las acciones mas comunes es la selección de ítems para su posterior trabajo con estos datos.

Es por eso que en el ejemplo se veras dos botones que permitirán pasar los registros entre controles.

 

private void btnAgregar_Click(object sender, EventArgs e)
{
    foreach(ListViewItem item in lswProductos.SelectedItems)
    {
        lswProductos.Items.Remove(item); 
        lswProductosSeleccionados.Items.Add(item);
    }
}

private void btnQuitar_Click(object sender, EventArgs e)
{
    foreach (ListViewItem item in lswProductosSeleccionados.SelectedItems)
    {
        lswProductosSeleccionados.Items.Remove(item);
        lswProductos.Items.Add(item);
    }
}

Como se observa en cada caso las listas son recorridas en sus ítems seleccionados, procediendo a la eliminación del registro y su posterior agregado en la segunda lista.

 

Edición del ítem seleccionado


Algo que será raro encontrar en el ListView, es que este no posee un método claro para poder detectar cual es el registro o ítem actualmente seleccionado, todas las propiedades hacen referencia a un conjunto de registros.

 

private void lswProductos_MouseDoubleClick(object sender, MouseEventArgs e)
{
    ListViewItem item = lswProductos.GetItemAt(e.X, e.Y);

    if (item != null)
    {
        txtId.Text = item.Text;
        txtDescripcion.Text = item.SubItems[1].Text;
        txtPrecio.Text = item.SubItems[2].Text;
    }
}

 

El evento MouseDoubleClick posee en sus argumentos dos propiedades que especifican las coordenadas del cursor, las cuales combinadas a el método GetItemAt() permiten seleccionar que ítem se ha pulsado, y con este la edición del mismo

 

[C#]
[VB.NET]

sábado, 20 de febrero de 2010

C# - JQuery - GridView, detectar selección checkbox

 

Introducción

La razón de este ejemplo es poder detectar la selección individual de cada checkbox introducido en una columna de la grilla y ante la marca de al menos un check poder habilita la acción de cierto un botón. Todo esto del lado del cliente.

 

Código JavaScript

Para esta operación nos ayudaremos de la librería jQuery.

La misma podrá apreciarse que ha sido incluida mediante la referencia de la primer línea del código del ejemplo.

<script src="js/jquery-1.4.2.min.js" type="text/javascript"></script>

<script language="javascript" type="text/javascript">

    $().ready(function() {


        $("#<%=GridView1.ClientID%> :checkbox").click(function() {

            var seleccionado = false;

            $("#<%=GridView1.ClientID%> :checkbox").each(function() {

                if (this.checked)
                    seleccionado = true;

            });

            var botondeshabilitar = $("#<%=btnEnviar.ClientID%>");
            
            if (seleccionado)
                botondeshabilitar.removeAttr('disabled');
            else
                botondeshabilitar.attr('disabled', 'disabled');

        })


    });

</script>

La instrucción $().ready(function(){..}) (línea 5) nos brindara la seguridad que el código que incluyamos allí dentro será ejecutado ni bien este disponible el objeto document de javascript.

Esto es importante ya que dentro de este estamos accediendo a los controles y es necesario hacerlo de forma segura.

Con $(..).click(function(){}), (línea 8) estamos seleccionado todos los checkbox que contenga el grdiview, y adjuntando el evento click, debe recordarse que este evento se agregara a cada control checkbox de forma individual.

Cada vez que es presionado un checkbox se evalúan todos los check en conjunto para saber si hay algún otro seleccionado o no, en caso de haber uno se deje habilitado el botón, en cado de no quedar ninguno se deshabilita.

La forma de recorrer cada ítem seleccionado $().each(function(){}), (línea 12), evalúa el estado del check de ese ítem en particular modificando la variable que se encuentra declarada fuera de esta función. Debe remarcarse que el “this” que se usa dentro del each es un objeto de javascript clásico, no es un objeto de jquery, es por eso que se usa directamente la propiedad “checked”, esta es de javascript, no de jquery.

Algo que seguro se preguntaran es porque en la líneas 8 y 12 se realiza la misma selección y no se reemplaza la segunda por un this. Esto se debe a que al adjuntar el evento al click este lo hace de forma individual, razón por la cual es this dentro del click representa un solo control checkbox, y no todos, haciendo necesario nuevamente la selección, para poder realizar el ciclo y evaluación de estado.

Hay que mencionar que es importante la forma en como se selecciona los controles de asp.net usando <%=%> (ejemplo línea 19), como se notara al renderizar un control de asp.net este cambia el id asignado por otro, para que tenga el control un identificador único, es por eso que lo correcto al trabajar con controles se utilice la propiedad ClientID.

[C#]
 

lunes, 15 de febrero de 2010

C# - [ASP.NET] TreeView búsqueda recursiva


Introducción
El ejemplo muestra como realizar una búsqueda recursiva.
En este se buscan los ítem que contengan en la descripción del nodo lo ingresado en un Textbox de búsqueda
Luego se carga un ListBox con los datos, la selección de un ítem es reflejada en el treeview
Carga del TreeView
La primer operación que se realizaría será la carga del árbol
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
        LoadTreeView();

}

private void LoadTreeView()
{
    TreeNode node1 = new TreeNode("Node 1");
    node1.ChildNodes.Add(new TreeNode("Node 1 - 1"));
    node1.ChildNodes.Add(new TreeNode("Node 1 - 2"));
    node1.ChildNodes.Add(new TreeNode("Node 1 - 3"));
    node1.ChildNodes.Add(new TreeNode("Node 1 - 4"));
    TreeView1.Nodes.Add(node1);

    TreeNode node2 = new TreeNode("Node 2");
    node2.ChildNodes.Add(new TreeNode("Node 2 - 1"));
    node2.ChildNodes.Add(new TreeNode("Node 2 - 2"));
    node2.ChildNodes.Add(new TreeNode("Node 2 - 3"));
    node2.ChildNodes.Add(new TreeNode("Node 2 - 4"));
    TreeView1.Nodes.Add(node2);

    TreeNode node3 = new TreeNode("Node 3");
    node3.ChildNodes.Add(new TreeNode("Node 3 - 1"));
    node3.ChildNodes.Add(new TreeNode("Node 2 - 4"));
    TreeView1.Nodes.Add(node3);
}
Búsqueda Recursiva
Una vez introducido el valor que se quiere buscar en los nodos del treeview, se acciona el evento del botón.
Mediante el método Find() se analizaría cada nodo comenzando por los definidos como padres (root) y en caso de detectar nodos hijos seguir de forma recursiva por cada rama del árbol añadiendo nuevos ítems en caso de que estos coincidan con el criterio de búsqueda.

protected void btnBuscar_Click(object sender, EventArgs e)
{
    List<TreeNode> lista = Find(txtBuscar.Text);

    lbItemsEncontrados.Items.Clear();

    foreach (TreeNode item in lista)
    {
        ListItem itemlist = new ListItem(item.Text, item.ValuePath);
        lbItemsEncontrados.Items.Add(itemlist);
    }
}

private List<TreeNode> Find(string param)
{
    List<TreeNode> list = new List<TreeNode>();

    foreach (TreeNode item in TreeView1.Nodes)
    {
        if (item.Text.Contains(param))
            list.Add(item);

        if (item.ChildNodes.Count > 0)
          list.AddRange(FindChild(item.ChildNodes, param));

    }

    return list;
}

private List<TreeNode> FindChild(TreeNodeCollection nodes, string param)
{
    List<TreeNode> list = new List<TreeNode>();

    foreach (TreeNode item in nodes)
    {
        if (item.Text.Contains(param))
            list.Add(item);

        if (item.ChildNodes.Count > 0)
            list.AddRange(FindChild(item.ChildNodes, param));
    }

    return list;
}
Selección de los ítems en la lista
El control ListBox donde se han cargado los valores encontrado en la búsqueda no provee una propiedad u opción para obtener los ítems que se han seleccionado.
Es por eso que ante un cambio en la selección es necesario recorrer todos los ítem de la lista y actualizar el estado de selección en el treeview.
Algo interesante para remarcar es el uso de la propiedad Value, en la búsqueda del nodo especifico. Si se analizo el código de la búsqueda recursiva se habrá notado que los items de listbox se han cargado usando la propiedad ValuePath del TreeView, la cual simplifica la selección de un nodo en concreto.

protected void lbItemsEncontrados_SelectedIndexChanged(object sender, EventArgs e)
{
    for (int i = 0; i < lbItemsEncontrados.Items.Count; i++)
    {
        ListItem item = lbItemsEncontrados.Items[i];

        TreeNode node = TreeView1.FindNode(Convert.ToString(item.Value));
        node.Checked = item.Selected;
        
    }

}
[C#]


[VB.NET]

domingo, 14 de febrero de 2010

C# - [WinForms] Instancia Única de Formulario

 

Introducción


La manipulación de las instancias de un formulario en algunas circunstancias puede no ser tan simple como aparenta.

Note en algunas oportunidades la necesidad que solo tener una única instancia, además de poder controlar las acciones o eventos que se ejecuten sobre este formulario, pero desde quien  realiza la apertura.

 

Ejemplo sobre Formularios  Simples


Para comenzar a ver este tema lo haremos en un primer ejemplo simple con formularios comunes.

Para esto tendremos un formulario que podrá denominarse Principal, el cual tiene dos botones, uno hará uso de instancias únicas, mientras que el segundo creara muchas instancias de ese formulario

 

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

    private Form2 form = null;

    private Form2 FormInstance
    {
        get
        {
            if (form == null)
            {
                form = new Form2();
                form.Disposed += new EventHandler(form_Disposed);
            }

            return form;
        }
    }

    void form_Disposed(object sender, EventArgs e)
    {
        form = null;
    }


    private void btnAbrirUnicaInstanciaForm_Click(object sender, EventArgs e)
    {
        Form2 frm = this.FormInstance;
         
        frm.Text = "Unica Instancia";
        frm.Show();

        // si el Formulario estaba abierto seguramente este en segundo plano
        // con esta linea hacemos que pase adelante
        frm.BringToFront();
    }

    private void btnAbrirInstanciaMultiplesForm_Click(object sender, EventArgs e)
    {
        Form2 frm = new Form2();

        frm.Text = "Instancia Multiple";
        frm.Show();
    }
}

El punto importante en este código y que marca la diferencia es el uso de la propiedad de nombre “FormInstance” esta propiedad permitirá aplicar la lógica que valide si ya existe una instancia creada para el formulario.

Si se analiza el código de la propiedad se notara que hace uso de una propiedad privada global al formulario, la idea es siempre crear y acceder al formulario a través de la propiedad y nunca hacer uso de la variable privada.

Es la propiedad la que provee del encapsulamiento de la lógica para determinar la reutilización de la instancia del formulario.

Hay un detalle adicional que por ahí noten como extraño, dentro de la propiedad al crear la instancia se adiciona el handler de un evento “Disposed”, seguramente se preguntaran cual es el objetivo de este evento.

Cuando se crea la instancia del formulario la variable privada contiene dicha instancia, pero al cerrarse el formulario la variable seguirá manteniendo el puntero cuando ya no sea una referencia valida, pues el formulario ha sido destruido, el tema es que nunca se reflejo esto en la variable privada. El uso del evento informa al formulario que realiza la apertura de la destrucción al cierre del formulario para que pueda quitar la referencia de la variable, en este caso simplemente igualándola a null.

Es importante también notar la diferencia en como se crea una instancia que es única, y cuando es múltiple, cuando se permiten muchas instancias solo basta con hacer uso del operador new cada vez que se quiere una nueva.

 

Formularios MDI


Como segundo ejemplo veremos como hacer esto mismo pero dentro de un formularios MDI, al cual le hemos agregado algunas otras tareas.

Básicamente la técnica es la misma, una propiedad en el formulario que valida la existencia de una instancia del formulario, pero en este caso en particular se han agregado algunos eventos adicionales que se querían trabajar desde el formulario que realiza la apertura.

Como se ver se hace uso de los eventos Load y FormClosed para poder informar en un StatusTrip de estas acciones.

 

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

    private frmSecundario form = null;

    private frmSecundario FormInstance
    {
        get
        {
            if (form == null)
            {
                form = new frmSecundario();
                form.MdiParent = this;

                form.Disposed += new EventHandler(form_Disposed);
                form.FormClosed += new FormClosedEventHandler(form_FormClosed);
                form.Load += new EventHandler(form_Load); 
                
            }

            return form;
        }
    }

    void form_Load(object sender, EventArgs e)
    {
        tslMensaje.Text = "Formulario abierto";
    }

    void form_FormClosed(object sender, FormClosedEventArgs e)
    {
        tslMensaje.Text = "Se ha cerrado el Formulario";
    }

    void form_Disposed(object sender, EventArgs e)
    {
        form = null;
    }


    private void instanciaUnicaToolStripMenuItem_Click(object sender, EventArgs e)
    {
        frmSecundario frm = this.FormInstance;

        // se varifica si el formulario no esta minimizado, en caso de estarlo
        // se lo cambia a un estado normal
        if (frm.WindowState == FormWindowState.Minimized)
            frm.WindowState = FormWindowState.Normal; 

        frm.Show(); 
    }

    private void instanciaMultipleToolStripMenuItem_Click(object sender, EventArgs e)
    {
        frmSecundario frm = new frmSecundario();
        frm.MdiParent = this;

        frm.Show();
    }
}

[C#]
[VB.NET]

domingo, 7 de febrero de 2010

C# – DataTable – Pasar Filas a Columnas y agregar filas adicionales

 

Introducción

Basado en una pregunta que he visto en uno de los foros arme unos ejemplo que explica como poder trabajar con DataTables.

Básicamente el planteo de las consultas se hacían porque era necesario realizar tareas que requieren de la iteración de datos existentes, para ser volcados en un nuevo conjunto de datos resultante.

se atacara dos aspectos en este articulo

  • como agregar una línea en blanco, según cierto corte de control
  • como unir dos conjuntos de datos, pero que estas sean representadas en columnas
 

Línea en Blanco

Como se parecía en el código es necesario ciclar cada ítem de los datos obtenidos y mediante un procesamiento o corte de control en cuyo ejemplo es representado mediante un asiento contable, el cual solo es a modo de ejemplo.

 

private void Form2_Load(object sender, EventArgs e)
{
    //se obtiene el conjunto de datos
    DataTable dtAsientos = GetDatos();

    //se crea el dataset resultado 
    DataTable dt = new DataTable();

    dt.Columns.Add("Asiento");
    dt.Columns.Add("Importe");

    // variables que actuaran como flag para el corte de control
    // en el ciclo
    string asientActual = "";
    double importe = 0; 


    foreach (DataRow row in dtAsientos.Rows)
    {

        if (string.IsNullOrEmpty(asientActual))
        {
            // la primera vez que entre esta sin inicializar la variable "asientActual"
            // por eso entrara por este pregunta del if

            asientActual = Convert.ToString(row["asiento"]);
            importe = Convert.ToDouble(row["Importe"]);
        }
        else if (Convert.ToString(row["asiento"]) == asientActual)
        {
            //si el asiento es el mismo solo acumulo el importe
            importe += Convert.ToDouble(row["Importe"]);
        }
        else
        {

            //aqui es donde se agregas la nueva fila
            DataRow blankRow = dt.NewRow();

            blankRow["Asiento"] = "";
            blankRow["Importe"] = importe.ToString("N2");
            dt.Rows.Add(blankRow);

            //se inicializa las variable al nuevo asiento
            asientActual = Convert.ToString(row["asiento"]);
            importe = Convert.ToDouble(row["Importe"]);
        }

        DataRow newRow = dt.NewRow();

        newRow["Asiento"] = row["Asiento"];
        newRow["Importe"] = row["Importe"];

        dt.Rows.Add(newRow);

    }

    // se agrega el registro que refleja el acumulado 
    // para el ultimo asiento, esto se hace fuera del foreach
    // ya que no se poden registro adicionales que agreguen el ultimo valor
    DataRow lastRow = dt.NewRow();

    lastRow["Asiento"] = "";
    lastRow["Importe"] = importe.ToString("N2");
    dt.Rows.Add(lastRow);

    //se carga el nuevo dataset en la grilla
    dataGridView1.AutoGenerateColumns = true;
    dataGridView1.DataSource = dt; 

}

En el código además se lleva un acumulado de los importes de cada registro, y justo en el momento de insertar la línea adicional es que se usa este importe para informar el total del asiento.

 

Pasar Filas a Columnas, con dos Datatable

en este ejemplo se observaran dos orígenes de datos, lo cuales serán procesador para obtener un único resultado final, pero en donde la unión no sea a nivel de registros, sino que se agregaran las columnas de uno a continuación de las otras.

 

private void Form1_Load(object sender, EventArgs e)
        {
            //
            // obtengo los datos que seran unidos postermente
            //
            DataTable dt1 = GetDatos();
            DataTable dt2 = GetDatos2();

            //
            // se crea un nuevo datatable resultande de la union
            //
            DataTable dt = new DataTable();

            dt.Columns.Add("Columna1");
            dt.Columns.Add("Columna2");
            dt.Columns.Add("Columna3");
            dt.Columns.Add("Columna4");
            dt.Columns.Add("Columna5");


            //
            // se recorre el primer origen de datos
            // en este se insertan los registros 
            //
            foreach (DataRow row in dt1.Rows)
            {
                DataRow newRow = dt.NewRow();

                newRow["Columna1"] = row["Columna1"];
                newRow["Columna2"] = row["Columna2"];

                dt.Rows.Add(newRow);  

            }

            //se recorre el segundo origen de datos
            for (int i = 0; i < dt2.Rows.Count; i++)
            {
                DataRow copyRow = null;

                // se pregunta si el indice del registro existe
                // o debe crearse uno adicional, ya que el segundo origen de datos
                // posee mas registro que el primero
                if (dt.Rows.Count <= i)
                {
                    copyRow = dt.NewRow();
                    dt.Rows.Add(copyRow);
                }
                else
                    copyRow = dt.Rows[i];

                DataRow originalRow = dt2.Rows[i];

                copyRow["Columna3"] = originalRow["Columna3"];
                copyRow["Columna4"] = originalRow["Columna4"];
                copyRow["Columna5"] = originalRow["Columna5"];
            }


            dataGridView1.AutoGenerateColumns = true; 
            dataGridView1.DataSource = dt; 
            
        }

 

En el código se observar que el dataset de resultado al crearlo se le definen las columnas de los dos dataset que son usados de origen de datos.

El primer ciclo por contar con el dataset resultado sin valores simplemente vuelva los registros allí.

El segundo es algo mas complejo ya que debe usas los índices de los registros creados en el paso anterior.

Además debe verificar si es que este segundo grupo de registro es mayor que el anterior de agregar las filas adicionales.

Conclusión

Para trabajar con DataTables para manipular la información que estos contienen y poder forma un conjunto de datos adicional que represente correctamente la datos en la disposición que se necesita, es necesario mencionar que un punto a tener en cuenta y esta referido al orden en se que proveen los datos iniciales.

Como se verán en ambos ejemplo el orden es importante ya que este determina el éxito del corte que se aplica al recorrer y procesar cada registro.

[C#]