Usando ASP.NET membership en SQL Azure

SQLAzure

El problema

En ocasiones nos olvidamos de que SQL Azure no es lo mismo que SQL Server.

Hoy mismo me he encontrado con una situación de aquellas que te hacen exclamar D’OH!!!, y que afortunadamente he podido solucionar, porque si no ahora mismo no estaría escribiendo este post :-)

Nota: Antes de seguir, aviso que no voy a entrar en opiniones acerca del sistema de membresía de ASP.NET, todos sabemos que los requerimientos mandan, y si te toca utilizar una herramienta -aunque esté tan mal diseñada como ésta- la usas y punto.

Volviendo al tema, en el proyecto que me ocupa actualmente necesitábamos utilizar ASP.NET membership en una base de datos de SQL Azure. En principio, a nadie se le había ocurrido que no puede hacerse. Al menos no directamente, vaya… Así que cuando he querido utilizar nuestro querido y odiado aspnet_regsql, me he quedado con cara de bobo al fallar el proceso estrepitosamente y mostrarse este error:

An error occurred during the execution of the SQL file ‘InstallCommon.sql’. The SQL error number is 208 and the SqlException message is: Invalid object name ‘master.dbo.sysdatabases’.

Si lo pensamos detenidamente tampoco no es nada extraño. Es normal que no podamos acceder a sysdatabases, de hecho, si lanzamos una consulta como esta en SQL Azure:

SELECT * FROM master.dbo.sysdatabases

Recibiremos un mensaje muy clarito al respecto:

Reference to database and/or server name in ‘master.dbo.sysdatabases’ is not supported in this version of SQL Server.

La solución

Está bien documentada en MSDN archive, y contiene un juego nuevo de scripts para la creación de la base de datos de membresía, adaptado a las particularidades de SQL Azure. Lo podéis encontrar y descargar en esta url:

http://archive.msdn.microsoft.com/KB2006191

Existen dos ficheros a descargar, aunque nos basta con InstallAspSchema.zip, el cual contiene los scripts separados por temas (membresía, roles, etc.) lo cual me parece genial, ya que así vamos a instalar sólo las partes que nos interesan.

aspnet_scripts

Es importante instalarlos en el orden correcto, así que el primero debe ser InstallCommon.sql, y luego ya podemos instalar el resto (en mi caso con InstallMemberShip y InstallRoles tengo más que suficiente).

Nota: En mi caso he tenido que pasar 2 veces el script de membresía ya que la primera vez se había dejado por crear algunas tablas, entre ellas la de aspnet_MemberShip (que guarda entre otros datos el password cifrado). Si alguno de vosotros lo prueba por favor, darme feedback para ver si es un problema común o ha sido algo particular.

Saludos y nos vemos!

More info.

General Guidelines and Limitations (SQL Azure Database)

http://msdn.microsoft.com/en-us/library/ee336245.aspx

[HowTo] Obtener el valor de una propiedad de usuario en LDAP

El motivo

A raíz de una pregunta que me lanzó ayer mi colega Ricardo, hoy vamos a ver cómo obtener el valor de una propiedad de un usuario del directorio activo de la organización. Esto de por si, no tiene ningún secreto, basta con utilizar las clases contenidas en el namespace System.DirectoryServices. Lo que sí tenemos que tener muy claro, es el nombre de esa propiedad dentro del esquema del LDAP de nuestra organización.

GetPropADUser

Por ejemplo, para obtener el mail, la propiedad a utilizar es mail, obvio, no? Sin embargo en otras ocasiones no siempre es así. Por ejemplo, el nombre de la propiedad que nos devuelve el teléfono de un usuario es telephoneNumber, el código postal es postalCode, pero por ejemplo la propiedad que nos devuelve los apellidos, no es surname, sino sn, o el nombre del usuario está definido como sAMAccountName. En resumen, muchas de las propiedades están definidas con unos nombres… como decirlo? Muy cachondos :-P

Obtener los atributos de una clase

A todo esto ¿Cómo podemos obtener los nombres de los atributos de un usuario del LDAP en tiempo de ejecución? Easy –> Buscando la definición de la clase user en el esquema de nuestro LDAP y obteniendo su catálogo de propiedades (básicas + extendidas):

[sourcecode language="csharp" wraplines="false"]

public static List<string> GetUserLDAPProperties(string LDAPUrl)
{
List<string> properties = new List<string>();
ActiveDirectorySchema adSchema = ActiveDirectorySchema.GetCurrentSchema();
ActiveDirectorySchemaClass userSchema =
default(ActiveDirectorySchemaClass);
ActiveDirectorySchemaPropertyCollection propertiesCollection =
default(ActiveDirectorySchemaPropertyCollection);
userSchema = adSchema.FindClass(“user”);
propertiesCollection = userSchema.MandatoryProperties;
foreach (ActiveDirectorySchemaProperty prop in propertiesCollection)
{
properties.Add(prop.Name);
}
propertiesCollection = userSchema.OptionalProperties;
foreach (ActiveDirectorySchemaProperty prop in propertiesCollection)
{
properties.Add(prop.Name);
}
properties.Sort();
return properties;
}

[/sourcecode]

De este modo obtenemos los nombres de las propiedades, con lo que ya sabemos los nombres de los atributos de un usuario de nuestro LDAP. Así pues, a partir del nombre de la propiedad que deseamos obtener, del identificador de seguridad de un usuario, y del nombre del dominio, ahora si vamos a ser capaces de obtener el valor de ese atributo para un usuario de nuestro LDAP (siempre que tenga ese atributo definido, claro):

[sourcecode language="csharp" wraplines="false"]

public static string GetNTAccountProperty(string sid, string domain, string propertyToLoad)
{
if (string.IsNullOrEmpty(sid)) throw new ArgumentNullException();
if (string.IsNullOrEmpty(domain)) throw new ArgumentNullException();
if (string.IsNullOrEmpty(propertyToLoad)) throw new ArgumentNullException();
string ldapDomainName = GetLDAPDomainName(domain);
using (DirectoryEntry entries = new DirectoryEntry(ldapDomainName))
{
string filter = string.Format(
“(&(objectCategory=person)(objectClass=user)(objectSID={0}))”, sid);
DirectorySearcher searcher = new DirectorySearcher(entries, filter);
searcher.PropertiesToLoad.Add(propertyToLoad);
searcher.PropertiesToLoad.Add(“objectSID”);
SearchResult result = searcher.FindOne();
if (!result.Properties.Contains(propertyToLoad))
throw new ActiveDirectoryObjectNotFoundException(
string.Format(“Property ‘{0}’ not found on NTAccount ‘{1}’”,
propertyToLoad, sid));
return result.Properties[propertyToLoad][0].ToString();
}
}

[/sourcecode]

Métodos de apoyo

Muchas de éstos métodos utilizan el nombre de nuestro LDAP en el siguiente formato: LDAP://DC=local,DC=miempresa,DC=com

Así que aquí tenéis una función que lo obtiene a partir del nombre del dominio:

[sourcecode language="csharp" wraplines="false"]

public static string GetLDAPDomainName(string domainName)
{
StringBuilder sb = new StringBuilder();
if (string.IsNullOrEmpty(domainName)) throw new ArgumentNullException();
string[] dcItems = domainName.Split(“.”.ToCharArray());
sb.Append(“LDAP://”);
foreach (string item in dcItems)
{
sb.AppendFormat(“DC={0},”, item);
}
return sb.ToString().Substring(0, sb.ToString().Length – 1);
}

[/sourcecode]

Que a su vez se obtiene de este otro método:

[sourcecode language="csharp" wraplines="false"]

public static string GetDomainName()
{
return IPGlobalProperties.GetIPGlobalProperties().DomainName;
}

[/sourcecode]

Proyecto de ejemplo

Hay algunas cosillas más, como la obtención de los usuarios del LDAP que (para no hacer el post más ‘tocho’ :-P) no hemos visto en el post. Podéis descargar el proyecto de ejemplo completo desde skydrive en esta ubicación:

http://cid-f3a970280830b5fe.office.live.com/self.aspx/MSDN%20Samples/TestLDAP.zip

Uffff… al final me ha salido un post más largo de lo que yo quería, pero bueno, espero que os sirva de algo :-)

Saludos desde Andorra,

[Partners] Microsoft Platform Ready

mpr_logo


¿Eres Microsoft Partner o lo tiene en mente?


Entonces tengo buenas notícias! El día 29 de Octubre se lanzo Microsoft Partner Network (MPN). Después de más de un año en transición, los nuevos niveles y los nuevos requisitos por competencias se exigirán a partir de esa fecha para los partners nuevos y para aquellos que tengan que renovar a partir de la fecha de lanzamiento del nuevo Partner Network (hasta la fecha de renovación se mantendrá el nivel y beneficios que se ostentará en el MSPP). MPN sustituye al antiguo Micrososft Partner Program (MSPP) que se lanzó hace 8 años.


Este cambio potencia la especialización de nuestro canal y la diferenciación, y responde a la demanda de nuestros clientes y partners. El programa tiene los siguientes niveles: Gold, Silver, Subscription y Community, y 30 competencias (Virtualización, Business inteligence, Application Platform, ISVs…).


Hay cambios en cuanto a los requisitos de las competencias tanto para el nivel Silver (antiguo Certified) como para el nivel Gold. Desde el punto de vista de los ISVs supone la incorporación de nuevas certificaciones de productos válidas para los dos niveles.


Para conseguir la competencia ISVs en el nivel Silver se solicitará una aplicación certificada con uno de los siguientes test:


  • Windows 7 Platform Ready
  • Windows Server 2008 R2 Platform Ready
  • Windows Azure Platform Ready
  • SQL Azure Platform Ready
  • Microsoft Surface Test

Los test que hasta ahora tuvierais hechos serán validos para la competencia hasta Mayo de 2011, pero es momento de ir alineándose con los nuevos requisitos. Ya puedes realizar los nuevos Test a través de Microsoft Platform Ready de forma rápida, sencilla y sin ningún coste para el partner.


http://www.microsoftplatformready.com/spain/home.aspx


Date de alta en Microsoft Platform Ready con tu usuario y contraseña del programa de partners, da de alta todas tus aplicaciones, independientemente de la fase en la que estén (desarrollo, pruebas, producción, etc) y aprovecha los recursos de MPR (recursos de desarrollo, realiza test de certificación, material de marketing, etc).


Si realizas algunos de los test, estos automáticamente volcaran en MPN y podrás asociarlo a la competencia para la que sean válidos.


A parte de ISVs, hay otras competencias que permitirán la certificación de una aplicación para obtenerla en su nivel Silver.


  • Application Integration  =  Windows Server 2008 R2 Platform Ready
  • Data Platform  =  SQL Server 2008 R2 Platform Ready
  • Business Intelligent  =  SQL Server 2008 R2 Platform Ready
  • Content Management   =  Sharepoint Server 2010 Platform Ready (proximamente disponible)
  • Unified Communication  =  Unified Communicatios Platform Ready ( aun no disponible)

Para aquellos que queráis optar a la competencia Gold de ISVs los test que se solicitaran son:


  • Certified for Windows Server 2008 R2 o
  • Windows 7 Logo Test

Un saludo,

Error en deployment SAP Crystal Reports 2010

SAPLogo

Hola a todos!

Un post rapidito: Al intentar distribuir mediante ClickOnce una aplicación Winforms que utiliza Crystal Reports 2010 (si, algunos de nosotros todavía usamos Winforms y el viejo CR), es posible que durante la instalación os encontréis con este error:

Setup has detected that the file ‘C:\…\Crystal Reports for .NET Framework 4.0\CRRuntime_32bit_13_0.msi’ has changed since it was initially published. Click OK to retry the download, or Cancel to exit setup.

El problema es que uno de los ficheros (Product.xml) está firmado con una clave pública incorrecta. Para que no os tengáis que pelear con lo mismo que yo (aunque al fin y al cabo buceando un poco por los foros de SAP lo encuentras rápido), aquí está la solución:

  1. Descargar una nueva versión del fichero Products.xml desde esta ubicación (en realidad es un ZIP).
  2. Descomprimir el ZIP en cualquier carpeta.
  3. Copiar el fichero Product.xml a la siguiente ubicación:

C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bootstrapper\Packages\Crystal Reports for .NET Framework 4.0

(*) Si no estáis en un entorno x64 debéis utilizar ‘Program Files’ sin el x86.

Luego recompiláis la aplicación, publicáis otra vez, y listos!

Un saludo,

[Debate] Nombre de Namespaces en métodos extensores ¿Y tu que opinas?

Ayer, a raíz de un post del colega Javier Torrecilla sobre métodos extensores, unos cuantos de nosotros entre los que estaban el propio Javier y Jorge Serrano nos enzarzamos en una discusión en twitter acerca del mejor modo de declarar nuestros métodos extensores.

Pongamos un ejemplo: Supongamos que queremos crear un método extensor para comprobar si un valor está entre dos valores (el clásico between de toda la vida).

Agrego una clase llamada ExtensionMethods a mi proyecto, o a otro proyecto mi solución y agrego este código:

namespace CustomExtensions


{


    public static class ExtensionMethods


    {


        public static bool Between<T>(this T @value, T min, T max) where T : IComparable<T>


        {


            return @value.CompareTo(min) >= 0 && @value.CompareTo(max) <= 0;


        }


    }


}

Suponiendo que estamos en un proyecto de tipo WinForms, si queremos utilizar este método extensor sobre un valor de tipo int basta con ir a cualquier formulario y llamar al método between sobre un valor de este tipo. Por ejemplo:

EM1

Oops! Que pasa? Por que no aparece el método extensor? Bueno, como ya os habréis dado cuenta el método extensor está declarado dentro de un namespce llamado ‘CustomExtensions’, que es distinto al namespace del formulario en el que lo estoy probando, con lo que no podemos usarlo directamente si previamente no hacemos un using:

using CustomExtensions;

Vale, ahora si que aparece:

EM2

Bien. Esto en si no es nada del otro mundo, pero la cuestión es que si deseamos evitar declarar el using (tenéis que pensar que este método extensor lo podéis reutilizar en 1000 proyectos distintos), no tenemos otra opción que:

  1. Declarar el método extensor en un namespace que se llame igual que el namespace en el que se va a usar.
  2. Declarar el método extensor en un namespace que se llame igual que el namespace del tipo que estamos extendiendo.
  3. Pasar de todo y llamarlo al namespace como queramos, y que a la hora de usarlo debamos usar un using para agregarlo.

Particularmente soy partidario del segundo punto, de modo que si vamos a extender elementos de tipo IComparable, en lugar de usar el namespace ‘CustomExtensions’ prefiero usar el nombre del namespace que contiene la definición de este tipo, o sea ‘System’:

namespace System


{


    public static class ExtensionMethods


    {


        public static bool Between<T>(this T @value, T min, T max) where T : IComparable<T>


        {


            return @value.CompareTo(min) >= 0 && @value.CompareTo(max) <= 0;


        }


    }


}



Pero ese es mi punto de vista, tu que opinas? Twitteros manifestaos! :-)

Redimensionar imágenes, convertirlas a byte array y viceversa (con transparencia)

El título del post es algo largo, pero resume un problema que me volvía de cabeza desde hace un tiempo, y que no era capaz de resolver… hasta hoy.

Cuando trabajamos con imágenes en una aplicación suele ser muy común almacenarlas en una base de datos. En el caso que me ocupa, al ser imágenes con una resolución bastante alta, un requisito es que éstas deben almacenarse a distintas resoluciones. Sin embargo, antes de continuar con el tema permitidme un paréntesis:

<PARENTESIS MODE = “on”>

Sé que existen bastantes detractores de ésta práctica, que suelen preferir guardar las imágenes en disco, pero esto a mi juicio conlleva una serie de inconvenientes:

  1. Pérdida de atomicidad: Mezclamos un sistema transaccional con un sistema de ficheros no transaccional (y no, de momento no recomiendo usar transacciones en el sistema de ficheros, al menos si no queréis quedaros calvos en el proceso). De modo que como no disponemos de un mecanismo transaccional, debemos implementar mecanismos de sincronización ‘a manija’ entre la base de datos y el sistema de ficheros, con todo lo que conlleva.
  2. Problemas al hacer copias de seguridad: Ya que mediante el SQL Server agent podemos planificar copias periódicas de la base de datos, pero no de los ficheros asociados. Así pues, hay que copiar los ficheros manualmente o lanzando un proceso desde nuestra aplicación.
  3. También suele argumentarse que si guardamos las imágenes en la base de datos, el tamaño de la base de datos puede incrementarse mucho y degradarse el rendimiento (recordar que SQL Server Express ‘sólo’ admite bases de datos de hasta 10GB). Esto no es cierto, ya que desde la versión 2008 existe la posibilidad de utilizar FILESTREAM, que permite almacenar los datos de un campo en el sistema de ficheros, obteniendo así lo mejor de ambos mundos.

<PARENTESIS MODE= “off”>

Vale, sigamos con el tema.

Como os decía, en el proyecto que me ocupa actualmente un requisito muy importante es almacenar distintas resoluciones de una imagen en la base de datos mediante FILESTREAM. Para ello, hay que redimensionar cada una de las imágenes y convertirlas en un array de bytes, para luego almacenarlas en un campo de tipo BLOB, concretamente varbinary(MAX). Posteriormente cuando queremos recuperar una imagen, se lee el array de bytes y se transforma otra vez en imagen para visualizarla por pantalla, imprimirla, o lo que sea…

Redimensionar imagenes

Cuál es el problema entonces? Existen multitud de ejemplos en Internet acerca de cómo redimensionar imágenes:

public static Image ResizeImage(this Image oldImage, int targetSize)


{


    Size newSize = calculateDimensions(oldImage.Size, targetSize);


    using (Bitmap newImage = new Bitmap(newSize.Width, 


        newSize.Height, PixelFormat.Format24bppRgb))


    {


        using (Graphics canvas = Graphics.FromImage(newImage))


        {


            canvas.SmoothingMode = SmoothingMode.AntiAlias;


            canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;


            canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;


            canvas.DrawImage(oldImage, new Rectangle(new Point(0, 0), newSize));


            using (MemoryStream m = new MemoryStream())


            {


                newImage.Save(m, ImageFormat.Jpeg);


                return (Image)newImage.Clone();


            }


        }


    }


}

El código anterior funciona bien en casi todos los casos, pero no cuando la imagen a redimensionar contiene partes transparentes, ya que las partes transparentes aparecen en negro. Esto es así porque la información de transparencia de una imagen se almacena en el canal alfa, y en el código anterior al crear el nuevo Bitmap estamos usando explícitamente el valor ‘Format24bppRgb’ de la enumeración PixelFormat, que almacena 8 bits para cada color primario.

images_fail

En su lugar, debemos utilizar el valor ‘Format32bppRgb’ que almacena 8 bits para cada color primario más 8 bits para el canal alfa. También podemos omitir el formato en el constructor y pasar sólo el ancho y alto, ya que por defecto se usará el valor ‘Format32bppRgb’ en caso que no sea suministrado.

De todos modos, el código anterior es sólo a efectos de ilustrar el ejemplo, ya que para redimensionar una imagen es mucho más sencillo usar el método ‘GetThumbnailImage’ de la clase ‘Image’:

public static Image ResizeImage(this Image oldImage, int targetSize)


{


    Size newSize = calculateDimensions(oldImage.Size, targetSize);


    return oldImage.GetThumbnailImage(newSize.Width, newSize.Height, () => false, IntPtr.Zero); 


}

Convirtiendo imágenes a bytes y viceversa

También existen multitud de ejemplos acerca de convertir imágenes a matrices y a la inversa. Veamos algunos ejemplos:

1) Mediante un MemoryStream: en este ejemplo se vuelca la imagen en un stream en memoria, y posteriormente se transforma en un array.

public static byte[] ConvertImageToByteArray(System.Drawing.Image imageIn)


{


    using (System.IO.MemoryStream ms = new System.IO.MemoryStream())


    {


        imageIn.Save(ms, ImageFormat.Jpeg);


        return ms.ToArray();


    }


}


 


public static Image ConvertByteArrayToImage(byte[] byteArrayIn)


{


    using (System.IO.MemoryStream ms = new System.IO.MemoryStream(byteArrayIn))


    {


        Image returnImage = Image.FromStream(ms);


        return returnImage;


    }


}

Resultaría muy sencillo si no fuese porque al convertir la imagen a un array volvemos a tener el problema de las transparencias.

2) Otro método es utilizar código unsafe para copiar literalmente los bits de la imagen a un array:

private unsafe byte[] BmpToBytes_Unsafe(Bitmap bmp)


{


    BitmapData bData = bmp.LockBits(new Rectangle(new Point(), bmp.Size),


        ImageLockMode.ReadOnly,


        PixelFormat.Format32bppArgb);


    int byteCount = bData.Stride * bmp.Height;


    byte[] bmpBytes = new byte[byteCount];


    Marshal.Copy(bData.Scan0, bmpBytes, 0, byteCount);


    bmp.UnlockBits(bData);


    return bmpBytes;


}


 


private unsafe Bitmap BytesToBmp_Unsafe(byte[] bmpBytes, Size imageSize)


{


    Bitmap bmp = new Bitmap(imageSize.Width, imageSize.Height);


    BitmapData bData = bmp.LockBits(new Rectangle(new Point(), bmp.Size),


        ImageLockMode.WriteOnly,


        PixelFormat.Format32bppArgb);


    Marshal.Copy(bmpBytes, 0, bData.Scan0, bmpBytes.Length);


    bmp.UnlockBits(bData);


    return bmp;


}

Sin duda éste método ofrece un mayor rendimiento, y además al especificar el formato ‘Format32bppArgb’ nos soluciona el problema de las transparencias, pero resulta que nos crea otro problema: Para posteriormente poder revertir el array a imagen necesitamos conocer el tamaño de la imagen original, y eso no es demasiado práctico.

AL final la solución ha sido mucho más simple y porque no, mucho más elegante: Usando un simple TypeConverter.ConvertTo:

public static byte[] ConvertImageToByteArray(System.Drawing.Image imageIn)


{


    return (byte[])TypeDescriptor.GetConverter(imageIn).ConvertTo(imageIn, typeof(byte[]));


}



images_success



En fin, espero que si alguien ha estado en la misma situación que yo, al menos este post le resuelva un poco la vida :-)



Saludos desde Andorra a punto de cerrar el año,

HowTo: Crear una pantalla de inicio (splash screen)

Nota: Otro post en respuesta a una pregunta bastante habitual en los foros MSDN: ¿Cómo crear una pantalla de inicio para mi aplicación?

He creado un pequeño proyecto de ejemplo, que pueda servir como plantilla base para que cada uno se lo personalice para su aplicación. Este proyecto tiene lo básico: Un formulario sin bordes con una imagen, una barra de progreso, una etiqueta para el título, otra para ir mostrando mensajes, y un botón por si se desea cancelar la carga del programa (al estilo Office 2010).

SplashScreen

Él proyecto es muy sencillo y lo podéis descargar desde aquí:

La pantalla de inicio utiliza un thread para mostrar los diferentes mensajes al cargar, ya que así no se bloquea la aplicación (y la barra de progreso). Esto es así porque en el proyecto de ejemplo, al cargar la pantalla de inicio se lanza un segundo hilo que llama a un método ‘initApplication’, y desde éste método simulamos varios procesos largos (en realidad de un segundo cada uno), y cada vez que se inicia uno de ellos hay que cambiar el mensaje:

public void initApplication()


{


    Thread.Sleep(DEFAULT_TIME);


    this.Invoke((MethodInvoker)(() => setMessage("Searching for updates...")));


    Thread.Sleep(DEFAULT_TIME);


    this.Invoke((MethodInvoker)(() => setMessage("Connectiong to database...")));


    Thread.Sleep(DEFAULT_TIME);


    this.Invoke((MethodInvoker)(() => setMessage("Connectiong to webservices...")));


    Thread.Sleep(DEFAULT_TIME);


    this.Invoke((MethodInvoker)(() => setMessage("Loading settings...")));


    Thread.Sleep(DEFAULT_TIME);


    this.Invoke((MethodInvoker)(() => setMessage("Loading user preferences...")));


    Thread.Sleep(DEFAULT_TIME);


    this.Invoke((MethodInvoker)(() => setMessage("Starting application...")));


    Thread.Sleep(DEFAULT_TIME);


    if (this.InvokeRequired) this.Invoke(new Action(finishProcess));


}

Recordar que desde un hilo que no sea el hilo principal, en .NET no se puede actualizar la interfaz de usuario directamente. En su lugar debemos usar el método Invoke. En el ejemplo anterior llamamos a un método ‘setMessage’ que se encarga de mostrar el texto en la etiqueta correspondiente. Para poder llamar a este método mediante Invoke tenemos dos opciones: Podemos usar un MethodInvoker o un Action, en nuestro caso usaremos el primero, ya que un Action se usa cuando no hay paso de parámetros, y este método precisa de un parámetro con el mensaje a mostrar:

public void setMessage(string msg)


{


    messageLabel.Text = msg;


}

Una vez finalizado el proceso de carga, se cierra el formulario y se devuelve un DialogResult = Ok. Por otro lado si en cualquier momento de la carga el usuario ha pulsado el botón ‘close’, se hace lo mismo pero devolviendo un DialogResult = Cancel:

private void finishProcess()


{


    this.DialogResult = System.Windows.Forms.DialogResult.OK;


    this.Close();


}

void closeButton_Click(object sender, EventArgs e)


{


    this.DialogResult = System.Windows.Forms.DialogResult.Cancel;


    this.Close();


}

Como veis el proyecto es muy sencillo, sólo debéis recordar un detalle importante: En una aplicación WinForms el punto de entrada a la misma se define en el método estático Main del Program.cs, y aquí hay una línea que inicializa el formulario inicial de nuestra aplicación:

Application.Run(new fMain());

Lo primero que solemos pensar es que aquí deberíamos lanzar el formulario fSplashScreen, y al cerrarlo mostrar el formulario principal, verdad? Pues no, no podemos hacer eso. El motivo no es otro que este formulario inicial va a definir el ciclo de vida de nuestra aplicación, y si lo cerramos, cerramos la aplicación. Ya se que en VB puede cambiarse este comportamiento, pero entre nosotros… hacerlo siempre me ha parecido una chapuza :-)

Así pues, aquí lanzaremos el formulario principal, y éste, al cargarse (mientras todavía no es visible) lanzará la pantalla de inicio de forma modal y esperará el valor de retorno. Si al cerrarse la pantalla de bienvenida el valor de retorno es Ok, continúa la carga y muestra el formulario principal, en caso contrario cierra el formulario principal y con por ende la aplicación:

void fMain_Load(object sender, EventArgs e)


{


    showSplashScreen();


}


 


private void showSplashScreen()


{


    using (fSplashScreen fsplash = new fSplashScreen())


    {


        if (fsplash.ShowDialog() == System.Windows.Forms.DialogResult.Cancel) this.Close();


    }


}



Espero que sirva como ejemplo a todos aquellos que desean tener una pantalla de inicio para sus aplicaciones. Un saludo!



Andorra, Noviembre 2010

Materiales de la charla sobre computación paralela en BcnDev

Hola de nuevo,

Después de la charla del viernes en BCNDEV, lo prometido es deuda. Os comenté que había realizado las demos en forma de un pequeño proyecto web, para no tener que ir con un pedazo-de-pepino a hacer las demos (más que nada porque no lo tengo :-P). Así que sólo quiero dejaros el enlace al proyecto que usé para las demos de la TPL, por si alguno las quiere probar:

Os recuerdo que si el proyecto lo probáis en una máquina virtual poco paralelismo vais a ver, pero publicarlo en el host o en cualquier estación con varios cores y veréis la diferencia de rendimiento. En algunos casos no hay mucha ganancia, pero en algunos otros la diferencia es de 5 a 1 o más.

Aprovecho para dejaros la versión más reciente de la presentación, y os prometo que no la voy a actualizar más :-) que bastante lo he hecho en los últimos días:

Saludos a todos,

HowTo: Crear un hook de teclado para registrar una hotkey en nuestra aplicación

hook

Hola de nuevo,

En los grupos de MSDN suele haber bastantes preguntas acerca de crear hooks de teclado, para que nuestra aplicación pueda ejecutar alguna acción determinada, en respuesta a alguna pulsación de teclado, aunque no esté activa.

He creado una pequeña clase llamada WindowsShell que contiene el código necesario para registrar una hotkey y asociarla a un formulario:

   1: using System;


   2: using System.Runtime.InteropServices;


   3: using System.Windows.Forms;


   4:  


   5: namespace TestRegisterHotKey


   6: {


   7:     public class WindowsShell


   8:     {


   9:         public enum ModifierEnum


  10:         {


  11:             MOD_ALT = 0x1,


  12:             MOD_CONTROL = 0x2,


  13:             MOD_SHIFT = 0x4,


  14:             MOD_WIN = 0x8


  15:         }


  16:  


  17:         private static int keyId;


  18:         public static int WM_HOTKEY = 0x312;


  19:  


  20:         [DllImport("user32.dll")]


  21:         private static extern bool RegisterHotKey(


  22:             IntPtr hWnd, int id, int fsModifiers, int vlc);


  23:  


  24:         [DllImport("user32.dll")]


  25:         private static extern bool UnregisterHotKey(


  26:             IntPtr hWnd, int id);


  27:  


  28:         public static void RegisterHotKey(Form f, Keys key)


  29:         {


  30:             int modifiers = 0;


  31:             if ((key & Keys.Alt) == Keys.Alt)


  32:                 modifiers = modifiers | (int)WindowsShell.ModifierEnum.MOD_ALT;


  33:             if ((key & Keys.Control) == Keys.Control)


  34:                 modifiers = modifiers | (int)WindowsShell.ModifierEnum.MOD_CONTROL;


  35:             if ((key & Keys.Shift) == Keys.Shift)


  36:                 modifiers = modifiers | (int)WindowsShell.ModifierEnum.MOD_SHIFT;


  37:             Keys k = key & ~Keys.Control & ~Keys.Shift & ~Keys.Alt;


  38:             f.Invoke(new Action(() =>


  39:             {


  40:                 keyId = f.GetHashCode();


  41:                 RegisterHotKey((IntPtr)f.Handle, keyId, modifiers, (int)k);


  42:             }));


  43:         }


  44:  


  45:         public static void UnregisterHotKey(Form f)


  46:         {


  47:             try


  48:             {


  49:                 f.Invoke(new Action(() =>


  50:                 {


  51:                     UnregisterHotKey(f.Handle, keyId);


  52:                 }));


  53:             }


  54:             catch (Exception)


  55:             {


  56:                 throw;


  57:             }


  58:         }


  59:  


  60:     }


  61: }

Posteriormente, para poder usar esta clase en un formulario cualquiera debemos interceptar el bucle de mensajes de Windows, y preguntar si se ha producido el mensaje en cuestión, y si la pulsación corresponde a nuestra hotkey:

   1: using System;


   2: using System.Windows.Forms;


   3:  


   4: namespace TestRegisterHotKey


   5: {


   6:     public partial class Form1 : Form


   7:     {        


   8:         public Form1()


   9:         {


  10:             InitializeComponent();


  11:             this.Shown += Form1_Shown;


  12:             this.FormClosed += Form1_FormClosed;


  13:         }


  14:  


  15:         void Form1_FormClosed(object sender, FormClosedEventArgs e)


  16:         {


  17:             WindowsShell.UnregisterHotKey(this);


  18:         }


  19:  


  20:         void Form1_Shown(object sender, EventArgs e)


  21:         {


  22:             Keys k = Keys.A | Keys.Control;


  23:             WindowsShell.RegisterHotKey(this, k);


  24:         }


  25:  


  26:         protected override void WndProc(ref Message m)


  27:         {


  28:             if (m.Msg == WindowsShell.WM_HOTKEY)


  29:             {


  30:                 Keys key = (Keys)(((int)m.LParam >> 16) & 0xFFFF);


  31:                 WindowsShell.ModifierEnum modifier = 


  32:                     (WindowsShell.ModifierEnum)((int)m.LParam & 0xFFFF);


  33:                 if (key == Keys.A && modifier == WindowsShell.ModifierEnum.MOD_CONTROL)


  34:                     showForm();


  35:             }


  36:             base.WndProc(ref m);


  37:         }


  38:  


  39:         private void showForm()


  40:         {


  41:             this.Text = 


  42:                 String.Format("Hokey pressed at: {0}", 


  43:                 DateTime.Now.ToLongTimeString());


  44:             this.Show();


  45:             if (this.WindowState == FormWindowState.Minimized)


  46:                 this.WindowState = FormWindowState.Normal;


  47:         }


  48:     }


  49: }



 



Espero que a más de uno le sirva,



HYEI :-)