Detectar cambios del portapapeles en una ventana FWH
Un ejemplo peque帽o para que una aplicaci贸n sepa cu谩ndo se copia algo al portapapeles (desde otro programa o desde la propia aplicaci贸n), sin consultarlo cada pocos milisegundos. Usa la API de Windows AddClipboardFormatListener y el mensaje WM_CLIPBOARDUPDATE (Windows Vista o superior).
Qu茅 hace
Registra la ventana como oyente del portapapeles con AddClipboardFormatListener. Si el registro falla, lo dice en pantalla y deshabilita los botones.
Una subclase m铆nima en C (dentro de #pragma BEGINDUMP) cuenta cada WM_CLIPBOARDUPDATE y pasa el resto de mensajes al procedimiento de ventana original.
Un TIMER de FWH lee ese contador cada 200 ms y a帽ade una l铆nea al listbox ("Evento #n hh:mm:ss").
Los botones copian cadenas ficticias con TClipBoard:SetText() y la etiqueta separa Intentos, Correctos (resultado de SetText) y Eventos (notificaciones recibidas).
Al cerrar, VALID para el timer, quita el oyente y restaura el procedimiento original.
Privacidad: el ejemplo no lee el contenido del portapapeles, solo cuenta notificaciones. Las 煤nicas copias que hace son las dos cadenas ficticias de los botones.
C贸digo
// clipwatch.prg - contador de cambios del portapapeles (AddClipboardFormatListener)
// No lee el contenido: solo cuenta las notificaciones WM_CLIPBOARDUPDATE.
#include "FiveWin.ch"
static nSeen := 0, nTries := 0, nOk := 0
function Main()
local oWnd, oLbx, oSay, oBtn1, oBtn2, oTmr, cSel := ""
DEFINE WINDOW oWnd TITLE "Escuchar el portapapeles (demo)" FROM 0, 0 TO 420, 600 PIXEL
@ 10, 10 LISTBOX oLbx VAR cSel ITEMS {} SIZE 570, 250 PIXEL OF oWnd
@ 270, 10 SAY oSay PROMPT "Intentos: 0 Correctos: 0 Eventos: 0" SIZE 570, 20 PIXEL OF oWnd
@ 300, 10 BUTTON oBtn1 PROMPT "Copiar texto ficticio 1" SIZE 200, 28 PIXEL OF oWnd ;
ACTION DoCopy( oWnd, "Texto ficticio 1" )
@ 300, 230 BUTTON oBtn2 PROMPT "Copiar texto ficticio 2" SIZE 200, 28 PIXEL OF oWnd ;
ACTION DoCopy( oWnd, "Texto ficticio 2" )
DEFINE TIMER oTmr INTERVAL 200 OF oWnd ACTION CheckClip( oLbx, oSay )
ACTIVATE WINDOW oWnd ;
ON INIT If( ClipListen( oWnd:hWnd ), oTmr:Activate(), ;
( oBtn1:Disable(), oBtn2:Disable(), ;
oSay:SetText( "No se pudo registrar el oyente del portapapeles" ) ) ) ;
VALID ( oTmr:DeActivate(), ClipUnlisten( oWnd:hWnd ), .T. )
return nil
// Una sola operacion: TClipBoard:SetText() abre, vacia, escribe y cierra, y devuelve .T./.F.
static function DoCopy( oWnd, cText )
local oClp, lOk
DEFINE CLIPBOARD oClp OF oWnd FORMAT TEXT
lOk := oClp:SetText( cText )
nTries++
if lOk
nOk++
endif
return lOk
static function CheckClip( oLbx, oSay )
local n := ClipCount()
do while nSeen < n
nSeen++
oLbx:Add( "Evento #" + ltrim( str( nSeen ) ) + " " + time() )
enddo
oSay:SetText( "Intentos: " + ltrim( str( nTries ) ) + " Correctos: " + ltrim( str( nOk ) ) + " Eventos: " + ltrim( str( nSeen ) ) )
return nil
#pragma BEGINDUMP
#define _WIN32_WINNT 0x0600
#include <windows.h>
#include <hbapi.h>
static WNDPROC s_old = NULL;
static HWND s_hwnd = NULL;
static volatile LONG s_count = 0;
static LRESULT CALLBACK ClipProc( HWND h, UINT m, WPARAM w, LPARAM l )
{
if( m == WM_CLIPBOARDUPDATE )
InterlockedIncrement( &s_count );
else if( m == WM_NCDESTROY && s_old )
{
WNDPROC o = s_old;
RemoveClipboardFormatListener( h );
SetWindowLongPtr( h, GWLP_WNDPROC, ( LONG_PTR ) o );
s_old = NULL; s_hwnd = NULL;
return CallWindowProc( o, h, m, w, l );
}
return CallWindowProc( s_old, h, m, w, l );
}
HB_FUNC( CLIPLISTEN )
{
HWND h = ( HWND )( ULONG_PTR ) hb_parnint( 1 );
if( s_old || ! IsWindow( h ) ) { hb_retl( 0 ); return; }
s_count = 0;
s_old = ( WNDPROC ) SetWindowLongPtr( h, GWLP_WNDPROC, ( LONG_PTR ) ClipProc );
s_hwnd = h;
hb_retl( s_old && AddClipboardFormatListener( h ) );
}
HB_FUNC( CLIPUNLISTEN )
{
HWND h = ( HWND )( ULONG_PTR ) hb_parnint( 1 );
if( s_old && h == s_hwnd )
{
RemoveClipboardFormatListener( h );
SetWindowLongPtr( h, GWLP_WNDPROC, ( LONG_PTR ) s_old );
s_old = NULL; s_hwnd = NULL;
}
hb_retl( 1 );
}
HB_FUNC( CLIPCOUNT ) { hb_retnl( ( long ) s_count ); }
#pragma ENDDUMPResultado de la prueba
馃搸 clipwatch_v6_run.pngCon 5 clics reales sobre los botones, la etiqueta marca Intentos: 5 Correctos: 5 Eventos: 5. En la versi贸n anterior, los 5 clics dieron 10 eventos. Son los recuentos observados en estas dos pruebas, no una garant铆a de un evento por copia. El ejemplo cuenta notificaciones de cambio, no operaciones del usuario: otros programas tambi茅n pueden modificar el portapapeles.
C贸mo se prob贸, y qu茅 no
Harbour 3.2.1dev, FWH 26.10, llvm-mingw, ejecutado en Wine 11.19. La captura es de ese entorno.
Compila y enlaza sin errores ni avisos, no genera error.log y se cierra con Alt+F4.
El aviso de fallo del oyente se comprob贸 forzando el error en una variante instrumentada: aparece el mensaje y los botones quedan deshabilitados.
La limpieza del cierre (quitar oyente y restaurar el procedimiento de ventana) se comprob贸 con trazas en una versi贸n anterior con el mismo c贸digo C; en esta versi贸n solo se comprob贸 que el proceso termina.
No probado: Windows nativo, cierre sin pasar por VALID, otros modos de cierre.
Notas
La subclase est谩 pensada para una sola ventana por proceso (demo).
Con @ ... LISTBOX hay que indicar la variable: @ r,c LISTBOX oLbx VAR cSel ITEMS .... Sin VAR, oLbx no recibe el objeto.
Referencias de la API: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-addclipboardformatlistener y https://learn.microsoft.com/en-us/windows/win32/dataxchg/wm-clipboardupdate (el c贸digo es propio, no copiado de ellas).
Si lo prob谩is en Windows nativo, contadnos qu茅 resultados obten茅is.
FiveTech AI