viernes, 26 de mayo de 2017

Cómo simular mensajes Toast en Android, iOS, Windows y OSX usando C++

Si vienes de programar en Java para Android seguramente buscarás alguna función similar en Delphi o C++ Builder. Sin embargo ésta función no existe de manera predeterminada. Lo que encontrarás son los típicos mensajes de pantalla al estilo de Windows como por ejemplo ShowMessage("..."); los mismos que requieren que el usuario los atiendan, para ello muestran uno o más botones (dependiendo del tipo de función utilizado para generar el mensaje) para que el usuario presione (clic) en algún botón para que así puedan desaparecer.

En cambio, los mensajes Toast de Android son muy buenos ya que muestran información corta al usuario y se desvanecen automáticamente, lo cual, en ciertas situaciones los hacen muy prácticos y de mucha utilidad.

Hoy veremos cómo hacer esto en C++ de manera muy fácil para que desde cualquier aplicación que estés desarrollando los puedas instanciar como a cualquier objeto y, con una línea de código podrás mandar mensajes tipo Toast a la pantalla del usuario.

Mira la imagen de la izquierda, el texto se auto ajusta al ancho y alto del mensaje enviado sin requerir mayor código. Inclusive si agregas algunas propiedades adicionales a este ejemplo podrías cambiar el fondo del mensaje, el borde, el color del texto así como el tipo de letra y otras de sus propiedades.


Además al utilizar C++/Delphi de embarcadero.com este código prácticamente funcionará en cualquier plataforma 😀😀😀



Empecemos...


¿Qué usaremos?

  • TFrame
  • TFloatAnimation
  • Anonymous Thread y 
  • Sincronizacón con el UI
  • Función Lambda
  • TPlatformServices
  • Algo de C++ 11 / Clang

Ahora agregamos los componentes y definiremos sus propiedades:

En la ventana "Object Inspector" podrán definir las propiedades.

1. Crear un nuevo proyecto multiplataforma C++
2. Agregar del "Tool Palette" el componente "Speedbutton"

  • Align: Center
  • StyleLookup: buttonstyle
  • Text: Show Toast!
3. En la ventana "Project Manager"
  • Clic derecho al Proyect1.exe
  • Add new -> Other...
  • En el folder "C++Builder Files
  • Seleccionar "FireMonkey Frame"
  • Clic en el botón [Ok]
4. En la ventana "Object Inspector"
  • Name: fToastMsg
  • Visible: false
5. Agregar del "Tool Palette" el componente "FloatAnimation"
  • PropertyName: Position.Y
  • Trigger: IsVisible=true
6. Agregar del "Tool Pallete" el componente "TRoundRect"
  • Align: Client
  • Fill: (a gusto del coder)
  • Stroke: (a gusto del coder)
7. Agregar del "Tool Pallete" el componente "TLabel"; dentro del componente anterior.
  • Align: Center
  • AutoSize: True
  • Name: lblWaitMsg
  • TextSettings->HorzAlign: Center
  • TextSettings->WordWrap: False
Te debe de quedar algo así:
c++ embarcadero


Ok. 

Ahora un poco de código C++

8. Agregar en Unit2.h, en la sección public la siguiente línea:
void __fastcall ShowToast(String pMsg);

Te debe de quedar algo así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class TfToastMsg : public TFrame
{
__published: // IDE-managed Components
 TFloatAnimation *FloatAnimation1;
 TRoundRect *RoundRect1;
 TLabel *lblWaitMsg;

private: // User declarations
public:  // User declarations
 __fastcall TfToastMsg(TComponent* Owner);
 void __fastcall ShowToast(String pMsg);
};

9. Agregar en Unit2.cpp, al final, final:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void __fastcall TfToastMsg::ShowToast(String pMsg)
{
 auto *xForm = ((TForm*)this->Owner);
 lblWaitMsg->Text = pMsg;
 if ( xForm->Width < lblWaitMsg->Width ) {
   lblWaitMsg->WordWrap = true;
   lblWaitMsg->Width = xForm->Width*0.8;
   this->Width = lblWaitMsg->Width+15;
   this->Height = lblWaitMsg->Height+8;
 } else {
  this->Width = lblWaitMsg->Width+15;
  this->Height = lblWaitMsg->Height+8;
 }

 this->Position->X = (xForm->Width/2) - (this->Width/2);

 int xDistFromBottom= 120;
 if (false) {
  FloatAnimation1->StartValue = 0;
  FloatAnimation1->StopValue = xDistFromBottom;
 } else {
  FloatAnimation1->StartValue = xForm->Height;
  FloatAnimation1->StopValue = xForm->Height-xDistFromBottom;
 }
 this->Visible=true;            //Starts animation.
 Application->ProcessMessages();         //pase a otros procesos
}

Quizás aquí puedas mejorar el código utilizando las propiedades Padding y Margin de los componentes, si es así me dejas saber :)

10. Presionar la tecla F12 o simplemente ir al modo "Diseño", seleccionar el componente "FloatAnimation1".

11. En la ventana "Object Inspector", pestaña "Events", dale doble clic a la derecha del evento "OnFinish"

12. Incluir el siguiente código de tal forma que quede así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
void __fastcall TfToastMsg::FloatAnimation1Finish(TObject *Sender)
{
 if(this->FloatAnimation1->Inverse) {
  this->FloatAnimation1->Inverse=false;
  this->Visible=false;
 } else {
  this->FloatAnimation1->Inverse=true;
  TThread::CreateAnonymousThread([this]() -> void {
   Sleep(1000); //Segundos que se muestra el mensaje al usuario
   TThread::Synchronize(TThread::CurrentThread, [this]() -> void {
    this->FloatAnimation1->Start();
   });
  })->Start();
 }
}

Ésta es la parte bella y mágica para dejar animar correctamente la desaparición del mensaje Toast mostrado al usuario, porque sin el soporte de la función Anónima, lambda y la sincronización con el UI, no sería posible la definición de este código de esta manera tan sencilla.

Al final, ese pedacito de código sólo permite que el mensaje se mantenga visible al usuario por un segundo (1s), para que NO desaparezca tan inmediatamente después de aparecer... inclusive podrían definir alguna propiedad para reemplazar la línea 9 "Sleep(1000)" por algo así como "Sleep(TimeUP)".

Analicen que dentro de la función Anónima, que es multi-thread, está definida la función lambda y dentro de esta lambda se define una función Sincronizar, con otra función lambda para actualizar correctamente el UI al iniciar la desaparición del mensaje Toast. En ambos casos en lugar de éstas funciones lambda se podrían usar funciones estándar del tipo "void __fastcall funct1(void);", pero por suerte con las lambda esto se simplifica enormemente. Yes, I 💖 lambda!

13. Hacer el proyecto compatible a Clang / C++11 :)
  • Ir al Menú Project->Options
  • Sección "C++ Compiler"
  • "Use 'classic' Borland compiler": False (Desmarcarlo)
  • Clic en [ Ok ]
14. Ahora vamos a Unit1.cpp

15. Agregar "#include "Unit2.h" debajo de "#include "Unit1.h" en "Unit1.cpp"

16. En modo diseño (F12), Doble clic al botón "Show Toast!" para crear el evento clic y agregaremos la siguiente línea de código:
(new TfToastMsg(this))->ShowToast("Hola Mundo!");
1
2
3
4
void __fastcall TForm1::SpeedButton1Click(TObject *Sender)
{
 (new TfToastMsg(this))->ShowToast("Hola Mundo!");
}

LISTO!!!

Presionamos F9 para correr la aplicación y obtendremos el mensaje Toast flotante...



Recuerden, este código correrá del mismo modo en Android, iOS, OSX y en Windows 10 👌👍

Más detalles del proyecto C++