Mostrando entradas con la etiqueta GridView. Mostrar todas las entradas
Mostrando entradas con la etiqueta GridView. Mostrar todas las entradas

lunes, 20 de junio de 2011

Configurar TimeOut a un SqlDataSource

Pequeño problemilla que me tenía loco. Tengo un Grid que busca en la base de datos con un SqlDataAdapter metiéndole el SQL a pelo por medio de un string.

En mi base de datos de desarrollo va bien porqué tengo pocos resultados, pero en la de preproducción empieza a dar TimeOut porque hay un montón de Join y Group y guarrerías varias y tarda más de los tres segundos que tiene por defecto el SqlDataSource como  TimeOut.

Solución Googleando un poquillo:

    protected void SqlDataSource_Selecting(object sender, SqlDataSourceSelectingEventArgs e)
{
e.Command.CommandTimeout = 300;
}


Ya tá, le he puesto cinco minutos y santas pascuas.

martes, 7 de diciembre de 2010

AsyncFileUpload + GridView. El infierno de un postback.

EDITADO: Me dá un problema, para mí, insalvable que es que me hace un doble postback. Cuidadin con esto.
En este día de puente, en donde la inmensa mayoría se están tomando unas merecidas vacaciones, heme aquí disfrutando como un enano con la suma del control AsyncFileUpload + un GridView.
Lo primero, aviso que la estructura de utilizar un datatable, seguro que se puede mejorar. De echo seguro que refactorizaré para mejorar. Pero el error que me ha consumido casi cinco horas de trabajo ha sido el siguiente: AsyncFileUpload NO hace postback en el evento de servidor AsyncFileUpload_UploadedComplete…(demonios!!).
El escenario es que voy a leer un fichero de texto del cliente, y lo voy a introducir directamente en un GridView. Para ello lo primero es saber que el AsyncFileUpload tiene tres eventos de cliente y uno de servidor. Por lo cual conformaremos nuestro control así:
<asp:AsyncFileUpload ID="AsyncFileUpload" Width="865px" 
OnClientUploadError="uploadError"
OnClientUploadStarted="StartUpload" 
OnClientUploadComplete="UploadComplete"
OnUploadedComplete="AsyncFileUpload_UploadedComplete"
CompleteBackColor="Lime"
UploaderStyle="Modern" ErrorBackColor="Red" 
UploadingBackColor="#66CCFF" runat="server"  />

Los Javascript que comprende la actuación de los tres primeros eventos en cliente son (*No consigo utilizar el Control.IdClient en un js)

// Funciones de AsyncFileUpload
function uploadError(sender, args) {
document.getElementById('ctl00_chPrincipal_lblStatus').innerText = args.get_fileName(),
"<span style='color:red;'>" + args.get_errorMessage() + "</span>";
}

function StartUpload(sender, args) {
document.getElementById('ctl00_chPrincipal_lblStatus').innerText = 'Uploading Started.';
}

function UploadComplete(sender, args) {
document.getElementById('ctl00_chPrincipal_lblStatus').innerText = 'Fichero subido corréctamente';
 __doPostBack('tctl00$chPrincipal$UpdatePanel', '');
}

Y el método que captura el evento de servidor es el siguiente:

protected void AsyncFileUpload_UploadedComplete(object sender, AjaxControlToolkit.AsyncFileUploadEventArgs e)
{
DataTable listaMatriculas = new DataTable();
listaMatriculas.Columns.Add("Matricula", typeof(string));
StreamReader lectorDelFichero = new StreamReader(AsyncFileUpload.FileContent);

string linea;
do
{
linea = lectorDelFichero.ReadLine();
DataRow fila = listaMatriculas.NewRow();
fila["Matricula"] = linea;
listaMatriculas.Rows.Add(fila);
}
while (linea != null);

GridMatriculas.DataSource = listaMatriculas;
GridMatriculas.DataBind();

}

El problema es que por mucho que le dijera que hiciera el DataBind del Grid, este no se actualizaba. Lo peor es que como no se actualizaba pensé que era un problema de la fuente de datos y salte de ArrayList, a List y luego a var de Linq… y nada, no se veía.


Desesperado, le puse un botón para recargar y, asombroso, eso sí que funciona. Lo cual me llevó a la idea de poner un UpdatePanel.Update()… lo cual tampoco sirvió. Entonces, me dije, esto es un problema de postback y me puse a mirar por San Google hasta llegar al excelente blog Schelfaut.NET (Sven Schelfaut's technical blog) en donde está la solución que he puesto en negrita. En el evento de cliente  UploadComplete, se fuera un postback sobre el UpdatePanel que contiene al GridView. Y con esto todo va como la seda.


La siguiente refactorización urgente es poder invocar directamente al IdClient  de un control desde un javascript que viene de una librería js. Y en vez de tener que construir una DataTable, poder utilizar una lista. Ya que StreamReader no soporta Linq.


Espero que os sea útil.

jueves, 1 de julio de 2010

GridView, onclick condicionales.

Gabriela me solicita una ayudita en relación al post Seleccionar por filas de un GridView y esto me da para un post sobre cómo tratar de forma condicional diferentes eventos de click en un mismo GridView.

Es decir, tener dos columnas del grid que tienen botones (o combos en el caso de gabriela)  y que la aplicación sepa que hacer en cada caso.

Para ello lo primero es entender que todo lo que ocurre en un GridView lanza el evento GridViewCommand. Aprovechándonos de ello declaramos en nuestro Grid una columnas con un botón en donde definiremos el comando que queremos invocar.
<asp:TemplateField>
<ItemTemplate>
<asp:ImageButton ID="imgbSeleccionar" runat="server" CausesValidation="false"
        CommandName="Seleccionar" 
        CommandArgument='<%# Eval("id")%>' 
        ImageUrl="~/imagenes/icon_06.png" ToolTip="Seleccionar." />
</ItemTemplate>
</asp:TemplateField>

Lo importante de este código son dos partes. Por un lado el CommandName que el que le va indicar al evento RowCommand el tipo de acción que hemos lanzado. (Acordaros de usar una nomenclatura clara y autoreferente).

Y por otro lado el CommandArgument que es en donde le vamos a mandar la información necesaria para realizar la acción que hemos lanzado.

En el código tendremos el evento RowCommand en donde capturaremos el CommandName y así tendremos la acción lanzada:


public void Grid_RowCommand(object sender, GridViewCommandEventArgs e)
{
switch (e.CommandName)
{
case "Seleccionar":
id = e.CommandArgument.ToString();
SeleccionaObjeto(id);
break;
default:
break;
}
}



Seguro ya os habréis dado cuenta que con este sistema puedes tener todas las acciones que se te ocurran, he incluso varias acciones en una misma columna.

Es importante recordar que existen tres acciones predefinidas que son: “Select”, “Edit” y “Delete” y que si usáis estos CommandName, deberéis gestionarlos en sus respectivos eventos u os dará un bonito error.

En resumen, Gabriela, espero que te haya sido de ayuda.

miércoles, 28 de enero de 2009

Ordenar número de tipo String (CHAPUZA)

Ya estamos terminando la pequeña aplicación de inventario de activos en la que llevo metido los dos últimos meses. Y me he encontrado con un problema de compleja resolución que al final he resuelto con una enorme CHAPUZA.

Tengo un campo identificador de nivel que es un string y que es numérico con decimales del 0 al 10. Algo como:

1
2
3
3.4
5
7
10
10.1

El problema es que cuando lo recupero de la base de datos y lo cargo en un grid ordenado por este campo, el 10 se me pone justo detrás del 1, seguido de 10.1.

La primera solución, conviértelo en un número, no funciona porque el IIS está en un equipo en castellano y la BD en un equipo en ingles. O sea, que me tengo que liar a cambiar las Culture y si la combinación no es la que yo quiero, entonces todo casca. Y cómo se acaba la cosa en dos días, no me he molestado en buscar cómo recuperar el idioma del IIS y del SQL.

La segunda, ponerle ceros por delante, me ha provocado empezar a escribir código y no hacer que funcionará del todo bien. Y eso que esta creo que es la solución correcta.

Y volviendo de comer, he dado con una solución que es una CHAPUZA pero funciona muy bien en este caso específico con unta tabla que prácticamente no va a cambiar este campo nunca.

Le he puesto un espacio delante de todos los números a excepción de los que superan la decena. Y al cero le he puesto dos espacios… y yá tá. (jeje, sí que funciona perfectamente en TODO).

viernes, 23 de enero de 2009

Seleccionar por filas en un GridView

Hay días en que uno se siente "con la musa" y se siente bastante feliz por esos pequeños desarrollos que hacen la vida más fácil.

Mi control preferido, el GridView, me ha dado una nueva alegría al conseguir el siguiente requerimiento del cliente:

Dado un, que se pueda pinchar en una fila y que se abra la ventana de detalle.

En principio esto lo hacíamos con los dos consabidos botones de edición y borrado. Pero el cliente nos pidió si se podía realizar pinchando en la fila en sí.

Me dí un garbeo por San Google y entre un buen número de páginas y técnicas me quede con esta.


Protected Sub GridView_RowDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles GridView.RowDataBound
'' Si es una fila de datos.
If e.Row.RowType = DataControlRowType.DataRow Then
'' Cargo el comportamiento en cada fila.
e.Row.Attributes.Add("onclick", Page.ClientScript.GetPostBackEventReference(sender, "Acción$" & CType(e.Row.FindControl("idcontrol"), Label).Text))
'' Le indico que en utilice el cursor de selección cuando se pase por encima.
e.Row.Style.Add("cursor", "hand")
End If
End Sub



Como ves, el código es simple. En el evento que se lanza cada vez que cargo los datos en una fila del Grid, le añado un evento "onclick" el cual hace un postback que será capturado por el evento RowCommand del propio Grid.

Aquí, las dos cosas que hay que tener cuidadín son las que he resaltado en negrita. Es decir, le tienes que pasar el nombre del comando (CommandName) para poder tener el control de lo que va a ocurrir al pulsar en la fila. (si pones edit o delete o select o etc., tendrá el comportamiento por defecto de cualquier Grid). Y le tienes que indicar (por lógica) cual es el control en el que has cargado el identificador de los datos sobre los que quieres funcionar. En este caso, el identificador en la base de datos de un registro.

Por lo cual en el GridView tendras que añadir o seleccionar la columna que va a proveer el indice de identificación. En este ejemplo, es un Id en una columna oculta y es algo así:


<asp:TemplateField HeaderText="id" Visible="False">
   <ItemTemplate>
      <asp:Label ID="idControl" runat="server" Text='<%# eval("id").toString %>' runat="server" />
   </ItemTemplate>
</asp:TemplateField>


Y ahora me voy a mi evento RowCommand de mi Grid y le añado un condicional para capturar el evento que he añadido en el onlick.


Protected Sub GridView_RowCommand(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewCommandEventArgs) Handles GridView.RowCommand
Select Case e.CommandName
Case "Acción"
'' Aquí hace cosas
End Select
End Sub


Jo!! Que bien funciona... peeeeero (siempre hay uno) y aquí viene mi aportación al mundo del desarrollo :), me veo que se superpone el comportamiento de la fila sobre el comportamiento esperado del botón de borrar (el cual no me quiero llevar al detalle). arrggg!!.

Pues, dándole unas poquillas vueltas, me doy cuenta que así como trato sobre la fila entera, puedo hacerlo un poco más granular y aplicar el comportamiento sobre las celdas, dejando fuera las que yo quiera que se comporten de otra forma.

Solución en el siguiente código en donde solamente he cambiado la linea de carga del atributo onclick por un bucle que carga dicho atributo a cada una de las filas excepto la última, que es donde tengo situado el botón de borrado.


Protected Sub GridView_RowDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles GridView.RowDataBound
Dim numCeldas As Integer
'' Si es una fila de datos.
If e.Row.RowType = DataControlRowType.DataRow Then
''Cuento el número de celdas que componen la fila.
numCeldas = e.Row.Cells.Count

'' Cargo el comportamiento en cada celda.
'' En este caso en todas menos en la última que es la de borrado.
For x As Integer = 0 To numCeldas - 2
e.Row.Cells(x).Attributes.Add("onclick", Page.ClientScript.GetPostBackEventReference(sender, "Accion$" & CType(e.Row.FindControl("IdControl"), Label).Text))
Next

'' Le indico que en utilice el cursor de selección cuando se pase por encima.
e.Row.Style.Add("cursor", "hand")
End If
End Sub


Y lo mejor de todo, de todo es que funciona de muerte. Perfecto y de una forma muy poquito vista en la Web.

jueves, 13 de marzo de 2008

Parámetros mútiples desde un GridView

Ciertamente la programación es una actividad en donde la inspiración va de la mano de la experiencia. Y hay días en donde “te baja la idea del cielo” y encuentras otra forma nueva, por lo menos para ti, de hacer las cosas.

Siempre me habían parecido espantosas las cadenas de parámetros que se envían desde un GridView para editar una fila (siempre que no uses programación visual). Por ejemplo, si tengo un grid como el de la imagen, y quiero mandar información a la ventana de al lado según la fila seleccionada tengo que hacer una “guarrería” como esta:
<asp:ImageButton ID="imgbImageButton" runat="server" CausesValidation="false" 
CommandArgument='<%# Eval("idVehiculo") & "@" & eval("Empleado").toString%>'
Para después con un string.split, cargar cada uno de los argumentos en un array y enviarlo al método adecuado.
Protected Sub gvDatosTabla_RowCommand(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewCommandEventArgs) Handles gvDatosTabla.RowCommand
Dim parametro() As String = Split(e.CommandArgument, "@")
lblDatosDni.Text = parametro(0)
lblDatosEmpleado.Text = parametro(1)
tbDatosImporte.Text = parametro(2)
End Sub
A causa de la lógica de negocio, me veo obligado a que esta ya larguísima cadena se me multiplique por tres y comienzo a comerme el tarro de cómo mejorar simplificar el código.
Y pienso… si tuviera un identificador único en la tabla no habría mayor problema.
A lo cual le he añadido, en la clase que recupera la tabla, un método que le añade una columna en donde almaceno un valor numérico incremental que se inicia en 1.
Private Function idToTable(ByVal tabla As DataTable) As DataTable
Dim id As Integer = 1
tabla.Columns.Add("IdTabla", Type.GetType("System.Int32"))
For Each filas As DataRow In tabla.Rows
filas("IdTabla") = id
id += 1
Next
Return tabla
End Function
Teniendo ya un identificador único, cargo la tabla en una propiedad que almacena en el gridView y ya tengo persistencia.
Public Property tabla() As DataTable
Get
Return ViewState("tabla")
End Get
Set(ByVal value As DataTable)
ViewState("tabla") = value
End Set
End Property
Y ya puedo cargarlo en el dataView para hacer el bind del GridView, teniendo cuidado de además de las columnas que se deben mostrar, incluir una columna oculta con el identificador único.
<asp:BoundField Visible="false" DataField="IdTabla" />
Ahora cuando selecciono una fila para editar ya no tengo que pasar los datos vía CommandArgument, simplemente envío el identificador único que he añadido. Y encuentro todos los valores de esa fila haciendo un select en la tabla.
Protected Sub gvDatosTabla_RowCommand(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewCommandEventArgs) Handles gvDatosTabla.RowCommand
Dim filas() As DataRow = tabla.Select("IdTabla=" & e.CommandArgument)
If filas.Length > 0 Then
Dim fila As DataRow = filas(0)
lblDatosDni.Text = fila("dni") lblDatosEmpleado.Text = fila("empleado")
lblDatosImporte.Text = fila("Importe")
Else
mensaje("No existe la fila seleccionada")
End If
End Sub
Otra enorme ventaja que tengo al usar este método es que puedo acceder a los datos de la fila seleccionada por medio del nombre del campo/columna, en vez del críptico y horroroso sistema de índices del Array.

martes, 22 de enero de 2008

Confirmación de borrado en un Grid más avanzado.

Hace casi un mes escribí la siguiente entrada que trataba de un método limpio y sencillo de visualizar una ventana de confirmación en javascript desde cualquier control .net que aceptara el oncClientClick.

Hoy traigo la versión más avanzada para realizarlo en un GridView en donde tengamos en una columna un texto descriptivo y en otra un enlace para borrar la fila.
La teoría es añadir al enlace de borrado el parámetro OnClientClick invocando el Javascript que nos saca la alerta y que es:

function fnConfirmacionBorrado(value)
{
return window.confirm('El registro ' + value + ' va ha ser eliminado, ¿Desea continuar?');
}


Despues hacemos el método que borra al entrada físicamente de la fuente de datos y que aquí no voy a describir por ser cansinamente repetitivo. Al que llamaremos borra().

Por último en el momento en que .NET va a ir rellenando fila a fila el GridView, incluiremos el OnClientClick introduciendo el valor de la columna con la descripción como el mensaje ‘value’ que le enviaremos a la función javascript.

Protected Sub gvListado_RowDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles gvListado.RowDataBound

‘ Recuperamos el enlace de eliminar.
Dim eliminar As LinkButton = CType(e.Row.FindControl("lnkEliminar"), LinkButton)

‘ Recuperamos la etiqueta con la descripción
Dim descripcion As Label = CType(e.Row.FindControl("lblDescripcion"), Label)

‘ comprobamos que no sea nothing para que no falle.
If Not IsNothing(eliminar) Then
eliminar.OnClientClick = "return fnConfirmacionBorrado('" & descripcion.Text & "');"
End If

End Sub


Es importante tener en cuenta el detalle que el javascript nos tiene que devolver un valor true o false para invocar o no la funcion borra(), para ello debemos de ponerle el return antes del nombre de la función javascript que nos muestra la ventana de confirmación.

Por último el GridView estaría construido de la siguiente forma:

<'asp:gridview id="gvListado" runat="server">
<'columns>
<'asp:templatefield>
<'headertemplate>Descripción <'/headertemplate>
<'itemtemplate>
<'asp:label id="lblDescripcion" runat="server" text="<%# eval("") %>" />
<'/itemtemplate>
<'/asp:templatefield>
<'asp:templatefield>
<'itemtemplate>
<'asp:linkbutton id=" lnkEliminar " onclick="Borrar" runat="server" text="Eliminar" causesvalidation="false">
<'/itemtemplate>
<'/asp:templatefield>
<'/columns>
<'/asp:gridview>

martes, 4 de diciembre de 2007

Confirmación de borrar en un Grid.

Este truquito lo he visto en un par de cientos de páginas y blog sa través del mundo mundial, pero no me puedo resistir a publicarlo ya que me parece tan simple, tan bonito y tan cómodo que merece la pena dedicar estos minutos en plantarlo a los cuatro lectores que siguen este blog.

Tengo un dataGrid , o gridView, y le he puesto tremendo botonazo para cargarme una fila. Pero haciendo pruebas me encuentro con que he hecho desaparecer media hora de trabajo que me costó introducir un test de pruebas (es una aplicación para gestionar test de autoescuela) sin tan siquiera avisarme de la barbaridad que iba a hacer.

Me acuerdo que ya he visto la solución por algún lado y, efectivamente, está en múltiples sitios pero creo que puedo mejorar la comunicación y explicarlo aún más sencillo.

En javascript hay una serie de métodos que abren una ventana de alerta. Uno de ellos se llama “confirm();” que abre la típica ventana de ¿Estás seguro?

Este caso específico lo he implementado para un botón de un grid, pero nos vale para cualquier control tipo botón o similar.

asp:button id="Button1" runat="server"
CausesValidation="False"
CommandName="Delete"
Text="Borrar"
CssClass="boton"
OnClientClick="return confirm('¿Está seguro de borrar esta Pregunta?');" />


Como vés es un botón normal y corriente, con un CommandName al estar integrado en un Grid. De aquí solamente hay que quedarse con la copla de las partes subrayadas.

OnClientClick”, que nos permite que este comportamiento suceda en el cliente, “return” que le devuelve al cliente si el usuario ha aceptado o no, “confirm()” en donde le decimos que nos abra una ventana de confirmación y el texto de la pregunta.

Por último esto no funcionaría a menos que le digamos que no valide el control, es decir “CausesValidation=false”.

Esto lo que consigue es que si el cliente pulsa aceptar, al retornar true se ejecuta el postback del botón y todo lo que implica. Vamos, en este caso borra la fila. En el caso que se retorne false entonces no realiza el postback y todo se queda como está.

Si quieres la información detallada del porqué pásate por este foro en donde RootK lo explica muy bien.