¡Bienvenid@ a La bloguera.net! Iniciar sesión | ÚNETE a la web | Ayuda

Los asserts nos permiten comprobar el resultado de las pruebas unitarias. Podemos destacar que existe un buen soporte para trabajar con genéricos, algo que nos puede facilitar mucho el trabajo a realizar a la hora de escribir nuestras pruebas. Existe un buen numero de asserts pero algunos son la negación o lo contrario de otro (por ejemplo: True-False, Null-NotNull...):

  • Equal<T>: Comprueba si dos objetos sean iguales.
  • NotEqual<T>: Comprueba que dos objetos no sean iguales.
  • NotSame: Comprueba que dos objetos no sean la misma instancia.
  • Same: Comrpueba que dos objetos sean la misma instancia.
  • Contains, Contains<T>: Verifica que una colección contiene un objeto dado.
  • DoesNotContain, DoesNotContain<T>: Verifica que una colección no contiene un objeto dado.
  • DoesNotThrow: Comprueba que un bloque de código no lanza ninguna excepción.
  • Throws, Throws<T>: Comprueba que un bloque de código lance una determinada excepción.
  • InRange<T>: Verifica que un objeto se encuentra dentro de un rango.
  • NotInRange<T>: Verifica que un objeto no se encuentra dentro de un rango.
  • IsAsignableFrom, IsAsignableFrom<T>: Comprueba que un objeto sea del tipo dado o de uno derivado.
  • Empty: Comprueba que una colección no contenga elementos.
  • False: Verifica si una condición es falsa.
  • IsNotType, IsNotType<T>: Verifica que un objeto no es exactamente del tipo dado.
  • IsType, IsType<T>: Verifica que un objeto es exactamente del tipo dado.
  • NotEmpty: Comprueba que una colección tenga algún elemento.
  • NotNull: Verifica que una referencia a un objeto no sea nula.
  • Null: Verifica que una referencia a un objeto no sea nula.
  • True: Verifica que una condición es verdadera.

Los asserts a los que podemos prestarles más atención puede que sean Equal, InRange y Throws. Para la aserción Equal podemos pensar que no tiene mayor misterio pero, ¿qué ocurre si comparamos dos objetos de una clase que hemos creado? Para verlo con un ejemplo he creado una clase FichaDomino que simplemente contiene dos valores enteros para representar una ficha de dominó.

[Fact]

public void TestFichasIguales()

{

    FichaDomino fich1 = new FichaDomino(6, 6);

    FichaDomino fich2 = new FichaDomino(6, 6);

    Assert.Equal(fich1, fich2);

}

Al jecutar este test esperamos que pase pero el resultado obtenido es el siguiente:

TestCase 'Tests.Test.TestFichasIguales' failed: Assert.Equal() Failure

Expected: 6:6

Actual:   6:6

       en Xunit.Assert.Equal[T](T expected, T actual, IComparer`1 comparer)

       en Xunit.Assert.Equal[T](T expected, T actual)

       E:\Documentos\Visual Studio 2008\Projects\DemoTDD\Tests\Test.cs(116,0): en Tests.Test.TestFichasIguales()

Esto ocurre así porque la comparación se realiza a través del método Equals que se hereda de la clase Object que lo único que hace es comparar si las referencias de los objetos a comparar son la misma. La solución es sobrescribir el método Equals o, implementar la interfaz IComparable y/o IComparable<T> (ya que podemos hacer uso de genéricos...). Indagando un poco en la forma que trabaja xUnit, primero se va comprobar si se implementa la interfaz Icomparable<T> para llamar a CompareTo de forma genérica (es decir, con un tipo concreto), si no es así se comprueba si se implementa la interfaz IComparable para llamar a CompareTo donde el argumento a comparar es un object. Si no se implementa alguna de estas interfaces entonces es necesario sobrescribir el método Equals.

La aserción InRange verifica que un objeto esté dentro de un rango de valores. En este caso pasa tres cuartos de lo mismo que ocurre con el assert Equal, para tipos simples (enteros, strings, fechas...) la comparación es trivial pero para objetos no. Aquí necesitamos implementar IComparable o IComparable<T> para que al comparar si el objeto dado esta dentro de un determinado rango de valores el resultado sea el correcto. Como alternativa podríamos pasarle a este assert un comparador que compare objetos de la clase FichaDomino, aunque pienso que podrían producirse fallos si olvidamos que siempre tenemos que utilizar un comparador personalizado al no haber implementado IComparable. La implementación es esta:

public int CompareTo(FichaDomino other)

{

    int result = 0;

 

    if (this.numIzq < other.numIzq)

    {

        if (this.numDer < other.numDer || this.numDer == other.numDer)

            result = -1;

        else

            result = 1;

    }

    else if (this.numIzq == other.numIzq)

    {

        if (this.numDer < other.numDer)

            result = -1;

        else if (this.numDer == other.numDer)

            result = 0;

        else

            result = 1;

    }

    else

        result = 1;

 

    return result;

}

Por último, Throws<T> nos permite verificar si se produce una excepción de un tipo determinado. El parámetro que toma este assert es un delegado a la función o al código que queremos testar para que el assert pueda recoger la excepción si se produce. Vamos a crear una ficha de dominó con los valores 8:8, si la implementación de constructor es correcta debería de lanzar un excepción del tipo ArgumentException por estar esos valores fuera de los valores que puede tener una ficha de dominó

public FichaDomino(int numIzq, int numDer)

{

    this.numIzq = numIzq;

    this.numDer = numDer;

}

el test para verificarlo es:

[Fact]

public void TestFichaNueva()

{

    Assert.Throws<ArgumentException>(

        delegate

        {

            FichaDomino fich1 = new FichaDomino(8, 8);

        });

}

y el resultado es:

TestCase 'Tests.Test.TestFichaNueva'

failed: Assert.Throws() Failure

Expected: System.ArgumentException

Actual:   (No exception was thrown)

       en Xunit.Assert.Throws(Type exceptionType, ThrowsDelegate testCode)

       en Xunit.Assert.Throws[T](ThrowsDelegate testCode)

       E:\Documentos\Visual Studio 2008\Projects\DemoTDD\Tests\Test.cs(122,0): en Tests.Test.TestFichaNueva()

Qué ocurre si tenemos un método que puede lanzar una misma excepción pero por diferentes causas, de esta forma no podemos determinar cuál es la causa por la que se ha producido la excepción. Para tener un control mayor sobre las excepciones que se pueden lanzar vamos a utilizar la clase Record para capturar una excepción y después poder operar con ella. Como ejemplo vamos a tomar la calculadora (utilizada en el post anterior) para obtener un número aleatorio dentro de un rango de valores [a,b]. Las causas para lanzar una excepción son que el rango no sea correcto (a > b) o que no exista rango (a = b). Teniendo la siguiente implementación para generar un número aleatorio dentro de un rango de valores:

public static double NumAleatorioEnRango(double min, double max)

{

    if (min > max)

        throw new ArgumentException("Rango no valido. El valor minimo > maximo");

    if (min == max)

        throw new ArgumentException("Rango no valido. El valor minimo = maximo");

 

    Random random = new Random(DateTime.Now.Millisecond);

 

    double aleatorio = random.NextDouble();

    double result = ((max - min) * aleatorio) + min;

    return result;

}

Vamos a lanzar la siguiente prueba donde queremos comprobar que se lanza una excepción cuando min > max:

[Fact]

public void TestNumAleatorio3()

{

    double min = 9.0;

    double max = 5.0;

 

    Exception recException = Record.Exception(

        delegate

        {

            Calc.NumAleatorioEnRango(min, max);

        });

 

    Assert.IsType<ArgumentException>(recException);

    Assert.Equal("Rango no valido. El valor minimo > maximo", recException.Message);

}

El resultado es que las dos aserciones son correctas y el test pasa como válido.

NOTA: adjunto va un zip con la demo de prueba.

 

Este post y siguientes tienen como finalidad hacer una introducción a xUnit y el desarrollo guiado por pruebas (TDD o del inglés Test Driven Development). El desarrollo guiado por pruebas es una práctica que forma parte de las metodologías ágiles para el desarrollo de software y que consiste en el uso de pruebas unitarias para escribir el código de una forma más limpia y con menos errores.

Lo que se pretende, entre otras cosas, es evitar el escribir código innecesario “por si en un futuro lo usamos” y con menos fallos. Muchas veces empezamos a escribir código e intentamos adaptarlo para posibles cambios que pueden darse lugar en un futuro pero que luego y de forma general no suelen llegar, con lo que trabajamos en algo que no nos va a servir de mucho. La idea es que por cada requerimiento que tengamos escribamos una prueba que inicialmente fallará. Si la prueba no falla puede ser porque la funcionalidad ya estaba implementada o que la prueba es errónea. Cada prueba unitaria se escribe con el código mínimo necesario y posteriormente se puede realizar una refactorización. De esta forma nos aseguramos que al hacer cambio en el código tenemos la forma o herramienta de probar que esos cambios son válidos y no se han introducido nuevos errores.

El ciclo del desarrollo guiado por pruebas consiste en primer lugar en elegir un requerimiento para el cual vamos a escribir una prueba, después comprobamos que falla (como ya hemos comentado antes) y entonces implementamos el código lo más simple posible para que funcione. Verificamos que todas nuestras pruebas unitarias son válidas y entonces hacemos una refactorización del código para eliminar partes duplicadas. Por último actualizamos la lista de requerimientos, añadiendo requisitos nuevos si han surgido y/o eliminando los ya implementados, y realizamos un nuevo ciclo. Esto es una introducción muy general al TDD, solo para que uno se haga una idea y podéis buscar más información a través de nuestro amigo google.

Ahora vayamos al lío, de entre todos los frameworks existentes para hacer test unitarios he decido usar xUnit, basado en nUnit (aunque tienen algunas diferencias claras) y cuyo autor es el mismo. Para poder usar xUnit (descargar desde aquí) tenemos varias opciones, por un lado tenemos xunit.console y xunit.gui (que vienen con xUnit) y por otro tenemos la posibilidad de integrar xUnit con Visual Studio por medio de TestDriven (que cuenta con edición personal gratuita) o con ReSapher (evaluación de 30 días). Yo voy a usar TestDriven y para ello, una vez instalado TestDriven, hay que activar el soporte de xUnit para TestDriven a través de xunit.installer.

xUnit.installer

En este momento es cuando podemos crear un nuevo proyecto en Visual Studio con el que vamos a realizar pruebas unitarias. Imaginemos que vamos a hacer una calculadora y vamos a tener una librería que de soporte para realizar operaciones con una calculadora. Tenemos una clase muy sencilla llamada Calc:

public class Calc

{

    public Calc()

    { }

 

    public int SumaNumeros(int a, int b)

    {

        throw new ArgumentException("No implementado");

    }

 

    public double DivideNumeros(double a, double b)

    {

        throw new ArgumentException("No implementado");

    }

}

Después creamos otro proyecto de librería de clases que es el que va a incluir todas nuestras pruebas unitarias y agregamos la referencia a la dll de xUnit y la de la librería sobre la que vamos a realizar las pruebas:

referencias

En la imagen podemos ver las referencias agregadas tanto al proyecto como a la clase que contiene las pruebas unitarias. También podemos ver que existe un método llamado ComprobarSuma que es el encargado de comprobar la operación suma de la calculadora. Para que xUnit identifique este método como una prueba unitaria hay que marcarlo con el atributo [Fact], de lo contrario no se ejecutará ningún test. Queda resaltar la sentencia Assert que establece la condición para determinar si la prueba unitaria es satisfactoria o no. Para ejecutar xUnit pinchamos con el botón derecho del ratón sobre el proyecto que contiene las pruebas unitarias y le damos a Run Test(s).

runtest

Si hacemos correr el test, el resultado obtenido es el siguiente (hay que recordar que como no estaba implementado el método de sumar números hacíamos que lanzara una excepción):

------ Test started: Assembly: Tests.dll ------

 

TestCase 'Tests.Test.ComprobarSuma' failed: System.ArgumentException : No implementado

       E:\Documentos\Visual Studio 2008\Projects\DemoTDD\DemoTDD\Calc.cs(14,0): en DemoTDD.Calc.SumaNumeros(Int32 a, Int32 b)

       E:\Documentos\Visual Studio 2008\Projects\DemoTDD\Tests\Test.cs(17,0): en Tests.Test.ComprobarSuma()

 

0 passed, 1 failed, 0 skipped, took 0,72 seconds.

Una vez visto que la prueba falla, es cuando hay que implementar el método que estamos probando. Para ver otro ejemplo, vamos a hacer que el método SumaNumeros nos devuelva la suma de los dos argumentos más uno. En el resultado al ejecutar el test nos dice que el test ha fallado y nos indica el valor esperado y el valor obtenido:

------ Test started: Assembly: Tests.dll ------

 

TestCase 'Tests.Test.ComprobarSuma' failed: Assert.Equal() Failure

Expected: 7

Actual:   8

       en Xunit.Assert.Equal[T](T expected, T actual, IComparer`1 comparer)

       en Xunit.Assert.Equal[T](T expected, T actual)

       E:\Documentos\Visual Studio 2008\Projects\DemoTDD\Tests\Test.cs(19,0): en Tests.Test.ComprobarSuma()

 

 

0 passed, 1 failed, 0 skipped, took 0,76 seconds. 

También es interesante saber que podemos añadir parámetros al atributo [Fact] para contemplar dos casos, cuando una prueba tarda demasiado tiempo y cuando por el motivo que sea no queremos que una determinada prueba se ejecute en un momento concreto.

Si queremos saltarnos una prueba tenemos que añadir el parámetro Skip que toma un valor de tipo string:

[Fact(Skip = "Sin implementar")]

public void SkipTest()

{

    //implementar despues

} 

La salida obtenida es:

0 passed, 0 failed, 1 skipped, took 0,72 seconds

Si queremos que test falle cuando exceda de un determinado tiempo tenemos que añadir el parámetro Timeout que toma un valor entero en milisegundos:

[Fact(Timeout = 2000)]

public void TimeoutTest()

{

    Thread.Sleep(6000);

} 

La salida obtenida es:

TestCase 'Tests.Test.TimeoutTest' failed: Test execution time exceeded: 2000ms

Y por último dejo una lista con todas las aserciones que contempla xUnit para comprobar el resultado de una prueba unitaria y que me gustaría comentar en el siguiente post:

  • Equal<T>
  • NotEqual<T>
  • NotSame
  • Same
  • Contains, Contains<T>
  • DoesNotContain, DoesNotContain<T>
  • DoesNotThrow
  • Throws, Throws<T>
  • InRange<T>
  • NotInRange<T>
  • IsAsignableFrom, IsAsignableFrom<T>
  • Empty
  • False
  • IsNotType, IsNotType<T>
  • IsType, IsType<T>
  • NotEmpty
  • NotNull
  • Null
  • True

NOTA: la versión que he utilizado de xUnit es la 1.0.3 que parece presentar un bug (que ya ha sido reportado) en el que la salida por consola no aparece, por eso si añadís algún Console.WriteLine no esperéis a que os aparezca por consola.

NOTA 2: teneis adjunto el ejemplo de código para VS2008

¿Os acordáis de de la aplicación MSDN Video, aquella que se desarrolló para dar a conocer la tecnología .Net allá por el 2003? Pues bien, desde hace un tiempo sabía que estaban actualizando el contenido para adaptarlo al framework 3.5 y Visual Studio 2008, lo que me parecía algo estupendo. Pues bien, anoche antes de acostarme decidí dar un paseo por la web de desarrolla con MSDN y sorpresa la mía al encontrarme que ya han publicado MSDN Video 2008.
 

Esta vez las tecnologías de las que se harán uso son, entre otras, Ajax, Linq, WPF y WCF. Se pretende que sea un proyecto en constante evolución, en el cual cada uno de nosotros podemos aportar nuestro granito de arena. El código fuente del que tenemos disponibilidad es una versión beta y también disponemos de un primer video de introducción en Silverlight. A parte, parece que sacarán un curso online para aprender el modo en el que se han utilizado las nuevas tecnologías que incorpora el framework 3.5.

Espero que os resulte útil, un saludo.

 

No se si a alguno de vosotros os habrá pasado, pero para mi instalar Visual Studio 2008 se ha convertido en una odisea y después de varios intentos he conseguido realizar una instalación satisfactoria.

Primer punto a tener en cuenta y, como suele ser habitual... antes de instalar un nuevo programa es recomendable desinstalar las betas o directamente el programa  de instalación te avisa de que antes de instalar una nueva versión tienes que instalar las betas que tengas en tu máquina.

Partimos de que mi sistema operativo es un Vista Business con VS2005, SQL Server 2005, Office 2007 entre otros y que antes de instalar Visual Studio 2008 tenia instalada la beta 2. VS2008 no va a ser distinto a cualquier otro programa y al intentar instalarlo sobre alguna pre-release os saldrá un cuadro de dialogo indicando que desinstaleis un determinado numero de componentes (ver imagen inferior). Existe una lista y un orden en el que desinstalar esos productos que podéis visitar aquí pero no todos los componentes que tendrás que desinstalar tienen que aparecer en esa lista, por ejemplo, los componentes que a mi me pedía desinstalar no aparecen en esa lista.

 

 

 

Puede ser que desinstales los componentes que os aparezcan en el cuadro de dialogo anterior y que al reiniciar la instalación os vuelva a pedir que desinstaleis otros componentes. De hecho al segundo intento me pidió desinstalar los siguientes hotfixes desde windows update: (KB110806), (KB930264) y (KB929300). Bueno... esto puede ser un poco tortuoso: intentar instalar, desinstalar componentes, volver a instalar... pero esto no es lo más frustrante, ¿qué ocurre cuando quieres desinstalar un componente que aparentemente no tienes instalado? te diriges a Panel de control -> Programas y características y ese componente no aparece en el listado de programas instalados. Por lo visto, esta herramienta te muestra como programas instalados los que quiere.
Una posible solución es irte al registro y borrar las entradas a mano, algo poco recomendable y otra opción es desinstalar esos componentes con la herramienta  Windows Installer Clean Up. Con esta herramienta podréis desinstalar todos componentes necesarios para poder instalar VS2008.

 

 



Una vez desinstalados todos los malditos componentes que me daban problemas pude iniciar la instalación de VS2008 pero otro problema que encontré fue que, a mitad de la instalación, ésta producía un error y terminaba. Lo que se me ocurrió fue desinstalar Office 2007 porque ya había oído que daba problemas cuando se intenta instalar algún programa teniéndolo instalado. Después de esto si, por fin tenia instalado VS en mi portátil como se puede ver en la imagen!!

 

 



Aunque tengo que decir... que no todo fueron alegrías. Al crear un nuevo proyecto, ya fuera web, wpf o windows form, VS producía un error y no cargaba el diseñador gráfico. La forma en que resolví este problema, y con este el ultimo, fue instalar las actualizaciones que podemos encontrar en el DVD de VS2008 en la carpeta: WCU\dotNetFramework\dotNetMSP\x86.

 

 

 

Con todo esto conseguí instalar VS2008 y tener un funcionamiento correcto, así que si alguno de vosotros instala VS2008 y tiene alguno de estos problemas, espero que os sean útiles los pasos que yo he seguido para solucionar los problemas que me han ido surgiendo.


Hace unos cuantos unos (bueno, unas semanas... que no encuentro tiempo para postear!!) necesitaba imprimir unos datos que se encontraban en un formulario y lo lógico sería usar reportes para hacerlo, pero por la falta de tiempo y por no haber trabajado reportes antes necesitaba una solución más rápida, entonces se me ocurrió imprimir el formulario y buscando por la red encontré este artículo en la knowledge base de Microsoft.  Pues bien, en primer lugar lo que hice fue pasar ese código a  C# y segundo hacer unas pequeñas variaciones al código para que la impresión del formulario se ajustara más a lo que deseaba. Para probarlo nos creamos un nuevo proyecto para una aplicación windows (en C#) y añadimos la siguiente clase al proyecto (extraída del articulo anterior):

 

using System;

using System.Collections.Generic;

using System.Text;

using System.Drawing.Printing;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.Drawing.Imaging;

using System.Runtime.InteropServices;

 

public class Win32APICall

{

    public const int DIB_RGB_COLORS = 0;

    public const int BI_RGB = 0;

    public const int WHITENESS = 16711778;

 

    [DllImport("user32.dll", EntryPoint = "PrintWindow", SetLastError = true,

        CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention =

        CallingConvention.StdCall)]

    public static extern UInt32 PrintWindow(IntPtr hWnd, IntPtr hDC, int dwFlags);

 

    [DllImport("gdi32.dll", EntryPoint = "CreateDIBSection", SetLastError = true,

        CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention =

        CallingConvention.StdCall)]

    public static extern IntPtr CreateDIBSection(IntPtr hdc, ref BITMAPINFOHEADER pbmi,

        Int32 iUsage, IntPtr ppvBits, IntPtr hSection, Int32 dwOffset);

 

    [DllImport("gdi32.dll", EntryPoint = "PatBlt", SetLastError = true, CharSet =

        CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]

    public static extern bool PatBlt(IntPtr hDC, Int32 nXLeft, Int32 nYLeft, Int32 nWidth,

        Int32 nHeight, Int32 dwRop);

 

    [DllImport("gdi32.dll", EntryPoint = "SelectObject", SetLastError = true,

        CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention =

        CallingConvention.StdCall)]

    public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObj);

 

    [DllImport("GDI32.dll", EntryPoint = "CreateCompatibleDC", SetLastError = true,

        CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention =

        CallingConvention.StdCall)]

    public static extern IntPtr CreateCompatibleDC(IntPtr hRefDC);

 

    [DllImport("GDI32.dll", EntryPoint = "DeleteDC", SetLastError = true,

        CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention =

        CallingConvention.StdCall)]

    public static extern bool DeleteDC(IntPtr hDC);

 

    [DllImport("GDI32.dll", EntryPoint = "DeleteObject", SetLastError = true,

        CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention =

        CallingConvention.StdCall)]

    public static extern bool DeleteObject(IntPtr hObj);

 

    [DllImport("User32.dll", EntryPoint = "ReleaseDC", SetLastError = true,

        CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention =

        CallingConvention.StdCall)]

    public static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);

 

    [DllImport("User32.dll", EntryPoint = "GetDC", SetLastError = true,

        CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention =

        CallingConvention.StdCall)]

    public static extern IntPtr GetDC(IntPtr hWnd);

   

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]

    public struct BITMAPINFOHEADER

    {

        public Int32 biSize;

        public Int32 biWidth;

        public Int32 biHeight;

        public Int16 biPlanes;

        public Int16 biBitCount;

        public Int32 biCompression;

        public Int32 biSizeImage;

        public Int32 biXPelsPerMeter;

        public Int32 biYPelsPerMeter;

        public Int32 biClrUsed;

        public Int32 biClrImportant;

    }

}

 
 

Ahora diseñamos el formulario que queremos imprimir y le añadimos un objeto PrintDocument printDocument1 y PrintDialog printDialog1, nos puede quedar algo como esto, un formulario que muestre información sobre libros (si, ya se que es feo, pero para el propósito que tiene es suficiente):

 

 

Después tendríamos que controlar el evento click del botón imprimir para que cuando pulsemos en el boton se imprima el formulario y a parte, tenemos que añadir a nuestro código el método que se encargará de imprimir el formulario (también extraído del articulo del que he hablado más arriba) llamado OnPrintPage:

 

private void buttonImprimir_Click(object sender, EventArgs e)

{

    printDialog1.Document = printDocument1;

    if ((printDialog1.ShowDialog() == DialogResult.OK))

    {

        printDocument1.PrintPage += new PrintPageEventHandler(this.OnPrintPage);

        printDocument1.Print();

    }

}
 

private void OnPrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)

{

    IntPtr hwndForm;

    hwndForm = this.Handle;

    IntPtr hdcDIBSection;

    IntPtr hdcRef;

    IntPtr hbmDIBSection;

    IntPtr hbmDIBSectionOld;

    Win32APICall.BITMAPINFOHEADER BMPheader;

    hdcRef = Win32APICall.GetDC(IntPtr.Zero);

    hdcDIBSection = Win32APICall.CreateCompatibleDC(hdcRef);

    Win32APICall.ReleaseDC(IntPtr.Zero, hdcRef);

    BMPheader.biBitCount = 24;

    BMPheader.biClrImportant = 0;

    BMPheader.biClrUsed = 0;

    BMPheader.biCompression = Win32APICall.BI_RGB;

    BMPheader.biSize = 40;

    BMPheader.biHeight = this.Height;

    BMPheader.biPlanes = 1;

    BMPheader.biSizeImage = 0;

    BMPheader.biWidth = this.Width;

    BMPheader.biXPelsPerMeter = 0;

    BMPheader.biYPelsPerMeter = 0;

    hbmDIBSection = Win32APICall.CreateDIBSection(hdcDIBSection, ref BMPheader,

        Win32APICall.DIB_RGB_COLORS, IntPtr.Zero, IntPtr.Zero, 0);

    hbmDIBSectionOld = Win32APICall.SelectObject(hdcDIBSection, hbmDIBSection);

    Win32APICall.PatBlt(hdcDIBSection, 0, 0, this.Width, this.Height,

        Win32APICall.WHITENESS);

    Win32APICall.PrintWindow(hwndForm, hdcDIBSection, 0);

    Win32APICall.SelectObject(hdcDIBSection, hbmDIBSectionOld);

    Bitmap imageFrm;

    imageFrm = Image.FromHbitmap(hbmDIBSection);

    e.Graphics.DrawImage(imageFrm, 0, 0);

    Win32APICall.DeleteDC(hdcDIBSection);

    Win32APICall.DeleteObject(hbmDIBSection);

}

 

Bien con esto ya podemos imprimir nuestro formulario, sencillo, pero vamos a ir un poquito más allá, la verdad es que imprimir el formulario así tal cual puede quedar un poquito feo, entonces lo que podemos hacer es modificar la apariencia del formulario para que se imprima lo que nosotros queramos que se vea. Para eso solo tenemos que cambiar la apariencia antes de imprimir el formulario y después de realizar la impresión devolver al formulario su apariencia original. Para el formulario que hemos creado podríamos, por ejemplo, ocultar el botón de impresión, el marco de la ventana, eliminar el borde de los textboxs y cambiar el color de fondo. El código final para el método buttonImprimir_Click sería:

 

private void buttonImprimir_Click(object sender, EventArgs e)

{

    Color colorFondo = this.BackColor;

    try

    {

        printDialog1.Document = printDocument1;

        if ((printDialog1.ShowDialog() == DialogResult.OK))

        {

            // ocultamos el borde del formulario

            this.FormBorderStyle = FormBorderStyle.None;

            //cambiamos el color de fondo del formulario

            this.BackColor = Color.White;

            //quitamos el borde a los texbox

            textBox1.BorderStyle = BorderStyle.None;

            textBox2.BorderStyle = BorderStyle.None;

            textBox3.BorderStyle = BorderStyle.None;

            textBox4.BorderStyle = BorderStyle.None;

            textBox5.BorderStyle = BorderStyle.None;

            textBox6.BorderStyle = BorderStyle.None;

            //ocultamos el boton de imprimir

            buttonImprimir.Visible = false;

            printDocument1.PrintPage += new PrintPageEventHandler(this.OnPrintPage);

            printDocument1.Print();

        }

    }

    catch (Exception)

    {

        MessageBox.Show("Error al imprimir", "Error",

            MessageBoxButtons.OK, MessageBoxIcon.Error);

    }

    finally

    {

        //restauramos las propiedades de los controles

        this.FormBorderStyle = FormBorderStyle.Sizable;

        this.BackColor = colorFondo;

        textBox1.BorderStyle = BorderStyle.FixedSingle;

        textBox2.BorderStyle = BorderStyle.FixedSingle;

        textBox3.BorderStyle = BorderStyle.FixedSingle;

        textBox4.BorderStyle = BorderStyle.FixedSingle;

        textBox5.BorderStyle = BorderStyle.FixedSingle;

        textBox6.BorderStyle = BorderStyle.FixedSingle;

        buttonImprimir.Visible = true;

        //refrescamos el form

        this.Refresh();

    }

}

 

Y por si quereis trastear con el código os lo dejo adjunto más abajo, un saludo!!!

 

Siguiendo en la linea del post anterior, vamos a comentar dos de los puntos que fueron mencionados, los problemas con operaciones aritméticas y de comparación que, aunque de conocimiento básico no viene nada mal recordar.
 

- Operaciones aritméticas. Supongamos que tenemos un determinado objeto, como pueda ser una imagen, que tiene dimensiones (ancho y alto) y que queremos redimensionarla de tal forma que necesitamos obtener los porcentajes en que se redimensionan para el ancho (width) y alto (height), pues basta con dividir las nuevas dimensiones por las originales:

 

//valores de ejemplo

int widthOrigen = 2698;

int heightOrigen = 1780;

int width = 1024;

int height = 768;

 

float porcentajeW, porcentajeH;

 

porcentajeW = (width / widthOrigen);

porcentajeH = (height / heightOrigen);

 

pero este código no es del todo correcto porque lo que se produce es una división de enteros, por lo que el resultado será un entero aunque luego lo almacenemos en un float, para que el resultado de la división sea correcto bastaría con hacer un casting de uno de los operandos, posteriormente el compilador "promociona" el valor de menor precisión al del tipo de mayor precisión, es decir, de int a float:

 

porcentajeW = (width / (float)widthOrigen);

porcentajeH = (height / (float)heightOrigen);

 

- Operaciones de comparación. Esto es algo básico en la programación orientada a objetos, si intentamos comparar dos objetos con el operador de comparación (==) pues no obtendremos el resultado deseado porque lo que hacemos es comparar sus referencias a memoria, solo es válido para los tipos básicos(int, float, string...). Para comparar objetos podemos y/o debemos usar el método Equals(object obj), pero no basta con hacer una llamada a este método, pues obtendríamos el mismo resultado que al usar el operador de comparación. Este método pertenece a la clase object y debe ser sobrescrito en nuestras clases para que compare dos objetos de forma correcta. Supongamos que tenemos una clase llamada Complejo como la siguiente:

 

public class Complejo

{

    public int a;

    public int b;

 

    public Complejo(int a, int b)

    {

        this.a = a;

        this.b = b;

    }

 

    //...

 

    public override bool Equals(object obj)

    {

        try

        {

            Complejo aux = (Complejo)obj;

 

            if (this.a == aux.a)

                if (this.b == aux.b)

                    return true;

        }

        catch (InvalidCastException)

        {

            // es un objeto de otro tipo

        }

 

        return false;

    } 

}
 

Podemos destacar el método Equals(object obj), que no tiene ninguna complejidad, el cual se encarga de sobrescribir la forma predeterminada de comparar dos objetos de tipo Complejo. Al compilar, obtendremos una advertencia por no sobrescribir el método GetHashCode() así que o lo sobrescribís o pasáis de la advertencia. Por último y para ver la diferencia entre una simple comparación y el método Equals() lo mejor es verlo con un ejemplo:

 

 

Complejo c1 = new Complejo(2, 5);

Complejo c2 = new Complejo(2, 5);

Complejo c3 = c1;

 

Console.WriteLine("c1 = c2: " + (c1 == c2));

Console.WriteLine("c1 = c3: " + (c1 == c3));

Console.WriteLine("c2 = c3: " + (c2 == c3));

 

Console.WriteLine("c1.Equals(c2): " + c1.Equals(c2));

Console.WriteLine("c1.Equals(c3): " + c1.Equals(c3));

Console.WriteLine("c2.Equals(c3): " + c2.Equals(c3));

 

c3.a = 7;

 

Console.WriteLine("c1 = c2: " + (c1 == c2));

Console.WriteLine("c1 = c3: " + (c1 == c3));

Console.WriteLine("c2 = c3: " + (c2 == c3));

 

Console.WriteLine("c1.Equals(c2): " + c1.Equals(c2));

Console.WriteLine("c1.Equals(c3): " + c1.Equals(c3));

Console.WriteLine("c2.Equals(c3): " + c2.Equals(c3));

 
 

¿Cuál es el resultado?

 

Muchas veces cometemos errores que se podrían evitar simplemente prestando un poquito más de atención. Aquí van una serie de errores que podemos cometer con cierta asiduidad, desde mi punto de vista, y que podríamos minimizar con un poco de atención:

- Copy/paste: es muy habitual copiar partes de código que pueden repetirse, por ejemplo, de un método a otro y al que  después le realizamos alguna pequeña modificación. Sobre esta base, empezamos a modificar dicho código y de una idea vamos a otra que a su vez nos conduce a otra, que hace que nos olvidemos de la primera idea y de quitar esas linea de código que esta vez nos resultan inútiles. Hay estudios que afirman que las personas no pueden realizar más de 7 tareas a la vez, ¿será verdad?

- Aritmética: las prisas, que no son nada buenas pueden llevarnos a cometer errores en las operaciones aritméticas  pues podemos pasar por alto realizar algún casting necesario con el que sin él, obtendríamos un resultado diferente al esperado.

- Caracteres especiales: hay que tener especial cuidado al escribir código que haga uso de caracteres especiales. Estas escribiendo una capa de acceso a datos para una base de datos cuando haces un ... LIKE '*algo*'... y la consulta select no devuelve los datos esperados... ¿estás seguro de que ese carácter especial para indicar una cadena de uno o más caracteres es *?¿y si es %, o cualquier otro? no está de más dar un vistazo rápido a la referencia de la bbdd con la que se está trabajando.

- Comparación de objetos: no es lo mismo hacer una comparación ( == ) que hacer un objecet.Equals(otroObject), el  resultado producido puede no ser el deseado de nuevo.

- Error de sintaxis: posiblemente con mayor incidencia en C# o Java que en VB puesto que éste último no es Case Sensitive, ¿a quién no se le olvida poner una coma? ¿o poner una palabra en minúscula cuando su primera letra es mayúscula? escribir una palabra de forma correcta... por el amor de Dios!! si llevamos ¿20? ¿30? ¿40...? años escribiendo y hablando nuestra lengua materna, como es el castellano en mi caso, y seguimos cometiendo faltas de ortografía... cómo no las vamos a cometer cuando pikamos código!!!


Éstos son algunos errores habituales (tomados con algo de humor) a la hora de programar, si ahora mismo os viene alguno más a la mente dejarlo caer por aquí y... ¡¡cuidado al pikar código!!!

 

 

 

website statistics

Haciendo referencia al post que ha publicado Thempra hoy mismo, os dejo una serie de enlaces con recursos para Windows Server 2008 de los que he sabido no hace mucho, espero que os sean útlies:

 http://blogs.technet.com/davidcervigon/archive/2007/09/27/cursos-online-y-libros-gratuitos-de-windows-server-2008.aspx 

http://blogs.technet.com/davidcervigon/archive/2007/09/25/como-instalar-windows-server-virtualization-en-la-rc0-de-windows-server-2008.aspx

http://blogs.technet.com/davidcervigon/archive/2007/09/21/como-conseguir-betas-y-productos-de-evaluacion.aspx

 

Imaginemos que desarrollamos una nueva aplicación en .Net y queremos que esta compruebe la existencia de un framework para .Net ya que sería un poquito feo que al instalar o ejecutar la aplicación diera un error por no disponer de un framework instalado y que no diera una alternativa a dicho suceso.
 

Si nuestra aplicación va a ser desplegada mediante un paquete de instalación, tipo ClickOnce, es fácil saber si esta instalado un determinado framework, podemos obtener más información en el siguiente enlace o en este otro con información para el Framework 3.0.
 

Pero... ¿y si lo qué queremos es distribuir directamente el fichero ejecutable? por ejemplo mediante un CD-Rom, pues tendremos que determinar de alguna forma la existencia o no del Framework .Net y como es evidente no podemos comprobarlo desde .Net. Una forma sería crear una aplicación que hiciera de lanzadera o launcher (en C/C++, VB...) para nuestra aplicación desarrollada en .Net. Otra forma posible, y a la vez sencilla, es comprobar mediante un script si tenemos instalado o no el framework en nuestra máquina.
 

Windows da soporte a lenguajes de script (JScrip o VBScript) mediante WSH (Windows Scripting Host o Windows Script Host) que viene instalado por defecto desde Windows 98 e Internet Explorer 5. WSH lo que nos permite es realizar scripts para la automatización de tareas, algo que seguro los administradores de sistemas Windows habrán agradecido en cierta medida.
 

Volviendo a lo que estamos tratando en este post, para poder comprobar la existencia del framework utilizaremos un script que comprueba si existen una/s determinada/s clave/s en el registro de Windows que hace/n referencia al Framework .Net y sus distintas versiones. En el siguiente enlace tenemos información detallada de qué claves hacen referencia a cada una de las versiones
del .Net Framework desde la versión 1.0 a la 3.5 y como podemos trabajar con ellas desde código manejado. Particularmente, yo accederé a la clave que hace referencia al Framework 2.0:
 

HKLM\Software\Microsoft\NET Framework Setup\NDP\v2.0.50727\Install
 

Y el código del script en VBScript (nombre.vbs) es el siguiente:

 

set WshShell=WScript.CreateObject("WScript.Shell")

 

On Error Resume Next

valor = WshShell.RegRead("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727\Install")

If valor = 1 Then

            valor=Msgbox("Tienes el Framework 2.0",vbYes)

            ‘o lanzar la aplicación: WshShell.Run("directorio\aplicacion", …)

End If

If valor = 0 Then

            valor=Msgbox("No tienes el Framework 2.0",vbYes)

‘otra opcion lanzar un navegador para descargar e instalar el framework necesario

End If 

 


Bien, en la primera linea declaramos un objeto de tipo WScript.Shell, este objeto aporta funciones para obtener información acerca de las características del sistema y de las variables del entorno, así como trabajar con el registro, que es lo que hacemos más abajo, y con accesos directos. Después, dependiendo de si el valor devuelto al leer el registro es 1 ó 0, se ejecutará nuestra aplicación o tendremos una alternativa para hacer otras cosas como instalar un framework. El script es un ejemplo, pero podríais hacerlo tan complejo como sea necesario.
 

Enlaces que pueden resultar de interés:

WSH Primer On Microsoft TechNet

Manual en castellano para WSH

Scripting en MSDN

El equipo del Instituto Politécnico Rensselaer ha desarrollado una pila poco más grande que una sello de correo que puede liberar cerca de 2.3 voltios, energía suficiente para alimentar una pequeña linterna o bombilla y a parte, condensadores de alta capacidad. Es solo un prototipo por el momento pero se espera obtener nuevos avances para en un futuro poder disponer de fuentes de energía más pequeñas y ligeras.

Estan compuestas por un 90% de celulosa y una estructura de nanotubo de carbono. Según Robert Linhardt (uno de los responsables del proyecto): "Los componentes están molecularmente unidos entre sí. Se imprime el nanotubo de carbono sobre el papel, y el conjunto se empapa de electrolitos."

 Estas baterías de papel funcionan incluso si se doblan, enrollan o cortan, lo que permite modificar su formar y controlar su potencia y voltaje según el tamaño y, además tienen un funcionamiento en un amplio rango de temperaturas.

La batería usa papel con un electrolito y nanotubos de carbono que están encajados en el papel. Los nanotubos forman los electrodos, el papel es el separador y el electrolito permite el flujo de corriente. Se ha demostrado que la sangre y el sudor son electrolitos adecuados para su funcionamiento, lo que podría traducirse en una posible uso en aplicaciones biológicas y médicas.
 

 

 

 

 

statistics

 

 

Ya sabéis que desde hace unos días está disponible la beta 2 de Visual Studio 2008 (codename Orcas) junto con la beta 2 de Framework 3.5. Pues bien, como las versiones beta de cualquier producto pueden generar algún conflicto al intentar instalar una versión posterior del mismo he decidido probarlo sobre una máquina virtual. Para ello descargué el Virtual PC 2007 desde aquí. Por otro lado, si teneis a mano un CD de Kit de Bienvenida de Imagine Cup, sí aquel que te mandaban a casa cuando pasabas la fase 1 (test) de la Imagine Cup, ya teneis una máquina virtual en dicho disco (también incorpora Virtual PC 2004) que podreis utilizar directamente para programar, pues viene con VS 2005 y SQL Server 2005 sobre un XP. Así os ahorrareis un tiempo considerable en preparar una maquina virtual, si es que no disponeis de una, para poder hacer uso de ella. Por otro lado y puesto que los fondos de escritorio que vienen de serie con Windows XP son algo feos, os podéis descargar algunos de los fondos de escritorio que vienen con Vista desde la web del autor.

Intel libera la biblioteca TBB 2.0 (Thread Building Blocks) que permite generar una capa de abstracción sobre la arquitectura de threads de la plataforma usada, facilitando la programación multinúcleo. Esto es para aprovechar la capacidad que pueden darlos los procesadores de última generación con dos y cuatro nucleos a los que no se les saca todo el rendimiento posible. TBB es una librería en C++ que ahora esta disponible como un proyecto open source bajo licencia GPLv2 junto a la versión que da soporte comercial completo.


La librería soporta, entre otros, los micros Pentium 4, Pentium D, Xeon, Core Duo, Core 2 Duo y procesadores no Intel compatibles con los anteriores. Además esta disponible para sistemas operativos como Windows XP Professional, Server 2003, Vista, Red Had Enterprise, SuSE Enterprise Server, Mandrake 10, Mac OS X 10.4.4 y los compiladores de Visual Studio 2003, Visual Studio 2005, compiladores C++ Intel para Windows, Linux y Mac, y GCC. Para mayor detalle de los requisitos software y hardware pincha aquí.

He encontrado este buscador de código llamado Krugle y que parece bastante interesante la funcionalidad que nos puede aportar. Nos permite buscar contenido entre más de cuarenta lenguajes diferentes y en diferentes áreas como definición de clases y funciones, código fuente, comentarios, llamadas a funciones. A parte de esto, el resultado de una búsqueda lo podemos dividir en código, sites y proyectos, de este modo si encontramos cierto código que nos resulte útil para poder utilizarlo basta con guardarlo en un archivo y agregarlo a proyecto al que queramos o estemos trabajando y si queremos obtener mas información podemos ver al proyecto al que pertenece. También dispone de una serie de plug-ins con los que podemos realizar búsquedas en los navegadores IE y Firefox. Hay otro plug-ins más para el lado oscuro Eclipse con el que podemos realizar búsquedas directamente desde el entorno.

 

 

 

 

 

 

blog counter

Uno de los grandes cambios de Windows XP a la versión de Windows Vista es el nuevo sistema gráfico que éste último incorpora, el cual ha sido denominado Aero. Se puede decir que Aero de divide en dos niveles, Aero Basic que ya incorpora una diferencia sustancial respecto a la interfaz gráfica de Windows XP y, Aero Glass (que no esta soportado por la versión “Home Basic”) proporciona una funcionalidad mayor como la aplicación de efectos de cristal a las ventanas o una gestión avanzada de las mismas, a parte de la experiencia visual que puede resultar más o menos gratificante dependiendo de los gustos particulares de cada uno.


Aero Glass hace que los bordes de nuestras ventanas hagan uso de transparencias para dar la sensación de cómo si estuviésemos viendo a través de un cristal, que tengamos vistas en miniatura de las aplicaciones que aparecen minimizadas en la barra de tareas o Windows Flip y Flip 3D. Esta funcionalidad queda expuesta a  través de DWM (Desktop Windows Management) que es quien se encarga de componer la apariencia que tiene el escritorio en cada momento.  Para poder ofrecer al usuario todo esto se hace uso directo de las aceleradoras gráficas, aquí es donde aparece el WDDM (Windows Display Diver Model), esta es la interfaz con la que DWM hace uso de los recursos de las tarjetas gráficas, así que si no disponemos de una gráfica más o menos reciente que soporte WDDM nos quedaremos sin "apreciar" todos estos efectos visuales. Se puede trabajar directamente con DWM pero es solo una posibilidad disponible para Vista, aun así, puesto que disponemos del Framework 3.0 en Windows XP y haciendo uso de WPF podemos simular este efecto de transparencia para los bordes de nuestras ventanas, aunque eso sí, no con la misma espectacularidad.


Vamos a trabar con Blend, así que creamos un nuevo proyecto de aplicación y listos para empezar con esto. Lo primero que tenemos que hacer es eliminar el típico marco de las aplicaciones de escritorio de XP. Para ello, en el objeto Window activamos la propiedad AllowsTransaprency, esto hará que automáticamente la propiedad WindowStyle cambie a None, a parte queremos darle a nuestra ventana el aspecto de borde redondeado con lo que tendremos que cambiar el valor de la propiedad Background a un color transparente así no aparecera un pico en las esquinas con el color de fondo de la ventana. El código quedaría en xaml del siguiente modo:


      AllowsTransparency="True"

      WindowStyle="None"

      Background="#00FFFFFF">


La ventana contiene un Grid en el que vamos a introducir un objeto de tipo Border que hará de marco de nuestra ventana y que pintaremos con un degradado. Para darle una apariencia más vistosa en realidad vamos a utilizar dos objetos border con un degradado diferente cada uno, de este modo el degradado no resultará tan "líneal", podemos usar los colores que más nos gusten y además tendremos que hacer uso de la componente alfa de los colores que formen parte del degradado para permitir ver lo que hay por detrás, es decir, darle transparencia al degradado. También modificaremos la propiedad CornerRadius para suavizar las esquinas:


  <Grid x:Name="LayoutRoot">

    <Border Margin="0,0,0,0" CornerRadius="5,5,5,5" ...>

      <Border.Background>

        <LinearGradientBrush>

           

        </LinearGradientBrush>

      </Border.Background>

    </Border>

    <Border Margin="0,0,0,0" CornerRadius="5,5,5,5" ...>

      <Border.Background>

        <LinearGradientBrush>

           

        </LinearGradientBrush>

      </Border.Background>

    </Border>

  </Grid>


El resultado es el siguiente:

 
 

 

Podemos observar como se ve lo que hay por detrás de lo que será nuestra ventana. El siguiente paso es definir el aérea cliente e incorporar un icono, el titulo de la ventana y los botones para poder minimizar, maximizar y cerrar. Se puede hacer de diferentes modos, pero lo haremos añadiendo un elemento de tipo DockPanel al que llamaremos rootDockPanel que a su vez contendrá un Grid (llamado areaClienteGrid) para definir el area de cliente de la ventana y otro DockPanel (llamado barraDockPanel) fijado en la parte superior (Top) del primero para hacer de contenedor del titulo de la ventana y los botones para minimizar, maximizar y cerrar. Un contenedor de tipo DockPanel coloca los elementos que contiene en la parte de arriba, abajo, izquierda o derecha. Los elementos se colocan según el orden que aparezcan en el código xaml pero también podemos definir de forma explícita la ubicación de los elementos. Este contenedor tiene una propiedad llamada LastChildFill mediante la que podemos determinar si el último elemento agregado a DockPanel ocupa todo el espacio que queda libre o no. Como dije antes, el contenerdor que alberga la barra de titulo lo fijaremos en la parte superior y al tener activada la propiedad LastChildFill el grid que representa el area de cliente ocupará el espacio restante disponible.

 

 

 

En la imagen superior está la jerarquía completa de todos los elementos que forman la ventana. El icono de la ventana y el titulo se encuentran dentro de un StackPanel (llamado tituloStackPanel) que esta alineado a la parte izquierda del elemento que lo contiene (barraDockPanel). El StackPanel es un contenedor que premite colocar los elementos que contiene uno seguido detrás de otro de forma tanto horizontal como vertical. Lo mismo haríamos con el StackPanel (llamado botonesStackPanel) que contiene los botones de minimizar, maximizar y cerrar. El resultado final es el siguiente:

 

 

 

Ahora solo nos queda añadir los eventos que nos permitan interactuar con la ventana. Para permitir que la ventana cambie de tamaño basta con establecer la propiedad ResizeMode a CanResizeWithGrip del objeto Window. Para añadir un evento basta con seleccionar el elemento al que queremos añadirle el evento y acceder a todos los eventos que tiene a través del panel de propiedades de Blend. Haciendo doble click en uno de los eventos automáticamente se lanza Visual Studio para añadir el código al evento. El código de todos los eventos es trivial, pero es interesante ver el código del evento que permite desplazar la ventana por el escritorio:


        private void borderTop_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)

        {

            object o = e.OriginalSource;

            if (o is Border)

                this.DragMove();

        }


WPF incorpora un nuevo modelo para la gestión de eventos que permite que la respuesta a un evento se propague a través de la jerarquía de controles. En el código de arriba lo que hacemos es controlar el origen del evento para ver si fue el borde quien produjo el evento MuseLeftButtonDown. Si no controlamos quien originó el evento y probamos a mover la ventana pinchando en el área blanca, el grid, como esta en la jerarquía de otro objeto que si controla ese mismo evento (el borde) la ventana se movería, pero ese no es el resultado que deseamos.


A parte de lo ya expuesto, hay otras propiedades con las que he jugado como margen, padding… para que los elementos tengan la apariencia y la ubicación que deseaba pero las más importantes han sido decritas. Adjunto el código para que le deis un vistazo a todo eso y juguéis con él, si os apetece.

 

 

Aqui teneis una serie de webcast que nos muestran paso a paso como hacer un sudoku trabajando con WPF. Es una serie de 5 webcast que, aunque en inglés, se pueden seguir. Además, antes o despues tenemos que aprender inglés, así que no viene nada mal empezar por unos webcast que pueden resultar bastante interesantes... Solo he visto una parte del primero pero ya tengo los 5 almacenados en mi disco duro para verlos en los ratos libres (que hay mucho que estudiar ahora... snif snif). Solo teneis que registraros en el evento y proceder a descargar los webcast en cuestión, aquí os paso los enlaces:

 MSDN Webcast: Building a Sudoku Game Using Windows Presentation Foundation (Part 1 of 5) (Level 100)

 MSDN Webcast: Building a Sudoku Game Using Windows Presentation Foundation (Part 2 of 5) (Level 100)

 MSDN Webcast: Building a Sudoku Game Using Windows Presentation Foundation (Part 3 of 5) (Level 100)

 MSDN Webcast: Building a Sudoku Game Using Windows Presentation Foundation (Part 4 of 5) (Level 100)

 MSDN Webcast: Building a Sudoku Game Using Windows Presentation Foundation (Part 5 of 5) (Level 100)

 

Que los disfruteis!! 

Más envíos Página siguiente >