Ir al contenido


Foto

Visor del ClipBoard


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

#1 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 06 noviembre 2014 - 05:57

Tras una pregunta en CD sobre la detección de la aparición de nuevo texto en el Clipboard, desarrolle un pequeño visor del Clipboard que puede servir de ejemplo para usarlo tal cual o de base para futuros desarrollos.

La técnica que he seguido para el primer ejemplo es compatible desde Windows 2000 en adelante y requiere tratar varios mensajes tras colocarnos en la lista de visores del Clipboard. El código está comentado para comprender bien como y porqué tratamos los mensajes.

El corazón es el mensaje WM_DRAWCLIPBOARD que recibiremos cuando se produzcan cambios en el Clipboard, tanto al cambiar éste como al entrar otro visor en la cola.

Código delphi:


delphi
  1. unit Unit1;
  2.  
  3. interface
  4.  
  5. uses
  6.   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  7.   Dialogs, StdCtrls, Clipbrd;
  8.  
  9. type
  10.   TForm1 = class(TForm)
  11.     Memo1: TMemo;
  12.     procedure FormCreate(Sender: TObject);
  13.   private
  14.     NextViewer: HWND;
  15.     procedure GetData;
  16.   protected
  17.     procedure WndProc(var Message: TMessage); override;
  18.   public
  19.     { Public declarations }
  20.   end;
  21.  
  22. var
  23.   Form1: TForm1;
  24.  
  25. implementation
  26.  
  27. {$R *.dfm}
  28. procedure TForm1.FormCreate(Sender: TObject);
  29. begin
  30.   //Clipboard.Clear;  // Podemos limpiar o no el clipboard al comenzar
  31.   // Nos registramos como ClipboardViewer y guardamos el anterior de la cadena
  32.   NextViewer:= SetClipboardViewer(Handle);
  33. end;
  34.  
  35.  
  36. // Procesamos los mensajes a bajo nivel
  37. procedure TForm1.WndProc(var Message: TMessage);
  38. begin
  39.   case Message.Msg of
  40.   WM_DRAWCLIPBOARD:
  41.   begin
  42.     // Si tenemos un visor en la cadena, le reenviamos el mensaje
  43.     if NextViewer <> 0 then
  44.       SendMessage(NextViewer, WM_DRAWCLIPBOARD, 0, 0);
  45.     // Miramos el ClipBoard
  46.     GetData();
  47.   end;
  48.  
  49.   WM_DESTROY:
  50.     // Notificamos que dejamos de ser visor del ClipBoard
  51.     ChangeClipboardChain(Handle, NextViewer);
  52.  
  53.   WM_CHANGECBCHAIN:
  54.     // Nos notifican que otra App deje de ser visor del ClipBoard
  55.     if HWND(Message.WParam) = NextViewer then
  56.       // Alctualizamos NextViewer
  57.       NextViewer:= Message.LParam
  58.     else if NextViewer <> 0 then
  59.       // Si no se actualizó reenviamos el mensaje
  60.       SendMessage(NextViewer, WM_CHANGECBCHAIN, Message.WParam, Message.LParam);
  61.  
  62.   end;
  63.   inherited WndProc(Message);
  64. end;
  65.  
  66. procedure TForm1.GetData;
  67. begin
  68.   if Clipboard.HasFormat(CF_TEXT) then
  69.     Memo1.Lines.Add(Clipboard.AsText);
  70. end;
  71.  
  72. end.



El equivalente en BuilderC++ sería el siguiente:


cpp
  1. //---------------------------------------------------------------------------
  2.  
  3. #ifndef Unit1H
  4. #define Unit1H
  5. //---------------------------------------------------------------------------
  6. #include <Classes.hpp>
  7. #include <Controls.hpp>
  8. #include <StdCtrls.hpp>
  9. #include <Forms.hpp>
  10. //---------------------------------------------------------------------------
  11. class TForm1 : public TForm
  12. {
  13. __published: // IDE-managed Components
  14.         TMemo *Memo1;
  15. private: // User declarations
  16.   void __fastcall WndProc(Messages::TMessage &Message);
  17.   void __fastcall GetData();
  18. public: // User declarations
  19.         __fastcall TForm1(TComponent* Owner);
  20. };
  21. //---------------------------------------------------------------------------
  22. extern PACKAGE TForm1 *Form1;
  23. //---------------------------------------------------------------------------
  24. #endif





cpp
  1. #include <vcl.h>
  2. #pragma hdrstop
  3.  
  4. #include "Unit1.h"
  5. #include <Clipbrd.hpp>
  6. //---------------------------------------------------------------------------
  7. #pragma package(smart_init)
  8. #pragma resource "*.dfm"
  9. TForm1 *Form1;
  10.  
  11. HWND  NextViewer;
  12. HWND  OldViewer;
  13.  
  14. //---------------------------------------------------------------------------
  15. __fastcall TForm1::TForm1(TComponent* Owner)
  16.         : TForm(Owner)
  17. {
  18.   //Clipboard()->Clear();  // Podemos limpiar o no el clipboard al comenzar
  19.   // Nos registramos como ClipboardViewer y guardamos el anterior de la cadena
  20.   NextViewer = SetClipboardViewer(Handle);
  21. }
  22.  
  23. //---------------------------------------------------------------------------
  24. // Procesamos los mensajes a bajo nivel
  25. void __fastcall TForm1::WndProc(Messages::TMessage &Message)
  26. {
  27.   switch (Message.Msg){
  28.     case WM_DRAWCLIPBOARD:
  29.       // Si tenemos un visor en la cadena, le reenviamos el mensaje
  30.       if(NextViewer!=0)
  31.         SendMessage(NextViewer, WM_DRAWCLIPBOARD, 0, 0);
  32.       GetData();
  33.       break;
  34.  
  35.     case WM_DESTROY:
  36.       // Notificamos que dejamos de ser visor del ClipBoard
  37.       ChangeClipboardChain(Handle, NextViewer);
  38.       break;
  39.  
  40.     case WM_CHANGECBCHAIN:
  41.       // Nos notifican que otra App deje de ser visor del ClipBoard
  42.       if((HWND)Message.WParam == NextViewer)
  43.         // Alctualizamos NextViewer
  44.         NextViewer = (HWND)Message.LParam;
  45.       else if(NextViewer != NULL)
  46.         // Si no se actualizó reenviamos el mensaje
  47.         SendMessage(NextViewer, WM_CHANGECBCHAIN, Message.WParam, Message.LParam);
  48.       break;
  49.   }
  50.  
  51.   TForm::WndProc(Message);
  52. }
  53.  
  54. //---------------------------------------------------------------------------
  55. void __fastcall TForm1::GetData()
  56. {
  57.   if(Clipboard()->HasFormat(CF_TEXT))
  58.     Memo1->Lines->Add(Clipboard()->AsText);
  59. }



En la siguiente entrada de este hilo explico como hacerlo más sencillo pero a partir de Windows Vista en adelante.


Saludos.
  • 0

#2 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 06 noviembre 2014 - 06:01

Ahora vamos a ver un código bastante más sencillo pero no compatible con WinXP. Nos serviría para Windows Vista en adelante.

Sólo usamos 2 APIs y un sólo mensaje, el WM_CLIPBOARDUPDATE que se nos enviará cada vez que aparezca un cambio en el ClipBoard.

El código delphi es el siguiente:


delphi
  1. unit Unit1;
  2.  
  3. interface
  4.  
  5. uses
  6.   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  7.   Dialogs, StdCtrls, Clipbrd;
  8.  
  9.   function AddClipboardFormatListener(hWindow: HWND): BOOL; stdcall; external  User32;
  10.   function RemoveClipboardFormatListener(hWindow: HWND): BOOL; stdcall; external  User32;
  11.  
  12.   const WM_CLIPBOARDUPDATE = $031D;
  13. type
  14.   TForm1 = class(TForm)
  15.     Memo1: TMemo;
  16.     procedure FormCreate(Sender: TObject);
  17.     procedure FormClose(Sender: TObject; var Action: TCloseAction);
  18.   private
  19.     procedure GetData;
  20.   protected
  21.     procedure WndProc(var Message: TMessage); override;
  22.   public
  23.     { Public declarations }
  24.   end;
  25.  
  26. var
  27.   Form1: TForm1;
  28.  
  29. implementation
  30.  
  31. {$R *.dfm}
  32.  
  33. procedure TForm1.FormCreate(Sender: TObject);
  34. begin
  35.   AddClipboardFormatListener(Handle);
  36. end;
  37.  
  38. procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
  39. begin
  40.   RemoveClipboardFormatListener(Handle);
  41. end;
  42.  
  43. procedure TForm1.GetData;
  44. begin
  45.   if Clipboard.HasFormat(CF_TEXT) then
  46.     Memo1.Lines.Add(Clipboard.AsText);
  47. end;
  48.  
  49. // Procesamos los mensajes a bajo nivel
  50. procedure TForm1.WndProc(var Message: TMessage);
  51. begin
  52.   if Message.Msg = WM_CLIPBOARDUPDATE then
  53.     GetData();
  54.  
  55.   inherited WndProc(Message);
  56. end;
  57.  
  58. end.



y el código BuilderC++:


cpp
  1. #ifndef Unit1H
  2. #define Unit1H
  3. //---------------------------------------------------------------------------
  4. #include <Classes.hpp>
  5. #include <Controls.hpp>
  6. #include <StdCtrls.hpp>
  7. #include <Forms.hpp>
  8. //---------------------------------------------------------------------------
  9. class TForm1 : public TForm
  10. {
  11. __published: // IDE-managed Components
  12.   TMemo *Memo1;
  13.   void __fastcall FormDestroy(TObject *Sender);
  14. private:
  15.   void __fastcall WndProc(Messages::TMessage &Message);
  16.   void __fastcall GetData();
  17. public: // User declarations
  18.   __fastcall TForm1(TComponent* Owner);
  19. };
  20. //---------------------------------------------------------------------------
  21. extern PACKAGE TForm1 *Form1;
  22. //---------------------------------------------------------------------------
  23. #endif





cpp
  1. #include <vcl.h>
  2. #include <Clipbrd.hpp>
  3. #pragma hdrstop
  4.  
  5. #include "Unit1.h"
  6.  
  7. //---------------------------------------------------------------------------
  8. #pragma package(smart_init)
  9. #pragma resource "*.dfm"
  10.  
  11.  
  12. #define WM_CLIPBOARDUPDATE              0x031D
  13. typedef bool (__stdcall *PAddClipboardFormatListener)(HWND hWnd);
  14. // "User32.dll", "AddClipboardFormatListener"
  15. typedef bool (__stdcall *PRemoveClipboardFormatListener)(HWND hWnd);
  16. // "User32.dll", "RemoveClipboardFormatListener"
  17.  
  18.  
  19.  
  20. TForm1 *Form1;
  21. //---------------------------------------------------------------------------
  22. __fastcall TForm1::TForm1(TComponent* Owner)
  23.         : TForm(Owner)
  24. {
  25.   PAddClipboardFormatListener _AddClipboardFormatListener =
  26.                       (PAddClipboardFormatListener)GetProcAddress(GetModuleHandle("User32.dll"), "AddClipboardFormatListener");
  27.   _AddClipboardFormatListener(Handle);
  28. }
  29. //---------------------------------------------------------------------------
  30.  
  31. void __fastcall TForm1::FormDestroy(TObject *Sender)
  32. {
  33.   PRemoveClipboardFormatListener _RemoveClipboardFormatListener =
  34.                       (PRemoveClipboardFormatListener)GetProcAddress(GetModuleHandle("User32.dll"), "RemoveClipboardFormatListener");
  35.   _RemoveClipboardFormatListener(Handle);
  36. }
  37.  
  38. //---------------------------------------------------------------------------
  39. // Procesamos los mensajes a bajo nivel
  40. void __fastcall TForm1::WndProc(Messages::TMessage &Message)
  41. {
  42.   if(Message.Msg == WM_CLIPBOARDUPDATE)
  43.     GetData();
  44.   TForm::WndProc(Message);
  45. }
  46.  
  47. //---------------------------------------------------------------------------
  48. void __fastcall TForm1::GetData()
  49. {
  50.   if(Clipboard()->HasFormat(CF_TEXT))
  51.     Memo1->Lines->Add(Clipboard()->AsText);
  52. }




Saludos.
  • 0

#3 seoane

seoane

    Advanced Member

  • Administrador
  • 1.259 mensajes
  • LocationEspaña

Escrito 07 noviembre 2014 - 06:31

:ap: No conocia esas dos funciones, me las apunto.

Aunque sugiero que para tratar con mensajes podemos usar las facilidades que da delphi


delphi
  1. const
  2.   WM_CLIPBOARDUPDATE = $031D;
  3.  
  4. type
  5.   TForm1 = class(TForm)
  6.     Memo1: TMemo;
  7.     procedure FormCreate(Sender: TObject);
  8.     procedure FormClose(Sender: TObject; var Action: TCloseAction);
  9.   private
  10.     { Private declarations }
  11.     procedure WMCLIPBOARDUPDATE(var Msg: TMessage); message WM_CLIPBOARDUPDATE;
  12.   public
  13.     { Public declarations }
  14.   end;
  15.  
  16. var
  17.   Form1: TForm1;
  18.  
  19. implementation
  20.  
  21. {$R *.dfm}
  22.  
  23. function AddClipboardFormatListener(hWindow: HWND): BOOL; stdcall; external  User32;
  24. function RemoveClipboardFormatListener(hWindow: HWND): BOOL; stdcall; external  User32;
  25.  
  26. procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
  27. begin
  28.   RemoveClipboardFormatListener(Handle);
  29. end;
  30.  
  31. procedure TForm1.FormCreate(Sender: TObject);
  32. begin
  33.   AddClipboardFormatListener(Handle);
  34. end;
  35.  
  36. procedure TForm1.WMCLIPBOARDUPDATE(var Msg: TMessage);
  37. begin
  38.   if Clipboard.HasFormat(CF_TEXT) then
  39.     Memo1.Lines.Add(Clipboard.AsText);
  40. end;


  • 0

#4 poliburro

poliburro

    Advanced Member

  • Administrador
  • 4.945 mensajes
  • LocationMéxico

Escrito 07 noviembre 2014 - 08:52

Muchas gracias amigo escafandra por el aporte. Vaya que dominas el tema de las APIs de windows.


  • 0

#5 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 07 noviembre 2014 - 09:12

...sugiero que para tratar con mensajes podemos usar las facilidades que da delphi...


Sabía que ahondarías en el tema y no te falta razón. Soy un clásico y aunque builderC++ tiene un sistema muy similar a delphi para tratar mensajes individuales, yo prefiero reescribir el método virtual WndProc y tratarlos en bloque, y a bajo nivel. Son manías mias.

Saludos.
  • 0

#6 poliburro

poliburro

    Advanced Member

  • Administrador
  • 4.945 mensajes
  • LocationMéxico

Escrito 07 noviembre 2014 - 10:07

:ap: No conocia esas dos funciones, me las apunto.

Aunque sugiero que para tratar con mensajes podemos usar las facilidades que da delphi



yo prefiero reescribir el método virtual WndProc y tratarlos en bloque, y a bajo nivel. Son manías mias.



Hola amigos, ¿sería mucho pedir si nos contaran los prosy  contras de hacerlo con lo que menciona seoane y como lo hace escafandra?

Yo conozco poco del manejo del API y creo que me sería sumamente enriquecedor saber cuál es la diferencia de hacerlo de una u otra manera.

Saludos.
  • 0

#7 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 07 noviembre 2014 - 12:51

Hola amigos, ¿sería mucho pedir si nos contaran los prosy  contras de hacerlo con lo que menciona seoane y como lo hace escafandra?

Yo conozco poco del manejo del API y creo que me sería sumamente enriquecedor saber cuál es la diferencia de hacerlo de una u otra manera.


Pues en realidad no hay mucha diferencia, salvo que la forma delphi aumenta el número de llamadas a procedimientos. A mi me gusta el estilo API porque estoy más acostumbrado a hacerlo en pregramas API puros y porque veo los mensajes de un sólo golpe de vista en ún sólo procedimiento o función (en C), si tengo que manejar varios mensajes al tiempo.  A veces también uso el estilo delphi, es cuestión de gustos, de estado de ánimo y velocidad de escritura.

Que cada cual que escoja la técnica que prefiera, ambas van a funcionar bien y la diferencia de velocidad de ejecución es despreciable.

Ya conocéis otra forma alternativa menos popular.  ;)


Saludos.
  • 0

#8 poliburro

poliburro

    Advanced Member

  • Administrador
  • 4.945 mensajes
  • LocationMéxico

Escrito 07 noviembre 2014 - 12:57


Que cada cual que escoja la técnica que prefiera, ambas van a funcionar bien y la diferencia de velocidad de ejecución es despreciable.

Ya conocéis otra forma alternativa menos popular.  ;)


Saludos.



Gracias por la info amigo.
  • 0

#9 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 07 noviembre 2014 - 05:46

Me acabo de dar cuente que en nuestro DA se había hecho una pregunta similar a la que originó este truco y que se hizo en CD, como comenté al inicio del mismo. Siento no haberme dado cuenta antes systemix, creo que con este truco, o lo que publiqué en CD, la pregunta está respondida.


Saludos.
  • 0




IP.Board spam blocked by CleanTalk.