Ir al contenido


Foto

Automatizar Cambios de Cursor


  • Por favor identifícate para responder
No hay respuestas en este tema

#1 Rolphy Reyes

Rolphy Reyes

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.092 mensajes
  • LocationRepública Dominicana

Escrito 25 febrero 2009 - 08:57

En casi cualquier libro o página sobre Delphi, encontrará un fragmento de código como el siguiente:



delphi
  1. Screen.Cursor := crHourglass;
  2. try
  3. // Operación larga
  4. finally
  5. Screen.Cursor := crDefault;
  6. end;



Si usamos este patrón literalmente, tendremos problemas: ¿qué sucederí­a si la operación de larga duración llamase, a su vez, a otra operación escrita en Delphi... que también cambiase la forma del cursor? Evidentemente, al terminar la operación anidada, la forma del cursor cambiarí­a al cursor por omisión, aunque a la operación externa le faltase algún tiempo todaví­a para terminar. No es difí­cil corregir el problema, modificando el patrón:



delphi
  1. var
  2. C: TCursor;
  3. begin
  4. C := Screen.Cursor;
  5. Screen.Cursor := crHourglass;
  6. try
  7. // Operación larga
  8. finally
  9. Screen.Cursor := C;
  10. end;
  11. end;



Lo malo es que ahora tenemos más instrucciones, y es fácil hartarse si hay que cambiar el cursor con cierta frecuencia. Aún peor es que la mayor parte del código lo consuma la instrucción try/finally, que no podemos encapsular; podemos, eso sí­, definir métodos SalvarCursor y RestaurarCursor, pero nos ahorrarí­an sólo una pequeña parte de lo que tenemos que teclear. Este caso pide a gritos una solución basada en la Programación Orientada a Aspectos (AOP)... que Delphi no soporta con naturalidad en su estado actual. En la AOP, estos problemas se resolverí­an
añadiendo atributos declarativos, como en COM+ y en los lenguajes .NET. En este caso particular, añadirí­amos un atributo LongDuration, o algo parecido, para marcar el método, en su declaración. Luego, en tiempo de ejecución, el entorno de ejecución deberí­a realizar la operación conocida con el nombre de aspect weaving (tejido, o entramado de aspectos) para que el atributo añadido tuviese algún efecto real.

En COM+, el aspecto weaving se implementa mediante intercepción de llamadas, interponiendo proxies especializados entre el objeto y sus clientes, en el momento en que se instancia el objeto. Y algo parecido podrí­a lograrse con un lenguaje .NET. A pesar de lo interesante que puede ser la AOP, entre otras cosas por su novedad, nos conformaremos con una solución mucho más simple, y casi tan efectiva. ¿Cuál es el problema que tenemos? Hay un patrón de código en el que se ejecutan ciertas instrucciones antes y después de una operación central. ¿Qué tal si aprovechamos un tipo de interfaz para ahorrarnos código? Vamos a crear una unidad, con dos funciones globales declaradas en su sección de interfaz:



delphi
  1. unit Cursores;
  2. interface
  3. uses Controls, Forms;
  4. function SaveCursor: IUnknown; overload;
  5. function SaveCursor(C: TCursor):
  6. IUnknown; overload;
  7. implementation
  8. // ...
  9. end.



Un adelanto: sólo tendremos que incluir una llamada a una de estas funciones antes de iniciar una operación lenta. Ahora vamos a declarar una clase dentro de la implementación de la unidad:



delphi
  1. type
  2. TSaveCursor = class(
  3. TInterfacedObject, IUnknown)
  4. private
  5. OldCursor: TCursor;
  6. constructor Create(C: TCursor);
  7. destructor Destroy; override;
  8. end;
  9. Esta serí­a la implementación de Create:
  10. constructor TSaveCursor.Create(C: TCursor);
  11. begin
  12. OldCursor := Screen.Cursor;
  13. Screen.Cursor := C;
  14. end;



Al crearse un objeto de la clase TSaveCursor, recordarí­amos la forma actual del cursor dentro de uno de sus campos, y cambiarí­amos el cursor de acuerdo al tipo de cursor pasado como parámetro al constructor. Esto se complementa con lo que sucede al destruir el objeto:



delphi
  1. destructor TSaveCursor.Destroy;
  2. begin
  3. Screen.Cursor := OldCursor;
  4. inherited Destroy;
  5. end;



Al destruir el objeto, por lo tanto, se restaura la forma inicial del cursor. Para terminar con la unidad, veamos lo que hacen las dos versiones sobrecargadas de la función global SaveCursor:



delphi
  1. function SaveCursor(C: TCursor): IUnknown;
  2. begin
  3. Result := TSaveCursor.Create(C);
  4. end;
  5. function SaveCursor: IUnknown;
  6. begin
  7. Result := SaveCursor(crHourglass);
  8. end;



Observe atentamente que ambos métodos crean un objeto de la clase SaveCursor... pero lo que devuelven al contexto que los llama es un puntero de interfaz, no el "puntero de clase" normal. Y todo este embrollo, ¿para qué nos sirve? El truco está en el tiempo de vida de los punteros de interfaz. Para verlo con más claridad, veamos cómo tendrí­amos que utilizar la función SaveCursor para cambiar el cursor dentro de un método que ejecuta una operación lenta:



delphi
  1. procedure OperacionLenta;
  2. begin
  3. SaveCursor;
  4. // ... instrucciones lentas ...
  5. end;



Cuando llamamos a SaveCursor, éste nos devuelve un puntero de interfaz instanciado... y cambia el cursor, por supuesto. Los objetos apuntados por tipos de interfaz se destruyen cuando no quedan referencias a él. En este caso ese momento llega al terminar el método. En ese momento se ejecutarí­a el destructor de TSaveCursor, y restaurarí­amos el cursor a su estado inicial. ¿Y qué pasa con las operaciones anidadas? Hagamos una prueba: ponga un botón sobre un formulario e intercepte su evento OnClick.



delphi
  1. begin
  2. SaveCursor(crHandPoint);
  3. Sleep(1000);
  4. SaveCursor;
  5. Sleep(1000);
  6. end;



Primero activamos el cursor de la mano que señala con un dedo, y esperamos un segundo. Luego, cambiamos al reloj de arena, y esperamos otro segundo. Visualmente, podemos comprobar que al terminar los dos segundos, el cursor vuelve a tomar su forma habitual. Y si traceamos la ejecución del evento, instrucción por instrucción, veremos que la restauración del cursor tiene lugar en el orden lógico que todos esperamos: primero se cambia el reloj de arena por la mano, e inmediatamente después, la mano se cambia por la flecha. Nos hemos ahorrado el try/finally, y unas cuantas lí­neas de código más...

NOTA: Esta información fue extraí­da de los boletines de Ian Marteens (su autor real).
  • 0




IP.Board spam blocked by CleanTalk.