viernes, 23 de enero de 2009

Leones o lobos: ¿cómo es tú organización?

[WARNING: post de desahogo]

¿Quién no se ha pasado alguna tarde muerta viendo documentales de naturaleza?

El mundo animal es fascinante y es curioso ver cómo se organizan las diferentes especies para lograr su objetivo: la supervivencia. Pongamos 2 ejemplos, los leones y los lobos.

Los leones

Los leones viven en la sabana, espacios abiertos y (en la época de lluvias) grandes manadas de gacelas, ñus y antílopes (vamos, comida) a su disposición. Se organizan en clanes, con uno o varios machos (aunque sólo uno es el dominante) y unas 5 o 6 leonas.

Las leonas son las encargadas de cazar y cuidar a las crías. Suele haber una leona veterana (no siempre la de más edad) que es la que organiza la caza, la que selecciona el objetivo y la que inicia el ataque. Esta leona es la que más probabilidades tiene de salir herida del choque, ya que es la que ataca cuando la presa no está herida ni cansada.

Por su parte los leones ... pues no hacen gran cosa, se pasan durmiendo entre 16 y 20 horas al día, son incapaces de cazar y en el momento que las leonas traen lo que han conseguido (con mucho esfuerzo y dejándose la piel en ello) van los leones y se quedan con la mejor parte.

¿Es esto justo? No, no es justo, es la ley del más fuerte.

¿Cuál es entonces la función de los leones? Básicamente, defender el territorio cuando llegan otros leones jóvenes a ocuparlo. Cuando un león se hace adulto y en su clan ya hay un macho dominante lo que tienen que hacer es ir a ocupar el territorio de otro clan. Esto sólo hay una forma de hacerlo y es venciendo al macho dominante del otro clan. Luchan y el que gane se queda el territorio. Si el que gana ya era el dueño del territorio, no pasa nada, todo sigue igual. Pero si el que gana es el macho joven que viene de otro clan, entonces se queda con todas las leonas y mata a todas las crías que haya. Esto lo hace por pura supervivencia, ya que esas crías son del anterior macho dominante y lo podrán poner en peligro en el futuro.

Cuando un león pierde un combate por el territorio, es desterrado y suele morir de hambre en pocos días, porque depende absolutamente de las leonas para conseguir comida.


Los lobos

Los lobos suelen vivir en bosques y terrenos más escarpados, con pocas presas a su disposición. También se asocian en clanes, con territorios muy marcados, suele haber varios machos y hembras, con un macho dominante que es el que guía al clan, pero también es el que organiza la caza y la defensa del territorio.

En un clan de lobos, todos cazan y la comida se reparte entre todos. Todos defienden el territorio y todos tienen, más o menos, las mismas obligaciones. Aunque se tiene la creencia de que los lobos son carroñeros, únicamente cazan animales sanos, y a veces se ven obligados a cazar animales más grandes que ellos, lo que entraña un gran peligro ya que suelen salir heridos del choque y sin sacar beneficio.

A veces uno o varios lobos abandonan el clan, pero como siempre han compartido las tareas de caza con sus compañeros, son capaces de sobrevivir y pueden llegar a formar su propio clan.



Las organizaciones

No sé si habéis visto la similitud que quería crear entre cómo se organizan los animales con la organización de las empresas, pero por si acaso me explico:

Hay empresas que son leones, hay un león que manda y que es el que se lleva el mérito de todo, pero son las leonas las que trabajan, se dejan la piel y ven como es el león el que se lleva la mejor parte de lo obtenido. Este primer caso se suele dar más en las grandes empresas, con varios leones defendiendo su territorio y sus leonas del ataque de otros machos jóvenes.

Los leones ocupan los puestos de gestión, ya sea dirigiendo proyectos o departamentos enteros con otros leones a sus órdenes. A veces llega otro león más joven y los echa de su territorio, y como pierde sus leonas es incapaz de sacar nada de trabajo adelante ya que es incapaz de cazar.

Hay otras organizaciones que son como un clan de lobos, todos reman en la misma dirección y, más o menos, el trabajo y el beneficio se reparten entre todos por igual. Suele darse más en empresas pequeñas y suele ser más gratificante trabajar en ellas.



A mí desde siempre me gustan más los lobos, pero por desgracia ahora estoy en un clan de leones ... y me toca cazar :(

martes, 20 de enero de 2009

Nada que añadir

El que trabaje en el desarrollo de software y no se vea reflejado en esto es que o es una rata o tiene una patata cocida en vez de corazón:

Fuckowski, memorias de un ingeniero

GRANDE Fuckowski, muy grande ;)




PD: es un tocho pero no tiene desperdicio, por favor, leerlo hasta el final.
PD2: sí, es de hace tiempo, pero Stairway to Heaven también y no deja de ser genial :D

lunes, 19 de enero de 2009

He leído: C# 3.0 Coobook de Jay Hilyard y Stephen Teilhet


Título: C# 3.0 CookBook
Autores: Jay Hilyard y Stephen Teilhet
Editorial: O'Reilly
Idioma: Inglés
ISBN-10: 059651610X
ISBN-13: 978-0596516109
Páginas: 886
En la web de O’reilly
En Amazon

Me gustó: este no es un libro de programación al uso, no habla sobre el CLR, la orientación a objetos y los ensamblados, sino que es un libro de “recetas”, sí, recetas como las de cocina de toda la vida. Por ejemplo, pongamos que tenemos que enviar ficheros por FTP usando C# y no tenemos ni idea, pues lo buscamos en el libro y con un poquito de suerte lo tendremos explicado, con su ejemplo de código y en muchos casos, diferentes formas de hacerlo.


Alguno diréis, “pues si no sé hacer algo lo busco en Internet”, y posiblemente lo encontréis mejor o peor explicado pero lo encontraréis. Así pues, ¿qué ofrece este libro? Básicamente que está muy bien explicado, abarca muchos temas y está muy bien organizado. Además, siempre te puede salvar de un problema si te quedas sin conexión :P


No me gustó: últimamente estoy teniendo mucha suerte con los libros que compro porque no les suelo encontrar muchas pegas, y eso cuando te gastas 40 o 50 € en algo se agradece y mucho :) A este libro tampoco le puedo poner muchos peros, igual podrían profundizar más en las explicaciones, pero creo que se perdería la esencia del libro.


Le falta: tratar temas como WCF (Windows Communication Foundation) y WF (Windows Workflow Foundation), pero tampoco es algo muy habitual y hay otros libros que los tratan en profundidad.


Lo recomiendo para: tenerlo siempre a mano porque nos puede sacar de más de un apuro. Pero ojo, no es un libro para aprender, es más para desarrolladores con una cierta experiencia que necesitan una solución para un problema puntual.

viernes, 16 de enero de 2009

Trasteando con Windows 7 (Primer contacto)

En un momento de aburrimiento máximo se me dio por instalar la beta de Windows 7 en una máquina virtual (que la cosa no está para tener equipos de prueba sólo para las betas) y tengo que reconocer que lo poco que lo llevo probando, no me disgusta. Si os aburrís tanto como yo, lo podéis descargar desde esta página.

Para mí, y seguramente para mucha más gente, el cambio será directo de Windows XP a Windows 7, así que voy a obviar las comparaciones con Vista, más que nada porque lo he usado muy poco y no tengo elementos de juicio reales para compararlos, vamos, que el trabajo del día a día lo hago con XP y con Vista no he pasado de abrir 4 o 5 aplicaciones. Además, sería muy fácil decir que Windows 7 es el Service Pack 2 de Vista (que no lo es, porque habrá SP2 de Vista), y que ahora que sale la versión nueva, veremos todas las bondades de Vista (no creo, y para eso ya está el proyecto Mojave que como operación de márketing no está nada mal :P).

Bueno, vamos al grano, instalar Windows 7 en una máquina virtual es bastante limpio y rápido, me parece que con 4 o 5 clicks ya lo tienes instalado :)

La primera impresión es que no hay casi cambios con respecto a Vista (vuelvo a decir que lo he usado muy poco) pero en un primer vistazo nos damos cuenta de algunas novedades:

- La barra de tareas:
Hay un rectángulo a la derecha que es para mostrar el escritorio. Creo que fue lo primero en que me fijé y lo veo bastante útil.

Las aplicaciones ancladas a la barra cuando le das con el botón derecho muestran diferentes opciones, como por ejemplo, el Explorador de Windows muestra accesos directos a Mis Documentos, Mis Imágenes o los últimos directorios usados.

Cuando abres una aplicación se muestra en la barra de tareas, pero únicamente el icono, ya no sale el título como hasta ahora. Además si tenemos varias instancias de la misma aplicación abierta se mostrará un único icono en la barra, y al pasar el ratón por encima veremos cada una con su título.

Los iconos en la barra de tareas (estén o no anclados) se pueden mover, con lo que los podemos ordenar a nuestro gusto. Esto parece una tontería, pero por lo menos a mí me parece bastante útil cuando se tienen unas cuantas aplicaciones diferentes abiertas (quién no ha abierto nunca más de 40 aplicaciones a la vez :P)

- El Action Center:
Se trata de un sitio en el que está centralizada la seguridad y el mantenimiento del equipo. Tiene un acceso directo en el área de notificación (abajo a la derecha, donde el reloj) que va mostrando si tenemos alguna acción pendiente de realizar, por ejemplo, si no tenemos antivirus.

- La UAC:
La UAC (User Account Control) permite ahora 4 niveles de notificación, desde el que te avisa por cada cambio en la configuración de Windows hasta el que sólo notifica unos pocos cambios. Todos aquellos a los que no les gusta la UAC de Vista supongo que pueden estar contentos.

- Snipping Tool:
Es una herramienta para hacer capturas de pantalla, y funciona realmente bien, porque te permite seleccionar porciones de pantalla y una vez capturada abre un editor para añadir trazos y así resaltar una zona o poner una anotación.

- Sticky Notes:
Es la típica aplicación de Post-its que casi todos tenemos instalada en nuestro equipo y con Windows 7 ya viene integrada con el sistema operativo.

- Funcionamiento general:
A falta de probarlo más en profundidad tengo que decir que la beta va bastante bien, no me ha dado ningún problema ni cuelgue, aunque el consumo de recursos es, en algunos momentos, exagerado. Le he quitado algunos de los efectos gráficos de las ventanas y la cosa mejora bastante, pero bueno, también lo estoy probando en una máquina virtual con 512 MB de RAM, así que no me espero mucho más :)


Un poco fuera de lo que es el funcionamiento de Windows 7, para poder hacer unas pruebas de trabajo “real” se me dio por instalar el Visual Studio 2008 y el Management Studio para el SQL 2005 Express que lo acompaña. Con el Visual Studio no hubo ningún problema, pero el Management Studio sigue teniendo el mismo problema de instalación que en Vista (cosa bastante normal), aunque te salta la UAC antes para pedirte permisos para ejecutarlo, la única forma de hacerlo es ejecutándolo en un entorno de administrador, como se explica aquí.

Según vaya haciendo más cosas os iré informando sobre mi impresiones con Windows 7, por ahora no me defrauda pero realmente aporta muy poco al mundo de los sistemas operativos.



Bola extra: Como curiosidad, el pez que aparece en el fondo de pantalla por defecto se llama Betta splendes y supongo que es un tipo de guiño a que se trata de una versión Beta del producto.

jueves, 15 de enero de 2009

He leído: Scrum and XP from the Trenches (How we do Scrum) de Henrik Kniberg




Título: Scrum and XP from the Trenches (How we do Scrum)
Autor: Henrik Kniberg
ISBN: 978-1-4303-2264-1
Páginas: 130

El original en inglés: en InfoQ
Si, como yo, lo preferís en papel: en lulu.com
Y para los que no podáis con el inglés: versión en castellano

Tengo que reconocer que este libro lo leí primero en formato digital, pero por esa extraña afición que tengo a los libros, no pude resistirme tenerlo en papel :)

Me gustó: ya al principio del libro el autor aclara que no quiere exponer “la” forma de aplicar Scrum sino “una” forma de aplicar Scrum, una forma válida pero no la única.

En menos de 130 páginas se consigue explicar el día a día del trabajo con Scrum . Es un libro mucho más práctico que teórico, se explican las bases muy por encima y se centra en las cosas que realmente acaban siendo útiles, como por ejemplo, cómo hacer el daily scrum o cómo organizar el “muro del product backlog” para que cada miembro del equipo pueda ver en todo momento el trabajo realizado y el que queda pendiente (por supuesto que hay herramientas para hacer esto, pero siempre queda mejor en soporte físico).
No me gustó: la calidad de las fotos en la versión impresa es bastante pobre, pero bueno, es un mal menor y al precio que compré el libro tampoco me puedo quejar mucho :P
Le falta: creo que nada, es lo que pretende ser y lo que te esperas que sea, una explicación de cómo trabajar con Scrum pero que queda lo suficientemente abierta para que cada uno la adapte a la forma de trabajar de su equipo.

Lo recomiendo para: todo el que quiera implantar Scrum en un proyecto por primera vez, evitará las típicas fases de prueba y error porque como se suele decir, es mucho más fácil aprender de los errores de otros que de los de uno mismo.

miércoles, 14 de enero de 2009

He leído: Pro C# 2008 and the .NET 3.5 Platform de Andew Troelsen


Título: Pro C# 2008 and the .NET 3.5 Platform
Autor: Andrew Troelsen
Editorial: Apress
ISBN10: 1-59059-884-9
ISBN13: 978-1-59059-884-9
Páginas: 1370


En la web de Apress
En Amazon


Lo cierto es que no lo he leído en su totalidad, pero es que son más de 1300 páginas y 4 apéndices extra descargables desde la web de la editorial... :S Pero sí que leí las partes que más me interesaban y creo que ya tengo suficientes elementos de juicio para valorar este libro.

Me gustó: Este sí que es un libro realmente completo, abarca prácticamente todo lo que un desarrollador tiene que saber sobre .NET, C# y las novedades que introduce el Framework 3.5 (LINQ, expresiones lambda, tipos anónimos, novedades en WCF, WF, WPF, ASP.NET ...). Además está todo muy bien explicado, con multitud de ejemplos (también se pueden descargar los proyectos de ejemplo de la web de Apress) e imágenes que ayudan a la comprensión.


El tono del libro es bastante ameno y está bien organizado. Antes de cada capítulo el autor hace una pequeña introducción de lo que se tratará, por lo que si no te interesa puedes pasar directamente al siguiente. Y también hay un resumen al final, que está muy bien para aclarar los conceptos del capítulo.

Para que os hagáis una idea de lo completo que es este libro, tiene 2 apéndices, uno sobre interoperabilidad con componentes COM y el otro sobre MONO :o

No me gustó: Ufff, es difícil encontrarle una pega a este libro, está realmente bien y es muy completo ... por decir algo, que es un tocho de más de 1300 páginas y pesa un montón :P Pero bueno, siempre viene bien para hacer un poco de brazo en la oficina o en casa :D

Le falta: que alguien lo traduzca al castellano, porque leerlo en inglés es un poquito duro, pero bueno, ya estamos acostumbrados y el autor no se hace pesado en ningún momento.

Lo recomiendo para: todo el que quiera profundizar en el desarrollo de aplicaciones en .NET con C# y el Framework 3.5, es un gran libro tanto de aprendizaje como para tenerlo de referencia (además, fardas un montón con eso del “Pro C#” :P )

lunes, 12 de enero de 2009

Obtener instancias de SQL Server y bases de datos disponibles desde .NET (VB.NET)

Aquí os dejo el código en VB.NET del formulario con los orígenes de datos de SQL Server disponibles, vamos, lo mismo que este ejemplo pero para los que no os gusta el ";" :P





Imports System.Data
Imports System.Data.Sql
Imports System.Data.SqlClient

Public Class frmConexiones
Private servidores As SqlDataSourceEnumerator
Private tablaServidores As DataTable
Private servidor As String


Public Sub New()

' Llamada necesaria para el Diseñador de Windows Forms.
InitializeComponent()

' Agregue cualquier inicialización después de la llamada a InitializeComponent().
servidores = SqlDataSourceEnumerator.Instance
tablaServidores = New DataTable()
End Sub



Private Sub cmbServidores_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmbServidores.Click


' Comprobamos que no se haya cargado ya el combobox
If tablaServidores.Rows.Count = 0 Then
' Obtenemos un dataTable con la información sobre
' las instancias disponibles de SQL Server 2000 y 2005
tablaServidores = servidores.GetDataSources()

' Creamos una lista para que sea el origen de datos
' del combobox
Dim listaServidores As List(Of String) = New List(Of String)

' Recorremos el dataTable y añadimos un valor nuevo la
' lista con cada fila
For Each rowServidor As DataRow In tablaServidores.Rows

' La instancia de SQL Server puede tener nombre
' de instancia o únicamente el nombre del servidor,
' comprobamos si hay nombre de instancia para
' mostrarlo
If String.IsNullOrEmpty(rowServidor("InstanceName").ToString()) Then
listaServidores.Add(rowServidor("ServerName").ToString())
Else
listaServidores.Add(rowServidor("ServerName") & "\\" & rowServidor("InstanceName"))
End If

Next

'Asignamos la lista de servidores como origen de datos
' del combobox
Me.cmbServidores.DataSource = listaServidores
End If

End Sub


Private Sub cmbBasesdeDatos_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmbBasesdeDatos.Click

Dim listaBasesDatos As List(Of String) = New List(Of String)
Dim cadenaConexion As String
Dim selectSQL As String

' Se comprueba que haya un servidor seleccionado para
' poder conectarnos
If Me.cmbServidores.Text = "" Then
MsgBox("Debe seleccionar un servidor")
Return
End If

servidor = Me.cmbServidores.Text

'Componemos la cadena de conexión con el servidor seleccionado y
' seguridad integrada
' si la autenticación se hace con usuario y password hay
' que cambiar la cadena
cadenaConexion = "Data Source=" & servidor & " ;Integrated Security=True;Initial Catalog=master"


Using con As New SqlConnection(cadenaConexion)

' Abrimos la conexión
con.Open()

'Obtenemos los nombres de las bases de datos que
' haya en el servidor
selectSQL = "select name from sys.databases;"

Dim com As SqlCommand = New SqlCommand(selectSQL, con)
Dim dr As SqlDataReader = com.ExecuteReader()

' Recorremos el dataReader
While (dr.Read())
listaBasesDatos.Add(dr(0).ToString())
End While

'Asignamos la lista de bases de datos como origen
'de datos del combobox
Me.cmbBasesdeDatos.DataSource = listaBasesDatos


End Using

End Sub

End Class




Happy codding ;)

Obtener instancias de SQL Server y bases de datos disponibles desde .NET (C#)

Por si alguna vez se os ha planteado la necesidad de mostrar un formulario con los orígenes de datos de SQL Server disponibles (algo como el formulario Agregar conexión de Visual Studio) voy a plantear un pequeño ejemplo.

Creamos un nuevo proyecto de Windows Forms al que llamaremos Conexiones, y en él un formulario frmConexiones en el que añadimos 2 combobox: cmbServidores y cmbBasesdeDatos (esta vez me estoy currando los nombres, no os quejaréis :P).

En el código del formulario añadimos lo siguiente:




using System;
using System.Windows.Forms;
using System.Data;
using System.Data.Sql;
using System.Collections.Generic;

namespace Conexiones
{
public partial class frmConexiones : Form
{
SqlDataSourceEnumerator servidores;
System.Data.DataTable tablaServidores;
String servidor;

public frmConexiones()
{
InitializeComponent();
servidores = SqlDataSourceEnumerator.Instance;
tablaServidores = new DataTable();
}
}
}



Ahora hay que añadir el código para el evento click de cada uno de los combobox:
Para obtener la lista de instancias de SQL Server disponibles usaremos el objeto SqlDataSourceEnumerator.Instance, concretamente el método GetDataSources() que nos devuelve un DataTable con las características de las instancias: nombre del servidor, nombre de instancia, si forma parte de un clúster y la versión (8.00.x para SQL Server 2000 y 9.00.x para SQL Server 2005):




private void cmbServidores_Click(object sender, EventArgs e)
{

// Comprobamos que no se haya cargado ya el combobox
if (tablaServidores.Rows.Count == 0)
{
// Obtenemos un dataTable con la información sobre las instancias visibles
// de SQL Server 2000 y 2005
tablaServidores = servidores.GetDataSources();


// Creamos una lista para que sea el origen de datos del combobox
List listaServidores = new List();


// Recorremos el dataTable y añadimos un valor nuevo a la lista con cada fila
foreach (DataRow rowServidor in tablaServidores.Rows)
{

// La instancia de SQL Server puede tener nombre de instancia
//o únicamente el nombre del servidor, comprobamos si hay
//nombre de instancia para mostrarlo
if (String.IsNullOrEmpty(rowServidor["InstanceName"].ToString()))
listaServidores.Add(rowServidor["ServerName"].ToString());
else
listaServidores.Add(rowServidor["ServerName"] + "\\" + rowServidor["InstanceName"]);
}

// Asignamos al origen de datos del combobox la lista con
// las instancias de servidores
this.cmbServidores.DataSource = listaServidores;
}
}



Y para el que muestra las bases de datos tenemos que ejecutar una consulta sobre la vista sys.databases:




private void cmbBasesdeDatos_Click(object sender, EventArgs e)
{
string select;
string cadenaConexion;
List listaBasesdatos = new List();

this.servidor = this.cmbServidores.Text;

// Componemos la cadena de conexión con el servidor seleccionado
// con seguridad integrada
// Si se conecta con usuario de SQL Server hay que cambiar
// la cadena de conexión
cadenaConexion = "Data Source=" + this.servidor + ";Integrated Security=True;Initial Catalog=master";

using (System.Data.SqlClient.SqlConnection con = new System.Data.SqlClient.SqlConnection(cadenaConexion))
{
// Abrimos la conexión
con.Open();

// Obtenemos los nombres de las bases de datos que haya en el servidor
// se pueden filtrar para no mostrar las bases de datos de sistema
select = "select name from sys.databases;";

// Obtenemos un dataReader con el resultado
System.Data.SqlClient.SqlCommand com =
new System.Data.SqlClient.SqlCommand(select, con);
System.Data.SqlClient.SqlDataReader dr = com.ExecuteReader();

// Recorremos el dataReader y añadimos un elemento nuevo
// por cada registro
while (dr.Read())
{
listaBasesdatos.Add(dr[0].ToString());
}

// Asignamos la lista de bases de datos como origen de datos del combobox
this.cmbBasesdeDatos.DataSource = listaBasesdatos;
}
}



Y ya está, con esto tenemos un formulario que permite seleccionar la instancia de SQL Server y la base de datos a la que nos queremos conectar :)

Si en vez de seguridad integrada usamos un usuario y password de SQL Server sólo hay que cambiar la cadena de conexión.

Happy codding ;)

PD: Si no os gusta C# tenéis el mismo código en VB.NET aquí.

jueves, 8 de enero de 2009

Imprimir el contenido de un DataGridView con PrintDocument

Igual alguna vez os habéis encontrado en la situación de querer imprimir el contenido de un DataGridView y poder presentárselos a alguien o tenerlos a mano sin necesidad de tener la aplicación en pantalla.

Está claro que hay formas muy sencillas de hacerlo, por ejemplo ese botón “Imprimir pantalla” que hay en el teclado y que mucha gente no sabe para qué sirve, crear informes con Crystal Reports u otro creador de informes, apuntarlos a mano en un papel, memorizarlos ...

Algunas de estas opciones nos mostrarán datos de más, otras de menos y otras dependerán de la cantidad de neuronas que nos queden disponibles en ese momento (bueno, y de las que cada uno traiga de fábrica :P) pero hay una opción bastante sencilla que nos puede sacar de más de un apuro: usar PrintDocument con el contenido del DataGridView.

Supongamos que tenemos un formulario con un DataGridView que se llama dataGridView1 (original, ¿a qué sí?) que tiene como origen de datos ... lo que sea, nos da igual porque lo que nos interesa es el contenido no si el origen de datos es un DataSet, un fichero XML o hemos metido los datos por código.

Bien, añadimos al formulario un objeto de tipo PrintDocument (está en el Cuadro de Herramientas en el apartado Impresión) con el nombre printDocument1 (para ser coherentes con la nomenclatura :P).

Ahora añadimos un botón (¿a qué no adivináis como se llama?) pues sí, button1 y en su evento click escribimos lo siguiente:


printDocument1.Print();



En el evento PrintPage de printDocument1 vamos a recorrer el contenido de dataGridView1 e imprimir una línea por cada fila del grid. Es muy importante saber que el evento se dispara por cada página que se vaya a imprimir, esto quiere decir que si tenemos 5 páginas el evento se disparará 5 veces y por tanto hay que saber en qué punto nos hemos quedado para continuar desde ahí y no volver a empezar.

// Variable a nivel de clase para recordar en qué punto nos hemos quedado
// la inicializamos a -1 para imprimir también las cabeceras de las columnas
private int i = -1;

private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
// La fuente que vamos a usar para imprimir.
Font printFont = new Font("Arial", 10);
float topMargin = e.MarginBounds.Top;
float yPos = 0;
float linesPerPage = 0;
int count = 0;
string texto = "" ;
DataGridViewRow row;

// Calculamos el número de líneas que caben en cada página.
linesPerPage = e.MarginBounds.Height / printFont.GetHeight(e.Graphics);


// Recorremos las filas del DataGridView hasta que llegemos
// a las líneas que nos caben en cada página o al final del grid.
while (count < linesPerPage && i < this.dataGridView1.Rows.Count)
{
row = dataGridView1.Rows[i];
texto = "";

foreach (DataGridViewCell celda in row.Cells)
{
texto += "\t" + celda.Value.ToString();
}

// Calculamos la posición en la que se escribe la línea
yPos = topMargin + (count * printFont.GetHeight(e.Graphics));

// Escribimos la línea con el objeto Graphics
e.Graphics.DrawString(texto, printFont, Brushes.Black, 10, yPos);

count++;
i++;
}

// Una vez fuera del bucle comprobamos si nos quedan más filas
// por imprimir, si quedan saldrán en la siguente página
if (i < this.dataGridView1.Rows.Count)
e.HasMorePages = true;
else
{
// si llegamos al final, se establece HasMorePages a
// false para que se acabe la impresión
e.HasMorePages = false;

// Es necesario poner el contador a 0 porque, por ejemplo si se hace
// una impresión desde PrintPreviewDialog, se vuelve disparar este
// evento como si fuese la primera vez, y si i está con el valor de la
// última fila del grid no se imprime nada
i = 0;
}

}




Con esto conseguimos imprimir todos los datos que hay en un grid, incluidas las cabeceras de las columnas, pero ¿habría forma de imprimir únicamente lo que seleccionemos en el grid?

La respuesta es sí y es bastante sencillo, simplemente hay que cambiar:


texto += "\t" + celda.Value.ToString();



por este otro:


if (celda.Selected)
{
texto += "\t" + celda.Value.ToString();
}
if (String.IsNullOrEmpty(seleccion) == false)
{
yPos = topMargin + (count * printFont.GetHeight(e.Graphics));
e.Graphics.DrawString(texto, printFont, Brushes.Black, 10, yPos);
count++;
}



Con esto sólo imprimimos las celdas que se hayan seleccionado, que pueden ser, varias filas, varias columnas o celdas sueltas, y además evitamos que se impriman filas en blanco.

Advertencia: que nadie piense que con esto vamos a sacar unos informes espectaculares con las columnas perfectamente alineadas y todo precioso, NO NO NO, esto es únicamente para hacer una impresión rápida de los valores que aparecen en un DataGridView, sin muchas florituras pero también sin muchas complicaciones.


Happy codding ;)


Bola extra: PrintDocument imprime directamente por la impresora predeterminada, para cambiar la impresiona, su configuración y la configuración del papel echarle un vistazo a PrintDialog y PageSetudDialog. Y para sacar la vista previa: PrintPreviewDialog.

miércoles, 7 de enero de 2009

Pasar filas a columnas con T-SQL: PIVOT() y las cosas que te alegran el día :)

English version

A veces las cosas que parecen complicadas tienen una solución muy fácil pero que no siempre es la primera que se nos ocurre (no es exactamente el principio de la navaja de Ockham, pero se le parece).

Veamos, el caso es que hay una tabla de logs en la que varios procesos van insertando el resultado de su paso por diferentes estados, algo como esto:



Proceso Estado Timestamp

Proceso1 10 2008/12/21 12:22:05.320
Proceso2 10 2008/12/21 12:22:05.358
Proceso1 11 2008/12/21 12:22:06.210
Proceso1 12 2008/12/21 12:22:06.588
Proceso2 11 2008/12/21 12:22:10.100
Proceso2 12 2008/12/21 12:22:10.048
Proceso3 10 2008/12/21 14:30:05.358
Proceso3 11 2008/12/21 14:30:20.052



Para poder hacer un seguimiento de los procesos y así identificar problemas de rendimiento en ciertos momentos: entran muchos procesos a la vez, se hacen copias de seguridad, el servidor se queda sin memoria, ... sacamos una consulta agrupando por proceso y hora.

SELECT proceso, count(*) as peticiones, datepart(hh,Timestamp) as hora
FROM MiLOG with (nolock)
GROUP BY proceso, datepart(hh,Timestamp)
ORDER BY 1,3

Con esta consulta teníamos un resultado parecido a este:


Proceso Peticiones Hora
Proceso1 10 12
Proceso1 2 13
Proceso2 5 12
Proceso2 3 13
Proceso3 25 14
Proceso3 4 15



Pero claro, esta salida es un poco complicada de tratar porque aunque estén agrupados por el proceso y la hora cuando hay muchos datos se vuelve muy farragoso comprobarlos, y acabamos llevando los resultados a una hoja de cálculo para que se viesen más claro. Entonces me pregunté, ¿no habrá una forma de hacerlo desde T-SQL para que me convierta las filas en columnas? Vamos, que haya una columna por cada hora y una única fila para cada proceso.

Como siempre la primera solución en una consulta complicada es una sub-consulta (no sé porqué pero es así, alguien tendría que estudiarlo :P) pero en este caso no se trataba de una sub-consulta, sino más bien de 24 sub-consultas, todas atacando a la misma tabla y todas usando la función datepart(hh,Timestamp). Seguro que con eso obtendríamos en resultado esperado, pero a costa de un rendimiento muy muy pobre.

Como esta situación es muy común tenía que haber una forma fácil que solucionarlo, así que en una primera búsqueda de Google no me sorprendió los primeros resultados mostrasen la misma solución de las sub-consultas (por el principio de que la primera solución en una consulta complicada es una sub-consulta :P) ya me veía haciendo una mega-consulta con sub-consultas, JOINs sobre la misma tabla y demás, pero ya en la primera página de resultados encontré una solución mucho más simple: la cláusula PIVOT (que es una novedad SQL Server 2005, así que no lo intentéis con versiones anteriores).

Así que con una consulta como esta:

WITH Procesos( proceso, peticiones, hora)
AS(
SELECT proceso, count(*) as peticiones, datepart(hh,Timestamp) as hora
FROM MiLOG with (nolock)
GROUP BY proceso, datepart(hh,Timestamp)
)
SELECT * FROM Procesos PIVOT(SUM(peticiones) FOR Hora IN ([0],[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],[21],[22],[23])) AS Resultado
order by proceso

sí que tenemos el resultado esperado:


Proceso 0 1 2 ... 21 22 23

Proceso1 0 10 23 4 5 21
Proceso2 12 11 15 22 17 8
Proceso3 9 7 2 19 9 13


Como curiosidad decir que la cláusula PIVOT() únicamente acepta funciones de agregado (COUNT, SUM, MAX, AVG, ...), lo cual es totalmente lógico.

Otra cosa, igual os extraña ese

WITH Procesos( proceso, peticiones, hora) AS(...)

se trata de una expresión de tabla común (CTE) que es una forma de especificar un conjunto de resultados temporal con un nombre que puede ser usado en otras consultas.

Bueno, para empezar el año creo que está bien :D

Happy codding ;)



Bola extra: después de pensar en las sub-consultas y antes de descubrir la cláusula PIVOT, pensé en usar CUBE, que también podía servir, pero no era exactamente lo que buscaba, aquí tenéis un muy buen tutorial sobre CUBE.