Supone uno de los temas que, de cuando en cuando, asoma entre las dudas de quienes empiezan a utilizar Xojo para el desarrollo de sus aplicaciones, ya sean estas multiplataforma o no. El caso es que en múltiples tipos de aplicaciones llega el momento en el que un proceso ha de realizarse en una cantidad de tiempo que exige reflejar el avance mediante el uso de un control de interfaz de usuario, como puede ser una ProgressBar. ¿Cuál es el problema? El primero, nuestra app ha de continuar comportándose de forma ágil (responder al resto de intereacciones del usuario), y este tipo de procesos en cuestión han de realizarse en sus propios Threads (o hilos de ejecución) con el inconveniente de que, en Xojo, los threads no pueden acceder o actuar sobre cualquier tipo de elemento de interfaz gráfica, aunque sea empleando trucos como pueda ser su acceso indirecto mediante Delegados.
Como cualquier problema que se pueda plantear en programación, existen varios enfoques o modos de solucionar un mismo problema. En este caso, utilizaré para este ejemplo uno en el que no sólo se contempla la pulsación de un botón asignado con la función de Cancelar, de modo que su acción quede reflejada mediante el elemento del lenguaje userCancelled sino también mediante cualquier otro elemento de la interfaz de usuario, como pueda ser una opción de menú o (por qué no) quizá algún otro proceso ejecutándose en segundo plano.
¡Threads al rescate!
En el proyecto, que puedes descargar desde este enlace, verás que he creado una nueva clase basada en Thread y que se limita a incrementar mediante un bucle infinito un contador, establecido como una Propiedad pública. Por supuesto, esto es lo que ocurre en el único evento que podemos añadir a un Thread: Run. Pensemos que esta es la tarea de larga duración y cuyo valor será el utilizado para incrementar el elemento de interfaz de usuario (una barra de progreso).
Como respuesta a un elemento de Menú, y también a la acción de un botón, ejecutaremos el código encargado de crear una nueva instancia y ejecutar nuestra subclase (el proceso largo):
if miThread = nil then miThread = new MiClaseThread
miThread.Run
While miThread.State = Thread.Running
ProgressBar1.Value = miThread.valor
app.DoEvents
Wend
miThread = nil
Como puedes observar, la actualización del elemento de interfaz de usuario se realiza comprobando si la instancia aún continúa en funcionamiento (procesando datos), en cuyo caso se procede a actualizar la barra de progreso con el nuevo valor y devolver tiempo de procesador para el refresco de la interfaz de usuario y que esta continúe respondiendo a las interacciones del usuario. Para ello empleamos DoEvents. En el caso de que el proceso haya finalizado… simplemente hacemos explícito el aclarado de la propiedad que contiene la referencia al objeto, asignándole Nil.
Por otra parte, contamos con un segundo método y que es el ejecutado mediante el botón de cancelar y también la opción de menú encargada de detener nuestro largo proceso de ejecución. ¿Su código?, simple:
miThread.Kill
Es decir, matamos el proceso en cuestión. Dicha acción causará que el bucle principal detecte el cambio de estado y por tanto se salga del mismo.
Por supuesto, si bien aquí se presenta el Thread con una lógica tremendamente simple, este podría estar ejecutando cualquier tipo de cálculo o proceso real… siempre y cuando constante la evolución de su proceso mediante una propiedad a la cual podamos acceder desde fuera del hilo propiamente dicho.