|
Archivo de la categoría '.NET'
Poco uso práctico tendría la elaboración de programas, si dichos programas no pudiesen interactuar con la información brindada por sus usuarios, dicha información define el problema puntual que un programa o grupo de programas debe resolver, sin importar la clase de problema a solucionar o tipo de algoritmo implementado, los datos que un programa toma para generar cualquier solución y como dichos datos son manejados, son en muchas ocasiones el factor determinante de su éxito o fracaso.
Cuando la cantidad de datos que un algoritmo debe manejar son pocos, la forma de almacenarlos y recuperarlos no determina un factor influyente en la performance del programa. Pero a medida que la cantidad de datos a manejar se incrementa, la velocidad con la que los datos son almacenados y recuperados se vuelve en ocasiones el mayor de los problemas, posiblemente por esta cuestión, es que este problema ha sido abordado en miles de ocasiones por infinidad de personas y no en vano, han encontrado algunas soluciones que podrían sernos de utilidad. Veamos en primera instancia la solución más inocente para el almacenamiento de una gran cantidad de datos, la idea más sencilla para almacenar un grupo de elementos, es sin ninguna duda emplear un arreglo donde en cada posición se almacenará un elemento, esto ya trae problemas desde el principio debido a que no es frecuente conocer cuántos elementos se deben almacenar, y no es muy performante la idea de ir copiando el arreglo cuando una redimension es necesaria.
La algoritmia y diseño de estructuras de datos permite crear a partir de las unidades de almacenamiento más elementales como arreglos y punteros, tipos de datos cada vez más complejos y abstractos como listas, árboles, diccionarios, etc., apoyados unos sobre otros al igual que los ladrillos en una pared. Empleando estructuras de datos es posible intentar resolver el problema de la redimension de los arreglos empleando quizás una lista, de la cuales hay diversas opciones a elegir(simplemente encadenada, doblemente encadenada, circular, etc.) dependiendo del tipo de problema que se deba resolver, esta opción resuelve el problema de la redimension en forma eficiente, ya que es posible crear y eliminar elementos con un costo mínimo, pero no resuelve otro problema que es el de la búsqueda de elementos, es claro que en una lista encontrar un elemento es bastante costoso, ya que se debe ir recorriendo elemento por elemento hasta poder encontrar el deseado, si existen n elementos, el algoritmo deberá recorrer los n elementos (en el peor caso claro está) para encontrar el elemento buscado, esto suele denominarse como O(n) o como que el orden del algoritmo es n, el orden de un algoritmo determina su eficiencia.
En este punto es donde se ha intentado minimizar el problema de las búsquedas. la algoritmia y diseño de estructuras de datos ha aportado diversas soluciones a este problema, por ejemplo la búsqueda binaria ha permitido mejorar el orden desde n a log2(n), un orden logarítmico es una solución bastante buena en contraposición al orden lineal que habíamos obtenido previamente. Un punto a considerar es que en el caso de la búsqueda binaria hay un costo extra en el momento de la inserción de elementos, debido a que los valores deben insertarse ordenadamente, dependiendo de la cantidad de inserciones en contraposición a las búsquedas esta solución puede ser interesante. Otra opción es emplear una Tabla de dispersión, que es una solución que bajo ciertas condiciones resuelve el problema con una performance aceptable y una ventaja que posee es que es relativamente sencilla de implementar.
Para comprender la técnica supongamos que tenemos e1, e2… ei elementos pertenecientes al conjunto E, donde E es el conjunto con el total de elementos con los que operaremos.
supongamos también que existe una función H capaz de tomar un elemento ei y devolver otro elemento de otro tipo (no importa que tipo sea mientras que soporte la operación de comparación, debe ser posible determinar si dos elementos que devuelve la función H son iguales o no lo son) esta idea es bastante similar a la noción de función matemática que se estudiaba en la escuela primaria, donde existen un dominio, una imagen y una función capaz de relacionar uno o varios elementos del dominio con otro de la imagen, el secreto de esta técnica es la idea de que con nuestra función podamos dividir el conjunto total de elementos E en varios subconjuntos (también llamados “buckets”) para poder buscar entre menos valores, con esta idea tendremos entonces todo el conjunto E dividido en subconjuntos menores donde cada subconjunto posee la siguiente particularidad, para cada uno de sus elementos la función H devuelve el mismo valor, o sea, existe una relación de equivalencia entre los elementos de cada subconjunto.
Aunque aún no conocemos quién es la función H, sabemos que cuanto más podamos dispersar el grupo de elementos de E mejor será la función ya que cuando deseemos encontrar un elemento tendremos que buscarlo entre menor cantidad de elementos dentro de algún subconjunto.
A continuación veremos una de las implementaciones más clásica de tablas de disperción, la misma emplea como estructura soporte de datos un arreglo de subconjuntos.
A la vez cada subconjunto suele implementarse sobre una lista simplemente encadenada, aunque podrían emplearse otras estructuras de datos.
La idea de esta implementación es que cada celda del arreglo o bucket contenga un subconjunto de E, si dado un elemento la función de dispersión es capaz de seleccionar una celda dentro del arreglo, simplemente deberemos incluir el elemento en el bucket correspondiente y el problema quedará resuelto, la búsqueda es similar.
La cantidad de buckets a emplear (longitud del arreglo de subconjuntos) también influirá en la performance, en muchas implementaciones es posible elegir la cantidad de buckets que desean emplearse al momento de crear la instancia de la clase.
No cabe duda que la elección de la función de dispersión es el punto clave de la implementación, una mala elección podría hacer que los elementos se almacenen por completo dentro de un mismo bucket quedando todos los demás vacios y en ese caso la estructura que estaríamos utilizando seria una lista simplemente encadenada, pero con aun mas complejidad agregada, hay mucha investigación en este campo y pueden encontrase algunas funciones de hash clásicas, un ejemplo para elementos del tipo de dato cadena de caracteres es djb2, presentada por Dan Bernstein en comp.lang.c
unsigned long
hash(unsigned char *str)
{
unsigned long hash = 5381;
int c;
while (c = *str++)
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
return hash;
}
Y pueden encontrase miles más como sdbm, lose lose, one at time, etc. Cada una de ellas realizando las operaciones más extraordinarias imaginables sobre los datos de entrada, un punto importante es que la función H sirve para solo algunos tipos de datos, si los elementos son un tipo complejo, como por ejemplo “automovil”, “empleado” la función de hash planteada previamente no podrá realizar ningún cálculo.
El uso de funciones de dispersión criptográficas, como por ejemplo SHA-1 también pueden utilizarse, ya que da un buen resultado, aunque su cálculo puede ser costoso.
Existen diversos modelos de tablas de dispersión, en nuestro primer caso estamos viendo el modelo de dispersión abierta (open hashing), pero existen también: Chaining, Coalesced hashing, Perfect hashing, Dynamic perfect hashing, Probabilistic hashing, Robin Hood hashing, etc. Chaining o Hashing cerrado lo veremos más adelante.
Un uso frecuente para las tablas de dispersión es el modelado de diccionarios, un diccionario es una estructura de datos compuesta por un grupo de elementos que poseen una clave y un valor, un diccionario permiten almacenar, buscar, eliminar, determinar si existen valores a través de su clave, la cual debe ser única por cada elemento del diccionario, en estos casos las operaciones de dispersión se efectúan sobre la clave.
Debe quedar claro que si bien los diccionarios pueden implementarse muy bien sobre una tabla de dispersión, también pueden existir otros tipos de datos que podrían implementarse sobre esta estructura como por ejemplo un conjunto, así como también un diccionario podría implementarse sobre un árbol o sobre una lista encadenada (lo cual no sería una muy buena idea en el común de los casos).
Muchos lenguajes modernos utilizan tablas de dispersión, un ejemplo es C# que proporciona una implementación en System.Collections.Generic.Dictionary.
Otra implementación puede encontrarse en System.Collections.Hashtable, pero la misma difiere un poco de lo comentado y la veremos luego.
En el caso de Dictionary de .NET se implementa un diccionario fuertemente tipado (Generics) sobre una tabla de dispersión abierta, siguiendo la idea de valor clave mencionada previamente. un punto interesante es la función de hash, funciona sobre cualquier tipo de dato, por ejemplo:
Dictionary<SampHashKey, string> oD1
Dictionary<int, string> oD2
Dictionary<string, int> oD3
Para poder tener una función de dispersión genérica .NET hace lo siguiente, la clase object posee un método llamado GetHashCode que devuelve un valor entero (int) con un código, la función GetHashCode de alguna forma permite mapear cualquier objeto a un numero entero, GetHashCode es empleado cuando se agrega, busca u opera con un elemento en la tabla de dispersión, para darle una entrada a la función de hash. Quien no esté totalmente convencido puede hacer la siguiente prueba:
public struct SampHashKey
{
public int a { set; get; }
public int b { set; get; }
public int c { set; get; }
public int d { set; get; }
}
public override int GetHashCode()
{
MessageBox.Show("llamando a GetHashCode");
return base.GetHashCode();
}
public void Test()
{
Dictionary<SampHashKey, string> oD = new Dictionary<SampHashKey, string>();
oD.Add(sSHK1, "s");
}
Al invocarse al método Add de oD, aparecerá el cuadro de texto con el mensaje “llamando a GetHashCode”. Esta es una idea inteligente y quizás la única posibilidad de poder emplear una misma función de hash para cualquier tipo de dato, permitiéndole a la instancia tabla de dispersión sobre la cual se encuentra implementado el Dictionary seleccionar un bucket donde almacenar los datos para toda clave posible. Esta metodología elegante y simple, sigue apoyándose una buena elección de función de dispersión y en una buena elección de función GetHashCode. Intuitivamente nos es lógico pensar que más allá de lo que haga la función de dispersión con el código de hash que devuelve GetHashCode, si varios objetos devuelven el mismo GetHashCode, todos ellos irán a parar inevitablemente al mismo bucket.¿Pero es posible que varios elementos tengan un mismo HashCode?, consideremos el siguiente ejemplo:
public struct SampHashKey
{
public int a { set; get; }
public int b { set; get; }
public int c { set; get; }
public int d { set; get; }
}
public void Test()
{
SampHashKey sSHK1 = new SampHashKey() { a = 1, b = 2, c = 3, d = 4 };
SampHashKey sSHK2 = new SampHashKey() { a = 2, b = 1, c = 3, d = 4 };
SampHashKey sSHK3 = new SampHashKey() { a = 2, b = 3, c = 1, d = 4 };
SampHashKey sSHK4 = new SampHashKey() { a = 2, b = 3, c = 4, d = 1 };
SampHashKey sSHK5 = new SampHashKey() { a = 3, b = 2, c = 4, d = 1 };
SampHashKey sSHK6 = new SampHashKey() { a = 3, b = 4, c = 2, d = 1 };
Hashtable oHT = new Hashtable();
oHT.Add(sSHK1, "Valor1");
oHT.Add(sSHK2, "Valor2");
oHT.Add(sSHK3, "Valor3");
oHT.Add(sSHK4, "Valor4");
oHT.Add(sSHK5, "Valor5");
oHT.Add(sSHK6, "Valor6");
int iSHK1Hash = sSHK1.GetHashCode();
int iSHK2Hash = sSHK2.GetHashCode();
int iSHK3Hash = sSHK3.GetHashCode();
int iSHK4Hash = sSHK4.GetHashCode();
int iSHK5Hash = sSHK5.GetHashCode();
int iSHK6Hash = sSHK6.GetHashCode();
if (sSHK1.GetHashCode() == sSHK2.GetHashCode() &&
sSHK2.GetHashCode() == sSHK3.GetHashCode() &&
sSHK3.GetHashCode() == sSHK4.GetHashCode() &&
sSHK4.GetHashCode() == sSHK5.GetHashCode() &&
sSHK5.GetHashCode() == sSHK6.GetHashCode())
MessageBox.Show("¡Ouch!");
}
Para quien no tenga ganas de cargar el IDE y probar el código en un WinForm, le comento que al invocar al método Test, un cuadro de texto aparecerá en pantalla indicando que varios objetos distintos pueden tener un mismo HashCode y no es para sorprenderse, ya que el ejemplo fue preparado para que esto sucediera.
En casos del mundo real las posibilidades de que esto suceda son menores, en realidad dependerán del objeto key seleccionado y los valores que el mismo posea, pero de todas formas es bueno saber que puede suceder y mejor aun, es saber que se puede solucionar gracias a la flexibilidad del diseño de las clases de .NET.
Consideremos lo siguiente:
public struct SampHashKey
{
public int a { set; get; }
public int b { set; get; }
public int c { set; get; }
public int d { set; get; }
public override int GetHashCode()
{
return a + Convert.ToInt32(Math.Pow(b, 2)) + Convert.ToInt32(Math.Pow(c, 3)) + Convert.ToInt32(Math.Pow(d, 4));
}
}
Sobrecargando el método GetHashCode apropiadamente, hemos conseguido devolver nuevos y distintos valores. Una vez más cabe mencionar que esta sobrecarga de la función GetHashCode sirve solo para este ejemplo puntual, seguramente debe funcionar desastrosamente para otro conjunto de valores. Pero si un caso de este tipo se diera en la vida real, se deberá enfocar hacia una solución similar, evaluar el conjunto de valores posibles y puntualmente encontrar un reemplazo adecuado para el método GetHashCode, la complejidad u orden de este diccionario solo puede calcularse en forma estadística, ya que en el peor de los casos será o(n) cuando todos los elementos se agrupen en un mismo bucket, pero claro que esto no es muy probable. el orden en promedio de un Dictionary es o(n/m) siendo n la cantidad de elementos y m la cantidad de buckets.
Como ya hemos mencionado, existen otros tipos de tablas de dispersión, en las tablas de dispersión cerradas, cada bucket puede contener un solo elemento, por lo que cuando otro elemento es mapeado al mismo bucket se produce una colisión la cual debe resolverse, en tal caso se emplean funciones de rehashing para encontrar una nueva ubicación para el elemento, la función de rehashig suele incluir como parámetro de entrada el numero de intento, ya que no es sabido cuantas colisiones se producirán hasta finalmente poder alojar al elemento.
Como ya habíamos mencionado .NET ofrece otra implementación de tabla de dispersión, pero en este caso no se implementa sobre una tabla de hash abierta. La clase de System.Collections.HashTable que es débilmente tipada sigue empleando el método GetHashCode para poder mapear cualquier objeto, y es otro diccionario implementado sobre una tabla de dispersión con ciertas particularidades, la estructura de datos permite alojar un elemento por celda, por lo que cuando otro elemento sea mapeado a la misma celda se producirá una colisión, la clase empleará una funciones de rehashing hasta que el ítem pueda reubicarse, este esquema posee también lo que se denomina factores de carga “Load Factors” y que se manifiesta como una proporción siendo un valor numérico entre 0.1 y 1.0 (que se establece en el constructor de la clase) e indica la máxima proporción de elementos en los buckets, por ejemplo un factor de carga de 0.5 indica que a lo sumo, la tabla de dispersión tendrá la mitad (0.5) de buckets completa, lo ideal sería pensar que el valor óptimo es 1, ya que le pide a la tabla de hash que intente llenar todos los buckets, pero el valor mágico es 0.72 y es el que se emplea por defecto si no se le establece otro en el constructor, inclusive si se establece el valor 1.0 el mismo será automáticamente escalado a 0.72. Otra particularidad es que la tabla puede crecer de tamaño si los factores de carga se encuentran comprometidos, si bien los factores de carga son costosos de mantener aseguran que los elementos se distribuyan de la forma más pareja posible en los buckets.
A continuación y para cerrar el articulo presentaremos un par de funciones muy interesantes:
H(key) = [GetHash(key) + 1 + (((GetHash(key) >> 5) + 1) % (hashsize – 1))] % hashsize
Hk(key) = [GetHash(key) + k * (1 + (((GetHash(key) >> 5) + 1) % (hashsize – 1)))] % hashsize
key = GetHashCode()
hashsize = cantidad de buckets
k = número de intento
Estas funciones son, como han de imaginarse, las funciones de hash y rehash empleadas por la clase HashTable de .NET. las mismas pueden encontrarlas, junto con más información referente en el artículo:
http://msdn.microsoft.com/en-us/library/aa289149.aspx escrito por Scott Mitchell.
VN:F [1.7.3_972] Rating: 0.0/10 (0 votos cast)
No tiene comentarios »
Lunes, 01 dic, 2008 @ 23:50 | Por Gustavo Cantero (The Wolf) | Windows Vista, WPF  |
 |
Hace unos meses estudiando para rendir la certificación “.NET Framework 3.5, Windows Presentation Foundation Application”, encontré en uno de los libros que leí un apartado que mostraba un ejemplo de cómo utilizar el efecto “Glass” de Windows Vista en nuestras aplicaciones WPF, el cual me pareció interesante por lo cual voy a contar como realizarlo.
Para quienes no saben que es el “Aero Glass” les comento que es el efecto que utiliza Windows Media Player en la parte inferior del reproductor para mostrar los controles sobre una sección “semitransparente” del formulario, o el Internet Explorer para hacer lo mismo sobre la sección superior, donde está el menú y la barra de direcciones.


Antes de comenzar voy a comentar que el Windows Vista posee un servicio llamado DWM (Desktop Windows Manager), el cual es el encargado de crear la imagen que se ve en el escritorio del sistema operativo cuando éste tiene seleccionado el tema “Aero”. En Windows Vista cuando no está seleccionado este tema (al igual que en todas las versiones anteriores de Windows) cada aplicación era la encargada de escribir su contenido en el buffer de la pantalla cada vez que el sistema operativo le enviaba el mensaje WM_PAINT. Con DWM el sistema posee una composición off-screen de la imagen de cada aplicación y la copia al buffer de la pantalla cuando es necesario, evitando así los problemas de “arrastre” que se producían cuando una aplicación estaba ocupada y el usuario la movía por el escritorio. Gracias al DWM también se puede ver una “vista en miniatura” de la aplicación al pasar el mouse sobre éste en la barra de tareas, dibujar todas las ventas en 3D para seleccionar la deseada al pulsar la tecla Win+Tab, y el mencionado efecto Glass (el cual voy a mostrar como crearlo a continuación).
Lo primero que hay que saber para realizar este efecto es que hay que utilizar la API de DWM, la cual se llama (obviamente) DwmApi.dll, y posee un método llamado DwmExtendFrameIntoClientArea que es el que se utiliza para generar el efecto Glass. Este efecto en si lo que logra es expandir el marco de la ventana que ya se ve con el efecto Glass hacia el interior de la misma. Para lograrlo este método utiliza dos parámetros: el primero es del tipo IntPtr y es el puntero Win32 de nuestra ventana, y el segundo es una estructura donde le pasamos los nuevos márgenes de nuestra ventana. Estos márgenes, como en cualquier función de WPF, no deben estar en píxeles sino que deben estar en una medida independiente, por lo cual si queremos convertir la cantidad de pixeles a esa medida deben multiplicarla por los DPIs del monitor, los cuales se pueden obtener a través de un objeto Graphics, como se muestra a continuación:
//Obtengo los DPIs del sistema operativo
Graphics g = Graphics.FromHwnd(windowHandle);
Si a cualquiera de los márgenes los establecemos en -1 toda la ventana va a tener el efecto Glass.
A continuación muestro el código de la clase estática que hice para aplicar el efecto a una ventana.
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
namespace GlassEffect
{
/// <summary>
/// Clase estática para el manejo del DWM
/// <summary>
public static class DWMHelper {
/// <summary>
/// API para generar el efecto Glass
/// </summary>
/// <param name="hwnd">Puntero a la ventana</param>
/// <param name="pMarInset">Márgenes nuevos</param>
[DllImport("DwmApi.dll")]
private static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref Margins pMarInset);
/// <summary>
/// Estructura con los márgenes nuevos
/// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct Margins {
public int cxLeftWidth;
public int cxRightWidth;
public int cyTopHeight;
public int cyBottomHeight;
}
/// <summary>
/// Aplica el efecto Glass a una ventana
/// </summary>
/// <param name="win">Ventana</param>
/// <param name="left">Cantida de pixeles de la izquierda</param>
/// <param name="right">Cantida de pixeles de la derecha</param>
/// <param name="top">Cantida de pixeles de arriba</param>
/// <param name="bottom">Cantida de pixeles de abajo</param>
public static void ApplyGlass(Window win, int left, int right, int top, int bottom) {
//Obtengo el puntero Win32 a la ventana
WindowInteropHelper windowInterop = new WindowInteropHelper(win);
IntPtr windowHandle = windowInterop.Handle;
HwndSource.FromHwnd(windowHandle).CompositionTarget.BackgroundColor = Colors.Transparent;
//Obtengo los DPIs del sistema operativo
Graphics g = Graphics.FromHwnd(windowHandle);
//Establezco los márgenes
Margins margins = new Margins();
margins.cxLeftWidth = Convert.ToInt32(left * (g.DpiX / 96));
margins.cxRightWidth = Convert.ToInt32(right * (g.DpiX / 96));
margins.cyTopHeight = Convert.ToInt32(top * (g.DpiY / 96));
margins.cyBottomHeight = Convert.ToInt32(bottom * (g.DpiY / 96));
// Extend the glass frame.
if (DwmExtendFrameIntoClientArea(windowHandle, ref margins) < 0)
throw new NotSupportedException("Error en la operación");
}
}
}
//Establezco los márgenes
Margins margins = new Margins();
margins.cxLeftWidth = Convert.ToInt32(left * (g.DpiX / 96));
margins.cxRightWidth = Convert.ToInt32(right * (g.DpiX / 96));
margins.cyTopHeight = Convert.ToInt32(top * (g.DpiY / 96));
margins.cyBottomHeight = Convert.ToInt32(bottom * (g.DpiY / 96));

Por último, en el siguiente enlace les dejo un proyecto de ejemplo funcionando para que puedan probarlo.

VN:F [1.7.3_972] Rating: 9.5/10 (11 votos cast)
4 Comentarios »
Jueves, 13 nov, 2008 @ 18:22 | Por Gustavo Cantero (The Wolf) | .NET |
 |
Muchas veces estamos trabajando con colores en .NET y necesitamos obtener el código HTML de los mismos para utilizarlo en un control, o el número correspondiente al color Ole para pasárselo a un componente COM (ActiveX). El primer caso puede resolverse obteniendo cada componente RGB del color y pasando su valor numérico a hexadecimal, pero para convertirlo a Ole para utilizarlo en COM es más complicado. Para resolver estas cuestiones en .NET existe la clase ColorTranslator, la cual se encuentra en el namespace System.Drawing, y permite convertir un color de .NET a HTML, Ole o Win32 y viceversa, o sea, de un valor entero de un color Ole o de Windows o de un string con un color HTML se puede obtener el color en .NET correspondiente. Un ejemplo de esto se muetra en la siguientes lineas:
string strBlanco = "#FFFFFF"; //Blanco
string strRojo = "#FF0000"; //Rojo
//Estructura de .NET con el color blanco
System.Drawing.Color colorBlanco = System.Drawing.ColorTranslator.FromHtml(strBlanco);
//Estructura de .NET con el color blanco
System.Drawing.Color colorRojo = System.Drawing.ColorTranslator.FromHtml(strRojo);
//Valor numérico que representa el color blanco en Windows
int intBlanco = System.Drawing.ColorTranslator.ToWin32(colorBlanco);
//Valor numérico que representa el color rojo en Ole
int intRojo = System.Drawing.ColorTranslator.ToOle(colorRojo);
VN:F [1.7.3_972] Rating: 8.0/10 (5 votos cast)
No tiene comentarios »
Viernes, 31 oct, 2008 @ 12:38 | Por Gustavo Cantero (The Wolf) | Visual Studio, XNA  |
 |
Ya salió el release final del XNA Game Studio 3. Para quienes no lo conocen, el XNA Game Studio es una herramienta gratis de Microsoft para hobistas, estudiantes y desarrolladores independientes que facilita el desarrollo de videojuegos para Windows, XBOX 360 y Microsoft Zune, usando librerias basadas en .NET Framework.
Algunas de las nuevas características que se incluyen en esta versión son las siguientes:
- Integración con la mayoria de los productos de la familia Visual Studio 2008
- Soporte para C# 3.0 y LINQ
- Soporte para desarrollar juegos para Microsoft Zune (en la versión anterior sólo se podia para Windows y XBOX)
- Mejoras en las API del XNA Framework para el soporte multimedia
- Efectos de sonido
- Modo de “trial” en los juegos, para que los usuarios puedan probarlo antes de decidir su compra
- y muchas novedades más……
Por último les dejo algunos enlaces de utilidad, incluyendo el de la descarga del XNA Game Studio 3:
VN:F [1.7.3_972] Rating: 10.0/10 (1 voto cast)
No tiene comentarios »
Miércoles, 29 oct, 2008 @ 11:55 | Por Gustavo Cantero (The Wolf) | Silverlight |
 |
Para quienes ya estén trabajando con Silverlight 2.0, o para quienes recién estén incursionando en esta potente tecnología, ha salido en CodePlex el primer release del Silverlight Toolkit, un paquete de controles que permitirán desarrollar nuestras aplicaciones más fácil y rápido. En esta versión se encuentran en su versión estable los siguientes controles:
Y en su versión preview se encuentran los siguientes:

Además de estos controles, en este Toolkit se incluyen varios temas de estilo profesional para utilizar en nuestras aplicaciones. A continuación detallo una lista con los mismos:
Sin duda, son buenas noticias para quienes estamos utilizando Silverlight.
VN:F [1.7.3_972] Rating: 6.3/10 (7 votos cast)
No tiene comentarios »

Ya está disponible el Community Technology Preview del Visual Studio 2010 y del .NET Framework 4! Esta información está disponible en Microsoft Connect. La dirección de donde bajarse el CTP es esta: http://www.microsoft.com/downloads/details.aspx?FamilyId=922B4655-93D0-4476-BDA4-94CF5F8D4814&displaylang=en. Cabe mencionar que la descarga ocupa alrededor de 7.2 Gb. y es una máquina virtual para Microsoft Virtual PC 2007, lo que permite que hagamos pruebas con estos productos sin alterar o dañar ninguna de las otras versiones que podamos tener instaladas en nuestro PC.
VN:F [1.7.3_972] Rating: 9.0/10 (3 votos cast)
No tiene comentarios »
Viernes, 17 oct, 2008 @ 13:16 | Por Gustavo Cantero (The Wolf) | Silverlight |
 |
El 13 de Octubre Microsoft anunció, a través de un comunicado de prensa, la versión RTM de Silverlight 2.0, la cual está disponible para descargarse desde el 14 de octubre desde la página de Silverlight.
Como novedad para los diseñadores, el Microsoft Blend 2.5 (que aún no estaba en su versión final) ahora es un Service Pack para el Microsoft Blend 2.0, o sea, para trabajar con Silverlight 2 solamente hay que instalarle el Service Pack 1 al Blend 2.0.
Para los desarrolladores: antes de instalar el Silverlight 2 tienen que instalar el Service Pack 1 para Visual Studio 2008 o, para la gente que no posee una licencia de Visual Studio 2008, Microsoft extendió el soporte a Visual Web Developer 2008 Express Edition para que también se pueda desarrollar aplicaciones para Silverlight 2 con esta herramienta, la cual es de gratis descarga.
Otra noticia importante es que existe un proyecto Open Source llamado eclipse4SL de Soyatec y Microsoft para poder crear aplicaciones Silverlight desde eclipse, el cual planean tenerlo terminado para la segunda mitad de 2009, aunque ya puede descargarse la versión Milestone 1.
Algunas de las novedades que se muestran en el comunicado de prensa son:
- Soporte para .NET Framework con una librería de clases enriquecida, la cual es un subconjunto compatible de .NET Framework.
- Controles potentes, entre los que se incluyen: DataGrid, ListBox, Slider, ScrollViewer, Calendar y más.
- Suporte de plantillas y skins avanzados, lo que facilita la personalización del “look and feel” de nuestras aplicaciones.
- Deep zoom. Esto permite tener interactividad y navegación con imágenes de ultra-alta resolución. Esta tecnología es similar a la utilizada en Virtual Earth y Google Map para ir “bajando” en un mapa hasta llegar cerca del suelo.
- Amplio soporte para networking, permitiendo hacer llamadas a través de REST, WS*/SOAP, POX, RSS y servicios HTTP estándar, permitiendo crear aplicación que se integren fácilmente a los sistemas existentes.
- Soporte del lenguaje .NET Framework para varios lenguajes, entre los cuales se incluyen Visual Basic, C#, JavaScript, IronPython y IronRuby.
- Protección avanzada de contenidos, gracias a Silverlight DRM que utiliza PlayReady.
- Mejora en la escalabilidad, utilizando capacidades de streaming y descarga progresiva, técnicas de búsqueda avanzadas, y soporte para “in-stream advertising”.
- Soporte multi-plataforma y multi-navegador, con soporte para Mac, Windows y Linux en Firefox, Safari e Internet Explorer.
Como último comentario cabe mencionar que la adopción de Silverlight sigue creciendo, con una penetración cerca del 50% en algunos países, y teniendo decenas de miles de aplicaciones desarrolladas con esta tecnología. Durante los 17 días de los juegos olímpicos de Beijing el sitio NBColympics.com, el cual utiliza tecnología Silverlight, tuvo más de 50 millones de visitantes, con 1.3 mil millones de páginas visitadas, 70 millones de streams de videos y 600 millones de minutos de video vistos.
A continuación dejo algunos enlaces de utilidad:
Sin duda es una buena noticia para la comunidad desarrolladora.
VN:F [1.7.3_972] Rating: 7.2/10 (5 votos cast)
No tiene comentarios »
El uso de arreglos (o arrays) es tan necesario en ciertas ocasiones que ha hecho que su aparición sea tan inmediata como los primeros lenguajes declarativos, y hasta la actualidad, los lenguajes más modernos no los han descartado.
| mov [BX],AX; |
Assember |
| array = (int *)malloc (N*sizeof(int)); |
C |
| type Int_Buffer is array (1..10) of Integer; |
Adda |
| int *iarray;iarray = new int [10]; |
C++ |
| DIM vars$(52) |
Basic |
| Dim c(3 to 82) as Integer |
Microsoft Visual Basic |
| int[] numbers = new int[10]; |
C# |
Entonces ¿podrían ser los arreglos tan necesarios en el T-SQL de Sql Server? La respuesta es que depende para quien, como suelen ser las respuestas a casi todas las preguntas formulables. Quizás esta necesidad no se manifieste tanto dentro del propio motor de base de datos, ya que siempre es posible acceder a los datos necesarios de alguna u otra forma, pero dentro del mundo de los desarrolladores la necesidad es innegable, como lo veremos con un ejemplo posteriormente. Claro que todo este preámbulo no tendría sentido si el T-SQL de Sql Server tuviese arreglos, pero de hecho no los posee (dejemos de lado por ahora la llegada de SQL Server 2008). ¿Entonces como se puede resolver este problema?, plantearemos entonces el siguiente caso que es, como acostumbramos, un caso del mundo real.
Hace un tiempo desarrollamos una aplicación que empleaba SQL Server 2005 como gestor para el soporte de datos. No tardó en aparecer la siguiente situación, entre nuestras tablas se daba, por las reglas de negocios el caso de que cuando se daba de alta un registro en una tabla del tipo “cabecera” debían asociársele unos cuantos registros del tipo “detalle” conjuntamente. Supongamos el caso de las tablas que se muestran a continuación que representan la estructura del problema abstrayendo complicaciones adicionales.
En nuestro caso estábamos empleando C# sobre el Framework 3.5 y ADO.NET para que nuestra capa de datos accediera al motor de base de datos SQL Server 2005, ante el problema analizamos las siguientes opciones;
- Emplear procedimientos almacenados (haciendo tantas llamadas como fueran necesarias).
- Emplear consultas dinámicas.
- Emplear un procedimiento almacenado que resolviera todo de alguna forma.
Recordemos que nuestro problema a resolver era que cuando se creaba un registro cabecera, en conjunto se creaban siempre varios registros detalle.
La primera aproximación fue la más inocente, poseer un procedimiento almacenado que inserta una cabecera y otro que inserta un detalle, llamémoslos ins_cabecera e ins_detalle para darle nombres intuitivos, entonces si había que crear una cabecera con, por ejemplo, 10 detalles. La metodología era llamar a los procedimientos almacenados desde nuestra capa de datos en .NET de la siguiente forma
ins_cabecera “datos cabecera”
ins_detalle “datos detalle 1″
ins_detalle “datos detalle 2″
ins_detalle “datos detalle 3″
ins_detalle “datos detalle 4″
ins_detalle “datos detalle 5″
ins_detalle “datos detalle 6″
ins_detalle “datos detalle 7″
ins_detalle “datos detalle 8″
ins_detalle “datos detalle 9″
ins_detalle “datos detalle 10″
Claro está que funciona perfectamente, pero hay algo que no se ve muy bien y es cada vez que se llama a una inserción en la capa de datos de .NET se produce una larga cantidad de llamadas contra el motor de base de datos, a pesar de ser procedimientos almacenados, claro está que hay una gran pérdida de tiempo. Inmediatamente se pensó que había que evitar hacer tantas llamadas.
Una segunda opción, (que creo que la descartamos incluso desde antes que a alguien se le ocurriera mencionarla), es la del uso de consultas dinámicas. Básicamente es la idea de escribir una larga sentencia T-SQL y pasársela al servidor de una sola vez para que el mismo la ejecute, este tipo de solución haría una sola llamada, claro está, pero hay muchas desventajas al usar consultas dinámicas en contrapartida a procedimientos almacenados.
En SQL Server (y estoy convencido que en cualquier otro motor de base de datos también), el uso de procedimientos almacenados ofrece ventajas en contraste a las consultas dinámicas, un factor clave es por ejemplo la compilación del plan de trabajo (la estrategia que empleará el motor de base de datos para acceder a los datos) que es reutilizada en procedimientos almacenados y no tiene que recalcularse cada vez que se lanza la consulta, un factor que suma puntos a la hora de evaluar la performance.
Además el uso de procedimientos almacenados asegura la inmunidad contra ataques por injection, como ya sabemos es posible crear consultas dinámicas parametrizadas, pero queda en manos de l del programador utilizarlos, quien puede omitir esta práctica y armar las sentencias por simple concatenación, quedando el sitio vulnerable a ataques por injection, (aunque parezca una locura, claramente este problema es más común de lo que uno se imagina), en cambio, en las llamadas a procedimientos almacenados no existe la posibilidad de dejar esa puerta de entrada. Finalmente otra ventaja es que los procedimientos almacenados pueden invocarse unos a otros, lo cual puede permitir atomizar y reutilizar código, otro punto a favor (Exceptuando lamentablemente nuestro caso en SQL Server 2005 donde la opción no está disponible) los procedimientos almacenados pueden depurarse con mucha facilidad, estos son a mi parecer motivos suficientes para desterrar a las consultas dinámicas, aunque hay situaciones en que no es posible utilizar procedimientos almacenados, por ejemplo existen aplicaciones que crean, modifican y destruyen estructuras de datos dentro del motor de base de datos empleando instrucciones DDL, en éstos casos el acceso a datos posiblemente deberá llevarse a cabo mediante consultas dinámicas. Esperemos que los programados sean cautos en esos casos delicados.
Entonces nos quedó nuestra última carta emplear una sola llamada a un procedimiento almacenado que resolviera todo el problema. Siendo algo así como la solución mágica, la misma consideraría pasarle a nuestro procedimiento almacenado, llamémoslo ins_cabdet, los datos de la cabecera y una estructura con todos los detalles (ya que no sabemos cuántos puede haber) para que los inserte juntos ins_cabdet de una sola vez.
Nuestra función desde la capa de datos en .NET tomaría la siguiente forma:
ins_cabdet “datos cabecera”, Estructura(“datos detalle 1″, “datos detalle 2″ … “datos detalle 10″)
Pero aquí mismo es donde aparece el problema, un arreglo nos vendría más que bien en este caso, pero ya dijimos que SQL Server no soporta arreglos (para quien recién halla cambiado de canal y se haya perdido el principio, recordemos que estamos omitiendo SQL Server 2008). Claramente ahora deberíamos pensar que los arreglos si eran tan necesarios el T-SQL de Sql Server. De todas formas no todo estuvo perdido, el problema pudimos resolverlo desde otro lugar algo lejano. Una de las características que posee SQL Server 2005 es el uso de XML, SQL Server 2005 posee un tipo de datos XML y es capaz de parsear XML’s empleando incluso XPath, entonces la idea fue clara. si no podemos pasarle arreglos al SQL Server intentemos pasarle un parámetro XML con los datos necesarios para poder realizar las inserciones, el XML es flexible y fácil de usar por lo que este tipo de solución es adaptable a muchos casos similares. El precio es claramente la creación del XML desde .NET y el parseo desde SQL Server, pero hoy día ya casi nada es gratis.
Debimos diseñar primero un formato de XML, creamos algo similar a lo siguiente:
<r>
<h nm="nombre_cab" />
<dc>
<d nm="nombre_de1"/>
<d nm="nombre_de2"/>
</dc>
</r>
Con nuestro fragmento XML armamos un procedimiento almacenado, al que llamamos Ins_CabDet el cual haría lo siguiente:
- Extracción de cabecera del xml
- Inserción de cabecera
- Para cada detalle del xml
- Extracción del detalle del xml
- Inserción del detalle
En el mundo real la función tomó la siguiente forma:
CREATE PROCEDURE Ins_CabDet
@doc xml
AS
BEGIN
BEGIN TRY
DECLARE @docHandle int
EXEC sp_xml_preparedocument @docHandle OUTPUT, @doc
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRAN
INSERT INTO Cabecera (Nombre)
SELECT nm 'Nombre'
FROM
OPENXML (@docHandle,'r/h',1)
WITH (
nm nvarchar(20)
)
DECLARE @IDCabecera int
SET @IDCabecera = @@IDENTITY
INSERT INTO Detalle (IDCabecera, Nombre, OtroDato1, OtroDato2)
SELECT @IDCabecera 'IDCabecera', nm 'Nombre', od1 'OtroDato1', od2 'OtroDato2'
FROM
OPENXML (@docHandle,'r/dc/d',1)
WITH (
nm nvarchar(20),
od1 int,
od2 datetime
)
COMMIT
RETURN 0
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK
RETURN -1
END CATCH
END
Básicamente hace lo esperado, parsea el XML y lo inserta en las tablas adecuadamente.
Finalmente quedaba un último punto, efectuar la llamada desde la capa de datos en .NET, escribimos el código necesario (en nuestro caso estábamos empleando VS2008) para armar el XML y llamar al procedimiento almacenado, creamos una función similar a la siguiente:
public void Ins_CabDet(string Cab_Nombre, List Detalles) {
using (SqlCommand objSCmd = new SqlCommand("Ins_CabDet", new SqlConnection("Data Source=XXX...."))) {
objSCmd.CommandType = CommandType.StoredProcedure;
objSCmd.Connection.Open();
MemoryStream objMS = new MemoryStream();
using (XmlWriter objXMLW = XmlWriter.Create(objMS))
{
objXMLW.WriteStartElement("r");
objXMLW.WriteStartElement("h");
objXMLW.WriteAttributeString("nm", Cab_Nombre);
objXMLW.WriteEndElement();
objXMLW.WriteStartElement("dc");
foreach (Detalle objDet in Detalles) {
objXMLW.WriteStartElement("d");
objXMLW.WriteAttributeString("nm", objDet.Nombre);
objXMLW.WriteAttributeString("od1", objDet.OtroDato1.ToString());
objXMLW.WriteAttributeString("od2", objDet.OtroDato2.ToString("yyyyMMdd", CultureInfo.InvariantCulture));
objXMLW.WriteEndElement();
}
objXMLW.WriteEndElement();
objXMLW.WriteEndElement();
objMS.Position = 0;
objSCmd.Parameters.Add("@doc", SqlDbType.Xml).Value = new SqlXml(objMS);
}
objSCmd.ExecuteNonQuery();
}
}
Detalle es la estructura:
public struct Detalle {
public string Nombre { set; get; }
public int OtroDato1 { set; get; }
public DateTime OtroDato2 { set; get; }
}
Con esta simple solución pudimos resolver el problema. Un punto a considerar es que en realidad no estamos pasando arreglos, de alguna forma estamos adaptando algo que fue pensado para otra cosa, como una especie de artificio para conseguir nuestro fin, por lo que este tipo de solución no es una solución universal y debería evaluarse en qué casos simplifica y en que otros complica el problema a resolver. Otra opción que no mencionamos es crear un tipo de datos y un procedimiento CLR, aprovechando las ventajas de poder ejecutar código manejado dentro del motor SQL Server 2005, esta es una opción válida que dependiendo del caso también podría implementarse.
Arreglos en SQL 2008
La versión 2008 de Sql Server ha resuelto el problema, finalmente hay arreglos disponibles, aunque los arreglos en T-SQL poseen una visión más cercana al manejo de tablas que al manejo de arreglos de los lenguajes de programación a los que los desarrolladores estamos acostumbrados, esto no provoca ningún problema. La idea aplicada al caso anterior es la siguiente;
suponiendo que seguimos teniendo las mismas tablas Cabecera y Detalle, lo primero que vamos a hacer es crear un tipo de datos de usuario, en SQL Server 2008 no hay que escribir código CLR, la forma de hacerlo es simplemente escribir un script, para nuestro caso el siguiente:
create type TipoDetalle as table
(
Nombre nvarchar(20),
OtroDato1 int,
OtroDato2 DateTime
)
Algo bastante similar a nuestra estructura en C#, una vez creado el tipo de datos ya podemos pasar a crear el procedimiento almacenado
CREATE PROCEDURE Ins_CabDet
(
@Nombre_Cab nvarchar(20),
@Detalles TipoDetalle readonly
)
AS
BEGIN
BEGIN TRY
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRAN
INSERT INTO Cabecera (Nombre) VALUES (@Nombre_Cab)
DECLARE @IDCabecera int
SET @IDCabecera = @@IDENTITY
INSERT INTO
Detalle (IDCabecera,Nombre, OtroDato1, OtroDato2)
SELECT @IDCabecera ,Nombre, OtroDato1, OtroDato2
FROM
@Detalles;
COMMIT
RETURN 0
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK
RETURN -1
END CATCH
END
Claramente puede verse en T-SQL un arreglo es visto como una especie tabla, en cada fila de la tabla hay un elemento (del tipo Detalle en nuestro caso, para eso primero debimos crearlo) y
cada columna representa una propiedad del elemento (Nombre, OtroDato1, etc.), como mencionamos previamente, es un poco diferente a lo usual, pero no puede negarse que es una visión bastante razonable para un motor de base de datos.
Finalmente queda ver como efectuar la llamada desde .NET, en C# la llamada tomaría la siguiente forma:
public void Ins_CabDet(string Cab_Nombre, List Detalles) {
DataTable dt = new DataTable();
dt.Columns.Add("Nombre", typeof(string));
dt.Columns.Add("OtroDato1", typeof(int));
dt.Columns.Add("OtroDato2", typeof(DateTime));
foreach (Detalle objDet in Detalles) {
DataRow dr = dt.NewRow();
dr[0] = objDet.Nombre;
dr[1] = objDet.OtroDato1;
dr[2] = objDet.OtroDato2;
dt.Rows.Add(dr);
}
using (SqlCommand objSCmd = new SqlCommand("Ins_CabDet", new SqlConnection("Data Source=XXX...."))) {
objSCmd.CommandType = CommandType.StoredProcedure;
objSCmd.Connection.Open();
objSCmd.Parameters.Add("@Nombre_Cab", SqlDbType.NVarChar, 20).Value = Cab_Nombre;
objSCmd.Parameters.AddWithValue("@Detalles", dt);
objSCmd.ExecuteNonQuery();
}
}
Un último detalle es que desde en el código C# puede verse como el arreglo es preparado como una tabla, mapeando la estructura que el motor espera recibir.
SQL Server 2008 trae muchas otras novedades como los campos Date (sin Time) y Time (sin Date), tipos de datos espaciales, la sentencia MERGE y otras cosas que seguramente veremos en algún otro artículo si es que las hemos utilizado para resolver algún problema.
VN:F [1.7.3_972] Rating: 9.8/10 (21 votos cast)
5 Comentarios »
Jueves, 18 sep, 2008 @ 12:10 | Por Gustavo Cantero (The Wolf) | .NET, ASP.NET, IIS   |
 |
Cuando accedemos por primera vez a una aplicacion web hecha con .NET el IIS compila las páginas y guarda los assemblies generados (junto con los de la carpeta bin) en una carpeta temporal de cache, la cual está en el mismo lugar donde se instala el framework. Rara vez sucede que el IIS no refresca esta cache, y cuando actualizamos nuestras páginas o assemblies, éstas no se ven reflejadas. Para asegurarnos que esta actualización suceda debemos limpiar esta cache, y para hacerlo debemos seguir los siguientes pasos:
- Detener el IIS, lo cual se puede hacer con el comando
"IISReset /stop"
- Eliminar el contenido de la carpeta “C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files”
- Iniciar nuevamente el IIS, lo cual se puede hacer con el comando
"IISReset"
Luego de esto, al ingresar en nuestra aplicación, el IIS vuelve a compilar el sitio y generar los archivos de cache.
VN:F [1.7.3_972] Rating: 9.5/10 (16 votos cast)
8 Comentarios »
Miércoles, 10 sep, 2008 @ 23:40 | Por Gustavo Cantero (The Wolf) | .NET |
 |
Hace unos meses nos encargaron el desarrollo de una aplicación web, desde la cual se podría buscar Podcasts, ver una vista preliminar de éstos, bajarlos en distintos formatos para dispositivos portátiles, suscribirse a series, recomendarlos, buscar por autor, etc. Estos Podcasts se guardan en “canales”, los cuales se muestran en un árbol (TreeView) en la parte izquierda de la página, la que en realidad está en el MasterPage del sitio. La aplicación funcionaba perfecto, pero nos topamos con un inconveniente: cuando el usuario abría el árbol y seleccionaba un canal la página hacía un Postback y, como es de suponerse, el árbol mantenía su estado actual; pero si el usuario en lugar de pulsar un botón o generar de cualquier otra manera un evento que realizara un Postback pulsaba en un link hacia otra página, el estado de la página anterior y de todos sus controles se perdía incluyendo el del árbol, quedando todos sus nodos cerrados.
Para solucionar esto nos dimos cuenta que debíamos persistir el estado del árbol desde el cliente en cookies, pero aún no sabíamos que guardar! Comenzamos revisando el código HTML generado por el TreeView para intentar obtener de alguna manera el estado de qué nodos estaban abiertos y cuales cerrados, y mientras revisábamos los tags de la página encontramos tres campos ocultos cuyo nombre tenían como prefijo el nombre del árbol:
<input type="hidden" name="ctl00_trvChannels_ExpandState" id="ctl00_ctlChannels_trvChannels_ExpandState" value="unnnunnuununnnnnnnnuuuunnunnnnnnnn" />
<input type="hidden" name="ctl00_trvChannels_SelectedNode" id="ctl00_ctlChannels_trvChannels_SelectedNode" value="" />
<input type="hidden" name="ctl00_trvChannels_PopulateLog" id="ctl00_ctlChannels_trvChannels_PopulateLog" value="" />
Al momento nos dimos cuenta que el árbol guardaba su estado en estos campos, y comprobamos que no lo hacía para comparar el actual con el de la invocación anterior, sino que lo hacía del lado del cliente para mantener su último estado! Lo siguiente a hacer era claro: entender cómo guardaba su estado en estos campos. En nuestro caso sólo nos interesaba saber qué nodos estaban expandidos, por lo cual nos enfocamos en el campo “ctl00_trvChannels_ExpandState”, y luego de “jugar” un rato con los estados llegamos a la conclusión de que cada carácter representa un nodo, ordenado desde el primero del árbol siguiendo por sus hijos (siempre de arriba hacia abajo) antes de seguir con el nodo “vecino”. A continuación muestro el orden con un ejemplo gráfico:
Estos caracteres representan tres posibles estados de un nodo:
- n: el nodo no posee hijos, por lo tanto, no puede expandirse (non expandable)
- c: el nodo posee hijos pero está cerrado (collapsed)
- e: el nodo posee hijos y están visibles, o sea, el nodo está expandido (expanded)
Ahora nos quedaba hacer alguna rutina en JavaScript que guardara este campo en una cookie antes de que el usuario dejara la página actual, para luego leerla en el servidor, lo cual resolvimos con la función que detallo a continuación, a la que llamamos en el evento onbeforeunload del objeto BODY:
function saveTreeViewState(){
document.cookie = "TreeViewState=" + escape(document.getElementById(idTreeViewState).value) + "; path=/;";
}
La variable idTreeViewState aquí utilizada la definimos en el evento Load de la página con el siguiente código:
Page.ClientScript.RegisterClientScriptBlock(
this.GetType(),
"TreeViewState",
"var idTreeViewState='" + trvChannels.ClientID + "_ExpandState';",
true);
Lo próximo a hacer fue modificar la rutina de carga del árbol del lado del servidor para que establezca el estado de los nodos en base al contenido de esta cookie. Esta tarea fue relativamente sencilla, solamente creamos dos variables de clase; la primera llamada strState del tipo string para guardar el contenido de la cookie, y la segunda, llamada intStatePosition y del tipo entero, para utilizarla de contador. Luego, si la página no estaba recibiendo un Postback, por cada nodo cargado en el árbol (en el orden descripto en la imagen anterior) hacemos una llamada a la siguiente función:
private void LoadNodeState(TreeNode Node) {
if (strState != null && strState.Length > ++intStatePosition)
Node.Expanded = strState[intStatePosition] == 'e';
}
Cabe mencionar que la variable intStatePosition la inicializamos en -1.
El código completo de esta página lo detallo a continuación. Hay que tener en cuenta que en este ejemplo el árbol está en un MasterPage, pero no habría ningún problema si estuviera en la página.
public partial class Site1 : System.Web.UI.MasterPage {
private string strState = null;
private int intStatePosition = -1;
protected void Page_Load(object sender, EventArgs e) {
if (!IsPostBack) {
//Estado del árbol almacenado en cookies
if (Request.Cookies.Get("TreeViewState") != null)
strState = Request.Cookies.Get("TreeViewState").Value;
//Nodo raiz del árbol
TreeNode objRootNode = new TreeNode("Nodo raíz");
trvChannels.Nodes.Add(objRootNode);
LoadNodeState(objRootNode);
SubNodes(objRootNode);
}
//Guardo en una variable de JavaScript el ID de cliente del campo oculto
//donde .NET guarda el estado del mismo
Page.ClientScript.RegisterClientScriptBlock(
typeof(Site1),
"TreeViewState",
"var idTreeViewState='" + trvChannels.ClientID + "_ExpandState';",
true);
}
/// <summary>
/// Crea los subnodos de un nodo dado
/// </summary>
/// <param name="ParentNode">Nodo padre</param>
private void SubNodes(TreeNode ParentNode) {
for (int intPos = 1; intPos < 6; intPos++) {
TreeNode objNewNode = new TreeNode();
ParentNode.ChildNodes.Add(objNewNode);
objNewNode.Text = "Nodo " + intPos.ToString();
LoadNodeState(objNewNode);
if (objNewNode.Depth < 4)
SubNodes(objNewNode);
}
}
/// <summary>
/// Establece el estado de "expandido" o no al nodo
/// dependiendo del valor de la cookie
/// </summary>
/// <param name="Node">Nodo</param>
private void LoadNodeState(TreeNode Node) {
if (strState != null && strState.Length > ++intStatePosition)
Node.Expanded = strState[intStatePosition] == 'e';
}
protected void trvChannels_SelectedNodeChanged(object sender, EventArgs e) {
lblSelectedNode.Text = "Nodo seleccionado: " + trvChannels.SelectedNode.ValuePath;
}
}
A continuación les dejo un proyecto realizado con Visual Studio® 2008 con el ejemplo para que prueben el código.

Espero que esta idea les sea de utilidad, y los instamos a que nos dejen sus comentarios.
VN:F [1.7.3_972] Rating: 9.4/10 (7 votos cast)
No tiene comentarios »
Cómo muchos sabrán, ayer salió la versión RTM (ya no un Release Candidate) del Microsoft® SQL Server 2008, el cual bajamos e instalamos en nuestro servidor. El primer inconveniente que encontramos fue que no se pudo hacer un upgrade desde el SQL Server 2005 Standard al SQL Server 2008 Web Edition, lo cual no fue mucho problema, simplemente desinstalamos nuestro SQL actual, instalamos el 2008 y “attachamos” las bases anteriores en el nuevo motor.
Hasta acá funcionó todo bien, pero no todo es tan sencillo, ya que con el Management Studio del SQL Server 2005 no se puede conectar al del SQL Server 2008 (obviamente), entonces intentamos instalar el Management Studio nuevo en las máquinas de desarrollo, las cuales ya tenian instalado el Visual Studio 2008 Professional.
Luego de varios pasos de validaciones, instalaciones de los archivos del instalador (si, aunque suene redundante) e instalaciones de parches (incluido el .NET 3.5 Service Pack 1), llegamos a un último chequeo en el que el instalador nos informa que hay un error en una “regla”. Este error es, ni más ni menos, que el siguiente: “Rule “Previous releases of Microsoft Visual Studio 2008″ failed.” A previous release of Microsoft Visual Studio 2008 is installed on this computer. Upgrade Microsoft Visual Studio 2008 to the SP1 before installing SQL Server 2008, o traducido, “Error en la regla ‘Versiones anteriores de Microsoft Visual Studio 2008′.” En el equipo hay instalada una versión anterior de Microsoft Visual Studio 2008. Actualice Microsoft Visual Studio 2008 al SP1 antes de instalar SQL Server 2008. Lo primero que intentamos hacer es instalar el “Visual Studio 2008 Service Pack 1″ pero, para nuestra sorpresa, sólo hay un Beta de este paquete, el cual bajamos e instalamos de todas formas, pero todo fue inutil, el instalador del SQL Server 2008 seguia devolviéndonos el mismo mensaje. Luego de buscar en el sitio de soporte de Microsoft® encontramos una página (http://support.microsoft.com/kb/956139/en-us) la cual nos dice que antes de instalar el SQL Server 2008 hagamos alguno de los siguientes puntos:
- Instalar una versión comercial del Service Pack 1 para Visual Studio 2008 – el cual aún no existe.
- Desinstalar todos los componentes del Visual Studio 2008 anteriores al Visual Studio 2008 - o sea, todos.
- No instalar ningún componente del SQL Server 2008 que requiera el Visual Studio 2008 – el problema acá es que no dice cuales son los componentes que lo requieren pero, para nuestro pesar, descubrimos que el Managemen Studio es uno que si.
Conclusión
Si ya necesitan utilizar el SQL Server 2008 por alguna de sus nuevas características como las de seguridad o sus nuevos tipos de datos GEOMETRY y GEOGRAPHY, lo mejor es instalarlo en un servidor y conectarse desde desde las máquinas de desarrollo con el Visual Studio o con un Terminal Server al servidor, aunque la mejor opción es esperar que liberen la versión final del Service Pack 1 de Visual Studio 2008, la cual estaria disponible el 11 de Agosto.
Comentarios del 11 de agosto del 2008
A partir de hoy ya se puede bajar el Service Pack 1 final para Visual Studio 2008 desde la siguiente dirección: http://www.microsoft.com/downloads/details.aspx?FamilyId=FBEE1648-7106-44A7-9649-6D9F6D58056E&displaylang=en
VN:F [1.7.3_972] Rating: 0.0/10 (0 votos cast)
Comentarios desactivados
Miércoles, 09 jul, 2008 @ 01:03 | Por Gustavo Cantero (The Wolf) | AJAX, ASP.NET  |
 |
Desarrollando una aplicación web para un cliente hicimos que todas las ventanas de la misma se mostraran con un ModalPopUp de la librería AJAX Control Toolkit de Microsoft, utilizando dentro de ésta el mismo diseño que para el resto del sitio, logrando así una imagen homogénea en toda la aplicación. El primer inconveniente que tuvimos fue cuando nuestro cliente nos solicitó que TODOS los popup de la aplicación fueran iguales, incluidos los de los validadores, los cuales se muestran con un SummaryValidator en un “alert” de JavaScript. Reemplazar esto fue sencillo, ya que simplemente creamos una función llamada “Alert” (nótese que se diferencia de la función original de JavaScript en que la primer letra está en mayúscula) que escribía el mensaje que se le pasara a través de un parámetro dentro del ModalPopUp, lo mostraba, y lo colocaba sobre el resto de los posibles ModalPopUp que hubiesen cambiando el zIndex de los layers, de la misma manera que lo mostramos en el artículo “Deshabilitar controles de la página hasta que finalicen los UpdatePanels”. Luego simplemente reemplazamos la llamada al alert con el siguiente código:
window.alert = Alert;
El código JavaScript quedaba como se muestra a continuación:
function Alert(msg) {
$get('divAlertContent').innerText = msg;
popUpShowed = $find('Alert');
popUpShowed.show();
popUpShowed._backgroundElement.style.zIndex += 10;
popUpShowed._foregroundElement.style.zIndex += 10;
}
y en la página colocamos un código parecido al siguiente (al presentado aquí le quitamos el diseño para que quede más legible):
<asp:Button ID="btnAlert" runat="server" Style="display: none" />
<cc1:ModalPopupExtender ID="mpeAlert" runat="server" Enabled="True" PopupControlID="panAlert" DropShadow="true" TargetControlID="btnAlert" OkControlID="btnAcceptAlert" BehaviorID="Alert" />
<asp:Panel ID="panAlert" runat="server" HorizontalAlign="Center" Style="display: none" Width="370px" Height="100px">
<div id="divAlertContent"></div>
<br />
<asp:Button ID="btnAcceptAlert" runat="server" Text="OK" />
</asp:Panel>
Esto daba como resultado que todos los “alerts” de la aplicación se mostraran en una ventana del mismo diseño.
El siguiente pedido que nos hizo el cliente fue que, al igual que los alerts “tradicionales”, éstos pudieran cerrarse al pulsar la tecla ESC. Para lograr esto modificamos la función anterior para que al mostrar el ModalPopUp guardara una referencia al mismo en una variable global llamada popUpShowed. Luego agregamos una llamada a una función nuestra en el evento “pageLoad” del framework de AJAX, la cual chequea si el usuario pulsó la tecla ESC y, en caso de que popUpShowed no sea nula, cerramos el PopUp al que referencia.
A continuación muestro una página de ejemplo con todo el código para que mostrar la función “Alert” funcionando.
<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="cc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "<a href="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd</a>">
<html xmlns="<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>">
<head runat="server">
<title>Página de ejemplo</title>
<script type="text/javascript">
var popUpShowed = null;
function pageLoad() {
document.body.onkeypress=keyPressHandler;
window.alert=Alert;
}
function showPopUp(popUp){
popUpShowed = $find(popUp);
popUpShowed.show();
}
function Alert(msg){
$get('divAlertContent').innerText = msg;
popUpShowed = $find('Alert');
popUpShowed.show();
popUpShowed._backgroundElement.style.zIndex += 10;
popUpShowed._foregroundElement.style.zIndex += 10;
}
function keyPressHandler(e){
if (popUpShowed != null){
var kC = (window.event) ? event.keyCode : e.keyCode;
var Esc = (window.event) ? 27 : e.DOM_VK_ESCAPE;
if(kC==Esc){
popUpShowed.hide();
popUpShowed = null;
}
}
}
</script>
<style>
.ModalBackground {
background-color: Gray;
filter: alpha(opacity=70);
opacity: 0.7;
}
.ModalPopup {
background-color: #eeeeee;
border-width: 1px;
border-style: solid;
border-color: Gray;
padding: 3px;
font-size: small;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<input type="button" value="Ejemplo" onclick="alert('Ejemplo desde javascript')" />
<asp:Button ID="btnAlert" runat="server" Style="display: none" />
<cc1:ModalPopupExtender ID="mpeAlert" runat="server" Enabled="True" PopupControlID="panAlert"
DropShadow="true" TargetControlID="btnAlert" OkControlID="btnAcceptAlert" BehaviorID="Alert"
BackgroundCssClass="ModalBackground" OnOkScript="popUpShowed = null" />
<asp:Panel ID="panAlert" runat="server" HorizontalAlign="Center" Style="display: none"
Width="370px" Height="100px" CssClass="ModalPopup">
<div id="divAlertContent">
</div>
<br />
<asp:Button ID="btnAcceptAlert" runat="server" Text="OK" />
</asp:Panel>
</form>
</body>
</html>
Por último les dejo un proyecto hecho con Visual Studio 2008 con un ejemplo del “Alert”

Gustavo Cantero (The Wolf)
MCP – MCSD – MCTS
VN:F [1.7.3_972] Rating: 8.1/10 (10 votos cast)
6 Comentarios »
Jueves, 19 jun, 2008 @ 00:30 | Por Gustavo Cantero (The Wolf) | AJAX, ASP.NET  |
 |
Hace unos meses comenzamos a desarrollar para un cliente una aplicación web más o menos compleja, con .NET 3.5 y bastante uso de AJAX. En esta aplicación hay páginas que utilizan varios UpdatePanels, y algunos de estos realizan procesos complejos que pueden llegar a demorar varios segundos, es por esto que nuestro cliente nos pidió que, al iniciar estos procesos, se bloquee la página para que el usuario no pueda ejecutar ninguna otra acción en la misma hasta tanto no finalice el proceso pendiente. Obviamente no queríamos ejecutar un script “a mano” cada vez que se ejecutaba una acción y otro al finalizar la misma, así que buscamos la manera de automatizarlo un poco. Comenzamos buscando los eventos propios del motor de AJAX de Microsoft® y encontramos que podemos capturar los del RequestManager cuando inicia una petición al servidor y cuando la misma finaliza. Esto lo podemos hacer utilizando los métodos add_initializeRequest y add_endRequest de la instancia actual del PageRequestManager, la cual podemos obtener con el siguiente código: Sys.WebForms.PageRequestManager.getInstance().
En el primer evento mostrábamos un ModalPopUp (control que muestra una “ventana” en la página y deshabilita todos los demás controles de la misma) de la librería ASP.NET AJAX Control Toolkit de Microsoft®, el cual contenía un mensaje que decía “Aguarde un momento…”, y en el segundo evento lo cerrábamos. A continuación se muestra como quedaba el PopUp en la página:
<cc1:ModalPopupExtender ID="mpeLoading" runat="server" BehaviorID="idmpeLoading" PopupControlID="pnlLoading" TargetControlID="btnLoading" EnableViewState="false" DropShadow="true" BackgroundCssClass="ModalBackground" />
<asp:Panel ID="pnlLoading" runat="server" Width="300" Height="50" HorizontalAlign="Center" CssClass="ModalPopup" EnableViewState="false" Style="display: none">
<br />Aguarde un momento...</asp:Panel>
<asp:Button ID="btnLoading" runat="server" Style="display: none" />
y acá está el código JavaScript:
function initializeRequest(sender, args){
$find('idmpeLoading').show();
}
function endRequest(sender, args){
$find('idmpeLoading').hide();
}
Sys.WebForms.PageRequestManager.getInstance().add_initializeRequest(initializeRequest);
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(endRequest);
Cabe aclarar que la variable idmpeLoading del código anterior contiene el identificador del ModalPopUp a mostrar.
Hasta este punto todo funcionaba bien, el problema surgió cuando utilizamos paneles modales con UpdatePanels dentro, en ese momento la ventana con el mensaje “Aguarde un momento…” se “dibujaba” debajo del panel que estaba utilizando el usuario, no cumpliendo con su finalidad, ya que de esta manera no se deshabilitaban todos los controles de la página.
Paso siguiente revisamos el código del ModalPopUp (el mismo se puede bajar desde el mismo link que mostré antes) y encontramos que a los paneles se les estable un valor fijo en el estilo zIndex para mostrarlos sobre los demás controles. Luego revisamos las propiedades del objeto de JavaScript que representa el panel y encontramos que posee dos objetos interesantes: _backgroundElement y _foregroundElement. Estos representan los DIVs del fondo y del contenido de PopUp respectivamente, entonces para hacer que estos objetos de HTML se posicionen sobre todos los demás simplemente tenemos que incrementarles la propiedad zIndex de su estilo. Entonces, nuestro código JavaScript queda como se muestra a continuación:
function initializeRequest(sender, args){
var mpeLoading = $find(idmpeLoading);
mpeLoading.show();
mpeLoading._backgroundElement.style.zIndex += 10;
mpeLoading._foregroundElement.style.zIndex += 10;
}
function endRequest(sender, args){
$find(idmpeLoading).hide();
}
Sys.WebForms.PageRequestManager.getInstance().add_initializeRequest(initializeRequest);
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(endRequest);
Por último, para hacer que esta funcionalidad esté presente en todas las páginas de nuestra aplicación, lo agregamos en la página “master” del sitio, logrando que en cualquier página (que utilice esta master) posea la funcionalidad aquí descripta.
En la siguiente imagen muestro como queda una aplicación de ejemplo que cree con Visual Studio 2008 para este artículo:

A continuación dejo el proyecto con el código fuente para quien quiera utilizar esta utilidad:

Gustavo Cantero (The Wolf)
MCP – MCSD – MCTS
VN:F [1.7.3_972] Rating: 8.5/10 (11 votos cast)
6 Comentarios »
|
|