PDA

Ver la Versión Completa : Error al copiar texto


Faust
26-08-2006, 08:03:59
Saludos compas del Club Delphi

Hace tiempo estaba desarrollando una aplicación que monitoreaba el portapapeles en busca de contenido apto para mi aplicación, pero al encontrar un error que jamás pude erradicar, me dí por vencido, ahora he retomado esta aplicación de nuevo, y e identifico más o menos por donde va el error, usé el truco 214 de Trucomania y un artículo que leí en la revista Síntesis no. 17 de Grupo Albor, lo que hago es que al crear el form registrarme para ver los mensajes del portapapeles;

Self.SiguienteHandle := SetClipboardViewer(Self.Handle)

al destruir el form, informar que salgo de la cadena de mensajes del portapapeles:

ChangeClipboardChain(Self.Handle, Self.SiguienteHandle);

al detectar un cambio en la cadena del portapapeles trato el mensaje:

procedure TForm1.WMChangeCBCHain(var ChangeCBCHainMessage: TMessage);
begin
if (ChangeCBCHainMessage.WParam = Self.SiguienteHandle) then
begin
SiguienteHandle := ChangeCBCHainMessage.LParam;
ChangeCBCHainMessage.Result:= 0
end
else
if (SiguienteHandle <> 0) then
ChangeCBCHainMessage.Result:= SendMessage(SiguienteHandle, WM_CHANGECBCHAIN, ChangeCBCHainMessage.WParam, ChangeCBCHainMessage.LParam);
end;

y al recibir el mensaje de que ha cambiado el portapapeles ejecuto lo siguiente

procedure TForm1.WMDrawClipboard(var DrawClipboardMessage: TMessage);
begin
DrawClipBoardMessage.Result:= SendMessage(SiguienteHandle, WM_DRAWCLIPBOARD, 0, 0);
if ClipBoard.HasFormat(CF_TEXT) then
begin
Memo1.Clear;
Memo1.Text:= ClipBoard.AsText //Aquí sucede el error
end
end;

Ahora algo importante: el error sucede aquí:
Memo1.Text:= ClipBoard.AsText //Aquí sucede el error
Solo cuando estoy ejecutando algún programa de Office, por ejemplo Excel y solamente cuando efectúo una operación de arrastre y lo raro es que algunas veces aparece el mensaje de error en Office y otras en mi aplicación, el mensaje de error de Office es: "No se puede vaciar el portapapeles"y el que aparece en mi aplicación es: "Cannot open clipboard", lo que me hace pensar que al efectuar una operación de arrastre del contenido de algún documento de Office sucede lo siguiente:

Office almacena el contenido previo del portapapeles a una variable temporal.
Office ocupa el portapapeles para realizar su operación de arrastre, esto desencadena el mensaje WM_DRAWCLIPBOARD, pero antes de hacer esto bloquea el acceso al portapapeles.
Al terminar la operación de arrastre Office restaura el contenido del portapapeles previo a la operación de arrastre y libera el portapapeles.Y es así como mi aplicación capta el mensaje WM_DRAWCLIPBOARD y trata de obtener acceso al portapapeles, pero al estar bloqueado por Office cae en el error "Cannot open clipboard", y cuando mi programa llega a obtener acceso al portapapeles, en Office ocurre el error "No se puede vaciar el contenido del portapapeles", creo que la solución a mi problema es saber cuando está bloqueado el acceso al portapapeles, si estoy en lo correcto, como se hace eso, si no por favor que alguien me ayude y tengo otra pequeña duda, en dónde, cómo y que debo asignar como valor al campo Result de los mensajes WM_CHANGECBCHAIN y WM_DRAWCLIPBOARD, según la ayuda de Windows se debe devolver cero despues de una operación exitosa con el mensaje.

El código de aquí es solo de prueba, una vez que funcione exitosamente lo implementaré en mi aplicación.

Bueno, después de alargarme un poco con la explicación de mi problema me despido, enviando un saludo y un abrazo amistoso a todos los delphimaniacos de Club Delphi y agradeciendo de una vez a todos aquellos que me puedan ayudar.

Gracias :)

Lepe
26-08-2006, 14:18:13
La filosofía es la siguiente:
Cualquier programa puede registrarse como Visor del portapapeles. Cuando el usuario copia algo en el portapapeles, windows mira quien es el primer programa "visor del portapapeles" (obviamente el programa de windows) y le pasa un mensaje indicando que el contenido del portapapeles ha cambiado. Ese programa, debe continuar la cadena, es decir, seguir informando al resto de programas que son visores del portapapeles del cambio surgido.

Por tanto tenemos que:

- Registrar nuestro programa para que capture cosas del Portapapeles automaticamente, y guardar quien es el siguiente programa "visor del portapapeles". Además debemos quitarnos de esa lista al cerrar nuestro programa:

var NextClipboard:Thandle; // variable global
procedure RegistrarVisor;
begin
NextClipboard := SetClipboardViewer(frmppal.Handle);
end;

procedure EliminarVisor;
begin
ChangeClipboardChain(FrmPpal.Handle, NextClipboard)
// aquí se elimina nuestro programa en la cadena de "visores del portapapeles" y se añade el que existía antes.
end;


- Obviamente necesitamos responder cuando cambie el portapapeles:

Tform1 = class(TForm)
private
procedure CambioPortapapeles(var Msg: TWMDrawClipboard); message wm_drawclipboard;
// cada vez que se dibuje algo en el portapapeles, que nos avise
end;
implementation

procedure TFrmPpal.CambioPortapapeles(var Msg: TWMDrawClipboard);
var
i: Integer;
found: Boolean;
str: String;

begin
found := False;
if (Clipboard.HasFormat(CF_TEXT)) then
begin
Str := clipboard.AsText; // ya tenemos el contenido nuevo

// ahora enviamo el cambio que ha habido en el portapapeles al
// siguiente programa visor del portapapeles.
SendMessage(NextClipboard, Msg.Msg, Msg.Msg, Msg.Msg);
end
end;


El error que yo veo, es que la linea

DrawClipBoardMessage.Result:= SendMessage(SiguienteHandle, WM_DRAWCLIPBOARD, 0, 0);
tienes que ponerlo al final de la rutina ¿por qué?, porque un programa "visor del portapapeles" puede cambiar el contenido del mismo, y si lo hace, tu línea
Memo1.Text:= ClipBoard.AsText
está desfasada con el contenido real del portapapeles.

Es más yo lo modificaba y solo ponía esto:

SendMessage(NextClipboard, Msg.Msg, Msg.Msg, Msg.Msg);

Tu procedimiento WMChangeCBCHain creo que es inconsistente, simplemente elimínalo.

Saludos

Faust
26-08-2006, 16:01:13
Gracias por tu respuesta... pero...

Tu procedimiento WMChangeCBChain creo que es inconsistente, simplemente elimínalo.

Pero si elimino el procedimiento WMChangeCBChain cómo sé si el handle al siguiente visor sigue siendo válido

dec
26-08-2006, 18:07:26
Hola,


Pero si elimino el procedimiento WMChangeCBChain cómo sé si el handle al siguiente visor sigue siendo válido


Según la ayuda del SDK de Win32 sobre la función "SetClipboardViewer":


If the function succeeds, the return value identifies the next window in the clipboard viewer chain. If an error occurs or there are no other windows in the clipboard viewer chain, the return value is NULL. To get extended error information, call GetLastError.


Es decir, por eso Lepe recoge el resultado de dicha función en la variable "global" "NextClipboard", y es esta misma variable la que se utiliza después en la función "ChangeClipboardChain".

Faust
28-08-2006, 06:25:05
Gracias por sus respuestas camaradas, aunque he usado la solución de Lepe han continuado los errores en Office, por lo que yo creo que la solución es en saber si algún otro programa ha bloqueado temporalmente el portapapeles, pues así antes de extraer el contenido del portapapeles puedo preguntar si está disponible, y evitar el error de Office.

De nuevo gracias por su ayuda y les mando un afectuoso saludo.

seoane
28-08-2006, 14:53:39
Te propongo una solución, no utilizar la unit clipbrd y copiar el contenido del portapapeles usando solo funciones de la API. Para copiar el texto podemos usar una función como esta:


function LeerTexto: string;
var
hText: THandle;
pText: PChar;
begin
Result:= EmptyStr;
if IsClipboardFormatAvailable(CF_TEXT) then
if OpenClipboard(0) then
try
hText:= GetClipboardData(CF_TEXT);
if hText <> 0 then
begin
pText:= GlobalLock(hText);
if pText <> nil then
begin
Result:= String(PChar(pText));
GlobalUnlock(hText);
end;
end;
finally
CloseClipboard;
end;
end;


La funcion anterior intentara copiar el texto del portapapeles, si no lo consigue devolvera una cadena vacia, pero no mostrara ningun error. Asi que podriamos utilizarla de la siguiente manera:


procedure TForm1.WMDrawClipboard(var Msg: TMessage);
var
Str: String;
begin
Str:= LeerTexto;
if Str <> EmptyStr then
memo1.Lines.Add(Str);
Msg.Result:= SendMessage(NextClipboard, Msg.Msg, Msg.wParam, Msg.LParam);
end;


¿Que te parece? por lo menos a mi ya no me sale ningún error al arrastrar en excel.

Lepe
28-08-2006, 14:58:16
Tengo un programa como he dicho, y me lee todo el contenido cada vez que se copia algo. Uso office 2002.

En casos de arrastrar y soltar, no me lee el portapapeles, ya no se "copia nada en esos momentos", incluso arrastrando desde excel a word y viceversa. Puede que un office de versión superior esté "haciendo virguerías".

Saludos

seoane
28-08-2006, 15:40:52
Lepe yo tampoco entiendo porque da ese error, así que monte el código tal como describíais y en el excel al arrastrar texto de una celda a otra me daba un error en mi aplicación. Con la función que puse ya no da errores mi aplicación, pero una de cada 3 veces (aproximadamente, no las conte ;) ) excel muestra el error "No se puede vaciar el portapapeles", así que volvemos a estar en la misma :o

Lepe
28-08-2006, 16:36:03
Pues yo tampoco sé que pasa.

Acabo de hacer la prueba como dices, seoane, y efectivamente si se copia texto en el portapapeles con el office 2002.

Mi programa hace uso del Microsoft Agent y habla por los altavoces (parlantes) el texto que se copia. acabo de escribir en una celda "quillo no me asustes que me da una flojera del copon" y moviendo la celda 15 veces consecutivas, le ha dado una flojera...:D

En serio, al menos en mi ordenador no puedo reproducir el error. Me funciona correctamente.

Ahora mismo no sé como tendrá el código nuestro compañero, yo al menos no toco el Result del TMessage para nada. Tengo el presentimiento de que si el siguiente "visor del portapapeles" no es válido, se está devolviendo false en ese parámetro lo cual "podría provocar" que excel mostrase ese error ... no sé...

Saludos

roman
28-08-2006, 18:23:32
Un comentario: en algún momento de este hilo, eliminaron al procedimiento WMChangeCBCHain. Esto no debe hacerse porque es fundamental para preservar el orden de la cadena. El valor de la siguiente ventana que se obtiene al usar SetClipboardViewer puede cambiar durante la vida de la aplicación, por ejemplo si el siguiente visor se sale de la cadena. Por ello es que hay que manejar WM_CHANGECBCHAIN, para detectar esos cambios.

// Saludos

seoane
28-08-2006, 20:44:10
Parece que este error no es la primera vez que aparece, según este articulo de microsoft el programa GetRight provocaba el mismo error si tenia activada la función de Monitorizar el portapapeles:

http://support.microsoft.com/default.aspx?scid=kb;en-us;196620

Para colmo, acabo de volver a probar con el mismo código de antes y ahora no consigo que aparezca el error y me canse de arrastrar celdas :D Parece mas un capricho del excel que un error por nuestra parte.

roman
28-08-2006, 20:50:20
Estos de Microsoft son increibles. Le echan la culpa al GetRight por montar un visor del portapapeles, siendo que éstos están documentados en el SDK, en lugar de aceptar que su excel está haciendo un uso incorrecto del clipboard.

// Saludos

Faust
30-08-2006, 15:10:04
Pues quien lo iba a decir, Microsoft es el creador de Windows y de Office, pero no parece, es como cuando uno activa la alarma de su propio coche...:eek:
el primero que cae es el dueño.

Faust
05-09-2006, 06:45:41
Efectivamente, me sirvió la solución de seoane, pero Excel continua haciendo de las suyas, pues ni modo, sino pues no hacer uso del monitor del portapapeles.

Gracias por su ayuda camaradas.